@samitouri / QOS-React-2 / commits / 75897c2dcd
Fork 0

The library for web and native user interfaces.

Initial public release

Paul O’Shannessy committed May 29, 2013 at 12:46 UTC 75897c2dcd1dd3a6ca46284dd37e13d22b4b16b4
317 files changed +43123
.editorconfig new
+12
@@ -0,0 +1,12 @@
1 +# http://editorconfig.org
2 +root = true
3 +
4 +[*]
5 +indent_style = space
6 +indent_size = 2
7 +end_of_line = lf
8 +charset = utf-8
9 +trim_trailing_whitespace = true
10 +
11 +[*.md]
12 +trim_trailing_whitespace = false
.gitignore new
+21
@@ -0,0 +1,21 @@
1 +.DS_STORE
2 +node_modules
3 +*~
4 +*.pyc
5 +static
6 +.grunt
7 +_SpecRunner.html
8 +build/
9 +*.gem
10 +docs/code
11 +docs/_site
12 +docs/.sass-cache
13 +docs/css/react.css
14 +docs/js/JSXTransformer.js
15 +docs/js/react.min.js
16 +docs/js/docs.js
17 +docs/js/live_editor.js
18 +docs/js/examples
19 +docs/downloads
20 +examples/shared/*.js
21 +
.jshintrc new
+20
@@ -0,0 +1,20 @@
1 +{
2 + "node": true,
3 +
4 + "boss": true,
5 + "curly": true,
6 + "devel": true,
7 + "eqnull": true,
8 + "expr": true,
9 + "funcscope": true,
10 + "globalstrict": true,
11 + "loopfunc": true,
12 + "newcap": false,
13 + "noempty": true,
14 + "nonstandard": true,
15 + "onecase": true,
16 + "regexdash": true,
17 + "trailing": true,
18 + "undef": true,
19 + "unused": "vars"
20 +}
CONTRIBUTING.md new
+63
@@ -0,0 +1,63 @@
1 +# Contributing to React
2 +
3 +React is one of Facebook's first open source projects that is both under very active development and is also being used to ship code to everybody on facebook.com. We're still working out the kinks to make contributing to this project as easy and transparent as possible, but we're not quite there yet. Hopefully this document makes the process for contributing clear and preempts some questions you may have.
4 +
5 +## Our Development Process
6 +
7 +Some of the core team will be working directly on GitHub. These changes will be public from the beginning. Other changesets will come via a bridge with Facebook's internal source control. This is a necessity as it allows engineers at Facebook outside of the core team to move fast and contribute from an environment they are comfortable in.
8 +
9 +### `master` is unsafe
10 +
11 +We will do our best to keep `master` in good shape, with tests passing at all times. But in order to move fast, we will make API changes that your application might not be compatible with. We will do our best to communicate these changes and always version appropriately so you can lock into a specific version if need be.
12 +
13 +### Pull Requests
14 +
15 +The core team will be monitoring for pull requests. When we get one, we will pull it in an apply it to Facebook's codebase and run our test suite to ensure nothing breaks. From here, we'll need to get another person to sign off on the changes. For API changes we may need to fix internal uses, which could cause some delay. We'll do our best to provide updates and feedback throughout the process.
16 +
17 +*Before* submitting a pull request, please make sure the following is done…
18 +
19 +1. Fork the repo and create your branch from `master`.
20 +2. If you've added code that should be tested, add tests!
21 +3. If you've changed APIs, update the documentation.
22 +4. Ensure the test suite passes (`grunt test`).
23 +5. Make sure your code lints (`grunt lint`) - we've done our best to make sure these rules match our internal linting guidelines.
24 +6. If you haven't already, complete the CLA.
25 +
26 +### Contributor License Agreement ("CLA")
27 +
28 +In order to accept your pull request, we need you to submit a CLA. You only need to do this once, so if you've done this for another Facebook open source project, you're good to go. If you are submitting a pull request for the first time, just let us know that you have completed the CLA and we can cross-check with your GitHub username.
29 +
30 +Complete your CLA here: <https://developers.facebook.com/opensource/cla>
31 +
32 +## Bugs
33 +
34 +### Where to Find Known Issues
35 +
36 +We will be using GitHub Issues for our public bugs. We will keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn't already exist.
37 +
38 +### Reporting New Issues
39 +
40 +The best way to get your bug fixed is to provide a reduced test case. jsFiddle, jsBin, and other sites provide a way to give live examples. Those are especially helpful though may not work for `JSX`-based code.
41 +
42 +### Security Bugs
43 +
44 +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. With that in mind, please do not file public issues and go through the process outlined on that page.
45 +
46 +## How to Get in Touch
47 +
48 +* IRC - [#reactjs on freenode](http://webchat.freenode.net/?channels=reactjs)
49 +* Mailing list - [reactjs on Google Groups](http://groups.google.com/group/reactjs)
50 +
51 +## Coding Style
52 +
53 +* Use semicolons;
54 +* Commas last,
55 +* 2 spaces for indentation (no tabs)
56 +* Prefer `'` over `"`
57 +* `"use strict";`
58 +* 80 character line length
59 +* "Attractive"
60 +
61 +## License
62 +
63 +By contributing to React, you agree that your contributions will be licensed under the [Apache License Version 2.0 (APLv2)](LICENSE).
Gruntfile.js new
+99
@@ -0,0 +1,99 @@
1 +'use strict';
2 +
3 +var exec = require('child_process').exec;
4 +var jsxTask = require('./grunt/tasks/jsx');
5 +var browserifyTask = require('./grunt/tasks/browserify');
6 +var wrapupTask = require('./grunt/tasks/wrapup');
7 +var phantomTask = require('./grunt/tasks/phantom');
8 +var releaseTasks = require('./grunt/tasks/release');
9 +
10 +module.exports = function(grunt) {
11 +
12 + grunt.initConfig({
13 + pkg: grunt.file.readJSON('package.json'),
14 + copy: require('./grunt/config/copy'),
15 + jsx: require('./grunt/config/jsx/jsx'),
16 + browserify: require('./grunt/config/browserify'),
17 + wrapup: require('./grunt/config/wrapup'),
18 + phantom: require('./grunt/config/phantom'),
19 + clean: ['./build', './*.gem', './docs/_site', './examples/shared/*.js'],
20 + jshint: require('./grunt/config/jshint'),
21 + compare_size: require('./grunt/config/compare_size')
22 + });
23 +
24 + grunt.config.set('compress', require('./grunt/config/compress'));
25 +
26 + grunt.loadNpmTasks('grunt-contrib-jshint');
27 + grunt.loadNpmTasks('grunt-contrib-copy');
28 + grunt.loadNpmTasks('grunt-contrib-clean');
29 + grunt.loadNpmTasks('grunt-compare-size');
30 + grunt.loadNpmTasks('grunt-contrib-compress');
31 +
32 + // Alias 'jshint' to 'lint' to better match the workflow we know
33 + grunt.registerTask('lint', ['jshint']);
34 +
35 + // Register jsx:debug and :release tasks.
36 + grunt.registerMultiTask('jsx', jsxTask);
37 +
38 + // Our own browserify-based tasks to build a single JS file build
39 + grunt.registerMultiTask('browserify', browserifyTask);
40 +
41 + // Similar to Browserify, use WrapUp to generate single JS file that
42 + // defines global variables instead of using require.
43 + grunt.registerMultiTask('wrapup', wrapupTask);
44 +
45 + grunt.registerMultiTask('phantom', phantomTask);
46 +
47 + grunt.registerTask('build:basic', ['jsx:debug', 'browserify:basic']);
48 + grunt.registerTask('build:transformer', ['jsx:debug', 'browserify:transformer']);
49 + grunt.registerTask('build:min', ['jsx:release', 'browserify:min']);
50 + grunt.registerTask('build:test', [
51 + 'jsx:debug',
52 + 'jsx:test',
53 + 'browserify:test'
54 + ]);
55 +
56 + grunt.registerTask('test', ['build:test', 'phantom:run']);
57 +
58 + // Optimized build task that does all of our builds. The subtasks will be run
59 + // in order so we can take advantage of that and only run jsx:debug once.
60 + grunt.registerTask('build', [
61 + 'jsx:debug',
62 + 'browserify:basic',
63 + 'browserify:transformer',
64 + 'jsx:release',
65 + 'browserify:min',
66 + 'copy:react_docs',
67 + 'compare_size'
68 + ]);
69 +
70 + // Automate the release!
71 + grunt.registerTask('release:setup', releaseTasks.setup);
72 + grunt.registerTask('release:bower', releaseTasks.bower);
73 + grunt.registerTask('release:docs', releaseTasks.docs);
74 + grunt.registerTask('release:msg', releaseTasks.msg);
75 + grunt.registerTask('release:starter', releaseTasks.starter);
76 +
77 + grunt.registerTask('release', [
78 + 'release:setup',
79 + 'clean',
80 + 'build',
81 + 'gem:only',
82 + 'release:bower',
83 + 'release:starter',
84 + 'compress',
85 + 'release:docs',
86 + 'release:msg'
87 + ]);
88 +
89 + // `gem` task to build the react-source gem
90 + grunt.registerTask('gem', ['build', 'gem:only']);
91 +
92 + grunt.registerTask('gem:only', function() {
93 + var done = this.async();
94 + exec('gem build react-source.gemspec', done);
95 + });
96 +
97 + // The default task - build - to keep setup easy
98 + grunt.registerTask('default', ['build']);
99 +};
LICENSE new
+201
@@ -0,0 +1,201 @@
1 + Apache License
2 + Version 2.0, January 2004
3 + http://www.apache.org/licenses/
4 +
5 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 +
7 + 1. Definitions.
8 +
9 + "License" shall mean the terms and conditions for use, reproduction,
10 + and distribution as defined by Sections 1 through 9 of this document.
11 +
12 + "Licensor" shall mean the copyright owner or entity authorized by
13 + the copyright owner that is granting the License.
14 +
15 + "Legal Entity" shall mean the union of the acting entity and all
16 + other entities that control, are controlled by, or are under common
17 + control with that entity. For the purposes of this definition,
18 + "control" means (i) the power, direct or indirect, to cause the
19 + direction or management of such entity, whether by contract or
20 + otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 + outstanding shares, or (iii) beneficial ownership of such entity.
22 +
23 + "You" (or "Your") shall mean an individual or Legal Entity
24 + exercising permissions granted by this License.
25 +
26 + "Source" form shall mean the preferred form for making modifications,
27 + including but not limited to software source code, documentation
28 + source, and configuration files.
29 +
30 + "Object" form shall mean any form resulting from mechanical
31 + transformation or translation of a Source form, including but
32 + not limited to compiled object code, generated documentation,
33 + and conversions to other media types.
34 +
35 + "Work" shall mean the work of authorship, whether in Source or
36 + Object form, made available under the License, as indicated by a
37 + copyright notice that is included in or attached to the work
38 + (an example is provided in the Appendix below).
39 +
40 + "Derivative Works" shall mean any work, whether in Source or Object
41 + form, that is based on (or derived from) the Work and for which the
42 + editorial revisions, annotations, elaborations, or other modifications
43 + represent, as a whole, an original work of authorship. For the purposes
44 + of this License, Derivative Works shall not include works that remain
45 + separable from, or merely link (or bind by name) to the interfaces of,
46 + the Work and Derivative Works thereof.
47 +
48 + "Contribution" shall mean any work of authorship, including
49 + the original version of the Work and any modifications or additions
50 + to that Work or Derivative Works thereof, that is intentionally
51 + submitted to Licensor for inclusion in the Work by the copyright owner
52 + or by an individual or Legal Entity authorized to submit on behalf of
53 + the copyright owner. For the purposes of this definition, "submitted"
54 + means any form of electronic, verbal, or written communication sent
55 + to the Licensor or its representatives, including but not limited to
56 + communication on electronic mailing lists, source code control systems,
57 + and issue tracking systems that are managed by, or on behalf of, the
58 + Licensor for the purpose of discussing and improving the Work, but
59 + excluding communication that is conspicuously marked or otherwise
60 + designated in writing by the copyright owner as "Not a Contribution."
61 +
62 + "Contributor" shall mean Licensor and any individual or Legal Entity
63 + on behalf of whom a Contribution has been received by Licensor and
64 + subsequently incorporated within the Work.
65 +
66 + 2. Grant of Copyright License. Subject to the terms and conditions of
67 + this License, each Contributor hereby grants to You a perpetual,
68 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 + copyright license to reproduce, prepare Derivative Works of,
70 + publicly display, publicly perform, sublicense, and distribute the
71 + Work and such Derivative Works in Source or Object form.
72 +
73 + 3. Grant of Patent License. Subject to the terms and conditions of
74 + this License, each Contributor hereby grants to You a perpetual,
75 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 + (except as stated in this section) patent license to make, have made,
77 + use, offer to sell, sell, import, and otherwise transfer the Work,
78 + where such license applies only to those patent claims licensable
79 + by such Contributor that are necessarily infringed by their
80 + Contribution(s) alone or by combination of their Contribution(s)
81 + with the Work to which such Contribution(s) was submitted. If You
82 + institute patent litigation against any entity (including a
83 + cross-claim or counterclaim in a lawsuit) alleging that the Work
84 + or a Contribution incorporated within the Work constitutes direct
85 + or contributory patent infringement, then any patent licenses
86 + granted to You under this License for that Work shall terminate
87 + as of the date such litigation is filed.
88 +
89 + 4. Redistribution. You may reproduce and distribute copies of the
90 + Work or Derivative Works thereof in any medium, with or without
91 + modifications, and in Source or Object form, provided that You
92 + meet the following conditions:
93 +
94 + (a) You must give any other recipients of the Work or
95 + Derivative Works a copy of this License; and
96 +
97 + (b) You must cause any modified files to carry prominent notices
98 + stating that You changed the files; and
99 +
100 + (c) You must retain, in the Source form of any Derivative Works
101 + that You distribute, all copyright, patent, trademark, and
102 + attribution notices from the Source form of the Work,
103 + excluding those notices that do not pertain to any part of
104 + the Derivative Works; and
105 +
106 + (d) If the Work includes a "NOTICE" text file as part of its
107 + distribution, then any Derivative Works that You distribute must
108 + include a readable copy of the attribution notices contained
109 + within such NOTICE file, excluding those notices that do not
110 + pertain to any part of the Derivative Works, in at least one
111 + of the following places: within a NOTICE text file distributed
112 + as part of the Derivative Works; within the Source form or
113 + documentation, if provided along with the Derivative Works; or,
114 + within a display generated by the Derivative Works, if and
115 + wherever such third-party notices normally appear. The contents
116 + of the NOTICE file are for informational purposes only and
117 + do not modify the License. You may add Your own attribution
118 + notices within Derivative Works that You distribute, alongside
119 + or as an addendum to the NOTICE text from the Work, provided
120 + that such additional attribution notices cannot be construed
121 + as modifying the License.
122 +
123 + You may add Your own copyright statement to Your modifications and
124 + may provide additional or different license terms and conditions
125 + for use, reproduction, or distribution of Your modifications, or
126 + for any such Derivative Works as a whole, provided Your use,
127 + reproduction, and distribution of the Work otherwise complies with
128 + the conditions stated in this License.
129 +
130 + 5. Submission of Contributions. Unless You explicitly state otherwise,
131 + any Contribution intentionally submitted for inclusion in the Work
132 + by You to the Licensor shall be under the terms and conditions of
133 + this License, without any additional terms or conditions.
134 + Notwithstanding the above, nothing herein shall supersede or modify
135 + the terms of any separate license agreement you may have executed
136 + with Licensor regarding such Contributions.
137 +
138 + 6. Trademarks. This License does not grant permission to use the trade
139 + names, trademarks, service marks, or product names of the Licensor,
140 + except as required for reasonable and customary use in describing the
141 + origin of the Work and reproducing the content of the NOTICE file.
142 +
143 + 7. Disclaimer of Warranty. Unless required by applicable law or
144 + agreed to in writing, Licensor provides the Work (and each
145 + Contributor provides its Contributions) on an "AS IS" BASIS,
146 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 + implied, including, without limitation, any warranties or conditions
148 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 + PARTICULAR PURPOSE. You are solely responsible for determining the
150 + appropriateness of using or redistributing the Work and assume any
151 + risks associated with Your exercise of permissions under this License.
152 +
153 + 8. Limitation of Liability. In no event and under no legal theory,
154 + whether in tort (including negligence), contract, or otherwise,
155 + unless required by applicable law (such as deliberate and grossly
156 + negligent acts) or agreed to in writing, shall any Contributor be
157 + liable to You for damages, including any direct, indirect, special,
158 + incidental, or consequential damages of any character arising as a
159 + result of this License or out of the use or inability to use the
160 + Work (including but not limited to damages for loss of goodwill,
161 + work stoppage, computer failure or malfunction, or any and all
162 + other commercial damages or losses), even if such Contributor
163 + has been advised of the possibility of such damages.
164 +
165 + 9. Accepting Warranty or Additional Liability. While redistributing
166 + the Work or Derivative Works thereof, You may choose to offer,
167 + and charge a fee for, acceptance of support, warranty, indemnity,
168 + or other liability obligations and/or rights consistent with this
169 + License. However, in accepting such obligations, You may act only
170 + on Your own behalf and on Your sole responsibility, not on behalf
171 + of any other Contributor, and only if You agree to indemnify,
172 + defend, and hold each Contributor harmless for any liability
173 + incurred by, or claims asserted against, such Contributor by reason
174 + of your accepting any such warranty or additional liability.
175 +
176 + END OF TERMS AND CONDITIONS
177 +
178 + APPENDIX: How to apply the Apache License to your work.
179 +
180 + To apply the Apache License to your work, attach the following
181 + boilerplate notice, with the fields enclosed by brackets "[]"
182 + replaced with your own identifying information. (Don't include
183 + the brackets!) The text should be enclosed in the appropriate
184 + comment syntax for the file format. We also recommend that a
185 + file or class name and description of purpose be included on the
186 + same "printed page" as the copyright notice for easier
187 + identification within third-party archives.
188 +
189 + Copyright [yyyy] [name of copyright owner]
190 +
191 + Licensed under the Apache License, Version 2.0 (the "License");
192 + you may not use this file except in compliance with the License.
193 + You may obtain a copy of the License at
194 +
195 + http://www.apache.org/licenses/LICENSE-2.0
196 +
197 + Unless required by applicable law or agreed to in writing, software
198 + distributed under the License is distributed on an "AS IS" BASIS,
199 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 + See the License for the specific language governing permissions and
201 + limitations under the License.
README.md new
+96
@@ -0,0 +1,96 @@
1 +# [React](http://facebook.github.io/react)
2 +
3 +React is a JavaScript library for building user interfaces.
4 +
5 +* **Declarative:** React uses a declarative paradigm that makes it easier to reason about your application.
6 +* **Efficient:** React minimizes interactions with the DOM by using a mock representation of the DOM.
7 +* **Flexible:** React works with the libraries and frameworks that you already know.
8 +
9 +[Learn how to use React in your own project.](http://facebook.github.io/docs/getting-started.html)
10 +
11 +## Examples
12 +
13 +We have several examples [on the website](http://facebook.github.io/react). Here is the first one to get you started:
14 +
15 +```js
16 +/** @jsx React.DOM */
17 +var HelloMessage = React.createClass({
18 + render: function() {
19 + return <div>{'Hello ' + this.props.name}</div>;
20 + }
21 +});
22 +
23 +React.renderComponent(
24 + <HelloMessage name="John" />,
25 + document.getElementById('container')
26 +);
27 +```
28 +
29 +This example will render "Hello John" into a container on the page.
30 +
31 +You'll notice that we used an XML-like syntax; [we call it JSX](http://facebook.github.io/docs/syntax.html). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML. A simple transform is included with React that allows converting JSX into native JavaScript for browsers to digest.
32 +
33 +## Installation
34 +
35 +The fastest way to get started is to serve JavaScript from the CDN:
36 +
37 +```html
38 +<!-- The core React library -->
39 +<script src="http://fbcdn.com/javascript/react/0.6.0/react.min.js"></script>
40 +<!-- In-browser JSX transformer, remove when pre-compiling JSX. -->
41 +<script src="http://fbcdn.com/javascript/react/0.6.0/JSXTransformer.js"></script>
42 +```
43 +
44 +We've also built a [starter kit](#) 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.
45 +
46 +If you'd like to use [bower](http://bower.io), it's as easy as:
47 +
48 +```sh
49 +bower install react
50 +```
51 +
52 +## Contribute
53 +
54 +The main purpose of this repository is to continue to evolve React core, making it faster and easier to use. If you're interested in helping with that, then keep reading. If you're not interested in helping right now that's ok too :) Any feedback you have about using React would be greatly appreciated.
55 +
56 +### Building Your Copy of React
57 +
58 +The process to build `react.js` is built entirely on top of node.js, using many libraries you may already be familiar with.
59 +
60 +#### Prerequisites
61 +
62 +* You have `node` installed at v0.10.0+ (it might work at lower versions, we just haven't tested).
63 +* You are familiar with `npm` and know whether or not you need to use `sudo` when installing packages globally.
64 +* You are familiar with `git`.
65 +
66 +#### Build
67 +
68 +Once you have the repository cloned, building a copy of `react.js` is really easy.
69 +
70 +```sh
71 +# grunt-cli is needed by grunt; you might have this installed already
72 +npm install -g grunt-cli
73 +npm install
74 +grunt build
75 +```
76 +
77 +At this point, you should now have a `build/` directory populated with everything you need to use React. The examples should all work.
78 +
79 +### Grunt
80 +
81 +We use grunt to automate many tasks. Run `grunt -h` to see a mostly complete listing. The important ones to know:
82 +
83 +```sh
84 +# Create test build & run tests with PhantomJS
85 +grunt test
86 +# Lint the core library code with JSHint
87 +grunt lint
88 +# Lint package code
89 +grunt lint:package
90 +# Wipe out build directory
91 +grunt clean
92 +```
93 +
94 +### More…
95 +
96 +There's only so much we can cram in here. To read more about the community and guidelines for submitting pull requests, please read the [Contributing document](CONTRIBUTING.md).
bin/jsx new
+42
@@ -0,0 +1,42 @@
1 +#!/usr/bin/env node
2 +"use strict";
3 +
4 +var visitors = require('../vendor/fbtransform/visitors').transformVisitors;
5 +var transform = require('../vendor/fbtransform/lib/transform').transform;
6 +var debranch = require("../vendor/woodchipper").debranch;
7 +
8 +require("commoner").resolve(function(id) {
9 + var context = this;
10 +
11 + // Note that the result of context.getProvidedP() is cached for the
12 + // duration of the build, so it is both consistent and cheap to
13 + // evaluate multiple times.
14 + return context.getProvidedP().then(function(idToPath) {
15 + // If a module declares its own identifier using @providesModule
16 + // then that identifier will be a key in the idToPath object.
17 + if (idToPath.hasOwnProperty(id)) {
18 + return context.readFileP(idToPath[id]);
19 + }
20 +
21 + // Otherwise assume the identifier maps directly to a path in the
22 + // filesystem.
23 + return context.readModuleP(id);
24 + });
25 +
26 +}).process(function(id, source) {
27 + var context = this;
28 +
29 + // This is where JSX, ES6, etc. desugaring happens.
30 + source = transform(visitors.react, source).code;
31 +
32 + return context.makePromise(function(callback) {
33 + var constants = context.config.constants || {};
34 +
35 + // Debranching means removing any obviously dead code after
36 + // replacing constant conditional expressions with literal
37 + // (boolean) values.
38 + debranch(constants, source, function(source) {
39 + callback(null, source);
40 + });
41 + });
42 +});
docs/Gemfile new
+16
@@ -0,0 +1,16 @@
1 +source 'https://rubygems.org'
2 +
3 +# jekyll, which builds it all
4 +gem 'jekyll', '~>1.0'
5 +
6 +# JSON
7 +gem 'json'
8 +
9 +# SASS for CSS
10 +gem 'sass'
11 +
12 +# For `rake watch`
13 +gem 'rb-fsevent'
14 +
15 +# Redcarpet for Markdown
16 +gem 'redcarpet'
docs/Gemfile.lock new
+46
@@ -0,0 +1,46 @@
1 +GEM
2 + remote: https://rubygems.org/
3 + specs:
4 + classifier (1.3.3)
5 + fast-stemmer (>= 1.0.0)
6 + colorator (0.1)
7 + commander (4.1.3)
8 + highline (~> 1.6.11)
9 + directory_watcher (1.4.1)
10 + fast-stemmer (1.0.2)
11 + highline (1.6.19)
12 + jekyll (1.0.2)
13 + classifier (~> 1.3)
14 + colorator (~> 0.1)
15 + commander (~> 4.1.3)
16 + directory_watcher (~> 1.4.1)
17 + kramdown (~> 1.0.2)
18 + liquid (~> 2.3)
19 + maruku (~> 0.5)
20 + pygments.rb (~> 0.5.0)
21 + safe_yaml (~> 0.7.0)
22 + json (1.8.0)
23 + kramdown (1.0.2)
24 + liquid (2.5.0)
25 + maruku (0.6.1)
26 + syntax (>= 1.0.0)
27 + posix-spawn (0.3.6)
28 + pygments.rb (0.5.0)
29 + posix-spawn (~> 0.3.6)
30 + yajl-ruby (~> 1.1.0)
31 + rb-fsevent (0.9.3)
32 + redcarpet (2.2.2)
33 + safe_yaml (0.7.1)
34 + sass (3.2.9)
35 + syntax (1.0.0)
36 + yajl-ruby (1.1.0)
37 +
38 +PLATFORMS
39 + ruby
40 +
41 +DEPENDENCIES
42 + jekyll (~> 1.0)
43 + json
44 + rb-fsevent
45 + redcarpet
46 + sass
docs/README.md new
+61
@@ -0,0 +1,61 @@
1 +# React Documentation & Website
2 +
3 +We use [Jekyll](http://jekyllrb.com/) to build the site using ([mostly](http://zpao.com/posts/adding-line-highlights-to-markdown-code-fences/)) Markdown, and we host it by pushing HTML to [GitHub Pages](http://pages.github.com/).
4 +
5 +## Installation
6 +
7 +If you are working on the site, you will want to install and run a local copy of it.
8 +
9 +### Dependencies
10 +
11 +In order to use Jekyll, you will need to have Ruby installed.
12 +
13 + - [Ruby](http://www.ruby-lang.org/) (version >= 1.8.7)
14 + - [RubyGems](http://rubygems.org/) (version >= 1.3.7)
15 + - [Bundler](http://gembundler.com/)
16 +
17 +Mac OS X comes pre-installed with Ruby, but you may need to update RubyGems (via `gem update --system`).
18 +Otherwise, [RVM](https://rvm.io/) and [rbenv](https://github.com/sstephenson/rbenv) are popular ways to install Ruby.
19 +Once you have RubyGems and installed Bundler (via `gem install bundler`), use it to install the dependencies:
20 +
21 +```sh
22 +$ cd react/docs
23 +$ bundle install # Might need sudo.
24 +```
25 +
26 +### Instructions
27 +
28 +The site requires React, so first make sure you've built the project (via `grunt`).
29 +
30 +Use Jekyll to serve the website locally (by default, at `http://localhost:4000`):
31 +
32 +```sh
33 +$ cd react/docs
34 +$ rake
35 +$ jekyll serve -w
36 +$ open http://localhost:4000/react/
37 +```
38 +
39 +We use [SASS](http://sass-lang.com/) (with [Bourbon](http://bourbon.io/)) for our CSS, and we use JSX to transform some of our JS.
40 +If you only want to modify the HTML or Markdown, you do not have to do anything because we package pre-compiled copies of the CSS and JS.
41 +If you want to modify the CSS or JS, use [Rake](http://rake.rubyforge.org/) to compile them:
42 +
43 +```sh
44 +$ cd react/docs
45 +$ rake watch # Automatically compiles as needed.
46 +# rake Manually compile CSS and JS.
47 +# rake css Manually compile CSS, only.
48 +# rake js Manually compile JS, only.
49 +```
50 +
51 +## Afterthoughts
52 +
53 +### Updating `facebook.github.io/react`
54 +
55 +The easiest way to do this is to have a separate clone of this repository, checked out to the `gh-pages` branch. We have a build step that expects this to be in a directory named `react-gh-pages` at the same depth as `react`. Then it's just a matter of running `grunt docs`, which will compile the site and copy it out to this repository. From there you can check it in.
56 +
57 +**Note:** This should only be done for new releases. You should create a tag corresponding to the relase tag in the main repository.
58 +
59 +### Removing the Jekyll / Ruby Dependency
60 +
61 +In an ideal world, we would not be adding a Ruby dependency on part of our project. We would like to move towards a point where we are using React to render the website.
docs/Rakefile new
+35
@@ -0,0 +1,35 @@
1 +require('rubygems')
2 +require('json')
3 +require('yaml')
4 +
5 +desc "generate css from sass"
6 +task :css do
7 + system "sass --style=compressed _css/react.scss css/react.css"
8 +end
9 +
10 +desc "generate js from jsx"
11 +task :js do
12 + system "../bin/jsx _js js"
13 +end
14 +
15 +desc "watch css & js"
16 +task :watch => [:update_version] do
17 + Process.spawn "sass --style=compressed --watch _css/react.scss:css/react.css"
18 + Process.spawn "../bin/jsx --watch _js js"
19 + Process.waitall
20 +end
21 +
22 +desc "update version to match ../package.json"
23 +task :update_version do
24 + react_version = JSON.parse(File.read('../package.json'))['version']
25 + site_config = YAML.load_file('_config.yml')
26 + site_config['react_version'] = react_version
27 + File.open('_config.yml', 'w+') { |f| f.write(site_config.to_yaml) }
28 +end
29 +
30 +desc "build into ../../react-gh-pages"
31 +task :release => [:default] do
32 + system "jekyll build -d ../../react-gh-pages"
33 +end
34 +
35 +task :default => [:update_version, :css, :js]
docs/_config.yml new
+14
@@ -0,0 +1,14 @@
1 +---
2 +markdown: redcarpet
3 +name: React
4 +react_version: 0.3.0
5 +redcarpet:
6 + extensions:
7 + - fenced_code_blocks
8 +pygments: true
9 +exclude:
10 +- Gemfile
11 +- Gemfile.lock
12 +- README.md
13 +- Rakefile
14 +baseurl: /react
docs/_css/_solarized.scss new
+173
@@ -0,0 +1,173 @@
1 +html * {
2 + color-profile: sRGB;
3 + rendering-intent: auto;
4 +}
5 + .cm-s-solarized-light {
6 + background-color: #f8f5ec;
7 + color: #637c84;
8 + }
9 + .cm-s-solarized-light .emphasis {
10 + font-weight: bold;
11 + }
12 + .cm-s-solarized-light .dotted {
13 + border-bottom: 1px dotted #cb4b16;
14 + }
15 + .cm-s-solarized-light .CodeMirror-gutter {
16 + background-color: #eee8d5;
17 + border-right: 3px solid #eee8d5;
18 + }
19 + .cm-s-solarized-light .CodeMirror-gutter .CodeMirror-gutter-text {
20 + color: #93a1a1;
21 + }
22 + .cm-s-solarized-light .CodeMirror-cursor {
23 + border-left-color: #002b36 !important;
24 + }
25 + .cm-s-solarized-light .CodeMirror-matchingbracket {
26 + color: #002b36;
27 + background-color: #eee8d5;
28 + box-shadow: 0 0 10px #eee8d5;
29 + font-weight: bold;
30 + }
31 + .cm-s-solarized-light .CodeMirror-nonmatchingbracket {
32 + color: #002b36;
33 + background-color: #eee8d5;
34 + box-shadow: 0 0 10px #eee8d5;
35 + font-weight: bold;
36 + color: #dc322f;
37 + border-bottom: 1px dotted #cb4b16;
38 + }
39 + .cm-s-solarized-light span.cm-keyword {
40 + color: #268bd2;
41 + }
42 + .cm-s-solarized-light span.cm-atom {
43 + color: #2aa198;
44 + }
45 + .cm-s-solarized-light span.cm-number {
46 + color: #586e75;
47 + }
48 + .cm-s-solarized-light span.cm-def {
49 + color: #637c84;
50 + }
51 + .cm-s-solarized-light span.cm-variable {
52 + color: #637c84;
53 + }
54 + .cm-s-solarized-light span.cm-variable-2 {
55 + color: #b58900;
56 + }
57 + .cm-s-solarized-light span.cm-variable-3 {
58 + color: #cb4b16;
59 + }
60 + .cm-s-solarized-light span.cm-comment {
61 + color: #93a1a1;
62 + }
63 + .cm-s-solarized-light span.cm-property {
64 + color: #637c84;
65 + }
66 + .cm-s-solarized-light span.cm-operator {
67 + color: #657b83;
68 + }
69 + .cm-s-solarized-light span.cm-string {
70 + color: #36958e;
71 + }
72 + .cm-s-solarized-light span.cm-error {
73 + font-weight: bold;
74 + border-bottom: 1px dotted #cb4b16;
75 + }
76 + .cm-s-solarized-light span.cm-bracket {
77 + color: #cb4b16;
78 + }
79 + .cm-s-solarized-light span.cm-tag {
80 + color: #657b83;
81 + }
82 + .cm-s-solarized-light span.cm-attribute {
83 + color: #586e75;
84 + font-weight: bold;
85 + }
86 + .cm-s-solarized-light span.cm-meta {
87 + color: #268bd2;
88 + }
89 + .cm-s-solarized-dark {
90 + background-color: #002b36;
91 + color: #839496;
92 + }
93 + .cm-s-solarized-dark .emphasis {
94 + font-weight: bold;
95 + }
96 + .cm-s-solarized-dark .dotted {
97 + border-bottom: 1px dotted #cb4b16;
98 + }
99 + .cm-s-solarized-dark .CodeMirror-gutter {
100 + background-color: #073642;
101 + border-right: 3px solid #073642;
102 + }
103 + .cm-s-solarized-dark .CodeMirror-gutter .CodeMirror-gutter-text {
104 + color: #586e75;
105 + }
106 + .cm-s-solarized-dark .CodeMirror-cursor {
107 + border-left-color: #fdf6e3 !important;
108 + }
109 + .cm-s-solarized-dark .CodeMirror-matchingbracket {
110 + color: #fdf6e3;
111 + background-color: #073642;
112 + box-shadow: 0 0 10px #073642;
113 + font-weight: bold;
114 + }
115 + .cm-s-solarized-dark .CodeMirror-nonmatchingbracket {
116 + color: #fdf6e3;
117 + background-color: #073642;
118 + box-shadow: 0 0 10px #073642;
119 + font-weight: bold;
120 + color: #dc322f;
121 + border-bottom: 1px dotted #cb4b16;
122 + }
123 + .cm-s-solarized-dark span.cm-keyword {
124 + color: #839496;
125 + font-weight: bold;
126 + }
127 + .cm-s-solarized-dark span.cm-atom {
128 + color: #2aa198;
129 + }
130 + .cm-s-solarized-dark span.cm-number {
131 + color: #93a1a1;
132 + }
133 + .cm-s-solarized-dark span.cm-def {
134 + color: #268bd2;
135 + }
136 + .cm-s-solarized-dark span.cm-variable {
137 + color: #cb4b16;
138 + }
139 + .cm-s-solarized-dark span.cm-variable-2 {
140 + color: #cb4b16;
141 + }
142 + .cm-s-solarized-dark span.cm-variable-3 {
143 + color: #cb4b16;
144 + }
145 + .cm-s-solarized-dark span.cm-comment {
146 + color: #586e75;
147 + }
148 + .cm-s-solarized-dark span.cm-property {
149 + color: #b58900;
150 + }
151 + .cm-s-solarized-dark span.cm-operator {
152 + color: #839496;
153 + }
154 + .cm-s-solarized-dark span.cm-string {
155 + color: #6c71c4;
156 + }
157 + .cm-s-solarized-dark span.cm-error {
158 + font-weight: bold;
159 + border-bottom: 1px dotted #cb4b16;
160 + }
161 + .cm-s-solarized-dark span.cm-bracket {
162 + color: #cb4b16;
163 + }
164 + .cm-s-solarized-dark span.cm-tag {
165 + color: #839496;
166 + }
167 + .cm-s-solarized-dark span.cm-attribute {
168 + color: #93a1a1;
169 + font-weight: bold;
170 + }
171 + .cm-s-solarized-dark span.cm-meta {
172 + color: #268bd2;
173 + }
docs/_css/_typography.scss new
+136
@@ -0,0 +1,136 @@
1 +@import 'variables.scss';
2 +
3 +$textColor: $mediumColor;
4 +$textColorLight: lighten($textColor, 20%);
5 +
6 +html {
7 + font-family: $helvetica;
8 + font-family: proxima-nova, $helvetica;
9 + font-weight: 300;
10 + color: $textColor;
11 + line-height: 1.28;
12 +}
13 +
14 +p {
15 + margin: 0 0 10px;
16 +}
17 +
18 +.subHeader {
19 + font-size: 21px;
20 + font-weight: 200;
21 + line-height: 30px;
22 + margin-bottom: 10px;
23 +}
24 +
25 +em {
26 + font-style: italic;
27 +}
28 +
29 +h1,
30 +h2,
31 +h3,
32 +h4,
33 +h5,
34 +h6 {
35 + margin: 10px 0;
36 + font-family: inherit;
37 + font-weight: bold;
38 + line-height: 20px;
39 + color: inherit;
40 + text-rendering: optimizelegibility;
41 +}
42 +
43 +h1 small,
44 +h2 small,
45 +h3 small,
46 +h4 small,
47 +h5 small,
48 +h6 small {
49 + font-weight: normal;
50 + color: $textColorLight
51 +}
52 +
53 +h1,
54 +h2,
55 +h3 {
56 + line-height: 40px;
57 +}
58 +
59 +h1 {
60 + font-size: 39px;
61 +}
62 +
63 +h2 {
64 + font-size: 31px;
65 +}
66 +
67 +h3 {
68 + font-size: 23px;
69 +}
70 +
71 +h4 {
72 + font-size: 17px;
73 +}
74 +
75 +h5 {
76 + font-size: 14px;
77 +}
78 +
79 +h6 {
80 + font-size: 11px;
81 +}
82 +
83 +h1 small {
84 + font-size: 24px;
85 +}
86 +
87 +h2 small {
88 + font-size: 18px;
89 +}
90 +
91 +h3 small {
92 + font-size: 16px;
93 +}
94 +
95 +h4 small {
96 + font-size: 14px;
97 +}
98 +
99 +ul,
100 +ol {
101 + margin: 0 0 10px 25px;
102 + padding: 0;
103 +}
104 +
105 +ul ul,
106 +ul ol,
107 +ol ol,
108 +ol ul {
109 + margin-bottom: 0;
110 +}
111 +
112 +li {
113 + line-height: 20px;
114 +}
115 +
116 +
117 +
118 +a {
119 + color: $linkColor;
120 + text-decoration: none;
121 + &:hover,
122 + &:focus {
123 + color: $linkInteract;
124 + text-decoration: underline;
125 + }
126 + &:focus {
127 + outline: thin dotted #333;
128 + outline: 5px auto -webkit-focus-ring-color;
129 + outline-offset: -2px;
130 + }
131 +}
132 +.center {
133 + text-align: center;
134 +}
135 +
136 +
docs/_css/_variables.scss new
+22
@@ -0,0 +1,22 @@
1 +$primary: #cc7a6f;
2 +$linkColor: darken($primary, 9%);
3 +$linkInteract: darken($linkColor, 9%);
4 +$pageBg: #f9f9f9;
5 +
6 +$lightColor: #e9e9e9;
7 +$mediumestColor: #666;
8 +$mediumColor: #484848;
9 +$darkColor: #2d2d2d;
10 +$darkestColor: #222222;
11 +$blueColor: #61dafb;
12 +$orangeColor: complement($blueColor);
13 +
14 +$lightTextColor: #fafafa;
15 +$mediumTextColor: #aaa;
16 +$darkTextColor: $mediumColor;
17 +
18 +$buttonBlueTop: #77a3d2;
19 +$buttonBlueBottom: #4783c2;
20 +$buttonGreyTop: #9a9a9a;
21 +$buttonGreyBottom: #646464;
22 +
docs/_css/bourbon/_bourbon-deprecated-upcoming.scss new
+13
@@ -0,0 +1,13 @@
1 +//************************************************************************//
2 +// These mixins/functions are deprecated
3 +// They will be removed in the next MAJOR version release
4 +//************************************************************************//
5 +@mixin box-shadow ($shadows...) {
6 + @include prefixer(box-shadow, $shadows, spec);
7 + @warn "box-shadow is deprecated and will be removed in the next major version release";
8 +}
9 +
10 +@mixin background-size ($lengths...) {
11 + @include prefixer(background-size, $lengths, spec);
12 + @warn "background-size is deprecated and will be removed in the next major version release";
13 +}
docs/_css/bourbon/_bourbon.scss new
+59
@@ -0,0 +1,59 @@
1 +// Custom Helpers
2 +@import "helpers/deprecated-webkit-gradient";
3 +@import "helpers/gradient-positions-parser";
4 +@import "helpers/linear-positions-parser";
5 +@import "helpers/radial-arg-parser";
6 +@import "helpers/radial-positions-parser";
7 +@import "helpers/render-gradients";
8 +@import "helpers/shape-size-stripper";
9 +
10 +// Custom Functions
11 +@import "functions/compact";
12 +@import "functions/flex-grid";
13 +@import "functions/grid-width";
14 +@import "functions/linear-gradient";
15 +@import "functions/modular-scale";
16 +@import "functions/px-to-em";
17 +@import "functions/radial-gradient";
18 +@import "functions/tint-shade";
19 +@import "functions/transition-property-name";
20 +
21 +// CSS3 Mixins
22 +@import "css3/animation";
23 +@import "css3/appearance";
24 +@import "css3/backface-visibility";
25 +@import "css3/background";
26 +@import "css3/background-image";
27 +@import "css3/border-image";
28 +@import "css3/border-radius";
29 +@import "css3/box-sizing";
30 +@import "css3/columns";
31 +@import "css3/flex-box";
32 +@import "css3/font-face";
33 +@import "css3/hidpi-media-query";
34 +@import "css3/image-rendering";
35 +@import "css3/inline-block";
36 +@import "css3/keyframes";
37 +@import "css3/linear-gradient";
38 +@import "css3/perspective";
39 +@import "css3/radial-gradient";
40 +@import "css3/transform";
41 +@import "css3/transition";
42 +@import "css3/user-select";
43 +@import "css3/placeholder";
44 +
45 +// Addons & other mixins
46 +@import "addons/button";
47 +@import "addons/clearfix";
48 +@import "addons/font-family";
49 +@import "addons/hide-text";
50 +@import "addons/html5-input-types";
51 +@import "addons/position";
52 +@import "addons/prefixer";
53 +@import "addons/retina-image";
54 +@import "addons/size";
55 +@import "addons/timing-functions";
56 +@import "addons/triangle";
57 +
58 +// Soon to be deprecated Mixins
59 +@import "bourbon-deprecated-upcoming";
docs/_css/bourbon/addons/_button.scss new
+273
@@ -0,0 +1,273 @@
1 +@mixin button ($style: simple, $base-color: #4294f0) {
2 +
3 + @if type-of($style) == color {
4 + $base-color: $style;
5 + $style: simple;
6 + }
7 +
8 + // Grayscale button
9 + @if $base-color == grayscale($base-color) {
10 + @if $style == simple {
11 + @include simple($base-color, $grayscale: true);
12 + }
13 +
14 + @else if $style == shiny {
15 + @include shiny($base-color, $grayscale: true);
16 + }
17 +
18 + @else if $style == pill {
19 + @include pill($base-color, $grayscale: true);
20 + }
21 + }
22 +
23 + // Colored button
24 + @else {
25 + @if $style == simple {
26 + @include simple($base-color);
27 + }
28 +
29 + @else if $style == shiny {
30 + @include shiny($base-color);
31 + }
32 +
33 + @else if $style == pill {
34 + @include pill($base-color);
35 + }
36 + }
37 +
38 + &:disabled {
39 + opacity: 0.5;
40 + cursor: not-allowed;
41 + }
42 +}
43 +
44 +
45 +// Simple Button
46 +//************************************************************************//
47 +@mixin simple($base-color, $grayscale: false) {
48 + $color: hsl(0, 0, 100%);
49 + $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
50 + $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%);
51 + $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%);
52 + $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%);
53 +
54 + @if lightness($base-color) > 70% {
55 + $color: hsl(0, 0, 20%);
56 + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
57 + }
58 +
59 + @if $grayscale == true {
60 + $border: grayscale($border);
61 + $inset-shadow: grayscale($inset-shadow);
62 + $stop-gradient: grayscale($stop-gradient);
63 + $text-shadow: grayscale($text-shadow);
64 + }
65 +
66 + border: 1px solid $border;
67 + border-radius: 3px;
68 + box-shadow: inset 0 1px 0 0 $inset-shadow;
69 + color: $color;
70 + display: inline-block;
71 + font-size: 11px;
72 + font-weight: bold;
73 + @include linear-gradient ($base-color, $stop-gradient);
74 + padding: 7px 18px;
75 + text-decoration: none;
76 + text-shadow: 0 1px 0 $text-shadow;
77 + background-clip: padding-box;
78 +
79 + &:hover:not(:disabled) {
80 + $base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
81 + $inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%);
82 + $stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%);
83 +
84 + @if $grayscale == true {
85 + $base-color-hover: grayscale($base-color-hover);
86 + $inset-shadow-hover: grayscale($inset-shadow-hover);
87 + $stop-gradient-hover: grayscale($stop-gradient-hover);
88 + }
89 +
90 + box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
91 + cursor: pointer;
92 + @include linear-gradient ($base-color-hover, $stop-gradient-hover);
93 + }
94 +
95 + &:active:not(:disabled) {
96 + $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
97 + $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%);
98 +
99 + @if $grayscale == true {
100 + $border-active: grayscale($border-active);
101 + $inset-shadow-active: grayscale($inset-shadow-active);
102 + }
103 +
104 + border: 1px solid $border-active;
105 + box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active, 0 1px 1px 0 #eee;
106 + }
107 +}
108 +
109 +
110 +// Shiny Button
111 +//************************************************************************//
112 +@mixin shiny($base-color, $grayscale: false) {
113 + $color: hsl(0, 0, 100%);
114 + $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81);
115 + $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122);
116 + $fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46);
117 + $inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12);
118 + $second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33);
119 + $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114);
120 + $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48);
121 +
122 + @if lightness($base-color) > 70% {
123 + $color: hsl(0, 0, 20%);
124 + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
125 + }
126 +
127 + @if $grayscale == true {
128 + $border: grayscale($border);
129 + $border-bottom: grayscale($border-bottom);
130 + $fourth-stop: grayscale($fourth-stop);
131 + $inset-shadow: grayscale($inset-shadow);
132 + $second-stop: grayscale($second-stop);
133 + $text-shadow: grayscale($text-shadow);
134 + $third-stop: grayscale($third-stop);
135 + }
136 +
137 + border: 1px solid $border;
138 + border-bottom: 1px solid $border-bottom;
139 + border-radius: 5px;
140 + box-shadow: inset 0 1px 0 0 $inset-shadow;
141 + color: $color;
142 + display: inline-block;
143 + font-size: 14px;
144 + font-weight: bold;
145 + @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%);
146 + padding: 8px 20px;
147 + text-align: center;
148 + text-decoration: none;
149 + text-shadow: 0 -1px 1px $text-shadow;
150 +
151 + &:hover:not(:disabled) {
152 + $first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18);
153 + $second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51);
154 + $third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66);
155 + $fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63);
156 +
157 + @if $grayscale == true {
158 + $first-stop-hover: grayscale($first-stop-hover);
159 + $second-stop-hover: grayscale($second-stop-hover);
160 + $third-stop-hover: grayscale($third-stop-hover);
161 + $fourth-stop-hover: grayscale($fourth-stop-hover);
162 + }
163 +
164 + cursor: pointer;
165 + @include linear-gradient(top, $first-stop-hover 0%,
166 + $second-stop-hover 50%,
167 + $third-stop-hover 50%,
168 + $fourth-stop-hover 100%);
169 + }
170 +
171 + &:active:not(:disabled) {
172 + $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122);
173 +
174 + @if $grayscale == true {
175 + $inset-shadow-active: grayscale($inset-shadow-active);
176 + }
177 +
178 + box-shadow: inset 0 0 20px 0 $inset-shadow-active, 0 1px 0 #fff;
179 + }
180 +}
181 +
182 +
183 +// Pill Button
184 +//************************************************************************//
185 +@mixin pill($base-color, $grayscale: false) {
186 + $color: hsl(0, 0, 100%);
187 + $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%);
188 + $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%);
189 + $border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%);
190 + $inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%);
191 + $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%);
192 + $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%);
193 +
194 + @if lightness($base-color) > 70% {
195 + $color: hsl(0, 0, 20%);
196 + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
197 + }
198 +
199 + @if $grayscale == true {
200 + $border-bottom: grayscale($border-bottom);
201 + $border-sides: grayscale($border-sides);
202 + $border-top: grayscale($border-top);
203 + $inset-shadow: grayscale($inset-shadow);
204 + $stop-gradient: grayscale($stop-gradient);
205 + $text-shadow: grayscale($text-shadow);
206 + }
207 +
208 + border: 1px solid $border-top;
209 + border-color: $border-top $border-sides $border-bottom;
210 + border-radius: 16px;
211 + box-shadow: inset 0 1px 0 0 $inset-shadow, 0 1px 2px 0 #b3b3b3;
212 + color: $color;
213 + display: inline-block;
214 + font-size: 11px;
215 + font-weight: normal;
216 + line-height: 1;
217 + @include linear-gradient ($base-color, $stop-gradient);
218 + padding: 5px 16px;
219 + text-align: center;
220 + text-decoration: none;
221 + text-shadow: 0 -1px 1px $text-shadow;
222 + background-clip: padding-box;
223 +
224 + &:hover:not(:disabled) {
225 + $base-color-hover: adjust-color($base-color, $lightness: -4.5%);
226 + $border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%);
227 + $border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%);
228 + $border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%);
229 + $inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%);
230 + $stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%);
231 + $text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%);
232 +
233 + @if $grayscale == true {
234 + $base-color-hover: grayscale($base-color-hover);
235 + $border-bottom: grayscale($border-bottom);
236 + $border-sides: grayscale($border-sides);
237 + $border-top: grayscale($border-top);
238 + $inset-shadow-hover: grayscale($inset-shadow-hover);
239 + $stop-gradient-hover: grayscale($stop-gradient-hover);
240 + $text-shadow-hover: grayscale($text-shadow-hover);
241 + }
242 +
243 + border: 1px solid $border-top;
244 + border-color: $border-top $border-sides $border-bottom;
245 + box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
246 + cursor: pointer;
247 + @include linear-gradient ($base-color-hover, $stop-gradient-hover);
248 + text-shadow: 0 -1px 1px $text-shadow-hover;
249 + background-clip: padding-box;
250 + }
251 +
252 + &:active:not(:disabled) {
253 + $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%);
254 + $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%);
255 + $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%);
256 + $inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%);
257 + $text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%);
258 +
259 + @if $grayscale == true {
260 + $active-color: grayscale($active-color);
261 + $border-active: grayscale($border-active);
262 + $border-bottom-active: grayscale($border-bottom-active);
263 + $inset-shadow-active: grayscale($inset-shadow-active);
264 + $text-shadow-active: grayscale($text-shadow-active);
265 + }
266 +
267 + background: $active-color;
268 + border: 1px solid $border-active;
269 + border-bottom: 1px solid $border-bottom-active;
270 + box-shadow: inset 0 0 6px 3px $inset-shadow-active, 0 1px 0 0 #fff;
271 + text-shadow: 0 -1px 1px $text-shadow-active;
272 + }
273 +}
docs/_css/bourbon/addons/_clearfix.scss new
+29
@@ -0,0 +1,29 @@
1 +// Micro clearfix provides an easy way to contain floats without adding additional markup
2 +//
3 +// Example usage:
4 +//
5 +// // Contain all floats within .wrapper
6 +// .wrapper {
7 +// @include clearfix;
8 +// .content,
9 +// .sidebar {
10 +// float : left;
11 +// }
12 +// }
13 +
14 +@mixin clearfix {
15 + *zoom: 1;
16 +
17 + &:before,
18 + &:after {
19 + content: " ";
20 + display: table;
21 + }
22 +
23 + &:after {
24 + clear: both;
25 + }
26 +}
27 +
28 +// Acknowledgements
29 +// Micro clearfix: [Nicolas Gallagher](http://nicolasgallagher.com/micro-clearfix-hack/)
docs/_css/bourbon/addons/_font-family.scss new
+5
@@ -0,0 +1,5 @@
1 +$georgia: Georgia, Cambria, "Times New Roman", Times, serif;
2 +$helvetica: "Helvetica Neue", Helvetica, Arial, sans-serif;
3 +$lucida-grande: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif;
4 +$monospace: "Bitstream Vera Sans Mono", Consolas, Courier, monospace;
5 +$verdana: Verdana, Geneva, sans-serif;
docs/_css/bourbon/addons/_hide-text.scss new
+5
@@ -0,0 +1,5 @@
1 +@mixin hide-text {
2 + color: transparent;
3 + font: 0/0 a;
4 + text-shadow: none;
5 +}
docs/_css/bourbon/addons/_html5-input-types.scss new
+56
@@ -0,0 +1,56 @@
1 +//************************************************************************//
2 +// Generate a variable ($all-text-inputs) with a list of all html5
3 +// input types that have a text-based input, excluding textarea.
4 +// http://diveintohtml5.org/forms.html
5 +//************************************************************************//
6 +$inputs-list: 'input[type="email"]',
7 + 'input[type="number"]',
8 + 'input[type="password"]',
9 + 'input[type="search"]',
10 + 'input[type="tel"]',
11 + 'input[type="text"]',
12 + 'input[type="url"]',
13 +
14 + // Webkit & Gecko may change the display of these in the future
15 + 'input[type="color"]',
16 + 'input[type="date"]',
17 + 'input[type="datetime"]',
18 + 'input[type="datetime-local"]',
19 + 'input[type="month"]',
20 + 'input[type="time"]',
21 + 'input[type="week"]';
22 +
23 +$unquoted-inputs-list: ();
24 +@each $input-type in $inputs-list {
25 + $unquoted-inputs-list: append($unquoted-inputs-list, unquote($input-type), comma);
26 +}
27 +
28 +$all-text-inputs: $unquoted-inputs-list;
29 +
30 +
31 +// Hover Pseudo-class
32 +//************************************************************************//
33 +$all-text-inputs-hover: ();
34 +@each $input-type in $unquoted-inputs-list {
35 + $input-type-hover: $input-type + ":hover";
36 + $all-text-inputs-hover: append($all-text-inputs-hover, $input-type-hover, comma);
37 +}
38 +
39 +// Focus Pseudo-class
40 +//************************************************************************//
41 +$all-text-inputs-focus: ();
42 +@each $input-type in $unquoted-inputs-list {
43 + $input-type-focus: $input-type + ":focus";
44 + $all-text-inputs-focus: append($all-text-inputs-focus, $input-type-focus, comma);
45 +}
46 +
47 +// You must use interpolation on the variable:
48 +// #{$all-text-inputs}
49 +// #{$all-text-inputs-hover}
50 +// #{$all-text-inputs-focus}
51 +
52 +// Example
53 +//************************************************************************//
54 +// #{$all-text-inputs}, textarea {
55 +// border: 1px solid red;
56 +// }
docs/_css/bourbon/addons/_position.scss new
+42
@@ -0,0 +1,42 @@
1 +@mixin position ($position: relative, $coordinates: 0 0 0 0) {
2 +
3 + @if type-of($position) == list {
4 + $coordinates: $position;
5 + $position: relative;
6 + }
7 +
8 + $top: nth($coordinates, 1);
9 + $right: nth($coordinates, 2);
10 + $bottom: nth($coordinates, 3);
11 + $left: nth($coordinates, 4);
12 +
13 + position: $position;
14 +
15 + @if $top == auto {
16 + top: $top;
17 + }
18 + @else if not(unitless($top)) {
19 + top: $top;
20 + }
21 +
22 + @if $right == auto {
23 + right: $right;
24 + }
25 + @else if not(unitless($right)) {
26 + right: $right;
27 + }
28 +
29 + @if $bottom == auto {
30 + bottom: $bottom;
31 + }
32 + @else if not(unitless($bottom)) {
33 + bottom: $bottom;
34 + }
35 +
36 + @if $left == auto {
37 + left: $left;
38 + }
39 + @else if not(unitless($left)) {
40 + left: $left;
41 + }
42 +}
docs/_css/bourbon/addons/_prefixer.scss new
+49
@@ -0,0 +1,49 @@
1 +//************************************************************************//
2 +// Example: @include prefixer(border-radius, $radii, webkit ms spec);
3 +//************************************************************************//
4 +$prefix-for-webkit: true !default;
5 +$prefix-for-mozilla: true !default;
6 +$prefix-for-microsoft: true !default;
7 +$prefix-for-opera: true !default;
8 +$prefix-for-spec: true !default; // required for keyframe mixin
9 +
10 +@mixin prefixer ($property, $value, $prefixes) {
11 + @each $prefix in $prefixes {
12 + @if $prefix == webkit {
13 + @if $prefix-for-webkit {
14 + -webkit-#{$property}: $value;
15 + }
16 + }
17 + @else if $prefix == moz {
18 + @if $prefix-for-mozilla {
19 + -moz-#{$property}: $value;
20 + }
21 + }
22 + @else if $prefix == ms {
23 + @if $prefix-for-microsoft {
24 + -ms-#{$property}: $value;
25 + }
26 + }
27 + @else if $prefix == o {
28 + @if $prefix-for-opera {
29 + -o-#{$property}: $value;
30 + }
31 + }
32 + @else if $prefix == spec {
33 + @if $prefix-for-spec {
34 + #{$property}: $value;
35 + }
36 + }
37 + @else {
38 + @warn "Unrecognized prefix: #{$prefix}";
39 + }
40 + }
41 +}
42 +
43 +@mixin disable-prefix-for-all() {
44 + $prefix-for-webkit: false;
45 + $prefix-for-mozilla: false;
46 + $prefix-for-microsoft: false;
47 + $prefix-for-opera: false;
48 + $prefix-for-spec: false;
49 +}
docs/_css/bourbon/addons/_retina-image.scss new
+32
@@ -0,0 +1,32 @@
1 +@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $asset-pipeline: false) {
2 + @if $asset-pipeline {
3 + background-image: image_url($filename + "." + $extension);
4 + }
5 + @else {
6 + background-image: url($filename + "." + $extension);
7 + }
8 +
9 + @include hidpi {
10 +
11 + @if $asset-pipeline {
12 + @if $retina-filename {
13 + background-image: image_url($retina-filename + "." + $extension);
14 + }
15 + @else {
16 + background-image: image_url($filename + "@2x" + "." + $extension);
17 + }
18 + }
19 +
20 + @else {
21 + @if $retina-filename {
22 + background-image: url($retina-filename + "." + $extension);
23 + }
24 + @else {
25 + background-image: url($filename + "@2x" + "." + $extension);
26 + }
27 + }
28 +
29 + background-size: $background-size;
30 +
31 + }
32 +}
docs/_css/bourbon/addons/_size.scss new
+44
@@ -0,0 +1,44 @@
1 +@mixin size($size) {
2 + @if length($size) == 1 {
3 + @if $size == auto {
4 + width: $size;
5 + height: $size;
6 + }
7 +
8 + @else if unitless($size) {
9 + width: $size + px;
10 + height: $size + px;
11 + }
12 +
13 + @else if not(unitless($size)) {
14 + width: $size;
15 + height: $size;
16 + }
17 + }
18 +
19 + // Width x Height
20 + @if length($size) == 2 {
21 + $width: nth($size, 1);
22 + $height: nth($size, 2);
23 +
24 + @if $width == auto {
25 + width: $width;
26 + }
27 + @else if not(unitless($width)) {
28 + width: $width;
29 + }
30 + @else if unitless($width) {
31 + width: $width + px;
32 + }
33 +
34 + @if $height == auto {
35 + height: $height;
36 + }
37 + @else if not(unitless($height)) {
38 + height: $height;
39 + }
40 + @else if unitless($height) {
41 + height: $height + px;
42 + }
43 + }
44 +}
docs/_css/bourbon/addons/_timing-functions.scss new
+32
@@ -0,0 +1,32 @@
1 +// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie)
2 +// Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html
3 +
4 +// EASE IN
5 +$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530);
6 +$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190);
7 +$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220);
8 +$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060);
9 +$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715);
10 +$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035);
11 +$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335);
12 +$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045);
13 +
14 +// EASE OUT
15 +$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940);
16 +$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000);
17 +$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000);
18 +$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000);
19 +$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000);
20 +$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000);
21 +$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000);
22 +$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275);
23 +
24 +// EASE IN OUT
25 +$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955);
26 +$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000);
27 +$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000);
28 +$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000);
29 +$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950);
30 +$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000);
31 +$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860);
32 +$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550);
docs/_css/bourbon/addons/_triangle.scss new
+45
@@ -0,0 +1,45 @@
1 +@mixin triangle ($size, $color, $direction) {
2 + height: 0;
3 + width: 0;
4 +
5 + @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) {
6 + border-color: transparent;
7 + border-style: solid;
8 + border-width: $size / 2;
9 +
10 + @if $direction == up {
11 + border-bottom-color: $color;
12 +
13 + } @else if $direction == right {
14 + border-left-color: $color;
15 +
16 + } @else if $direction == down {
17 + border-top-color: $color;
18 +
19 + } @else if $direction == left {
20 + border-right-color: $color;
21 + }
22 + }
23 +
24 + @else if ($direction == up-right) or ($direction == up-left) {
25 + border-top: $size solid $color;
26 +
27 + @if $direction == up-right {
28 + border-left: $size solid transparent;
29 +
30 + } @else if $direction == up-left {
31 + border-right: $size solid transparent;
32 + }
33 + }
34 +
35 + @else if ($direction == down-right) or ($direction == down-left) {
36 + border-bottom: $size solid $color;
37 +
38 + @if $direction == down-right {
39 + border-left: $size solid transparent;
40 +
41 + } @else if $direction == down-left {
42 + border-right: $size solid transparent;
43 + }
44 + }
45 +}
docs/_css/bourbon/css3/_animation.scss new
+52
@@ -0,0 +1,52 @@
1 +// http://www.w3.org/TR/css3-animations/#the-animation-name-property-
2 +// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties.
3 +
4 +// Official animation shorthand property.
5 +@mixin animation ($animations...) {
6 + @include prefixer(animation, $animations, webkit moz spec);
7 +}
8 +
9 +// Individual Animation Properties
10 +@mixin animation-name ($names...) {
11 + @include prefixer(animation-name, $names, webkit moz spec);
12 +}
13 +
14 +
15 +@mixin animation-duration ($times...) {
16 + @include prefixer(animation-duration, $times, webkit moz spec);
17 +}
18 +
19 +
20 +@mixin animation-timing-function ($motions...) {
21 +// ease | linear | ease-in | ease-out | ease-in-out
22 + @include prefixer(animation-timing-function, $motions, webkit moz spec);
23 +}
24 +
25 +
26 +@mixin animation-iteration-count ($values...) {
27 +// infinite | <number>
28 + @include prefixer(animation-iteration-count, $values, webkit moz spec);
29 +}
30 +
31 +
32 +@mixin animation-direction ($directions...) {
33 +// normal | alternate
34 + @include prefixer(animation-direction, $directions, webkit moz spec);
35 +}
36 +
37 +
38 +@mixin animation-play-state ($states...) {
39 +// running | paused
40 + @include prefixer(animation-play-state, $states, webkit moz spec);
41 +}
42 +
43 +
44 +@mixin animation-delay ($times...) {
45 + @include prefixer(animation-delay, $times, webkit moz spec);
46 +}
47 +
48 +
49 +@mixin animation-fill-mode ($modes...) {
50 +// none | forwards | backwards | both
51 + @include prefixer(animation-fill-mode, $modes, webkit moz spec);
52 +}
docs/_css/bourbon/css3/_appearance.scss new
+3
@@ -0,0 +1,3 @@
1 +@mixin appearance ($value) {
2 + @include prefixer(appearance, $value, webkit moz ms o spec);
3 +}
docs/_css/bourbon/css3/_backface-visibility.scss new
+6
@@ -0,0 +1,6 @@
1 +//************************************************************************//
2 +// Backface-visibility mixin
3 +//************************************************************************//
4 +@mixin backface-visibility($visibility) {
5 + @include prefixer(backface-visibility, $visibility, webkit spec);
6 +}
docs/_css/bourbon/css3/_background-image.scss new
+48
@@ -0,0 +1,48 @@
1 +//************************************************************************//
2 +// Background-image property for adding multiple background images with
3 +// gradients, or for stringing multiple gradients together.
4 +//************************************************************************//
5 +
6 +@mixin background-image($images...) {
7 + background-image: _add-prefix($images, webkit);
8 + background-image: _add-prefix($images);
9 +}
10 +
11 +@function _add-prefix($images, $vendor: false) {
12 + $images-prefixed: ();
13 + $gradient-positions: false;
14 + @for $i from 1 through length($images) {
15 + $type: type-of(nth($images, $i)); // Get type of variable - List or String
16 +
17 + // If variable is a list - Gradient
18 + @if $type == list {
19 + $gradient-type: nth(nth($images, $i), 1); // linear or radial
20 + $gradient-pos: null;
21 + $gradient-args: null;
22 +
23 + @if ($gradient-type == linear) or ($gradient-type == radial) {
24 + $gradient-pos: nth(nth($images, $i), 2); // Get gradient position
25 + $gradient-args: nth(nth($images, $i), 3); // Get actual gradient (red, blue)
26 + }
27 + @else {
28 + $gradient-args: nth(nth($images, $i), 2); // Get actual gradient (red, blue)
29 + }
30 +
31 + $gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos);
32 + $gradient: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
33 + $images-prefixed: append($images-prefixed, $gradient, comma);
34 + }
35 + // If variable is a string - Image
36 + @else if $type == string {
37 + $images-prefixed: join($images-prefixed, nth($images, $i), comma);
38 + }
39 + }
40 + @return $images-prefixed;
41 +}
42 +
43 +//Examples:
44 + //@include background-image(linear-gradient(top, orange, red));
45 + //@include background-image(radial-gradient(50% 50%, cover circle, orange, red));
46 + //@include background-image(url("/images/a.png"), linear-gradient(orange, red));
47 + //@include background-image(url("image.png"), linear-gradient(orange, red), url("image.png"));
48 + //@include background-image(linear-gradient(hsla(0, 100%, 100%, 0.25) 0%, hsla(0, 100%, 100%, 0.08) 50%, transparent 50%), linear-gradient(orange, red));
docs/_css/bourbon/css3/_background.scss new
+103
@@ -0,0 +1,103 @@
1 +//************************************************************************//
2 +// Background property for adding multiple backgrounds using shorthand
3 +// notation.
4 +//************************************************************************//
5 +
6 +@mixin background(
7 + $background-1 , $background-2: false,
8 + $background-3: false, $background-4: false,
9 + $background-5: false, $background-6: false,
10 + $background-7: false, $background-8: false,
11 + $background-9: false, $background-10: false,
12 + $fallback: false
13 +) {
14 + $backgrounds: compact($background-1, $background-2,
15 + $background-3, $background-4,
16 + $background-5, $background-6,
17 + $background-7, $background-8,
18 + $background-9, $background-10);
19 +
20 + $fallback-color: false;
21 + @if (type-of($fallback) == color) or ($fallback == "transparent") {
22 + $fallback-color: $fallback;
23 + }
24 + @else {
25 + $fallback-color: _extract-background-color($backgrounds);
26 + }
27 +
28 + @if $fallback-color {
29 + background-color: $fallback-color;
30 + }
31 + background: _background-add-prefix($backgrounds, webkit);
32 + background: _background-add-prefix($backgrounds);
33 +}
34 +
35 +@function _extract-background-color($backgrounds) {
36 + $final-bg-layer: nth($backgrounds, length($backgrounds));
37 + @if type-of($final-bg-layer) == list {
38 + @for $i from 1 through length($final-bg-layer) {
39 + $value: nth($final-bg-layer, $i);
40 + @if type-of($value) == color {
41 + @return $value;
42 + }
43 + }
44 + }
45 + @return false;
46 +}
47 +
48 +@function _background-add-prefix($backgrounds, $vendor: false) {
49 + $backgrounds-prefixed: ();
50 +
51 + @for $i from 1 through length($backgrounds) {
52 + $shorthand: nth($backgrounds, $i); // Get member for current index
53 + $type: type-of($shorthand); // Get type of variable - List (gradient) or String (image)
54 +
55 + // If shorthand is a list (gradient)
56 + @if $type == list {
57 + $first-member: nth($shorthand, 1); // Get first member of shorthand
58 +
59 + // Linear Gradient
60 + @if index(linear radial, nth($first-member, 1)) {
61 + $gradient-type: nth($first-member, 1); // linear || radial
62 + $gradient-args: false;
63 + $gradient-positions: false;
64 + $shorthand-start: false;
65 + @if type-of($first-member) == list { // Linear gradient plus additional shorthand values - lg(red,orange)repeat,...
66 + $gradient-positions: nth($first-member, 2);
67 + $gradient-args: nth($first-member, 3);
68 + $shorthand-start: 2;
69 + }
70 + @else { // Linear gradient only - lg(red,orange),...
71 + $gradient-positions: nth($shorthand, 2);
72 + $gradient-args: nth($shorthand, 3); // Get gradient (red, blue)
73 + }
74 +
75 + $gradient-positions: _gradient-positions-parser($gradient-type, $gradient-positions);
76 + $gradient: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
77 +
78 + // Append any additional shorthand args to gradient
79 + @if $shorthand-start {
80 + @for $j from $shorthand-start through length($shorthand) {
81 + $gradient: join($gradient, nth($shorthand, $j), space);
82 + }
83 + }
84 + $backgrounds-prefixed: append($backgrounds-prefixed, $gradient, comma);
85 + }
86 + // Image with additional properties
87 + @else {
88 + $backgrounds-prefixed: append($backgrounds-prefixed, $shorthand, comma);
89 + }
90 + }
91 + // If shorthand is a simple string (color or image)
92 + @else if $type == string {
93 + $backgrounds-prefixed: join($backgrounds-prefixed, $shorthand, comma);
94 + }
95 + }
96 + @return $backgrounds-prefixed;
97 +}
98 +
99 +//Examples:
100 + //@include background(linear-gradient(top, orange, red));
101 + //@include background(radial-gradient(circle at 40% 40%, orange, red));
102 + //@include background(url("/images/a.png") no-repeat, linear-gradient(orange, red));
103 + //@include background(url("image.png") center center, linear-gradient(orange, red), url("image.png"));
docs/_css/bourbon/css3/_border-image.scss new
+55
@@ -0,0 +1,55 @@
1 +@mixin border-image($images) {
2 + -webkit-border-image: _border-add-prefix($images, webkit);
3 + -moz-border-image: _border-add-prefix($images, moz);
4 + -o-border-image: _border-add-prefix($images, o);
5 + border-image: _border-add-prefix($images);
6 +}
7 +
8 +@function _border-add-prefix($images, $vendor: false) {
9 + $border-image: null;
10 + $images-type: type-of(nth($images, 1));
11 + $first-var: nth(nth($images, 1), 1); // Get type of Gradient (Linear || radial)
12 +
13 + // If input is a gradient
14 + @if $images-type == string {
15 + @if ($first-var == "linear") or ($first-var == "radial") {
16 + $gradient-type: nth($images, 1); // Get type of gradient (linear || radial)
17 + $gradient-pos: nth($images, 2); // Get gradient position
18 + $gradient-args: nth($images, 3); // Get actual gradient (red, blue)
19 + $gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos);
20 + $border-image: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
21 + }
22 + // If input is a URL
23 + @else {
24 + $border-image: $images;
25 + }
26 + }
27 + // If input is gradient or url + additional args
28 + @else if $images-type == list {
29 + $type: type-of(nth($images, 1)); // Get type of variable - List or String
30 +
31 + // If variable is a list - Gradient
32 + @if $type == list {
33 + $gradient: nth($images, 1);
34 + $gradient-type: nth($gradient, 1); // Get type of gradient (linear || radial)
35 + $gradient-pos: nth($gradient, 2); // Get gradient position
36 + $gradient-args: nth($gradient, 3); // Get actual gradient (red, blue)
37 + $gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos);
38 + $border-image: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
39 +
40 + @for $i from 2 through length($images) {
41 + $border-image: append($border-image, nth($images, $i));
42 + }
43 + }
44 + }
45 + @return $border-image;
46 +}
47 +
48 +//Examples:
49 +// @include border-image(url("image.png"));
50 +// @include border-image(url("image.png") 20 stretch);
51 +// @include border-image(linear-gradient(45deg, orange, yellow));
52 +// @include border-image(linear-gradient(45deg, orange, yellow) stretch);
53 +// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round);
54 +// @include border-image(radial-gradient(top, cover, orange, yellow, orange));
55 +
docs/_css/bourbon/css3/_border-radius.scss new
+22
@@ -0,0 +1,22 @@
1 +//************************************************************************//
2 +// Shorthand Border-radius mixins
3 +//************************************************************************//
4 +@mixin border-top-radius($radii) {
5 + @include prefixer(border-top-left-radius, $radii, spec);
6 + @include prefixer(border-top-right-radius, $radii, spec);
7 +}
8 +
9 +@mixin border-bottom-radius($radii) {
10 + @include prefixer(border-bottom-left-radius, $radii, spec);
11 + @include prefixer(border-bottom-right-radius, $radii, spec);
12 +}
13 +
14 +@mixin border-left-radius($radii) {
15 + @include prefixer(border-top-left-radius, $radii, spec);
16 + @include prefixer(border-bottom-left-radius, $radii, spec);
17 +}
18 +
19 +@mixin border-right-radius($radii) {
20 + @include prefixer(border-top-right-radius, $radii, spec);
21 + @include prefixer(border-bottom-right-radius, $radii, spec);
22 +}
docs/_css/bourbon/css3/_box-sizing.scss new
+4
@@ -0,0 +1,4 @@
1 +@mixin box-sizing ($box) {
2 +// content-box | border-box | inherit
3 + @include prefixer(box-sizing, $box, webkit moz spec);
4 +}
docs/_css/bourbon/css3/_columns.scss new
+47
@@ -0,0 +1,47 @@
1 +@mixin columns($arg: auto) {
2 +// <column-count> || <column-width>
3 + @include prefixer(columns, $arg, webkit moz spec);
4 +}
5 +
6 +@mixin column-count($int: auto) {
7 +// auto || integer
8 + @include prefixer(column-count, $int, webkit moz spec);
9 +}
10 +
11 +@mixin column-gap($length: normal) {
12 +// normal || length
13 + @include prefixer(column-gap, $length, webkit moz spec);
14 +}
15 +
16 +@mixin column-fill($arg: auto) {
17 +// auto || length
18 + @include prefixer(columns-fill, $arg, webkit moz spec);
19 +}
20 +
21 +@mixin column-rule($arg) {
22 +// <border-width> || <border-style> || <color>
23 + @include prefixer(column-rule, $arg, webkit moz spec);
24 +}
25 +
26 +@mixin column-rule-color($color) {
27 + @include prefixer(column-rule-color, $color, webkit moz spec);
28 +}
29 +
30 +@mixin column-rule-style($style: none) {
31 +// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid
32 + @include prefixer(column-rule-style, $style, webkit moz spec);
33 +}
34 +
35 +@mixin column-rule-width ($width: none) {
36 + @include prefixer(column-rule-width, $width, webkit moz spec);
37 +}
38 +
39 +@mixin column-span($arg: none) {
40 +// none || all
41 + @include prefixer(column-span, $arg, webkit moz spec);
42 +}
43 +
44 +@mixin column-width($length: auto) {
45 +// auto || length
46 + @include prefixer(column-width, $length, webkit moz spec);
47 +}
docs/_css/bourbon/css3/_flex-box.scss new
+52
@@ -0,0 +1,52 @@
1 +// CSS3 Flexible Box Model and property defaults
2 +
3 +// Custom shorthand notation for flexbox
4 +@mixin box($orient: inline-axis, $pack: start, $align: stretch) {
5 + @include display-box;
6 + @include box-orient($orient);
7 + @include box-pack($pack);
8 + @include box-align($align);
9 +}
10 +
11 +@mixin display-box {
12 + display: -webkit-box;
13 + display: -moz-box;
14 + display: box;
15 +}
16 +
17 +@mixin box-orient($orient: inline-axis) {
18 +// horizontal|vertical|inline-axis|block-axis|inherit
19 + @include prefixer(box-orient, $orient, webkit moz spec);
20 +}
21 +
22 +@mixin box-pack($pack: start) {
23 +// start|end|center|justify
24 + @include prefixer(box-pack, $pack, webkit moz spec);
25 +}
26 +
27 +@mixin box-align($align: stretch) {
28 +// start|end|center|baseline|stretch
29 + @include prefixer(box-align, $align, webkit moz spec);
30 +}
31 +
32 +@mixin box-direction($direction: normal) {
33 +// normal|reverse|inherit
34 + @include prefixer(box-direction, $direction, webkit moz spec);
35 +}
36 +
37 +@mixin box-lines($lines: single) {
38 +// single|multiple
39 + @include prefixer(box-lines, $lines, webkit moz spec);
40 +}
41 +
42 +@mixin box-ordinal-group($int: 1) {
43 + @include prefixer(box-ordinal-group, $int, webkit moz spec);
44 +}
45 +
46 +@mixin box-flex($value: 0.0) {
47 + @include prefixer(box-flex, $value, webkit moz spec);
48 +}
49 +
50 +@mixin box-flex-group($int: 1) {
51 + @include prefixer(box-flex-group, $int, webkit moz spec);
52 +}
docs/_css/bourbon/css3/_font-face.scss new
+23
@@ -0,0 +1,23 @@
1 +// Order of the includes matters, and it is: normal, bold, italic, bold+italic.
2 +
3 +@mixin font-face($font-family, $file-path, $weight: normal, $style: normal, $asset-pipeline: false ) {
4 + @font-face {
5 + font-family: $font-family;
6 + font-weight: $weight;
7 + font-style: $style;
8 +
9 + @if $asset-pipeline == true {
10 + src: font-url('#{$file-path}.eot');
11 + src: font-url('#{$file-path}.eot?#iefix') format('embedded-opentype'),
12 + font-url('#{$file-path}.woff') format('woff'),
13 + font-url('#{$file-path}.ttf') format('truetype'),
14 + font-url('#{$file-path}.svg##{$font-family}') format('svg');
15 + } @else {
16 + src: url('#{$file-path}.eot');
17 + src: url('#{$file-path}.eot?#iefix') format('embedded-opentype'),
18 + url('#{$file-path}.woff') format('woff'),
19 + url('#{$file-path}.ttf') format('truetype'),
20 + url('#{$file-path}.svg##{$font-family}') format('svg');
21 + }
22 + }
23 +}
docs/_css/bourbon/css3/_hidpi-media-query.scss new
+10
@@ -0,0 +1,10 @@
1 +// HiDPI mixin. Default value set to 1.3 to target Google Nexus 7 (http://bjango.com/articles/min-device-pixel-ratio/)
2 +@mixin hidpi($ratio: 1.3) {
3 + @media only screen and (-webkit-min-device-pixel-ratio: $ratio),
4 + only screen and (min--moz-device-pixel-ratio: $ratio),
5 + only screen and (-o-min-device-pixel-ratio: #{$ratio}/1),
6 + only screen and (min-resolution: #{round($ratio*96)}dpi),
7 + only screen and (min-resolution: #{$ratio}dppx) {
8 + @content;
9 + }
10 +}
docs/_css/bourbon/css3/_image-rendering.scss new
+13
@@ -0,0 +1,13 @@
1 +@mixin image-rendering ($mode:optimizeQuality) {
2 +
3 + @if ($mode == optimize-contrast) {
4 + image-rendering: -moz-crisp-edges;
5 + image-rendering: -o-crisp-edges;
6 + image-rendering: -webkit-optimize-contrast;
7 + image-rendering: optimize-contrast;
8 + }
9 +
10 + @else {
11 + image-rendering: $mode;
12 + }
13 +}
docs/_css/bourbon/css3/_inline-block.scss new
+8
@@ -0,0 +1,8 @@
1 +// Legacy support for inline-block in IE7 (maybe IE6)
2 +@mixin inline-block {
3 + display: inline-block;
4 + vertical-align: baseline;
5 + zoom: 1;
6 + *display: inline;
7 + *vertical-align: auto;
8 +}
docs/_css/bourbon/css3/_keyframes.scss new
+43
@@ -0,0 +1,43 @@
1 +// Adds keyframes blocks for supported prefixes, removing redundant prefixes in the block's content
2 +@mixin keyframes($name) {
3 + $original-prefix-for-webkit: $prefix-for-webkit;
4 + $original-prefix-for-mozilla: $prefix-for-mozilla;
5 + $original-prefix-for-microsoft: $prefix-for-microsoft;
6 + $original-prefix-for-opera: $prefix-for-opera;
7 + $original-prefix-for-spec: $prefix-for-spec;
8 +
9 + @if $original-prefix-for-webkit {
10 + @include disable-prefix-for-all();
11 + $prefix-for-webkit: true;
12 + @-webkit-keyframes #{$name} {
13 + @content;
14 + }
15 + }
16 + @if $original-prefix-for-mozilla {
17 + @include disable-prefix-for-all();
18 + $prefix-for-mozilla: true;
19 + @-moz-keyframes #{$name} {
20 + @content;
21 + }
22 + }
23 + @if $original-prefix-for-opera {
24 + @include disable-prefix-for-all();
25 + $prefix-for-opera: true;
26 + @-o-keyframes #{$name} {
27 + @content;
28 + }
29 + }
30 + @if $original-prefix-for-spec {
31 + @include disable-prefix-for-all();
32 + $prefix-for-spec: true;
33 + @keyframes #{$name} {
34 + @content;
35 + }
36 + }
37 +
38 + $prefix-for-webkit: $original-prefix-for-webkit;
39 + $prefix-for-mozilla: $original-prefix-for-mozilla;
40 + $prefix-for-microsoft: $original-prefix-for-microsoft;
41 + $prefix-for-opera: $original-prefix-for-opera;
42 + $prefix-for-spec: $original-prefix-for-spec;
43 +}
docs/_css/bourbon/css3/_linear-gradient.scss new
+41
@@ -0,0 +1,41 @@
1 +@mixin linear-gradient($pos, $G1, $G2: false,
2 + $G3: false, $G4: false,
3 + $G5: false, $G6: false,
4 + $G7: false, $G8: false,
5 + $G9: false, $G10: false,
6 + $deprecated-pos1: left top,
7 + $deprecated-pos2: left bottom,
8 + $fallback: false) {
9 + // Detect what type of value exists in $pos
10 + $pos-type: type-of(nth($pos, 1));
11 + $pos-spec: null;
12 + $pos-degree: null;
13 +
14 + // If $pos is missing from mixin, reassign vars and add default position
15 + @if ($pos-type == color) or (nth($pos, 1) == "transparent") {
16 + $G10: $G9; $G9: $G8; $G8: $G7; $G7: $G6; $G6: $G5;
17 + $G5: $G4; $G4: $G3; $G3: $G2; $G2: $G1; $G1: $pos;
18 + $pos: null;
19 + }
20 +
21 + @if $pos {
22 + $positions: _linear-positions-parser($pos);
23 + $pos-degree: nth($positions, 1);
24 + $pos-spec: nth($positions, 2);
25 + }
26 +
27 + $full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10);
28 +
29 + // Set $G1 as the default fallback color
30 + $fallback-color: nth($G1, 1);
31 +
32 + // If $fallback is a color use that color as the fallback color
33 + @if (type-of($fallback) == color) or ($fallback == "transparent") {
34 + $fallback-color: $fallback;
35 + }
36 +
37 + background-color: $fallback-color;
38 + background-image: _deprecated-webkit-gradient(linear, $deprecated-pos1, $deprecated-pos2, $full); // Safari <= 5.0
39 + background-image: -webkit-linear-gradient($pos-degree $full); // Safari 5.1+, Chrome
40 + background-image: unquote("linear-gradient(#{$pos-spec}#{$full})");
41 +}
docs/_css/bourbon/css3/_perspective.scss new
+8
@@ -0,0 +1,8 @@
1 +@mixin perspective($depth: none) {
2 + // none | <length>
3 + @include prefixer(perspective, $depth, webkit moz spec);
4 +}
5 +
6 +@mixin perspective-origin($value: 50% 50%) {
7 + @include prefixer(perspective-origin, $value, webkit moz spec);
8 +}
docs/_css/bourbon/css3/_placeholder.scss new
+29
@@ -0,0 +1,29 @@
1 +$placeholders: '-webkit-input-placeholder',
2 + '-moz-placeholder',
3 + '-ms-input-placeholder';
4 +
5 +@mixin placeholder {
6 + @each $placeholder in $placeholders {
7 + @if $placeholder == "-webkit-input-placeholder" {
8 + &::#{$placeholder} {
9 + @content;
10 + }
11 + }
12 + @else if $placeholder == "-moz-placeholder" {
13 + // FF 18-
14 + &:#{$placeholder} {
15 + @content;
16 + }
17 +
18 + // FF 19+
19 + &::#{$placeholder} {
20 + @content;
21 + }
22 + }
23 + @else {
24 + &:#{$placeholder} {
25 + @content;
26 + }
27 + }
28 + }
29 +}
docs/_css/bourbon/css3/_radial-gradient.scss new
+44
@@ -0,0 +1,44 @@
1 +// Requires Sass 3.1+
2 +@mixin radial-gradient($G1, $G2,
3 + $G3: false, $G4: false,
4 + $G5: false, $G6: false,
5 + $G7: false, $G8: false,
6 + $G9: false, $G10: false,
7 + $pos: null,
8 + $shape-size: null,
9 + $deprecated-pos1: center center,
10 + $deprecated-pos2: center center,
11 + $deprecated-radius1: 0,
12 + $deprecated-radius2: 460,
13 + $fallback: false) {
14 +
15 + $data: _radial-arg-parser($G1, $G2, $pos, $shape-size);
16 + $G1: nth($data, 1);
17 + $G2: nth($data, 2);
18 + $pos: nth($data, 3);
19 + $shape-size: nth($data, 4);
20 +
21 + $full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10);
22 +
23 + // Strip deprecated cover/contain for spec
24 + $shape-size-spec: _shape-size-stripper($shape-size);
25 +
26 + // Set $G1 as the default fallback color
27 + $first-color: nth($full, 1);
28 + $fallback-color: nth($first-color, 1);
29 +
30 + @if (type-of($fallback) == color) or ($fallback == "transparent") {
31 + $fallback-color: $fallback;
32 + }
33 +
34 + // Add Commas and spaces
35 + $shape-size: if($shape-size, '#{$shape-size}, ', null);
36 + $pos: if($pos, '#{$pos}, ', null);
37 + $pos-spec: if($pos, 'at #{$pos}', null);
38 + $shape-size-spec: if(($shape-size-spec != ' ') and ($pos == null), '#{$shape-size-spec}, ', '#{$shape-size-spec} ');
39 +
40 + background-color: $fallback-color;
41 + background-image: _deprecated-webkit-gradient(radial, $deprecated-pos1, $deprecated-pos2, $full, $deprecated-radius1, $deprecated-radius2); // Safari <= 5.0 && IOS 4
42 + background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full}));
43 + background-image: unquote("radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})");
44 +}
docs/_css/bourbon/css3/_transform.scss new
+15
@@ -0,0 +1,15 @@
1 +@mixin transform($property: none) {
2 +// none | <transform-function>
3 + @include prefixer(transform, $property, webkit moz ms o spec);
4 +}
5 +
6 +@mixin transform-origin($axes: 50%) {
7 +// x-axis - left | center | right | length | %
8 +// y-axis - top | center | bottom | length | %
9 +// z-axis - length
10 + @include prefixer(transform-origin, $axes, webkit moz ms o spec);
11 +}
12 +
13 +@mixin transform-style ($style: flat) {
14 + @include prefixer(transform-style, $style, webkit moz ms o spec);
15 +}
docs/_css/bourbon/css3/_transition.scss new
+34
@@ -0,0 +1,34 @@
1 +// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable.
2 +// Example: @include transition (all, 2.0s, ease-in-out);
3 +// @include transition ((opacity, width), (1.0s, 2.0s), ease-in, (0, 2s));
4 +// @include transition ($property:(opacity, width), $delay: (1.5s, 2.5s));
5 +
6 +@mixin transition ($properties...) {
7 + @if length($properties) >= 1 {
8 + @include prefixer(transition, $properties, webkit moz spec);
9 + }
10 +
11 + @else {
12 + $properties: all 0.15s ease-out 0;
13 + @include prefixer(transition, $properties, webkit moz spec);
14 + }
15 +}
16 +
17 +@mixin transition-property ($properties...) {
18 + -webkit-transition-property: transition-property-names($properties, 'webkit');
19 + -moz-transition-property: transition-property-names($properties, 'moz');
20 + transition-property: transition-property-names($properties, false);
21 +}
22 +
23 +@mixin transition-duration ($times...) {
24 + @include prefixer(transition-duration, $times, webkit moz spec);
25 +}
26 +
27 +@mixin transition-timing-function ($motions...) {
28 +// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier()
29 + @include prefixer(transition-timing-function, $motions, webkit moz spec);
30 +}
31 +
32 +@mixin transition-delay ($times...) {
33 + @include prefixer(transition-delay, $times, webkit moz spec);
34 +}
docs/_css/bourbon/css3/_user-select.scss new
+3
@@ -0,0 +1,3 @@
1 +@mixin user-select($arg: none) {
2 + @include prefixer(user-select, $arg, webkit moz ms spec);
3 +}
docs/_css/bourbon/functions/_compact.scss new
+11
@@ -0,0 +1,11 @@
1 +// Remove `false` values from a list
2 +
3 +@function compact($vars...) {
4 + $list: ();
5 + @each $var in $vars {
6 + @if $var {
7 + $list: append($list, $var, comma);
8 + }
9 + }
10 + @return $list;
11 +}
docs/_css/bourbon/functions/_flex-grid.scss new
+39
@@ -0,0 +1,39 @@
1 +// Flexible grid
2 +@function flex-grid($columns, $container-columns: $fg-max-columns) {
3 + $width: $columns * $fg-column + ($columns - 1) * $fg-gutter;
4 + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
5 + @return percentage($width / $container-width);
6 +}
7 +
8 +// Flexible gutter
9 +@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {
10 + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
11 + @return percentage($gutter / $container-width);
12 +}
13 +
14 +// The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function.
15 +// This function takes the fluid grid equation (target / context = result) and uses columns to help define each.
16 +//
17 +// The calculation presumes that your column structure will be missing the last gutter:
18 +//
19 +// -- column -- gutter -- column -- gutter -- column
20 +//
21 +// $fg-column: 60px; // Column Width
22 +// $fg-gutter: 25px; // Gutter Width
23 +// $fg-max-columns: 12; // Total Columns For Main Container
24 +//
25 +// div {
26 +// width: flex-grid(4); // returns (315px / 995px) = 31.65829%;
27 +// margin-left: flex-gutter(); // returns (25px / 995px) = 2.51256%;
28 +//
29 +// p {
30 +// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;
31 +// float: left;
32 +// margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%;
33 +// }
34 +//
35 +// blockquote {
36 +// float: left;
37 +// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;
38 +// }
39 +// }
\ No newline at end of file
docs/_css/bourbon/functions/_grid-width.scss new
+13
@@ -0,0 +1,13 @@
1 +@function grid-width($n) {
2 + @return $n * $gw-column + ($n - 1) * $gw-gutter;
3 +}
4 +
5 +// The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function.
6 +//
7 +// $gw-column: 100px; // Column Width
8 +// $gw-gutter: 40px; // Gutter Width
9 +//
10 +// div {
11 +// width: grid-width(4); // returns 520px;
12 +// margin-left: $gw-gutter; // returns 40px;
13 +// }
docs/_css/bourbon/functions/_linear-gradient.scss new
+13
@@ -0,0 +1,13 @@
1 +@function linear-gradient($pos, $gradients...) {
2 + $type: linear;
3 + $pos-type: type-of(nth($pos, 1));
4 +
5 + // if $pos doesn't exist, fix $gradient
6 + @if ($pos-type == color) or (nth($pos, 1) == "transparent") {
7 + $gradients: zip($pos $gradients);
8 + $pos: false;
9 + }
10 +
11 + $type-gradient: $type, $pos, $gradients;
12 + @return $type-gradient;
13 +}
docs/_css/bourbon/functions/_modular-scale.scss new
+40
@@ -0,0 +1,40 @@
1 +@function modular-scale($value, $increment, $ratio) {
2 + @if $increment > 0 {
3 + @for $i from 1 through $increment {
4 + $value: ($value * $ratio);
5 + }
6 + }
7 +
8 + @if $increment < 0 {
9 + $increment: abs($increment);
10 + @for $i from 1 through $increment {
11 + $value: ($value / $ratio);
12 + }
13 + }
14 +
15 + @return $value;
16 +}
17 +
18 +// div {
19 +// Increment Up GR with positive value
20 +// font-size: modular-scale(14px, 1, 1.618); // returns: 22.652px
21 +//
22 +// Increment Down GR with negative value
23 +// font-size: modular-scale(14px, -1, 1.618); // returns: 8.653px
24 +//
25 +// Can be used with ceil(round up) or floor(round down)
26 +// font-size: floor( modular-scale(14px, 1, 1.618) ); // returns: 22px
27 +// font-size: ceil( modular-scale(14px, 1, 1.618) ); // returns: 23px
28 +// }
29 +//
30 +// modularscale.com
31 +
32 +@function golden-ratio($value, $increment) {
33 + @return modular-scale($value, $increment, 1.618)
34 +}
35 +
36 +// div {
37 +// font-size: golden-ratio(14px, 1); // returns: 22.652px
38 +// }
39 +//
40 +// goldenratiocalculator.com
docs/_css/bourbon/functions/_px-to-em.scss new
+8
@@ -0,0 +1,8 @@
1 +// Convert pixels to ems
2 +// eg. for a relational value of 12px write em(12) when the parent is 16px
3 +// if the parent is another value say 24px write em(12, 24)
4 +
5 +@function em($pxval, $base: 16) {
6 + @return ($pxval / $base) * 1em;
7 +}
8 +
docs/_css/bourbon/functions/_radial-gradient.scss new
+23
@@ -0,0 +1,23 @@
1 +// This function is required and used by the background-image mixin.
2 +@function radial-gradient($G1, $G2,
3 + $G3: false, $G4: false,
4 + $G5: false, $G6: false,
5 + $G7: false, $G8: false,
6 + $G9: false, $G10: false,
7 + $pos: null,
8 + $shape-size: null) {
9 +
10 + $data: _radial-arg-parser($G1, $G2, $pos, $shape-size);
11 + $G1: nth($data, 1);
12 + $G2: nth($data, 2);
13 + $pos: nth($data, 3);
14 + $shape-size: nth($data, 4);
15 +
16 + $type: radial;
17 + $gradient: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10);
18 +
19 + $type-gradient: $type, $shape-size $pos, $gradient;
20 + @return $type-gradient;
21 +}
22 +
23 +
docs/_css/bourbon/functions/_tint-shade.scss new
+9
@@ -0,0 +1,9 @@
1 +// Add percentage of white to a color
2 +@function tint($color, $percent){
3 + @return mix(white, $color, $percent);
4 +}
5 +
6 +// Add percentage of black to a color
7 +@function shade($color, $percent){
8 + @return mix(black, $color, $percent);
9 +}
docs/_css/bourbon/functions/_transition-property-name.scss new
+22
@@ -0,0 +1,22 @@
1 +// Return vendor-prefixed property names if appropriate
2 +// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background
3 +//************************************************************************//
4 +@function transition-property-names($props, $vendor: false) {
5 + $new-props: ();
6 +
7 + @each $prop in $props {
8 + $new-props: append($new-props, transition-property-name($prop, $vendor), comma);
9 + }
10 +
11 + @return $new-props;
12 +}
13 +
14 +@function transition-property-name($prop, $vendor: false) {
15 + // put other properties that need to be prefixed here aswell
16 + @if $vendor and $prop == transform {
17 + @return unquote('-'+$vendor+'-'+$prop);
18 + }
19 + @else {
20 + @return $prop;
21 + }
22 +}
\ No newline at end of file
docs/_css/bourbon/helpers/_deprecated-webkit-gradient.scss new
+39
@@ -0,0 +1,39 @@
1 +// Render Deprecated Webkit Gradient - Linear || Radial
2 +//************************************************************************//
3 +@function _deprecated-webkit-gradient($type,
4 + $deprecated-pos1, $deprecated-pos2,
5 + $full,
6 + $deprecated-radius1: false, $deprecated-radius2: false) {
7 + $gradient-list: ();
8 + $gradient: false;
9 + $full-length: length($full);
10 + $percentage: false;
11 + $gradient-type: $type;
12 +
13 + @for $i from 1 through $full-length {
14 + $gradient: nth($full, $i);
15 +
16 + @if length($gradient) == 2 {
17 + $color-stop: color-stop(nth($gradient, 2), nth($gradient, 1));
18 + $gradient-list: join($gradient-list, $color-stop, comma);
19 + }
20 + @else if $gradient != null {
21 + @if $i == $full-length {
22 + $percentage: 100%;
23 + }
24 + @else {
25 + $percentage: ($i - 1) * (100 / ($full-length - 1)) + "%";
26 + }
27 + $color-stop: color-stop(unquote($percentage), $gradient);
28 + $gradient-list: join($gradient-list, $color-stop, comma);
29 + }
30 + }
31 +
32 + @if $type == radial {
33 + $gradient: -webkit-gradient(radial, $deprecated-pos1, $deprecated-radius1, $deprecated-pos2, $deprecated-radius2, $gradient-list);
34 + }
35 + @else if $type == linear {
36 + $gradient: -webkit-gradient(linear, $deprecated-pos1, $deprecated-pos2, $gradient-list);
37 + }
38 + @return $gradient;
39 +}
docs/_css/bourbon/helpers/_gradient-positions-parser.scss new
+13
@@ -0,0 +1,13 @@
1 +@function _gradient-positions-parser($gradient-type, $gradient-positions) {
2 + @if $gradient-positions
3 + and ($gradient-type == linear)
4 + and (type-of($gradient-positions) != color) {
5 + $gradient-positions: _linear-positions-parser($gradient-positions);
6 + }
7 + @else if $gradient-positions
8 + and ($gradient-type == radial)
9 + and (type-of($gradient-positions) != color) {
10 + $gradient-positions: _radial-positions-parser($gradient-positions);
11 + }
12 + @return $gradient-positions;
13 +}
docs/_css/bourbon/helpers/_linear-positions-parser.scss new
+61
@@ -0,0 +1,61 @@
1 +@function _linear-positions-parser($pos) {
2 + $type: type-of(nth($pos, 1));
3 + $spec: null;
4 + $degree: null;
5 + $side: null;
6 + $corner: null;
7 + $length: length($pos);
8 + // Parse Side and corner positions
9 + @if ($length > 1) {
10 + @if nth($pos, 1) == "to" { // Newer syntax
11 + $side: nth($pos, 2);
12 +
13 + @if $length == 2 { // eg. to top
14 + // Swap for backwards compatability
15 + $degree: _position-flipper(nth($pos, 2));
16 + }
17 + @else if $length == 3 { // eg. to top left
18 + $corner: nth($pos, 3);
19 + }
20 + }
21 + @else if $length == 2 { // Older syntax ("top left")
22 + $side: _position-flipper(nth($pos, 1));
23 + $corner: _position-flipper(nth($pos, 2));
24 + }
25 +
26 + @if ("#{$side} #{$corner}" == "left top") or ("#{$side} #{$corner}" == "top left") {
27 + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
28 + }
29 + @else if ("#{$side} #{$corner}" == "right top") or ("#{$side} #{$corner}" == "top right") {
30 + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
31 + }
32 + @else if ("#{$side} #{$corner}" == "right bottom") or ("#{$side} #{$corner}" == "bottom right") {
33 + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
34 + }
35 + @else if ("#{$side} #{$corner}" == "left bottom") or ("#{$side} #{$corner}" == "bottom left") {
36 + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
37 + }
38 + $spec: to $side $corner;
39 + }
40 + @else if $length == 1 {
41 + // Swap for backwards compatability
42 + @if $type == string {
43 + $degree: $pos;
44 + $spec: to _position-flipper($pos);
45 + }
46 + @else {
47 + $degree: -270 - $pos; //rotate the gradient opposite from spec
48 + $spec: $pos;
49 + }
50 + }
51 + $degree: unquote($degree + ",");
52 + $spec: unquote($spec + ",");
53 + @return $degree $spec;
54 +}
55 +
56 +@function _position-flipper($pos) {
57 + @return if($pos == left, right, null)
58 + if($pos == right, left, null)
59 + if($pos == top, bottom, null)
60 + if($pos == bottom, top, null);
61 +}
docs/_css/bourbon/helpers/_radial-arg-parser.scss new
+69
@@ -0,0 +1,69 @@
1 +@function _radial-arg-parser($G1, $G2, $pos, $shape-size) {
2 + @each $value in $G1, $G2 {
3 + $first-val: nth($value, 1);
4 + $pos-type: type-of($first-val);
5 + $spec-at-index: null;
6 +
7 + // Determine if spec was passed to mixin
8 + @if type-of($value) == list {
9 + $spec-at-index: if(index($value, at), index($value, at), false);
10 + }
11 + @if $spec-at-index {
12 + @if $spec-at-index > 1 {
13 + @for $i from 1 through ($spec-at-index - 1) {
14 + $shape-size: $shape-size nth($value, $i);
15 + }
16 + @for $i from ($spec-at-index + 1) through length($value) {
17 + $pos: $pos nth($value, $i);
18 + }
19 + }
20 + @else if $spec-at-index == 1 {
21 + @for $i from ($spec-at-index + 1) through length($value) {
22 + $pos: $pos nth($value, $i);
23 + }
24 + }
25 + $G1: false;
26 + }
27 +
28 + // If not spec calculate correct values
29 + @else {
30 + @if ($pos-type != color) or ($first-val != "transparent") {
31 + @if ($pos-type == number)
32 + or ($first-val == "center")
33 + or ($first-val == "top")
34 + or ($first-val == "right")
35 + or ($first-val == "bottom")
36 + or ($first-val == "left") {
37 +
38 + $pos: $value;
39 +
40 + @if $pos == $G1 {
41 + $G1: false;
42 + }
43 + }
44 +
45 + @else if
46 + ($first-val == "ellipse")
47 + or ($first-val == "circle")
48 + or ($first-val == "closest-side")
49 + or ($first-val == "closest-corner")
50 + or ($first-val == "farthest-side")
51 + or ($first-val == "farthest-corner")
52 + or ($first-val == "contain")
53 + or ($first-val == "cover") {
54 +
55 + $shape-size: $value;
56 +
57 + @if $value == $G1 {
58 + $G1: false;
59 + }
60 +
61 + @else if $value == $G2 {
62 + $G2: false;
63 + }
64 + }
65 + }
66 + }
67 + }
68 + @return $G1, $G2, $pos, $shape-size;
69 +}
docs/_css/bourbon/helpers/_radial-positions-parser.scss new
+18
@@ -0,0 +1,18 @@
1 +@function _radial-positions-parser($gradient-pos) {
2 + $shape-size: nth($gradient-pos, 1);
3 + $pos: nth($gradient-pos, 2);
4 + $shape-size-spec: _shape-size-stripper($shape-size);
5 +
6 + $pre-spec: unquote(if($pos, "#{$pos}, ", null))
7 + unquote(if($shape-size, "#{$shape-size},", null));
8 + $pos-spec: if($pos, "at #{$pos}", null);
9 +
10 + $spec: "#{$shape-size-spec} #{$pos-spec}";
11 +
12 + // Add comma
13 + @if ($spec != ' ') {
14 + $spec: "#{$spec},"
15 + }
16 +
17 + @return $pre-spec $spec;
18 +}
docs/_css/bourbon/helpers/_render-gradients.scss new
+26
@@ -0,0 +1,26 @@
1 +// User for linear and radial gradients within background-image or border-image properties
2 +
3 +@function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) {
4 + $pre-spec: null;
5 + $spec: null;
6 + $vendor-gradients: null;
7 + @if $gradient-type == linear {
8 + @if $gradient-positions {
9 + $pre-spec: nth($gradient-positions, 1);
10 + $spec: nth($gradient-positions, 2);
11 + }
12 + }
13 + @else if $gradient-type == radial {
14 + $pre-spec: nth($gradient-positions, 1);
15 + $spec: nth($gradient-positions, 2);
16 + }
17 +
18 + @if $vendor {
19 + $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients);
20 + }
21 + @else if $vendor == false {
22 + $vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})";
23 + $vendor-gradients: unquote($vendor-gradients);
24 + }
25 + @return $vendor-gradients;
26 +}
docs/_css/bourbon/helpers/_shape-size-stripper.scss new
+10
@@ -0,0 +1,10 @@
1 +@function _shape-size-stripper($shape-size) {
2 + $shape-size-spec: null;
3 + @each $value in $shape-size {
4 + @if ($value == "cover") or ($value == "contain") {
5 + $value: null;
6 + }
7 + $shape-size-spec: "#{$shape-size-spec} #{$value}";
8 + }
9 + @return $shape-size-spec;
10 +}
docs/_css/react.scss new
+630
@@ -0,0 +1,630 @@
1 +@import 'bourbon/bourbon';
2 +@import '_variables';
3 +@import '_typography';
4 +@import '_solarized';
5 +
6 +$skinnyContentWidth: 650px;
7 +$contentWidth: 920px;
8 +$contentPadding: 20px;
9 +$columnWidth: 280px;
10 +$columnGutter: 40px;
11 +$twoColumnWidth: 2 * $columnWidth + $columnGutter;
12 +
13 +
14 +
15 +// basic reset
16 +* {
17 + @include box-sizing(border-box);
18 + border: none;
19 + margin: 0;
20 + padding: 0;
21 +}
22 +
23 +html {
24 + background: $pageBg;
25 +}
26 +
27 +
28 +
29 +.left {
30 + float: left;
31 +}
32 +
33 +.right {
34 + float: right;
35 +}
36 +
37 +
38 +.container {
39 + padding-top: 50px;
40 + min-width: $contentWidth + (2 * $contentPadding);
41 +}
42 +
43 +.wrap {
44 + width: $contentWidth + (2 * $contentPadding);
45 + margin-left: auto;
46 + margin-right: auto;
47 + padding-left: 20px;
48 + padding-right: 20px;
49 +}
50 +
51 +.skinnyWrap {
52 + width: $skinnyContentWidth + (2 * $contentPadding);
53 + margin-left: auto;
54 + margin-right: auto;
55 + padding-left: 20px;
56 + padding-right: 20px;
57 +}
58 +
59 +hr {
60 + height: 0;
61 + border-top: 1px solid #ccc;
62 + border-bottom: 1px solid #eee;
63 +}
64 +
65 +ul,
66 +li {
67 + margin-left: 20px;
68 +}
69 +
70 +// Main Nav
71 +
72 +.nav-main {
73 + @include clearfix;
74 + background: $darkestColor;
75 + color: $lightTextColor;
76 + position: fixed;
77 + top: 0;
78 + height: 50px;
79 + box-shadow: 0 0 5px rgba(0, 0, 0, .5);
80 + width: 100%;
81 + z-index: 100;
82 +
83 + a {
84 + color: $lightColor;
85 + text-decoration: none;
86 + }
87 +
88 + .nav-site {
89 + float: right;
90 + margin: 0;
91 +
92 + li {
93 + margin: 0;
94 + }
95 +
96 + a {
97 + padding: 0 8px;
98 + text-transform: uppercase;
99 + letter-spacing: 1px;
100 + line-height: 50px;
101 + display: inline-block;
102 + height: 50px;
103 + color: $mediumTextColor;
104 +
105 + &:hover {
106 + color: $lightTextColor;
107 + }
108 +
109 + &.active {
110 + color: $lightTextColor;
111 + border-bottom: 3px solid $primary;
112 + background: #333;
113 + }
114 + }
115 + }
116 +
117 + .nav-home {
118 + color: #00d8ff;
119 + font-size: 24px;
120 + line-height: 50px;
121 + }
122 +
123 + .nav-logo {
124 + @include retina-image('../img/logo_small', 38px 38px);
125 + vertical-align: middle;
126 + }
127 +
128 + ul {
129 + display: inline;
130 + }
131 +
132 + li {
133 + display: inline;
134 + }
135 +}
136 +
137 +// Hero!
138 +
139 +.hero {
140 + height: 300px;
141 + // background: $darkColor url(../img/header.png) no-repeat center;
142 + background: $darkColor;
143 + padding-top: 50px;
144 + color: $lightColor;
145 + font-weight: 300;
146 +
147 + .text {
148 + font-size: 64px;
149 + text-align: center;
150 + }
151 +
152 + .minitext {
153 + font-size: 16px;
154 + text-align: center;
155 + text-transform: uppercase;
156 + }
157 +
158 + strong {
159 + color: $blueColor;
160 + font-weight: 400;
161 + }
162 +}
163 +
164 +.buttons-unit {
165 + margin-top: 60px;
166 + text-align: center;
167 +
168 + a {
169 + color: $blueColor;
170 + }
171 +
172 + .button {
173 + font-size: 24px;
174 + background: $primary;
175 + color: $lightTextColor;
176 +
177 + &:active {
178 + background: darken($primary, 5%);
179 + }
180 + }
181 +}
182 +
183 +// Downloads
184 +
185 +.buttons-unit.downloads {
186 + margin: 30px 0;
187 +}
188 +
189 +// Docs Nav
190 +
191 +.nav-docs {
192 + color: $darkColor;
193 + font-size: 14px;
194 + // position: fixed;
195 + float: left;
196 + top: 100px;
197 + width: 180px;
198 +
199 + ul {
200 + list-style: none;
201 + margin: 0;
202 + }
203 + li {
204 + margin: 0;
205 + }
206 + h3 {
207 + text-transform: uppercase;
208 + font-size: 14px;
209 + }
210 + a {
211 + color: $mediumestColor;
212 + display: block;
213 +
214 + &:hover {
215 + text-decoration: none;
216 + color: $primary;
217 + }
218 +
219 + &.active {
220 + color: $primary;
221 + }
222 + }
223 +
224 + .nav-docs-section {
225 + border-bottom: 1px solid #ccc;
226 + border-top: 1px solid #eee;
227 + padding: 12px 0;
228 +
229 + &:first-child {
230 + padding-top: 0;
231 + border-top: 0;
232 + }
233 +
234 + &:last-child {
235 + padding-bottom: 0;
236 + border-bottom: 0;
237 + }
238 + }
239 +
240 +}
241 +
242 +// Home Page specifics
243 +
244 +.home-section {
245 + margin: 50px 0;
246 +}
247 +
248 +.home-divider {
249 + border-top-color: #bbb;
250 + margin: 0 auto;
251 + width: 400px;
252 +}
253 +
254 +.marketing-row {
255 + @include clearfix;
256 + margin: 50px 0;
257 +}
258 +
259 +.marketing-col {
260 + float: left;
261 + margin-right: 40px;
262 + width: $columnWidth;
263 +
264 + h3 {
265 + color: $darkColor;
266 + font-size: 24px;
267 + font-weight: normal;
268 + text-transform: uppercase;
269 + }
270 + p {
271 + font-size: 16px;
272 + }
273 +}
274 +
275 +.marketing-col:last-child {
276 + margin-right: 0;
277 +}
278 +
279 +#examples {
280 + h3 {
281 + color: $darkColor;
282 + font-size: 24px;
283 + font-weight: normal;
284 + margin-bottom: 5px;
285 + }
286 +
287 + p {
288 + margin: 0 0 25px 0;
289 + max-width: $twoColumnWidth;
290 + }
291 +
292 + .example {
293 + margin-top: 60px;
294 + }
295 +
296 + #todoExample {
297 + font-size: 14px;
298 + ul {
299 + list-style-type: square;
300 + margin: 0 0 10px 0;
301 + }
302 + input {
303 + border: 1px solid #ccc;
304 + font: 14px proxima-nova, $helvetica;
305 + padding: 3px;
306 + width: 150px;
307 + }
308 + button {
309 + font: 14px proxima-nova, $helvetica;
310 + margin-left: 5px;
311 + padding: 4px 10px;
312 + }
313 + }
314 +
315 + #markdownExample {
316 + textarea {
317 + border: 1px solid #ccc;
318 + font: 14px proxima-nova, $helvetica;
319 + margin-bottom: 10px;
320 + padding: 5px;
321 + }
322 + }
323 +}
324 +
325 +.home-bottom-section {
326 + margin-bottom: 100px;
327 +}
328 +
329 +.docs-nextprev {
330 + @include clearfix;
331 +}
332 +
333 +.docs-prev {
334 + float: left;
335 +}
336 +
337 +.docs-next {
338 + float: right;
339 +}
340 +
341 +footer {
342 + font-size: 13px;
343 + font-weight: 600;
344 + margin-top: 36px;
345 + margin-bottom: 18px;
346 + overflow: auto;
347 +}
348 +
349 +section.black content {
350 + padding-bottom: 18px;
351 +}
352 +
353 +/**
354 + * Docs
355 + */
356 +
357 +.documentationContent {
358 + @include clearfix;
359 +
360 + .subHeader {
361 + font-size: 24px;
362 + }
363 +
364 + // H2s form documentation topic dividers. Extra space helps.
365 + h2 {
366 + margin-top: 30px;
367 + }
368 +
369 + padding-top: 20px;
370 +
371 + // Make a notice box out of blockquotes in the documetation:
372 + blockquote {
373 + padding: 15px 30px 15px 15px;
374 + margin: 20px 0;
375 + background-color: rgba(204, 122, 111, 0.09999999999999998);
376 + border-left: 5px solid rgba(191, 87, 73, 0.19999999999999996);
377 + h4 {
378 + margin-top: 0;
379 + }
380 + p:last-child {
381 + margin-bottom: 0;
382 + }
383 + // Treat first child as the title - promote to H4.
384 + p:first-child {
385 + font-weight: bold;
386 + font-size: 17.5px;
387 + line-height: 20px;
388 + margin-top: 0;
389 + text-rendering: optimizelegibility;
390 + }
391 + }
392 +}
393 +
394 +.docs-prevnext {
395 + padding-top: 40px;
396 + padding-bottom: 40px;
397 +}
398 +
399 +
400 +/* Button */
401 +
402 +.button {
403 + @include background(linear-gradient($buttonGreyTop, $buttonGreyBottom));
404 + // border: 1px solid $darkestColor;
405 + border-radius: 4px;
406 + padding: 8px 16px;
407 + font-size: 18px;
408 + font-weight: 400;
409 + margin: 0 12px;
410 + // word-spacing: -2px;
411 + // letter-spacing: 1px;
412 + display: inline-block;
413 + color: $lightTextColor;
414 + text-decoration: none;
415 + text-shadow: 0 1px 3px rgba(0, 0, 0, .3);
416 + box-shadow: 0 1px 1px rgba(0, 0, 0, .2);
417 + text-decoration: none;
418 +
419 + &:hover {
420 + text-decoration: none;
421 + }
422 +
423 + &:active {
424 + box-shadow: none;
425 + }
426 +}
427 +
428 +.hero {
429 + .button {
430 + box-shadow: 1px 3px 3px rgba(0, 0, 0, .3);
431 + }
432 +}
433 +
434 +.button.blue {
435 + @include background(linear-gradient($buttonBlueTop, $buttonBlueBottom));
436 +}
437 +
438 +
439 +/* Row */
440 +
441 +.row {
442 + padding-bottom: 4px;
443 +}
444 +
445 +.row .span4 {
446 + width: 33.33%;
447 + display: table-cell;
448 +}
449 +
450 +.row .span8 {
451 + width: 66.66%;
452 + display: table-cell;
453 +}
454 +
455 +.row .span6 {
456 + width: 50%;
457 + display: table-cell;
458 +}
459 +
460 +
461 +/* Content */
462 +
463 +p {
464 + margin: 10px 0;
465 +}
466 +
467 +.highlight {
468 + padding: 10px;
469 + margin-bottom: 20px;
470 +}
471 +
472 +.inner-content {
473 + float: right;
474 + width: $skinnyContentWidth;
475 +}
476 +
477 +.nosidebar .inner-content {
478 + float: none;
479 + margin: 0 auto;
480 +}
481 +
482 +/* Code Mirror */
483 +
484 +div.CodeMirror pre, div.CodeMirror-linenumber, code {
485 + font-family: 'source-code-pro', Menlo, 'Courier New', Consolas, monospace;
486 + font-size: 13px;
487 + line-height: 20px;
488 +}
489 +
490 +div.CodeMirror-linenumber:after {
491 + content: '.';
492 +}
493 +
494 +.CodeMirror, div.CodeMirror-gutters, div.highlight {
495 + border: none;
496 +}
497 +
498 +small code,
499 +li code,
500 +p code {
501 + color: #555;
502 + background-color: rgba(0, 0, 0, .04);
503 + padding: 1px 3px;
504 +}
505 +
506 +.cm-s-default span.cm-string-2 {
507 + color: inherit;
508 +}
509 +
510 +.playground {
511 + @include clearfix;
512 +}
513 +
514 +.playground::before {
515 + border-bottom: none !important;
516 + border-radius: 3px 3px 0 0;
517 + padding: 3px 7px;
518 + font-size: 12px;
519 + font-weight: bold;
520 + color: #c2c0bc;
521 + background-color: #f1ede4;
522 + content: 'Live editor';
523 +}
524 +
525 +.playground::before,
526 +.playgroundCode,
527 +.playgroundPreview {
528 + border: 1px solid rgba(16,16,16,0.1);
529 +}
530 +
531 +.playgroundCode {
532 + border-radius: 0 3px 3px 3px;
533 + float: left;
534 + overflow: hidden;
535 + width: $twoColumnWidth;
536 +}
537 +
538 +.playgroundPreview {
539 + background-color: white;
540 + border-radius: 3px;
541 + float: right;
542 + padding: 15px 20px;
543 + width: $columnWidth;
544 +}
545 +
546 +.MarkdownEditor textarea {
547 + width: 100%;
548 + height: 100px
549 +}
550 +
551 +.hll {
552 + background-color: #f7ebc6;
553 + border-left: 5px solid #f7d87c;
554 + display: block;
555 + margin-left: -14px;
556 + margin-right: -14px;
557 + padding-left: 9px;
558 +}
559 +
560 +/* Codemirror doesn't support <jsx> syntax. Instead of highlighting it
561 + as error, just ignore it */
562 +.highlight .javascript .err {
563 + background-color: transparent;
564 + color: inherit;
565 +}
566 +
567 +.highlight {
568 + position: relative;
569 + margin-bottom: 14px;
570 + padding: 30px 14px 14px;
571 + border: none;
572 + border-radius: 0;
573 + overflow: auto;
574 +}
575 +
576 +
577 +.highlight pre {
578 + padding: 0;
579 + margin-top: 0;
580 + margin-bottom: 0;
581 + background-color: transparent;
582 + border: 0;
583 +}
584 +
585 +.highlight pre code {
586 + font-size: inherit;
587 +}
588 +
589 +.highlight pre .lineno {
590 + display: inline-block;
591 + width: 22px;
592 + padding-right: 5px;
593 + margin-right: 10px;
594 + color: #bebec5;
595 + text-align: right;
596 +}
597 +
598 +/* Echo out a label for the example */
599 +
600 +.highlight:after {
601 + position: absolute;
602 + top: 0;
603 + right: 0;
604 + left: 0;
605 + padding: 3px 7px;
606 + font-size: 12px;
607 + font-weight: bold;
608 + color: #c2c0bc;
609 + background-color: #f1ede4;
610 + content: "Code";
611 +}
612 +
613 +.downloadCenter {
614 + text-align: center;
615 + margin-top: 20px;
616 + margin-bottom: 25px;
617 +}
618 +
619 +.downloadSection:hover {
620 + text-decoration: none !important;
621 +}
622 +
623 +@media screen and (max-width: 960px) {
624 + .nav-main {
625 + position: static;
626 + }
627 + .container {
628 + padding-top: 0;
629 + }
630 +}
docs/_includes/nav_docs.html new
+23
@@ -0,0 +1,23 @@
1 +<div class="nav-docs">
2 + <div class="nav-docs-section">
3 + <h3>Getting started</h3>
4 + <ul>
5 + <li><a href="/react/docs/getting-started.html"{% if page.id == 'docs-getting-started' %} class="active"{% endif %}>Getting Started</a></li>
6 + <li><a href="/react/docs/tutorial.html"{% if page.id == 'docs-tutorial' %} class="active"{% endif %}>Tutorial</a></li>
7 + <li><a href="/react/docs/common-questions.html"{% if page.id == 'docs-common-questions' %} class="active"{% endif %}>Common Questions</a></li>
8 + </ul>
9 + </div>
10 +
11 + <div class="nav-docs-section">
12 + <h3>Reference</h3>
13 + <ul>
14 + <li><a href="/react/docs/syntax.html"{% if page.id == 'docs-syntax' %} class="active"{% endif %}>JSX Syntax</a></li>
15 + <li><a href="/react/docs/component-basics.html"{% if page.id == 'docs-component-basics' %} class="active"{% endif %}>Component Basics</a></li>
16 + <li><a href="/react/docs/component-data.html"{% if page.id == 'docs-component-data' %} class="active"{% endif %}>Component Data</a></li>
17 + <li><a href="/react/docs/component-lifecycle.html"{% if page.id == 'docs-component-lifecycle' %} class="active"{% endif %}>Component Lifecycle</a></li>
18 + <li><a href="/react/docs/event-handling.html"{% if page.id == 'docs-event-handling' %} class="active"{% endif %}>Event Handling</a></li>
19 + <li><a href="/react/docs/advanced-components.html"{% if page.id == 'docs-advanced-components' %} class="active"{% endif %}>Advanced Components</a></li>
20 + <li><a href="/react/docs/api.html"{% if page.id == 'docs-api' %} class="active"{% endif %}>API</a></li>
21 + </ul>
22 + </div>
23 +</div>
docs/_js/examples/hello.js new
+19
@@ -0,0 +1,19 @@
1 +/**
2 + * @jsx React.DOM
3 + */
4 +
5 +var HELLO_COMPONENT = "\
6 +/** @jsx React.DOM */\n\
7 +var HelloMessage = React.createClass({\n\
8 + render: function() {\n\
9 + return <div>{'Hello ' + this.props.name}</div>;\n\
10 + }\n\
11 +});\n\
12 +\n\
13 +React.renderComponent(<HelloMessage name=\"John\" />, mountNode);\
14 +";
15 +
16 +React.renderComponent(
17 + <ReactPlayground codeText={HELLO_COMPONENT} />,
18 + document.getElementById('helloExample')
19 +);
docs/_js/examples/markdown.js new
+42
@@ -0,0 +1,42 @@
1 +/**
2 + * @jsx React.DOM
3 + */
4 +
5 +var MARKDOWN_COMPONENT = "\
6 +/** @jsx React.DOM */\n\
7 +\n\
8 +var converter = new Showdown.converter();\n\
9 +\n\
10 +var MarkdownEditor = React.createClass({\n\
11 + getInitialState: function() {\n\
12 + return {value: 'Type some *markdown* here!'};\n\
13 + },\n\
14 + handleKeyUp: React.autoBind(function() {\n\
15 + this.setState({value: this.refs.textarea.getDOMNode().value});\n\
16 + }),\n\
17 + render: function() {\n\
18 + return (\n\
19 + <div className=\"MarkdownEditor\">\n\
20 + <h3>Input</h3>\n\
21 + <textarea onKeyUp={this.handleKeyUp} ref=\"textarea\">\n\
22 + {this.state.value}\n\
23 + </textarea>\n\
24 + <h3>Output</h3>\n\
25 + <div\n\
26 + className=\"content\"\n\
27 + dangerouslySetInnerHTML={{\n\
28 + __html: converter.makeHtml(this.state.value)\n\
29 + }}\n\
30 + />\n\
31 + </div>\n\
32 + );\n\
33 + }\n\
34 +});\n\
35 +\n\
36 +React.renderComponent(<MarkdownEditor />, mountNode);\
37 +";
38 +
39 +React.renderComponent(
40 + <ReactPlayground codeText={MARKDOWN_COMPONENT} />,
41 + document.getElementById('markdownExample')
42 +);
docs/_js/examples/timer.js new
+32
@@ -0,0 +1,32 @@
1 +/**
2 + * @jsx React.DOM
3 + */
4 +
5 +var TIMER_COMPONENT = "\
6 +/** @jsx React.DOM */\n\
7 +var Timer = React.createClass({\n\
8 + getInitialState: function() {\n\
9 + return {secondsElapsed: 0};\n\
10 + },\n\
11 + tick: React.autoBind(function() {\n\
12 + this.setState({secondsElapsed: this.state.secondsElapsed + 1});\n\
13 + }),\n\
14 + componentDidMount: function() {\n\
15 + setInterval(this.tick, 1000);\n\
16 + },\n\
17 + render: function() {\n\
18 + return (\n\
19 + <div>\n\
20 + {'Seconds Ellapsed: ' + this.state.secondsElapsed}\n\
21 + </div>\n\
22 + );\n\
23 + }\n\
24 +});\n\
25 +\n\
26 +React.renderComponent(<Timer />, mountNode);\
27 +";
28 +
29 +React.renderComponent(
30 + <ReactPlayground codeText={TIMER_COMPONENT} />,
31 + document.getElementById('timerExample')
32 +);
docs/_js/examples/todo.js new
+57
@@ -0,0 +1,57 @@
1 +/**
2 + * @jsx React.DOM
3 + */
4 +
5 +var TODO_COMPONENT = "\
6 +/** @jsx React.DOM */\n\
7 +var TodoList = React.createClass({\n\
8 + render: function() {\n\
9 + var items = this.props.items.map(function(item) {\n\
10 + return <li>{item}</li>;\n\
11 + });\n\
12 + return <ul>{items}</ul>;\n\
13 + }\n\
14 +});\n\
15 +\n\
16 +var TodoCreate = React.createClass({\n\
17 + handleSubmit: function() {\n\
18 + var textInput = this.refs.textInput.getDOMNode();\n\
19 + this.props.onCreate(textInput.value);\n\
20 + textInput.value = '';\n\
21 + return false;\n\
22 + },\n\
23 + render: function() {\n\
24 + return (\n\
25 + <form onSubmit={this.handleSubmit.bind(this)}>\n\
26 + <input type=\"text\" ref=\"textInput\" />\n\
27 + <button>Add</button>\n\
28 + </form>\n\
29 + );\n\
30 + }\n\
31 +});\n\
32 +\n\
33 +var TodoApp = React.createClass({\n\
34 + getInitialState: function() {\n\
35 + return {items: []};\n\
36 + },\n\
37 + onItemCreate: function(value) {\n\
38 + this.setState({items: this.state.items.concat([value])});\n\
39 + },\n\
40 + render: function() {\n\
41 + return (\n\
42 + <div>\n\
43 + <h3>TODO</h3>\n\
44 + <TodoList items={this.state.items} />\n\
45 + <TodoCreate onCreate={this.onItemCreate.bind(this)} />\n\
46 + </div>\n\
47 + );\n\
48 + }\n\
49 +});\n\
50 +\n\
51 +React.renderComponent(<TodoApp />, mountNode);\
52 +";
53 +
54 +React.renderComponent(
55 + <ReactPlayground codeText={TODO_COMPONENT} />,
56 + document.getElementById('todoExample')
57 +);
docs/_js/live_editor.js new
+98
@@ -0,0 +1,98 @@
1 +/**
2 + * @jsx React.DOM
3 + */
4 +
5 +var CodeMirrorEditor = React.createClass({
6 + componentDidMount: function(root) {
7 + this.editor = CodeMirror.fromTextArea(this.refs.editor.getDOMNode(), {
8 + mode: 'javascript',
9 + lineNumbers: false,
10 + matchBrackets: true,
11 + theme: 'solarized-light'
12 + });
13 + this.editor.on('change', this.onChange.bind(this));
14 + this.onChange();
15 + },
16 + onChange: function() {
17 + if (this.props.onChange) {
18 + var content = this.editor.getValue();
19 + this.props.onChange(content);
20 + }
21 + },
22 + render: function() {
23 + // wrap in a div to fully contain CodeMirror
24 + return (
25 + <div class={this.props.className}>
26 + <textarea ref="editor">{this.props.codeText}</textarea>
27 + </div>
28 + );
29 + }
30 +});
31 +
32 +var ReactPlayground = React.createClass({
33 + MODES: {XJS: 'XJS', JS: 'JS'}, //keyMirror({XJS: true, JS: true}),
34 +
35 + getInitialState: function() {
36 + return {mode: this.MODES.XJS, code: this.props.codeText};
37 + },
38 +
39 + bindState: function(name) {
40 + return function(value) {
41 + var newState = {};
42 + newState[name] = value;
43 + this.setState(newState);
44 + }.bind(this);
45 + },
46 +
47 + getDesugaredCode: function() {
48 + return JSXTransformer.transform(this.state.code).code;
49 + },
50 +
51 + render: function() {
52 + var content;
53 + if (this.state.mode === this.MODES.XJS) {
54 + content =
55 + <CodeMirrorEditor
56 + onChange={this.bindState('code')}
57 + class="playgroundStage"
58 + codeText={this.state.code}
59 + />;
60 + } else if (this.state.mode === this.MODES.JS) {
61 + content =
62 + <div class={{playgroundJS: true, playgroundStage: true}}>
63 + {this.getDesugaredCode()}
64 + </div>;
65 + }
66 +
67 + return (
68 + <div class="playground">
69 + <div class="playgroundCode">
70 + {content}
71 + </div>
72 + <div class="playgroundPreview">
73 + <div ref="mount" />
74 + </div>
75 + </div>
76 + );
77 + },
78 + componentDidMount: function() {
79 + this.executeCode();
80 + },
81 + componentDidUpdate: function() {
82 + this.executeCode();
83 + },
84 + executeCode: function() {
85 + var mountNode = this.refs.mount.getDOMNode();
86 +
87 + try {
88 + React.unmountAndReleaseReactRootNode(mountNode);
89 + } catch (e) { }
90 +
91 + try {
92 + eval(this.getDesugaredCode());
93 + } catch (e) {
94 + React.renderComponent(<div content={e.toString()} class={{playgroundError: true}} />, mountNode);
95 + }
96 + }
97 +});
98 +
docs/_layouts/default.html new
+75
@@ -0,0 +1,75 @@
1 +<!DOCTYPE html>
2 +<html>
3 +<head>
4 + <meta charset="utf-8">
5 + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6 + <title>React | {{ page.title }}</title>
7 + <meta name="viewport" content="width=device-width">
8 + <meta property="og:title" content="React | {{ page.title }}" />
9 + <meta property="og:type" content="website" />
10 + <meta property="og:url" content="http://facebook.github.io/react{{ page.url }}" />
11 + <meta property="og:image" content="http://facebook.github.io/react/img/logo_og.png" />
12 + <meta property="og:description" content="A JavaScript library for building user interfaces" />
13 +
14 + <link rel="shortcut icon" href="/react/favicon.ico">
15 +
16 + <link rel="stylesheet" href="/react/css/react.css">
17 + <link rel="stylesheet" href="/react/css/syntax.css">
18 + <link rel="stylesheet" href="/react/css/codemirror.css">
19 +
20 + <script type="text/javascript" src="//use.typekit.net/vqa1hcx.js"></script>
21 + <script type="text/javascript">try{Typekit.load();}catch(e){}</script>
22 +
23 + <script type="text/javascript" src="/react/js/codemirror.js"></script>
24 + <script type="text/javascript" src="/react/js/javascript.js"></script>
25 + <script type="text/javascript" src="/react/js/react.min.js"></script>
26 + <script type="text/javascript" src="/react/js/JSXTransformer.js"></script>
27 + <script type="text/javascript" src="/react/js/live_editor.js"></script>
28 + <script type="text/javascript" src="/react/js/showdown.js"></script>
29 +</head>
30 +<body>
31 +
32 + <div class="container">
33 +
34 + <div class="nav-main">
35 + <div class="wrap">
36 + <a class="nav-home" href="/react/index.html">
37 + <img class="nav-logo" alt="" src="/react/img/logo_small.png">
38 + React
39 + </a>
40 + <ul class="nav-site">
41 + <li><a href="/react/docs/getting-started.html"{% if page.sectionid == 'docs' %} class="active"{% endif %}>docs</a></li>
42 + <li><a href="/react/support.html"{% if page.id == 'support' %} class="active"{% endif %}>support</a></li>
43 + <li><a href="/react/downloads.html"{% if page.id == 'downloads' %} class="active"{% endif %}>download</a></li>
44 + <li><a href="http://github.com/facebook/react">github</a>
45 + </ul>
46 + <!-- <iframe src="http://ghbtns.com/github&#45;btn.html?user=facebook&#38;repo=react.js&#38;type=fork"allowtransparency="true" frameborder="0" scrolling="0" width="62" height="20"></iframe> -->
47 + </div>
48 + </div>
49 +
50 + {% if page.id == 'home' %}
51 + <div class="hero">
52 + <div class="wrap">
53 + <div class="text"><strong>React</strong></div>
54 + <div class="minitext">
55 + A JavaScript library for building user interfaces
56 + </div>
57 +
58 + <div class="buttons-unit">
59 + <a href="/react/docs/getting-started.html" class="button">Get Started</a>
60 + <a href="/react/downloads.html" class="button">Download React v{{site.react_version}}</a>
61 + </div>
62 + </div>
63 + </div>
64 + {% endif %}
65 +
66 + {{ content }}
67 +
68 + <footer class="wrap">
69 + <div class="left">A Facebook &amp; Instagram collaboration.</div>
70 + <div class="right">&copy; 2013 Facebook Inc.</div>
71 + </footer>
72 + </div>
73 +</body>
74 +</html>
75 +
docs/_layouts/docs.html new
+23
@@ -0,0 +1,23 @@
1 +---
2 +layout: default
3 +sectionid: docs
4 +---
5 +
6 +<section class="content wrap documentationContent">
7 + {% include nav_docs.html %}
8 +
9 + <div class="inner-content">
10 + <h1>{{ page.title }}</h1>
11 + <div class="subHeader">{{ page.description }}</div>
12 + {{ content }}
13 +
14 + <div class="docs-prevnext">
15 + {% if page.prev %}
16 + <a class="docs-prev" href="/react/docs/{{ page.prev }}">&larr; Prev</a>
17 + {% endif %}
18 + {% if page.next %}
19 + <a class="docs-next" href="/react/docs/{{ page.next }}">Next &rarr;</a>
20 + {% endif %}
21 + </div>
22 + </div>
23 +</section>
docs/_layouts/page.html new
+7
@@ -0,0 +1,7 @@
1 +---
2 +layout: default
3 +---
4 +
5 +<section class="content wrap">
6 + {{ content }}
7 +</section>
docs/_layouts/post.html new
+12
@@ -0,0 +1,12 @@
1 +---
2 +layout: default
3 +---
4 +
5 +<section class="content wrap">
6 + <h2>{{ page.title }}</h2>
7 + <p class="meta">{{ page.date | date_to_string }}</p>
8 +
9 + <div id="post">
10 + {{ content }}
11 + </div>
12 +</section>
docs/_layouts/single.html new
+20
@@ -0,0 +1,20 @@
1 +---
2 +layout: default
3 +---
4 +
5 +<section class="content wrap documentationContent nosidebar">
6 + <div class="inner-content">
7 + <h1>{{ page.title }}</h1>
8 + <div class="subHeader">{{ page.description }}</div>
9 + {{ content }}
10 +
11 + <div class="docs-prevnext">
12 + {% if page.prev %}
13 + <a class="docs-prev" href="/react/docs/{{ page.prev }}">&larr; Prev</a>
14 + {% endif %}
15 + {% if page.next %}
16 + <a class="docs-next" href="/react/docs/{{ page.next }}">Next &rarr;</a>
17 + {% endif %}
18 + </div>
19 + </div>
20 +</section>
docs/_plugins/highlight_lines.rb new
+39
@@ -0,0 +1,39 @@
1 +# Replace Jekyll's handling of the Redcarpet code_block (which already adds
2 +# support for highlighting, but needs support for the very non-standard
3 +# "code fences with line highlights" extension).
4 +# Since this is currently depending on Redcarpet to cooperate, we are going to
5 +# be naive, and only allow line highlighting when a language is specified. If
6 +# you don't want any syntax highlighting but want to highlight lines, then you
7 +# need to specify text as your language, like:
8 +# ```text{4}
9 +
10 +
11 +module Jekyll
12 + module Converters
13 + class Markdown
14 + class RedcarpetParser
15 + module WithPygments
16 + def block_code(code, lang)
17 + require 'pygments'
18 + lang_parts = lang && lang.split('{')
19 + lang = lang_parts && !lang_parts[0].empty? && lang_parts[0] || 'text'
20 + hl_lines = ''
21 + if lang_parts && lang_parts.size >= 2
22 + hl_lines = lang_parts[1].gsub(/[{}]/, '').split(',').map do |ln|
23 + if matches = /(\d+)-(\d+)/.match(ln)
24 + ln = Range.new(matches[1], matches[2]).to_a.join(' ')
25 + end
26 + ln
27 + end.join(' ')
28 + end
29 + output = add_code_tags(
30 + Pygments.highlight(code, :lexer => lang,
31 + :options => { :encoding => 'utf-8', :hl_lines => hl_lines }),
32 + lang
33 + )
34 + end
35 + end
36 + end
37 + end
38 + end
39 +end
docs/css/codemirror.css new
+239
@@ -0,0 +1,239 @@
1 +/* BASICS */
2 +
3 +.CodeMirror {
4 + /* Set height, width, borders, and global font properties here */
5 + font-family: monospace;
6 +}
7 +.CodeMirror-scroll {
8 + /* Set scrolling behaviour here */
9 + overflow: auto;
10 +}
11 +
12 +/* PADDING */
13 +
14 +.CodeMirror-lines {
15 + padding: 14px 0; /* Vertical padding around content */
16 +}
17 +.CodeMirror pre {
18 + padding: 0 14px; /* Horizontal padding of content */
19 +}
20 +
21 +.CodeMirror-scrollbar-filler {
22 + background-color: white; /* The little square between H and V scrollbars */
23 +}
24 +
25 +/* GUTTER */
26 +
27 +.CodeMirror-gutters {
28 + border-right: 1px solid #ddd;
29 + background-color: #f7f7f7;
30 +}
31 +.CodeMirror-linenumbers {}
32 +.CodeMirror-linenumber {
33 + padding: 0 3px 0 5px;
34 + min-width: 20px;
35 + text-align: right;
36 + color: #999;
37 +}
38 +
39 +/* CURSOR */
40 +
41 +.CodeMirror div.CodeMirror-cursor {
42 + border-left: 1px solid black;
43 +}
44 +/* Shown when moving in bi-directional text */
45 +.CodeMirror div.CodeMirror-secondarycursor {
46 + border-left: 1px solid silver;
47 +}
48 +.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor {
49 + width: auto;
50 + border: 0;
51 + background: transparent;
52 + background: rgba(0, 200, 0, .4);
53 + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800);
54 +}
55 +/* Kludge to turn off filter in ie9+, which also accepts rgba */
56 +.CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor:not(#nonsense_id) {
57 + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
58 +}
59 +/* Can style cursor different in overwrite (non-insert) mode */
60 +.CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {}
61 +
62 +/* DEFAULT THEME */
63 +
64 +.cm-s-default .cm-keyword {color: #708;}
65 +.cm-s-default .cm-atom {color: #219;}
66 +.cm-s-default .cm-number {color: #164;}
67 +.cm-s-default .cm-def {color: #00f;}
68 +.cm-s-default .cm-variable {color: black;}
69 +.cm-s-default .cm-variable-2 {color: #05a;}
70 +.cm-s-default .cm-variable-3 {color: #085;}
71 +.cm-s-default .cm-property {color: black;}
72 +.cm-s-default .cm-operator {color: black;}
73 +.cm-s-default .cm-comment {color: #a50;}
74 +.cm-s-default .cm-string {color: #a11;}
75 +.cm-s-default .cm-string-2 {color: #f50;}
76 +.cm-s-default .cm-meta {color: #555;}
77 +.cm-s-default .cm-error {color: #f00;}
78 +.cm-s-default .cm-qualifier {color: #555;}
79 +.cm-s-default .cm-builtin {color: #30a;}
80 +.cm-s-default .cm-bracket {color: #997;}
81 +.cm-s-default .cm-tag {color: #170;}
82 +.cm-s-default .cm-attribute {color: #00c;}
83 +.cm-s-default .cm-header {color: blue;}
84 +.cm-s-default .cm-quote {color: #090;}
85 +.cm-s-default .cm-hr {color: #999;}
86 +.cm-s-default .cm-link {color: #00c;}
87 +
88 +.cm-negative {color: #d44;}
89 +.cm-positive {color: #292;}
90 +.cm-header, .cm-strong {font-weight: bold;}
91 +.cm-em {font-style: italic;}
92 +.cm-emstrong {font-style: italic; font-weight: bold;}
93 +.cm-link {text-decoration: underline;}
94 +
95 +.cm-invalidchar {color: #f00;}
96 +
97 +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
98 +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
99 +
100 +/* STOP */
101 +
102 +/* The rest of this file contains styles related to the mechanics of
103 + the editor. You probably shouldn't touch them. */
104 +
105 +.CodeMirror {
106 + line-height: 1;
107 + position: relative;
108 + overflow: hidden;
109 +}
110 +
111 +.CodeMirror-scroll {
112 + /* 30px is the magic margin used to hide the element's real scrollbars */
113 + /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */
114 + margin-bottom: -30px; margin-right: -30px;
115 + padding-bottom: 30px; padding-right: 30px;
116 + height: 100%;
117 + outline: none; /* Prevent dragging from highlighting the element */
118 + position: relative;
119 +}
120 +.CodeMirror-sizer {
121 + position: relative;
122 +}
123 +
124 +/* The fake, visible scrollbars. Used to force redraw during scrolling
125 + before actuall scrolling happens, thus preventing shaking and
126 + flickering artifacts. */
127 +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler {
128 + position: absolute;
129 + z-index: 6;
130 + display: none;
131 +}
132 +.CodeMirror-vscrollbar {
133 + right: 0; top: 0;
134 + overflow-x: hidden;
135 + overflow-y: scroll;
136 +}
137 +.CodeMirror-hscrollbar {
138 + bottom: 0; left: 0;
139 + overflow-y: hidden;
140 + overflow-x: scroll;
141 +}
142 +.CodeMirror-scrollbar-filler {
143 + right: 0; bottom: 0;
144 + z-index: 6;
145 +}
146 +
147 +.CodeMirror-gutters {
148 + position: absolute; left: 0; top: 0;
149 + height: 100%;
150 + z-index: 3;
151 +}
152 +.CodeMirror-gutter {
153 + height: 100%;
154 + display: inline-block;
155 + /* Hack to make IE7 behave */
156 + *zoom:1;
157 + *display:inline;
158 +}
159 +.CodeMirror-gutter-elt {
160 + position: absolute;
161 + cursor: default;
162 + z-index: 4;
163 +}
164 +
165 +.CodeMirror-lines {
166 + cursor: text;
167 +}
168 +.CodeMirror pre {
169 + /* Reset some styles that the rest of the page might have set */
170 + -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0;
171 + border-width: 0;
172 + background: transparent;
173 + font-family: inherit;
174 + font-size: inherit;
175 + margin: 0;
176 + white-space: pre;
177 + word-wrap: normal;
178 + line-height: inherit;
179 + color: inherit;
180 + z-index: 2;
181 + position: relative;
182 + overflow: visible;
183 +}
184 +.CodeMirror-wrap pre {
185 + word-wrap: break-word;
186 + white-space: pre-wrap;
187 + word-break: normal;
188 +}
189 +.CodeMirror-linebackground {
190 + position: absolute;
191 + left: 0; right: 0; top: 0; bottom: 0;
192 + z-index: 0;
193 +}
194 +
195 +.CodeMirror-linewidget {
196 + position: relative;
197 + z-index: 2;
198 + overflow: auto;
199 +}
200 +
201 +.CodeMirror-wrap .CodeMirror-scroll {
202 + overflow-x: hidden;
203 +}
204 +
205 +.CodeMirror-measure {
206 + position: absolute;
207 + width: 100%; height: 0px;
208 + overflow: hidden;
209 + visibility: hidden;
210 +}
211 +.CodeMirror-measure pre { position: static; }
212 +
213 +.CodeMirror div.CodeMirror-cursor {
214 + position: absolute;
215 + visibility: hidden;
216 + border-right: none;
217 + width: 0;
218 +}
219 +.CodeMirror-focused div.CodeMirror-cursor {
220 + visibility: visible;
221 +}
222 +
223 +.CodeMirror-selected { background: #d9d9d9; }
224 +.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
225 +
226 +.cm-searching {
227 + background: #ffa;
228 + background: rgba(255, 255, 0, .4);
229 +}
230 +
231 +/* IE7 hack to prevent it from returning funny offsetTops on the spans */
232 +.CodeMirror span { *vertical-align: text-bottom; }
233 +
234 +@media print {
235 + /* Hide the cursor when printing */
236 + .CodeMirror div.CodeMirror-cursor {
237 + visibility: hidden;
238 + }
239 +}
docs/css/main.css new
+165
@@ -0,0 +1,165 @@
1 +/*****************************************************************************/
2 +/*
3 +/* Common
4 +/*
5 +/*****************************************************************************/
6 +
7 +/* Global Reset */
8 +* {
9 + margin: 0;
10 + padding: 0;
11 +}
12 +
13 +html, body { height: 100%; }
14 +
15 +body {
16 + background-color: #FFF;
17 + font: 13.34px Helvetica, Arial, sans-serif;
18 + font-size: small;
19 + text-align: center;
20 +}
21 +
22 +h1, h2, h3, h4, h5, h6 {
23 + font-size: 100%; }
24 +
25 +h1 { margin-bottom: 1em; }
26 +p { margin: 1em 0; }
27 +
28 +a { color: #00a; }
29 +a:hover { color: #000; }
30 +a:visited { color: #a0a; }
31 +
32 +table {
33 + font-size: inherit;
34 + font: 100%;
35 +}
36 +
37 +/*****************************************************************************/
38 +/*
39 +/* Home
40 +/*
41 +/*****************************************************************************/
42 +ul.posts {
43 + list-style-type: none;
44 + margin-bottom: 2em;
45 +}
46 +
47 +ul.posts li {
48 + line-height: 1.75em;
49 +}
50 +
51 +ul.posts span {
52 + color: #aaa;
53 + font-family: Monaco, "Courier New", monospace;
54 + font-size: 80%;
55 +}
56 +
57 +/*****************************************************************************/
58 +/*
59 +/* Site
60 +/*
61 +/*****************************************************************************/
62 +
63 +.site {
64 + font-size: 115%;
65 + text-align: justify;
66 + width: 42em;
67 + margin: 3em auto 2em;
68 + line-height: 1.5em;
69 +}
70 +
71 +.site .header a {
72 + font-weight: bold;
73 + text-decoration: none;
74 +}
75 +
76 +.site .header h1.title {
77 + display: inline-block;
78 + margin-bottom: 2em;
79 +}
80 +
81 +.site .header h1.title a {
82 + color: #a00;
83 +}
84 +
85 +.site .header h1.title a:hover {
86 + color: #000;
87 +}
88 +
89 +.site .header a.extra {
90 + color: #aaa;
91 + margin-left: 1em;
92 +}
93 +
94 +.site .header a.extra:hover {
95 + color: #000;
96 +}
97 +
98 +.site .meta {
99 + color: #aaa;
100 +}
101 +
102 +.site .footer {
103 + font-size: 80%;
104 + color: #666;
105 + border-top: 4px solid #eee;
106 + margin-top: 2em;
107 + overflow: hidden;
108 +}
109 +
110 +.site .footer .contact {
111 + float: left;
112 + margin-right: 3em;
113 +}
114 +
115 +.site .footer .contact a {
116 + color: #8085C1;
117 +}
118 +
119 +.site .footer .rss {
120 + margin-top: 1.1em;
121 + margin-right: -.2em;
122 + float: right;
123 +}
124 +
125 +.site .footer .rss img {
126 + border: 0;
127 +}
128 +
129 +/*****************************************************************************/
130 +/*
131 +/* Posts
132 +/*
133 +/*****************************************************************************/
134 +
135 +/* standard */
136 +#post pre {
137 + border: 1px solid #ddd;
138 + background-color: #eef;
139 + padding: 0 .4em;
140 +}
141 +
142 +#post ul, #post ol {
143 + margin-left: 1.35em;
144 +}
145 +
146 +#post code {
147 + border: 1px solid #ddd;
148 + background-color: #eef;
149 + padding: 0 .2em;
150 +}
151 +
152 +#post pre code {
153 + border: none;
154 +}
155 +
156 +/* terminal */
157 +#post pre.terminal {
158 + border: 1px solid #000;
159 + background-color: #333;
160 + color: #FFF;
161 +}
162 +
163 +#post pre.terminal code {
164 + background-color: #333;
165 +}
\ No newline at end of file
docs/css/syntax.css new
+409
@@ -0,0 +1,409 @@
1 +.highlight pre code {
2 + color: #637c84;
3 +}
4 +
5 +.highlight {
6 + color: #333333;
7 + background: #f8f5ec;
8 +}
9 +
10 +.highlight .c {
11 + color: #93a1a1;
12 +}
13 +
14 +
15 +.highlight .g {
16 + color: #637c84;
17 +}
18 +
19 +/* Generic */
20 +
21 +.highlight .k {
22 + color: #859900;
23 +}
24 +
25 +/* Keyword */
26 +
27 +.highlight .l {
28 + color: #637c84;
29 +}
30 +
31 +/* Literal */
32 +
33 +.highlight .n {
34 + color: #637c84;
35 +}
36 +
37 +/* Name */
38 +
39 +.highlight .o {
40 + color: #859900;
41 +}
42 +
43 +/* Operator */
44 +
45 +.highlight .x {
46 + color: #cc7a6f;
47 +}
48 +
49 +/* Other */
50 +
51 +.highlight .p {
52 + color: #637c84;
53 +}
54 +
55 +/* Punctuation */
56 +
57 +.highlight .cm {
58 + color: #93a1a1;
59 +}
60 +
61 +/* Comment.Multiline */
62 +
63 +.highlight .cp {
64 + color: #859900;
65 +}
66 +
67 +/* Comment.Preproc */
68 +
69 +.highlight .c1 {
70 + color: #93a1a1;
71 +}
72 +
73 +/* Comment.Single */
74 +
75 +.highlight .cs {
76 + color: #859900;
77 +}
78 +
79 +/* Comment.Special */
80 +
81 +.highlight .gd {
82 + color: #36958e;
83 +}
84 +
85 +/* Generic.Deleted */
86 +
87 +.highlight .ge {
88 + font-style: italic;
89 + color: #637c84;
90 +}
91 +
92 +/* Generic.Emph */
93 +
94 +.highlight .gr {
95 + color: #dc322f;
96 +}
97 +
98 +/* Generic.Error */
99 +
100 +.highlight .gh {
101 + color: #cc7a6f;
102 +}
103 +
104 +/* Generic.Heading */
105 +
106 +.highlight .gi {
107 + color: #859900;
108 +}
109 +
110 +/* Generic.Inserted */
111 +
112 +.highlight .go {
113 + color: #637c84;
114 +}
115 +
116 +/* Generic.Output */
117 +
118 +.highlight .gp {
119 + color: #637c84;
120 +}
121 +
122 +/* Generic.Prompt */
123 +
124 +.highlight .gs {
125 + font-weight: bold;
126 + color: #637c84;
127 +}
128 +
129 +/* Generic.Strong */
130 +
131 +.highlight .gu {
132 + color: #cc7a6f;
133 +}
134 +
135 +/* Generic.Subheading */
136 +
137 +.highlight .gt {
138 + color: #637c84;
139 +}
140 +
141 +/* Generic.Traceback */
142 +
143 +.highlight .kc {
144 + color: #cc7a6f;
145 +}
146 +
147 +/* Keyword.Constant */
148 +
149 +.highlight .kd {
150 + color: #268bd2;
151 +}
152 +
153 +/* Keyword.Declaration */
154 +
155 +.highlight .kn {
156 + color: #859900;
157 +}
158 +
159 +/* Keyword.Namespace */
160 +
161 +.highlight .kp {
162 + color: #859900;
163 +}
164 +
165 +/* Keyword.Pseudo */
166 +
167 +.highlight .kr {
168 + color: #268bd2;
169 +}
170 +
171 +/* Keyword.Reserved */
172 +
173 +.highlight .kt {
174 + color: #dc322f;
175 +}
176 +
177 +/* Keyword.Type */
178 +
179 +.highlight .ld {
180 + color: #637c84;
181 +}
182 +
183 +/* Literal.Date */
184 +
185 +.highlight .m {
186 + color: #36958e;
187 +}
188 +
189 +/* Literal.Number */
190 +
191 +.highlight .s {
192 + color: #36958e;
193 +}
194 +
195 +/* Literal.String */
196 +
197 +.highlight .na {
198 + color: #637c84;
199 +}
200 +
201 +/* Name.Attribute */
202 +
203 +.highlight .nb {
204 + color: #b58900;
205 +}
206 +
207 +/* Name.Builtin */
208 +
209 +.highlight .nc {
210 + color: #268bd2;
211 +}
212 +
213 +/* Name.Class */
214 +
215 +.highlight .no {
216 + color: #cc7a6f;
217 +}
218 +
219 +/* Name.Constant */
220 +
221 +.highlight .nd {
222 + color: #268bd2;
223 +}
224 +
225 +/* Name.Decorator */
226 +
227 +.highlight .ni {
228 + color: #cc7a6f;
229 +}
230 +
231 +/* Name.Entity */
232 +
233 +.highlight .ne {
234 + color: #cc7a6f;
235 +}
236 +
237 +/* Name.Exception */
238 +
239 +.highlight .nf {
240 + color: #268bd2;
241 +}
242 +
243 +/* Name.Function */
244 +
245 +.highlight .nl {
246 + color: #637c84;
247 +}
248 +
249 +/* Name.Label */
250 +
251 +.highlight .nn {
252 + color: #637c84;
253 +}
254 +
255 +/* Name.Namespace */
256 +
257 +.highlight .nx {
258 + color: #637c84;
259 +}
260 +
261 +/* Name.Other */
262 +
263 +.highlight .py {
264 + color: #637c84;
265 +}
266 +
267 +/* Name.Property */
268 +
269 +.highlight .nt {
270 + color: #268bd2;
271 +}
272 +
273 +/* Name.Tag */
274 +
275 +.highlight .nv {
276 + color: #268bd2;
277 +}
278 +
279 +/* Name.Variable */
280 +
281 +.highlight .ow {
282 + color: #859900;
283 +}
284 +
285 +/* Operator.Word */
286 +
287 +.highlight .w {
288 + color: #637c84;
289 +}
290 +
291 +/* Text.Whitespace */
292 +
293 +.highlight .mf {
294 + color: #36958e;
295 +}
296 +
297 +/* Literal.Number.Float */
298 +
299 +.highlight .mh {
300 + color: #36958e;
301 +}
302 +
303 +/* Literal.Number.Hex */
304 +
305 +.highlight .mi {
306 + color: #36958e;
307 +}
308 +
309 +/* Literal.Number.Integer */
310 +
311 +.highlight .mo {
312 + color: #36958e;
313 +}
314 +
315 +/* Literal.Number.Oct */
316 +
317 +.highlight .sb {
318 + color: #93a1a1;
319 +}
320 +
321 +/* Literal.String.Backtick */
322 +
323 +.highlight .sc {
324 + color: #36958e;
325 +}
326 +
327 +/* Literal.String.Char */
328 +
329 +.highlight .sd {
330 + color: #637c84;
331 +}
332 +
333 +/* Literal.String.Doc */
334 +
335 +.highlight .s2 {
336 + color: #36958e;
337 +}
338 +
339 +/* Literal.String.Double */
340 +
341 +.highlight .se {
342 + color: #cc7a6f;
343 +}
344 +
345 +/* Literal.String.Escape */
346 +
347 +.highlight .sh {
348 + color: #637c84;
349 +}
350 +
351 +/* Literal.String.Heredoc */
352 +
353 +.highlight .si {
354 + color: #36958e;
355 +}
356 +
357 +/* Literal.String.Interpol */
358 +
359 +.highlight .sx {
360 + color: #36958e;
361 +}
362 +
363 +/* Literal.String.Other */
364 +
365 +.highlight .sr {
366 + color: #dc322f;
367 +}
368 +
369 +/* Literal.String.Regex */
370 +
371 +.highlight .s1 {
372 + color: #36958e;
373 +}
374 +
375 +/* Literal.String.Single */
376 +
377 +.highlight .ss {
378 + color: #36958e;
379 +}
380 +
381 +/* Literal.String.Symbol */
382 +
383 +.highlight .bp {
384 + color: #268bd2;
385 +}
386 +
387 +/* Name.Builtin.Pseudo */
388 +
389 +.highlight .vc {
390 + color: #268bd2;
391 +}
392 +
393 +/* Name.Variable.Class */
394 +
395 +.highlight .vg {
396 + color: #268bd2;
397 +}
398 +
399 +/* Name.Variable.Global */
400 +
401 +.highlight .vi {
402 + color: #268bd2;
403 +}
404 +
405 +/* Name.Variable.Instance */
406 +
407 +.highlight .il {
408 + color: #36958e;
409 +}
docs/docs/advanced-components.md new
+119
@@ -0,0 +1,119 @@
1 +---
2 +id: docs-advanced-components
3 +title: Advanced Components
4 +description: How to build advanced composite components.
5 +layout: docs
6 +prev: event-handling.html
7 +next: api.html
8 +---
9 +
10 +Composite components extend a `ReactCompositeComponent` base class that provides
11 +a very powerful API that makes React flexible and able to easily work with other
12 +libraries and frameworks.
13 +
14 +## Lifecycle Methods
15 +
16 +Composite components can optionally implement lifecycle methods that are invoked
17 +at various stages in the [component lifecycle](component-lifecycle.html) that
18 +each have unique guarantees.
19 +
20 +### Mounting
21 +
22 + - `getInitialState(): object` is invoked before a component is mounted.
23 + Stateful components should implement this and return the initial state data.
24 + - `componentWillMount()` is invoked immediately before mounting occurs.
25 + - `componentDidMount(DOMElement rootNode)` is invoked immediately after
26 + mounting occurs. Initialization that requires DOM nodes should go here.
27 +
28 +### Updating
29 +
30 + - `componentWillReceiveProps(object nextProps)` is invoked when a mounted
31 + component receives new props. This method should be used to compare
32 + `this.props` and `nextProps` to perform state transitions using
33 + `this.setState()`.
34 + - `shouldComponentUpdate(object nextProps, object nextState): boolean` is
35 + invoked when a component decides whether any changes warrant an update to the
36 + DOM. Implement this as an optimization to compare `this.props` with
37 + `nextProps` and `this.state` with `nextState` and return false if React
38 + should skip updating.
39 + - `componentWillUpdate(object nextProps, object nextState)` is invoked
40 + immediately before updating occurs. You cannot call `this.setState()` here.
41 + - `componentDidUpdate(object prevProps, object prevState, DOMElement rootNode)`
42 + is invoked immediately after updating occurs.
43 +
44 +### Unmounting
45 +
46 + - `componentWillUnmount()` is invoked immediately before a component is
47 + unmounted and destroyed. Cleanup should go here.
48 +
49 +## Mounted Methods
50 +
51 +_Mounted_ composite components also support the following methods:
52 +
53 + - `getDOMNode(): DOMElement` can be invoked on any mounted component in order
54 + to obtain a reference to its rendered DOM node.
55 + - `forceUpdate()` can be invoked on any mounted component when you know that
56 + some deeper aspect of the component's state has changed without using
57 + `this.setState()`.
58 +
59 +> Note:
60 +>
61 +> The `DOMElement rootNode` argument of `componentDidMount()` and
62 +> `componentDidUpdate()` is a convenience. The same node can be obtained by
63 +> calling `this.getDOMNode()`.
64 +
65 +## Component Refs
66 +
67 +A common use case of event callbacks or the lifecycle methods is to operate on a
68 +component returned by `render()`. For example, consider a search component that
69 +should auto-focus the input once mounted:
70 +
71 +```javascript
72 +var SearchForm = React.createClass({
73 + render: function() {
74 + return (
75 + <form action={this.props.action}>
76 + <input type="search" placeholder="Search..." />
77 + </form>
78 + );
79 + },
80 + componentDidMount: function(rootNode) {
81 + var searchInput = rootNode.firstChild;
82 + searchInput.focus();
83 + }
84 +});
85 +```
86 +
87 +Although this implementation works, it is fragile because `componentDidMount()`
88 +now relies on `render()` returning a particular DOM structure.
89 +
90 +React provides a better way for composite components to reference components
91 +that it constructs in its `render()` method through the use of refs. A component
92 +can assign a `ref` to any component it constructs. This will create a reference
93 +to those components on `this.refs`. For example:
94 +
95 +```javascript{5,10}
96 +var SearchForm = React.createClass({
97 + render: function() {
98 + return (
99 + <form action={this.props.action}>
100 + <input type="search" placeholder="Search..." ref="searchInput" />
101 + </form>
102 + );
103 + },
104 + componentDidMount: function(rootNode) {
105 + var searchInput = this.refs.searchInput.getDOMNode();
106 + searchInput.focus();
107 + }
108 +});
109 +```
110 +
111 +In this example, `this.refs.searchInput` will reference the `<input>` component
112 +and is available in most lifecycle methods and event callbacks. We obtain a
113 +reference to the `<input>`'s DOM node using `getDOMNode()`.
114 +
115 +> Note:
116 +>
117 +> If you want to preserve compatibility with Google Closure Compiler's
118 +> property crushing in `ADVANCED_OPTIMIZATIONS` mode, make sure to use string
119 +> literals with `this.refs`.
docs/docs/api.md new
+104
@@ -0,0 +1,104 @@
1 +---
2 +id: docs-api
3 +title: React API
4 +layout: docs
5 +prev: advanced-components.html
6 +---
7 +
8 +## React
9 +
10 +`React` is the entry point to the React framework. If you're using one of the prebuilt packages it's available as a global; if you're using CommonJS modules you can `require()` it.
11 +
12 +### DOM
13 +
14 +`React.DOM` provides all of the standard HTML tags needed to build a React app. You generally don't use it directly; instead, just include it as part of the `/** @jsx React.DOM */` docblock.
15 +
16 +### initializeTouchEvents(boolean shouldUseTouch)
17 +
18 +Configure React's event system to handle touch events on mobile devices.
19 +
20 +### function autoBind(function method)
21 +
22 +Marks the provided function to be automatically bound to each React component instance created. This allows React components to define automatically bound methods and ensure that when called they will always reference their current instance.
23 +
24 +Example:
25 +
26 +```javascript
27 +React.createClass({
28 + click: React.autoBind(function(evt) {
29 + this.setState({jumping: true});
30 + }),
31 + render: function() {
32 + // Look: no bind!
33 + return <a onClick={this.click}>Jump</a>;
34 + }
35 +});
36 +```
37 +
38 +### function createClass(object specification)
39 +
40 +Creates a component given a specification. A component implements a `render` method which returns a single child. That child may have an arbitrarily deep child structure. One thing that makes components different than a 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.
41 +
42 +### ReactComponent renderComponent(ReactComponent container, DOMElement mountPoint)
43 +
44 +Renders a React component into the DOM in the supplied `container`.
45 +
46 +If the React component was previously rendered into `container`, this will perform an update on it and only mutate the DOM as necessary to reflect the latest React component.
47 +
48 +## AbstractEvent
49 +
50 +Your event handlers will be passed instances of `AbstractEvent`, a cross-browser wrapper around the browser's native event. It has the same interface as the browser's native event (such as `stopPropagation()` and `preventDefault()`) except they work exactly the same across all browsers.
51 +
52 +If you find that you need the underlying browser event for some reason, simply use the `nativeEvent` attribute to get it.
53 +
54 +## ReactComponent
55 +
56 +Component classses created by `createClass()` return instances of `ReactComponent` when called. Most of the time when you're using React you're either creating or consuming `ReactComponent`s.
57 +
58 +### DOMElement getDOMNode()
59 +
60 +If this component has been mounted into the DOM, this returns the corresponding native browser DOM element. This method is useful for reading values out of the DOM, such as form field values and performing DOM measurements.
61 +
62 +### setProps(object nextProps)
63 +
64 +When you're integrating with an external JavaScript application you may want to signal a change to a React component rendered with `renderComponent()`. Simply call `setProps()` to change its properties and trigger a re-render.
65 +
66 +**Note:** This method can only be called on a root-level component. That is, it's only available on the component passed directly to `renderComponent()` and none of its children. If you're inclined to use `setProps()` on a child component, instead take advantage of reactive updates and pass the new prop to the child component when it's created in `render()`.
67 +
68 +### replaceProps(object nextProps)
69 +
70 +Like `setProps()` but deletes any pre-existing props that are not in nextProps.
71 +
72 +### ReactComponent transferPropsTo(ReactComponent targetComponent)
73 +
74 +Transfer properties from this component to a target component that have not already been set on the target component. This is usually used to pass down properties to the returned root component. `targetComponent`, now updated with some new props is returned as a convenience.
75 +
76 +### setState(object nextState)
77 +
78 +Merges nextState with the current state. This is the primary method you use to trigger UI updates from event handlers and server request callbacks.
79 +
80 +**Note:** *NEVER* mutate `this.state` directly. As calling `setState()` afterwards may replace the mutation you made. Treat `this.state` as if it were immutable.
81 +
82 +**Note:** `setState()` does not immediately mutate `this.state` but creates a pending state transition. Accessing `this.state` after calling this method can potentially return the existing value.
83 +
84 +### replaceState(object nextState)
85 +
86 +Like `setState()` but deletes any pre-existing state keys that are not in nextState.
87 +
88 +### forceUpdate()
89 +
90 +If your `render()` method reads from something other than `this.props` or `this.state` you'll need to tell React when it needs to re-run `render()`. Use `forceUpdate()` to cause React to automatically re-render. This will cause `render()` to be called on the component and all of its children but React will only update the DOM if the markup changes.
91 +
92 +Normally you should try to avoid all uses of `forceUpdate()` and only read from `this.props` and `this.state` in `render()`. This makes your application much simpler and more efficient.
93 +
94 +### object getInitialState()
95 +### componentWillMount()
96 +### componentDidMount(DOMElement domNode)
97 +### componentWillReceiveProps(object nextProps)
98 +### boolean shouldComponentUpdate(object nextProps, object nextState)
99 +### componentWillUpdate(object nextProps, object nextState)
100 +### ReactComponent render()
101 +### componentDidUpdate(object prevProps, object prevState, DOMElement domNode)
102 +### componentWillUnmount()
103 +
104 +These are overridable lifecycle methods; see the [lifecycle methods](lifecycle.html) documentation for more information.
docs/docs/common-questions.md new
+22
@@ -0,0 +1,22 @@
1 +---
2 +id: docs-common-questions
3 +title: Common Questions
4 +layout: docs
5 +prev: tutorial.html
6 +next: syntax.html
7 +---
8 +### What browsers does React support?
9 +
10 +React supports the latest two Chrome, Firefox, Safari, and Internet Explorer versions. React can work with Internet Explorer 8 with polyfills.
11 +
12 +### How do I get React to support Internet Explorer 8?
13 +
14 +React requires ES5 JavaScript shims to run in Internet Explorer 8. Include the [ES5 Shims](https://github.com/kriskowal/es5-shim) to implement these shims.
15 +
16 +### Who uses React?
17 +
18 +The [Instagram](http://instagram.com/) website is built entirely in React. The [Facebook](https://www.facebook.com/) website is also increasingly using React, including the common commenting plugin across the site.
19 +
20 +### Can I integrate with other JavaScript libraries?
21 +
22 +Absolutely! In fact, we encourage it! See [our GitHub repo](http://github.com/facebook/react/) for a [TodoMVC example using Backbone](https://github.com/facebook/react/tree/master/examples/todomvc-backbone) and a [jQuery + Bootstrap modal demo](https://github.com/facebook/react/tree/master/examples/jquery-bootstrap).
docs/docs/component-basics.md new
+73
@@ -0,0 +1,73 @@
1 +---
2 +id: docs-component-basics
3 +title: Component Basics
4 +description: What are components?
5 +layout: docs
6 +next: component-data.html
7 +prev: syntax.html
8 +---
9 +
10 +_Components_ are the basic units of composition in React. Components encapsulate
11 +the logic necessary to take input parameters and render markup. Components can
12 +be rendered into an existing DOM element on the page by using
13 +`React.renderComponent`:
14 +
15 +```javascript
16 +// Replaces everything in `document.body` with <div>Hello, world!</div>;
17 +React.renderComponent(<div>Hello, world!</div>, document.body);
18 +```
19 +
20 +Keep in mind that `<div>` is **not** a DOM element! Keep reading...
21 +
22 +## Types of Components
23 +
24 +There are two types of components:
25 +
26 + - **Composite Components**
27 + - **DOM Components**
28 +
29 +### Composite Components <small>such as `TodoApp` and `Typeahead`.</small>
30 +
31 +The majority of your React code will be implementing composite components.
32 +
33 +Composite components are higher-level components with custom rendering logic
34 +that may compose other composite components or DOM components.
35 +
36 +```javascript
37 +/** @jsx React.DOM */
38 +var LinkButton = React.createClass({
39 + render: function() {
40 + return <a className="btn" />;
41 + }
42 +});
43 +
44 +var myButton = <LinkButton />;
45 +```
46 +
47 +This example defines a `LinkButton` component class using `React.createClass()`,
48 +and its `render()` method composes the `<a>` DOM component.
49 +
50 +### DOM Components <small>such as `div` and `span`.</small>
51 +
52 +DOM components are the set of classes that correspond to browser DOM elements.
53 +They are defined in `React.DOM` and can be brought "into scope" by setting
54 +`@jsx React.DOM` in the docblock. See [JSX Syntax](syntax.html) for more
55 +details.
56 +
57 +Although `React.DOM` components look like browser DOM elements, they differ in a
58 +few ways:
59 +
60 +- All property names, including event handlers, are camelCased.
61 +- JavaScript identifiers should be used, namely `className` and `htmlFor`.
62 +- The `style` prop expects an object instead of a string. The object should map
63 + camelCased style properties to values, e.g. `{backgroundColor: '#fff'}`.
64 +
65 +Here is an example of a React link styled as a button with a click handler:
66 +
67 +```javascript
68 +/** @jsx React.DOM */
69 +var handleClick = function() {alert('Clicked!');};
70 +var inlineStyle = {textDecoration: 'none'};
71 +
72 +var myLink = <a className="btn" onClick={handleClick} style={inlineStyle} />;
73 +```
docs/docs/component-data.md new
+145
@@ -0,0 +1,145 @@
1 +---
2 +id: docs-component-data
3 +title: Component Data
4 +description: How is data passed into a component?
5 +layout: docs
6 +prev: component-basics.html
7 +next: component-lifecycle.html
8 +---
9 +
10 +## Props
11 +
12 +Components use data to determine what should be rendered. For example:
13 +
14 +```javascript
15 +var LikeLink = React.createClass({
16 + render: function() {
17 + var text = this.props.liked ? 'Liked' : 'Like';
18 + return <a>{text}</a>;
19 + }
20 +});
21 +var myLikeLink = <LikeLink liked={false} />;
22 +```
23 +
24 +In this example, `LikeLink` takes `liked` as boolean data. This type of data
25 +that is passed in is called a "prop". Examples of props on DOM components
26 +include `className` and `onClick`.
27 +
28 +Whenever a component's props change, its `render()` function will be
29 +re-evaluated and the DOM will be updated. React will ensure that the DOM is
30 +always kept up-to-date.
31 +
32 +## State
33 +
34 +Let's build a small `LikeApp` application that makes use of the `<LikeLink>`
35 +component from above. It should start off unliked and we should be able to like
36 +it by clicking the link:
37 +
38 +```javascript
39 +var LikeApp = React.createClass({
40 + render: function() {
41 + var isClicked = false;
42 + return <LikeLink liked={isClicked} onClick={this.handleClick.bind(this)} />;
43 + },
44 + handleClick: function() {
45 + // Somehow update `isClicked`.
46 + }
47 +});
48 +```
49 +
50 +This renders a `<LikeLink>` with a click listener. However, it is not clear how
51 +`handleClick` should update `isClicked` to true. `LikeApp` needs a way to store
52 +**state** about whether or not it has been clicked.
53 +
54 +### State vs. Props
55 +
56 +State is data that is managed _internally_ by a composite component. Like props,
57 +the `render()` function will be re-evaluated whenever state changes. Props and
58 +state differ in that:
59 +
60 + - Props are passed in from the creator.
61 + - State is private to and managed by the component.
62 +
63 +### Managing State
64 +
65 +Let's update our `LikeApp` component using state:
66 +
67 +```javascript{2-4,6,10}
68 +var LikeApp = React.createClass({
69 + getInitialState: function() {
70 + return {isClicked: false};
71 + },
72 + render: function() {
73 + var isClicked = this.state.isClicked;
74 + return <LikeLink liked={isClicked} onClick={this.handleClick.bind(this)} />;
75 + },
76 + handleClick: function() {
77 + this.setState({isClicked: true});
78 + }
79 +});
80 +```
81 +
82 +There's a lot going on here, so let's work our way from top to bottom:
83 +
84 + - `getInitialState()` describes what state data looks like when the component
85 + is created.
86 + - In `render()`, state data can be accessed via `this.state`.
87 + - When the link is clicked, we update state using `setState()`.
88 +
89 +Now when we click the link, the `<LikeLink>` will get updated, right? Wrong.
90 +
91 +## Transferring Props
92 +
93 +If you have been following carefully, you may have noticed that although we pass
94 +a click handler into `<LikeLink>` as a prop, `LikeLink` does not do anything
95 +with `this.props.onClick`! Let's fix that.
96 +
97 +```javascript{4}
98 +var LikeLink = React.createClass({
99 + render: function() {
100 + var text = this.props.liked ? 'Liked' : 'Like';
101 + return <a onClick={this.props.onClick}>{text}</a>;
102 + }
103 +});
104 +```
105 +
106 +Although this works, realize that this would quickly become tedious if we wanted
107 +to also transfer `href`, `title`, `target`, and other events from `this` to the
108 +rendered `<a>`. React provides a convenience method, `transferPropsTo()`, for
109 +transferring props:
110 +
111 +```javascript{4}
112 +var LikeLink = React.createClass({
113 + render: function() {
114 + var text = this.props.liked ? 'Liked' : 'Like';
115 + return this.transferPropsTo(<a>{text}</a>);
116 + }
117 +});
118 +```
119 +
120 +This will transfer all props from `this` to the specified component (including
121 +`onClick`).
122 +
123 +## Summary
124 +
125 +Now we are done. `LikeApp` renders an unliked link which, when clicked, will:
126 +
127 +1. Update the internal state of `LikeApp`.
128 +2. Change the props passed into `LikeLink`.
129 +3. Change the return value of `render()`.
130 +4. Trigger an update to the DOM.
131 +
132 +It's worth noting that React will handle new return values of `render()` by
133 +making the mininal set of mutations necessary to bring the DOM up-to-date. In
134 +this case, only the `textContent` of the rendered link will be mutated.
135 +
136 +In summary:
137 +
138 + - Props are passed in whereas state is managed internally by a component.
139 + - Never mutate `this.props` or `this.state`. You should pass props into other
140 + components and mutate state using `setState()`.
141 + - State is private. Never read `state` or call `setState()` on
142 + anything but `this`.
143 + - Whenever props or state changes, `render()` will be re-evaluated and the DOM
144 + updated. Also, `render()` should not depend on anything besides `this.props`
145 + and `this.state`.
docs/docs/component-lifecycle.md new
+85
@@ -0,0 +1,85 @@
1 +---
2 +id: docs-component-lifecycle
3 +title: Component Lifecycle
4 +description: What happens when I render a React component?
5 +layout: docs
6 +prev: component-data.html
7 +next: event-handling.html
8 +---
9 +
10 +## Mounting
11 +
12 +[We have previously seen](component-basics.html) how to render components into
13 +existing DOM elements on the page:
14 +
15 +```javascript
16 +React.renderComponent(<div>Hello, world!</div>, document.body);
17 +```
18 +
19 +In this one simple line, we have accomplished the following:
20 +
21 + - A `<div>` (defined by `React.DOM.div`) component is instantiated.
22 + - The component is **mounted** into `document.body`.
23 +
24 +**Mounting** is the process of initializing a React component by creating its
25 +DOM nodes and inserting the them into a supplied container node.
26 +
27 +At this point, the entire page consists of a single `<div>` with "Hello,
28 +world!".
29 +
30 +## Updating
31 +
32 +Let's add a second call to `React.renderComponent()` after three
33 +seconds:
34 +
35 +```javascript{2-4}
36 +React.renderComponent(<div>Hello, world!</div>, document.body);
37 +setTimeout(function() {
38 + React.renderComponent(<div>Goodbye, world.</div>, document.body);
39 +}, 3000);
40 +```
41 +
42 +The second call to `React.renderComponent()` will trigger the following:
43 +
44 + - The `<div>` component will check the new props to see if anything changed.
45 + - The set of changes are used to **update** the DOM node as necessary.
46 +
47 +**Updating** is the process of mutating the rendered DOM nodes and occurs
48 +whenever either props or state has changed. This ensures that the rendered DOM
49 +is consistent with the data.
50 +
51 +## Unmounting
52 +
53 +Let's add one final call to `React.renderComponent()` after another three
54 +seconds:
55 +
56 +```javascript{5-7}
57 +React.renderComponent(<div>Hello, world!</div>, document.body);
58 +setTimeout(function() {
59 + React.renderComponent(<div>Goodbye, world.</div>, document.body);
60 +}, 3000);
61 +setTimeout(function() {
62 + React.renderComponent(<img src="/images/fin.png" />, document.body);
63 +}, 6000);
64 +```
65 +
66 +The third call to `React.renderComponent()` will trigger the following:
67 +
68 + - An `<img>` (defined by `React.DOM.img`) component is instantiated.
69 + - React will compare the `<div>` component with the `<img>` component.
70 + - Since the component class is different, the `<div>` component will be
71 + **unmounted**.
72 + - The `<img>` component will then be mounted into `document.body`.
73 +
74 +**Unmounting** is the process of releasing resources that have been allocated by
75 +a component. This allows user interfaces built with React to live long without
76 +memory leaks.
77 +
78 +Components can also be unmounted using
79 +`React.unmountAndReleaseReactRootNode()`:
80 +
81 +```javascript
82 +React.unmountAndReleaseReactRootNode(document.body);
83 +```
84 +
85 +This will unmount any components mounted immediately within `document.body`.
docs/docs/event-handling.md new
+124
@@ -0,0 +1,124 @@
1 +---
2 +id: docs-event-handling
3 +title: Event Handling
4 +description: How do events work with React components?
5 +layout: docs
6 +prev: component-lifecycle.html
7 +next: advanced-components.html
8 +---
9 +
10 +Events in React work the way they do with HTML, except the event names are
11 +camelCased.
12 +
13 +```javascript
14 +var Clicker = React.createClass({
15 + render: function() {
16 + return <span onClick={this.handleClick}>Click me!</span>;
17 + },
18 + handleClick: function(event) {
19 + alert('You clicked me!');
20 + }
21 +});
22 +```
23 +
24 +When `<Clicker>` is clicked, the `handleClick()` function will get fired. Under
25 +the hood, React uses top-level event delegation to achieve high performance.
26 +
27 +## Automatically Binding Callbacks
28 +
29 +Just like any callback in JavaScript, if you want to refer to the component as
30 +`this` from the callback, you need to bind the callback to the component:
31 +
32 +```javascript{3}
33 +var Clicker = React.createClass({
34 + render: function() {
35 + var handleClick = this.handleClick.bind(this);
36 + return <span onClick={handleClick}>Click me!</span>;
37 + },
38 + handleClick: function(event) {
39 + alert(this.ALERT_MESSAGE);
40 + },
41 + ALERT_MESSAGE: 'You clicked me!'
42 +});
43 +```
44 +
45 +React provides a convenient and _efficient_ way to bind methods using
46 +`React.autoBind()`:
47 +
48 +```javascript{3,5-7}
49 +var Clicker = React.createClass({
50 + render: function() {
51 + return <span onClick={this.handleClick}>Click me!</span>;
52 + },
53 + handleClick: React.autoBind(function(event) {
54 + alert(this.ALERT_MESSAGE);
55 + }),
56 + ALERT_MESSAGE: 'You clicked me!'
57 +});
58 +```
59 +
60 +> Note:
61 +>
62 +> Binding a function allocates memory to create a new bound function. Since
63 +> `render()` may be invoked many times, it is a bad place to bind functions.
64 +> `React.autoBind()` sidesteps this issue by only binding once at instantiation
65 +> time.
66 +
67 +## DOM Events
68 +
69 +React uses [top-level event delegation](http://davidwalsh.name/event-delegate)
70 +to achieve high performance when implementing DOM events. For each type of DOM
71 +event, React adds a single top-level listener and determines which event
72 +handlers to execute by simulating event capturing and bubbling.
73 +
74 +DOM event handlers are called with a normalized `AbstractEvent` object that has
75 +cross-browser compatible implementations of `stopPropagation` and
76 +`preventDefault()`. If you need access to the raw browser event, you can use the
77 +`nativeEvent` property.
78 +
79 +> Note:
80 +>
81 +> The `AbstractEvent` object is JSON serializable so that React applications can
82 +> be executed inside web workers.
83 +
84 +### Touch Events
85 +
86 +If you want to use touch events, you must configure React's event system to
87 +initialize them:
88 +
89 +```javascript
90 +// Invoke before calling `React.renderComponent()`.
91 +React.initializeTouchEvents(true);
92 +```
93 +
94 +## Custom Events
95 +
96 +Notice that event listeners are attached by simply passing them into components
97 +as props. For DOM components, events are handled using top-level event
98 +delegation. For composite components, event handling is up to the component's
99 +implementation.
100 +
101 +Here is an example of a toggle link that fires a custom `onToggle` event:
102 +
103 +```javascript
104 +var ToggleLink = React.createClass({
105 + getInitialState: function() {
106 + return {isEnabled: false};
107 + },
108 + render: function() {
109 + return <a onClick={this.handleClick}>Toggle</a>;
110 + },
111 + handleClick: React.autoBind(function() {
112 + var willEnable = !this.state.isEnabled;
113 + if (this.props.onToggle) {
114 + this.props.onToggle(willEnable)
115 + }
116 + this.setState({isEnabled: willEnable});
117 + })
118 +});
119 +
120 +var handleToggle = function(enabled) {
121 + alert(enabled ? 'Enabled.' : 'Disabled.');
122 +};
123 +var myToggleLink = <ToggleLink onToggle={handleToggle} />;
124 +```
docs/docs/getting-started.md new
+106
@@ -0,0 +1,106 @@
1 +---
2 +id: docs-getting-started
3 +title: Getting Started
4 +layout: docs
5 +next: tutorial.html
6 +---
7 +Download the starter kit to get started.
8 +
9 +<div class="buttons-unit downloads">
10 + <a href="/react/downloads/react-{{site.react_version}}.zip" class="button">
11 + Download Starter Kit {{site.react_version}}
12 + </a>
13 +</div>
14 +
15 +## Hello, world!
16 +
17 +In the root directory of the starter kit, create a `helloworld.html` with the following contents.
18 +
19 +```html
20 +<!DOCTYPE html>
21 +<html>
22 + <head>
23 + <script src="build/react.min.js"></script>
24 + <script src="build/JSXTransformer.js"></script>
25 + </head>
26 + <body>
27 + <div id="example"></div>
28 + <script type="text/jsx">
29 + /** @jsx React.DOM */
30 + React.renderComponent(
31 + <h1>Hello, world!</h1>,
32 + document.getElementById('example')
33 + );
34 + </script>
35 + </body>
36 +</html>
37 +```
38 +
39 +The XML syntax inside of JavaScript is called JSX; check out the [JSX syntax](syntax.html) 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.
40 +
41 +### Separate File
42 +
43 +Your React JSX file can live in a separate file. Create the following `src/helloworld.js`.
44 +
45 +```javascript
46 +/** @jsx React.DOM */
47 +React.renderComponent(
48 + <h1>Hello, world!</h1>,
49 + document.getElementById('example')
50 +);
51 +```
52 +Then reference it from `helloworld.html`:
53 +
54 +```html{10}
55 +<script type="text/jsx" src="src/helloworld.js"></script>
56 +```
57 +
58 +### Offline Transform
59 +
60 +First install the command-line tools (requires [npm](http://npmjs.org/)):
61 +
62 +```
63 +npm install -g react-tools
64 +```
65 +
66 +Then, translate your `src/helloworld.js` file to plain JavaScript:
67 +
68 +```
69 +jsx --watch src/ build/
70 +
71 +```
72 +
73 +The file `build/helloworld.js` is autogenerated whenever you make a change.
74 +
75 +```javascript{3}
76 +/** @jsx React.DOM */
77 +React.renderComponent(
78 + React.DOM.h1(null, 'Hello, world!'),
79 + document.getElementyById('example')
80 +);
81 +```
82 +
83 +Update your HTML file as below:
84 +
85 +```html{6,10}
86 +<!DOCTYPE html>
87 +<html>
88 + <head>
89 + <title>Hello React!</title>
90 + <script src="build/react.min.js"></script>
91 + <!-- No need for JSXTransformer! -->
92 + </head>
93 + <body>
94 + <div id="example"></div>
95 + <script src="build/helloworld.js"></script>
96 + </body>
97 +</html>
98 +```
99 +
100 +## Want CommonJS?
101 +
102 +If you want to use React within a module system, [fork our repo](http://github.com/facebook/react), `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.
103 +
104 +## Next Steps
105 +
106 +Check out [the tutorial](tutorial.html) and the other examples in the `/examples` directory to learn more. Good luck, and welcome!
docs/docs/syntax.md new
+151
@@ -0,0 +1,151 @@
1 +---
2 +id: docs-syntax
3 +title: JSX Syntax
4 +description: Writing JavaScript with XML syntax.
5 +layout: docs
6 +prev: common-questions.html
7 +next: component-basics.html
8 +---
9 +
10 +JSX is a JavaScript XML syntax extension recommended (but not required) for use
11 +with React.
12 +
13 +JSX makes code that deeply nested React components more readable, and writing it
14 +feels like writing HTML. React documentation examples make use of JSX.
15 +
16 +## The Transform
17 +
18 +JSX transforms XML-like syntax into native JavaScript. It turns XML elements and
19 +attributes into function calls and objects, respectively.
20 +
21 +```javascript
22 +var Nav;
23 +// Input (JSX):
24 +var app = <Nav color="blue" />;
25 +// Output (JS):
26 +var app = Nav({color:'blue'});
27 +```
28 +
29 +Notice that in order to use `<Nav />`, the `Nav` variable must be in scope.
30 +
31 +JSX also allows specifying children using XML syntax:
32 +
33 +```javascript
34 +var Nav, Profile;
35 +// Input (JSX):
36 +var app = <Nav color="blue"><Profile>click</Profile></Nav>;
37 +// Output (JS):
38 +var app = Nav({color:'blue'}, Profile({}, 'click'));
39 +```
40 +
41 +The [Getting Started](getting-started.html) guide shows how to setup JSX
42 +compilation.
43 +
44 +> Note:
45 +>
46 +> Details about the code transform are given here to increase understanding, but
47 +> your code should not rely on these implementation details.
48 +
49 +## React and JSX
50 +
51 +React and JSX are independent technologies, but JSX was primarily built with
52 +React in mind. The two valid uses of JSX are:
53 +
54 +- To construct instances of React DOM components (`React.DOM.*`).
55 +- To construct instances of composite components created with
56 + `React.createClass()`.
57 +
58 +**React DOM Components**
59 +
60 +To construct a `<div>` is to create a variable that refers to `React.DOM.div`.
61 +
62 +```javascript
63 +var div = React.DOM.div;
64 +var app = <div className="appClass">Hello, React!</div>;
65 +```
66 +
67 +**React Component Components**
68 +
69 +To construct an instance of a composite component, create a variable that
70 +references the class.
71 +
72 +```javascript
73 +var MyComponent = React.createClass({/*...*/});
74 +var app = <MyComponent someProperty={true} />;
75 +```
76 +
77 +See [Component Basics](component-basics.html) to learn more about components.
78 +
79 +> Note:
80 +>
81 +> Since JSX is JavaScript, identifiers such as `class` and `for` are discouraged
82 +> as XML attribute names. Instead, React DOM components expect attributes like
83 +> `className` and `htmlFor`, respectively.
84 +
85 +## DOM Convenience
86 +
87 +Having to define variables for every type of DOM element can get tedious
88 +(e.g. `var div, span, h1, h2, ...`). JSX provides a convenience to address this
89 +problem by allowing you to specify a variable in an `@jsx` docblock field. JSX
90 +will use that field to find DOM components.
91 +
92 +```javascript
93 +/**
94 + * @jsx React.DOM
95 + */
96 +var Nav;
97 +// Input (JSX):
98 +var tree = <Nav><span /></Nav>;
99 +// Output (JS):
100 +var tree = Nav({}, React.DOM.span({}));
101 +```
102 +
103 +> Remember:
104 +>
105 +> JSX simply transforms elements into function calls and has no notion of the
106 +> DOM. The docblock parameter is only a convenience to resolve the most commonly
107 +> used elements. In general, JSX has no notion of the DOM.
108 +
109 +## JavaScript Expressions
110 +
111 +#### Attribute Expressions
112 +
113 +To use a JavaScript expression as an attribute value, wrap the expression in a
114 +pair of curly braces (`{}`) instead of quotes (`""`).
115 +
116 +```javascript
117 +// Input (JSX):
118 +var person = <Person name={window.isLoggedIn ? window.name : ''} />;
119 +// Output (JS):
120 +var person = Person({name: window.isLoggedIn ? window.name : ''});
121 +```
122 +
123 +#### Child Expressions
124 +
125 +Likewise, JavaScript expressions may be used to express children:
126 +
127 +```javascript
128 +// Input (JSX):
129 +var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
130 +// Output (JS):
131 +var content = Container({}, window.isLoggedIn ? <Nav /> : <Login />);
132 +```
133 +
134 +## Tooling
135 +
136 +Beyond the compilation step, JSX does not require any special tools.
137 +
138 +- Many editors already include reasonable support for JSX (Vim, Emacs js2-mode).
139 +- Linting provides accurate line numbers after compiling without sourcemaps.
140 +- Elements use standard scoping so linters can find usage of out-of-scope
141 + components.
142 +
143 +## Prior Work
144 +
145 +JSX is similar to several other JavaScript embedded XML language
146 +proposals/projects. Some of the features of JSX that distinguish it from similar
147 +efforts include:
148 +
149 +- JSX is a simple syntactic transform.
150 +- JSX neither provides nor requires a runtime library.
151 +- JSX does not alter or add to the semantics of JavaScript.
docs/docs/tutorial.md new
+674
@@ -0,0 +1,674 @@
1 +---
2 +id: docs-tutorial
3 +title: Tutorial
4 +layout: docs
5 +prev: getting-started.html
6 +next: common-questions.html
7 +---
8 +
9 +We'll be building a simple, but realistic comments box that you can drop into a blog, similar to Disqus, LiveFyre or Facebook comments.
10 +
11 +We'll provide:
12 +
13 +* A view of all of the comments
14 +* A form to submit a comment
15 +* Hooks for you to provide a custom backend
16 +
17 +It'll also have a few neat features:
18 +
19 +* **Optimistic commenting:** comments appear in the list before they're saved on the server so it feels fast.
20 +* **Live updates:** as other users comment we'll pop them into the comment view in real time
21 +* **Markdown formatting:** users can use Markdown to format their text
22 +
23 +## Getting started
24 +
25 +For this tutorial we'll use prebuilt JavaScript files on a CDN. Open up your favorite editor and create a new HTML document:
26 +
27 +```html
28 +<!-- template.html -->
29 +<html>
30 + <head>
31 + <title>Hello React</title>
32 + <script src="http://fb.me/react-{{site.react_version}}.js"></script>
33 + <script src="http://fb.me/JSXTransformer-{{site.react_version}}.js"></script>
34 + </head>
35 + <body>
36 + <div id="content"></div>
37 + <script type="text/jsx">
38 + /**
39 + * @jsx React.DOM
40 + */
41 + // Your code here
42 + </script>
43 + </body>
44 +</html>
45 +```
46 +
47 +For the remainder of this tutorial, we'll be writing our JavaScript code in this script tag.
48 +
49 +## Your first component
50 +
51 +React is all about modular, composable components. For our comment box example, we'll have the following component structure:
52 +
53 +```
54 +- CommentBox
55 + - CommentList
56 + - Comment
57 + - CommentForm
58 +```
59 +
60 +Let's build the `CommentBox` component, which is just a simple `<div>`:
61 +
62 +```javascript
63 +// tutorial1.js
64 +var CommentBox = React.createClass({
65 + render: function() {
66 + return (
67 + <div class="commentBox">
68 + Hello, world! I am a CommentBox.
69 + </div>
70 + );
71 + }
72 +});
73 +React.renderComponent(
74 + <CommentBox />,
75 + document.getElementById('content')
76 +);
77 +```
78 +
79 +### JSX Syntax
80 +
81 +The first thing you'll notice is the XML-ish syntax in your JavaScript. We have a simple precompiler that translates the syntactic sugar to this plain JavaScript:
82 +
83 +```javascript
84 +// tutorial1-raw.js
85 +var CommentBox = React.createClass({
86 + render: function() {
87 + return (
88 + ReactDOM.div({
89 + className: 'commentBox',
90 + children: 'Hello, world! I am a CommentBox.'
91 + })
92 + );
93 + }
94 +});
95 +React.renderComponent(
96 + CommentBox({}),
97 + document.getElementById('content')
98 +);
99 +```
100 +
101 +Its use is optional but we've found JSX syntax easier to use than plain JavaScript. Read more on the [JSX Syntax article](syntax.html).
102 +
103 +### What's going on
104 +
105 +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.
106 +
107 +The `<div>` tags are not actual DOM nodes; they are instantiations of React `div` components. You can think of these as markers or pieces of data that React knows how to handle. React is **safe**. We are not generating HTML strings so XSS protection is the default.
108 +
109 +You do not have to return basic HTML. You can return a tree of components that you (or someone else built). This is what makes React **composable**: a key tenet of maintainable frontends.
110 +
111 +`React.renderComponent()` instantiates the root component, starts the framework, and injects the markup into a raw DOM element, provided as the second argument.
112 +
113 +# Composing components
114 +
115 +Let's build skeletons for `CommentList` and `CommentForm` which will, again, be simple `<div>`s:
116 +
117 +```javascript
118 +// tutorial2.js
119 +var CommentList = React.createClass({
120 + render: function() {
121 + return (
122 + <div class="commentList">
123 + Hello, world! I am a CommentList.
124 + </div>
125 + );
126 + }
127 +});
128 +
129 +var CommentForm = React.createClass({
130 + render: function() {
131 + return (
132 + <div class="commentForm">
133 + Hello, world! I am a CommentForm.
134 + </div>
135 + );
136 + }
137 +});
138 +```
139 +
140 +Next, update the `CommentBox` component to use its new friends:
141 +
142 +```javascript{6-8}
143 +// tutorial3.js
144 +var CommentBox = React.createClass({
145 + render: function() {
146 + return (
147 + <div class="commentBox">
148 + <h1>Comments</h1>
149 + <CommentList />
150 + <CommentForm />
151 + </div>
152 + );
153 + }
154 +});
155 +```
156 +
157 +Notice how we're mixing HTML tags and components we've built. HTML components are regular React components, just like the ones you define, with one difference. The JSX compiler will automatically rewrite HTML tags to "ReactDOM.tagName" expressions and leave everything else alone. This is to prevent the pollution of the global namespace.
158 +
159 +## Component Properties
160 +
161 +Let's create our third component, `Comment`. We will want to pass it the author name and comment text so we can reuse the same code for each unique comment. First let's add some comments to the `CommentList`:
162 +
163 +```javascript{6-7}
164 +// tutorial4.js
165 +var CommentList = React.createClass({
166 + render: function() {
167 + return (
168 + <div class="commentList">
169 + <Comment author="Pete Hunt">This is one comment</Comment>
170 + <Comment author="Jordan Walke">This is *another* comment</Comment>
171 + </div>
172 + );
173 + }
174 +});
175 +```
176 +
177 +Note that we have passed some data from the parent `CommentList` component to the child `Comment` component as both XML-like children and attributes. Data passed from parent to child is called **props**, short for properties.
178 +
179 +## Using props
180 +
181 +Let's create the Comment component. It will read the data passed to it from the CommentList and render some markup:
182 +
183 +```javascript
184 +// tutorial5.js
185 +var Comment = React.createClass({
186 + render: function() {
187 + return (
188 + <div class="comment">
189 + <h2 class="commentAuthor">
190 + {this.props.author}
191 + </h2>
192 + {this.props.children}
193 + </div>
194 + );
195 + }
196 +});
197 +```
198 +
199 +By surrounding a JavaScript expression in braces inside JSX (as either an attribute or child), you can drop text or React components into the tree. We access named attributes passed to the component as keys on `this.props` and any nested elements as `this.props.children`.
200 +
201 +## Adding Markdown
202 +
203 +Markdown is a simple way to format your text inline. For example, surrounding text with asterisks will make it emphasized.
204 +
205 +First, add the third-party **Showdown** library to your application. This is a JavaScript library which takes Markdown text and converts it to raw HTML. This requires a script tag in your head (which we have already included in the React playground).
206 +
207 +Next, let's convert the comment text to Markdown and output it:
208 +
209 +```javascript{2,10}
210 +// tutorial6.js
211 +var converter = new Showdown.converter();
212 +var Comment = React.createClass({
213 + render: function() {
214 + return (
215 + <div class="comment">
216 + <h2 class="commentAuthor">
217 + {this.props.author}
218 + </h2>
219 + {converter.makeHtml(this.props.children.toString())}
220 + </div>
221 + );
222 + }
223 +});
224 +```
225 +
226 +All we're doing here is calling the Showdown library. We need to convert `this.props.children` from React's wrapped text to a raw string that Showdown will understand so we explicitly call `toString()`.
227 +
228 +But there's a problem! Our rendered comments look like this in the browser: "`<p>`This is `<em>`another`</em>` comment`</p>`". We want those tags to actually render as HTML.
229 +
230 +That's React protecting you from an XSS attack. There's a way to get around it but the framework warns you not to use it:
231 +
232 +```javascript{5,11}
233 +// tutorial7.js
234 +var converter = new Showdown.converter();
235 +var Comment = React.createClass({
236 + render: function() {
237 + var rawMarkup = converter.makeHtml(this.props.children.toString());
238 + return (
239 + <div class="comment">
240 + <h2 class="commentAuthor">
241 + {this.props.author}
242 + </h2>
243 + <span dangerouslySetInnerHTML={{"{{"}}__html: rawMarkup}} />
244 + </div>
245 + );
246 + }
247 +});
248 +```
249 +
250 +This is a special API that intentionally makes it difficult to insert raw HTML, but for Showdown we'll take advantage of this backdoor.
251 +
252 +**Remember:** by using this feature you're relying on Showdown to be secure.
253 +
254 +## Hook up the data model
255 +
256 +So far we've been inserting the comments directly in the source code. Instead, let's render a blob of JSON data into the comment list. Eventually this will come from the server, but for now, write it in your source:
257 +
258 +```javascript
259 +// tutorial8.js
260 +var data = [
261 + {author: 'Pete Hunt', text: 'This is one comment'},
262 + {author: 'Jordan Walke', text: 'This is *another* comment'}
263 +];
264 +```
265 +
266 +We need to get this data into `CommentList` in a modular way. Modify `CommentBox` and the `renderComponent()` call to pass this data into the `CommentList` via props:
267 +
268 +```javascript{7,15}
269 +// tutorial9.js
270 +var CommentBox = React.createClass({
271 + render: function() {
272 + return (
273 + <div class="commentBox">
274 + <h1>Comments</h1>
275 + <CommentList data={this.props.data} />
276 + <CommentForm />
277 + </div>
278 + );
279 + }
280 +});
281 +
282 +React.renderComponent(
283 + <CommentBox data={data} />,
284 + document.getElementById('content')
285 +);
286 +```
287 +
288 +Now that the data is available in the `CommentList`, let's render the comments dynamically:
289 +
290 +```javascript{4-6}
291 +// tutorial10.js
292 +var CommentList = React.createClass({
293 + render: function() {
294 + var commentNodes = this.props.data.map(function (comment) {
295 + return <Comment author={comment.author}>{comment.text}</Comment>;
296 + });
297 + return (
298 + <div class="commentList">
299 + {commentNodes}
300 + </div>
301 + );
302 + }
303 +});
304 +```
305 +
306 +That's it!
307 +
308 +## Fetching from the server
309 +
310 +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:
311 +
312 +```javascript{2}
313 +// tutorial11.js
314 +React.renderComponent(
315 + <CommentBox url="comments.json" />,
316 + document.getElementById('example')
317 +);
318 +```
319 +
320 +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.
321 +
322 +## Reactive state
323 +
324 +So far, each component has rendered itself once based on its props. `props` are immutable: they are passed from the parent and are "owned" by the parent. To implement interactions, we introduce mutable **state** to the component. `this.state` is private to the component and can be changed by calling `this.setState()`. When the state is updated, the component re-renders itself.
325 +
326 +`render()` methods are written declaratively as functions of `this.props` and `this.state`. The framework guarantees the UI is always consistent with the inputs.
327 +
328 +When the server fetches data, we will be changing the comment data we have. Let's add an array of comment data to the `CommentBox` component as its state:
329 +
330 +```javascript{3-5}
331 +// tutorial12.js
332 +var CommentBox = React.createClass({
333 + getInitialState: function() {
334 + return {data: []};
335 + },
336 + render: function() {
337 + return (
338 + <div class="commentBox">
339 + <h1>Comments</h1>
340 + <CommentList data={this.state.data} />
341 + <CommentForm />
342 + </div>
343 + );
344 + }
345 +});
346 +```
347 +
348 +`getInitialState()` executes exactly once during the lifecycle of the component and sets up the initial state of the component.
349 +
350 +### Updating state
351 +When the component is first created, we want to GET some JSON from the server and update the state to reflect the latest data. In a real application this would be a dynamic endpoint, but for this example, we will use a static JSON file to keep things simple:
352 +
353 +```javascript
354 +// tutorial13.json
355 +[
356 + {'author': 'Pete Hunt', 'text': 'This is one comment'},
357 + {'author': 'Jordan Walke', 'text': 'This is *another* comment'}
358 +]
359 +```
360 +
361 +We will use jQuery 1.5 to help make an asynchronous request to the server.
362 +
363 +Note: because this is becoming an AJAX application you'll need to develop your app using a web server rather than as a file sitting on your file system. The easiest way to do this is to run `python -m SimpleHTTPServer` in your application's directory.
364 +
365 +```javascript{4-11}
366 +// tutorial13.js
367 +var CommentBox = React.createClass({
368 + getInitialState: function() {
369 + $.ajax({
370 + url: 'comments.json',
371 + dataType: 'json',
372 + mimeType: 'textPlain',
373 + success: function(data) {
374 + this.setState({data: data});
375 + }.bind(this)
376 + });
377 + return {data: []};
378 + },
379 + render: function() {
380 + return (
381 + <div class="commentBox">
382 + <h1>Comments</h1>
383 + <CommentList data={this.state.data} />
384 + <CommentForm />
385 + </div>
386 + );
387 + }
388 +});
389 +```
390 +
391 +The key 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.
392 +
393 +```javascript{3,17-21,35}
394 +// tutorial14.js
395 +var CommentBox = React.createClass({
396 + loadCommentsFromServer: function() {
397 + $.ajax({
398 + url: this.props.url,
399 + dataType: 'json',
400 + mimeType: 'textPlain',
401 + success: function(data) {
402 + this.setState({data: data});
403 + }.bind(this)
404 + });
405 + },
406 + getInitialState: function() {
407 + return {data: []};
408 + },
409 + componentWillMount: function() {
410 + this.loadCommentsFromServer();
411 + setInterval(
412 + this.loadCommentsFromServer.bind(this),
413 + this.props.pollInterval
414 + );
415 + },
416 + render: function() {
417 + return (
418 + <div class="commentBox">
419 + <h1>Comments</h1>
420 + <CommentList data={this.state.data} />
421 + <CommentForm />
422 + </div>
423 + );
424 + }
425 +});
426 +
427 +React.renderComponent(
428 + <CommentBox url="comments.json" pollInterval={5000} />,
429 + document.getElementById('content')
430 +);
431 +
432 +```
433 +
434 +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 60 seconds after that. Try running this in your browser and changing the `comments.json` file; within 5 seconds, the changes will show!
435 +
436 +## Adding new comments
437 +
438 +Now it's time to build the form. Our `CommentForm` component should ask the user for their name and comment text and send a request to the server to save the comment.
439 +
440 +```javascript{5-8}
441 +// tutorial15.js
442 +var CommentForm = React.createClass({
443 + render: function() {
444 + return (
445 + <form class="commentForm">
446 + <input type="text" placeholder="Your name" />
447 + <input type="text" placeholder="Say something..." />
448 + </form>
449 + );
450 + }
451 +});
452 +```
453 +
454 +Let's make the form interactive. When the user presses enter, we should clear the form, submit a request to the server, and refresh the list of comments. To start, let's listen for this event and just clear the form.
455 +
456 +```javascript{3-12,15,20}
457 +// tutorial16.js
458 +var CommentForm = React.createClass({
459 + handleSubmit: React.autoBind(function() {
460 + var author = this.refs.author.getDOMNode().value.trim();
461 + var text = this.refs.text.getDOMNode().value.trim();
462 + if (!text || !author) {
463 + return;
464 + }
465 + // TODO: send request to the server
466 + this.refs.author.getDOMNode().value = '';
467 + this.refs.text.getDOMNode().value = '';
468 + }),
469 + render: function() {
470 + return (
471 + <form class="commentForm" onSubmit={this.handleSubmit}>
472 + <input type="text" placeholder="Your name" ref="author" />
473 + <input
474 + type="text"
475 + placeholder="Say something..."
476 + ref="text"
477 + />
478 + </form>
479 + );
480 + }
481 +});
482 +```
483 +
484 +#### Events
485 +
486 +React attaches event handlers to components using a camelCase naming convention. We attach an onKeyUp handler to the text field, check if the user has entered text and pressed the enter key and then clear the form fields.
487 +
488 +`React.autoBind()` is a simple way to ensure that a method is always bound to its component. Inside the method, `this` will be bound to the component instance.
489 +
490 +#### Refs
491 +
492 +We use the `ref` attribute to assign a name to a child component and `this.refs` to reference the component. We can call `getDOMNode()` on a component to get the native browser DOM element.
493 +
494 +#### Callbacks as props
495 +
496 +When a user submits a comment, we will need to refresh the list of comments to include the new one. It makes sense to do all of this logic in `CommentBox` since `CommentBox` owns the state that represents the list of comments.
497 +
498 +We need to pass data from the child component to its parent. We do this by passing a `callback` in props from parent to child:
499 +
500 +```javascript{13-15,30}
501 +// tutorial17.js
502 +var CommentBox = React.createClass({
503 + loadCommentsFromServer: function() {
504 + $.ajax({
505 + url: this.props.url,
506 + dataType: 'json',
507 + mimeType: 'textPlain',
508 + success: function(data) {
509 + this.setState({data: data});
510 + }.bind(this)
511 + });
512 + },
513 + handleCommentSubmit: React.autoBind(function(comment) {
514 + // TODO: submit to the server and refresh the list
515 + }),
516 + getInitialState: function() {
517 + return {data: []};
518 + },
519 + componentWillMount: function() {
520 + this.loadCommentsFromServer();
521 + setInterval(
522 + this.loadCommentsFromServer.bind(this),
523 + this.props.pollInterval
524 + );
525 + },
526 + render: function() {
527 + return (
528 + <div class="commentBox">
529 + <h1>Comments</h1>
530 + <CommentList data={this.state.data} />
531 + <CommentForm
532 + onCommentSubmit={this.handleCommentSubmit}
533 + />
534 + </div>
535 + );
536 + }
537 +});
538 +```
539 +
540 +Let's call the callback from the `CommentForm` when the user presses enter:
541 +
542 +```javascript{6}
543 +// tutorial18.js
544 +var CommentForm = React.createClass({
545 + handleSubmit: React.autoBind(function() {
546 + var author = this.refs.author.getDOMNode().value.trim();
547 + var text = this.refs.text.getDOMNode().value.trim();
548 + this.props.onCommentSubmit({author: author, text: text});
549 + this.refs.author.getDOMNode().value = '';
550 + this.refs.text.getDOMNode().value = '';
551 + }),
552 + render: function() {
553 + return (
554 + <form class="commentForm" onSubmit={this.handleSubmit}>
555 + <input type="text" placeholder="Your name" ref="author" />
556 + <input
557 + type="text"
558 + placeholder="Say something..."
559 + ref="text"
560 + />
561 + </form>
562 + );
563 + }
564 +});
565 +```
566 +
567 +Now that the callbacks are in place, all we have to do is submit to the server and refresh the list:
568 +
569 +```javascript{14-22}
570 +// tutorial19.js
571 +var CommentBox = React.createClass({
572 + loadCommentsFromServer: function() {
573 + $.ajax({
574 + url: this.props.url,
575 + dataType: 'json',
576 + mimeType: 'textPlain',
577 + success: function(data) {
578 + this.setState({data: data});
579 + }.bind(this)
580 + });
581 + },
582 + handleCommentSubmit: React.autoBind(function(comment) {
583 + $.ajax({
584 + url: this.props.url,
585 + data: comment,
586 + dataType: 'json',
587 + mimeType: 'textPlain',
588 + success: function(data) {
589 + this.setState({data: data});
590 + }.bind(this)
591 + });
592 + }),
593 + getInitialState: function() {
594 + return {data: []};
595 + },
596 + componentWillMount: function() {
597 + this.loadCommentsFromServer();
598 + setInterval(
599 + this.loadCommentsFromServer.bind(this),
600 + this.props.pollInterval
601 + );
602 + },
603 + render: function() {
604 + return (
605 + <div class="commentBox">
606 + <h1>Comments</h1>
607 + <CommentList data={this.state.data} />
608 + <CommentForm
609 + onCommentSubmit={this.handleCommentSubmit}
610 + />
611 + </div>
612 + );
613 + }
614 +});
615 +```
616 +
617 +## Optimization: optimistic updates
618 +
619 +Our application is now feature complete but it feels slow to have to wait for the request to complete before your comment appears in the list. We can optimistically add this comment to the list to make the app feel faster.
620 +
621 +```javascript{14-16}
622 +// tutorial20.js
623 +var CommentBox = React.createClass({
624 + loadCommentsFromServer: function() {
625 + $.ajax({
626 + url: this.props.url,
627 + dataType: 'json',
628 + mimeType: 'textPlain',
629 + success: function(data) {
630 + this.setState({data: data});
631 + }.bind(this)
632 + });
633 + },
634 + handleCommentSubmit: React.autoBind(function(comment) {
635 + var comments = this.state.data;
636 + comments.push(comment);
637 + this.setState({data: comments});
638 + $.ajax({
639 + url: this.props.url,
640 + data: comment,
641 + dataType: 'json',
642 + mimeType: 'textPlain',
643 + success: function(data) {
644 + this.setState({data: data});
645 + }.bind(this)
646 + });
647 + }),
648 + getInitialState: function() {
649 + return {data: []};
650 + },
651 + componentWillMount: function() {
652 + this.loadCommentsFromServer();
653 + setInterval(
654 + this.loadCommentsFromServer.bind(this),
655 + this.props.pollInterval
656 + );
657 + },
658 + render: function() {
659 + return (
660 + <div class="commentBox">
661 + <h1>Comments</h1>
662 + <CommentList data={this.state.data} />
663 + <CommentForm
664 + onCommentSubmit={this.handleCommentSubmit}
665 + />
666 + </div>
667 + );
668 + }
669 +});
670 +```
671 +
672 +## Congrats!
673 +
674 +You have just built a comment box in a few simple steps. Learn more about React in the [reference](syntax.html) or start hacking! Good luck!
docs/downloads.md new
+44
@@ -0,0 +1,44 @@
1 +---
2 +id: downloads
3 +title: Downloads
4 +layout: single
5 +---
6 +Download the starter kit to get everything you need to
7 +[get started with React](/react/docs/getting-started.html).
8 +
9 +<div class="buttons-unit downloads">
10 + <a href="/react/downloads/react-{{site.react_version}}.zip" class="button">
11 + Download Starter Kit {{site.react_version}}
12 + </a>
13 +</div>
14 +
15 +## Individual Downloads
16 +
17 +#### <a href="http://fb/me/react-{{site.react_version}}.min.js">React Core {{site.react_version}} (production)</a>
18 +The compressed, production version of React core
19 +
20 +#### <a href="http://fb/me/react-{{site.react_version}}.js">React Core {{site.react_version}} (development)</a>
21 +The uncompressed, development version of React core with inline documentation.
22 +
23 +#### <a href="http://fb/me/JSXTransformer-{{site.react_version}}.js">JSX Transform</a>
24 +The JSX transformer used to support [XML syntax](/react/docs/syntax.html) in JavaScript.
25 +
26 +## Bower
27 +
28 +```sh
29 +$ bower install react
30 +```
31 +
32 +## NPM
33 +
34 +```sh
35 +$ npm install -g react-tools
36 +```
37 +
38 +## Release Notes
39 +
40 +**0.3** Initial public release.
41 +
42 +**0.2** Standardize API & refactor component lifecycle. Normalize DOM interactions.
43 +
44 +**0.1** Initial release.
docs/extractCode.js new
+37
@@ -0,0 +1,37 @@
1 +var argv = require('optimist').argv;
2 +var fs = require('fs');
3 +
4 +var CODE_SAMPLE = /```[\S]+\s*[\s\S]*?```/g;
5 +var PARTS = /```[\S]+\s*\/\/\s+(.+?)\n([\s\S]*?)```/;
6 +
7 +function truncate(s, n) {
8 + n = n || 256
9 + if (s.length < n) {
10 + return s;
11 + }
12 + return s.substring(0, n) + '...';
13 +}
14 +
15 +function main(dest, filenames) {
16 + if (!dest) {
17 + throw new Error('no dest provided');
18 + }
19 + filenames.map(function (filename) {
20 + var content = fs.readFileSync(filename).toString('utf8');
21 + var codeSamples = content.match(CODE_SAMPLE);
22 +
23 + codeSamples.map(function (codeSample) {
24 + // Do a little jank preprocessing
25 + codeSample = codeSample.replace('<!--', '//').replace(' -->', '');
26 + var extracted = codeSample.match(PARTS);
27 + if (!extracted) {
28 + throw new Error('Code sample did not match correct format in ' + filename + ': ' + truncate(codeSample));
29 + }
30 + var filename = extracted[1];
31 + var content = extracted[2].replace(/\*\*/g, '');
32 + fs.writeFileSync(argv.dest + '/' + filename, content);
33 + });
34 + });
35 +}
36 +
37 +main(argv.dest, argv._);
\ No newline at end of file
docs/favicon.ico
Binary files /dev/null and b/docs/favicon.ico differ
docs/img/logo_og.png
Binary files /dev/null and b/docs/img/logo_og.png differ
docs/img/logo_small.png
Binary files /dev/null and b/docs/img/logo_small.png differ
docs/img/logo_small@2x.png
Binary files /dev/null and b/docs/img/logo_small@2x.png differ
docs/img/noise.png
Binary files /dev/null and b/docs/img/noise.png differ
docs/index.md new
+84
@@ -0,0 +1,84 @@
1 +---
2 +layout: page
3 +title: A JavaScript library for building user interfaces
4 +id: home
5 +---
6 +<section class="light home-section">
7 + <div class="marketing-row">
8 + <div class="marketing-col">
9 + <h3>Declarative</h3>
10 + <p>
11 + React uses a declarative paradigm that makes it easier to reason about
12 + your application.
13 + </p>
14 + </div>
15 + <div class="marketing-col">
16 + <h3>Efficient</h3>
17 + <p>
18 + React minimizes interactions with the DOM by using a mock representation
19 + of the DOM.
20 + </p>
21 + </div>
22 + <div class="marketing-col">
23 + <h3>Flexible</h3>
24 + <p>
25 + React works with the libraries and frameworks that you already know.
26 + </p>
27 + </div>
28 + </div>
29 +</section>
30 +<hr class="home-divider" />
31 +<section class="home-section">
32 + <div id="examples">
33 + <div class="example">
34 + <h3>A Simple Component</h3>
35 + <p>
36 + React components implement a `render()` method that takes input data and
37 + returns what to display. This example constructs the component using an
38 + XML-like syntax called JSX. Input data is passed to the component as XML
39 + attributes, and `render()` accesses this data via `this.props`.
40 + </p>
41 + <div id="helloExample"></div>
42 + </div>
43 + <div class="example">
44 + <h3>A Stateful Component</h3>
45 + <p>
46 + In addition to taking data from its creator (accessed via `this.props`),
47 + a component can maintain internal state data (accessed via
48 + `this.state`). When a component's state data changes, the rendered
49 + markup will be updated by re-invoking `render()`.
50 + </p>
51 + <div id="timerExample"></div>
52 + </div>
53 + <div class="example">
54 + <h3>An Application</h3>
55 + <p>
56 + Using properties and state, we can put together a small Todo
57 + application. React provides an interface to the DOM via `refs`. Although
58 + event handlers appear to be rendered inline, they will be
59 + collected and implemented using event delegation.
60 + </p>
61 + <div id="todoExample"></div>
62 + </div>
63 + <div class="example">
64 + <h3>A Component Using External Plugins</h3>
65 + <p>
66 + React is flexible and provides hooks that allow you to interface with
67 + other libraries and frameworks. This example uses Showdown, an external
68 + Markdown library, to convert the textarea's value in real-time.
69 + </p>
70 + <div id="markdownExample"></div>
71 + </div>
72 + </div>
73 + <script type="text/javascript" src="js/examples/hello.js"></script>
74 + <script type="text/javascript" src="js/examples/timer.js"></script>
75 + <script type="text/javascript" src="js/examples/todo.js"></script>
76 + <script type="text/javascript" src="js/examples/markdown.js"></script>
77 +</section>
78 +<hr class="home-divider" />
79 +<section class="home-bottom-section">
80 + <div class="buttons-unit">
81 + <a href="/getting-started.html" class="button">Get Started</a>
82 + <a href="/download.html" class="button">Download React v{{site.react_version}}</a>
83 + </div>
84 +</section>
docs/js/codemirror.js new
+5230
@@ -0,0 +1,5230 @@
1 +// CodeMirror is the only global var we claim
2 +window.CodeMirror = (function() {
3 + "use strict";
4 +
5 + // BROWSER SNIFFING
6 +
7 + // Crude, but necessary to handle a number of hard-to-feature-detect
8 + // bugs and behavior differences.
9 + var gecko = /gecko\/\d/i.test(navigator.userAgent);
10 + var ie = /MSIE \d/.test(navigator.userAgent);
11 + var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8);
12 + var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
13 + var webkit = /WebKit\//.test(navigator.userAgent);
14 + var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent);
15 + var chrome = /Chrome\//.test(navigator.userAgent);
16 + var opera = /Opera\//.test(navigator.userAgent);
17 + var safari = /Apple Computer/.test(navigator.vendor);
18 + var khtml = /KHTML\//.test(navigator.userAgent);
19 + var mac_geLion = /Mac OS X 1\d\D([7-9]|\d\d)\D/.test(navigator.userAgent);
20 + var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(navigator.userAgent);
21 + var phantom = /PhantomJS/.test(navigator.userAgent);
22 +
23 + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
24 + // This is woefully incomplete. Suggestions for alternative methods welcome.
25 + var mobile = ios || /Android|webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(navigator.userAgent);
26 + var mac = ios || /Mac/.test(navigator.platform);
27 + var windows = /windows/i.test(navigator.platform);
28 +
29 + var opera_version = opera && navigator.userAgent.match(/Version\/(\d*\.\d*)/);
30 + if (opera_version) opera_version = Number(opera_version[1]);
31 + // Some browsers use the wrong event properties to signal cmd/ctrl on OS X
32 + var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11));
33 + var captureMiddleClick = gecko || (ie && !ie_lt9);
34 +
35 + // Optimize some code when these features are not used
36 + var sawReadOnlySpans = false, sawCollapsedSpans = false;
37 +
38 + // CONSTRUCTOR
39 +
40 + function CodeMirror(place, options) {
41 + if (!(this instanceof CodeMirror)) return new CodeMirror(place, options);
42 +
43 + this.options = options = options || {};
44 + // Determine effective options based on given values and defaults.
45 + for (var opt in defaults) if (!options.hasOwnProperty(opt) && defaults.hasOwnProperty(opt))
46 + options[opt] = defaults[opt];
47 + setGuttersForLineNumbers(options);
48 +
49 + var docStart = typeof options.value == "string" ? 0 : options.value.first;
50 + var display = this.display = makeDisplay(place, docStart);
51 + display.wrapper.CodeMirror = this;
52 + updateGutters(this);
53 + if (options.autofocus && !mobile) focusInput(this);
54 +
55 + this.state = {keyMaps: [],
56 + overlays: [],
57 + modeGen: 0,
58 + overwrite: false, focused: false,
59 + suppressEdits: false, pasteIncoming: false,
60 + draggingText: false,
61 + highlight: new Delayed()};
62 +
63 + this.nextOpId = 0;
64 + themeChanged(this);
65 + if (options.lineWrapping)
66 + this.display.wrapper.className += " CodeMirror-wrap";
67 +
68 + var doc = options.value;
69 + if (typeof doc == "string") doc = new Doc(options.value, options.mode);
70 + operation(this, attachDoc)(this, doc);
71 +
72 + // Override magic textarea content restore that IE sometimes does
73 + // on our hidden textarea on reload
74 + if (ie) setTimeout(bind(resetInput, this, true), 20);
75 +
76 + registerEventHandlers(this);
77 + // IE throws unspecified error in certain cases, when
78 + // trying to access activeElement before onload
79 + var hasFocus; try { hasFocus = (document.activeElement == display.input); } catch(e) { }
80 + if (hasFocus || (options.autofocus && !mobile)) setTimeout(bind(onFocus, this), 20);
81 + else onBlur(this);
82 +
83 + operation(this, function() {
84 + for (var opt in optionHandlers)
85 + if (optionHandlers.propertyIsEnumerable(opt))
86 + optionHandlers[opt](this, options[opt], Init);
87 + for (var i = 0; i < initHooks.length; ++i) initHooks[i](this);
88 + })();
89 + }
90 +
91 + // DISPLAY CONSTRUCTOR
92 +
93 + function makeDisplay(place, docStart) {
94 + var d = {};
95 + var input = d.input = elt("textarea", null, null, "position: absolute; padding: 0; width: 1px; height: 1em; outline: none;");
96 + if (webkit) input.style.width = "1000px";
97 + else input.setAttribute("wrap", "off");
98 + input.setAttribute("autocorrect", "off"); input.setAttribute("autocapitalize", "off");
99 + // Wraps and hides input textarea
100 + d.inputDiv = elt("div", [input], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
101 + // The actual fake scrollbars.
102 + d.scrollbarH = elt("div", [elt("div", null, null, "height: 1px")], "CodeMirror-hscrollbar");
103 + d.scrollbarV = elt("div", [elt("div", null, null, "width: 1px")], "CodeMirror-vscrollbar");
104 + d.scrollbarFiller = elt("div", null, "CodeMirror-scrollbar-filler");
105 + // DIVs containing the selection and the actual code
106 + d.lineDiv = elt("div");
107 + d.selectionDiv = elt("div", null, null, "position: relative; z-index: 1");
108 + // Blinky cursor, and element used to ensure cursor fits at the end of a line
109 + d.cursor = elt("div", "\u00a0", "CodeMirror-cursor");
110 + // Secondary cursor, shown when on a 'jump' in bi-directional text
111 + d.otherCursor = elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor");
112 + // Used to measure text size
113 + d.measure = elt("div", null, "CodeMirror-measure");
114 + // Wraps everything that needs to exist inside the vertically-padded coordinate system
115 + d.lineSpace = elt("div", [d.measure, d.selectionDiv, d.lineDiv, d.cursor, d.otherCursor],
116 + null, "position: relative; outline: none");
117 + // Moved around its parent to cover visible view
118 + d.mover = elt("div", [elt("div", [d.lineSpace], "CodeMirror-lines")], null, "position: relative");
119 + // Set to the height of the text, causes scrolling
120 + d.sizer = elt("div", [d.mover], "CodeMirror-sizer");
121 + // D is needed because behavior of elts with overflow: auto and padding is inconsistent across browsers
122 + d.heightForcer = elt("div", "\u00a0", null, "position: absolute; height: " + scrollerCutOff + "px");
123 + // Will contain the gutters, if any
124 + d.gutters = elt("div", null, "CodeMirror-gutters");
125 + d.lineGutter = null;
126 + // Helper element to properly size the gutter backgrounds
127 + var scrollerInner = elt("div", [d.sizer, d.heightForcer, d.gutters], null, "position: relative; min-height: 100%");
128 + // Provides scrolling
129 + d.scroller = elt("div", [scrollerInner], "CodeMirror-scroll");
130 + d.scroller.setAttribute("tabIndex", "-1");
131 + // The element in which the editor lives.
132 + d.wrapper = elt("div", [d.inputDiv, d.scrollbarH, d.scrollbarV,
133 + d.scrollbarFiller, d.scroller], "CodeMirror");
134 + // Work around IE7 z-index bug
135 + if (ie_lt8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
136 + if (place.appendChild) place.appendChild(d.wrapper); else place(d.wrapper);
137 +
138 + // Needed to hide big blue blinking cursor on Mobile Safari
139 + if (ios) input.style.width = "0px";
140 + if (!webkit) d.scroller.draggable = true;
141 + // Needed to handle Tab key in KHTML
142 + if (khtml) { d.inputDiv.style.height = "1px"; d.inputDiv.style.position = "absolute"; }
143 + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
144 + else if (ie_lt8) d.scrollbarH.style.minWidth = d.scrollbarV.style.minWidth = "18px";
145 +
146 + // Current visible range (may be bigger than the view window).
147 + d.viewOffset = d.lastSizeC = 0;
148 + d.showingFrom = d.showingTo = docStart;
149 +
150 + // Used to only resize the line number gutter when necessary (when
151 + // the amount of lines crosses a boundary that makes its width change)
152 + d.lineNumWidth = d.lineNumInnerWidth = d.lineNumChars = null;
153 + // See readInput and resetInput
154 + d.prevInput = "";
155 + // Set to true when a non-horizontal-scrolling widget is added. As
156 + // an optimization, widget aligning is skipped when d is false.
157 + d.alignWidgets = false;
158 + // Flag that indicates whether we currently expect input to appear
159 + // (after some event like 'keypress' or 'input') and are polling
160 + // intensively.
161 + d.pollingFast = false;
162 + // Self-resetting timeout for the poller
163 + d.poll = new Delayed();
164 + // True when a drag from the editor is active
165 + d.draggingText = false;
166 +
167 + d.cachedCharWidth = d.cachedTextHeight = null;
168 + d.measureLineCache = [];
169 + d.measureLineCachePos = 0;
170 +
171 + // Tracks when resetInput has punted to just putting a short
172 + // string instead of the (large) selection.
173 + d.inaccurateSelection = false;
174 +
175 + // Tracks the maximum line length so that the horizontal scrollbar
176 + // can be kept static when scrolling.
177 + d.maxLine = null;
178 + d.maxLineLength = 0;
179 + d.maxLineChanged = false;
180 +
181 + // Used for measuring wheel scrolling granularity
182 + d.wheelDX = d.wheelDY = d.wheelStartX = d.wheelStartY = null;
183 +
184 + return d;
185 + }
186 +
187 + // STATE UPDATES
188 +
189 + // Used to get the editor into a consistent state again when options change.
190 +
191 + function loadMode(cm) {
192 + cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption);
193 + cm.doc.iter(function(line) {
194 + if (line.stateAfter) line.stateAfter = null;
195 + if (line.styles) line.styles = null;
196 + });
197 + cm.doc.frontier = cm.doc.first;
198 + startWorker(cm, 100);
199 + cm.state.modeGen++;
200 + if (cm.curOp) regChange(cm);
201 + }
202 +
203 + function wrappingChanged(cm) {
204 + if (cm.options.lineWrapping) {
205 + cm.display.wrapper.className += " CodeMirror-wrap";
206 + cm.display.sizer.style.minWidth = "";
207 + } else {
208 + cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-wrap", "");
209 + computeMaxLength(cm);
210 + }
211 + estimateLineHeights(cm);
212 + regChange(cm);
213 + clearCaches(cm);
214 + setTimeout(function(){updateScrollbars(cm.display, cm.doc.height);}, 100);
215 + }
216 +
217 + function estimateHeight(cm) {
218 + var th = textHeight(cm.display), wrapping = cm.options.lineWrapping;
219 + var perLine = wrapping && Math.max(5, cm.display.scroller.clientWidth / charWidth(cm.display) - 3);
220 + return function(line) {
221 + if (lineIsHidden(cm.doc, line))
222 + return 0;
223 + else if (wrapping)
224 + return (Math.ceil(line.text.length / perLine) || 1) * th;
225 + else
226 + return th;
227 + };
228 + }
229 +
230 + function estimateLineHeights(cm) {
231 + var doc = cm.doc, est = estimateHeight(cm);
232 + doc.iter(function(line) {
233 + var estHeight = est(line);
234 + if (estHeight != line.height) updateLineHeight(line, estHeight);
235 + });
236 + }
237 +
238 + function keyMapChanged(cm) {
239 + var style = keyMap[cm.options.keyMap].style;
240 + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
241 + (style ? " cm-keymap-" + style : "");
242 + }
243 +
244 + function themeChanged(cm) {
245 + cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-s-\S+/g, "") +
246 + cm.options.theme.replace(/(^|\s)\s*/g, " cm-s-");
247 + clearCaches(cm);
248 + }
249 +
250 + function guttersChanged(cm) {
251 + updateGutters(cm);
252 + regChange(cm);
253 + }
254 +
255 + function updateGutters(cm) {
256 + var gutters = cm.display.gutters, specs = cm.options.gutters;
257 + removeChildren(gutters);
258 + for (var i = 0; i < specs.length; ++i) {
259 + var gutterClass = specs[i];
260 + var gElt = gutters.appendChild(elt("div", null, "CodeMirror-gutter " + gutterClass));
261 + if (gutterClass == "CodeMirror-linenumbers") {
262 + cm.display.lineGutter = gElt;
263 + gElt.style.width = (cm.display.lineNumWidth || 1) + "px";
264 + }
265 + }
266 + gutters.style.display = i ? "" : "none";
267 + }
268 +
269 + function lineLength(doc, line) {
270 + if (line.height == 0) return 0;
271 + var len = line.text.length, merged, cur = line;
272 + while (merged = collapsedSpanAtStart(cur)) {
273 + var found = merged.find();
274 + cur = getLine(doc, found.from.line);
275 + len += found.from.ch - found.to.ch;
276 + }
277 + cur = line;
278 + while (merged = collapsedSpanAtEnd(cur)) {
279 + var found = merged.find();
280 + len -= cur.text.length - found.from.ch;
281 + cur = getLine(doc, found.to.line);
282 + len += cur.text.length - found.to.ch;
283 + }
284 + return len;
285 + }
286 +
287 + function computeMaxLength(cm) {
288 + var d = cm.display, doc = cm.doc;
289 + d.maxLine = getLine(doc, doc.first);
290 + d.maxLineLength = lineLength(doc, d.maxLine);
291 + d.maxLineChanged = true;
292 + doc.iter(function(line) {
293 + var len = lineLength(doc, line);
294 + if (len > d.maxLineLength) {
295 + d.maxLineLength = len;
296 + d.maxLine = line;
297 + }
298 + });
299 + }
300 +
301 + // Make sure the gutters options contains the element
302 + // "CodeMirror-linenumbers" when the lineNumbers option is true.
303 + function setGuttersForLineNumbers(options) {
304 + var found = false;
305 + for (var i = 0; i < options.gutters.length; ++i) {
306 + if (options.gutters[i] == "CodeMirror-linenumbers") {
307 + if (options.lineNumbers) found = true;
308 + else options.gutters.splice(i--, 1);
309 + }
310 + }
311 + if (!found && options.lineNumbers)
312 + options.gutters.push("CodeMirror-linenumbers");
313 + }
314 +
315 + // SCROLLBARS
316 +
317 + // Re-synchronize the fake scrollbars with the actual size of the
318 + // content. Optionally force a scrollTop.
319 + function updateScrollbars(d /* display */, docHeight) {
320 + var totalHeight = docHeight + 2 * paddingTop(d);
321 + d.sizer.style.minHeight = d.heightForcer.style.top = totalHeight + "px";
322 + var scrollHeight = Math.max(totalHeight, d.scroller.scrollHeight);
323 + var needsH = d.scroller.scrollWidth > d.scroller.clientWidth;
324 + var needsV = scrollHeight > d.scroller.clientHeight;
325 + if (needsV) {
326 + d.scrollbarV.style.display = "block";
327 + d.scrollbarV.style.bottom = needsH ? scrollbarWidth(d.measure) + "px" : "0";
328 + d.scrollbarV.firstChild.style.height =
329 + (scrollHeight - d.scroller.clientHeight + d.scrollbarV.clientHeight) + "px";
330 + } else d.scrollbarV.style.display = "";
331 + if (needsH) {
332 + d.scrollbarH.style.display = "block";
333 + d.scrollbarH.style.right = needsV ? scrollbarWidth(d.measure) + "px" : "0";
334 + d.scrollbarH.firstChild.style.width =
335 + (d.scroller.scrollWidth - d.scroller.clientWidth + d.scrollbarH.clientWidth) + "px";
336 + } else d.scrollbarH.style.display = "";
337 + if (needsH && needsV) {
338 + d.scrollbarFiller.style.display = "block";
339 + d.scrollbarFiller.style.height = d.scrollbarFiller.style.width = scrollbarWidth(d.measure) + "px";
340 + } else d.scrollbarFiller.style.display = "";
341 +
342 + if (mac_geLion && scrollbarWidth(d.measure) === 0)
343 + d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px";
344 + }
345 +
346 + function visibleLines(display, doc, viewPort) {
347 + var top = display.scroller.scrollTop, height = display.wrapper.clientHeight;
348 + if (typeof viewPort == "number") top = viewPort;
349 + else if (viewPort) {top = viewPort.top; height = viewPort.bottom - viewPort.top;}
350 + top = Math.floor(top - paddingTop(display));
351 + var bottom = Math.ceil(top + height);
352 + return {from: lineAtHeight(doc, top), to: lineAtHeight(doc, bottom)};
353 + }
354 +
355 + // LINE NUMBERS
356 +
357 + function alignHorizontally(cm) {
358 + var display = cm.display;
359 + if (!display.alignWidgets && (!display.gutters.firstChild || !cm.options.fixedGutter)) return;
360 + var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
361 + var gutterW = display.gutters.offsetWidth, l = comp + "px";
362 + for (var n = display.lineDiv.firstChild; n; n = n.nextSibling) if (n.alignable) {
363 + for (var i = 0, a = n.alignable; i < a.length; ++i) a[i].style.left = l;
364 + }
365 + if (cm.options.fixedGutter)
366 + display.gutters.style.left = (comp + gutterW) + "px";
367 + }
368 +
369 + function maybeUpdateLineNumberWidth(cm) {
370 + if (!cm.options.lineNumbers) return false;
371 + var doc = cm.doc, last = lineNumberFor(cm.options, doc.first + doc.size - 1), display = cm.display;
372 + if (last.length != display.lineNumChars) {
373 + var test = display.measure.appendChild(elt("div", [elt("div", last)],
374 + "CodeMirror-linenumber CodeMirror-gutter-elt"));
375 + var innerW = test.firstChild.offsetWidth, padding = test.offsetWidth - innerW;
376 + display.lineGutter.style.width = "";
377 + display.lineNumInnerWidth = Math.max(innerW, display.lineGutter.offsetWidth - padding);
378 + display.lineNumWidth = display.lineNumInnerWidth + padding;
379 + display.lineNumChars = display.lineNumInnerWidth ? last.length : -1;
380 + display.lineGutter.style.width = display.lineNumWidth + "px";
381 + return true;
382 + }
383 + return false;
384 + }
385 +
386 + function lineNumberFor(options, i) {
387 + return String(options.lineNumberFormatter(i + options.firstLineNumber));
388 + }
389 + function compensateForHScroll(display) {
390 + return display.scroller.getBoundingClientRect().left - display.sizer.getBoundingClientRect().left;
391 + }
392 +
393 + // DISPLAY DRAWING
394 +
395 + function updateDisplay(cm, changes, viewPort) {
396 + var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo;
397 + var updated = updateDisplayInner(cm, changes, viewPort);
398 + if (updated) {
399 + signalLater(cm, "update", cm);
400 + if (cm.display.showingFrom != oldFrom || cm.display.showingTo != oldTo)
401 + signalLater(cm, "viewportChange", cm, cm.display.showingFrom, cm.display.showingTo);
402 + }
403 + updateSelection(cm);
404 + updateScrollbars(cm.display, cm.doc.height);
405 +
406 + return updated;
407 + }
408 +
409 + // Uses a set of changes plus the current scroll position to
410 + // determine which DOM updates have to be made, and makes the
411 + // updates.
412 + function updateDisplayInner(cm, changes, viewPort) {
413 + var display = cm.display, doc = cm.doc;
414 + if (!display.wrapper.clientWidth) {
415 + display.showingFrom = display.showingTo = doc.first;
416 + display.viewOffset = 0;
417 + return;
418 + }
419 +
420 + // Compute the new visible window
421 + // If scrollTop is specified, use that to determine which lines
422 + // to render instead of the current scrollbar position.
423 + var visible = visibleLines(display, doc, viewPort);
424 + // Bail out if the visible area is already rendered and nothing changed.
425 + if (changes.length == 0 &&
426 + visible.from > display.showingFrom && visible.to < display.showingTo)
427 + return;
428 +
429 + if (maybeUpdateLineNumberWidth(cm))
430 + changes = [{from: doc.first, to: doc.first + doc.size}];
431 + var gutterW = display.sizer.style.marginLeft = display.gutters.offsetWidth + "px";
432 + display.scrollbarH.style.left = cm.options.fixedGutter ? gutterW : "0";
433 +
434 + // When merged lines are present, the line that needs to be
435 + // redrawn might not be the one that was changed.
436 + if (sawCollapsedSpans)
437 + for (var i = 0; i < changes.length; ++i) {
438 + var ch = changes[i], merged;
439 + while (merged = collapsedSpanAtStart(getLine(doc, ch.from))) {
440 + var from = merged.find().from.line;
441 + if (ch.diff) ch.diff -= ch.from - from;
442 + ch.from = from;
443 + }
444 + }
445 +
446 + // Used to determine which lines need their line numbers updated
447 + var positionsChangedFrom = Infinity;
448 + if (cm.options.lineNumbers)
449 + for (var i = 0; i < changes.length; ++i)
450 + if (changes[i].diff) { positionsChangedFrom = changes[i].from; break; }
451 +
452 + var end = doc.first + doc.size;
453 + var from = Math.max(visible.from - cm.options.viewportMargin, doc.first);
454 + var to = Math.min(end, visible.to + cm.options.viewportMargin);
455 + if (display.showingFrom < from && from - display.showingFrom < 20) from = Math.max(doc.first, display.showingFrom);
456 + if (display.showingTo > to && display.showingTo - to < 20) to = Math.min(end, display.showingTo);
457 + if (sawCollapsedSpans) {
458 + from = lineNo(visualLine(doc, getLine(doc, from)));
459 + while (to < end && lineIsHidden(doc, getLine(doc, to))) ++to;
460 + }
461 +
462 + // Create a range of theoretically intact lines, and punch holes
463 + // in that using the change info.
464 + var intact = computeIntact([{from: display.showingFrom, to: display.showingTo}], changes);
465 +
466 + // Clip off the parts that won't be visible
467 + var intactLines = 0;
468 + for (var i = 0; i < intact.length; ++i) {
469 + var range = intact[i];
470 + if (range.from < from) range.from = from;
471 + if (range.to > to) range.to = to;
472 + if (range.from >= range.to) intact.splice(i--, 1);
473 + else intactLines += range.to - range.from;
474 + }
475 + if (intactLines == to - from && from == display.showingFrom && to == display.showingTo) {
476 + updateViewOffset(cm);
477 + return;
478 + }
479 + intact.sort(function(a, b) {return a.from - b.from;});
480 +
481 + var focused = document.activeElement;
482 + if (intactLines < (to - from) * .7) display.lineDiv.style.display = "none";
483 + patchDisplay(cm, from, to, intact, positionsChangedFrom);
484 + display.lineDiv.style.display = "";
485 + if (document.activeElement != focused && focused.offsetHeight) focused.focus();
486 +
487 + var different = from != display.showingFrom || to != display.showingTo ||
488 + display.lastSizeC != display.wrapper.clientHeight;
489 + // This is just a bogus formula that detects when the editor is
490 + // resized or the font size changes.
491 + if (different) display.lastSizeC = display.wrapper.clientHeight;
492 + display.showingFrom = from; display.showingTo = to;
493 + startWorker(cm, 100);
494 +
495 + var prevBottom = display.lineDiv.offsetTop;
496 + for (var node = display.lineDiv.firstChild, height; node; node = node.nextSibling) if (node.lineObj) {
497 + if (ie_lt8) {
498 + var bot = node.offsetTop + node.offsetHeight;
499 + height = bot - prevBottom;
500 + prevBottom = bot;
501 + } else {
502 + var box = node.getBoundingClientRect();
503 + height = box.bottom - box.top;
504 + }
505 + var diff = node.lineObj.height - height;
506 + if (height < 2) height = textHeight(display);
507 + if (diff > .001 || diff < -.001) {
508 + updateLineHeight(node.lineObj, height);
509 + var widgets = node.lineObj.widgets;
510 + if (widgets) for (var i = 0; i < widgets.length; ++i)
511 + widgets[i].height = widgets[i].node.offsetHeight;
512 + }
513 + }
514 + updateViewOffset(cm);
515 +
516 + if (visibleLines(display, doc, viewPort).to >= to)
517 + updateDisplayInner(cm, [], viewPort);
518 + return true;
519 + }
520 +
521 + function updateViewOffset(cm) {
522 + var off = cm.display.viewOffset = heightAtLine(cm, getLine(cm.doc, cm.display.showingFrom));
523 + // Position the mover div to align with the current virtual scroll position
524 + cm.display.mover.style.top = off + "px";
525 + }
526 +
527 + function computeIntact(intact, changes) {
528 + for (var i = 0, l = changes.length || 0; i < l; ++i) {
529 + var change = changes[i], intact2 = [], diff = change.diff || 0;
530 + for (var j = 0, l2 = intact.length; j < l2; ++j) {
531 + var range = intact[j];
532 + if (change.to <= range.from && change.diff) {
533 + intact2.push({from: range.from + diff, to: range.to + diff});
534 + } else if (change.to <= range.from || change.from >= range.to) {
535 + intact2.push(range);
536 + } else {
537 + if (change.from > range.from)
538 + intact2.push({from: range.from, to: change.from});
539 + if (change.to < range.to)
540 + intact2.push({from: change.to + diff, to: range.to + diff});
541 + }
542 + }
543 + intact = intact2;
544 + }
545 + return intact;
546 + }
547 +
548 + function getDimensions(cm) {
549 + var d = cm.display, left = {}, width = {};
550 + for (var n = d.gutters.firstChild, i = 0; n; n = n.nextSibling, ++i) {
551 + left[cm.options.gutters[i]] = n.offsetLeft;
552 + width[cm.options.gutters[i]] = n.offsetWidth;
553 + }
554 + return {fixedPos: compensateForHScroll(d),
555 + gutterTotalWidth: d.gutters.offsetWidth,
556 + gutterLeft: left,
557 + gutterWidth: width,
558 + wrapperWidth: d.wrapper.clientWidth};
559 + }
560 +
561 + function patchDisplay(cm, from, to, intact, updateNumbersFrom) {
562 + var dims = getDimensions(cm);
563 + var display = cm.display, lineNumbers = cm.options.lineNumbers;
564 + if (!intact.length && (!webkit || !cm.display.currentWheelTarget))
565 + removeChildren(display.lineDiv);
566 + var container = display.lineDiv, cur = container.firstChild;
567 +
568 + function rm(node) {
569 + var next = node.nextSibling;
570 + if (webkit && mac && cm.display.currentWheelTarget == node) {
571 + node.style.display = "none";
572 + node.lineObj = null;
573 + } else {
574 + node.parentNode.removeChild(node);
575 + }
576 + return next;
577 + }
578 +
579 + var nextIntact = intact.shift(), lineN = from;
580 + cm.doc.iter(from, to, function(line) {
581 + if (nextIntact && nextIntact.to == lineN) nextIntact = intact.shift();
582 + if (lineIsHidden(cm.doc, line)) {
583 + if (line.height != 0) updateLineHeight(line, 0);
584 + if (line.widgets && cur.previousSibling) for (var i = 0; i < line.widgets.length; ++i)
585 + if (line.widgets[i].showIfHidden) {
586 + var prev = cur.previousSibling;
587 + if (/pre/i.test(prev.nodeName)) {
588 + var wrap = elt("div", null, null, "position: relative");
589 + prev.parentNode.replaceChild(wrap, prev);
590 + wrap.appendChild(prev);
591 + prev = wrap;
592 + }
593 + var wnode = prev.appendChild(elt("div", [line.widgets[i].node], "CodeMirror-linewidget"));
594 + positionLineWidget(line.widgets[i], wnode, prev, dims);
595 + }
596 + } else if (nextIntact && nextIntact.from <= lineN && nextIntact.to > lineN) {
597 + // This line is intact. Skip to the actual node. Update its
598 + // line number if needed.
599 + while (cur.lineObj != line) cur = rm(cur);
600 + if (lineNumbers && updateNumbersFrom <= lineN && cur.lineNumber)
601 + setTextContent(cur.lineNumber, lineNumberFor(cm.options, lineN));
602 + cur = cur.nextSibling;
603 + } else {
604 + // For lines with widgets, make an attempt to find and reuse
605 + // the existing element, so that widgets aren't needlessly
606 + // removed and re-inserted into the dom
607 + if (line.widgets) for (var j = 0, search = cur, reuse; search && j < 20; ++j, search = search.nextSibling)
608 + if (search.lineObj == line && /div/i.test(search.nodeName)) { reuse = search; break; }
609 + // This line needs to be generated.
610 + var lineNode = buildLineElement(cm, line, lineN, dims, reuse);
611 + if (lineNode != reuse) {
612 + container.insertBefore(lineNode, cur);
613 + } else {
614 + while (cur != reuse) cur = rm(cur);
615 + cur = cur.nextSibling;
616 + }
617 +
618 + lineNode.lineObj = line;
619 + }
620 + ++lineN;
621 + });
622 + while (cur) cur = rm(cur);
623 + }
624 +
625 + function buildLineElement(cm, line, lineNo, dims, reuse) {
626 + var lineElement = lineContent(cm, line);
627 + var markers = line.gutterMarkers, display = cm.display, wrap;
628 +
629 + if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets)
630 + return lineElement;
631 +
632 + // Lines with gutter elements, widgets or a background class need
633 + // to be wrapped again, and have the extra elements added to the
634 + // wrapper div
635 +
636 + if (reuse) {
637 + reuse.alignable = null;
638 + var isOk = true, widgetsSeen = 0;
639 + for (var n = reuse.firstChild, next; n; n = next) {
640 + next = n.nextSibling;
641 + if (!/\bCodeMirror-linewidget\b/.test(n.className)) {
642 + reuse.removeChild(n);
643 + } else {
644 + for (var i = 0, first = true; i < line.widgets.length; ++i) {
645 + var widget = line.widgets[i], isFirst = false;
646 + if (!widget.above) { isFirst = first; first = false; }
647 + if (widget.node == n.firstChild) {
648 + positionLineWidget(widget, n, reuse, dims);
649 + ++widgetsSeen;
650 + if (isFirst) reuse.insertBefore(lineElement, n);
651 + break;
652 + }
653 + }
654 + if (i == line.widgets.length) { isOk = false; break; }
655 + }
656 + }
657 + if (isOk && widgetsSeen == line.widgets.length) {
658 + wrap = reuse;
659 + reuse.className = line.wrapClass || "";
660 + }
661 + }
662 + if (!wrap) {
663 + wrap = elt("div", null, line.wrapClass, "position: relative");
664 + wrap.appendChild(lineElement);
665 + }
666 + // Kludge to make sure the styled element lies behind the selection (by z-index)
667 + if (line.bgClass)
668 + wrap.insertBefore(elt("div", "\u00a0", line.bgClass + " CodeMirror-linebackground"), wrap.firstChild);
669 + if (cm.options.lineNumbers || markers) {
670 + var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " +
671 + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"),
672 + wrap.firstChild);
673 + if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap);
674 + if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"]))
675 + wrap.lineNumber = gutterWrap.appendChild(
676 + elt("div", lineNumberFor(cm.options, lineNo),
677 + "CodeMirror-linenumber CodeMirror-gutter-elt",
678 + "left: " + dims.gutterLeft["CodeMirror-linenumbers"] + "px; width: "
679 + + display.lineNumInnerWidth + "px"));
680 + if (markers)
681 + for (var k = 0; k < cm.options.gutters.length; ++k) {
682 + var id = cm.options.gutters[k], found = markers.hasOwnProperty(id) && markers[id];
683 + if (found)
684 + gutterWrap.appendChild(elt("div", [found], "CodeMirror-gutter-elt", "left: " +
685 + dims.gutterLeft[id] + "px; width: " + dims.gutterWidth[id] + "px"));
686 + }
687 + }
688 + if (ie_lt8) wrap.style.zIndex = 2;
689 + if (line.widgets && wrap != reuse) for (var i = 0, ws = line.widgets; i < ws.length; ++i) {
690 + var widget = ws[i], node = elt("div", [widget.node], "CodeMirror-linewidget");
691 + positionLineWidget(widget, node, wrap, dims);
692 + if (widget.above)
693 + wrap.insertBefore(node, cm.options.lineNumbers && line.height != 0 ? gutterWrap : lineElement);
694 + else
695 + wrap.appendChild(node);
696 + signalLater(widget, "redraw");
697 + }
698 + return wrap;
699 + }
700 +
701 + function positionLineWidget(widget, node, wrap, dims) {
702 + if (widget.noHScroll) {
703 + (wrap.alignable || (wrap.alignable = [])).push(node);
704 + var width = dims.wrapperWidth;
705 + node.style.left = dims.fixedPos + "px";
706 + if (!widget.coverGutter) {
707 + width -= dims.gutterTotalWidth;
708 + node.style.paddingLeft = dims.gutterTotalWidth + "px";
709 + }
710 + node.style.width = width + "px";
711 + }
712 + if (widget.coverGutter) {
713 + node.style.zIndex = 5;
714 + node.style.position = "relative";
715 + if (!widget.noHScroll) node.style.marginLeft = -dims.gutterTotalWidth + "px";
716 + }
717 + }
718 +
719 + // SELECTION / CURSOR
720 +
721 + function updateSelection(cm) {
722 + var display = cm.display;
723 + var collapsed = posEq(cm.doc.sel.from, cm.doc.sel.to);
724 + if (collapsed || cm.options.showCursorWhenSelecting)
725 + updateSelectionCursor(cm);
726 + else
727 + display.cursor.style.display = display.otherCursor.style.display = "none";
728 + if (!collapsed)
729 + updateSelectionRange(cm);
730 + else
731 + display.selectionDiv.style.display = "none";
732 +
733 + // Move the hidden textarea near the cursor to prevent scrolling artifacts
734 + var headPos = cursorCoords(cm, cm.doc.sel.head, "div");
735 + var wrapOff = display.wrapper.getBoundingClientRect(), lineOff = display.lineDiv.getBoundingClientRect();
736 + display.inputDiv.style.top = Math.max(0, Math.min(display.wrapper.clientHeight - 10,
737 + headPos.top + lineOff.top - wrapOff.top)) + "px";
738 + display.inputDiv.style.left = Math.max(0, Math.min(display.wrapper.clientWidth - 10,
739 + headPos.left + lineOff.left - wrapOff.left)) + "px";
740 + }
741 +
742 + // No selection, plain cursor
743 + function updateSelectionCursor(cm) {
744 + var display = cm.display, pos = cursorCoords(cm, cm.doc.sel.head, "div");
745 + display.cursor.style.left = pos.left + "px";
746 + display.cursor.style.top = pos.top + "px";
747 + display.cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
748 + display.cursor.style.display = "";
749 +
750 + if (pos.other) {
751 + display.otherCursor.style.display = "";
752 + display.otherCursor.style.left = pos.other.left + "px";
753 + display.otherCursor.style.top = pos.other.top + "px";
754 + display.otherCursor.style.height = (pos.other.bottom - pos.other.top) * .85 + "px";
755 + } else { display.otherCursor.style.display = "none"; }
756 + }
757 +
758 + // Highlight selection
759 + function updateSelectionRange(cm) {
760 + var display = cm.display, doc = cm.doc, sel = cm.doc.sel;
761 + var fragment = document.createDocumentFragment();
762 + var clientWidth = display.lineSpace.offsetWidth, pl = paddingLeft(cm.display);
763 +
764 + function add(left, top, width, bottom) {
765 + if (top < 0) top = 0;
766 + fragment.appendChild(elt("div", null, "CodeMirror-selected", "position: absolute; left: " + left +
767 + "px; top: " + top + "px; width: " + (width == null ? clientWidth - left : width) +
768 + "px; height: " + (bottom - top) + "px"));
769 + }
770 +
771 + function drawForLine(line, fromArg, toArg, retTop) {
772 + var lineObj = getLine(doc, line);
773 + var lineLen = lineObj.text.length, rVal = retTop ? Infinity : -Infinity;
774 + function coords(ch) {
775 + return charCoords(cm, Pos(line, ch), "div", lineObj);
776 + }
777 +
778 + iterateBidiSections(getOrder(lineObj), fromArg || 0, toArg == null ? lineLen : toArg, function(from, to, dir) {
779 + var leftPos = coords(dir == "rtl" ? to - 1 : from);
780 + var rightPos = coords(dir == "rtl" ? from : to - 1);
781 + var left = leftPos.left, right = rightPos.right;
782 + if (rightPos.top - leftPos.top > 3) { // Different lines, draw top part
783 + add(left, leftPos.top, null, leftPos.bottom);
784 + left = pl;
785 + if (leftPos.bottom < rightPos.top) add(left, leftPos.bottom, null, rightPos.top);
786 + }
787 + if (toArg == null && to == lineLen) right = clientWidth;
788 + if (fromArg == null && from == 0) left = pl;
789 + rVal = retTop ? Math.min(rightPos.top, rVal) : Math.max(rightPos.bottom, rVal);
790 + if (left < pl + 1) left = pl;
791 + add(left, rightPos.top, right - left, rightPos.bottom);
792 + });
793 + return rVal;
794 + }
795 +
796 + if (sel.from.line == sel.to.line) {
797 + drawForLine(sel.from.line, sel.from.ch, sel.to.ch);
798 + } else {
799 + var fromObj = getLine(doc, sel.from.line);
800 + var cur = fromObj, merged, path = [sel.from.line, sel.from.ch], singleLine;
801 + while (merged = collapsedSpanAtEnd(cur)) {
802 + var found = merged.find();
803 + path.push(found.from.ch, found.to.line, found.to.ch);
804 + if (found.to.line == sel.to.line) {
805 + path.push(sel.to.ch);
806 + singleLine = true;
807 + break;
808 + }
809 + cur = getLine(doc, found.to.line);
810 + }
811 +
812 + // This is a single, merged line
813 + if (singleLine) {
814 + for (var i = 0; i < path.length; i += 3)
815 + drawForLine(path[i], path[i+1], path[i+2]);
816 + } else {
817 + var middleTop, middleBot, toObj = getLine(doc, sel.to.line);
818 + if (sel.from.ch)
819 + // Draw the first line of selection.
820 + middleTop = drawForLine(sel.from.line, sel.from.ch, null, false);
821 + else
822 + // Simply include it in the middle block.
823 + middleTop = heightAtLine(cm, fromObj) - display.viewOffset;
824 +
825 + if (!sel.to.ch)
826 + middleBot = heightAtLine(cm, toObj) - display.viewOffset;
827 + else
828 + middleBot = drawForLine(sel.to.line, collapsedSpanAtStart(toObj) ? null : 0, sel.to.ch, true);
829 +
830 + if (middleTop < middleBot) add(pl, middleTop, null, middleBot);
831 + }
832 + }
833 +
834 + removeChildrenAndAdd(display.selectionDiv, fragment);
835 + display.selectionDiv.style.display = "";
836 + }
837 +
838 + // Cursor-blinking
839 + function restartBlink(cm) {
840 + var display = cm.display;
841 + clearInterval(display.blinker);
842 + var on = true;
843 + display.cursor.style.visibility = display.otherCursor.style.visibility = "";
844 + display.blinker = setInterval(function() {
845 + if (!display.cursor.offsetHeight) return;
846 + display.cursor.style.visibility = display.otherCursor.style.visibility = (on = !on) ? "" : "hidden";
847 + }, cm.options.cursorBlinkRate);
848 + }
849 +
850 + // HIGHLIGHT WORKER
851 +
852 + function startWorker(cm, time) {
853 + if (cm.doc.mode.startState && cm.doc.frontier < cm.display.showingTo)
854 + cm.state.highlight.set(time, bind(highlightWorker, cm));
855 + }
856 +
857 + function highlightWorker(cm) {
858 + var doc = cm.doc;
859 + if (doc.frontier < doc.first) doc.frontier = doc.first;
860 + if (doc.frontier >= cm.display.showingTo) return;
861 + var end = +new Date + cm.options.workTime;
862 + var state = copyState(doc.mode, getStateBefore(cm, doc.frontier));
863 + var changed = [], prevChange;
864 + doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) {
865 + if (doc.frontier >= cm.display.showingFrom) { // Visible
866 + var oldStyles = line.styles;
867 + line.styles = highlightLine(cm, line, state);
868 + var ischange = !oldStyles || oldStyles.length != line.styles.length;
869 + for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i];
870 + if (ischange) {
871 + if (prevChange && prevChange.end == doc.frontier) prevChange.end++;
872 + else changed.push(prevChange = {start: doc.frontier, end: doc.frontier + 1});
873 + }
874 + line.stateAfter = copyState(doc.mode, state);
875 + } else {
876 + processLine(cm, line, state);
877 + line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null;
878 + }
879 + ++doc.frontier;
880 + if (+new Date > end) {
881 + startWorker(cm, cm.options.workDelay);
882 + return true;
883 + }
884 + });
885 + if (changed.length)
886 + operation(cm, function() {
887 + for (var i = 0; i < changed.length; ++i)
888 + regChange(this, changed[i].start, changed[i].end);
889 + })();
890 + }
891 +
892 + // Finds the line to start with when starting a parse. Tries to
893 + // find a line with a stateAfter, so that it can start with a
894 + // valid state. If that fails, it returns the line with the
895 + // smallest indentation, which tends to need the least context to
896 + // parse correctly.
897 + function findStartLine(cm, n) {
898 + var minindent, minline, doc = cm.doc;
899 + for (var search = n, lim = n - 100; search > lim; --search) {
900 + if (search <= doc.first) return doc.first;
901 + var line = getLine(doc, search - 1);
902 + if (line.stateAfter) return search;
903 + var indented = countColumn(line.text, null, cm.options.tabSize);
904 + if (minline == null || minindent > indented) {
905 + minline = search - 1;
906 + minindent = indented;
907 + }
908 + }
909 + return minline;
910 + }
911 +
912 + function getStateBefore(cm, n) {
913 + var doc = cm.doc, display = cm.display;
914 + if (!doc.mode.startState) return true;
915 + var pos = findStartLine(cm, n), state = pos > doc.first && getLine(doc, pos-1).stateAfter;
916 + if (!state) state = startState(doc.mode);
917 + else state = copyState(doc.mode, state);
918 + doc.iter(pos, n, function(line) {
919 + processLine(cm, line, state);
920 + var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo;
921 + line.stateAfter = save ? copyState(doc.mode, state) : null;
922 + ++pos;
923 + });
924 + return state;
925 + }
926 +
927 + // POSITION MEASUREMENT
928 +
929 + function paddingTop(display) {return display.lineSpace.offsetTop;}
930 + function paddingLeft(display) {
931 + var e = removeChildrenAndAdd(display.measure, elt("pre")).appendChild(elt("span", "x"));
932 + return e.offsetLeft;
933 + }
934 +
935 + function measureChar(cm, line, ch, data) {
936 + var dir = -1;
937 + data = data || measureLine(cm, line);
938 +
939 + for (var pos = ch;; pos += dir) {
940 + var r = data[pos];
941 + if (r) break;
942 + if (dir < 0 && pos == 0) dir = 1;
943 + }
944 + return {left: pos < ch ? r.right : r.left,
945 + right: pos > ch ? r.left : r.right,
946 + top: r.top, bottom: r.bottom};
947 + }
948 +
949 + function measureLine(cm, line) {
950 + // First look in the cache
951 + var display = cm.display, cache = cm.display.measureLineCache;
952 + for (var i = 0; i < cache.length; ++i) {
953 + var memo = cache[i];
954 + if (memo.text == line.text && memo.markedSpans == line.markedSpans &&
955 + display.scroller.clientWidth == memo.width &&
956 + memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass)
957 + return memo.measure;
958 + }
959 +
960 + var measure = measureLineInner(cm, line);
961 + // Store result in the cache
962 + var memo = {text: line.text, width: display.scroller.clientWidth,
963 + markedSpans: line.markedSpans, measure: measure,
964 + classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass};
965 + if (cache.length == 16) cache[++display.measureLineCachePos % 16] = memo;
966 + else cache.push(memo);
967 + return measure;
968 + }
969 +
970 + function measureLineInner(cm, line) {
971 + var display = cm.display, measure = emptyArray(line.text.length);
972 + var pre = lineContent(cm, line, measure);
973 +
974 + // IE does not cache element positions of inline elements between
975 + // calls to getBoundingClientRect. This makes the loop below,
976 + // which gathers the positions of all the characters on the line,
977 + // do an amount of layout work quadratic to the number of
978 + // characters. When line wrapping is off, we try to improve things
979 + // by first subdividing the line into a bunch of inline blocks, so
980 + // that IE can reuse most of the layout information from caches
981 + // for those blocks. This does interfere with line wrapping, so it
982 + // doesn't work when wrapping is on, but in that case the
983 + // situation is slightly better, since IE does cache line-wrapping
984 + // information and only recomputes per-line.
985 + if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) {
986 + var fragment = document.createDocumentFragment();
987 + var chunk = 10, n = pre.childNodes.length;
988 + for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) {
989 + var wrap = elt("div", null, null, "display: inline-block");
990 + for (var j = 0; j < chunk && n; ++j) {
991 + wrap.appendChild(pre.firstChild);
992 + --n;
993 + }
994 + fragment.appendChild(wrap);
995 + }
996 + pre.appendChild(fragment);
997 + }
998 +
999 + removeChildrenAndAdd(display.measure, pre);
1000 +
1001 + var outer = display.lineDiv.getBoundingClientRect();
1002 + var vranges = [], data = emptyArray(line.text.length), maxBot = pre.offsetHeight;
1003 + // Work around an IE7/8 bug where it will sometimes have randomly
1004 + // replaced our pre with a clone at this point.
1005 + if (ie_lt9 && display.measure.first != pre)
1006 + removeChildrenAndAdd(display.measure, pre);
1007 +
1008 + for (var i = 0, cur; i < measure.length; ++i) if (cur = measure[i]) {
1009 + var size = cur.getBoundingClientRect();
1010 + var top = Math.max(0, size.top - outer.top), bot = Math.min(size.bottom - outer.top, maxBot);
1011 + for (var j = 0; j < vranges.length; j += 2) {
1012 + var rtop = vranges[j], rbot = vranges[j+1];
1013 + if (rtop > bot || rbot < top) continue;
1014 + if (rtop <= top && rbot >= bot ||
1015 + top <= rtop && bot >= rbot ||
1016 + Math.min(bot, rbot) - Math.max(top, rtop) >= (bot - top) >> 1) {
1017 + vranges[j] = Math.min(top, rtop);
1018 + vranges[j+1] = Math.max(bot, rbot);
1019 + break;
1020 + }
1021 + }
1022 + if (j == vranges.length) vranges.push(top, bot);
1023 + data[i] = {left: size.left - outer.left, right: size.right - outer.left, top: j};
1024 + }
1025 + for (var i = 0, cur; i < data.length; ++i) if (cur = data[i]) {
1026 + var vr = cur.top;
1027 + cur.top = vranges[vr]; cur.bottom = vranges[vr+1];
1028 + }
1029 + if (!cm.options.lineWrapping) {
1030 + var last = pre.lastChild;
1031 + if (last.nodeType == 3) last = pre.appendChild(elt("span", "\u200b"));
1032 + data.width = last.getBoundingClientRect().right - outer.left;
1033 + }
1034 +
1035 + return data;
1036 + }
1037 +
1038 + function clearCaches(cm) {
1039 + cm.display.measureLineCache.length = cm.display.measureLineCachePos = 0;
1040 + cm.display.cachedCharWidth = cm.display.cachedTextHeight = null;
1041 + cm.display.maxLineChanged = true;
1042 + cm.display.lineNumChars = null;
1043 + }
1044 +
1045 + // Context is one of "line", "div" (display.lineDiv), "local"/null (editor), or "page"
1046 + function intoCoordSystem(cm, lineObj, rect, context) {
1047 + if (lineObj.widgets) for (var i = 0; i < lineObj.widgets.length; ++i) if (lineObj.widgets[i].above) {
1048 + var size = widgetHeight(lineObj.widgets[i]);
1049 + rect.top += size; rect.bottom += size;
1050 + }
1051 + if (context == "line") return rect;
1052 + if (!context) context = "local";
1053 + var yOff = heightAtLine(cm, lineObj);
1054 + if (context != "local") yOff -= cm.display.viewOffset;
1055 + if (context == "page") {
1056 + var lOff = cm.display.lineSpace.getBoundingClientRect();
1057 + yOff += lOff.top + (window.pageYOffset || (document.documentElement || document.body).scrollTop);
1058 + var xOff = lOff.left + (window.pageXOffset || (document.documentElement || document.body).scrollLeft);
1059 + rect.left += xOff; rect.right += xOff;
1060 + }
1061 + rect.top += yOff; rect.bottom += yOff;
1062 + return rect;
1063 + }
1064 +
1065 + function charCoords(cm, pos, context, lineObj) {
1066 + if (!lineObj) lineObj = getLine(cm.doc, pos.line);
1067 + return intoCoordSystem(cm, lineObj, measureChar(cm, lineObj, pos.ch), context);
1068 + }
1069 +
1070 + function cursorCoords(cm, pos, context, lineObj, measurement) {
1071 + lineObj = lineObj || getLine(cm.doc, pos.line);
1072 + if (!measurement) measurement = measureLine(cm, lineObj);
1073 + function get(ch, right) {
1074 + var m = measureChar(cm, lineObj, ch, measurement);
1075 + if (right) m.left = m.right; else m.right = m.left;
1076 + return intoCoordSystem(cm, lineObj, m, context);
1077 + }
1078 + var order = getOrder(lineObj), ch = pos.ch;
1079 + if (!order) return get(ch);
1080 + var main, other, linedir = order[0].level;
1081 + for (var i = 0; i < order.length; ++i) {
1082 + var part = order[i], rtl = part.level % 2, nb, here;
1083 + if (part.from < ch && part.to > ch) return get(ch, rtl);
1084 + var left = rtl ? part.to : part.from, right = rtl ? part.from : part.to;
1085 + if (left == ch) {
1086 + // Opera and IE return bogus offsets and widths for edges
1087 + // where the direction flips, but only for the side with the
1088 + // lower level. So we try to use the side with the higher
1089 + // level.
1090 + if (i && part.level < (nb = order[i-1]).level) here = get(nb.level % 2 ? nb.from : nb.to - 1, true);
1091 + else here = get(rtl && part.from != part.to ? ch - 1 : ch);
1092 + if (rtl == linedir) main = here; else other = here;
1093 + } else if (right == ch) {
1094 + var nb = i < order.length - 1 && order[i+1];
1095 + if (!rtl && nb && nb.from == nb.to) continue;
1096 + if (nb && part.level < nb.level) here = get(nb.level % 2 ? nb.to - 1 : nb.from);
1097 + else here = get(rtl ? ch : ch - 1, true);
1098 + if (rtl == linedir) main = here; else other = here;
1099 + }
1100 + }
1101 + if (linedir && !ch) other = get(order[0].to - 1);
1102 + if (!main) return other;
1103 + if (other) main.other = other;
1104 + return main;
1105 + }
1106 +
1107 + // Coords must be lineSpace-local
1108 + function coordsChar(cm, x, y) {
1109 + var doc = cm.doc;
1110 + y += cm.display.viewOffset;
1111 + if (y < 0) return {line: doc.first, ch: 0, outside: true};
1112 + var lineNo = lineAtHeight(doc, y), last = doc.first + doc.size - 1;
1113 + if (lineNo > last)
1114 + return Pos(doc.size - 1, getLine(doc, last).text.length);
1115 + if (x < 0) x = 0;
1116 +
1117 + for (;;) {
1118 + var lineObj = getLine(doc, lineNo);
1119 + var found = coordsCharInner(cm, lineObj, lineNo, x, y);
1120 + var merged = collapsedSpanAtEnd(lineObj);
1121 + var mergedPos = merged && merged.find();
1122 + if (merged && found.ch >= mergedPos.from.ch)
1123 + lineNo = mergedPos.to.line;
1124 + else
1125 + return found;
1126 + }
1127 + }
1128 +
1129 + function coordsCharInner(cm, lineObj, lineNo, x, y) {
1130 + var innerOff = y - heightAtLine(cm, lineObj);
1131 + var wrongLine = false, cWidth = cm.display.wrapper.clientWidth;
1132 + var measurement = measureLine(cm, lineObj);
1133 +
1134 + function getX(ch) {
1135 + var sp = cursorCoords(cm, Pos(lineNo, ch), "line",
1136 + lineObj, measurement);
1137 + wrongLine = true;
1138 + if (innerOff > sp.bottom) return Math.max(0, sp.left - cWidth);
1139 + else if (innerOff < sp.top) return sp.left + cWidth;
1140 + else wrongLine = false;
1141 + return sp.left;
1142 + }
1143 +
1144 + var bidi = getOrder(lineObj), dist = lineObj.text.length;
1145 + var from = lineLeft(lineObj), to = lineRight(lineObj);
1146 + var fromX = getX(from), toX = getX(to);
1147 +
1148 + if (x > toX) return {line: lineNo, ch: to, outside: wrongLine};
1149 + // Do a binary search between these bounds.
1150 + for (;;) {
1151 + if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
1152 + var after = x - fromX < toX - x, ch = after ? from : to;
1153 + while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch;
1154 + return {line: lineNo, ch: ch, after: after, outside: wrongLine};
1155 + }
1156 + var step = Math.ceil(dist / 2), middle = from + step;
1157 + if (bidi) {
1158 + middle = from;
1159 + for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
1160 + }
1161 + var middleX = getX(middle);
1162 + if (middleX > x) {to = middle; toX = middleX; if (wrongLine) toX += 1000; dist -= step;}
1163 + else {from = middle; fromX = middleX; dist = step;}
1164 + }
1165 + }
1166 +
1167 + var measureText;
1168 + function textHeight(display) {
1169 + if (display.cachedTextHeight != null) return display.cachedTextHeight;
1170 + if (measureText == null) {
1171 + measureText = elt("pre");
1172 + // Measure a bunch of lines, for browsers that compute
1173 + // fractional heights.
1174 + for (var i = 0; i < 49; ++i) {
1175 + measureText.appendChild(document.createTextNode("x"));
1176 + measureText.appendChild(elt("br"));
1177 + }
1178 + measureText.appendChild(document.createTextNode("x"));
1179 + }
1180 + removeChildrenAndAdd(display.measure, measureText);
1181 + var height = measureText.offsetHeight / 50;
1182 + if (height > 3) display.cachedTextHeight = height;
1183 + removeChildren(display.measure);
1184 + return height || 1;
1185 + }
1186 +
1187 + function charWidth(display) {
1188 + if (display.cachedCharWidth != null) return display.cachedCharWidth;
1189 + var anchor = elt("span", "x");
1190 + var pre = elt("pre", [anchor]);
1191 + removeChildrenAndAdd(display.measure, pre);
1192 + var width = anchor.offsetWidth;
1193 + if (width > 2) display.cachedCharWidth = width;
1194 + return width || 10;
1195 + }
1196 +
1197 + // OPERATIONS
1198 +
1199 + // Operations are used to wrap changes in such a way that each
1200 + // change won't have to update the cursor and display (which would
1201 + // be awkward, slow, and error-prone), but instead updates are
1202 + // batched and then all combined and executed at once.
1203 +
1204 + function startOperation(cm) {
1205 + cm.curOp = {
1206 + // An array of ranges of lines that have to be updated. See
1207 + // updateDisplay.
1208 + changes: [],
1209 + updateInput: null,
1210 + userSelChange: null,
1211 + textChanged: null,
1212 + selectionChanged: false,
1213 + updateMaxLine: false,
1214 + updateScrollPos: false,
1215 + id: ++cm.nextOpId
1216 + };
1217 + if (!delayedCallbackDepth++) delayedCallbacks = [];
1218 + }
1219 +
1220 + function endOperation(cm) {
1221 + var op = cm.curOp, doc = cm.doc, display = cm.display;
1222 + cm.curOp = null;
1223 +
1224 + if (op.updateMaxLine) computeMaxLength(cm);
1225 + if (display.maxLineChanged && !cm.options.lineWrapping) {
1226 + var width = measureLine(cm, display.maxLine).width;
1227 + display.sizer.style.minWidth = Math.max(0, width + 3 + scrollerCutOff) + "px";
1228 + display.maxLineChanged = false;
1229 + var maxScrollLeft = Math.max(0, display.sizer.offsetLeft + display.sizer.offsetWidth - display.scroller.clientWidth);
1230 + if (maxScrollLeft < doc.scrollLeft && !op.updateScrollPos)
1231 + setScrollLeft(cm, Math.min(display.scroller.scrollLeft, maxScrollLeft), true);
1232 + }
1233 + var newScrollPos, updated;
1234 + if (op.updateScrollPos) {
1235 + newScrollPos = op.updateScrollPos;
1236 + } else if (op.selectionChanged) {
1237 + var coords = cursorCoords(cm, doc.sel.head);
1238 + newScrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
1239 + }
1240 + if (op.changes.length || newScrollPos && newScrollPos.scrollTop != null)
1241 + updated = updateDisplay(cm, op.changes, newScrollPos && newScrollPos.scrollTop);
1242 + if (!updated && op.selectionChanged) updateSelection(cm);
1243 + if (op.updateScrollPos) {
1244 + display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop;
1245 + display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft;
1246 + alignHorizontally(cm);
1247 + } else if (newScrollPos) {
1248 + scrollCursorIntoView(cm);
1249 + }
1250 + if (op.selectionChanged) restartBlink(cm);
1251 +
1252 + if (cm.state.focused && op.updateInput)
1253 + resetInput(cm, op.userSelChange);
1254 +
1255 + var hidden = op.maybeHiddenMarkers, unhidden = op.maybeUnhiddenMarkers;
1256 + if (hidden) for (var i = 0; i < hidden.length; ++i)
1257 + if (!hidden[i].lines.length) signal(hidden[i], "hide");
1258 + if (unhidden) for (var i = 0; i < unhidden.length; ++i)
1259 + if (unhidden[i].lines.length) signal(unhidden[i], "unhide");
1260 +
1261 + var delayed;
1262 + if (!--delayedCallbackDepth) {
1263 + delayed = delayedCallbacks;
1264 + delayedCallbacks = null;
1265 + }
1266 + if (op.textChanged)
1267 + signal(cm, "change", cm, op.textChanged);
1268 + if (op.selectionChanged) signal(cm, "cursorActivity", cm);
1269 + if (delayed) for (var i = 0; i < delayed.length; ++i) delayed[i](cm);
1270 + }
1271 +
1272 + // Wraps a function in an operation. Returns the wrapped function.
1273 + function operation(cm1, f) {
1274 + return function() {
1275 + var cm = cm1 || this, withOp = !cm.curOp;
1276 + if (withOp) startOperation(cm);
1277 + try { var result = f.apply(cm, arguments); }
1278 + finally { if (withOp) endOperation(cm); }
1279 + return result;
1280 + };
1281 + }
1282 + function docOperation(f) {
1283 + return function() {
1284 + var withOp = this.cm && !this.cm.curOp, result;
1285 + if (withOp) startOperation(this.cm);
1286 + try { result = f.apply(this, arguments); }
1287 + finally { if (withOp) endOperation(this.cm); }
1288 + return result;
1289 + };
1290 + }
1291 + function runInOp(cm, f) {
1292 + var withOp = !cm.curOp, result;
1293 + if (withOp) startOperation(cm);
1294 + try { result = f(); }
1295 + finally { if (withOp) endOperation(cm); }
1296 + return result;
1297 + }
1298 +
1299 + function regChange(cm, from, to, lendiff) {
1300 + if (from == null) from = cm.doc.first;
1301 + if (to == null) to = cm.doc.first + cm.doc.size;
1302 + cm.curOp.changes.push({from: from, to: to, diff: lendiff});
1303 + }
1304 +
1305 + // INPUT HANDLING
1306 +
1307 + function slowPoll(cm) {
1308 + if (cm.display.pollingFast) return;
1309 + cm.display.poll.set(cm.options.pollInterval, function() {
1310 + readInput(cm);
1311 + if (cm.state.focused) slowPoll(cm);
1312 + });
1313 + }
1314 +
1315 + function fastPoll(cm) {
1316 + var missed = false;
1317 + cm.display.pollingFast = true;
1318 + function p() {
1319 + var changed = readInput(cm);
1320 + if (!changed && !missed) {missed = true; cm.display.poll.set(60, p);}
1321 + else {cm.display.pollingFast = false; slowPoll(cm);}
1322 + }
1323 + cm.display.poll.set(20, p);
1324 + }
1325 +
1326 + // prevInput is a hack to work with IME. If we reset the textarea
1327 + // on every change, that breaks IME. So we look for changes
1328 + // compared to the previous content instead. (Modern browsers have
1329 + // events that indicate IME taking place, but these are not widely
1330 + // supported or compatible enough yet to rely on.)
1331 + function readInput(cm) {
1332 + var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel;
1333 + if (!cm.state.focused || hasSelection(input) || isReadOnly(cm)) return false;
1334 + var text = input.value;
1335 + if (text == prevInput && posEq(sel.from, sel.to)) return false;
1336 + var withOp = !cm.curOp;
1337 + if (withOp) startOperation(cm);
1338 + sel.shift = false;
1339 + var same = 0, l = Math.min(prevInput.length, text.length);
1340 + while (same < l && prevInput[same] == text[same]) ++same;
1341 + var from = sel.from, to = sel.to;
1342 + if (same < prevInput.length)
1343 + from = Pos(from.line, from.ch - (prevInput.length - same));
1344 + else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming)
1345 + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same)));
1346 + var updateInput = cm.curOp.updateInput;
1347 + makeChange(cm.doc, {from: from, to: to, text: splitLines(text.slice(same)),
1348 + origin: cm.state.pasteIncoming ? "paste" : "+input"}, "end");
1349 +
1350 + cm.curOp.updateInput = updateInput;
1351 + if (text.length > 1000) input.value = cm.display.prevInput = "";
1352 + else cm.display.prevInput = text;
1353 + if (withOp) endOperation(cm);
1354 + cm.state.pasteIncoming = false;
1355 + return true;
1356 + }
1357 +
1358 + function resetInput(cm, user) {
1359 + var minimal, selected, doc = cm.doc;
1360 + if (!posEq(doc.sel.from, doc.sel.to)) {
1361 + cm.display.prevInput = "";
1362 + minimal = hasCopyEvent &&
1363 + (doc.sel.to.line - doc.sel.from.line > 100 || (selected = cm.getSelection()).length > 1000);
1364 + if (minimal) cm.display.input.value = "-";
1365 + else cm.display.input.value = selected || cm.getSelection();
1366 + if (cm.state.focused) selectInput(cm.display.input);
1367 + } else if (user) cm.display.prevInput = cm.display.input.value = "";
1368 + cm.display.inaccurateSelection = minimal;
1369 + }
1370 +
1371 + function focusInput(cm) {
1372 + if (cm.options.readOnly != "nocursor" && (!mobile || document.activeElement != cm.display.input))
1373 + cm.display.input.focus();
1374 + }
1375 +
1376 + function isReadOnly(cm) {
1377 + return cm.options.readOnly || cm.doc.cantEdit;
1378 + }
1379 +
1380 + // EVENT HANDLERS
1381 +
1382 + function registerEventHandlers(cm) {
1383 + var d = cm.display;
1384 + on(d.scroller, "mousedown", operation(cm, onMouseDown));
1385 + on(d.scroller, "dblclick", operation(cm, e_preventDefault));
1386 + on(d.lineSpace, "selectstart", function(e) {
1387 + if (!eventInWidget(d, e)) e_preventDefault(e);
1388 + });
1389 + // Gecko browsers fire contextmenu *after* opening the menu, at
1390 + // which point we can't mess with it anymore. Context menu is
1391 + // handled in onMouseDown for Gecko.
1392 + if (!captureMiddleClick) on(d.scroller, "contextmenu", function(e) {onContextMenu(cm, e);});
1393 +
1394 + on(d.scroller, "scroll", function() {
1395 + setScrollTop(cm, d.scroller.scrollTop);
1396 + setScrollLeft(cm, d.scroller.scrollLeft, true);
1397 + signal(cm, "scroll", cm);
1398 + });
1399 + on(d.scrollbarV, "scroll", function() {
1400 + setScrollTop(cm, d.scrollbarV.scrollTop);
1401 + });
1402 + on(d.scrollbarH, "scroll", function() {
1403 + setScrollLeft(cm, d.scrollbarH.scrollLeft);
1404 + });
1405 +
1406 + on(d.scroller, "mousewheel", function(e){onScrollWheel(cm, e);});
1407 + on(d.scroller, "DOMMouseScroll", function(e){onScrollWheel(cm, e);});
1408 +
1409 + function reFocus() { if (cm.state.focused) setTimeout(bind(focusInput, cm), 0); }
1410 + on(d.scrollbarH, "mousedown", reFocus);
1411 + on(d.scrollbarV, "mousedown", reFocus);
1412 + // Prevent wrapper from ever scrolling
1413 + on(d.wrapper, "scroll", function() { d.wrapper.scrollTop = d.wrapper.scrollLeft = 0; });
1414 +
1415 + function onResize() {
1416 + // Might be a text scaling operation, clear size caches.
1417 + d.cachedCharWidth = d.cachedTextHeight = null;
1418 + clearCaches(cm);
1419 + runInOp(cm, bind(regChange, cm));
1420 + }
1421 + on(window, "resize", onResize);
1422 + // Above handler holds on to the editor and its data structures.
1423 + // Here we poll to unregister it when the editor is no longer in
1424 + // the document, so that it can be garbage-collected.
1425 + function unregister() {
1426 + for (var p = d.wrapper.parentNode; p && p != document.body; p = p.parentNode) {}
1427 + if (p) setTimeout(unregister, 5000);
1428 + else off(window, "resize", onResize);
1429 + }
1430 + setTimeout(unregister, 5000);
1431 +
1432 + on(d.input, "keyup", operation(cm, function(e) {
1433 + if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1434 + if (e_prop(e, "keyCode") == 16) cm.doc.sel.shift = false;
1435 + }));
1436 + on(d.input, "input", bind(fastPoll, cm));
1437 + on(d.input, "keydown", operation(cm, onKeyDown));
1438 + on(d.input, "keypress", operation(cm, onKeyPress));
1439 + on(d.input, "focus", bind(onFocus, cm));
1440 + on(d.input, "blur", bind(onBlur, cm));
1441 +
1442 + function drag_(e) {
1443 + if (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))) return;
1444 + e_stop(e);
1445 + }
1446 + if (cm.options.dragDrop) {
1447 + on(d.scroller, "dragstart", function(e){onDragStart(cm, e);});
1448 + on(d.scroller, "dragenter", drag_);
1449 + on(d.scroller, "dragover", drag_);
1450 + on(d.scroller, "drop", operation(cm, onDrop));
1451 + }
1452 + on(d.scroller, "paste", function(e){
1453 + if (eventInWidget(d, e)) return;
1454 + focusInput(cm);
1455 + fastPoll(cm);
1456 + });
1457 + on(d.input, "paste", function() {
1458 + cm.state.pasteIncoming = true;
1459 + fastPoll(cm);
1460 + });
1461 +
1462 + function prepareCopy() {
1463 + if (d.inaccurateSelection) {
1464 + d.prevInput = "";
1465 + d.inaccurateSelection = false;
1466 + d.input.value = cm.getSelection();
1467 + selectInput(d.input);
1468 + }
1469 + }
1470 + on(d.input, "cut", prepareCopy);
1471 + on(d.input, "copy", prepareCopy);
1472 +
1473 + // Needed to handle Tab key in KHTML
1474 + if (khtml) on(d.sizer, "mouseup", function() {
1475 + if (document.activeElement == d.input) d.input.blur();
1476 + focusInput(cm);
1477 + });
1478 + }
1479 +
1480 + function eventInWidget(display, e) {
1481 + for (var n = e_target(e); n != display.wrapper; n = n.parentNode) {
1482 + if (!n) return true;
1483 + if (/\bCodeMirror-(?:line)?widget\b/.test(n.className) ||
1484 + n.parentNode == display.sizer && n != display.mover) return true;
1485 + }
1486 + }
1487 +
1488 + function posFromMouse(cm, e, liberal) {
1489 + var display = cm.display;
1490 + if (!liberal) {
1491 + var target = e_target(e);
1492 + if (target == display.scrollbarH || target == display.scrollbarH.firstChild ||
1493 + target == display.scrollbarV || target == display.scrollbarV.firstChild ||
1494 + target == display.scrollbarFiller) return null;
1495 + }
1496 + var x, y, space = display.lineSpace.getBoundingClientRect();
1497 + // Fails unpredictably on IE[67] when mouse is dragged around quickly.
1498 + try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
1499 + return coordsChar(cm, x - space.left, y - space.top);
1500 + }
1501 +
1502 + var lastClick, lastDoubleClick;
1503 + function onMouseDown(e) {
1504 + var cm = this, display = cm.display, doc = cm.doc, sel = doc.sel;
1505 + sel.shift = e_prop(e, "shiftKey");
1506 +
1507 + if (eventInWidget(display, e)) {
1508 + if (!webkit) {
1509 + display.scroller.draggable = false;
1510 + setTimeout(function(){display.scroller.draggable = true;}, 100);
1511 + }
1512 + return;
1513 + }
1514 + if (clickInGutter(cm, e)) return;
1515 + var start = posFromMouse(cm, e);
1516 +
1517 + switch (e_button(e)) {
1518 + case 3:
1519 + if (captureMiddleClick) onContextMenu.call(cm, cm, e);
1520 + return;
1521 + case 2:
1522 + if (start) extendSelection(cm.doc, start);
1523 + setTimeout(bind(focusInput, cm), 20);
1524 + e_preventDefault(e);
1525 + return;
1526 + }
1527 + // For button 1, if it was clicked inside the editor
1528 + // (posFromMouse returning non-null), we have to adjust the
1529 + // selection.
1530 + if (!start) {if (e_target(e) == display.scroller) e_preventDefault(e); return;}
1531 +
1532 + if (!cm.state.focused) onFocus(cm);
1533 +
1534 + var now = +new Date, type = "single";
1535 + if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
1536 + type = "triple";
1537 + e_preventDefault(e);
1538 + setTimeout(bind(focusInput, cm), 20);
1539 + selectLine(cm, start.line);
1540 + } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
1541 + type = "double";
1542 + lastDoubleClick = {time: now, pos: start};
1543 + e_preventDefault(e);
1544 + var word = findWordAt(getLine(doc, start.line).text, start);
1545 + extendSelection(cm.doc, word.from, word.to);
1546 + } else { lastClick = {time: now, pos: start}; }
1547 +
1548 + var last = start;
1549 + if (cm.options.dragDrop && dragAndDrop && !isReadOnly(cm) && !posEq(sel.from, sel.to) &&
1550 + !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
1551 + var dragEnd = operation(cm, function(e2) {
1552 + if (webkit) display.scroller.draggable = false;
1553 + cm.state.draggingText = false;
1554 + off(document, "mouseup", dragEnd);
1555 + off(display.scroller, "drop", dragEnd);
1556 + if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
1557 + e_preventDefault(e2);
1558 + extendSelection(cm.doc, start);
1559 + focusInput(cm);
1560 + }
1561 + });
1562 + // Let the drag handler handle this.
1563 + if (webkit) display.scroller.draggable = true;
1564 + cm.state.draggingText = dragEnd;
1565 + // IE's approach to draggable
1566 + if (display.scroller.dragDrop) display.scroller.dragDrop();
1567 + on(document, "mouseup", dragEnd);
1568 + on(display.scroller, "drop", dragEnd);
1569 + return;
1570 + }
1571 + e_preventDefault(e);
1572 + if (type == "single") extendSelection(cm.doc, clipPos(doc, start));
1573 +
1574 + var startstart = sel.from, startend = sel.to;
1575 +
1576 + function doSelect(cur) {
1577 + if (type == "single") {
1578 + extendSelection(cm.doc, clipPos(doc, start), cur);
1579 + return;
1580 + }
1581 +
1582 + startstart = clipPos(doc, startstart);
1583 + startend = clipPos(doc, startend);
1584 + if (type == "double") {
1585 + var word = findWordAt(getLine(doc, cur.line).text, cur);
1586 + if (posLess(cur, startstart)) extendSelection(cm.doc, word.from, startend);
1587 + else extendSelection(cm.doc, startstart, word.to);
1588 + } else if (type == "triple") {
1589 + if (posLess(cur, startstart)) extendSelection(cm.doc, startend, clipPos(doc, Pos(cur.line, 0)));
1590 + else extendSelection(cm.doc, startstart, clipPos(doc, Pos(cur.line + 1, 0)));
1591 + }
1592 + }
1593 +
1594 + var editorSize = display.wrapper.getBoundingClientRect();
1595 + // Used to ensure timeout re-tries don't fire when another extend
1596 + // happened in the meantime (clearTimeout isn't reliable -- at
1597 + // least on Chrome, the timeouts still happen even when cleared,
1598 + // if the clear happens after their scheduled firing time).
1599 + var counter = 0;
1600 +
1601 + function extend(e) {
1602 + var curCount = ++counter;
1603 + var cur = posFromMouse(cm, e, true);
1604 + if (!cur) return;
1605 + if (!posEq(cur, last)) {
1606 + if (!cm.state.focused) onFocus(cm);
1607 + last = cur;
1608 + doSelect(cur);
1609 + var visible = visibleLines(display, doc);
1610 + if (cur.line >= visible.to || cur.line < visible.from)
1611 + setTimeout(operation(cm, function(){if (counter == curCount) extend(e);}), 150);
1612 + } else {
1613 + var outside = e.clientY < editorSize.top ? -20 : e.clientY > editorSize.bottom ? 20 : 0;
1614 + if (outside) setTimeout(operation(cm, function() {
1615 + if (counter != curCount) return;
1616 + display.scroller.scrollTop += outside;
1617 + extend(e);
1618 + }), 50);
1619 + }
1620 + }
1621 +
1622 + function done(e) {
1623 + counter = Infinity;
1624 + var cur = posFromMouse(cm, e);
1625 + if (cur) doSelect(cur);
1626 + e_preventDefault(e);
1627 + focusInput(cm);
1628 + off(document, "mousemove", move);
1629 + off(document, "mouseup", up);
1630 + }
1631 +
1632 + var move = operation(cm, function(e) {
1633 + if (!ie && !e_button(e)) done(e);
1634 + else extend(e);
1635 + });
1636 + var up = operation(cm, done);
1637 + on(document, "mousemove", move);
1638 + on(document, "mouseup", up);
1639 + }
1640 +
1641 + function onDrop(e) {
1642 + var cm = this;
1643 + if (eventInWidget(cm.display, e) || (cm.options.onDragEvent && cm.options.onDragEvent(cm, addStop(e))))
1644 + return;
1645 + e_preventDefault(e);
1646 + var pos = posFromMouse(cm, e, true), files = e.dataTransfer.files;
1647 + if (!pos || isReadOnly(cm)) return;
1648 + if (files && files.length && window.FileReader && window.File) {
1649 + var n = files.length, text = Array(n), read = 0;
1650 + var loadFile = function(file, i) {
1651 + var reader = new FileReader;
1652 + reader.onload = function() {
1653 + text[i] = reader.result;
1654 + if (++read == n) {
1655 + pos = clipPos(cm.doc, pos);
1656 + replaceRange(cm.doc, text.join(""), pos, "around", "paste");
1657 + }
1658 + };
1659 + reader.readAsText(file);
1660 + };
1661 + for (var i = 0; i < n; ++i) loadFile(files[i], i);
1662 + } else {
1663 + // Don't do a replace if the drop happened inside of the selected text.
1664 + if (cm.state.draggingText && !(posLess(pos, cm.doc.sel.from) || posLess(cm.doc.sel.to, pos))) {
1665 + cm.state.draggingText(e);
1666 + // Ensure the editor is re-focused
1667 + setTimeout(bind(focusInput, cm), 20);
1668 + return;
1669 + }
1670 + try {
1671 + var text = e.dataTransfer.getData("Text");
1672 + if (text) {
1673 + var curFrom = cm.doc.sel.from, curTo = cm.doc.sel.to;
1674 + setSelection(cm.doc, pos, pos);
1675 + if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste");
1676 + cm.replaceSelection(text, null, "paste");
1677 + focusInput(cm);
1678 + onFocus(cm);
1679 + }
1680 + }
1681 + catch(e){}
1682 + }
1683 + }
1684 +
1685 + function clickInGutter(cm, e) {
1686 + var display = cm.display;
1687 + try { var mX = e.clientX, mY = e.clientY; }
1688 + catch(e) { return false; }
1689 +
1690 + if (mX >= Math.floor(display.gutters.getBoundingClientRect().right)) return false;
1691 + e_preventDefault(e);
1692 + if (!hasHandler(cm, "gutterClick")) return true;
1693 +
1694 + var lineBox = display.lineDiv.getBoundingClientRect();
1695 + if (mY > lineBox.bottom) return true;
1696 + mY -= lineBox.top - display.viewOffset;
1697 +
1698 + for (var i = 0; i < cm.options.gutters.length; ++i) {
1699 + var g = display.gutters.childNodes[i];
1700 + if (g && g.getBoundingClientRect().right >= mX) {
1701 + var line = lineAtHeight(cm.doc, mY);
1702 + var gutter = cm.options.gutters[i];
1703 + signalLater(cm, "gutterClick", cm, line, gutter, e);
1704 + break;
1705 + }
1706 + }
1707 + return true;
1708 + }
1709 +
1710 + function onDragStart(cm, e) {
1711 + if (eventInWidget(cm.display, e)) return;
1712 +
1713 + var txt = cm.getSelection();
1714 + e.dataTransfer.setData("Text", txt);
1715 +
1716 + // Use dummy image instead of default browsers image.
1717 + // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there.
1718 + if (e.dataTransfer.setDragImage && !safari) {
1719 + var img = elt("img", null, null, "position: fixed; left: 0; top: 0;");
1720 + if (opera) {
1721 + img.width = img.height = 1;
1722 + cm.display.wrapper.appendChild(img);
1723 + // Force a relayout, or Opera won't use our image for some obscure reason
1724 + img._top = img.offsetTop;
1725 + }
1726 + e.dataTransfer.setDragImage(img, 0, 0);
1727 + if (opera) img.parentNode.removeChild(img);
1728 + }
1729 + }
1730 +
1731 + function setScrollTop(cm, val) {
1732 + if (Math.abs(cm.doc.scrollTop - val) < 2) return;
1733 + cm.doc.scrollTop = val;
1734 + if (!gecko) updateDisplay(cm, [], val);
1735 + if (cm.display.scroller.scrollTop != val) cm.display.scroller.scrollTop = val;
1736 + if (cm.display.scrollbarV.scrollTop != val) cm.display.scrollbarV.scrollTop = val;
1737 + if (gecko) updateDisplay(cm, []);
1738 + }
1739 + function setScrollLeft(cm, val, isScroller) {
1740 + if (isScroller ? val == cm.doc.scrollLeft : Math.abs(cm.doc.scrollLeft - val) < 2) return;
1741 + val = Math.min(val, cm.display.scroller.scrollWidth - cm.display.scroller.clientWidth);
1742 + cm.doc.scrollLeft = val;
1743 + alignHorizontally(cm);
1744 + if (cm.display.scroller.scrollLeft != val) cm.display.scroller.scrollLeft = val;
1745 + if (cm.display.scrollbarH.scrollLeft != val) cm.display.scrollbarH.scrollLeft = val;
1746 + }
1747 +
1748 + // Since the delta values reported on mouse wheel events are
1749 + // unstandardized between browsers and even browser versions, and
1750 + // generally horribly unpredictable, this code starts by measuring
1751 + // the scroll effect that the first few mouse wheel events have,
1752 + // and, from that, detects the way it can convert deltas to pixel
1753 + // offsets afterwards.
1754 + //
1755 + // The reason we want to know the amount a wheel event will scroll
1756 + // is that it gives us a chance to update the display before the
1757 + // actual scrolling happens, reducing flickering.
1758 +
1759 + var wheelSamples = 0, wheelPixelsPerUnit = null;
1760 + // Fill in a browser-detected starting value on browsers where we
1761 + // know one. These don't have to be accurate -- the result of them
1762 + // being wrong would just be a slight flicker on the first wheel
1763 + // scroll (if it is large enough).
1764 + if (ie) wheelPixelsPerUnit = -.53;
1765 + else if (gecko) wheelPixelsPerUnit = 15;
1766 + else if (chrome) wheelPixelsPerUnit = -.7;
1767 + else if (safari) wheelPixelsPerUnit = -1/3;
1768 +
1769 + function onScrollWheel(cm, e) {
1770 + var dx = e.wheelDeltaX, dy = e.wheelDeltaY;
1771 + if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail;
1772 + if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail;
1773 + else if (dy == null) dy = e.wheelDelta;
1774 +
1775 + // Webkit browsers on OS X abort momentum scrolls when the target
1776 + // of the scroll event is removed from the scrollable element.
1777 + // This hack (see related code in patchDisplay) makes sure the
1778 + // element is kept around.
1779 + if (dy && mac && webkit) {
1780 + for (var cur = e.target; cur != scroll; cur = cur.parentNode) {
1781 + if (cur.lineObj) {
1782 + cm.display.currentWheelTarget = cur;
1783 + break;
1784 + }
1785 + }
1786 + }
1787 +
1788 + var display = cm.display, scroll = display.scroller;
1789 + // On some browsers, horizontal scrolling will cause redraws to
1790 + // happen before the gutter has been realigned, causing it to
1791 + // wriggle around in a most unseemly way. When we have an
1792 + // estimated pixels/delta value, we just handle horizontal
1793 + // scrolling entirely here. It'll be slightly off from native, but
1794 + // better than glitching out.
1795 + if (dx && !gecko && !opera && wheelPixelsPerUnit != null) {
1796 + if (dy)
1797 + setScrollTop(cm, Math.max(0, Math.min(scroll.scrollTop + dy * wheelPixelsPerUnit, scroll.scrollHeight - scroll.clientHeight)));
1798 + setScrollLeft(cm, Math.max(0, Math.min(scroll.scrollLeft + dx * wheelPixelsPerUnit, scroll.scrollWidth - scroll.clientWidth)));
1799 + e_preventDefault(e);
1800 + display.wheelStartX = null; // Abort measurement, if in progress
1801 + return;
1802 + }
1803 +
1804 + if (dy && wheelPixelsPerUnit != null) {
1805 + var pixels = dy * wheelPixelsPerUnit;
1806 + var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
1807 + if (pixels < 0) top = Math.max(0, top + pixels - 50);
1808 + else bot = Math.min(cm.doc.height, bot + pixels + 50);
1809 + updateDisplay(cm, [], {top: top, bottom: bot});
1810 + }
1811 +
1812 + if (wheelSamples < 20) {
1813 + if (display.wheelStartX == null) {
1814 + display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
1815 + display.wheelDX = dx; display.wheelDY = dy;
1816 + setTimeout(function() {
1817 + if (display.wheelStartX == null) return;
1818 + var movedX = scroll.scrollLeft - display.wheelStartX;
1819 + var movedY = scroll.scrollTop - display.wheelStartY;
1820 + var sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
1821 + (movedX && display.wheelDX && movedX / display.wheelDX);
1822 + display.wheelStartX = display.wheelStartY = null;
1823 + if (!sample) return;
1824 + wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1);
1825 + ++wheelSamples;
1826 + }, 200);
1827 + } else {
1828 + display.wheelDX += dx; display.wheelDY += dy;
1829 + }
1830 + }
1831 + }
1832 +
1833 + function doHandleBinding(cm, bound, dropShift) {
1834 + if (typeof bound == "string") {
1835 + bound = commands[bound];
1836 + if (!bound) return false;
1837 + }
1838 + // Ensure previous input has been read, so that the handler sees a
1839 + // consistent view of the document
1840 + if (cm.display.pollingFast && readInput(cm)) cm.display.pollingFast = false;
1841 + var doc = cm.doc, prevShift = doc.sel.shift;
1842 + try {
1843 + if (isReadOnly(cm)) cm.state.suppressEdits = true;
1844 + if (dropShift) doc.sel.shift = false;
1845 + bound(cm);
1846 + } finally {
1847 + doc.sel.shift = prevShift;
1848 + cm.state.suppressEdits = false;
1849 + }
1850 + return true;
1851 + }
1852 +
1853 + function allKeyMaps(cm) {
1854 + var maps = cm.state.keyMaps.slice(0);
1855 + maps.push(cm.options.keyMap);
1856 + if (cm.options.extraKeys) maps.unshift(cm.options.extraKeys);
1857 + return maps;
1858 + }
1859 +
1860 + var maybeTransition;
1861 + function handleKeyBinding(cm, e) {
1862 + // Handle auto keymap transitions
1863 + var startMap = getKeyMap(cm.options.keyMap), next = startMap.auto;
1864 + clearTimeout(maybeTransition);
1865 + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
1866 + if (getKeyMap(cm.options.keyMap) == startMap)
1867 + cm.options.keyMap = (next.call ? next.call(null, cm) : next);
1868 + }, 50);
1869 +
1870 + var name = keyName(e, true), handled = false;
1871 + if (!name) return false;
1872 + var keymaps = allKeyMaps(cm);
1873 +
1874 + if (e_prop(e, "shiftKey")) {
1875 + // First try to resolve full name (including 'Shift-'). Failing
1876 + // that, see if there is a cursor-motion command (starting with
1877 + // 'go') bound to the keyname without 'Shift-'.
1878 + handled = lookupKey("Shift-" + name, keymaps, function(b) {return doHandleBinding(cm, b, true);})
1879 + || lookupKey(name, keymaps, function(b) {
1880 + if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(cm, b);
1881 + });
1882 + } else {
1883 + handled = lookupKey(name, keymaps, function(b) { return doHandleBinding(cm, b); });
1884 + }
1885 + if (handled == "stop") handled = false;
1886 +
1887 + if (handled) {
1888 + e_preventDefault(e);
1889 + restartBlink(cm);
1890 + if (ie_lt9) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
1891 + }
1892 + return handled;
1893 + }
1894 +
1895 + function handleCharBinding(cm, e, ch) {
1896 + var handled = lookupKey("'" + ch + "'", allKeyMaps(cm),
1897 + function(b) { return doHandleBinding(cm, b, true); });
1898 + if (handled) {
1899 + e_preventDefault(e);
1900 + restartBlink(cm);
1901 + }
1902 + return handled;
1903 + }
1904 +
1905 + var lastStoppedKey = null;
1906 + function onKeyDown(e) {
1907 + var cm = this;
1908 + if (!cm.state.focused) onFocus(cm);
1909 + if (ie && e.keyCode == 27) { e.returnValue = false; }
1910 + if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1911 + var code = e_prop(e, "keyCode");
1912 + // IE does strange things with escape.
1913 + cm.doc.sel.shift = code == 16 || e_prop(e, "shiftKey");
1914 + // First give onKeyEvent option a chance to handle this.
1915 + var handled = handleKeyBinding(cm, e);
1916 + if (opera) {
1917 + lastStoppedKey = handled ? code : null;
1918 + // Opera has no cut event... we try to at least catch the key combo
1919 + if (!handled && code == 88 && !hasCopyEvent && e_prop(e, mac ? "metaKey" : "ctrlKey"))
1920 + cm.replaceSelection("");
1921 + }
1922 + }
1923 +
1924 + function onKeyPress(e) {
1925 + var cm = this;
1926 + if (cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return;
1927 + var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
1928 + if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
1929 + if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return;
1930 + var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
1931 + if (this.options.electricChars && this.doc.mode.electricChars &&
1932 + this.options.smartIndent && !isReadOnly(this) &&
1933 + this.doc.mode.electricChars.indexOf(ch) > -1)
1934 + setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75);
1935 + if (handleCharBinding(cm, e, ch)) return;
1936 + fastPoll(cm);
1937 + }
1938 +
1939 + function onFocus(cm) {
1940 + if (cm.options.readOnly == "nocursor") return;
1941 + if (!cm.state.focused) {
1942 + signal(cm, "focus", cm);
1943 + cm.state.focused = true;
1944 + if (cm.display.wrapper.className.search(/\bCodeMirror-focused\b/) == -1)
1945 + cm.display.wrapper.className += " CodeMirror-focused";
1946 + resetInput(cm, true);
1947 + }
1948 + slowPoll(cm);
1949 + restartBlink(cm);
1950 + }
1951 + function onBlur(cm) {
1952 + if (cm.state.focused) {
1953 + signal(cm, "blur", cm);
1954 + cm.state.focused = false;
1955 + cm.display.wrapper.className = cm.display.wrapper.className.replace(" CodeMirror-focused", "");
1956 + }
1957 + clearInterval(cm.display.blinker);
1958 + setTimeout(function() {if (!cm.state.focused) cm.doc.sel.shift = false;}, 150);
1959 + }
1960 +
1961 + var detectingSelectAll;
1962 + function onContextMenu(cm, e) {
1963 + var display = cm.display, sel = cm.doc.sel;
1964 + if (eventInWidget(display, e)) return;
1965 +
1966 + var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop;
1967 + if (!pos || opera) return; // Opera is difficult.
1968 + if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
1969 + operation(cm, setSelection)(cm.doc, pos, pos);
1970 +
1971 + var oldCSS = display.input.style.cssText;
1972 + display.inputDiv.style.position = "absolute";
1973 + display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
1974 + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" +
1975 + "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);";
1976 + focusInput(cm);
1977 + resetInput(cm, true);
1978 + // Adds "Select all" to context menu in FF
1979 + if (posEq(sel.from, sel.to)) display.input.value = display.prevInput = " ";
1980 +
1981 + function rehide() {
1982 + display.inputDiv.style.position = "relative";
1983 + display.input.style.cssText = oldCSS;
1984 + if (ie_lt9) display.scrollbarV.scrollTop = display.scroller.scrollTop = scrollPos;
1985 + slowPoll(cm);
1986 +
1987 + // Try to detect the user choosing select-all
1988 + if (display.input.selectionStart != null && (!ie || ie_lt9)) {
1989 + clearTimeout(detectingSelectAll);
1990 + var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value), i = 0;
1991 + display.prevInput = " ";
1992 + display.input.selectionStart = 1; display.input.selectionEnd = extval.length;
1993 + var poll = function(){
1994 + if (display.prevInput == " " && display.input.selectionStart == 0)
1995 + operation(cm, commands.selectAll)(cm);
1996 + else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500);
1997 + else resetInput(cm);
1998 + };
1999 + detectingSelectAll = setTimeout(poll, 200);
2000 + }
2001 + }
2002 +
2003 + if (captureMiddleClick) {
2004 + e_stop(e);
2005 + var mouseup = function() {
2006 + off(window, "mouseup", mouseup);
2007 + setTimeout(rehide, 20);
2008 + };
2009 + on(window, "mouseup", mouseup);
2010 + } else {
2011 + setTimeout(rehide, 50);
2012 + }
2013 + }
2014 +
2015 + // UPDATING
2016 +
2017 + function changeEnd(change) {
2018 + return Pos(change.from.line + change.text.length - 1,
2019 + lst(change.text).length + (change.text.length == 1 ? change.from.ch : 0));
2020 + }
2021 +
2022 + // Hint can be null|"end"|"start"|"around"|{anchor,head}
2023 + function computeSelAfterChange(sel, change, hint) {
2024 + if (hint && typeof hint == "object") return hint; // Assumed to be {from, to} object
2025 + if (hint == "start") return {anchor: change.from, head: change.from};
2026 +
2027 + var end = changeEnd(change);
2028 + if (hint == "around") return {anchor: change.from, head: end};
2029 + if (hint == "end") return {anchor: end, head: end};
2030 +
2031 + // hint is null, leave the selection alone as much as possible
2032 + var adjustPos = function(pos) {
2033 + if (posLess(pos, change.from)) return pos;
2034 + if (!posLess(change.to, pos)) return end;
2035 +
2036 + var line = pos.line + change.text.length - (change.to.line - change.from.line) - 1, ch = pos.ch;
2037 + if (pos.line == change.to.line) ch += end.ch - change.to.ch;
2038 + return Pos(line, ch);
2039 + };
2040 + return {anchor: adjustPos(sel.anchor), head: adjustPos(sel.head)};
2041 + }
2042 +
2043 + // Replace the range from from to to by the strings in replacement.
2044 + // change is a {from, to, text [, origin]} object
2045 + function makeChange(doc, change, selUpdate, ignoreReadOnly) {
2046 + if (doc.cm) {
2047 + if (!doc.cm.curOp) return operation(doc.cm, makeChange)(doc, change, selUpdate, ignoreReadOnly);
2048 + if (doc.cm.state.suppressEdits) return;
2049 + }
2050 +
2051 + // Possibly split or suppress the update based on the presence
2052 + // of read-only spans in its range.
2053 + var split = sawReadOnlySpans && !ignoreReadOnly && removeReadOnlyRanges(doc, change.from, change.to);
2054 + if (split) {
2055 + for (var i = split.length - 1; i >= 1; --i)
2056 + makeChangeNoReadonly(doc, {from: split[i].from, to: split[i].to, text: [""]});
2057 + if (split.length)
2058 + makeChangeNoReadonly(doc, {from: split[0].from, to: split[0].to, text: change.text}, selUpdate);
2059 + } else {
2060 + makeChangeNoReadonly(doc, change, selUpdate);
2061 + }
2062 + }
2063 +
2064 + function makeChangeNoReadonly(doc, change, selUpdate) {
2065 + var selAfter = computeSelAfterChange(doc.sel, change, selUpdate);
2066 + addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN);
2067 +
2068 + makeChangeSingleDoc(doc, change, selAfter, stretchSpansOverChange(doc, change));
2069 + var rebased = [];
2070 +
2071 + linkedDocs(doc, function(doc, sharedHist) {
2072 + if (!sharedHist && indexOf(rebased, doc.history) == -1) {
2073 + rebaseHist(doc.history, change);
2074 + rebased.push(doc.history);
2075 + }
2076 + makeChangeSingleDoc(doc, change, null, stretchSpansOverChange(doc, change));
2077 + });
2078 + }
2079 +
2080 + function makeChangeFromHistory(doc, type) {
2081 + var hist = doc.history;
2082 + var event = (type == "undo" ? hist.done : hist.undone).pop();
2083 + if (!event) return;
2084 + hist.dirtyCounter += type == "undo" ? -1 : 1;
2085 +
2086 + var anti = {changes: [], anchorBefore: event.anchorAfter, headBefore: event.headAfter,
2087 + anchorAfter: event.anchorBefore, headAfter: event.headBefore};
2088 + (type == "undo" ? hist.undone : hist.done).push(anti);
2089 +
2090 + for (var i = event.changes.length - 1; i >= 0; --i) {
2091 + var change = event.changes[i];
2092 + change.origin = type;
2093 + anti.changes.push(historyChangeFromChange(doc, change));
2094 +
2095 + var after = i ? computeSelAfterChange(doc.sel, change, null)
2096 + : {anchor: event.anchorBefore, head: event.headBefore};
2097 + makeChangeSingleDoc(doc, change, after, mergeOldSpans(doc, change));
2098 + var rebased = [];
2099 +
2100 + linkedDocs(doc, function(doc, sharedHist) {
2101 + if (!sharedHist && indexOf(rebased, doc.history) == -1) {
2102 + rebaseHist(doc.history, change);
2103 + rebased.push(doc.history);
2104 + }
2105 + makeChangeSingleDoc(doc, change, null, mergeOldSpans(doc, change));
2106 + });
2107 + }
2108 + }
2109 +
2110 + function shiftDoc(doc, distance) {
2111 + function shiftPos(pos) {return Pos(pos.line + distance, pos.ch);}
2112 + doc.first += distance;
2113 + if (doc.cm) regChange(doc.cm, doc.first, doc.first, distance);
2114 + doc.sel.head = shiftPos(doc.sel.head); doc.sel.anchor = shiftPos(doc.sel.anchor);
2115 + doc.sel.from = shiftPos(doc.sel.from); doc.sel.to = shiftPos(doc.sel.to);
2116 + }
2117 +
2118 + function makeChangeSingleDoc(doc, change, selAfter, spans) {
2119 + if (doc.cm && !doc.cm.curOp)
2120 + return operation(doc.cm, makeChangeSingleDoc)(doc, change, selAfter, spans);
2121 +
2122 + if (change.to.line < doc.first) {
2123 + shiftDoc(doc, change.text.length - 1 - (change.to.line - change.from.line));
2124 + return;
2125 + }
2126 + if (change.from.line > doc.lastLine()) return;
2127 +
2128 + // Clip the change to the size of this doc
2129 + if (change.from.line < doc.first) {
2130 + var shift = change.text.length - 1 - (doc.first - change.from.line);
2131 + shiftDoc(doc, shift);
2132 + change = {from: Pos(doc.first, 0), to: Pos(change.to.line + shift, change.to.ch),
2133 + text: [lst(change.text)], origin: change.origin};
2134 + }
2135 + var last = doc.lastLine();
2136 + if (change.to.line > last) {
2137 + change = {from: change.from, to: Pos(last, getLine(doc, last).text.length),
2138 + text: [change.text[0]], origin: change.origin};
2139 + }
2140 +
2141 + if (!selAfter) selAfter = computeSelAfterChange(doc.sel, change, null);
2142 + if (doc.cm) makeChangeSingleDocInEditor(doc.cm, change, spans, selAfter);
2143 + else updateDoc(doc, change, spans, selAfter);
2144 + }
2145 +
2146 + function makeChangeSingleDocInEditor(cm, change, spans, selAfter) {
2147 + var doc = cm.doc, display = cm.display, from = change.from, to = change.to;
2148 +
2149 + var recomputeMaxLength = false, checkWidthStart = from.line;
2150 + if (!cm.options.lineWrapping) {
2151 + checkWidthStart = lineNo(visualLine(doc, getLine(doc, from.line)));
2152 + doc.iter(checkWidthStart, to.line + 1, function(line) {
2153 + if (line == display.maxLine) {
2154 + recomputeMaxLength = true;
2155 + return true;
2156 + }
2157 + });
2158 + }
2159 +
2160 + updateDoc(doc, change, spans, selAfter, estimateHeight(cm));
2161 +
2162 + if (!cm.options.lineWrapping) {
2163 + doc.iter(checkWidthStart, from.line + change.text.length, function(line) {
2164 + var len = lineLength(doc, line);
2165 + if (len > display.maxLineLength) {
2166 + display.maxLine = line;
2167 + display.maxLineLength = len;
2168 + display.maxLineChanged = true;
2169 + recomputeMaxLength = false;
2170 + }
2171 + });
2172 + if (recomputeMaxLength) cm.curOp.updateMaxLine = true;
2173 + }
2174 +
2175 + // Adjust frontier, schedule worker
2176 + doc.frontier = Math.min(doc.frontier, from.line);
2177 + startWorker(cm, 400);
2178 +
2179 + var lendiff = change.text.length - (to.line - from.line) - 1;
2180 + // Remember that these lines changed, for updating the display
2181 + regChange(cm, from.line, to.line + 1, lendiff);
2182 + if (hasHandler(cm, "change")) {
2183 + var changeObj = {from: from, to: to, text: change.text, origin: change.origin};
2184 + if (cm.curOp.textChanged) {
2185 + for (var cur = cm.curOp.textChanged; cur.next; cur = cur.next) {}
2186 + cur.next = changeObj;
2187 + } else cm.curOp.textChanged = changeObj;
2188 + }
2189 + }
2190 +
2191 + function replaceRange(doc, code, from, to, origin) {
2192 + if (!to) to = from;
2193 + if (posLess(to, from)) { var tmp = to; to = from; from = tmp; }
2194 + if (typeof code == "string") code = splitLines(code);
2195 + makeChange(doc, {from: from, to: to, text: code, origin: origin}, null);
2196 + }
2197 +
2198 + // POSITION OBJECT
2199 +
2200 + function Pos(line, ch) {
2201 + if (!(this instanceof Pos)) return new Pos(line, ch);
2202 + this.line = line; this.ch = ch;
2203 + }
2204 + CodeMirror.Pos = Pos;
2205 +
2206 + function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
2207 + function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
2208 + function copyPos(x) {return Pos(x.line, x.ch);}
2209 +
2210 + // SELECTION
2211 +
2212 + function clipLine(doc, n) {return Math.max(doc.first, Math.min(n, doc.first + doc.size - 1));}
2213 + function clipPos(doc, pos) {
2214 + if (pos.line < doc.first) return Pos(doc.first, 0);
2215 + var last = doc.first + doc.size - 1;
2216 + if (pos.line > last) return Pos(last, getLine(doc, last).text.length);
2217 + var ch = pos.ch, linelen = getLine(doc, pos.line).text.length;
2218 + if (ch == null || ch > linelen) return Pos(pos.line, linelen);
2219 + else if (ch < 0) return Pos(pos.line, 0);
2220 + else return pos;
2221 + }
2222 + function isLine(doc, l) {return l >= doc.first && l < doc.first + doc.size;}
2223 +
2224 + // If shift is held, this will move the selection anchor. Otherwise,
2225 + // it'll set the whole selection.
2226 + function extendSelection(doc, pos, other, bias) {
2227 + if (doc.sel.shift || doc.sel.extend) {
2228 + var anchor = doc.sel.anchor;
2229 + if (other) {
2230 + var posBefore = posLess(pos, anchor);
2231 + if (posBefore != posLess(other, anchor)) {
2232 + anchor = pos;
2233 + pos = other;
2234 + } else if (posBefore != posLess(pos, other)) {
2235 + pos = other;
2236 + }
2237 + }
2238 + setSelection(doc, anchor, pos, bias);
2239 + } else {
2240 + setSelection(doc, pos, other || pos, bias);
2241 + }
2242 + if (doc.cm) doc.cm.curOp.userSelChange = true;
2243 + }
2244 +
2245 + // Update the selection. Last two args are only used by
2246 + // updateDoc, since they have to be expressed in the line
2247 + // numbers before the update.
2248 + function setSelection(doc, anchor, head, bias, checkAtomic) {
2249 + var sel = doc.sel;
2250 + sel.goalColumn = null;
2251 + // Skip over atomic spans.
2252 + if (checkAtomic || !posEq(anchor, sel.anchor))
2253 + anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push");
2254 + if (checkAtomic || !posEq(head, sel.head))
2255 + head = skipAtomic(doc, head, bias, checkAtomic != "push");
2256 +
2257 + if (posEq(sel.anchor, anchor) && posEq(sel.head, head)) return;
2258 +
2259 + sel.anchor = anchor; sel.head = head;
2260 + var inv = posLess(head, anchor);
2261 + sel.from = inv ? head : anchor;
2262 + sel.to = inv ? anchor : head;
2263 +
2264 + if (doc.cm)
2265 + doc.cm.curOp.updateInput = doc.cm.curOp.selectionChanged = true;
2266 + }
2267 +
2268 + function reCheckSelection(cm) {
2269 + setSelection(cm.doc, cm.doc.sel.from, cm.doc.sel.to, null, "push");
2270 + }
2271 +
2272 + function skipAtomic(doc, pos, bias, mayClear) {
2273 + var flipped = false, curPos = pos;
2274 + var dir = bias || 1;
2275 + doc.cantEdit = false;
2276 + search: for (;;) {
2277 + var line = getLine(doc, curPos.line), toClear;
2278 + if (line.markedSpans) {
2279 + for (var i = 0; i < line.markedSpans.length; ++i) {
2280 + var sp = line.markedSpans[i], m = sp.marker;
2281 + if ((sp.from == null || (m.inclusiveLeft ? sp.from <= curPos.ch : sp.from < curPos.ch)) &&
2282 + (sp.to == null || (m.inclusiveRight ? sp.to >= curPos.ch : sp.to > curPos.ch))) {
2283 + if (mayClear && m.clearOnEnter) {
2284 + (toClear || (toClear = [])).push(m);
2285 + continue;
2286 + } else if (!m.atomic) continue;
2287 + var newPos = m.find()[dir < 0 ? "from" : "to"];
2288 + if (posEq(newPos, curPos)) {
2289 + newPos.ch += dir;
2290 + if (newPos.ch < 0) {
2291 + if (newPos.line > doc.first) newPos = clipPos(doc, Pos(newPos.line - 1));
2292 + else newPos = null;
2293 + } else if (newPos.ch > line.text.length) {
2294 + if (newPos.line < doc.first + doc.size - 1) newPos = Pos(newPos.line + 1, 0);
2295 + else newPos = null;
2296 + }
2297 + if (!newPos) {
2298 + if (flipped) {
2299 + // Driven in a corner -- no valid cursor position found at all
2300 + // -- try again *with* clearing, if we didn't already
2301 + if (!mayClear) return skipAtomic(doc, pos, bias, true);
2302 + // Otherwise, turn off editing until further notice, and return the start of the doc
2303 + doc.cantEdit = true;
2304 + return Pos(doc.first, 0);
2305 + }
2306 + flipped = true; newPos = pos; dir = -dir;
2307 + }
2308 + }
2309 + curPos = newPos;
2310 + continue search;
2311 + }
2312 + }
2313 + if (toClear) for (var i = 0; i < toClear.length; ++i) toClear[i].clear();
2314 + }
2315 + return curPos;
2316 + }
2317 + }
2318 +
2319 + // SCROLLING
2320 +
2321 + function scrollCursorIntoView(cm) {
2322 + var coords = scrollPosIntoView(cm, cm.doc.sel.head);
2323 + if (!cm.state.focused) return;
2324 + var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
2325 + if (coords.top + box.top < 0) doScroll = true;
2326 + else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false;
2327 + if (doScroll != null && !phantom) {
2328 + var hidden = display.cursor.style.display == "none";
2329 + if (hidden) {
2330 + display.cursor.style.display = "";
2331 + display.cursor.style.left = coords.left + "px";
2332 + display.cursor.style.top = (coords.top - display.viewOffset) + "px";
2333 + }
2334 + display.cursor.scrollIntoView(doScroll);
2335 + if (hidden) display.cursor.style.display = "none";
2336 + }
2337 + }
2338 +
2339 + function scrollPosIntoView(cm, pos) {
2340 + for (;;) {
2341 + var changed = false, coords = cursorCoords(cm, pos);
2342 + var scrollPos = calculateScrollPos(cm, coords.left, coords.top, coords.left, coords.bottom);
2343 + var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft;
2344 + if (scrollPos.scrollTop != null) {
2345 + setScrollTop(cm, scrollPos.scrollTop);
2346 + if (Math.abs(cm.doc.scrollTop - startTop) > 1) changed = true;
2347 + }
2348 + if (scrollPos.scrollLeft != null) {
2349 + setScrollLeft(cm, scrollPos.scrollLeft);
2350 + if (Math.abs(cm.doc.scrollLeft - startLeft) > 1) changed = true;
2351 + }
2352 + if (!changed) return coords;
2353 + }
2354 + }
2355 +
2356 + function scrollIntoView(cm, x1, y1, x2, y2) {
2357 + var scrollPos = calculateScrollPos(cm, x1, y1, x2, y2);
2358 + if (scrollPos.scrollTop != null) setScrollTop(cm, scrollPos.scrollTop);
2359 + if (scrollPos.scrollLeft != null) setScrollLeft(cm, scrollPos.scrollLeft);
2360 + }
2361 +
2362 + function calculateScrollPos(cm, x1, y1, x2, y2) {
2363 + var display = cm.display, pt = paddingTop(display);
2364 + y1 += pt; y2 += pt;
2365 + var screen = display.scroller.clientHeight - scrollerCutOff, screentop = display.scroller.scrollTop, result = {};
2366 + var docBottom = cm.doc.height + 2 * pt;
2367 + var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;
2368 + if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
2369 + else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
2370 +
2371 + var screenw = display.scroller.clientWidth - scrollerCutOff, screenleft = display.scroller.scrollLeft;
2372 + x1 += display.gutters.offsetWidth; x2 += display.gutters.offsetWidth;
2373 + var gutterw = display.gutters.offsetWidth;
2374 + var atLeft = x1 < gutterw + 10;
2375 + if (x1 < screenleft + gutterw || atLeft) {
2376 + if (atLeft) x1 = 0;
2377 + result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
2378 + } else if (x2 > screenw + screenleft - 3) {
2379 + result.scrollLeft = x2 + 10 - screenw;
2380 + }
2381 + return result;
2382 + }
2383 +
2384 + // API UTILITIES
2385 +
2386 + function indentLine(cm, n, how, aggressive) {
2387 + var doc = cm.doc;
2388 + if (!how) how = "add";
2389 + if (how == "smart") {
2390 + if (!cm.doc.mode.indent) how = "prev";
2391 + else var state = getStateBefore(cm, n);
2392 + }
2393 +
2394 + var tabSize = cm.options.tabSize;
2395 + var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize);
2396 + var curSpaceString = line.text.match(/^\s*/)[0], indentation;
2397 + if (how == "smart") {
2398 + indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text);
2399 + if (indentation == Pass) {
2400 + if (!aggressive) return;
2401 + how = "prev";
2402 + }
2403 + }
2404 + if (how == "prev") {
2405 + if (n > doc.first) indentation = countColumn(getLine(doc, n-1).text, null, tabSize);
2406 + else indentation = 0;
2407 + } else if (how == "add") {
2408 + indentation = curSpace + cm.options.indentUnit;
2409 + } else if (how == "subtract") {
2410 + indentation = curSpace - cm.options.indentUnit;
2411 + }
2412 + indentation = Math.max(0, indentation);
2413 +
2414 + var indentString = "", pos = 0;
2415 + if (cm.options.indentWithTabs)
2416 + for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";}
2417 + if (pos < indentation) indentString += spaceStr(indentation - pos);
2418 +
2419 + if (indentString != curSpaceString)
2420 + replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input");
2421 + line.stateAfter = null;
2422 + }
2423 +
2424 + function changeLine(cm, handle, op) {
2425 + var no = handle, line = handle, doc = cm.doc;
2426 + if (typeof handle == "number") line = getLine(doc, clipLine(doc, handle));
2427 + else no = lineNo(handle);
2428 + if (no == null) return null;
2429 + if (op(line, no)) regChange(cm, no, no + 1);
2430 + else return null;
2431 + return line;
2432 + }
2433 +
2434 + function findPosH(doc, dir, unit, visually) {
2435 + var end = doc.sel.head, line = end.line, ch = end.ch;
2436 + var lineObj = getLine(doc, line);
2437 + function findNextLine() {
2438 + var l = line + dir;
2439 + if (l < doc.first || l >= doc.first + doc.size) return false;
2440 + line = l;
2441 + return lineObj = getLine(doc, l);
2442 + }
2443 + function moveOnce(boundToLine) {
2444 + var next = (visually ? moveVisually : moveLogically)(lineObj, ch, dir, true);
2445 + if (next == null) {
2446 + if (!boundToLine && findNextLine()) {
2447 + if (visually) ch = (dir < 0 ? lineRight : lineLeft)(lineObj);
2448 + else ch = dir < 0 ? lineObj.text.length : 0;
2449 + } else return false;
2450 + } else ch = next;
2451 + return true;
2452 + }
2453 + if (unit == "char") moveOnce();
2454 + else if (unit == "column") moveOnce(true);
2455 + else if (unit == "word") {
2456 + var sawWord = false;
2457 + for (;;) {
2458 + if (dir < 0) if (!moveOnce()) break;
2459 + if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
2460 + else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
2461 + if (dir > 0) if (!moveOnce()) break;
2462 + }
2463 + }
2464 + return skipAtomic(doc, Pos(line, ch), dir, true);
2465 + }
2466 +
2467 + function findWordAt(line, pos) {
2468 + var start = pos.ch, end = pos.ch;
2469 + if (line) {
2470 + if (pos.after === false || end == line.length) --start; else ++end;
2471 + var startChar = line.charAt(start);
2472 + var check = isWordChar(startChar) ? isWordChar :
2473 + /\s/.test(startChar) ? function(ch) {return /\s/.test(ch);} :
2474 + function(ch) {return !/\s/.test(ch) && !isWordChar(ch);};
2475 + while (start > 0 && check(line.charAt(start - 1))) --start;
2476 + while (end < line.length && check(line.charAt(end))) ++end;
2477 + }
2478 + return {from: Pos(pos.line, start), to: Pos(pos.line, end)};
2479 + }
2480 +
2481 + function selectLine(cm, line) {
2482 + extendSelection(cm.doc, Pos(line, 0), clipPos(cm.doc, Pos(line + 1, 0)));
2483 + }
2484 +
2485 + // PROTOTYPE
2486 +
2487 + // The publicly visible API. Note that operation(null, f) means
2488 + // 'wrap f in an operation, performed on its `this` parameter'
2489 +
2490 + CodeMirror.prototype = {
2491 + focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);},
2492 +
2493 + setOption: function(option, value) {
2494 + var options = this.options, old = options[option];
2495 + if (options[option] == value && option != "mode") return;
2496 + options[option] = value;
2497 + if (optionHandlers.hasOwnProperty(option))
2498 + operation(this, optionHandlers[option])(this, value, old);
2499 + },
2500 +
2501 + getOption: function(option) {return this.options[option];},
2502 + getDoc: function() {return this.doc;},
2503 +
2504 + addKeyMap: function(map) {
2505 + this.state.keyMaps.push(map);
2506 + },
2507 + removeKeyMap: function(map) {
2508 + var maps = this.state.keyMaps;
2509 + for (var i = 0; i < maps.length; ++i)
2510 + if ((typeof map == "string" ? maps[i].name : maps[i]) == map) {
2511 + maps.splice(i, 1);
2512 + return true;
2513 + }
2514 + },
2515 +
2516 + addOverlay: operation(null, function(spec, options) {
2517 + var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
2518 + if (mode.startState) throw new Error("Overlays may not be stateful.");
2519 + this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque});
2520 + this.state.modeGen++;
2521 + regChange(this);
2522 + }),
2523 + removeOverlay: operation(null, function(spec) {
2524 + var overlays = this.state.overlays;
2525 + for (var i = 0; i < overlays.length; ++i) {
2526 + if (overlays[i].modeSpec == spec) {
2527 + overlays.splice(i, 1);
2528 + this.state.modeGen++;
2529 + regChange(this);
2530 + return;
2531 + }
2532 + }
2533 + }),
2534 +
2535 + indentLine: operation(null, function(n, dir, aggressive) {
2536 + if (typeof dir != "string") {
2537 + if (dir == null) dir = this.options.smartIndent ? "smart" : "prev";
2538 + else dir = dir ? "add" : "subtract";
2539 + }
2540 + if (isLine(this.doc, n)) indentLine(this, n, dir, aggressive);
2541 + }),
2542 + indentSelection: operation(null, function(how) {
2543 + var sel = this.doc.sel;
2544 + if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how);
2545 + var e = sel.to.line - (sel.to.ch ? 0 : 1);
2546 + for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how);
2547 + }),
2548 +
2549 + // Fetch the parser token for a given character. Useful for hacks
2550 + // that want to inspect the mode state (say, for completion).
2551 + getTokenAt: function(pos) {
2552 + var doc = this.doc;
2553 + pos = clipPos(doc, pos);
2554 + var state = getStateBefore(this, pos.line), mode = this.doc.mode;
2555 + var line = getLine(doc, pos.line);
2556 + var stream = new StringStream(line.text, this.options.tabSize);
2557 + while (stream.pos < pos.ch && !stream.eol()) {
2558 + stream.start = stream.pos;
2559 + var style = mode.token(stream, state);
2560 + }
2561 + return {start: stream.start,
2562 + end: stream.pos,
2563 + string: stream.current(),
2564 + className: style || null, // Deprecated, use 'type' instead
2565 + type: style || null,
2566 + state: state};
2567 + },
2568 +
2569 + getStateAfter: function(line) {
2570 + var doc = this.doc;
2571 + line = clipLine(doc, line == null ? doc.first + doc.size - 1: line);
2572 + return getStateBefore(this, line + 1);
2573 + },
2574 +
2575 + cursorCoords: function(start, mode) {
2576 + var pos, sel = this.doc.sel;
2577 + if (start == null) pos = sel.head;
2578 + else if (typeof start == "object") pos = clipPos(this.doc, start);
2579 + else pos = start ? sel.from : sel.to;
2580 + return cursorCoords(this, pos, mode || "page");
2581 + },
2582 +
2583 + charCoords: function(pos, mode) {
2584 + return charCoords(this, clipPos(this.doc, pos), mode || "page");
2585 + },
2586 +
2587 + coordsChar: function(coords) {
2588 + var off = this.display.lineSpace.getBoundingClientRect();
2589 + var scrollY = window.pageYOffset || (document.documentElement || document.body).scrollTop;
2590 + var scrollX = window.pageXOffset || (document.documentElement || document.body).scrollLeft;
2591 + return coordsChar(this, coords.left - off.left - scrollX, coords.top - off.top - scrollY);
2592 + },
2593 +
2594 + defaultTextHeight: function() { return textHeight(this.display); },
2595 +
2596 + setGutterMarker: operation(null, function(line, gutterID, value) {
2597 + return changeLine(this, line, function(line) {
2598 + var markers = line.gutterMarkers || (line.gutterMarkers = {});
2599 + markers[gutterID] = value;
2600 + if (!value && isEmpty(markers)) line.gutterMarkers = null;
2601 + return true;
2602 + });
2603 + }),
2604 +
2605 + clearGutter: operation(null, function(gutterID) {
2606 + var cm = this, doc = cm.doc, i = doc.first;
2607 + doc.iter(function(line) {
2608 + if (line.gutterMarkers && line.gutterMarkers[gutterID]) {
2609 + line.gutterMarkers[gutterID] = null;
2610 + regChange(cm, i, i + 1);
2611 + if (isEmpty(line.gutterMarkers)) line.gutterMarkers = null;
2612 + }
2613 + ++i;
2614 + });
2615 + }),
2616 +
2617 + addLineClass: operation(null, function(handle, where, cls) {
2618 + return changeLine(this, handle, function(line) {
2619 + var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
2620 + if (!line[prop]) line[prop] = cls;
2621 + else if (new RegExp("\\b" + cls + "\\b").test(line[prop])) return false;
2622 + else line[prop] += " " + cls;
2623 + return true;
2624 + });
2625 + }),
2626 +
2627 + removeLineClass: operation(null, function(handle, where, cls) {
2628 + return changeLine(this, handle, function(line) {
2629 + var prop = where == "text" ? "textClass" : where == "background" ? "bgClass" : "wrapClass";
2630 + var cur = line[prop];
2631 + if (!cur) return false;
2632 + else if (cls == null) line[prop] = null;
2633 + else {
2634 + var upd = cur.replace(new RegExp("^" + cls + "\\b\\s*|\\s*\\b" + cls + "\\b"), "");
2635 + if (upd == cur) return false;
2636 + line[prop] = upd || null;
2637 + }
2638 + return true;
2639 + });
2640 + }),
2641 +
2642 + addLineWidget: operation(null, function(handle, node, options) {
2643 + return addLineWidget(this, handle, node, options);
2644 + }),
2645 +
2646 + removeLineWidget: function(widget) { widget.clear(); },
2647 +
2648 + lineInfo: function(line) {
2649 + if (typeof line == "number") {
2650 + if (!isLine(this.doc, line)) return null;
2651 + var n = line;
2652 + line = getLine(this.doc, line);
2653 + if (!line) return null;
2654 + } else {
2655 + var n = lineNo(line);
2656 + if (n == null) return null;
2657 + }
2658 + return {line: n, handle: line, text: line.text, gutterMarkers: line.gutterMarkers,
2659 + textClass: line.textClass, bgClass: line.bgClass, wrapClass: line.wrapClass,
2660 + widgets: line.widgets};
2661 + },
2662 +
2663 + getViewport: function() { return {from: this.display.showingFrom, to: this.display.showingTo};},
2664 +
2665 + addWidget: function(pos, node, scroll, vert, horiz) {
2666 + var display = this.display;
2667 + pos = cursorCoords(this, clipPos(this.doc, pos));
2668 + var top = pos.bottom, left = pos.left;
2669 + node.style.position = "absolute";
2670 + display.sizer.appendChild(node);
2671 + if (vert == "over") {
2672 + top = pos.top;
2673 + } else if (vert == "above" || vert == "near") {
2674 + var vspace = Math.max(display.wrapper.clientHeight, this.doc.height),
2675 + hspace = Math.max(display.sizer.clientWidth, display.lineSpace.clientWidth);
2676 + // Default to positioning above (if specified and possible); otherwise default to positioning below
2677 + if ((vert == 'above' || pos.bottom + node.offsetHeight > vspace) && pos.top > node.offsetHeight)
2678 + top = pos.top - node.offsetHeight;
2679 + else if (pos.bottom + node.offsetHeight <= vspace)
2680 + top = pos.bottom;
2681 + if (left + node.offsetWidth > hspace)
2682 + left = hspace - node.offsetWidth;
2683 + }
2684 + node.style.top = (top + paddingTop(display)) + "px";
2685 + node.style.left = node.style.right = "";
2686 + if (horiz == "right") {
2687 + left = display.sizer.clientWidth - node.offsetWidth;
2688 + node.style.right = "0px";
2689 + } else {
2690 + if (horiz == "left") left = 0;
2691 + else if (horiz == "middle") left = (display.sizer.clientWidth - node.offsetWidth) / 2;
2692 + node.style.left = left + "px";
2693 + }
2694 + if (scroll)
2695 + scrollIntoView(this, left, top, left + node.offsetWidth, top + node.offsetHeight);
2696 + },
2697 +
2698 + triggerOnKeyDown: operation(null, onKeyDown),
2699 +
2700 + execCommand: function(cmd) {return commands[cmd](this);},
2701 +
2702 + // Stuff used by commands, probably not much use to outside code.
2703 + moveH: operation(null, function(dir, unit) {
2704 + var sel = this.doc.sel, pos = dir < 0 ? sel.from : sel.to;
2705 + if (sel.shift || sel.extend || posEq(sel.from, sel.to))
2706 + pos = findPosH(this.doc, dir, unit, this.options.rtlMoveVisually);
2707 + extendSelection(this.doc, pos, pos, dir);
2708 + }),
2709 +
2710 + deleteH: operation(null, function(dir, unit) {
2711 + var sel = this.doc.sel;
2712 + if (!posEq(sel.from, sel.to)) replaceRange(this.doc, "", sel.from, sel.to, "+delete");
2713 + else replaceRange(this.doc, "", sel.from, findPosH(this.doc, dir, unit, false), "+delete");
2714 + this.curOp.userSelChange = true;
2715 + }),
2716 +
2717 + moveV: operation(null, function(dir, unit) {
2718 + var doc = this.doc, display = this.display;
2719 + var cur = doc.sel.head, pos = cursorCoords(this, cur, "div");
2720 + var x = pos.left, y;
2721 + if (doc.sel.goalColumn != null) x = doc.sel.goalColumn;
2722 + if (unit == "page") {
2723 + var pageSize = Math.min(display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
2724 + y = pos.top + dir * pageSize;
2725 + } else if (unit == "line") {
2726 + y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
2727 + }
2728 + do {
2729 + var target = coordsChar(this, x, y);
2730 + y += dir * 5;
2731 + } while (target.outside && (dir < 0 ? y > 0 : y < doc.height));
2732 +
2733 + if (unit == "page") display.scrollbarV.scrollTop += charCoords(this, target, "div").top - pos.top;
2734 + extendSelection(this.doc, target, target, dir);
2735 + doc.sel.goalColumn = x;
2736 + }),
2737 +
2738 + toggleOverwrite: function() {
2739 + if (this.state.overwrite = !this.state.overwrite)
2740 + this.display.cursor.className += " CodeMirror-overwrite";
2741 + else
2742 + this.display.cursor.className = this.display.cursor.className.replace(" CodeMirror-overwrite", "");
2743 + },
2744 +
2745 + scrollTo: operation(null, function(x, y) {
2746 + this.curOp.updateScrollPos = {scrollLeft: x, scrollTop: y};
2747 + }),
2748 + getScrollInfo: function() {
2749 + var scroller = this.display.scroller, co = scrollerCutOff;
2750 + return {left: scroller.scrollLeft, top: scroller.scrollTop,
2751 + height: scroller.scrollHeight - co, width: scroller.scrollWidth - co,
2752 + clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co};
2753 + },
2754 +
2755 + scrollIntoView: function(pos) {
2756 + if (typeof pos == "number") pos = Pos(pos, 0);
2757 + if (!pos || pos.line != null) {
2758 + pos = pos ? clipPos(this.doc, pos) : this.doc.sel.head;
2759 + scrollPosIntoView(this, pos);
2760 + } else {
2761 + scrollIntoView(this, pos.left, pos.top, pos.right, pos.bottom);
2762 + }
2763 + },
2764 +
2765 + setSize: function(width, height) {
2766 + function interpret(val) {
2767 + return typeof val == "number" || /^\d+$/.test(String(val)) ? val + "px" : val;
2768 + }
2769 + if (width != null) this.display.wrapper.style.width = interpret(width);
2770 + if (height != null) this.display.wrapper.style.height = interpret(height);
2771 + this.refresh();
2772 + },
2773 +
2774 + on: function(type, f) {on(this, type, f);},
2775 + off: function(type, f) {off(this, type, f);},
2776 +
2777 + operation: function(f){return runInOp(this, f);},
2778 +
2779 + refresh: operation(null, function() {
2780 + clearCaches(this);
2781 + this.curOp.updateScrollPos = {scrollTop: this.doc.scrollTop, scrollLeft: this.doc.scrollLeft};
2782 + regChange(this);
2783 + }),
2784 +
2785 + swapDoc: operation(null, function(doc) {
2786 + var old = this.doc;
2787 + old.cm = null;
2788 + attachDoc(this, doc);
2789 + clearCaches(this);
2790 + this.curOp.updateScrollPos = {scrollTop: doc.scrollTop, scrollLeft: doc.scrollLeft};
2791 + return old;
2792 + }),
2793 +
2794 + getInputField: function(){return this.display.input;},
2795 + getWrapperElement: function(){return this.display.wrapper;},
2796 + getScrollerElement: function(){return this.display.scroller;},
2797 + getGutterElement: function(){return this.display.gutters;}
2798 + };
2799 +
2800 + // OPTION DEFAULTS
2801 +
2802 + var optionHandlers = CodeMirror.optionHandlers = {};
2803 +
2804 + // The default configuration options.
2805 + var defaults = CodeMirror.defaults = {};
2806 +
2807 + function option(name, deflt, handle, notOnInit) {
2808 + CodeMirror.defaults[name] = deflt;
2809 + if (handle) optionHandlers[name] =
2810 + notOnInit ? function(cm, val, old) {if (old != Init) handle(cm, val, old);} : handle;
2811 + }
2812 +
2813 + var Init = CodeMirror.Init = {toString: function(){return "CodeMirror.Init";}};
2814 +
2815 + // These two are, on init, called from the constructor because they
2816 + // have to be initialized before the editor can start at all.
2817 + option("value", "", function(cm, val) {
2818 + cm.setValue(val);
2819 + }, true);
2820 + option("mode", null, function(cm, val) {
2821 + cm.doc.modeOption = val;
2822 + loadMode(cm);
2823 + }, true);
2824 +
2825 + option("indentUnit", 2, loadMode, true);
2826 + option("indentWithTabs", false);
2827 + option("smartIndent", true);
2828 + option("tabSize", 4, function(cm) {
2829 + loadMode(cm);
2830 + clearCaches(cm);
2831 + regChange(cm);
2832 + }, true);
2833 + option("electricChars", true);
2834 + option("rtlMoveVisually", !windows);
2835 +
2836 + option("theme", "default", function(cm) {
2837 + themeChanged(cm);
2838 + guttersChanged(cm);
2839 + }, true);
2840 + option("keyMap", "default", keyMapChanged);
2841 + option("extraKeys", null);
2842 +
2843 + option("onKeyEvent", null);
2844 + option("onDragEvent", null);
2845 +
2846 + option("lineWrapping", false, wrappingChanged, true);
2847 + option("gutters", [], function(cm) {
2848 + setGuttersForLineNumbers(cm.options);
2849 + guttersChanged(cm);
2850 + }, true);
2851 + option("fixedGutter", true, function(cm, val) {
2852 + cm.display.gutters.style.left = val ? compensateForHScroll(cm.display) + "px" : "0";
2853 + cm.refresh();
2854 + }, true);
2855 + option("lineNumbers", false, function(cm) {
2856 + setGuttersForLineNumbers(cm.options);
2857 + guttersChanged(cm);
2858 + }, true);
2859 + option("firstLineNumber", 1, guttersChanged, true);
2860 + option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true);
2861 + option("showCursorWhenSelecting", false, updateSelection, true);
2862 +
2863 + option("readOnly", false, function(cm, val) {
2864 + if (val == "nocursor") {onBlur(cm); cm.display.input.blur();}
2865 + else if (!val) resetInput(cm, true);
2866 + });
2867 + option("dragDrop", true);
2868 +
2869 + option("cursorBlinkRate", 530);
2870 + option("cursorHeight", 1);
2871 + option("workTime", 100);
2872 + option("workDelay", 100);
2873 + option("flattenSpans", true);
2874 + option("pollInterval", 100);
2875 + option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;});
2876 + option("viewportMargin", 10, function(cm){cm.refresh();}, true);
2877 +
2878 + option("tabindex", null, function(cm, val) {
2879 + cm.display.input.tabIndex = val || "";
2880 + });
2881 + option("autofocus", null);
2882 +
2883 + // MODE DEFINITION AND QUERYING
2884 +
2885 + // Known modes, by name and by MIME
2886 + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
2887 +
2888 + CodeMirror.defineMode = function(name, mode) {
2889 + if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
2890 + if (arguments.length > 2) {
2891 + mode.dependencies = [];
2892 + for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
2893 + }
2894 + modes[name] = mode;
2895 + };
2896 +
2897 + CodeMirror.defineMIME = function(mime, spec) {
2898 + mimeModes[mime] = spec;
2899 + };
2900 +
2901 + CodeMirror.resolveMode = function(spec) {
2902 + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
2903 + spec = mimeModes[spec];
2904 + else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
2905 + return CodeMirror.resolveMode("application/xml");
2906 + if (typeof spec == "string") return {name: spec};
2907 + else return spec || {name: "null"};
2908 + };
2909 +
2910 + CodeMirror.getMode = function(options, spec) {
2911 + spec = CodeMirror.resolveMode(spec);
2912 + var mfactory = modes[spec.name];
2913 + if (!mfactory) return CodeMirror.getMode(options, "text/plain");
2914 + var modeObj = mfactory(options, spec);
2915 + if (modeExtensions.hasOwnProperty(spec.name)) {
2916 + var exts = modeExtensions[spec.name];
2917 + for (var prop in exts) {
2918 + if (!exts.hasOwnProperty(prop)) continue;
2919 + if (modeObj.hasOwnProperty(prop)) modeObj["_" + prop] = modeObj[prop];
2920 + modeObj[prop] = exts[prop];
2921 + }
2922 + }
2923 + modeObj.name = spec.name;
2924 + return modeObj;
2925 + };
2926 +
2927 + CodeMirror.defineMode("null", function() {
2928 + return {token: function(stream) {stream.skipToEnd();}};
2929 + });
2930 + CodeMirror.defineMIME("text/plain", "null");
2931 +
2932 + var modeExtensions = CodeMirror.modeExtensions = {};
2933 + CodeMirror.extendMode = function(mode, properties) {
2934 + var exts = modeExtensions.hasOwnProperty(mode) ? modeExtensions[mode] : (modeExtensions[mode] = {});
2935 + copyObj(properties, exts);
2936 + };
2937 +
2938 + // EXTENSIONS
2939 +
2940 + CodeMirror.defineExtension = function(name, func) {
2941 + CodeMirror.prototype[name] = func;
2942 + };
2943 +
2944 + CodeMirror.defineOption = option;
2945 +
2946 + var initHooks = [];
2947 + CodeMirror.defineInitHook = function(f) {initHooks.push(f);};
2948 +
2949 + // MODE STATE HANDLING
2950 +
2951 + // Utility functions for working with state. Exported because modes
2952 + // sometimes need to do this.
2953 + function copyState(mode, state) {
2954 + if (state === true) return state;
2955 + if (mode.copyState) return mode.copyState(state);
2956 + var nstate = {};
2957 + for (var n in state) {
2958 + var val = state[n];
2959 + if (val instanceof Array) val = val.concat([]);
2960 + nstate[n] = val;
2961 + }
2962 + return nstate;
2963 + }
2964 + CodeMirror.copyState = copyState;
2965 +
2966 + function startState(mode, a1, a2) {
2967 + return mode.startState ? mode.startState(a1, a2) : true;
2968 + }
2969 + CodeMirror.startState = startState;
2970 +
2971 + CodeMirror.innerMode = function(mode, state) {
2972 + while (mode.innerMode) {
2973 + var info = mode.innerMode(state);
2974 + state = info.state;
2975 + mode = info.mode;
2976 + }
2977 + return info || {mode: mode, state: state};
2978 + };
2979 +
2980 + // STANDARD COMMANDS
2981 +
2982 + var commands = CodeMirror.commands = {
2983 + selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()));},
2984 + killLine: function(cm) {
2985 + var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
2986 + if (!sel && cm.getLine(from.line).length == from.ch)
2987 + cm.replaceRange("", from, Pos(from.line + 1, 0), "+delete");
2988 + else cm.replaceRange("", from, sel ? to : Pos(from.line), "+delete");
2989 + },
2990 + deleteLine: function(cm) {
2991 + var l = cm.getCursor().line;
2992 + cm.replaceRange("", Pos(l, 0), Pos(l), "+delete");
2993 + },
2994 + undo: function(cm) {cm.undo();},
2995 + redo: function(cm) {cm.redo();},
2996 + goDocStart: function(cm) {cm.extendSelection(Pos(cm.firstLine(), 0));},
2997 + goDocEnd: function(cm) {cm.extendSelection(Pos(cm.lastLine()));},
2998 + goLineStart: function(cm) {
2999 + cm.extendSelection(lineStart(cm, cm.getCursor().line));
3000 + },
3001 + goLineStartSmart: function(cm) {
3002 + var cur = cm.getCursor(), start = lineStart(cm, cur.line);
3003 + var line = cm.getLineHandle(start.line);
3004 + var order = getOrder(line);
3005 + if (!order || order[0].level == 0) {
3006 + var firstNonWS = Math.max(0, line.text.search(/\S/));
3007 + var inWS = cur.line == start.line && cur.ch <= firstNonWS && cur.ch;
3008 + cm.extendSelection(Pos(start.line, inWS ? 0 : firstNonWS));
3009 + } else cm.extendSelection(start);
3010 + },
3011 + goLineEnd: function(cm) {
3012 + cm.extendSelection(lineEnd(cm, cm.getCursor().line));
3013 + },
3014 + goLineUp: function(cm) {cm.moveV(-1, "line");},
3015 + goLineDown: function(cm) {cm.moveV(1, "line");},
3016 + goPageUp: function(cm) {cm.moveV(-1, "page");},
3017 + goPageDown: function(cm) {cm.moveV(1, "page");},
3018 + goCharLeft: function(cm) {cm.moveH(-1, "char");},
3019 + goCharRight: function(cm) {cm.moveH(1, "char");},
3020 + goColumnLeft: function(cm) {cm.moveH(-1, "column");},
3021 + goColumnRight: function(cm) {cm.moveH(1, "column");},
3022 + goWordLeft: function(cm) {cm.moveH(-1, "word");},
3023 + goWordRight: function(cm) {cm.moveH(1, "word");},
3024 + delCharBefore: function(cm) {cm.deleteH(-1, "char");},
3025 + delCharAfter: function(cm) {cm.deleteH(1, "char");},
3026 + delWordBefore: function(cm) {cm.deleteH(-1, "word");},
3027 + delWordAfter: function(cm) {cm.deleteH(1, "word");},
3028 + indentAuto: function(cm) {cm.indentSelection("smart");},
3029 + indentMore: function(cm) {cm.indentSelection("add");},
3030 + indentLess: function(cm) {cm.indentSelection("subtract");},
3031 + insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");},
3032 + defaultTab: function(cm) {
3033 + if (cm.somethingSelected()) cm.indentSelection("add");
3034 + else cm.replaceSelection("\t", "end", "+input");
3035 + },
3036 + transposeChars: function(cm) {
3037 + var cur = cm.getCursor(), line = cm.getLine(cur.line);
3038 + if (cur.ch > 0 && cur.ch < line.length - 1)
3039 + cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
3040 + Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
3041 + },
3042 + newlineAndIndent: function(cm) {
3043 + operation(cm, function() {
3044 + cm.replaceSelection("\n", "end", "+input");
3045 + cm.indentLine(cm.getCursor().line, null, true);
3046 + })();
3047 + },
3048 + toggleOverwrite: function(cm) {cm.toggleOverwrite();}
3049 + };
3050 +
3051 + // STANDARD KEYMAPS
3052 +
3053 + var keyMap = CodeMirror.keyMap = {};
3054 + keyMap.basic = {
3055 + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
3056 + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
3057 + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
3058 + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
3059 + };
3060 + // Note that the save and find-related commands aren't defined by
3061 + // default. Unknown commands are simply ignored.
3062 + keyMap.pcDefault = {
3063 + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
3064 + "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
3065 + "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
3066 + "Ctrl-Backspace": "delWordBefore", "Ctrl-Delete": "delWordAfter", "Ctrl-S": "save", "Ctrl-F": "find",
3067 + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
3068 + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
3069 + fallthrough: "basic"
3070 + };
3071 + keyMap.macDefault = {
3072 + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
3073 + "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
3074 + "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordBefore",
3075 + "Ctrl-Alt-Backspace": "delWordAfter", "Alt-Delete": "delWordAfter", "Cmd-S": "save", "Cmd-F": "find",
3076 + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
3077 + "Cmd-[": "indentLess", "Cmd-]": "indentMore",
3078 + fallthrough: ["basic", "emacsy"]
3079 + };
3080 + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
3081 + keyMap.emacsy = {
3082 + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
3083 + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
3084 + "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
3085 + "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
3086 + };
3087 +
3088 + // KEYMAP DISPATCH
3089 +
3090 + function getKeyMap(val) {
3091 + if (typeof val == "string") return keyMap[val];
3092 + else return val;
3093 + }
3094 +
3095 + function lookupKey(name, maps, handle) {
3096 + function lookup(map) {
3097 + map = getKeyMap(map);
3098 + var found = map[name];
3099 + if (found === false) return "stop";
3100 + if (found != null) {
3101 + try { var done = handle(found); }
3102 + catch (e) { if (e != Pass) throw e; }
3103 + if (done) return true;
3104 + }
3105 + if (map.nofallthrough) return "stop";
3106 +
3107 + var fallthrough = map.fallthrough;
3108 + if (fallthrough == null) return false;
3109 + if (Object.prototype.toString.call(fallthrough) != "[object Array]")
3110 + return lookup(fallthrough);
3111 + for (var i = 0, e = fallthrough.length; i < e; ++i) {
3112 + var done = lookup(fallthrough[i]);
3113 + if (done) return done;
3114 + }
3115 + return false;
3116 + }
3117 +
3118 + for (var i = 0; i < maps.length; ++i) {
3119 + var done = lookup(maps[i]);
3120 + if (done) return done;
3121 + }
3122 + }
3123 + function isModifierKey(event) {
3124 + var name = keyNames[e_prop(event, "keyCode")];
3125 + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
3126 + }
3127 + function keyName(event, noShift) {
3128 + var name = keyNames[e_prop(event, "keyCode")];
3129 + if (name == null || event.altGraphKey) return false;
3130 + if (e_prop(event, "altKey")) name = "Alt-" + name;
3131 + if (e_prop(event, flipCtrlCmd ? "metaKey" : "ctrlKey")) name = "Ctrl-" + name;
3132 + if (e_prop(event, flipCtrlCmd ? "ctrlKey" : "metaKey")) name = "Cmd-" + name;
3133 + if (!noShift && e_prop(event, "shiftKey")) name = "Shift-" + name;
3134 + return name;
3135 + }
3136 + CodeMirror.lookupKey = lookupKey;
3137 + CodeMirror.isModifierKey = isModifierKey;
3138 + CodeMirror.keyName = keyName;
3139 +
3140 + // FROMTEXTAREA
3141 +
3142 + CodeMirror.fromTextArea = function(textarea, options) {
3143 + if (!options) options = {};
3144 + options.value = textarea.value;
3145 + if (!options.tabindex && textarea.tabindex)
3146 + options.tabindex = textarea.tabindex;
3147 + // Set autofocus to true if this textarea is focused, or if it has
3148 + // autofocus and no other element is focused.
3149 + if (options.autofocus == null) {
3150 + var hasFocus = document.body;
3151 + // doc.activeElement occasionally throws on IE
3152 + try { hasFocus = document.activeElement; } catch(e) {}
3153 + options.autofocus = hasFocus == textarea ||
3154 + textarea.getAttribute("autofocus") != null && hasFocus == document.body;
3155 + }
3156 +
3157 + function save() {textarea.value = cm.getValue();}
3158 + if (textarea.form) {
3159 + // Deplorable hack to make the submit method do the right thing.
3160 + on(textarea.form, "submit", save);
3161 + var form = textarea.form, realSubmit = form.submit;
3162 + try {
3163 + var wrappedSubmit = form.submit = function() {
3164 + save();
3165 + form.submit = realSubmit;
3166 + form.submit();
3167 + form.submit = wrappedSubmit;
3168 + };
3169 + } catch(e) {}
3170 + }
3171 +
3172 + textarea.style.display = "none";
3173 + var cm = CodeMirror(function(node) {
3174 + textarea.parentNode.insertBefore(node, textarea.nextSibling);
3175 + }, options);
3176 + cm.save = save;
3177 + cm.getTextArea = function() { return textarea; };
3178 + cm.toTextArea = function() {
3179 + save();
3180 + textarea.parentNode.removeChild(cm.getWrapperElement());
3181 + textarea.style.display = "";
3182 + if (textarea.form) {
3183 + off(textarea.form, "submit", save);
3184 + if (typeof textarea.form.submit == "function")
3185 + textarea.form.submit = realSubmit;
3186 + }
3187 + };
3188 + return cm;
3189 + };
3190 +
3191 + // STRING STREAM
3192 +
3193 + // Fed to the mode parsers, provides helper functions to make
3194 + // parsers more succinct.
3195 +
3196 + // The character stream used by a mode's parser.
3197 + function StringStream(string, tabSize) {
3198 + this.pos = this.start = 0;
3199 + this.string = string;
3200 + this.tabSize = tabSize || 8;
3201 + }
3202 +
3203 + StringStream.prototype = {
3204 + eol: function() {return this.pos >= this.string.length;},
3205 + sol: function() {return this.pos == 0;},
3206 + peek: function() {return this.string.charAt(this.pos) || undefined;},
3207 + next: function() {
3208 + if (this.pos < this.string.length)
3209 + return this.string.charAt(this.pos++);
3210 + },
3211 + eat: function(match) {
3212 + var ch = this.string.charAt(this.pos);
3213 + if (typeof match == "string") var ok = ch == match;
3214 + else var ok = ch && (match.test ? match.test(ch) : match(ch));
3215 + if (ok) {++this.pos; return ch;}
3216 + },
3217 + eatWhile: function(match) {
3218 + var start = this.pos;
3219 + while (this.eat(match)){}
3220 + return this.pos > start;
3221 + },
3222 + eatSpace: function() {
3223 + var start = this.pos;
3224 + while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
3225 + return this.pos > start;
3226 + },
3227 + skipToEnd: function() {this.pos = this.string.length;},
3228 + skipTo: function(ch) {
3229 + var found = this.string.indexOf(ch, this.pos);
3230 + if (found > -1) {this.pos = found; return true;}
3231 + },
3232 + backUp: function(n) {this.pos -= n;},
3233 + column: function() {return countColumn(this.string, this.start, this.tabSize);},
3234 + indentation: function() {return countColumn(this.string, null, this.tabSize);},
3235 + match: function(pattern, consume, caseInsensitive) {
3236 + if (typeof pattern == "string") {
3237 + var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;};
3238 + if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
3239 + if (consume !== false) this.pos += pattern.length;
3240 + return true;
3241 + }
3242 + } else {
3243 + var match = this.string.slice(this.pos).match(pattern);
3244 + if (match && match.index > 0) return null;
3245 + if (match && consume !== false) this.pos += match[0].length;
3246 + return match;
3247 + }
3248 + },
3249 + current: function(){return this.string.slice(this.start, this.pos);}
3250 + };
3251 + CodeMirror.StringStream = StringStream;
3252 +
3253 + // TEXTMARKERS
3254 +
3255 + function TextMarker(doc, type) {
3256 + this.lines = [];
3257 + this.type = type;
3258 + this.doc = doc;
3259 + }
3260 + CodeMirror.TextMarker = TextMarker;
3261 +
3262 + TextMarker.prototype.clear = function() {
3263 + if (this.explicitlyCleared) return;
3264 + var cm = this.doc.cm, withOp = cm && !cm.curOp;
3265 + if (withOp) startOperation(cm);
3266 + var min = null, max = null;
3267 + for (var i = 0; i < this.lines.length; ++i) {
3268 + var line = this.lines[i];
3269 + var span = getMarkedSpanFor(line.markedSpans, this);
3270 + if (span.to != null) max = lineNo(line);
3271 + line.markedSpans = removeMarkedSpan(line.markedSpans, span);
3272 + if (span.from != null)
3273 + min = lineNo(line);
3274 + else if (this.collapsed && !lineIsHidden(this.doc, line) && cm)
3275 + updateLineHeight(line, textHeight(cm.display));
3276 + }
3277 + if (cm && this.collapsed && !cm.options.lineWrapping) for (var i = 0; i < this.lines.length; ++i) {
3278 + var visual = visualLine(cm.doc, this.lines[i]), len = lineLength(cm.doc, visual);
3279 + if (len > cm.display.maxLineLength) {
3280 + cm.display.maxLine = visual;
3281 + cm.display.maxLineLength = len;
3282 + cm.display.maxLineChanged = true;
3283 + }
3284 + }
3285 +
3286 + if (min != null && cm) regChange(cm, min, max + 1);
3287 + this.lines.length = 0;
3288 + this.explicitlyCleared = true;
3289 + if (this.collapsed && this.doc.cantEdit) {
3290 + this.doc.cantEdit = false;
3291 + if (cm) reCheckSelection(cm);
3292 + }
3293 + if (withOp) endOperation(cm);
3294 + signalLater(this, "clear");
3295 + };
3296 +
3297 + TextMarker.prototype.find = function() {
3298 + var from, to;
3299 + for (var i = 0; i < this.lines.length; ++i) {
3300 + var line = this.lines[i];
3301 + var span = getMarkedSpanFor(line.markedSpans, this);
3302 + if (span.from != null || span.to != null) {
3303 + var found = lineNo(line);
3304 + if (span.from != null) from = Pos(found, span.from);
3305 + if (span.to != null) to = Pos(found, span.to);
3306 + }
3307 + }
3308 + if (this.type == "bookmark") return from;
3309 + return from && {from: from, to: to};
3310 + };
3311 +
3312 + TextMarker.prototype.getOptions = function(copyWidget) {
3313 + var repl = this.replacedWith;
3314 + return {className: this.className,
3315 + inclusiveLeft: this.inclusiveLeft, inclusiveRight: this.inclusiveRight,
3316 + atomic: this.atomic,
3317 + collapsed: this.collapsed,
3318 + clearOnEnter: this.clearOnEnter,
3319 + replacedWith: copyWidget ? repl && repl.cloneNode(true) : repl,
3320 + readOnly: this.readOnly,
3321 + startStyle: this.startStyle, endStyle: this.endStyle};
3322 + };
3323 +
3324 + TextMarker.prototype.attachLine = function(line) {
3325 + if (!this.lines.length && this.doc.cm) {
3326 + var op = this.doc.cm.curOp;
3327 + if (!op.maybeHiddenMarkers || indexOf(op.maybeHiddenMarkers, this) == -1)
3328 + (op.maybeUnhiddenMarkers || (op.maybeUnhiddenMarkers = [])).push(this);
3329 + }
3330 + this.lines.push(line);
3331 + };
3332 + TextMarker.prototype.detachLine = function(line) {
3333 + this.lines.splice(indexOf(this.lines, line), 1);
3334 + if (!this.lines.length && this.doc.cm) {
3335 + var op = this.doc.cm.curOp;
3336 + (op.maybeHiddenMarkers || (op.maybeHiddenMarkers = [])).push(this);
3337 + }
3338 + };
3339 +
3340 + function markText(doc, from, to, options, type) {
3341 + if (options && options.shared) return markTextShared(doc, from, to, options, type);
3342 + if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type);
3343 +
3344 + var marker = new TextMarker(doc, type);
3345 + if (type == "range" && !posLess(from, to)) return marker;
3346 + if (options) copyObj(options, marker);
3347 + if (marker.replacedWith) {
3348 + marker.collapsed = true;
3349 + marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget");
3350 + }
3351 + if (marker.collapsed) sawCollapsedSpans = true;
3352 +
3353 + var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine;
3354 + doc.iter(curLine, to.line + 1, function(line) {
3355 + if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine)
3356 + updateMaxLine = true;
3357 + var span = {from: null, to: null, marker: marker};
3358 + size += line.text.length;
3359 + if (curLine == from.line) {span.from = from.ch; size -= from.ch;}
3360 + if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;}
3361 + if (marker.collapsed) {
3362 + if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch);
3363 + if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch);
3364 + else updateLineHeight(line, 0);
3365 + }
3366 + addMarkedSpan(line, span);
3367 + ++curLine;
3368 + });
3369 + if (marker.collapsed) doc.iter(from.line, to.line + 1, function(line) {
3370 + if (lineIsHidden(doc, line)) updateLineHeight(line, 0);
3371 + });
3372 +
3373 + if (marker.readOnly) {
3374 + sawReadOnlySpans = true;
3375 + if (doc.history.done.length || doc.history.undone.length)
3376 + doc.clearHistory();
3377 + }
3378 + if (marker.collapsed) {
3379 + if (collapsedAtStart != collapsedAtEnd)
3380 + throw new Error("Inserting collapsed marker overlapping an existing one");
3381 + marker.size = size;
3382 + marker.atomic = true;
3383 + }
3384 + if (cm) {
3385 + if (updateMaxLine) cm.curOp.updateMaxLine = true;
3386 + if (marker.className || marker.startStyle || marker.endStyle || marker.collapsed)
3387 + regChange(cm, from.line, to.line + 1);
3388 + if (marker.atomic) reCheckSelection(cm);
3389 + }
3390 + return marker;
3391 + }
3392 +
3393 + // SHARED TEXTMARKERS
3394 +
3395 + function SharedTextMarker(markers, primary) {
3396 + this.markers = markers;
3397 + this.primary = primary;
3398 + for (var i = 0, me = this; i < markers.length; ++i) {
3399 + markers[i].parent = this;
3400 + on(markers[i], "clear", function(){me.clear();});
3401 + }
3402 + }
3403 + CodeMirror.SharedTextMarker = SharedTextMarker;
3404 +
3405 + SharedTextMarker.prototype.clear = function() {
3406 + if (this.explicitlyCleared) return;
3407 + this.explicitlyCleared = true;
3408 + for (var i = 0; i < this.markers.length; ++i)
3409 + this.markers[i].clear();
3410 + signalLater(this, "clear");
3411 + };
3412 + SharedTextMarker.prototype.find = function() {
3413 + return this.primary.find();
3414 + };
3415 + SharedTextMarker.prototype.getOptions = function(copyWidget) {
3416 + var inner = this.primary.getOptions(copyWidget);
3417 + inner.shared = true;
3418 + return inner;
3419 + };
3420 +
3421 + function markTextShared(doc, from, to, options, type) {
3422 + options = copyObj(options);
3423 + options.shared = false;
3424 + var markers = [markText(doc, from, to, options, type)], primary = markers[0];
3425 + linkedDocs(doc, function(doc) {
3426 + markers.push(markText(doc, clipPos(doc, from), clipPos(doc, to), options, type));
3427 + for (var i = 0; i < doc.linked.length; ++i)
3428 + if (doc.linked[i].isParent) return;
3429 + primary = lst(markers);
3430 + });
3431 + return new SharedTextMarker(markers, primary);
3432 + }
3433 +
3434 + // TEXTMARKER SPANS
3435 +
3436 + function getMarkedSpanFor(spans, marker) {
3437 + if (spans) for (var i = 0; i < spans.length; ++i) {
3438 + var span = spans[i];
3439 + if (span.marker == marker) return span;
3440 + }
3441 + }
3442 + function removeMarkedSpan(spans, span) {
3443 + for (var r, i = 0; i < spans.length; ++i)
3444 + if (spans[i] != span) (r || (r = [])).push(spans[i]);
3445 + return r;
3446 + }
3447 + function addMarkedSpan(line, span) {
3448 + line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
3449 + span.marker.attachLine(line);
3450 + }
3451 +
3452 + function markedSpansBefore(old, startCh, isInsert) {
3453 + if (old) for (var i = 0, nw; i < old.length; ++i) {
3454 + var span = old[i], marker = span.marker;
3455 + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh);
3456 + if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) {
3457 + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh);
3458 + (nw || (nw = [])).push({from: span.from,
3459 + to: endsAfter ? null : span.to,
3460 + marker: marker});
3461 + }
3462 + }
3463 + return nw;
3464 + }
3465 +
3466 + function markedSpansAfter(old, endCh, isInsert) {
3467 + if (old) for (var i = 0, nw; i < old.length; ++i) {
3468 + var span = old[i], marker = span.marker;
3469 + var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh);
3470 + if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) {
3471 + var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh);
3472 + (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh,
3473 + to: span.to == null ? null : span.to - endCh,
3474 + marker: marker});
3475 + }
3476 + }
3477 + return nw;
3478 + }
3479 +
3480 + function stretchSpansOverChange(doc, change) {
3481 + var oldFirst = isLine(doc, change.from.line) && getLine(doc, change.from.line).markedSpans;
3482 + var oldLast = isLine(doc, change.to.line) && getLine(doc, change.to.line).markedSpans;
3483 + if (!oldFirst && !oldLast) return null;
3484 +
3485 + var startCh = change.from.ch, endCh = change.to.ch, isInsert = posEq(change.from, change.to);
3486 + // Get the spans that 'stick out' on both sides
3487 + var first = markedSpansBefore(oldFirst, startCh, isInsert);
3488 + var last = markedSpansAfter(oldLast, endCh, isInsert);
3489 +
3490 + // Next, merge those two ends
3491 + var sameLine = change.text.length == 1, offset = lst(change.text).length + (sameLine ? startCh : 0);
3492 + if (first) {
3493 + // Fix up .to properties of first
3494 + for (var i = 0; i < first.length; ++i) {
3495 + var span = first[i];
3496 + if (span.to == null) {
3497 + var found = getMarkedSpanFor(last, span.marker);
3498 + if (!found) span.to = startCh;
3499 + else if (sameLine) span.to = found.to == null ? null : found.to + offset;
3500 + }
3501 + }
3502 + }
3503 + if (last) {
3504 + // Fix up .from in last (or move them into first in case of sameLine)
3505 + for (var i = 0; i < last.length; ++i) {
3506 + var span = last[i];
3507 + if (span.to != null) span.to += offset;
3508 + if (span.from == null) {
3509 + var found = getMarkedSpanFor(first, span.marker);
3510 + if (!found) {
3511 + span.from = offset;
3512 + if (sameLine) (first || (first = [])).push(span);
3513 + }
3514 + } else {
3515 + span.from += offset;
3516 + if (sameLine) (first || (first = [])).push(span);
3517 + }
3518 + }
3519 + }
3520 +
3521 + var newMarkers = [first];
3522 + if (!sameLine) {
3523 + // Fill gap with whole-line-spans
3524 + var gap = change.text.length - 2, gapMarkers;
3525 + if (gap > 0 && first)
3526 + for (var i = 0; i < first.length; ++i)
3527 + if (first[i].to == null)
3528 + (gapMarkers || (gapMarkers = [])).push({from: null, to: null, marker: first[i].marker});
3529 + for (var i = 0; i < gap; ++i)
3530 + newMarkers.push(gapMarkers);
3531 + newMarkers.push(last);
3532 + }
3533 + return newMarkers;
3534 + }
3535 +
3536 + function mergeOldSpans(doc, change) {
3537 + var old = getOldSpans(doc, change);
3538 + var stretched = stretchSpansOverChange(doc, change);
3539 + if (!old) return stretched;
3540 + if (!stretched) return old;
3541 +
3542 + for (var i = 0; i < old.length; ++i) {
3543 + var oldCur = old[i], stretchCur = stretched[i];
3544 + if (oldCur && stretchCur) {
3545 + spans: for (var j = 0; j < stretchCur.length; ++j) {
3546 + var span = stretchCur[j];
3547 + for (var k = 0; k < oldCur.length; ++k)
3548 + if (oldCur[k].marker == span.marker) continue spans;
3549 + oldCur.push(span);
3550 + }
3551 + } else if (stretchCur) {
3552 + old[i] = stretchCur;
3553 + }
3554 + }
3555 + return old;
3556 + }
3557 +
3558 + function removeReadOnlyRanges(doc, from, to) {
3559 + var markers = null;
3560 + doc.iter(from.line, to.line + 1, function(line) {
3561 + if (line.markedSpans) for (var i = 0; i < line.markedSpans.length; ++i) {
3562 + var mark = line.markedSpans[i].marker;
3563 + if (mark.readOnly && (!markers || indexOf(markers, mark) == -1))
3564 + (markers || (markers = [])).push(mark);
3565 + }
3566 + });
3567 + if (!markers) return null;
3568 + var parts = [{from: from, to: to}];
3569 + for (var i = 0; i < markers.length; ++i) {
3570 + var mk = markers[i], m = mk.find();
3571 + for (var j = 0; j < parts.length; ++j) {
3572 + var p = parts[j];
3573 + if (posLess(p.to, m.from) || posLess(m.to, p.from)) continue;
3574 + var newParts = [j, 1];
3575 + if (posLess(p.from, m.from) || !mk.inclusiveLeft && posEq(p.from, m.from))
3576 + newParts.push({from: p.from, to: m.from});
3577 + if (posLess(m.to, p.to) || !mk.inclusiveRight && posEq(p.to, m.to))
3578 + newParts.push({from: m.to, to: p.to});
3579 + parts.splice.apply(parts, newParts);
3580 + j += newParts.length - 1;
3581 + }
3582 + }
3583 + return parts;
3584 + }
3585 +
3586 + function collapsedSpanAt(line, ch) {
3587 + var sps = sawCollapsedSpans && line.markedSpans, found;
3588 + if (sps) for (var sp, i = 0; i < sps.length; ++i) {
3589 + sp = sps[i];
3590 + if (!sp.marker.collapsed) continue;
3591 + if ((sp.from == null || sp.from < ch) &&
3592 + (sp.to == null || sp.to > ch) &&
3593 + (!found || found.width < sp.marker.width))
3594 + found = sp.marker;
3595 + }
3596 + return found;
3597 + }
3598 + function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); }
3599 + function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); }
3600 +
3601 + function visualLine(doc, line) {
3602 + var merged;
3603 + while (merged = collapsedSpanAtStart(line))
3604 + line = getLine(doc, merged.find().from.line);
3605 + return line;
3606 + }
3607 +
3608 + function lineIsHidden(doc, line) {
3609 + var sps = sawCollapsedSpans && line.markedSpans;
3610 + if (sps) for (var sp, i = 0; i < sps.length; ++i) {
3611 + sp = sps[i];
3612 + if (!sp.marker.collapsed) continue;
3613 + if (sp.from == null) return true;
3614 + if (sp.from == 0 && sp.marker.inclusiveLeft && lineIsHiddenInner(doc, line, sp))
3615 + return true;
3616 + }
3617 + }
3618 + function lineIsHiddenInner(doc, line, span) {
3619 + if (span.to == null) {
3620 + var end = span.marker.find().to, endLine = getLine(doc, end.line);
3621 + return lineIsHiddenInner(doc, endLine, getMarkedSpanFor(endLine.markedSpans, span.marker));
3622 + }
3623 + if (span.marker.inclusiveRight && span.to == line.text.length)
3624 + return true;
3625 + for (var sp, i = 0; i < line.markedSpans.length; ++i) {
3626 + sp = line.markedSpans[i];
3627 + if (sp.marker.collapsed && sp.from == span.to &&
3628 + (sp.marker.inclusiveLeft || span.marker.inclusiveRight) &&
3629 + lineIsHiddenInner(doc, line, sp)) return true;
3630 + }
3631 + }
3632 +
3633 + function detachMarkedSpans(line) {
3634 + var spans = line.markedSpans;
3635 + if (!spans) return;
3636 + for (var i = 0; i < spans.length; ++i)
3637 + spans[i].marker.detachLine(line);
3638 + line.markedSpans = null;
3639 + }
3640 +
3641 + function attachMarkedSpans(line, spans) {
3642 + if (!spans) return;
3643 + for (var i = 0; i < spans.length; ++i)
3644 + spans[i].marker.attachLine(line);
3645 + line.markedSpans = spans;
3646 + }
3647 +
3648 + // LINE WIDGETS
3649 +
3650 + var LineWidget = CodeMirror.LineWidget = function(cm, node, options) {
3651 + for (var opt in options) if (options.hasOwnProperty(opt))
3652 + this[opt] = options[opt];
3653 + this.cm = cm;
3654 + this.node = node;
3655 + };
3656 + function widgetOperation(f) {
3657 + return function() {
3658 + var withOp = !this.cm.curOp;
3659 + if (withOp) startOperation(this.cm);
3660 + try {var result = f.apply(this, arguments);}
3661 + finally {if (withOp) endOperation(this.cm);}
3662 + return result;
3663 + };
3664 + }
3665 + LineWidget.prototype.clear = widgetOperation(function() {
3666 + var ws = this.line.widgets, no = lineNo(this.line);
3667 + if (no == null || !ws) return;
3668 + for (var i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1);
3669 + if (!ws.length) this.line.widgets = null;
3670 + updateLineHeight(this.line, Math.max(0, this.line.height - widgetHeight(this)));
3671 + regChange(this.cm, no, no + 1);
3672 + });
3673 + LineWidget.prototype.changed = widgetOperation(function() {
3674 + var oldH = this.height;
3675 + this.height = null;
3676 + var diff = widgetHeight(this) - oldH;
3677 + if (!diff) return;
3678 + updateLineHeight(this.line, this.line.height + diff);
3679 + var no = lineNo(this.line);
3680 + regChange(this.cm, no, no + 1);
3681 + });
3682 +
3683 + function widgetHeight(widget) {
3684 + if (widget.height != null) return widget.height;
3685 + if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1)
3686 + removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative"));
3687 + return widget.height = widget.node.offsetHeight;
3688 + }
3689 +
3690 + function addLineWidget(cm, handle, node, options) {
3691 + var widget = new LineWidget(cm, node, options);
3692 + if (widget.noHScroll) cm.display.alignWidgets = true;
3693 + changeLine(cm, handle, function(line) {
3694 + (line.widgets || (line.widgets = [])).push(widget);
3695 + widget.line = line;
3696 + if (!lineIsHidden(cm.doc, line) || widget.showIfHidden) {
3697 + var aboveVisible = heightAtLine(cm, line) < cm.display.scroller.scrollTop;
3698 + updateLineHeight(line, line.height + widgetHeight(widget));
3699 + if (aboveVisible)
3700 + cm.curOp.updateScrollPos = {scrollTop: cm.doc.scrollTop + widget.height,
3701 + scrollLeft: cm.doc.scrollLeft};
3702 + }
3703 + return true;
3704 + });
3705 + return widget;
3706 + }
3707 +
3708 + // LINE DATA STRUCTURE
3709 +
3710 + // Line objects. These hold state related to a line, including
3711 + // highlighting info (the styles array).
3712 + function makeLine(text, markedSpans, estimateHeight) {
3713 + var line = {text: text};
3714 + attachMarkedSpans(line, markedSpans);
3715 + line.height = estimateHeight ? estimateHeight(line) : 1;
3716 + return line;
3717 + }
3718 +
3719 + function updateLine(line, text, markedSpans, estimateHeight) {
3720 + line.text = text;
3721 + if (line.stateAfter) line.stateAfter = null;
3722 + if (line.styles) line.styles = null;
3723 + if (line.order != null) line.order = null;
3724 + detachMarkedSpans(line);
3725 + attachMarkedSpans(line, markedSpans);
3726 + var estHeight = estimateHeight ? estimateHeight(line) : 1;
3727 + if (estHeight != line.height) updateLineHeight(line, estHeight);
3728 + signalLater(line, "change");
3729 + }
3730 +
3731 + function cleanUpLine(line) {
3732 + line.parent = null;
3733 + detachMarkedSpans(line);
3734 + }
3735 +
3736 + // Run the given mode's parser over a line, update the styles
3737 + // array, which contains alternating fragments of text and CSS
3738 + // classes.
3739 + function runMode(cm, text, mode, state, f) {
3740 + var flattenSpans = cm.options.flattenSpans;
3741 + var curText = "", curStyle = null;
3742 + var stream = new StringStream(text, cm.options.tabSize);
3743 + if (text == "" && mode.blankLine) mode.blankLine(state);
3744 + while (!stream.eol()) {
3745 + var style = mode.token(stream, state);
3746 + if (stream.pos > 5000) {
3747 + flattenSpans = false;
3748 + // Webkit seems to refuse to render text nodes longer than 57444 characters
3749 + stream.pos = Math.min(text.length, stream.start + 50000);
3750 + style = null;
3751 + }
3752 + var substr = stream.current();
3753 + stream.start = stream.pos;
3754 + if (!flattenSpans || curStyle != style) {
3755 + if (curText) f(curText, curStyle);
3756 + curText = substr; curStyle = style;
3757 + } else curText = curText + substr;
3758 + }
3759 + if (curText) f(curText, curStyle);
3760 + }
3761 +
3762 + function highlightLine(cm, line, state) {
3763 + // A styles array always starts with a number identifying the
3764 + // mode/overlays that it is based on (for easy invalidation).
3765 + var st = [cm.state.modeGen];
3766 + // Compute the base array of styles
3767 + runMode(cm, line.text, cm.doc.mode, state, function(txt, style) {st.push(txt, style);});
3768 +
3769 + // Run overlays, adjust style array.
3770 + for (var o = 0; o < cm.state.overlays.length; ++o) {
3771 + var overlay = cm.state.overlays[o], i = 1;
3772 + runMode(cm, line.text, overlay.mode, true, function(txt, style) {
3773 + var start = i, len = txt.length;
3774 + // Ensure there's a token end at the current position, and that i points at it
3775 + while (len) {
3776 + var cur = st[i], len_ = cur.length;
3777 + if (len_ <= len) {
3778 + len -= len_;
3779 + } else {
3780 + st.splice(i, 1, cur.slice(0, len), st[i+1], cur.slice(len));
3781 + len = 0;
3782 + }
3783 + i += 2;
3784 + }
3785 + if (!style) return;
3786 + if (overlay.opaque) {
3787 + st.splice(start, i - start, txt, style);
3788 + i = start + 2;
3789 + } else {
3790 + for (; start < i; start += 2) {
3791 + var cur = st[start+1];
3792 + st[start+1] = cur ? cur + " " + style : style;
3793 + }
3794 + }
3795 + });
3796 + }
3797 +
3798 + return st;
3799 + }
3800 +
3801 + function getLineStyles(cm, line) {
3802 + if (!line.styles || line.styles[0] != cm.state.modeGen)
3803 + line.styles = highlightLine(cm, line, line.stateAfter = getStateBefore(cm, lineNo(line)));
3804 + return line.styles;
3805 + }
3806 +
3807 + // Lightweight form of highlight -- proceed over this line and
3808 + // update state, but don't save a style array.
3809 + function processLine(cm, line, state) {
3810 + var mode = cm.doc.mode;
3811 + var stream = new StringStream(line.text, cm.options.tabSize);
3812 + if (line.text == "" && mode.blankLine) mode.blankLine(state);
3813 + while (!stream.eol() && stream.pos <= 5000) {
3814 + mode.token(stream, state);
3815 + stream.start = stream.pos;
3816 + }
3817 + }
3818 +
3819 + var styleToClassCache = {};
3820 + function styleToClass(style) {
3821 + if (!style) return null;
3822 + return styleToClassCache[style] ||
3823 + (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-"));
3824 + }
3825 +
3826 + function lineContent(cm, realLine, measure) {
3827 + var merged, line = realLine, lineBefore, sawBefore, simple = true;
3828 + while (merged = collapsedSpanAtStart(line)) {
3829 + simple = false;
3830 + line = getLine(cm.doc, merged.find().from.line);
3831 + if (!lineBefore) lineBefore = line;
3832 + }
3833 +
3834 + var builder = {pre: elt("pre"), col: 0, pos: 0, display: !measure,
3835 + measure: null, addedOne: false, cm: cm};
3836 + if (line.textClass) builder.pre.className = line.textClass;
3837 +
3838 + do {
3839 + builder.measure = line == realLine && measure;
3840 + builder.pos = 0;
3841 + builder.addToken = builder.measure ? buildTokenMeasure : buildToken;
3842 + if (measure && sawBefore && line != realLine && !builder.addedOne) {
3843 + measure[0] = builder.pre.appendChild(zeroWidthElement(cm.display.measure));
3844 + builder.addedOne = true;
3845 + }
3846 + var next = insertLineContent(line, builder, getLineStyles(cm, line));
3847 + sawBefore = line == lineBefore;
3848 + if (next) {
3849 + line = getLine(cm.doc, next.to.line);
3850 + simple = false;
3851 + }
3852 + } while (next);
3853 +
3854 + if (measure && !builder.addedOne)
3855 + measure[0] = builder.pre.appendChild(simple ? elt("span", "\u00a0") : zeroWidthElement(cm.display.measure));
3856 + if (!builder.pre.firstChild && !lineIsHidden(cm.doc, realLine))
3857 + builder.pre.appendChild(document.createTextNode("\u00a0"));
3858 +
3859 + return builder.pre;
3860 + }
3861 +
3862 + var tokenSpecialChars = /[\t\u0000-\u0019\u200b\u2028\u2029\uFEFF]/g;
3863 + function buildToken(builder, text, style, startStyle, endStyle) {
3864 + if (!text) return;
3865 + if (!tokenSpecialChars.test(text)) {
3866 + builder.col += text.length;
3867 + var content = document.createTextNode(text);
3868 + } else {
3869 + var content = document.createDocumentFragment(), pos = 0;
3870 + while (true) {
3871 + tokenSpecialChars.lastIndex = pos;
3872 + var m = tokenSpecialChars.exec(text);
3873 + var skipped = m ? m.index - pos : text.length - pos;
3874 + if (skipped) {
3875 + content.appendChild(document.createTextNode(text.slice(pos, pos + skipped)));
3876 + builder.col += skipped;
3877 + }
3878 + if (!m) break;
3879 + pos += skipped + 1;
3880 + if (m[0] == "\t") {
3881 + var tabSize = builder.cm.options.tabSize, tabWidth = tabSize - builder.col % tabSize;
3882 + content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab"));
3883 + builder.col += tabWidth;
3884 + } else {
3885 + var token = elt("span", "\u2022", "cm-invalidchar");
3886 + token.title = "\\u" + m[0].charCodeAt(0).toString(16);
3887 + content.appendChild(token);
3888 + builder.col += 1;
3889 + }
3890 + }
3891 + }
3892 + if (style || startStyle || endStyle || builder.measure) {
3893 + var fullStyle = style || "";
3894 + if (startStyle) fullStyle += startStyle;
3895 + if (endStyle) fullStyle += endStyle;
3896 + return builder.pre.appendChild(elt("span", [content], fullStyle));
3897 + }
3898 + builder.pre.appendChild(content);
3899 + }
3900 +
3901 + function buildTokenMeasure(builder, text, style, startStyle, endStyle) {
3902 + for (var i = 0; i < text.length; ++i) {
3903 + if (i && i < text.length &&
3904 + builder.cm.options.lineWrapping &&
3905 + spanAffectsWrapping.test(text.slice(i - 1, i + 1)))
3906 + builder.pre.appendChild(elt("wbr"));
3907 + builder.measure[builder.pos++] =
3908 + buildToken(builder, text.charAt(i), style,
3909 + i == 0 && startStyle, i == text.length - 1 && endStyle);
3910 + }
3911 + if (text.length) builder.addedOne = true;
3912 + }
3913 +
3914 + function buildCollapsedSpan(builder, size, widget) {
3915 + if (widget) {
3916 + if (!builder.display) widget = widget.cloneNode(true);
3917 + builder.pre.appendChild(widget);
3918 + if (builder.measure && size) {
3919 + builder.measure[builder.pos] = widget;
3920 + builder.addedOne = true;
3921 + }
3922 + }
3923 + builder.pos += size;
3924 + }
3925 +
3926 + // Outputs a number of spans to make up a line, taking highlighting
3927 + // and marked text into account.
3928 + function insertLineContent(line, builder, styles) {
3929 + var spans = line.markedSpans;
3930 + if (!spans) {
3931 + for (var i = 1; i < styles.length; i+=2)
3932 + builder.addToken(builder, styles[i], styleToClass(styles[i+1]));
3933 + return;
3934 + }
3935 +
3936 + var allText = line.text, len = allText.length;
3937 + var pos = 0, i = 1, text = "", style;
3938 + var nextChange = 0, spanStyle, spanEndStyle, spanStartStyle, collapsed;
3939 + for (;;) {
3940 + if (nextChange == pos) { // Update current marker set
3941 + spanStyle = spanEndStyle = spanStartStyle = "";
3942 + collapsed = null; nextChange = Infinity;
3943 + var foundBookmark = null;
3944 + for (var j = 0; j < spans.length; ++j) {
3945 + var sp = spans[j], m = sp.marker;
3946 + if (sp.from <= pos && (sp.to == null || sp.to > pos)) {
3947 + if (sp.to != null && nextChange > sp.to) { nextChange = sp.to; spanEndStyle = ""; }
3948 + if (m.className) spanStyle += " " + m.className;
3949 + if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle;
3950 + if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle;
3951 + if (m.collapsed && (!collapsed || collapsed.marker.width < m.width))
3952 + collapsed = sp;
3953 + } else if (sp.from > pos && nextChange > sp.from) {
3954 + nextChange = sp.from;
3955 + }
3956 + if (m.type == "bookmark" && sp.from == pos && m.replacedWith)
3957 + foundBookmark = m.replacedWith;
3958 + }
3959 + if (collapsed && (collapsed.from || 0) == pos) {
3960 + buildCollapsedSpan(builder, (collapsed.to == null ? len : collapsed.to) - pos,
3961 + collapsed.from != null && collapsed.marker.replacedWith);
3962 + if (collapsed.to == null) return collapsed.marker.find();
3963 + }
3964 + if (foundBookmark && !collapsed) buildCollapsedSpan(builder, 0, foundBookmark);
3965 + }
3966 + if (pos >= len) break;
3967 +
3968 + var upto = Math.min(len, nextChange);
3969 + while (true) {
3970 + if (text) {
3971 + var end = pos + text.length;
3972 + if (!collapsed) {
3973 + var tokenText = end > upto ? text.slice(0, upto - pos) : text;
3974 + builder.addToken(builder, tokenText, style ? style + spanStyle : spanStyle,
3975 + spanStartStyle, pos + tokenText.length == nextChange ? spanEndStyle : "");
3976 + }
3977 + if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
3978 + pos = end;
3979 + spanStartStyle = "";
3980 + }
3981 + text = styles[i++]; style = styleToClass(styles[i++]);
3982 + }
3983 + }
3984 + }
3985 +
3986 + // DOCUMENT DATA STRUCTURE
3987 +
3988 + function updateDoc(doc, change, markedSpans, selAfter, estimateHeight) {
3989 + function spansFor(n) {return markedSpans ? markedSpans[n] : null;}
3990 +
3991 + var from = change.from, to = change.to, text = change.text;
3992 + var firstLine = getLine(doc, from.line), lastLine = getLine(doc, to.line);
3993 + var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line;
3994 +
3995 + // First adjust the line structure
3996 + if (from.ch == 0 && to.ch == 0 && lastText == "") {
3997 + // This is a whole-line replace. Treated specially to make
3998 + // sure line objects move the way they are supposed to.
3999 + for (var i = 0, e = text.length - 1, added = []; i < e; ++i)
4000 + added.push(makeLine(text[i], spansFor(i), estimateHeight));
4001 + updateLine(lastLine, lastLine.text, lastSpans, estimateHeight);
4002 + if (nlines) doc.remove(from.line, nlines);
4003 + if (added.length) doc.insert(from.line, added);
4004 + } else if (firstLine == lastLine) {
4005 + if (text.length == 1) {
4006 + updateLine(firstLine, firstLine.text.slice(0, from.ch) + lastText + firstLine.text.slice(to.ch),
4007 + lastSpans, estimateHeight);
4008 + } else {
4009 + for (var added = [], i = 1, e = text.length - 1; i < e; ++i)
4010 + added.push(makeLine(text[i], spansFor(i), estimateHeight));
4011 + added.push(makeLine(lastText + firstLine.text.slice(to.ch), lastSpans, estimateHeight));
4012 + updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0), estimateHeight);
4013 + doc.insert(from.line + 1, added);
4014 + }
4015 + } else if (text.length == 1) {
4016 + updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0] + lastLine.text.slice(to.ch),
4017 + spansFor(0), estimateHeight);
4018 + doc.remove(from.line + 1, nlines);
4019 + } else {
4020 + updateLine(firstLine, firstLine.text.slice(0, from.ch) + text[0], spansFor(0), estimateHeight);
4021 + updateLine(lastLine, lastText + lastLine.text.slice(to.ch), lastSpans, estimateHeight);
4022 + for (var i = 1, e = text.length - 1, added = []; i < e; ++i)
4023 + added.push(makeLine(text[i], spansFor(i), estimateHeight));
4024 + if (nlines > 1) doc.remove(from.line + 1, nlines - 1);
4025 + doc.insert(from.line + 1, added);
4026 + }
4027 +
4028 + setSelection(doc, selAfter.anchor, selAfter.head, null, true);
4029 + }
4030 +
4031 + function LeafChunk(lines) {
4032 + this.lines = lines;
4033 + this.parent = null;
4034 + for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
4035 + lines[i].parent = this;
4036 + height += lines[i].height;
4037 + }
4038 + this.height = height;
4039 + }
4040 +
4041 + LeafChunk.prototype = {
4042 + chunkSize: function() { return this.lines.length; },
4043 + removeInner: function(at, n) {
4044 + for (var i = at, e = at + n; i < e; ++i) {
4045 + var line = this.lines[i];
4046 + this.height -= line.height;
4047 + cleanUpLine(line);
4048 + signalLater(line, "delete");
4049 + }
4050 + this.lines.splice(at, n);
4051 + },
4052 + collapse: function(lines) {
4053 + lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
4054 + },
4055 + insertInner: function(at, lines, height) {
4056 + this.height += height;
4057 + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
4058 + for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
4059 + },
4060 + iterN: function(at, n, op) {
4061 + for (var e = at + n; at < e; ++at)
4062 + if (op(this.lines[at])) return true;
4063 + }
4064 + };
4065 +
4066 + function BranchChunk(children) {
4067 + this.children = children;
4068 + var size = 0, height = 0;
4069 + for (var i = 0, e = children.length; i < e; ++i) {
4070 + var ch = children[i];
4071 + size += ch.chunkSize(); height += ch.height;
4072 + ch.parent = this;
4073 + }
4074 + this.size = size;
4075 + this.height = height;
4076 + this.parent = null;
4077 + }
4078 +
4079 + BranchChunk.prototype = {
4080 + chunkSize: function() { return this.size; },
4081 + removeInner: function(at, n) {
4082 + this.size -= n;
4083 + for (var i = 0; i < this.children.length; ++i) {
4084 + var child = this.children[i], sz = child.chunkSize();
4085 + if (at < sz) {
4086 + var rm = Math.min(n, sz - at), oldHeight = child.height;
4087 + child.removeInner(at, rm);
4088 + this.height -= oldHeight - child.height;
4089 + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
4090 + if ((n -= rm) == 0) break;
4091 + at = 0;
4092 + } else at -= sz;
4093 + }
4094 + if (this.size - n < 25) {
4095 + var lines = [];
4096 + this.collapse(lines);
4097 + this.children = [new LeafChunk(lines)];
4098 + this.children[0].parent = this;
4099 + }
4100 + },
4101 + collapse: function(lines) {
4102 + for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
4103 + },
4104 + insertInner: function(at, lines, height) {
4105 + this.size += lines.length;
4106 + this.height += height;
4107 + for (var i = 0, e = this.children.length; i < e; ++i) {
4108 + var child = this.children[i], sz = child.chunkSize();
4109 + if (at <= sz) {
4110 + child.insertInner(at, lines, height);
4111 + if (child.lines && child.lines.length > 50) {
4112 + while (child.lines.length > 50) {
4113 + var spilled = child.lines.splice(child.lines.length - 25, 25);
4114 + var newleaf = new LeafChunk(spilled);
4115 + child.height -= newleaf.height;
4116 + this.children.splice(i + 1, 0, newleaf);
4117 + newleaf.parent = this;
4118 + }
4119 + this.maybeSpill();
4120 + }
4121 + break;
4122 + }
4123 + at -= sz;
4124 + }
4125 + },
4126 + maybeSpill: function() {
4127 + if (this.children.length <= 10) return;
4128 + var me = this;
4129 + do {
4130 + var spilled = me.children.splice(me.children.length - 5, 5);
4131 + var sibling = new BranchChunk(spilled);
4132 + if (!me.parent) { // Become the parent node
4133 + var copy = new BranchChunk(me.children);
4134 + copy.parent = me;
4135 + me.children = [copy, sibling];
4136 + me = copy;
4137 + } else {
4138 + me.size -= sibling.size;
4139 + me.height -= sibling.height;
4140 + var myIndex = indexOf(me.parent.children, me);
4141 + me.parent.children.splice(myIndex + 1, 0, sibling);
4142 + }
4143 + sibling.parent = me.parent;
4144 + } while (me.children.length > 10);
4145 + me.parent.maybeSpill();
4146 + },
4147 + iterN: function(at, n, op) {
4148 + for (var i = 0, e = this.children.length; i < e; ++i) {
4149 + var child = this.children[i], sz = child.chunkSize();
4150 + if (at < sz) {
4151 + var used = Math.min(n, sz - at);
4152 + if (child.iterN(at, used, op)) return true;
4153 + if ((n -= used) == 0) break;
4154 + at = 0;
4155 + } else at -= sz;
4156 + }
4157 + }
4158 + };
4159 +
4160 + var nextDocId = 0;
4161 + var Doc = CodeMirror.Doc = function(text, mode, firstLine) {
4162 + if (!(this instanceof Doc)) return new Doc(text, mode, firstLine);
4163 + if (firstLine == null) firstLine = 0;
4164 +
4165 + BranchChunk.call(this, [new LeafChunk([makeLine("", null)])]);
4166 + this.first = firstLine;
4167 + this.scrollTop = this.scrollLeft = 0;
4168 + this.cantEdit = false;
4169 + this.history = makeHistory();
4170 + this.frontier = firstLine;
4171 + var start = Pos(firstLine, 0);
4172 + this.sel = {from: start, to: start, head: start, anchor: start, shift: false, extend: false, goalColumn: null};
4173 + this.id = ++nextDocId;
4174 + this.modeOption = mode;
4175 +
4176 + if (typeof text == "string") text = splitLines(text);
4177 + updateDoc(this, {from: start, to: start, text: text}, null, {head: start, anchor: start});
4178 + };
4179 +
4180 + Doc.prototype = createObj(BranchChunk.prototype, {
4181 + iter: function(from, to, op) {
4182 + if (op) this.iterN(from - this.first, to - (from - this.first), op);
4183 + else this.iterN(this.first, this.first + this.size, from);
4184 + },
4185 +
4186 + insert: function(at, lines) {
4187 + var height = 0;
4188 + for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
4189 + this.insertInner(at - this.first, lines, height);
4190 + },
4191 + remove: function(at, n) { this.removeInner(at - this.first, n); },
4192 +
4193 + getValue: function(lineSep) {
4194 + var lines = getLines(this, this.first, this.first + this.size);
4195 + if (lineSep === false) return lines;
4196 + return lines.join(lineSep || "\n");
4197 + },
4198 + setValue: function(code) {
4199 + var top = Pos(this.first, 0), last = this.first + this.size - 1;
4200 + makeChange(this, {from: top, to: Pos(last, getLine(this, last).text.length),
4201 + text: splitLines(code), origin: "setValue"},
4202 + {head: top, anchor: top}, true);
4203 + },
4204 + replaceRange: function(code, from, to, origin) {
4205 + from = clipPos(this, from);
4206 + to = to ? clipPos(this, to) : from;
4207 + replaceRange(this, code, from, to, origin);
4208 + },
4209 + getRange: function(from, to, lineSep) {
4210 + var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
4211 + if (lineSep === false) return lines;
4212 + return lines.join(lineSep || "\n");
4213 + },
4214 +
4215 + getLine: function(line) {var l = this.getLineHandle(line); return l && l.text;},
4216 + setLine: function(line, text) {
4217 + if (isLine(this, line))
4218 + replaceRange(this, text, Pos(line, 0), clipPos(this, Pos(line)));
4219 + },
4220 + removeLine: function(line) {
4221 + if (isLine(this, line))
4222 + replaceRange(this, "", Pos(line, 0), clipPos(this, Pos(line + 1, 0)));
4223 + },
4224 +
4225 + getLineHandle: function(line) {if (isLine(this, line)) return getLine(this, line);},
4226 + getLineNumber: function(line) {return lineNo(line);},
4227 +
4228 + lineCount: function() {return this.size;},
4229 + firstLine: function() {return this.first;},
4230 + lastLine: function() {return this.first + this.size - 1;},
4231 +
4232 + clipPos: function(pos) {return clipPos(this, pos);},
4233 +
4234 + getCursor: function(start) {
4235 + var sel = this.sel, pos;
4236 + if (start == null || start == "head") pos = sel.head;
4237 + else if (start == "anchor") pos = sel.anchor;
4238 + else if (start == "end" || start === false) pos = sel.to;
4239 + else pos = sel.from;
4240 + return copyPos(pos);
4241 + },
4242 + somethingSelected: function() {return !posEq(this.sel.head, this.sel.anchor);},
4243 +
4244 + setCursor: docOperation(function(line, ch, extend) {
4245 + var pos = clipPos(this, typeof line == "number" ? Pos(line, ch || 0) : line);
4246 + if (extend) extendSelection(this, pos);
4247 + else setSelection(this, pos, pos);
4248 + }),
4249 + setSelection: docOperation(function(anchor, head) {
4250 + setSelection(this, clipPos(this, anchor), clipPos(this, head || anchor));
4251 + }),
4252 + extendSelection: docOperation(function(from, to) {
4253 + extendSelection(this, clipPos(this, from), to && clipPos(this, to));
4254 + }),
4255 +
4256 + getSelection: function(lineSep) {return this.getRange(this.sel.from, this.sel.to, lineSep);},
4257 + replaceSelection: function(code, collapse, origin) {
4258 + makeChange(this, {from: this.sel.from, to: this.sel.to, text: splitLines(code), origin: origin}, collapse || "around");
4259 + },
4260 + undo: docOperation(function() {makeChangeFromHistory(this, "undo");}),
4261 + redo: docOperation(function() {makeChangeFromHistory(this, "redo");}),
4262 +
4263 + setExtending: function(val) {this.sel.extend = val;},
4264 +
4265 + historySize: function() {
4266 + var hist = this.history;
4267 + return {undo: hist.done.length, redo: hist.undone.length};
4268 + },
4269 + clearHistory: function() {this.history = makeHistory();},
4270 +
4271 + markClean: function() {
4272 + this.history.dirtyCounter = 0;
4273 + this.history.lastOp = this.history.lastOrigin = null;
4274 + },
4275 + isClean: function () {return this.history.dirtyCounter == 0;},
4276 +
4277 + getHistory: function() {
4278 + return {done: copyHistoryArray(this.history.done),
4279 + undone: copyHistoryArray(this.history.undone)};
4280 + },
4281 + setHistory: function(histData) {
4282 + var hist = this.history = makeHistory();
4283 + hist.done = histData.done.slice(0);
4284 + hist.undone = histData.undone.slice(0);
4285 + },
4286 +
4287 + markText: function(from, to, options) {
4288 + return markText(this, clipPos(this, from), clipPos(this, to), options, "range");
4289 + },
4290 + setBookmark: function(pos, options) {
4291 + var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options),
4292 + insertLeft: options && options.insertLeft};
4293 + pos = clipPos(this, pos);
4294 + return markText(this, pos, pos, realOpts, "bookmark");
4295 + },
4296 + findMarksAt: function(pos) {
4297 + pos = clipPos(this, pos);
4298 + var markers = [], spans = getLine(this, pos.line).markedSpans;
4299 + if (spans) for (var i = 0; i < spans.length; ++i) {
4300 + var span = spans[i];
4301 + if ((span.from == null || span.from <= pos.ch) &&
4302 + (span.to == null || span.to >= pos.ch))
4303 + markers.push(span.marker.parent || span.marker);
4304 + }
4305 + return markers;
4306 + },
4307 +
4308 + posFromIndex: function(off) {
4309 + var ch, lineNo = this.first;
4310 + this.iter(function(line) {
4311 + var sz = line.text.length + 1;
4312 + if (sz > off) { ch = off; return true; }
4313 + off -= sz;
4314 + ++lineNo;
4315 + });
4316 + return clipPos(this, Pos(lineNo, ch));
4317 + },
4318 + indexFromPos: function (coords) {
4319 + coords = clipPos(this, coords);
4320 + var index = coords.ch;
4321 + if (coords.line < this.first || coords.ch < 0) return 0;
4322 + this.iter(this.first, coords.line, function (line) {
4323 + index += line.text.length + 1;
4324 + });
4325 + return index;
4326 + },
4327 +
4328 + copy: function(copyHistory) {
4329 + var doc = new Doc(getLines(this, this.first, this.first + this.size), this.modeOption, this.first);
4330 + doc.scrollTop = this.scrollTop; doc.scrollLeft = this.scrollLeft;
4331 + doc.sel = {from: this.sel.from, to: this.sel.to, head: this.sel.head, anchor: this.sel.anchor,
4332 + shift: this.sel.shift, extend: false, goalColumn: this.sel.goalColumn};
4333 + if (copyHistory) {
4334 + doc.history.undoDepth = this.history.undoDepth;
4335 + doc.setHistory(this.getHistory());
4336 + }
4337 + return doc;
4338 + },
4339 +
4340 + linkedDoc: function(options) {
4341 + if (!options) options = {};
4342 + var from = this.first, to = this.first + this.size;
4343 + if (options.from != null && options.from > from) from = options.from;
4344 + if (options.to != null && options.to < to) to = options.to;
4345 + var copy = new Doc(getLines(this, from, to), options.mode || this.modeOption, from);
4346 + if (options.sharedHist) copy.history = this.history;
4347 + (this.linked || (this.linked = [])).push({doc: copy, sharedHist: options.sharedHist});
4348 + copy.linked = [{doc: this, isParent: true, sharedHist: options.sharedHist}];
4349 + return copy;
4350 + },
4351 + unlinkDoc: function(other) {
4352 + if (other instanceof CodeMirror) other = other.doc;
4353 + if (this.linked) for (var i = 0; i < this.linked.length; ++i) {
4354 + var link = this.linked[i];
4355 + if (link.doc != other) continue;
4356 + this.linked.splice(i, 1);
4357 + other.unlinkDoc(this);
4358 + break;
4359 + }
4360 + // If the histories were shared, split them again
4361 + if (other.history == this.history) {
4362 + var splitIds = [other.id];
4363 + linkedDocs(other, function(doc) {splitIds.push(doc.id);}, true);
4364 + other.history = makeHistory();
4365 + other.history.done = copyHistoryArray(this.history.done, splitIds);
4366 + other.history.undone = copyHistoryArray(this.history.undone, splitIds);
4367 + }
4368 + },
4369 + iterLinkedDocs: function(f) {linkedDocs(this, f);},
4370 +
4371 + getMode: function() {return this.mode;},
4372 + getEditor: function() {return this.cm;}
4373 + });
4374 +
4375 + Doc.prototype.eachLine = Doc.prototype.iter;
4376 +
4377 + // The Doc methods that should be available on CodeMirror instances
4378 + var toDelegate = ("setValue getValue getSelection replaceSelection eachLine undo redo historySize clearHistory markClean isClean " +
4379 + "getHistory setHistory markText setBookmark findMarksAt lineCount firstLine lastLine clipPos getCursor " +
4380 + "somethingSelected setCursor setSelection extendSelection setExtending getLine setLine removeLine getMode " +
4381 + "replaceRange getRange getLineHandle getLineNumber posFromIndex indexFromPos linkedDoc unlinkDoc iterLinkedDocs").split(" ");
4382 + for (var i = 0; i < toDelegate.length; ++i) (function(method) {
4383 + var target = Doc.prototype[method];
4384 + CodeMirror.prototype[method] = function() {return target.apply(this.doc, arguments);};
4385 + })(toDelegate[i]);
4386 +
4387 + function linkedDocs(doc, f, sharedHistOnly) {
4388 + function propagate(doc, skip, sharedHist) {
4389 + if (doc.linked) for (var i = 0; i < doc.linked.length; ++i) {
4390 + var rel = doc.linked[i];
4391 + if (rel.doc == skip) continue;
4392 + var shared = sharedHist && rel.sharedHist;
4393 + if (sharedHistOnly && !shared) continue;
4394 + f(rel.doc, shared);
4395 + propagate(rel.doc, doc, shared);
4396 + }
4397 + }
4398 + propagate(doc, null, true);
4399 + }
4400 +
4401 + function attachDoc(cm, doc) {
4402 + if (doc.cm) throw new Error("This document is already in use.");
4403 + cm.doc = doc;
4404 + doc.cm = cm;
4405 + estimateLineHeights(cm);
4406 + loadMode(cm);
4407 + if (!cm.options.lineWrapping) computeMaxLength(cm);
4408 + cm.options.mode = doc.modeOption;
4409 + regChange(cm);
4410 + }
4411 +
4412 + // LINE UTILITIES
4413 +
4414 + function getLine(chunk, n) {
4415 + n -= chunk.first;
4416 + while (!chunk.lines) {
4417 + for (var i = 0;; ++i) {
4418 + var child = chunk.children[i], sz = child.chunkSize();
4419 + if (n < sz) { chunk = child; break; }
4420 + n -= sz;
4421 + }
4422 + }
4423 + return chunk.lines[n];
4424 + }
4425 +
4426 + function getBetween(doc, start, end) {
4427 + var out = [], n = start.line;
4428 + doc.iter(start.line, end.line + 1, function(line) {
4429 + var text = line.text;
4430 + if (n == end.line) text = text.slice(0, end.ch);
4431 + if (n == start.line) text = text.slice(start.ch);
4432 + out.push(text);
4433 + ++n;
4434 + });
4435 + return out;
4436 + }
4437 + function getLines(doc, from, to) {
4438 + var out = [];
4439 + doc.iter(from, to, function(line) { out.push(line.text); });
4440 + return out;
4441 + }
4442 +
4443 + function updateLineHeight(line, height) {
4444 + var diff = height - line.height;
4445 + for (var n = line; n; n = n.parent) n.height += diff;
4446 + }
4447 +
4448 + function lineNo(line) {
4449 + if (line.parent == null) return null;
4450 + var cur = line.parent, no = indexOf(cur.lines, line);
4451 + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
4452 + for (var i = 0;; ++i) {
4453 + if (chunk.children[i] == cur) break;
4454 + no += chunk.children[i].chunkSize();
4455 + }
4456 + }
4457 + return no + cur.first;
4458 + }
4459 +
4460 + function lineAtHeight(chunk, h) {
4461 + var n = chunk.first;
4462 + outer: do {
4463 + for (var i = 0, e = chunk.children.length; i < e; ++i) {
4464 + var child = chunk.children[i], ch = child.height;
4465 + if (h < ch) { chunk = child; continue outer; }
4466 + h -= ch;
4467 + n += child.chunkSize();
4468 + }
4469 + return n;
4470 + } while (!chunk.lines);
4471 + for (var i = 0, e = chunk.lines.length; i < e; ++i) {
4472 + var line = chunk.lines[i], lh = line.height;
4473 + if (h < lh) break;
4474 + h -= lh;
4475 + }
4476 + return n + i;
4477 + }
4478 +
4479 + function heightAtLine(cm, lineObj) {
4480 + lineObj = visualLine(cm.doc, lineObj);
4481 +
4482 + var h = 0, chunk = lineObj.parent;
4483 + for (var i = 0; i < chunk.lines.length; ++i) {
4484 + var line = chunk.lines[i];
4485 + if (line == lineObj) break;
4486 + else h += line.height;
4487 + }
4488 + for (var p = chunk.parent; p; chunk = p, p = chunk.parent) {
4489 + for (var i = 0; i < p.children.length; ++i) {
4490 + var cur = p.children[i];
4491 + if (cur == chunk) break;
4492 + else h += cur.height;
4493 + }
4494 + }
4495 + return h;
4496 + }
4497 +
4498 + function getOrder(line) {
4499 + var order = line.order;
4500 + if (order == null) order = line.order = bidiOrdering(line.text);
4501 + return order;
4502 + }
4503 +
4504 + // HISTORY
4505 +
4506 + function makeHistory() {
4507 + return {
4508 + // Arrays of history events. Doing something adds an event to
4509 + // done and clears undo. Undoing moves events from done to
4510 + // undone, redoing moves them in the other direction.
4511 + done: [], undone: [], undoDepth: Infinity,
4512 + // Used to track when changes can be merged into a single undo
4513 + // event
4514 + lastTime: 0, lastOp: null, lastOrigin: null,
4515 + // Used by the isClean() method
4516 + dirtyCounter: 0
4517 + };
4518 + }
4519 +
4520 + function attachLocalSpans(doc, change, from, to) {
4521 + var existing = change["spans_" + doc.id], n = 0;
4522 + doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function(line) {
4523 + if (line.markedSpans)
4524 + (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans;
4525 + ++n;
4526 + });
4527 + }
4528 +
4529 + function historyChangeFromChange(doc, change) {
4530 + var histChange = {from: change.from, to: changeEnd(change), text: getBetween(doc, change.from, change.to)};
4531 + attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);
4532 + linkedDocs(doc, function(doc) {attachLocalSpans(doc, histChange, change.from.line, change.to.line + 1);}, true);
4533 + return histChange;
4534 + }
4535 +
4536 + function addToHistory(doc, change, selAfter, opId) {
4537 + var hist = doc.history;
4538 + hist.undone.length = 0;
4539 + var time = +new Date, cur = lst(hist.done);
4540 +
4541 + if (cur &&
4542 + (hist.lastOp == opId ||
4543 + hist.lastOrigin == change.origin && change.origin &&
4544 + ((change.origin.charAt(0) == "+" && hist.lastTime > time - 600) || change.origin.charAt(0) == "*"))) {
4545 + // Merge this change into the last event
4546 + var last = lst(cur.changes);
4547 + if (posEq(change.from, change.to) && posEq(change.from, last.to)) {
4548 + // Optimized case for simple insertion -- don't want to add
4549 + // new changesets for every character typed
4550 + last.to = changeEnd(change);
4551 + } else {
4552 + // Add new sub-event
4553 + cur.changes.push(historyChangeFromChange(doc, change));
4554 + }
4555 + cur.anchorAfter = selAfter.anchor; cur.headAfter = selAfter.head;
4556 + } else {
4557 + // Can not be merged, start a new event.
4558 + cur = {changes: [historyChangeFromChange(doc, change)],
4559 + anchorBefore: doc.sel.anchor, headBefore: doc.sel.head,
4560 + anchorAfter: selAfter.anchor, headAfter: selAfter.head};
4561 + hist.done.push(cur);
4562 + while (hist.done.length > hist.undoDepth)
4563 + hist.done.shift();
4564 + if (hist.dirtyCounter < 0)
4565 + // The user has made a change after undoing past the last clean state.
4566 + // We can never get back to a clean state now until markClean() is called.
4567 + hist.dirtyCounter = NaN;
4568 + else
4569 + hist.dirtyCounter++;
4570 + }
4571 + hist.lastTime = time;
4572 + hist.lastOp = opId;
4573 + hist.lastOrigin = change.origin;
4574 + }
4575 +
4576 + function removeClearedSpans(spans) {
4577 + if (!spans) return null;
4578 + for (var i = 0, out; i < spans.length; ++i) {
4579 + if (spans[i].marker.explicitlyCleared) { if (!out) out = spans.slice(0, i); }
4580 + else if (out) out.push(spans[i]);
4581 + }
4582 + return !out ? spans : out.length ? out : null;
4583 + }
4584 +
4585 + function getOldSpans(doc, change) {
4586 + var found = change["spans_" + doc.id];
4587 + if (!found) return null;
4588 + for (var i = 0, nw = []; i < change.text.length; ++i)
4589 + nw.push(removeClearedSpans(found[i]));
4590 + return nw;
4591 + }
4592 +
4593 + // Used both to provide a JSON-safe object in .getHistory, and, when
4594 + // detaching a document, to split the history in two
4595 + function copyHistoryArray(events, newGroup) {
4596 + for (var i = 0, copy = []; i < events.length; ++i) {
4597 + var event = events[i], changes = event.changes, newChanges = [];
4598 + copy.push({changes: newChanges, anchorBefore: event.anchorBefore, headBefore: event.headBefore,
4599 + anchorAfter: event.anchorAfter, headAfter: event.headAfter});
4600 + for (var j = 0; j < changes.length; ++j) {
4601 + var change = changes[j], m;
4602 + newChanges.push({from: change.from, to: change.to, text: change.text});
4603 + if (newGroup) for (var prop in change) if (m = prop.match(/^spans_(\d+)$/)) {
4604 + if (indexOf(newGroup, Number(m[1])) > -1) {
4605 + lst(newChanges)[prop] = change[prop];
4606 + delete change[prop];
4607 + }
4608 + }
4609 + }
4610 + }
4611 + return copy;
4612 + }
4613 +
4614 + // Rebasing/resetting history to deal with externally-sourced changes
4615 +
4616 + function rebaseHistSel(pos, from, to, diff) {
4617 + if (to < pos.line) {
4618 + pos.line += diff;
4619 + } else if (from < pos.line) {
4620 + pos.line = from;
4621 + pos.ch = 0;
4622 + }
4623 + }
4624 +
4625 + // Tries to rebase an array of history events given a change in the
4626 + // document. If the change touches the same lines as the event, the
4627 + // event, and everything 'behind' it, is discarded. If the change is
4628 + // before the event, the event's positions are updated. Uses a
4629 + // copy-on-write scheme for the positions, to avoid having to
4630 + // reallocate them all on every rebase, but also avoid problems with
4631 + // shared position objects being unsafely updated.
4632 + function rebaseHistArray(array, from, to, diff) {
4633 + for (var i = 0; i < array.length; ++i) {
4634 + var sub = array[i], ok = true;
4635 + for (var j = 0; j < sub.changes.length; ++j) {
4636 + var cur = sub.changes[j];
4637 + if (!sub.copied) { cur.from = copyPos(cur.from); cur.to = copyPos(cur.to); }
4638 + if (to < cur.from.line) {
4639 + cur.from.line += diff;
4640 + cur.to.line += diff;
4641 + } else if (from <= cur.to.line) {
4642 + ok = false;
4643 + break;
4644 + }
4645 + }
4646 + if (!sub.copied) {
4647 + sub.anchorBefore = copyPos(sub.anchorBefore); sub.headBefore = copyPos(sub.headBefore);
4648 + sub.anchorAfter = copyPos(sub.anchorAfter); sub.readAfter = copyPos(sub.headAfter);
4649 + sub.copied = true;
4650 + }
4651 + if (!ok) {
4652 + array.splice(0, i + 1);
4653 + i = 0;
4654 + } else {
4655 + rebaseHistSel(sub.anchorBefore); rebaseHistSel(sub.headBefore);
4656 + rebaseHistSel(sub.anchorAfter); rebaseHistSel(sub.headAfter);
4657 + }
4658 + }
4659 + }
4660 +
4661 + function rebaseHist(hist, change) {
4662 + var from = change.from.line, to = change.to.line, diff = change.text.length - (to - from) - 1;
4663 + rebaseHistArray(hist.done, from, to, diff);
4664 + rebaseHistArray(hist.undone, from, to, diff);
4665 + }
4666 +
4667 + // EVENT OPERATORS
4668 +
4669 + function stopMethod() {e_stop(this);}
4670 + // Ensure an event has a stop method.
4671 + function addStop(event) {
4672 + if (!event.stop) event.stop = stopMethod;
4673 + return event;
4674 + }
4675 +
4676 + function e_preventDefault(e) {
4677 + if (e.preventDefault) e.preventDefault();
4678 + else e.returnValue = false;
4679 + }
4680 + function e_stopPropagation(e) {
4681 + if (e.stopPropagation) e.stopPropagation();
4682 + else e.cancelBubble = true;
4683 + }
4684 + function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
4685 + CodeMirror.e_stop = e_stop;
4686 + CodeMirror.e_preventDefault = e_preventDefault;
4687 + CodeMirror.e_stopPropagation = e_stopPropagation;
4688 +
4689 + function e_target(e) {return e.target || e.srcElement;}
4690 + function e_button(e) {
4691 + var b = e.which;
4692 + if (b == null) {
4693 + if (e.button & 1) b = 1;
4694 + else if (e.button & 2) b = 3;
4695 + else if (e.button & 4) b = 2;
4696 + }
4697 + if (mac && e.ctrlKey && b == 1) b = 3;
4698 + return b;
4699 + }
4700 +
4701 + // Allow 3rd-party code to override event properties by adding an override
4702 + // object to an event object.
4703 + function e_prop(e, prop) {
4704 + var overridden = e.override && e.override.hasOwnProperty(prop);
4705 + return overridden ? e.override[prop] : e[prop];
4706 + }
4707 +
4708 + // EVENT HANDLING
4709 +
4710 + function on(emitter, type, f) {
4711 + if (emitter.addEventListener)
4712 + emitter.addEventListener(type, f, false);
4713 + else if (emitter.attachEvent)
4714 + emitter.attachEvent("on" + type, f);
4715 + else {
4716 + var map = emitter._handlers || (emitter._handlers = {});
4717 + var arr = map[type] || (map[type] = []);
4718 + arr.push(f);
4719 + }
4720 + }
4721 +
4722 + function off(emitter, type, f) {
4723 + if (emitter.removeEventListener)
4724 + emitter.removeEventListener(type, f, false);
4725 + else if (emitter.detachEvent)
4726 + emitter.detachEvent("on" + type, f);
4727 + else {
4728 + var arr = emitter._handlers && emitter._handlers[type];
4729 + if (!arr) return;
4730 + for (var i = 0; i < arr.length; ++i)
4731 + if (arr[i] == f) { arr.splice(i, 1); break; }
4732 + }
4733 + }
4734 +
4735 + function signal(emitter, type /*, values...*/) {
4736 + var arr = emitter._handlers && emitter._handlers[type];
4737 + if (!arr) return;
4738 + var args = Array.prototype.slice.call(arguments, 2);
4739 + for (var i = 0; i < arr.length; ++i) arr[i].apply(null, args);
4740 + }
4741 +
4742 + var delayedCallbacks, delayedCallbackDepth = 0;
4743 + function signalLater(emitter, type /*, values...*/) {
4744 + var arr = emitter._handlers && emitter._handlers[type];
4745 + if (!arr) return;
4746 + var args = Array.prototype.slice.call(arguments, 2);
4747 + function bnd(f) {return function(){f.apply(null, args);};};
4748 + for (var i = 0; i < arr.length; ++i)
4749 + if (delayedCallbacks) delayedCallbacks.push(bnd(arr[i]));
4750 + else arr[i].apply(null, args);
4751 + }
4752 +
4753 + function hasHandler(emitter, type) {
4754 + var arr = emitter._handlers && emitter._handlers[type];
4755 + return arr && arr.length > 0;
4756 + }
4757 +
4758 + CodeMirror.on = on; CodeMirror.off = off; CodeMirror.signal = signal;
4759 +
4760 + // MISC UTILITIES
4761 +
4762 + // Number of pixels added to scroller and sizer to hide scrollbar
4763 + var scrollerCutOff = 30;
4764 +
4765 + // Returned or thrown by various protocols to signal 'I'm not
4766 + // handling this'.
4767 + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
4768 +
4769 + function Delayed() {this.id = null;}
4770 + Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
4771 +
4772 + // Counts the column offset in a string, taking tabs into account.
4773 + // Used mostly to find indentation.
4774 + function countColumn(string, end, tabSize) {
4775 + if (end == null) {
4776 + end = string.search(/[^\s\u00a0]/);
4777 + if (end == -1) end = string.length;
4778 + }
4779 + for (var i = 0, n = 0; i < end; ++i) {
4780 + if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
4781 + else ++n;
4782 + }
4783 + return n;
4784 + }
4785 + CodeMirror.countColumn = countColumn;
4786 +
4787 + var spaceStrs = [""];
4788 + function spaceStr(n) {
4789 + while (spaceStrs.length <= n)
4790 + spaceStrs.push(lst(spaceStrs) + " ");
4791 + return spaceStrs[n];
4792 + }
4793 +
4794 + function lst(arr) { return arr[arr.length-1]; }
4795 +
4796 + function selectInput(node) {
4797 + if (ios) { // Mobile Safari apparently has a bug where select() is broken.
4798 + node.selectionStart = 0;
4799 + node.selectionEnd = node.value.length;
4800 + } else node.select();
4801 + }
4802 +
4803 + function indexOf(collection, elt) {
4804 + if (collection.indexOf) return collection.indexOf(elt);
4805 + for (var i = 0, e = collection.length; i < e; ++i)
4806 + if (collection[i] == elt) return i;
4807 + return -1;
4808 + }
4809 +
4810 + function createObj(base, props) {
4811 + if (!base) return;
4812 + createObj.prototype = base;
4813 + var inst = new createObj();
4814 + if (props) copyObj(props, inst);
4815 + return inst;
4816 + }
4817 +
4818 + function copyObj(obj, target) {
4819 + if (!target) target = {};
4820 + for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop];
4821 + return target;
4822 + }
4823 +
4824 + function emptyArray(size) {
4825 + for (var a = [], i = 0; i < size; ++i) a.push(undefined);
4826 + return a;
4827 + }
4828 +
4829 + function bind(f) {
4830 + var args = Array.prototype.slice.call(arguments, 1);
4831 + return function(){return f.apply(null, args);};
4832 + }
4833 +
4834 + var nonASCIISingleCaseWordChar = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc]/;
4835 + function isWordChar(ch) {
4836 + return /\w/.test(ch) || ch > "\x80" &&
4837 + (ch.toUpperCase() != ch.toLowerCase() || nonASCIISingleCaseWordChar.test(ch));
4838 + }
4839 +
4840 + function isEmpty(obj) {
4841 + for (var n in obj) if (obj.hasOwnProperty(n) && obj[n]) return false;
4842 + return true;
4843 + }
4844 +
4845 + var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F]/;
4846 +
4847 + // DOM UTILITIES
4848 +
4849 + function elt(tag, content, className, style) {
4850 + var e = document.createElement(tag);
4851 + if (className) e.className = className;
4852 + if (style) e.style.cssText = style;
4853 + if (typeof content == "string") setTextContent(e, content);
4854 + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]);
4855 + return e;
4856 + }
4857 +
4858 + function removeChildren(e) {
4859 + // IE will break all parent-child relations in subnodes when setting innerHTML
4860 + if (!ie) e.innerHTML = "";
4861 + else while (e.firstChild) e.removeChild(e.firstChild);
4862 + return e;
4863 + }
4864 +
4865 + function removeChildrenAndAdd(parent, e) {
4866 + return removeChildren(parent).appendChild(e);
4867 + }
4868 +
4869 + function setTextContent(e, str) {
4870 + if (ie_lt9) {
4871 + e.innerHTML = "";
4872 + e.appendChild(document.createTextNode(str));
4873 + } else e.textContent = str;
4874 + }
4875 +
4876 + // FEATURE DETECTION
4877 +
4878 + // Detect drag-and-drop
4879 + var dragAndDrop = function() {
4880 + // There is *some* kind of drag-and-drop support in IE6-8, but I
4881 + // couldn't get it to work yet.
4882 + if (ie_lt9) return false;
4883 + var div = elt('div');
4884 + return "draggable" in div || "dragDrop" in div;
4885 + }();
4886 +
4887 + // For a reason I have yet to figure out, some browsers disallow
4888 + // word wrapping between certain characters *only* if a new inline
4889 + // element is started between them. This makes it hard to reliably
4890 + // measure the position of things, since that requires inserting an
4891 + // extra span. This terribly fragile set of regexps matches the
4892 + // character combinations that suffer from this phenomenon on the
4893 + // various browsers.
4894 + var spanAffectsWrapping = /^$/; // Won't match any two-character string
4895 + if (gecko) spanAffectsWrapping = /$'/;
4896 + else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
4897 + else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
4898 +
4899 + var knownScrollbarWidth;
4900 + function scrollbarWidth(measure) {
4901 + if (knownScrollbarWidth != null) return knownScrollbarWidth;
4902 + var test = elt("div", null, null, "width: 50px; height: 50px; overflow-x: scroll");
4903 + removeChildrenAndAdd(measure, test);
4904 + if (test.offsetWidth)
4905 + knownScrollbarWidth = test.offsetHeight - test.clientHeight;
4906 + return knownScrollbarWidth || 0;
4907 + }
4908 +
4909 + var zwspSupported;
4910 + function zeroWidthElement(measure) {
4911 + if (zwspSupported == null) {
4912 + var test = elt("span", "\u200b");
4913 + removeChildrenAndAdd(measure, elt("span", [test, document.createTextNode("x")]));
4914 + if (measure.firstChild.offsetHeight != 0)
4915 + zwspSupported = test.offsetWidth <= 1 && test.offsetHeight > 2 && !ie_lt8;
4916 + }
4917 + if (zwspSupported) return elt("span", "\u200b");
4918 + else return elt("span", "\u00a0", null, "display: inline-block; width: 1px; margin-right: -1px");
4919 + }
4920 +
4921 + // See if "".split is the broken IE version, if so, provide an
4922 + // alternative way to split lines.
4923 + var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
4924 + var pos = 0, result = [], l = string.length;
4925 + while (pos <= l) {
4926 + var nl = string.indexOf("\n", pos);
4927 + if (nl == -1) nl = string.length;
4928 + var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
4929 + var rt = line.indexOf("\r");
4930 + if (rt != -1) {
4931 + result.push(line.slice(0, rt));
4932 + pos += rt + 1;
4933 + } else {
4934 + result.push(line);
4935 + pos = nl + 1;
4936 + }
4937 + }
4938 + return result;
4939 + } : function(string){return string.split(/\r\n?|\n/);};
4940 + CodeMirror.splitLines = splitLines;
4941 +
4942 + var hasSelection = window.getSelection ? function(te) {
4943 + try { return te.selectionStart != te.selectionEnd; }
4944 + catch(e) { return false; }
4945 + } : function(te) {
4946 + try {var range = te.ownerDocument.selection.createRange();}
4947 + catch(e) {}
4948 + if (!range || range.parentElement() != te) return false;
4949 + return range.compareEndPoints("StartToEnd", range) != 0;
4950 + };
4951 +
4952 + var hasCopyEvent = (function() {
4953 + var e = elt("div");
4954 + if ("oncopy" in e) return true;
4955 + e.setAttribute("oncopy", "return;");
4956 + return typeof e.oncopy == 'function';
4957 + })();
4958 +
4959 + // KEY NAMING
4960 +
4961 + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
4962 + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
4963 + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
4964 + 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
4965 + 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
4966 + 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
4967 + 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
4968 + CodeMirror.keyNames = keyNames;
4969 + (function() {
4970 + // Number keys
4971 + for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
4972 + // Alphabetic keys
4973 + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
4974 + // Function keys
4975 + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
4976 + })();
4977 +
4978 + // BIDI HELPERS
4979 +
4980 + function iterateBidiSections(order, from, to, f) {
4981 + if (!order) return f(from, to, "ltr");
4982 + for (var i = 0; i < order.length; ++i) {
4983 + var part = order[i];
4984 + if (part.from < to && part.to > from || from == to && part.to == from)
4985 + f(Math.max(part.from, from), Math.min(part.to, to), part.level == 1 ? "rtl" : "ltr");
4986 + }
4987 + }
4988 +
4989 + function bidiLeft(part) { return part.level % 2 ? part.to : part.from; }
4990 + function bidiRight(part) { return part.level % 2 ? part.from : part.to; }
4991 +
4992 + function lineLeft(line) { var order = getOrder(line); return order ? bidiLeft(order[0]) : 0; }
4993 + function lineRight(line) {
4994 + var order = getOrder(line);
4995 + if (!order) return line.text.length;
4996 + return bidiRight(lst(order));
4997 + }
4998 +
4999 + function lineStart(cm, lineN) {

This file is too large to show in full.

docs/js/javascript.js new
+425
@@ -0,0 +1,425 @@
1 +// TODO actually recognize syntax of TypeScript constructs
2 +
3 +CodeMirror.defineMode("javascript", function(config, parserConfig) {
4 + var indentUnit = config.indentUnit;
5 + var jsonMode = parserConfig.json;
6 + var isTS = parserConfig.typescript;
7 +
8 + // Tokenizer
9 +
10 + var keywords = function(){
11 + function kw(type) {return {type: type, style: "keyword"};}
12 + var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
13 + var operator = kw("operator"), atom = {type: "atom", style: "atom"};
14 +
15 + var jsKeywords = {
16 + "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
17 + "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
18 + "var": kw("var"), "const": kw("var"), "let": kw("var"),
19 + "function": kw("function"), "catch": kw("catch"),
20 + "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
21 + "in": operator, "typeof": operator, "instanceof": operator,
22 + "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
23 + };
24 +
25 + // Extend the 'normal' keywords with the TypeScript language extensions
26 + if (isTS) {
27 + var type = {type: "variable", style: "variable-3"};
28 + var tsKeywords = {
29 + // object-like things
30 + "interface": kw("interface"),
31 + "class": kw("class"),
32 + "extends": kw("extends"),
33 + "constructor": kw("constructor"),
34 +
35 + // scope modifiers
36 + "public": kw("public"),
37 + "private": kw("private"),
38 + "protected": kw("protected"),
39 + "static": kw("static"),
40 +
41 + "super": kw("super"),
42 +
43 + // types
44 + "string": type, "number": type, "bool": type, "any": type
45 + };
46 +
47 + for (var attr in tsKeywords) {
48 + jsKeywords[attr] = tsKeywords[attr];
49 + }
50 + }
51 +
52 + return jsKeywords;
53 + }();
54 +
55 + var isOperatorChar = /[+\-*&%=<>!?|]/;
56 +
57 + function chain(stream, state, f) {
58 + state.tokenize = f;
59 + return f(stream, state);
60 + }
61 +
62 + function nextUntilUnescaped(stream, end) {
63 + var escaped = false, next;
64 + while ((next = stream.next()) != null) {
65 + if (next == end && !escaped)
66 + return false;
67 + escaped = !escaped && next == "\\";
68 + }
69 + return escaped;
70 + }
71 +
72 + // Used as scratch variables to communicate multiple values without
73 + // consing up tons of objects.
74 + var type, content;
75 + function ret(tp, style, cont) {
76 + type = tp; content = cont;
77 + return style;
78 + }
79 +
80 + function jsTokenBase(stream, state) {
81 + var ch = stream.next();
82 + if (ch == '"' || ch == "'")
83 + return chain(stream, state, jsTokenString(ch));
84 + else if (/[\[\]{}\(\),;\:\.]/.test(ch))
85 + return ret(ch);
86 + else if (ch == "0" && stream.eat(/x/i)) {
87 + stream.eatWhile(/[\da-f]/i);
88 + return ret("number", "number");
89 + }
90 + else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
91 + stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
92 + return ret("number", "number");
93 + }
94 + else if (ch == "/") {
95 + if (stream.eat("*")) {
96 + return chain(stream, state, jsTokenComment);
97 + }
98 + else if (stream.eat("/")) {
99 + stream.skipToEnd();
100 + return ret("comment", "comment");
101 + }
102 + else if (state.lastType == "operator" || state.lastType == "keyword c" ||
103 + /^[\[{}\(,;:]$/.test(state.lastType)) {
104 + nextUntilUnescaped(stream, "/");
105 + stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
106 + return ret("regexp", "string-2");
107 + }
108 + else {
109 + stream.eatWhile(isOperatorChar);
110 + return ret("operator", null, stream.current());
111 + }
112 + }
113 + else if (ch == "#") {
114 + stream.skipToEnd();
115 + return ret("error", "error");
116 + }
117 + else if (isOperatorChar.test(ch)) {
118 + stream.eatWhile(isOperatorChar);
119 + return ret("operator", null, stream.current());
120 + }
121 + else {
122 + stream.eatWhile(/[\w\$_]/);
123 + var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
124 + return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
125 + ret("variable", "variable", word);
126 + }
127 + }
128 +
129 + function jsTokenString(quote) {
130 + return function(stream, state) {
131 + if (!nextUntilUnescaped(stream, quote))
132 + state.tokenize = jsTokenBase;
133 + return ret("string", "string");
134 + };
135 + }
136 +
137 + function jsTokenComment(stream, state) {
138 + var maybeEnd = false, ch;
139 + while (ch = stream.next()) {
140 + if (ch == "/" && maybeEnd) {
141 + state.tokenize = jsTokenBase;
142 + break;
143 + }
144 + maybeEnd = (ch == "*");
145 + }
146 + return ret("comment", "comment");
147 + }
148 +
149 + // Parser
150 +
151 + var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
152 +
153 + function JSLexical(indented, column, type, align, prev, info) {
154 + this.indented = indented;
155 + this.column = column;
156 + this.type = type;
157 + this.prev = prev;
158 + this.info = info;
159 + if (align != null) this.align = align;
160 + }
161 +
162 + function inScope(state, varname) {
163 + for (var v = state.localVars; v; v = v.next)
164 + if (v.name == varname) return true;
165 + }
166 +
167 + function parseJS(state, style, type, content, stream) {
168 + var cc = state.cc;
169 + // Communicate our context to the combinators.
170 + // (Less wasteful than consing up a hundred closures on every call.)
171 + cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
172 +
173 + if (!state.lexical.hasOwnProperty("align"))
174 + state.lexical.align = true;
175 +
176 + while(true) {
177 + var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
178 + if (combinator(type, content)) {
179 + while(cc.length && cc[cc.length - 1].lex)
180 + cc.pop()();
181 + if (cx.marked) return cx.marked;
182 + if (type == "variable" && inScope(state, content)) return "variable-2";
183 + return style;
184 + }
185 + }
186 + }
187 +
188 + // Combinator utils
189 +
190 + var cx = {state: null, column: null, marked: null, cc: null};
191 + function pass() {
192 + for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
193 + }
194 + function cont() {
195 + pass.apply(null, arguments);
196 + return true;
197 + }
198 + function register(varname) {
199 + function inList(list) {
200 + for (var v = list; v; v = v.next)
201 + if (v.name == varname) return true;
202 + return false;
203 + }
204 + var state = cx.state;
205 + if (state.context) {
206 + cx.marked = "def";
207 + if (inList(state.localVars)) return;
208 + state.localVars = {name: varname, next: state.localVars};
209 + } else {
210 + if (inList(state.globalVars)) return;
211 + state.globalVars = {name: varname, next: state.globalVars};
212 + }
213 + }
214 +
215 + // Combinators
216 +
217 + var defaultVars = {name: "this", next: {name: "arguments"}};
218 + function pushcontext() {
219 + cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
220 + cx.state.localVars = defaultVars;
221 + }
222 + function popcontext() {
223 + cx.state.localVars = cx.state.context.vars;
224 + cx.state.context = cx.state.context.prev;
225 + }
226 + function pushlex(type, info) {
227 + var result = function() {
228 + var state = cx.state;
229 + state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info);
230 + };
231 + result.lex = true;
232 + return result;
233 + }
234 + function poplex() {
235 + var state = cx.state;
236 + if (state.lexical.prev) {
237 + if (state.lexical.type == ")")
238 + state.indented = state.lexical.indented;
239 + state.lexical = state.lexical.prev;
240 + }
241 + }
242 + poplex.lex = true;
243 +
244 + function expect(wanted) {
245 + return function(type) {
246 + if (type == wanted) return cont();
247 + else if (wanted == ";") return pass();
248 + else return cont(arguments.callee);
249 + };
250 + }
251 +
252 + function statement(type) {
253 + if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
254 + if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
255 + if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
256 + if (type == "{") return cont(pushlex("}"), block, poplex);
257 + if (type == ";") return cont();
258 + if (type == "function") return cont(functiondef);
259 + if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
260 + poplex, statement, poplex);
261 + if (type == "variable") return cont(pushlex("stat"), maybelabel);
262 + if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
263 + block, poplex, poplex);
264 + if (type == "case") return cont(expression, expect(":"));
265 + if (type == "default") return cont(expect(":"));
266 + if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
267 + statement, poplex, popcontext);
268 + return pass(pushlex("stat"), expression, expect(";"), poplex);
269 + }
270 + function expression(type) {
271 + if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
272 + if (type == "function") return cont(functiondef);
273 + if (type == "keyword c") return cont(maybeexpression);
274 + if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
275 + if (type == "operator") return cont(expression);
276 + if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
277 + if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
278 + return cont();
279 + }
280 + function maybeexpression(type) {
281 + if (type.match(/[;\}\)\],]/)) return pass();
282 + return pass(expression);
283 + }
284 +
285 + function maybeoperator(type, value) {
286 + if (type == "operator") {
287 + if (/\+\+|--/.test(value)) return cont(maybeoperator);
288 + if (value == "?") return cont(expression, expect(":"), expression);
289 + return cont(expression);
290 + }
291 + if (type == ";") return;
292 + if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
293 + if (type == ".") return cont(property, maybeoperator);
294 + if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
295 + }
296 + function maybelabel(type) {
297 + if (type == ":") return cont(poplex, statement);
298 + return pass(maybeoperator, expect(";"), poplex);
299 + }
300 + function property(type) {
301 + if (type == "variable") {cx.marked = "property"; return cont();}
302 + }
303 + function objprop(type) {
304 + if (type == "variable") cx.marked = "property";
305 + if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
306 + }
307 + function commasep(what, end) {
308 + function proceed(type) {
309 + if (type == ",") return cont(what, proceed);
310 + if (type == end) return cont();
311 + return cont(expect(end));
312 + }
313 + return function(type) {
314 + if (type == end) return cont();
315 + else return pass(what, proceed);
316 + };
317 + }
318 + function block(type) {
319 + if (type == "}") return cont();
320 + return pass(statement, block);
321 + }
322 + function maybetype(type) {
323 + if (type == ":") return cont(typedef);
324 + return pass();
325 + }
326 + function typedef(type) {
327 + if (type == "variable"){cx.marked = "variable-3"; return cont();}
328 + return pass();
329 + }
330 + function vardef1(type, value) {
331 + if (type == "variable") {
332 + register(value);
333 + return isTS ? cont(maybetype, vardef2) : cont(vardef2);
334 + }
335 + return pass();
336 + }
337 + function vardef2(type, value) {
338 + if (value == "=") return cont(expression, vardef2);
339 + if (type == ",") return cont(vardef1);
340 + }
341 + function forspec1(type) {
342 + if (type == "var") return cont(vardef1, expect(";"), forspec2);
343 + if (type == ";") return cont(forspec2);
344 + if (type == "variable") return cont(formaybein);
345 + return cont(forspec2);
346 + }
347 + function formaybein(_type, value) {
348 + if (value == "in") return cont(expression);
349 + return cont(maybeoperator, forspec2);
350 + }
351 + function forspec2(type, value) {
352 + if (type == ";") return cont(forspec3);
353 + if (value == "in") return cont(expression);
354 + return cont(expression, expect(";"), forspec3);
355 + }
356 + function forspec3(type) {
357 + if (type != ")") cont(expression);
358 + }
359 + function functiondef(type, value) {
360 + if (type == "variable") {register(value); return cont(functiondef);}
361 + if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
362 + }
363 + function funarg(type, value) {
364 + if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();}
365 + }
366 +
367 + // Interface
368 +
369 + return {
370 + startState: function(basecolumn) {
371 + return {
372 + tokenize: jsTokenBase,
373 + lastType: null,
374 + cc: [],
375 + lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
376 + localVars: parserConfig.localVars,
377 + globalVars: parserConfig.globalVars,
378 + context: parserConfig.localVars && {vars: parserConfig.localVars},
379 + indented: 0
380 + };
381 + },
382 +
383 + token: function(stream, state) {
384 + if (stream.sol()) {
385 + if (!state.lexical.hasOwnProperty("align"))
386 + state.lexical.align = false;
387 + state.indented = stream.indentation();
388 + }
389 + if (stream.eatSpace()) return null;
390 + var style = state.tokenize(stream, state);
391 + if (type == "comment") return style;
392 + state.lastType = type;
393 + return parseJS(state, style, type, content, stream);
394 + },
395 +
396 + indent: function(state, textAfter) {
397 + if (state.tokenize == jsTokenComment) return CodeMirror.Pass;
398 + if (state.tokenize != jsTokenBase) return 0;
399 + var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
400 + if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
401 + var type = lexical.type, closing = firstChar == type;
402 + if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0);
403 + else if (type == "form" && firstChar == "{") return lexical.indented;
404 + else if (type == "form") return lexical.indented + indentUnit;
405 + else if (type == "stat")
406 + return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? indentUnit : 0);
407 + else if (lexical.info == "switch" && !closing)
408 + return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
409 + else if (lexical.align) return lexical.column + (closing ? 0 : 1);
410 + else return lexical.indented + (closing ? 0 : indentUnit);
411 + },
412 +
413 + electricChars: ":{}",
414 +
415 + jsonMode: jsonMode
416 + };
417 +});
418 +
419 +CodeMirror.defineMIME("text/javascript", "javascript");
420 +CodeMirror.defineMIME("text/ecmascript", "javascript");
421 +CodeMirror.defineMIME("application/javascript", "javascript");
422 +CodeMirror.defineMIME("application/ecmascript", "javascript");
423 +CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
424 +CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
425 +CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
docs/js/showdown.js new
+1302
@@ -0,0 +1,1302 @@
1 +//
2 +// showdown.js -- A javascript port of Markdown.
3 +//
4 +// Copyright (c) 2007 John Fraser.
5 +//
6 +// Original Markdown Copyright (c) 2004-2005 John Gruber
7 +// <http://daringfireball.net/projects/markdown/>
8 +//
9 +// Redistributable under a BSD-style open source license.
10 +// See license.txt for more information.
11 +//
12 +// The full source distribution is at:
13 +//
14 +// A A L
15 +// T C A
16 +// T K B
17 +//
18 +// <http://www.attacklab.net/>
19 +//
20 +
21 +//
22 +// Wherever possible, Showdown is a straight, line-by-line port
23 +// of the Perl version of Markdown.
24 +//
25 +// This is not a normal parser design; it's basically just a
26 +// series of string substitutions. It's hard to read and
27 +// maintain this way, but keeping Showdown close to the original
28 +// design makes it easier to port new features.
29 +//
30 +// More importantly, Showdown behaves like markdown.pl in most
31 +// edge cases. So web applications can do client-side preview
32 +// in Javascript, and then build identical HTML on the server.
33 +//
34 +// This port needs the new RegExp functionality of ECMA 262,
35 +// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers
36 +// should do fine. Even with the new regular expression features,
37 +// We do a lot of work to emulate Perl's regex functionality.
38 +// The tricky changes in this file mostly have the "attacklab:"
39 +// label. Major or self-explanatory changes don't.
40 +//
41 +// Smart diff tools like Araxis Merge will be able to match up
42 +// this file with markdown.pl in a useful way. A little tweaking
43 +// helps: in a copy of markdown.pl, replace "#" with "//" and
44 +// replace "$text" with "text". Be sure to ignore whitespace
45 +// and line endings.
46 +//
47 +
48 +
49 +//
50 +// Showdown usage:
51 +//
52 +// var text = "Markdown *rocks*.";
53 +//
54 +// var converter = new Showdown.converter();
55 +// var html = converter.makeHtml(text);
56 +//
57 +// alert(html);
58 +//
59 +// Note: move the sample code to the bottom of this
60 +// file before uncommenting it.
61 +//
62 +
63 +
64 +//
65 +// Showdown namespace
66 +//
67 +var Showdown = {};
68 +
69 +//
70 +// converter
71 +//
72 +// Wraps all "globals" so that the only thing
73 +// exposed is makeHtml().
74 +//
75 +Showdown.converter = function() {
76 +
77 +//
78 +// Globals:
79 +//
80 +
81 +// Global hashes, used by various utility routines
82 +var g_urls;
83 +var g_titles;
84 +var g_html_blocks;
85 +
86 +// Used to track when we're inside an ordered or unordered list
87 +// (see _ProcessListItems() for details):
88 +var g_list_level = 0;
89 +
90 +
91 +this.makeHtml = function(text) {
92 +//
93 +// Main function. The order in which other subs are called here is
94 +// essential. Link and image substitutions need to happen before
95 +// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
96 +// and <img> tags get encoded.
97 +//
98 +
99 + // Clear the global hashes. If we don't clear these, you get conflicts
100 + // from other articles when generating a page which contains more than
101 + // one article (e.g. an index page that shows the N most recent
102 + // articles):
103 + g_urls = new Array();
104 + g_titles = new Array();
105 + g_html_blocks = new Array();
106 +
107 + // attacklab: Replace ~ with ~T
108 + // This lets us use tilde as an escape char to avoid md5 hashes
109 + // The choice of character is arbitray; anything that isn't
110 + // magic in Markdown will work.
111 + text = text.replace(/~/g,"~T");
112 +
113 + // attacklab: Replace $ with ~D
114 + // RegExp interprets $ as a special character
115 + // when it's in a replacement string
116 + text = text.replace(/\$/g,"~D");
117 +
118 + // Standardize line endings
119 + text = text.replace(/\r\n/g,"\n"); // DOS to Unix
120 + text = text.replace(/\r/g,"\n"); // Mac to Unix
121 +
122 + // Make sure text begins and ends with a couple of newlines:
123 + text = "\n\n" + text + "\n\n";
124 +
125 + // Convert all tabs to spaces.
126 + text = _Detab(text);
127 +
128 + // Strip any lines consisting only of spaces and tabs.
129 + // This makes subsequent regexen easier to write, because we can
130 + // match consecutive blank lines with /\n+/ instead of something
131 + // contorted like /[ \t]*\n+/ .
132 + text = text.replace(/^[ \t]+$/mg,"");
133 +
134 + // Turn block-level HTML blocks into hash entries
135 + text = _HashHTMLBlocks(text);
136 +
137 + // Strip link definitions, store in hashes.
138 + text = _StripLinkDefinitions(text);
139 +
140 + text = _RunBlockGamut(text);
141 +
142 + text = _UnescapeSpecialChars(text);
143 +
144 + // attacklab: Restore dollar signs
145 + text = text.replace(/~D/g,"$$");
146 +
147 + // attacklab: Restore tildes
148 + text = text.replace(/~T/g,"~");
149 +
150 + return text;
151 +}
152 +
153 +
154 +var _StripLinkDefinitions = function(text) {
155 +//
156 +// Strips link definitions from text, stores the URLs and titles in
157 +// hash references.
158 +//
159 +
160 + // Link defs are in the form: ^[id]: url "optional title"
161 +
162 + /*
163 + var text = text.replace(/
164 + ^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
165 + [ \t]*
166 + \n? // maybe *one* newline
167 + [ \t]*
168 + <?(\S+?)>? // url = $2
169 + [ \t]*
170 + \n? // maybe one newline
171 + [ \t]*
172 + (?:
173 + (\n*) // any lines skipped = $3 attacklab: lookbehind removed
174 + ["(]
175 + (.+?) // title = $4
176 + [")]
177 + [ \t]*
178 + )? // title is optional
179 + (?:\n+|$)
180 + /gm,
181 + function(){...});
182 + */
183 + var text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|\Z)/gm,
184 + function (wholeMatch,m1,m2,m3,m4) {
185 + m1 = m1.toLowerCase();
186 + g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive
187 + if (m3) {
188 + // Oops, found blank lines, so it's not a title.
189 + // Put back the parenthetical statement we stole.
190 + return m3+m4;
191 + } else if (m4) {
192 + g_titles[m1] = m4.replace(/"/g,"&quot;");
193 + }
194 +
195 + // Completely remove the definition from the text
196 + return "";
197 + }
198 + );
199 +
200 + return text;
201 +}
202 +
203 +
204 +var _HashHTMLBlocks = function(text) {
205 + // attacklab: Double up blank lines to reduce lookaround
206 + text = text.replace(/\n/g,"\n\n");
207 +
208 + // Hashify HTML blocks:
209 + // We only want to do this for block-level HTML tags, such as headers,
210 + // lists, and tables. That's because we still want to wrap <p>s around
211 + // "paragraphs" that are wrapped in non-block-level tags, such as anchors,
212 + // phrase emphasis, and spans. The list of tags we're looking for is
213 + // hard-coded:
214 + var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
215 + var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
216 +
217 + // First, look for nested blocks, e.g.:
218 + // <div>
219 + // <div>
220 + // tags for inner block must be indented.
221 + // </div>
222 + // </div>
223 + //
224 + // The outermost tags must start at the left margin for this to match, and
225 + // the inner nested divs must be indented.
226 + // We need to do this before the next, more liberal match, because the next
227 + // match will start at the first `<div>` and stop at the first `</div>`.
228 +
229 + // attacklab: This regex can be expensive when it fails.
230 + /*
231 + var text = text.replace(/
232 + ( // save in $1
233 + ^ // start of line (with /m)
234 + <($block_tags_a) // start tag = $2
235 + \b // word break
236 + // attacklab: hack around khtml/pcre bug...
237 + [^\r]*?\n // any number of lines, minimally matching
238 + </\2> // the matching end tag
239 + [ \t]* // trailing spaces/tabs
240 + (?=\n+) // followed by a newline
241 + ) // attacklab: there are sentinel newlines at end of document
242 + /gm,function(){...}};
243 + */
244 + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);
245 +
246 + //
247 + // Now match more liberally, simply from `\n<tag>` to `</tag>\n`
248 + //
249 +
250 + /*
251 + var text = text.replace(/
252 + ( // save in $1
253 + ^ // start of line (with /m)
254 + <($block_tags_b) // start tag = $2
255 + \b // word break
256 + // attacklab: hack around khtml/pcre bug...
257 + [^\r]*? // any number of lines, minimally matching
258 + .*</\2> // the matching end tag
259 + [ \t]* // trailing spaces/tabs
260 + (?=\n+) // followed by a newline
261 + ) // attacklab: there are sentinel newlines at end of document
262 + /gm,function(){...}};
263 + */
264 + text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?.*<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);
265 +
266 + // Special case just for <hr />. It was easier to make a special case than
267 + // to make the other regex more complicated.
268 +
269 + /*
270 + text = text.replace(/
271 + ( // save in $1
272 + \n\n // Starting after a blank line
273 + [ ]{0,3}
274 + (<(hr) // start tag = $2
275 + \b // word break
276 + ([^<>])*? //
277 + \/?>) // the matching end tag
278 + [ \t]*
279 + (?=\n{2,}) // followed by a blank line
280 + )
281 + /g,hashElement);
282 + */
283 + text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);
284 +
285 + // Special case for standalone HTML comments:
286 +
287 + /*
288 + text = text.replace(/
289 + ( // save in $1
290 + \n\n // Starting after a blank line
291 + [ ]{0,3} // attacklab: g_tab_width - 1
292 + <!
293 + (--[^\r]*?--\s*)+
294 + >
295 + [ \t]*
296 + (?=\n{2,}) // followed by a blank line
297 + )
298 + /g,hashElement);
299 + */
300 + text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g,hashElement);
301 +
302 + // PHP and ASP-style processor instructions (<?...?> and <%...%>)
303 +
304 + /*
305 + text = text.replace(/
306 + (?:
307 + \n\n // Starting after a blank line
308 + )
309 + ( // save in $1
310 + [ ]{0,3} // attacklab: g_tab_width - 1
311 + (?:
312 + <([?%]) // $2
313 + [^\r]*?
314 + \2>
315 + )
316 + [ \t]*
317 + (?=\n{2,}) // followed by a blank line
318 + )
319 + /g,hashElement);
320 + */
321 + text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);
322 +
323 + // attacklab: Undo double lines (see comment at top of this function)
324 + text = text.replace(/\n\n/g,"\n");
325 + return text;
326 +}
327 +
328 +var hashElement = function(wholeMatch,m1) {
329 + var blockText = m1;
330 +
331 + // Undo double lines
332 + blockText = blockText.replace(/\n\n/g,"\n");
333 + blockText = blockText.replace(/^\n/,"");
334 +
335 + // strip trailing blank lines
336 + blockText = blockText.replace(/\n+$/g,"");
337 +
338 + // Replace the element text with a marker ("~KxK" where x is its key)
339 + blockText = "\n\n~K" + (g_html_blocks.push(blockText)-1) + "K\n\n";
340 +
341 + return blockText;
342 +};
343 +
344 +var _RunBlockGamut = function(text) {
345 +//
346 +// These are all the transformations that form block-level
347 +// tags like paragraphs, headers, and list items.
348 +//
349 + text = _DoHeaders(text);
350 +
351 + // Do Horizontal Rules:
352 + var key = hashBlock("<hr />");
353 + text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,key);
354 + text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm,key);
355 + text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm,key);
356 +
357 + text = _DoLists(text);
358 + text = _DoCodeBlocks(text);
359 + text = _DoBlockQuotes(text);
360 +
361 + // We already ran _HashHTMLBlocks() before, in Markdown(), but that
362 + // was to escape raw HTML in the original Markdown source. This time,
363 + // we're escaping the markup we've just created, so that we don't wrap
364 + // <p> tags around block-level tags.
365 + text = _HashHTMLBlocks(text);
366 + text = _FormParagraphs(text);
367 +
368 + return text;
369 +}
370 +
371 +
372 +var _RunSpanGamut = function(text) {
373 +//
374 +// These are all the transformations that occur *within* block-level
375 +// tags like paragraphs, headers, and list items.
376 +//
377 +
378 + text = _DoCodeSpans(text);
379 + text = _EscapeSpecialCharsWithinTagAttributes(text);
380 + text = _EncodeBackslashEscapes(text);
381 +
382 + // Process anchor and image tags. Images must come first,
383 + // because ![foo][f] looks like an anchor.
384 + text = _DoImages(text);
385 + text = _DoAnchors(text);
386 +
387 + // Make links out of things like `<http://example.com/>`
388 + // Must come after _DoAnchors(), because you can use < and >
389 + // delimiters in inline links like [this](<url>).
390 + text = _DoAutoLinks(text);
391 + text = _EncodeAmpsAndAngles(text);
392 + text = _DoItalicsAndBold(text);
393 +
394 + // Do hard breaks:
395 + text = text.replace(/ +\n/g," <br />\n");
396 +
397 + return text;
398 +}
399 +
400 +var _EscapeSpecialCharsWithinTagAttributes = function(text) {
401 +//
402 +// Within tags -- meaning between < and > -- encode [\ ` * _] so they
403 +// don't conflict with their use in Markdown for code, italics and strong.
404 +//
405 +
406 + // Build a regex to find HTML tags and comments. See Friedl's
407 + // "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
408 + var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
409 +
410 + text = text.replace(regex, function(wholeMatch) {
411 + var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");
412 + tag = escapeCharacters(tag,"\\`*_");
413 + return tag;
414 + });
415 +
416 + return text;
417 +}
418 +
419 +var _DoAnchors = function(text) {
420 +//
421 +// Turn Markdown link shortcuts into XHTML <a> tags.
422 +//
423 + //
424 + // First, handle reference-style links: [link text] [id]
425 + //
426 +
427 + /*
428 + text = text.replace(/
429 + ( // wrap whole match in $1
430 + \[
431 + (
432 + (?:
433 + \[[^\]]*\] // allow brackets nested one level
434 + |
435 + [^\[] // or anything else
436 + )*
437 + )
438 + \]
439 +
440 + [ ]? // one optional space
441 + (?:\n[ ]*)? // one optional newline followed by spaces
442 +
443 + \[
444 + (.*?) // id = $3
445 + \]
446 + )()()()() // pad remaining backreferences
447 + /g,_DoAnchors_callback);
448 + */
449 + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);
450 +
451 + //
452 + // Next, inline-style links: [link text](url "optional title")
453 + //
454 +
455 + /*
456 + text = text.replace(/
457 + ( // wrap whole match in $1
458 + \[
459 + (
460 + (?:
461 + \[[^\]]*\] // allow brackets nested one level
462 + |
463 + [^\[\]] // or anything else
464 + )
465 + )
466 + \]
467 + \( // literal paren
468 + [ \t]*
469 + () // no id, so leave $3 empty
470 + <?(.*?)>? // href = $4
471 + [ \t]*
472 + ( // $5
473 + (['"]) // quote char = $6
474 + (.*?) // Title = $7
475 + \6 // matching quote
476 + [ \t]* // ignore any spaces/tabs between closing quote and )
477 + )? // title is optional
478 + \)
479 + )
480 + /g,writeAnchorTag);
481 + */
482 + text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);
483 +
484 + //
485 + // Last, handle reference-style shortcuts: [link text]
486 + // These must come last in case you've also got [link test][1]
487 + // or [link test](/foo)
488 + //
489 +
490 + /*
491 + text = text.replace(/
492 + ( // wrap whole match in $1
493 + \[
494 + ([^\[\]]+) // link text = $2; can't contain '[' or ']'
495 + \]
496 + )()()()()() // pad rest of backreferences
497 + /g, writeAnchorTag);
498 + */
499 + text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
500 +
501 + return text;
502 +}
503 +
504 +var writeAnchorTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
505 + if (m7 == undefined) m7 = "";
506 + var whole_match = m1;
507 + var link_text = m2;
508 + var link_id = m3.toLowerCase();
509 + var url = m4;
510 + var title = m7;
511 +
512 + if (url == "") {
513 + if (link_id == "") {
514 + // lower-case and turn embedded newlines into spaces
515 + link_id = link_text.toLowerCase().replace(/ ?\n/g," ");
516 + }
517 + url = "#"+link_id;
518 +
519 + if (g_urls[link_id] != undefined) {
520 + url = g_urls[link_id];
521 + if (g_titles[link_id] != undefined) {
522 + title = g_titles[link_id];
523 + }
524 + }
525 + else {
526 + if (whole_match.search(/\(\s*\)$/m)>-1) {
527 + // Special case for explicit empty url
528 + url = "";
529 + } else {
530 + return whole_match;
531 + }
532 + }
533 + }
534 +
535 + url = escapeCharacters(url,"*_");
536 + var result = "<a href=\"" + url + "\"";
537 +
538 + if (title != "") {
539 + title = title.replace(/"/g,"&quot;");
540 + title = escapeCharacters(title,"*_");
541 + result += " title=\"" + title + "\"";
542 + }
543 +
544 + result += ">" + link_text + "</a>";
545 +
546 + return result;
547 +}
548 +
549 +
550 +var _DoImages = function(text) {
551 +//
552 +// Turn Markdown image shortcuts into <img> tags.
553 +//
554 +
555 + //
556 + // First, handle reference-style labeled images: ![alt text][id]
557 + //
558 +
559 + /*
560 + text = text.replace(/
561 + ( // wrap whole match in $1
562 + !\[
563 + (.*?) // alt text = $2
564 + \]
565 +
566 + [ ]? // one optional space
567 + (?:\n[ ]*)? // one optional newline followed by spaces
568 +
569 + \[
570 + (.*?) // id = $3
571 + \]
572 + )()()()() // pad rest of backreferences
573 + /g,writeImageTag);
574 + */
575 + text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);
576 +
577 + //
578 + // Next, handle inline images: ![alt text](url "optional title")
579 + // Don't forget: encode * and _
580 +
581 + /*
582 + text = text.replace(/
583 + ( // wrap whole match in $1
584 + !\[
585 + (.*?) // alt text = $2
586 + \]
587 + \s? // One optional whitespace character
588 + \( // literal paren
589 + [ \t]*
590 + () // no id, so leave $3 empty
591 + <?(\S+?)>? // src url = $4
592 + [ \t]*
593 + ( // $5
594 + (['"]) // quote char = $6
595 + (.*?) // title = $7
596 + \6 // matching quote
597 + [ \t]*
598 + )? // title is optional
599 + \)
600 + )
601 + /g,writeImageTag);
602 + */
603 + text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);
604 +
605 + return text;
606 +}
607 +
608 +var writeImageTag = function(wholeMatch,m1,m2,m3,m4,m5,m6,m7) {
609 + var whole_match = m1;
610 + var alt_text = m2;
611 + var link_id = m3.toLowerCase();
612 + var url = m4;
613 + var title = m7;
614 +
615 + if (!title) title = "";
616 +
617 + if (url == "") {
618 + if (link_id == "") {
619 + // lower-case and turn embedded newlines into spaces
620 + link_id = alt_text.toLowerCase().replace(/ ?\n/g," ");
621 + }
622 + url = "#"+link_id;
623 +
624 + if (g_urls[link_id] != undefined) {
625 + url = g_urls[link_id];
626 + if (g_titles[link_id] != undefined) {
627 + title = g_titles[link_id];
628 + }
629 + }
630 + else {
631 + return whole_match;
632 + }
633 + }
634 +
635 + alt_text = alt_text.replace(/"/g,"&quot;");
636 + url = escapeCharacters(url,"*_");
637 + var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
638 +
639 + // attacklab: Markdown.pl adds empty title attributes to images.
640 + // Replicate this bug.
641 +
642 + //if (title != "") {
643 + title = title.replace(/"/g,"&quot;");
644 + title = escapeCharacters(title,"*_");
645 + result += " title=\"" + title + "\"";
646 + //}
647 +
648 + result += " />";
649 +
650 + return result;
651 +}
652 +
653 +
654 +var _DoHeaders = function(text) {
655 +
656 + // Setext-style headers:
657 + // Header 1
658 + // ========
659 + //
660 + // Header 2
661 + // --------
662 + //
663 + text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
664 + function(wholeMatch,m1){return hashBlock('<h1 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h1>");});
665 +
666 + text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
667 + function(matchFound,m1){return hashBlock('<h2 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h2>");});
668 +
669 + // atx-style headers:
670 + // # Header 1
671 + // ## Header 2
672 + // ## Header 2 with closing hashes ##
673 + // ...
674 + // ###### Header 6
675 + //
676 +
677 + /*
678 + text = text.replace(/
679 + ^(\#{1,6}) // $1 = string of #'s
680 + [ \t]*
681 + (.+?) // $2 = Header text
682 + [ \t]*
683 + \#* // optional closing #'s (not counted)
684 + \n+
685 + /gm, function() {...});
686 + */
687 +
688 + text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
689 + function(wholeMatch,m1,m2) {
690 + var h_level = m1.length;
691 + return hashBlock("<h" + h_level + ' id="' + headerId(m2) + '">' + _RunSpanGamut(m2) + "</h" + h_level + ">");
692 + });
693 +
694 + function headerId(m) {
695 + return m.replace(/[^\w]/g, '').toLowerCase();
696 + }
697 + return text;
698 +}
699 +
700 +// This declaration keeps Dojo compressor from outputting garbage:
701 +var _ProcessListItems;
702 +
703 +var _DoLists = function(text) {
704 +//
705 +// Form HTML ordered (numbered) and unordered (bulleted) lists.
706 +//
707 +
708 + // attacklab: add sentinel to hack around khtml/safari bug:
709 + // http://bugs.webkit.org/show_bug.cgi?id=11231
710 + text += "~0";
711 +
712 + // Re-usable pattern to match any entirel ul or ol list:
713 +
714 + /*
715 + var whole_list = /
716 + ( // $1 = whole list
717 + ( // $2
718 + [ ]{0,3} // attacklab: g_tab_width - 1
719 + ([*+-]|\d+[.]) // $3 = first list item marker
720 + [ \t]+
721 + )
722 + [^\r]+?
723 + ( // $4
724 + ~0 // sentinel for workaround; should be $
725 + |
726 + \n{2,}
727 + (?=\S)
728 + (?! // Negative lookahead for another list item marker
729 + [ \t]*
730 + (?:[*+-]|\d+[.])[ \t]+
731 + )
732 + )
733 + )/g
734 + */
735 + var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
736 +
737 + if (g_list_level) {
738 + text = text.replace(whole_list,function(wholeMatch,m1,m2) {
739 + var list = m1;
740 + var list_type = (m2.search(/[*+-]/g)>-1) ? "ul" : "ol";
741 +
742 + // Turn double returns into triple returns, so that we can make a
743 + // paragraph for the last item in a list, if necessary:
744 + list = list.replace(/\n{2,}/g,"\n\n\n");;
745 + var result = _ProcessListItems(list);
746 +
747 + // Trim any trailing whitespace, to put the closing `</$list_type>`
748 + // up on the preceding line, to get it past the current stupid
749 + // HTML block parser. This is a hack to work around the terrible
750 + // hack that is the HTML block parser.
751 + result = result.replace(/\s+$/,"");
752 + result = "<"+list_type+">" + result + "</"+list_type+">\n";
753 + return result;
754 + });
755 + } else {
756 + whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
757 + text = text.replace(whole_list,function(wholeMatch,m1,m2,m3) {
758 + var runup = m1;
759 + var list = m2;
760 +
761 + var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol";
762 + // Turn double returns into triple returns, so that we can make a
763 + // paragraph for the last item in a list, if necessary:
764 + var list = list.replace(/\n{2,}/g,"\n\n\n");;
765 + var result = _ProcessListItems(list);
766 + result = runup + "<"+list_type+">\n" + result + "</"+list_type+">\n";
767 + return result;
768 + });
769 + }
770 +
771 + // attacklab: strip sentinel
772 + text = text.replace(/~0/,"");
773 +
774 + return text;
775 +}
776 +
777 +_ProcessListItems = function(list_str) {
778 +//
779 +// Process the contents of a single ordered or unordered list, splitting it
780 +// into individual list items.
781 +//
782 + // The $g_list_level global keeps track of when we're inside a list.
783 + // Each time we enter a list, we increment it; when we leave a list,
784 + // we decrement. If it's zero, we're not in a list anymore.
785 + //
786 + // We do this because when we're not inside a list, we want to treat
787 + // something like this:
788 + //
789 + // I recommend upgrading to version
790 + // 8. Oops, now this line is treated
791 + // as a sub-list.
792 + //
793 + // As a single paragraph, despite the fact that the second line starts
794 + // with a digit-period-space sequence.
795 + //
796 + // Whereas when we're inside a list (or sub-list), that line will be
797 + // treated as the start of a sub-list. What a kludge, huh? This is
798 + // an aspect of Markdown's syntax that's hard to parse perfectly
799 + // without resorting to mind-reading. Perhaps the solution is to
800 + // change the syntax rules such that sub-lists must start with a
801 + // starting cardinal number; e.g. "1." or "a.".
802 +
803 + g_list_level++;
804 +
805 + // trim trailing blank lines:
806 + list_str = list_str.replace(/\n{2,}$/,"\n");
807 +
808 + // attacklab: add sentinel to emulate \z
809 + list_str += "~0";
810 +
811 + /*
812 + list_str = list_str.replace(/
813 + (\n)? // leading line = $1
814 + (^[ \t]*) // leading whitespace = $2
815 + ([*+-]|\d+[.]) [ \t]+ // list marker = $3
816 + ([^\r]+? // list item text = $4
817 + (\n{1,2}))
818 + (?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
819 + /gm, function(){...});
820 + */
821 + list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
822 + function(wholeMatch,m1,m2,m3,m4){
823 + var item = m4;
824 + var leading_line = m1;
825 + var leading_space = m2;
826 +
827 + if (leading_line || (item.search(/\n{2,}/)>-1)) {
828 + item = _RunBlockGamut(_Outdent(item));
829 + }
830 + else {
831 + // Recursion for sub-lists:
832 + item = _DoLists(_Outdent(item));
833 + item = item.replace(/\n$/,""); // chomp(item)
834 + item = _RunSpanGamut(item);
835 + }
836 +
837 + return "<li>" + item + "</li>\n";
838 + }
839 + );
840 +
841 + // attacklab: strip sentinel
842 + list_str = list_str.replace(/~0/g,"");
843 +
844 + g_list_level--;
845 + return list_str;
846 +}
847 +
848 +
849 +var _DoCodeBlocks = function(text) {
850 +//
851 +// Process Markdown `<pre><code>` blocks.
852 +//
853 +
854 + /*
855 + text = text.replace(text,
856 + /(?:\n\n|^)
857 + ( // $1 = the code block -- one or more lines, starting with a space/tab
858 + (?:
859 + (?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
860 + .*\n+
861 + )+
862 + )
863 + (\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
864 + /g,function(){...});
865 + */
866 +
867 + // attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
868 + text += "~0";
869 +
870 + text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
871 + function(wholeMatch,m1,m2) {
872 + var codeblock = m1;
873 + var nextChar = m2;
874 +
875 + codeblock = _EncodeCode( _Outdent(codeblock));
876 + codeblock = _Detab(codeblock);
877 + codeblock = codeblock.replace(/^\n+/g,""); // trim leading newlines
878 + codeblock = codeblock.replace(/\n+$/g,""); // trim trailing whitespace
879 +
880 + codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
881 +
882 + return hashBlock(codeblock) + nextChar;
883 + }
884 + );
885 +
886 + // attacklab: strip sentinel
887 + text = text.replace(/~0/,"");
888 +
889 + return text;
890 +}
891 +
892 +var hashBlock = function(text) {
893 + text = text.replace(/(^\n+|\n+$)/g,"");
894 + return "\n\n~K" + (g_html_blocks.push(text)-1) + "K\n\n";
895 +}
896 +
897 +
898 +var _DoCodeSpans = function(text) {
899 +//
900 +// * Backtick quotes are used for <code></code> spans.
901 +//
902 +// * You can use multiple backticks as the delimiters if you want to
903 +// include literal backticks in the code span. So, this input:
904 +//
905 +// Just type ``foo `bar` baz`` at the prompt.
906 +//
907 +// Will translate to:
908 +//
909 +// <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
910 +//
911 +// There's no arbitrary limit to the number of backticks you
912 +// can use as delimters. If you need three consecutive backticks
913 +// in your code, use four for delimiters, etc.
914 +//
915 +// * You can use spaces to get literal backticks at the edges:
916 +//
917 +// ... type `` `bar` `` ...
918 +//
919 +// Turns to:
920 +//
921 +// ... type <code>`bar`</code> ...
922 +//
923 +
924 + /*
925 + text = text.replace(/
926 + (^|[^\\]) // Character before opening ` can't be a backslash
927 + (`+) // $2 = Opening run of `
928 + ( // $3 = The code block
929 + [^\r]*?
930 + [^`] // attacklab: work around lack of lookbehind
931 + )
932 + \2 // Matching closer
933 + (?!`)
934 + /gm, function(){...});
935 + */
936 +
937 + text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
938 + function(wholeMatch,m1,m2,m3,m4) {
939 + var c = m3;
940 + c = c.replace(/^([ \t]*)/g,""); // leading whitespace
941 + c = c.replace(/[ \t]*$/g,""); // trailing whitespace
942 + c = _EncodeCode(c);
943 + return m1+"<code>"+c+"</code>";
944 + });
945 +
946 + return text;
947 +}
948 +
949 +
950 +var _EncodeCode = function(text) {
951 +//
952 +// Encode/escape certain characters inside Markdown code runs.
953 +// The point is that in code, these characters are literals,
954 +// and lose their special Markdown meanings.
955 +//
956 + // Encode all ampersands; HTML entities are not
957 + // entities within a Markdown code span.
958 + text = text.replace(/&/g,"&amp;");
959 +
960 + // Do the angle bracket song and dance:
961 + text = text.replace(/</g,"&lt;");
962 + text = text.replace(/>/g,"&gt;");
963 +
964 + // Now, escape characters that are magic in Markdown:
965 + text = escapeCharacters(text,"\*_{}[]\\",false);
966 +
967 +// jj the line above breaks this:
968 +//---
969 +
970 +//* Item
971 +
972 +// 1. Subitem
973 +
974 +// special char: *
975 +//---
976 +
977 + return text;
978 +}
979 +
980 +
981 +var _DoItalicsAndBold = function(text) {
982 +
983 + // <strong> must go first:
984 + text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
985 + "<strong>$2</strong>");
986 +
987 + text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
988 + "<em>$2</em>");
989 +
990 + return text;
991 +}
992 +
993 +
994 +var _DoBlockQuotes = function(text) {
995 +
996 + /*
997 + text = text.replace(/
998 + ( // Wrap whole match in $1
999 + (
1000 + ^[ \t]*>[ \t]? // '>' at the start of a line
1001 + .+\n // rest of the first line
1002 + (.+\n)* // subsequent consecutive lines
1003 + \n* // blanks
1004 + )+
1005 + )
1006 + /gm, function(){...});
1007 + */
1008 +
1009 + text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
1010 + function(wholeMatch,m1) {
1011 + var bq = m1;
1012 +
1013 + // attacklab: hack around Konqueror 3.5.4 bug:
1014 + // "----------bug".replace(/^-/g,"") == "bug"
1015 +
1016 + bq = bq.replace(/^[ \t]*>[ \t]?/gm,"~0"); // trim one level of quoting
1017 +
1018 + // attacklab: clean up hack
1019 + bq = bq.replace(/~0/g,"");
1020 +
1021 + bq = bq.replace(/^[ \t]+$/gm,""); // trim whitespace-only lines
1022 + bq = _RunBlockGamut(bq); // recurse
1023 +
1024 + bq = bq.replace(/(^|\n)/g,"$1 ");
1025 + // These leading spaces screw with <pre> content, so we need to fix that:
1026 + bq = bq.replace(
1027 + /(\s*<pre>[^\r]+?<\/pre>)/gm,
1028 + function(wholeMatch,m1) {
1029 + var pre = m1;
1030 + // attacklab: hack around Konqueror 3.5.4 bug:
1031 + pre = pre.replace(/^ /mg,"~0");
1032 + pre = pre.replace(/~0/g,"");
1033 + return pre;
1034 + });
1035 +
1036 + return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
1037 + });
1038 + return text;
1039 +}
1040 +
1041 +
1042 +var _FormParagraphs = function(text) {
1043 +//
1044 +// Params:
1045 +// $text - string to process with html <p> tags
1046 +//
1047 +
1048 + // Strip leading and trailing lines:
1049 + text = text.replace(/^\n+/g,"");
1050 + text = text.replace(/\n+$/g,"");
1051 +
1052 + var grafs = text.split(/\n{2,}/g);
1053 + var grafsOut = new Array();
1054 +
1055 + //
1056 + // Wrap <p> tags.
1057 + //
1058 + var end = grafs.length;
1059 + for (var i=0; i<end; i++) {
1060 + var str = grafs[i];
1061 +
1062 + // if this is an HTML marker, copy it
1063 + if (str.search(/~K(\d+)K/g) >= 0) {
1064 + grafsOut.push(str);
1065 + }
1066 + else if (str.search(/\S/) >= 0) {
1067 + str = _RunSpanGamut(str);
1068 + str = str.replace(/^([ \t]*)/g,"<p>");
1069 + str += "</p>"
1070 + grafsOut.push(str);
1071 + }
1072 +
1073 + }
1074 +
1075 + //
1076 + // Unhashify HTML blocks
1077 + //
1078 + end = grafsOut.length;
1079 + for (var i=0; i<end; i++) {
1080 + // if this is a marker for an html block...
1081 + while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
1082 + var blockText = g_html_blocks[RegExp.$1];
1083 + blockText = blockText.replace(/\$/g,"$$$$"); // Escape any dollar signs
1084 + grafsOut[i] = grafsOut[i].replace(/~K\d+K/,blockText);
1085 + }
1086 + }
1087 +
1088 + return grafsOut.join("\n\n");
1089 +}
1090 +
1091 +
1092 +var _EncodeAmpsAndAngles = function(text) {
1093 +// Smart processing for ampersands and angle brackets that need to be encoded.
1094 +
1095 + // Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
1096 + // http://bumppo.net/projects/amputator/
1097 + text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g,"&amp;");
1098 +
1099 + // Encode naked <'s
1100 + text = text.replace(/<(?![a-z\/?\$!])/gi,"&lt;");
1101 +
1102 + return text;
1103 +}
1104 +
1105 +
1106 +var _EncodeBackslashEscapes = function(text) {
1107 +//
1108 +// Parameter: String.
1109 +// Returns: The string, with after processing the following backslash
1110 +// escape sequences.
1111 +//
1112 +
1113 + // attacklab: The polite way to do this is with the new
1114 + // escapeCharacters() function:
1115 + //
1116 + // text = escapeCharacters(text,"\\",true);
1117 + // text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
1118 + //
1119 + // ...but we're sidestepping its use of the (slow) RegExp constructor
1120 + // as an optimization for Firefox. This function gets called a LOT.
1121 +
1122 + text = text.replace(/\\(\\)/g,escapeCharacters_callback);
1123 + text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g,escapeCharacters_callback);
1124 + return text;
1125 +}
1126 +
1127 +
1128 +var _DoAutoLinks = function(text) {
1129 +
1130 + text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi,"<a href=\"$1\">$1</a>");
1131 +
1132 + // Email addresses: <address@domain.foo>
1133 +
1134 + /*
1135 + text = text.replace(/
1136 + <
1137 + (?:mailto:)?
1138 + (
1139 + [-.\w]+
1140 + \@
1141 + [-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
1142 + )
1143 + >
1144 + /gi, _DoAutoLinks_callback());
1145 + */
1146 + text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
1147 + function(wholeMatch,m1) {
1148 + return _EncodeEmailAddress( _UnescapeSpecialChars(m1) );
1149 + }
1150 + );
1151 +
1152 + return text;
1153 +}
1154 +
1155 +
1156 +var _EncodeEmailAddress = function(addr) {
1157 +//
1158 +// Input: an email address, e.g. "foo@example.com"
1159 +//
1160 +// Output: the email address as a mailto link, with each character
1161 +// of the address encoded as either a decimal or hex entity, in
1162 +// the hopes of foiling most address harvesting spam bots. E.g.:
1163 +//
1164 +// <a href="&#x6D;&#97;&#105;&#108;&#x74;&#111;:&#102;&#111;&#111;&#64;&#101;
1165 +// x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;">&#102;&#111;&#111;
1166 +// &#64;&#101;x&#x61;&#109;&#x70;&#108;&#x65;&#x2E;&#99;&#111;&#109;</a>
1167 +//
1168 +// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
1169 +// mailing list: <http://tinyurl.com/yu7ue>
1170 +//
1171 +
1172 + // attacklab: why can't javascript speak hex?
1173 + function char2hex(ch) {
1174 + var hexDigits = '0123456789ABCDEF';
1175 + var dec = ch.charCodeAt(0);
1176 + return(hexDigits.charAt(dec>>4) + hexDigits.charAt(dec&15));
1177 + }
1178 +
1179 + var encode = [
1180 + function(ch){return "&#"+ch.charCodeAt(0)+";";},
1181 + function(ch){return "&#x"+char2hex(ch)+";";},
1182 + function(ch){return ch;}
1183 + ];
1184 +
1185 + addr = "mailto:" + addr;
1186 +
1187 + addr = addr.replace(/./g, function(ch) {
1188 + if (ch == "@") {
1189 + // this *must* be encoded. I insist.
1190 + ch = encode[Math.floor(Math.random()*2)](ch);
1191 + } else if (ch !=":") {
1192 + // leave ':' alone (to spot mailto: later)
1193 + var r = Math.random();
1194 + // roughly 10% raw, 45% hex, 45% dec
1195 + ch = (
1196 + r > .9 ? encode[2](ch) :
1197 + r > .45 ? encode[1](ch) :
1198 + encode[0](ch)
1199 + );
1200 + }
1201 + return ch;
1202 + });
1203 +
1204 + addr = "<a href=\"" + addr + "\">" + addr + "</a>";
1205 + addr = addr.replace(/">.+:/g,"\">"); // strip the mailto: from the visible part
1206 +
1207 + return addr;
1208 +}
1209 +
1210 +
1211 +var _UnescapeSpecialChars = function(text) {
1212 +//
1213 +// Swap back in all the special characters we've hidden.
1214 +//
1215 + text = text.replace(/~E(\d+)E/g,
1216 + function(wholeMatch,m1) {
1217 + var charCodeToReplace = parseInt(m1);
1218 + return String.fromCharCode(charCodeToReplace);
1219 + }
1220 + );
1221 + return text;
1222 +}
1223 +
1224 +
1225 +var _Outdent = function(text) {
1226 +//
1227 +// Remove one level of line-leading tabs or spaces
1228 +//
1229 +
1230 + // attacklab: hack around Konqueror 3.5.4 bug:
1231 + // "----------bug".replace(/^-/g,"") == "bug"
1232 +
1233 + text = text.replace(/^(\t|[ ]{1,4})/gm,"~0"); // attacklab: g_tab_width
1234 +
1235 + // attacklab: clean up hack
1236 + text = text.replace(/~0/g,"")
1237 +
1238 + return text;
1239 +}
1240 +
1241 +var _Detab = function(text) {
1242 +// attacklab: Detab's completely rewritten for speed.
1243 +// In perl we could fix it by anchoring the regexp with \G.
1244 +// In javascript we're less fortunate.
1245 +
1246 + // expand first n-1 tabs
1247 + text = text.replace(/\t(?=\t)/g," "); // attacklab: g_tab_width
1248 +
1249 + // replace the nth with two sentinels
1250 + text = text.replace(/\t/g,"~A~B");
1251 +
1252 + // use the sentinel to anchor our regex so it doesn't explode
1253 + text = text.replace(/~B(.+?)~A/g,
1254 + function(wholeMatch,m1,m2) {
1255 + var leadingText = m1;
1256 + var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width
1257 +
1258 + // there *must* be a better way to do this:
1259 + for (var i=0; i<numSpaces; i++) leadingText+=" ";
1260 +
1261 + return leadingText;
1262 + }
1263 + );
1264 +
1265 + // clean up sentinels
1266 + text = text.replace(/~A/g," "); // attacklab: g_tab_width
1267 + text = text.replace(/~B/g,"");
1268 +
1269 + return text;
1270 +}
1271 +
1272 +
1273 +//
1274 +// attacklab: Utility functions
1275 +//
1276 +
1277 +
1278 +var escapeCharacters = function(text, charsToEscape, afterBackslash) {
1279 + // First we have to escape the escape characters so that
1280 + // we can build a character class out of them
1281 + var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g,"\\$1") + "])";
1282 +
1283 + if (afterBackslash) {
1284 + regexString = "\\\\" + regexString;
1285 + }
1286 +
1287 + var regex = new RegExp(regexString,"g");
1288 + text = text.replace(regex,escapeCharacters_callback);
1289 +
1290 + return text;
1291 +}
1292 +
1293 +
1294 +var escapeCharacters_callback = function(wholeMatch,m1) {
1295 + var charCodeToEscape = m1.charCodeAt(0);
1296 + return "~E"+charCodeToEscape+"E";
1297 +}
1298 +
1299 +} // end of Showdown.converter
1300 +
1301 +// export
1302 +if (typeof exports != 'undefined') exports.Showdown = Showdown;
\ No newline at end of file
docs/support.md new
+15
@@ -0,0 +1,15 @@
1 +---
2 +title: Need help?
3 +layout: single
4 +id: support
5 +---
6 +
7 +**React** is worked on full-time by Facebook's product infrastructure and Instagram's user interface engineering teams. They're often around and available for questions.
8 +
9 +## Google Groups mailing list
10 +
11 +<a href="http://groups.google.com/group/reactjs" target="_blank">The **reactjs** Google Group</a> is the best place to ask questions and find answers.
12 +
13 +## IRC
14 +
15 +Many developers and users idle on Freenode.net's IRC network in **[#reactjs on freenode](irc://chat.freenode.net/reactjs)**.
examples/ballmer-peak/ballmer_peak.png
Binary files /dev/null and b/examples/ballmer-peak/ballmer_peak.png differ
examples/ballmer-peak/example.js new
+47
@@ -0,0 +1,47 @@
1 +/**
2 + * @jsx React.DOM
3 + */
4 +
5 +function computeBallmerPeak(x) {
6 + // see: http://ask.metafilter.com/76859/Make-a-function-of-this-graph-Thats-like-an-antigraph
7 + x = x * 100;
8 + return (
9 + 1-1/(1+Math.exp(-(x-6)))*.5 + Math.exp(-Math.pow(Math.abs(x-10), 2)*10)
10 + ) / 1.6;
11 +}
12 +
13 +var BallmerPeakCalculator = React.createClass({
14 + getInitialState: function() {
15 + return {bac: 0};
16 + },
17 + handleChange: React.autoBind(function() {
18 + this.setState({bac: this.refs.bac.getDOMNode().value});
19 + }),
20 + render: function() {
21 + var bac;
22 + var pct;
23 + pct = computeBallmerPeak(this.state.bac);
24 + if (isNaN(pct)) {
25 + pct = 'N/A';
26 + } else {
27 + pct = (100 - Math.round(pct * 100)) + '%';
28 + }
29 + return (
30 + <div>
31 + <img src="./ballmer_peak.png" />
32 + <p>Credit due to <a href="http://xkcd.com/323/">xkcd</a>.</p>
33 + <h4>Compute your Ballmer Peak:</h4>
34 + <p>
35 + If your BAC is{' '}
36 + <input ref="bac" type="text" onKeyUp={this.handleChange} value={this.state.bac} />
37 + {', '}then <b>{pct}</b> of your lines of code will have bugs.
38 + </p>
39 + </div>
40 + );
41 + }
42 +});
43 +
44 +React.renderComponent(
45 + <BallmerPeakCalculator />,
46 + document.getElementById('container')
47 +);
examples/ballmer-peak/index.html new
+40
@@ -0,0 +1,40 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
5 + <title>Ballmer Peak calculator</title>
6 + <link rel="stylesheet" href="../shared/css/base.css" />
7 + </head>
8 + <body>
9 + <h1>Ballmer Peak calculator</h1>
10 + <div id="container">
11 + <p>
12 + If you can see this, React is not working right. This is probably because you&apos;re viewing
13 + this on your file system instead of a web server. Try running
14 + <pre>
15 + python -m SimpleHTTPServer
16 + </pre>
17 + and going to <a href="http://localhost:8080/">http://localhost:8080/</a>.
18 + </p>
19 + </div>
20 + <h4>Example Details</h4>
21 + <ul>
22 + <li>
23 + This is built with
24 + <a href="https://github.com/substack/node-browserify">browserify</a>.
25 + </li>
26 + <li>
27 + This is written with JSX in a separate file and transformed in the browser.
28 + </li>
29 + </ul>
30 + <p>
31 + </p>
32 + <p>
33 + Learn more at
34 + <a href="http://facebook.github.io/react" target="_blank">facebook.github.io/react</a>.
35 + </p>
36 + <script src="../../build/react.js"></script>
37 + <script src="../../build/JSXTransformer.js"></script>
38 + <script type="text/jsx" src="example.js"></script>
39 + </body>
40 +</html>
examples/basic-jsx-external/example.js new
+22
@@ -0,0 +1,22 @@
1 +/**
2 + * @jsx React.DOM
3 + */
4 +var ExampleApplication = React.createClass({
5 + render: function() {
6 + var elapsed = Math.round(this.props.elapsed / 100);
7 + var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
8 + var message =
9 + 'React has been successfully running for ' + seconds + ' seconds.';
10 +
11 + return <p>{message}</p>;
12 + }
13 +});
14 +
15 +var start = new Date().getTime();
16 +
17 +setInterval(function() {
18 + React.renderComponent(
19 + <ExampleApplication elapsed={new Date().getTime() - start} />,
20 + document.getElementById('container')
21 + );
22 +}, 50);
examples/basic-jsx-external/index.html new
+40
@@ -0,0 +1,40 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
5 + <title>Basic Example with external JSX</title>
6 + <link rel="stylesheet" href="../shared/css/base.css" />
7 + </head>
8 + <body>
9 + <h1>Basic Example with external JSX</h1>
10 + <div id="container">
11 + <p>
12 + If you can see this, React is not working right. This is probably because you&apos;re viewing
13 + this on your file system instead of a web server. Try running
14 + <pre>
15 + python -m SimpleHTTPServer
16 + </pre>
17 + and going to <a href="http://localhost:8080/">http://localhost:8080/</a>.
18 + </p>
19 + </div>
20 + <h4>Example Details</h4>
21 + <ul>
22 + <li>
23 + This is built with
24 + <a href="https://github.com/substack/node-browserify">browserify</a>.
25 + </li>
26 + <li>
27 + This is written with JSX in a separate file and transformed in the browser.
28 + </li>
29 + </ul>
30 + <p>
31 + </p>
32 + <p>
33 + Learn more at
34 + <a href="http://facebook.github.io/react" target="_blank">facebook.github.io/react</a>.
35 + </p>
36 + <script src="../../build/react.js"></script>
37 + <script src="../../build/JSXTransformer.js"></script>
38 + <script type="text/jsx" src="example.js"></script>
39 + </body>
40 +</html>
examples/basic-jsx-precompile/example.js new
+22
@@ -0,0 +1,22 @@
1 +/**
2 + * @jsx React.DOM
3 + */
4 +var ExampleApplication = React.createClass({
5 + render: function() {
6 + var elapsed = Math.round(this.props.elapsed / 100);
7 + var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
8 + var message =
9 + 'React has been successfully running for ' + seconds + ' seconds.';
10 +
11 + return <p>{message}</p>;
12 + }
13 +});
14 +
15 +var start = new Date().getTime();
16 +
17 +setInterval(function() {
18 + React.renderComponent(
19 + <ExampleApplication elapsed={new Date().getTime() - start} />,
20 + document.getElementById('container')
21 + );
22 +}, 50);
examples/basic-jsx-precompile/index.html new
+46
@@ -0,0 +1,46 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
5 + <title>Basic Example with precompiled JSX</title>
6 + <link rel="stylesheet" href="../shared/css/base.css" />
7 + </head>
8 + <body>
9 + <h1>Basic Example with precompiled JSX</h1>
10 + <div id="container">
11 + <p>
12 + To install React, follow the instructions on
13 + <a href="http://www.github.com/facebook/react.js/">GitHub</a>.
14 + </p>
15 + <p>
16 + If you can see this, React is not running. You probably didn't run:
17 + <pre>
18 + npm install -g react-tools
19 + jsx . build/
20 + </pre>
21 + </p>
22 + </div>
23 + <h4>Example Details</h4>
24 + <ul>
25 + <li>
26 + This is built with
27 + <a href="https://github.com/substack/node-browserify">browserify</a>.
28 + </li>
29 + <li>
30 + This is written with JSX and precompiled to vanilla JS by doing:
31 + <pre>
32 + npm install -g react-tools
33 + jsx . build/
34 + </pre>
35 + </li>
36 + </ul>
37 + <p>
38 + </p>
39 + <p>
40 + Learn more at
41 + <a href="http://facebook.github.io/react" target="_blank">facebook.github.io/react</a>.
42 + </p>
43 + <script src="../../build/react.js"></script>
44 + <script src="build/example.js"></script>
45 + </body>
46 +</html>
examples/basic-jsx/index.html new
+61
@@ -0,0 +1,61 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
5 + <title>Basic Example with JSX</title>
6 + <link rel="stylesheet" href="../shared/css/base.css" />
7 + </head>
8 + <body>
9 + <h1>Basic Example with JSX</h1>
10 + <div id="container">
11 + <p>
12 + To install React, follow the instructions on
13 + <a href="http://www.github.com/facebook/react.js/">GitHub</a>.
14 + </p>
15 + <p>
16 + If you can see this, React is not working right.
17 + If you checked out the source from GitHub make sure to run <code>grunt</code>.
18 + </p>
19 + </div>
20 + <h4>Example Details</h4>
21 + <ul>
22 + <li>
23 + This is built with
24 + <a href="https://github.com/substack/node-browserify">browserify</a>.
25 + </li>
26 + <li>
27 + This is written with JSX and transformed in the browser.
28 + </li>
29 + </ul>
30 + <p>
31 + </p>
32 + <p>
33 + Learn more at
34 + <a href="http://facebook.github.io/react" target="_blank">facebook.github.io/react</a>.
35 + </p>
36 + <script src="../../build/react.js"></script>
37 + <script src="../../build/JSXTransformer.js"></script>
38 + <script type="text/jsx">
39 + /**
40 + * @jsx React.DOM
41 + */
42 + var ExampleApplication = React.createClass({
43 + render: function() {
44 + var elapsed = Math.round(this.props.elapsed / 100);
45 + var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
46 + var message =
47 + 'React has been successfully running for ' + seconds + ' seconds.';
48 +
49 + return <p>{message}</p>;
50 + }
51 + });
52 + var start = new Date().getTime();
53 + setInterval(function() {
54 + React.renderComponent(
55 + <ExampleApplication elapsed={new Date().getTime() - start} />,
56 + document.getElementById('container')
57 + );
58 + }, 50);
59 + </script>
60 + </body>
61 +</html>
examples/basic/index.html new
+54
@@ -0,0 +1,54 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
5 + <title>Basic Example</title>
6 + <link rel="stylesheet" href="../shared/css/base.css" />
7 + </head>
8 + <body>
9 + <h1>Basic Example</h1>
10 + <div id="container">
11 + <p>
12 + To install React, follow the instructions on
13 + <a href="http://www.github.com/facebook/react.js/">GitHub</a>.
14 + </p>
15 + <p>
16 + If you can see this, React is not working right.
17 + If you checked out the source from GitHub make sure to run <code>grunt</code>.
18 + </p>
19 + </div>
20 + <h4>Example Details</h4>
21 + <ul>
22 + <li>
23 + This is built with
24 + <a href="https://github.com/substack/node-browserify">browserify</a>.
25 + </li>
26 + </ul>
27 + <p>
28 + </p>
29 + <p>
30 + Learn more at
31 + <a href="http://facebook.github.io/react" target="_blank">facebook.github.io/react</a>.
32 + </p>
33 + <script src="../../build/react.js"></script>
34 + <script>
35 + var ExampleApplication = React.createClass({
36 + render: function() {
37 + var elapsed = Math.round(this.props.elapsed / 100);
38 + var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
39 + var message =
40 + 'React has been successfully running for ' + seconds + ' seconds.';
41 +
42 + return React.DOM.p(null, message);
43 + }
44 + });
45 + var start = new Date().getTime();
46 + setInterval(function() {
47 + React.renderComponent(
48 + ExampleApplication({elapsed: new Date().getTime() - start}),
49 + document.getElementById('container')
50 + );
51 + }, 50);
52 + </script>
53 + </body>
54 +</html>
examples/jquery-bootstrap/css/example.css new
+3
@@ -0,0 +1,3 @@
1 +.example {
2 + margin: 20px;
3 +}
\ No newline at end of file
examples/jquery-bootstrap/index.html new
+18
@@ -0,0 +1,18 @@
1 +<!doctype html>
2 +<html lang="en">
3 +<head>
4 + <meta charset="utf-8">
5 + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6 + <title>jQuery Integration</title>
7 + <link rel="stylesheet" href="thirdparty/bootstrap.min.css" type="text/css" />
8 + <link rel="stylesheet" href="css/example.css" type="text/css" />
9 +</head>
10 +<body>
11 + <div id="jqueryexample"></div>
12 + <script src="../../build/react.js"></script>
13 + <script src="../../build/JSXTransformer.js"></script>
14 + <script type="text/javascript" src="../shared/thirdparty/jquery.min.js" charset="utf-8"></script>
15 + <script type="text/javascript" src="thirdparty/bootstrap.min.js" charset="utf-8"></script>
16 + <script type="text/jsx" src="js/app.js"></script>
17 +</body>
18 +</html>
examples/jquery-bootstrap/js/app.js new
+124
@@ -0,0 +1,124 @@
1 +/** @jsx React.DOM */
2 +
3 +// Simple pure-React component so we don't have to remember
4 +// Bootstrap's classes
5 +var BootstrapButton = React.createClass({
6 + render: function() {
7 + // transferPropsTo() is smart enough to merge classes provided
8 + // to this component.
9 + return this.transferPropsTo(
10 + <a href="javascript:;" role="button" class="btn">
11 + {this.props.children}
12 + </a>
13 + );
14 + }
15 +});
16 +
17 +var BootstrapModal = React.createClass({
18 + // The following two methods are the only places we need to
19 + // integrate with Bootstrap or jQuery!
20 + componentDidMount: function() {
21 + // When the component is added, turn it into a modal
22 + $(this.getDOMNode()).modal({backdrop: 'static', keyboard: false});
23 + },
24 + componentWillUnmount: function() {
25 + // And when it's destroyed, hide it.
26 + $(this.getDOMNode()).modal('hide');
27 + },
28 + render: function() {
29 + var confirmButton = null;
30 + var cancelButton = null;
31 +
32 + if (this.props.confirm) {
33 + confirmButton = (
34 + <BootstrapButton
35 + onClick={this.onConfirm}
36 + class="btn-primary">
37 + {this.props.confirm}
38 + </BootstrapButton>
39 + );
40 + }
41 + if (this.props.cancel) {
42 + cancelButton = (
43 + <BootstrapButton onClick={this.onCancel}>
44 + {this.props.cancel}
45 + </BootstrapButton>
46 + );
47 + }
48 +
49 + return (
50 + <div class="modal hide fade">
51 + <div class="modal-header">
52 + <button
53 + type="button"
54 + class="close"
55 + onClick={this.onCancel}
56 + dangerouslyInsertInnerHtml={{__html: '&times;'}}
57 + />
58 + <h3>{this.props.title}</h3>
59 + </div>
60 + <div class="modal-body">
61 + {this.props.children}
62 + </div>
63 + <div class="modal-footer">
64 + {cancelButton}
65 + {confirmButton}
66 + </div>
67 + </div>
68 + );
69 + },
70 + onCancel: React.autoBind(function() {
71 + if (this.props.onCancel) {
72 + this.props.onCancel();
73 + }
74 + this.close();
75 + }),
76 + onConfirm: React.autoBind(function() {
77 + if (this.props.onConfirm) {
78 + this.props.onConfirm();
79 + }
80 + this.close();
81 + }),
82 + close: function() {
83 + if (this.props.onClose) {
84 + this.props.onClose();
85 + }
86 + }
87 +});
88 +
89 +var Example = React.createClass({
90 + getInitialState: function() {
91 + return {modalVisible: false};
92 + },
93 + toggleModal: React.autoBind(function() {
94 + this.setState({modalVisible: !this.state.modalVisible});
95 + }),
96 + handleCancel: React.autoBind(function() {
97 + if (confirm('Are you sure you want to cancel?')) {
98 + this.toggleModal();
99 + }
100 + }),
101 + render: function() {
102 + var modal = null;
103 + if (this.state.modalVisible) {
104 + modal = (
105 + <BootstrapModal
106 + confirm="OK"
107 + cancel="Cancel"
108 + onCancel={this.handleCancel}
109 + onConfirm={this.toggleModal}
110 + title="Hello, Bootstrap!">
111 + This is a React component powered by jQuery and Bootstrap!
112 + </BootstrapModal>
113 + );
114 + }
115 + return (
116 + <div class="example">
117 + {modal}
118 + <BootstrapButton onClick={this.toggleModal}>Toggle modal</BootstrapButton>
119 + </div>
120 + );
121 + }
122 +});
123 +
124 +React.renderComponent(<Example />, document.getElementById('jqueryexample'));
\ No newline at end of file
examples/jquery-bootstrap/thirdparty/bootstrap.min.css new
+9
@@ -0,0 +1,9 @@
1 +/*!
2 + * Bootstrap v2.3.1
3 + *
4 + * Copyright 2012 Twitter, Inc
5 + * Licensed under the Apache License v2.0
6 + * http://www.apache.org/licenses/LICENSE-2.0
7 + *
8 + * Designed and built with all the love in the world @twitter by @mdo and @fat.
9 + */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}
examples/jquery-bootstrap/thirdparty/bootstrap.min.js new
+6
@@ -0,0 +1,6 @@
1 +/*!
2 +* Bootstrap.js by @fat & @mdo
3 +* Copyright 2012 Twitter, Inc.
4 +* http://www.apache.org/licenses/LICENSE-2.0.txt
5 +*/
6 +!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||s.toggleClass("open"),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f<s.length-1&&f++,~f||(f=0),s.eq(f).focus()}};var s=e.fn.dropdown;e.fn.dropdown=function(t){return this.each(function(){var r=e(this),i=r.data("dropdown");i||r.data("dropdown",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.dropdown.Constructor=n,e.fn.dropdown.noConflict=function(){return e.fn.dropdown=s,this},e(document).on("click.dropdown.data-api",r).on("click.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("click.dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api",t,n.prototype.toggle).on("keydown.dropdown.data-api",t+", [role=menu]",n.prototype.keydown)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=n,this.$element=e(t).delegate('[data-dismiss="modal"]',"click.dismiss.modal",e.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};t.prototype={constructor:t,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var t=this,n=e.Event("show");this.$element.trigger(n);if(this.isShown||n.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var n=e.support.transition&&t.$element.hasClass("fade");t.$element.parent().length||t.$element.appendTo(document.body),t.$element.show(),n&&t.$element[0].offsetWidth,t.$element.addClass("in").attr("aria-hidden",!1),t.enforceFocus(),n?t.$element.one(e.support.transition.end,function(){t.$element.focus().trigger("shown")}):t.$element.focus().trigger("shown")})},hide:function(t){t&&t.preventDefault();var n=this;t=e.Event("hide"),this.$element.trigger(t);if(!this.isShown||t.isDefaultPrevented())return;this.isShown=!1,this.escape(),e(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),e.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var t=this;e(document).on("focusin.modal",function(e){t.$element[0]!==e.target&&!t.$element.has(e.target).length&&t.$element.focus()})},escape:function(){var e=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(t){t.which==27&&e.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var t=this,n=setTimeout(function(){t.$element.off(e.support.transition.end),t.hideModal()},500);this.$element.one(e.support.transition.end,function(){clearTimeout(n),t.hideModal()})},hideModal:function(){var e=this;this.$element.hide(),this.backdrop(function(){e.removeBackdrop(),e.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},backdrop:function(t){var n=this,r=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var i=e.support.transition&&r;this.$backdrop=e('<div class="modal-backdrop '+r+'" />').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(n=e.isFunction(this.source)?this.source(this.query,e.proxy(this.process,this)):this.source,n?this.process(n):this)},process:function(t){var n=this;return t=e.grep(t,function(e){return n.matcher(e)}),t=this.sorter(t),t.length?this.render(t.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(e){return~e.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(e){var t=[],n=[],r=[],i;while(i=e.shift())i.toLowerCase().indexOf(this.query.toLowerCase())?~i.indexOf(this.query)?n.push(i):r.push(i):t.push(i);return t.concat(n,r)},highlighter:function(e){var t=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return e.replace(new RegExp("("+t+")","ig"),function(e,t){return"<strong>"+t+"</strong>"})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
\ No newline at end of file
examples/shared/css/base.css new
+62
@@ -0,0 +1,62 @@
1 +body {
2 + background: #fff;
3 + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;;
4 + font-size: 15px;
5 + line-height: 1.7;
6 + margin: 0;
7 + padding: 30px;
8 +}
9 +
10 +a {
11 + color: #4183c4;
12 + text-decoration: none;
13 +}
14 +
15 +a:hover {
16 + text-decoration: underline;
17 +}
18 +
19 +code {
20 + background-color: #f8f8f8;
21 + border: 1px solid #ddd;
22 + border-radius: 3px;
23 + font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace;
24 + font-size: 12px;
25 + margin: 0 2px;
26 + padding: 0px 5px;
27 +}
28 +
29 +h1, h2, h3, h4 {
30 + font-weight: bold;
31 + margin: 0 0 15px;
32 + padding: 0;
33 +}
34 +
35 +h1 {
36 + border-bottom: 1px solid #ddd;
37 + font-size: 2.5em;
38 + font-weight: bold;
39 + margin: 0 0 15px;
40 + padding: 0;
41 +}
42 +
43 +h2 {
44 + border-bottom: 1px solid #eee;
45 + font-size: 2em;
46 +}
47 +
48 +h3 {
49 + font-size: 1.5em;
50 +}
51 +
52 +h4 {
53 + font-size: 1.2em;
54 +}
55 +
56 +p, ul {
57 + margin: 15px 0;
58 +}
59 +
60 +ul {
61 + padding-left: 30px;
62 +}
examples/shared/thirdparty/jquery.min.js new
+4
@@ -0,0 +1,4 @@
1 +/*! jQuery v1.9.0 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license */(function(e,t){"use strict";function n(e){var t=e.length,n=st.type(e);return st.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e){var t=Tt[e]={};return st.each(e.match(lt)||[],function(e,n){t[n]=!0}),t}function i(e,n,r,i){if(st.acceptData(e)){var o,a,s=st.expando,u="string"==typeof n,l=e.nodeType,c=l?st.cache:e,f=l?e[s]:e[s]&&s;if(f&&c[f]&&(i||c[f].data)||!u||r!==t)return f||(l?e[s]=f=K.pop()||st.guid++:f=s),c[f]||(c[f]={},l||(c[f].toJSON=st.noop)),("object"==typeof n||"function"==typeof n)&&(i?c[f]=st.extend(c[f],n):c[f].data=st.extend(c[f].data,n)),o=c[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[st.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[st.camelCase(n)])):a=o,a}}function o(e,t,n){if(st.acceptData(e)){var r,i,o,a=e.nodeType,u=a?st.cache:e,l=a?e[st.expando]:st.expando;if(u[l]){if(t&&(r=n?u[l]:u[l].data)){st.isArray(t)?t=t.concat(st.map(t,st.camelCase)):t in r?t=[t]:(t=st.camelCase(t),t=t in r?[t]:t.split(" "));for(i=0,o=t.length;o>i;i++)delete r[t[i]];if(!(n?s:st.isEmptyObject)(r))return}(n||(delete u[l].data,s(u[l])))&&(a?st.cleanData([e],!0):st.support.deleteExpando||u!=u.window?delete u[l]:u[l]=null)}}}function a(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(Nt,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:wt.test(r)?st.parseJSON(r):r}catch(o){}st.data(e,n,r)}else r=t}return r}function s(e){var t;for(t in e)if(("data"!==t||!st.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function u(){return!0}function l(){return!1}function c(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function f(e,t,n){if(t=t||0,st.isFunction(t))return st.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return st.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=st.grep(e,function(e){return 1===e.nodeType});if(Wt.test(t))return st.filter(t,r,!n);t=st.filter(t,r)}return st.grep(e,function(e){return st.inArray(e,t)>=0===n})}function p(e){var t=zt.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function d(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function h(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function g(e){var t=nn.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function m(e,t){for(var n,r=0;null!=(n=e[r]);r++)st._data(n,"globalEval",!t||st._data(t[r],"globalEval"))}function y(e,t){if(1===t.nodeType&&st.hasData(e)){var n,r,i,o=st._data(e),a=st._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)st.event.add(t,n,s[n][r])}a.data&&(a.data=st.extend({},a.data))}}function v(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!st.support.noCloneEvent&&t[st.expando]){r=st._data(t);for(i in r.events)st.removeEvent(t,i,r.handle);t.removeAttribute(st.expando)}"script"===n&&t.text!==e.text?(h(t).text=e.text,g(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),st.support.html5Clone&&e.innerHTML&&!st.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Zt.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}function b(e,n){var r,i,o=0,a=e.getElementsByTagName!==t?e.getElementsByTagName(n||"*"):e.querySelectorAll!==t?e.querySelectorAll(n||"*"):t;if(!a)for(a=[],r=e.childNodes||e;null!=(i=r[o]);o++)!n||st.nodeName(i,n)?a.push(i):st.merge(a,b(i,n));return n===t||n&&st.nodeName(e,n)?st.merge([e],a):a}function x(e){Zt.test(e.type)&&(e.defaultChecked=e.checked)}function T(e,t){if(t in e)return t;for(var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Nn.length;i--;)if(t=Nn[i]+n,t in e)return t;return r}function w(e,t){return e=t||e,"none"===st.css(e,"display")||!st.contains(e.ownerDocument,e)}function N(e,t){for(var n,r=[],i=0,o=e.length;o>i;i++)n=e[i],n.style&&(r[i]=st._data(n,"olddisplay"),t?(r[i]||"none"!==n.style.display||(n.style.display=""),""===n.style.display&&w(n)&&(r[i]=st._data(n,"olddisplay",S(n.nodeName)))):r[i]||w(n)||st._data(n,"olddisplay",st.css(n,"display")));for(i=0;o>i;i++)n=e[i],n.style&&(t&&"none"!==n.style.display&&""!==n.style.display||(n.style.display=t?r[i]||"":"none"));return e}function C(e,t,n){var r=mn.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function k(e,t,n,r,i){for(var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;4>o;o+=2)"margin"===n&&(a+=st.css(e,n+wn[o],!0,i)),r?("content"===n&&(a-=st.css(e,"padding"+wn[o],!0,i)),"margin"!==n&&(a-=st.css(e,"border"+wn[o]+"Width",!0,i))):(a+=st.css(e,"padding"+wn[o],!0,i),"padding"!==n&&(a+=st.css(e,"border"+wn[o]+"Width",!0,i)));return a}function E(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=ln(e),a=st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=un(e,t,o),(0>i||null==i)&&(i=e.style[t]),yn.test(i))return i;r=a&&(st.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+k(e,t,n||(a?"border":"content"),r,o)+"px"}function S(e){var t=V,n=bn[e];return n||(n=A(e,t),"none"!==n&&n||(cn=(cn||st("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(cn[0].contentWindow||cn[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=A(e,t),cn.detach()),bn[e]=n),n}function A(e,t){var n=st(t.createElement(e)).appendTo(t.body),r=st.css(n[0],"display");return n.remove(),r}function j(e,t,n,r){var i;if(st.isArray(t))st.each(t,function(t,i){n||kn.test(e)?r(e,i):j(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==st.type(t))r(e,t);else for(i in t)j(e+"["+i+"]",t[i],n,r)}function D(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(lt)||[];if(st.isFunction(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function L(e,n,r,i){function o(u){var l;return a[u]=!0,st.each(e[u]||[],function(e,u){var c=u(n,r,i);return"string"!=typeof c||s||a[c]?s?!(l=c):t:(n.dataTypes.unshift(c),o(c),!1)}),l}var a={},s=e===$n;return o(n.dataTypes[0])||!a["*"]&&o("*")}function H(e,n){var r,i,o=st.ajaxSettings.flatOptions||{};for(r in n)n[r]!==t&&((o[r]?e:i||(i={}))[r]=n[r]);return i&&st.extend(!0,e,i),e}function M(e,n,r){var i,o,a,s,u=e.contents,l=e.dataTypes,c=e.responseFields;for(o in c)o in r&&(n[c[o]]=r[o]);for(;"*"===l[0];)l.shift(),i===t&&(i=e.mimeType||n.getResponseHeader("Content-Type"));if(i)for(o in u)if(u[o]&&u[o].test(i)){l.unshift(o);break}if(l[0]in r)a=l[0];else{for(o in r){if(!l[0]||e.converters[o+" "+l[0]]){a=o;break}s||(s=o)}a=a||s}return a?(a!==l[0]&&l.unshift(a),r[a]):t}function q(e,t){var n,r,i,o,a={},s=0,u=e.dataTypes.slice(),l=u[0];if(e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u[1])for(n in e.converters)a[n.toLowerCase()]=e.converters[n];for(;i=u[++s];)if("*"!==i){if("*"!==l&&l!==i){if(n=a[l+" "+i]||a["* "+i],!n)for(r in a)if(o=r.split(" "),o[1]===i&&(n=a[l+" "+o[0]]||a["* "+o[0]])){n===!0?n=a[r]:a[r]!==!0&&(i=o[0],u.splice(s--,0,i));break}if(n!==!0)if(n&&e["throws"])t=n(t);else try{t=n(t)}catch(c){return{state:"parsererror",error:n?c:"No conversion from "+l+" to "+i}}}l=i}return{state:"success",data:t}}function _(){try{return new e.XMLHttpRequest}catch(t){}}function F(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}function O(){return setTimeout(function(){Qn=t}),Qn=st.now()}function B(e,t){st.each(t,function(t,n){for(var r=(rr[t]||[]).concat(rr["*"]),i=0,o=r.length;o>i;i++)if(r[i].call(e,t,n))return})}function P(e,t,n){var r,i,o=0,a=nr.length,s=st.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;for(var t=Qn||O(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;u>a;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),1>o&&u?n:(s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:st.extend({},t),opts:st.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Qn||O(),duration:n.duration,tweens:[],createTween:function(t,n){var r=st.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?s.resolveWith(e,[l,t]):s.rejectWith(e,[l,t]),this}}),c=l.props;for(R(c,l.opts.specialEasing);a>o;o++)if(r=nr[o].call(l,e,c,l.opts))return r;return B(l,c),st.isFunction(l.opts.start)&&l.opts.start.call(e,l),st.fx.timer(st.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function R(e,t){var n,r,i,o,a;for(n in e)if(r=st.camelCase(n),i=t[r],o=e[n],st.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=st.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}function W(e,t,n){var r,i,o,a,s,u,l,c,f,p=this,d=e.style,h={},g=[],m=e.nodeType&&w(e);n.queue||(c=st._queueHooks(e,"fx"),null==c.unqueued&&(c.unqueued=0,f=c.empty.fire,c.empty.fire=function(){c.unqueued||f()}),c.unqueued++,p.always(function(){p.always(function(){c.unqueued--,st.queue(e,"fx").length||c.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[d.overflow,d.overflowX,d.overflowY],"inline"===st.css(e,"display")&&"none"===st.css(e,"float")&&(st.support.inlineBlockNeedsLayout&&"inline"!==S(e.nodeName)?d.zoom=1:d.display="inline-block")),n.overflow&&(d.overflow="hidden",st.support.shrinkWrapBlocks||p.done(function(){d.overflow=n.overflow[0],d.overflowX=n.overflow[1],d.overflowY=n.overflow[2]}));for(r in t)if(o=t[r],Zn.exec(o)){if(delete t[r],u=u||"toggle"===o,o===(m?"hide":"show"))continue;g.push(r)}if(a=g.length){s=st._data(e,"fxshow")||st._data(e,"fxshow",{}),"hidden"in s&&(m=s.hidden),u&&(s.hidden=!m),m?st(e).show():p.done(function(){st(e).hide()}),p.done(function(){var t;st._removeData(e,"fxshow");for(t in h)st.style(e,t,h[t])});for(r=0;a>r;r++)i=g[r],l=p.createTween(i,m?s[i]:0),h[i]=s[i]||st.style(e,i),i in s||(s[i]=l.start,m&&(l.end=l.start,l.start="width"===i||"height"===i?1:0))}}function $(e,t,n,r,i){return new $.prototype.init(e,t,n,r,i)}function I(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=wn[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}function z(e){return st.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}var X,U,V=e.document,Y=e.location,J=e.jQuery,G=e.$,Q={},K=[],Z="1.9.0",et=K.concat,tt=K.push,nt=K.slice,rt=K.indexOf,it=Q.toString,ot=Q.hasOwnProperty,at=Z.trim,st=function(e,t){return new st.fn.init(e,t,X)},ut=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,lt=/\S+/g,ct=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,ft=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,pt=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,dt=/^[\],:{}\s]*$/,ht=/(?:^|:|,)(?:\s*\[)+/g,gt=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,mt=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,yt=/^-ms-/,vt=/-([\da-z])/gi,bt=function(e,t){return t.toUpperCase()},xt=function(){V.addEventListener?(V.removeEventListener("DOMContentLoaded",xt,!1),st.ready()):"complete"===V.readyState&&(V.detachEvent("onreadystatechange",xt),st.ready())};st.fn=st.prototype={jquery:Z,constructor:st,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:ft.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof st?n[0]:n,st.merge(this,st.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:V,!0)),pt.test(i[1])&&st.isPlainObject(n))for(i in n)st.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=V.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=V,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):st.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),st.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return nt.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=st.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return st.each(this,e,t)},ready:function(e){return st.ready.promise().done(e),this},slice:function(){return this.pushStack(nt.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(st.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:tt,sort:[].sort,splice:[].splice},st.fn.init.prototype=st.fn,st.extend=st.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||st.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(e=arguments[u]))for(n in e)r=s[n],i=e[n],s!==i&&(c&&i&&(st.isPlainObject(i)||(o=st.isArray(i)))?(o?(o=!1,a=r&&st.isArray(r)?r:[]):a=r&&st.isPlainObject(r)?r:{},s[n]=st.extend(c,a,i)):i!==t&&(s[n]=i));return s},st.extend({noConflict:function(t){return e.$===st&&(e.$=G),t&&e.jQuery===st&&(e.jQuery=J),st},isReady:!1,readyWait:1,holdReady:function(e){e?st.readyWait++:st.ready(!0)},ready:function(e){if(e===!0?!--st.readyWait:!st.isReady){if(!V.body)return setTimeout(st.ready);st.isReady=!0,e!==!0&&--st.readyWait>0||(U.resolveWith(V,[st]),st.fn.trigger&&st(V).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===st.type(e)},isArray:Array.isArray||function(e){return"array"===st.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?Q[it.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==st.type(e)||e.nodeType||st.isWindow(e))return!1;try{if(e.constructor&&!ot.call(e,"constructor")&&!ot.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||ot.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||V;var r=pt.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=st.buildFragment([e],t,i),i&&st(i).remove(),st.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=st.trim(n),n&&dt.test(n.replace(gt,"@").replace(mt,"]").replace(ht,"")))?Function("return "+n)():(st.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||st.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&st.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(yt,"ms-").replace(vt,bt)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,r){var i,o=0,a=e.length,s=n(e);if(r){if(s)for(;a>o&&(i=t.apply(e[o],r),i!==!1);o++);else for(o in e)if(i=t.apply(e[o],r),i===!1)break}else if(s)for(;a>o&&(i=t.call(e[o],o,e[o]),i!==!1);o++);else for(o in e)if(i=t.call(e[o],o,e[o]),i===!1)break;return e},trim:at&&!at.call("\ufeff\u00a0")?function(e){return null==e?"":at.call(e)}:function(e){return null==e?"":(e+"").replace(ct,"")},makeArray:function(e,t){var r=t||[];return null!=e&&(n(Object(e))?st.merge(r,"string"==typeof e?[e]:e):tt.call(r,e)),r},inArray:function(e,t,n){var r;if(t){if(rt)return rt.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else for(;n[o]!==t;)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,r){var i,o=0,a=e.length,s=n(e),u=[];if(s)for(;a>o;o++)i=t(e[o],o,r),null!=i&&(u[u.length]=i);else for(o in e)i=t(e[o],o,r),null!=i&&(u[u.length]=i);return et.apply([],u)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(r=e[n],n=e,e=r),st.isFunction(e)?(i=nt.call(arguments,2),o=function(){return e.apply(n||this,i.concat(nt.call(arguments)))},o.guid=e.guid=e.guid||st.guid++,o):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===st.type(r)){o=!0;for(u in r)st.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,st.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(st(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),st.ready.promise=function(t){if(!U)if(U=st.Deferred(),"complete"===V.readyState)setTimeout(st.ready);else if(V.addEventListener)V.addEventListener("DOMContentLoaded",xt,!1),e.addEventListener("load",st.ready,!1);else{V.attachEvent("onreadystatechange",xt),e.attachEvent("onload",st.ready);var n=!1;try{n=null==e.frameElement&&V.documentElement}catch(r){}n&&n.doScroll&&function i(){if(!st.isReady){try{n.doScroll("left")}catch(e){return setTimeout(i,50)}st.ready()}}()}return U.promise(t)},st.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){Q["[object "+t+"]"]=t.toLowerCase()}),X=st(V);var Tt={};st.Callbacks=function(e){e="string"==typeof e?Tt[e]||r(e):st.extend({},e);var n,i,o,a,s,u,l=[],c=!e.once&&[],f=function(t){for(n=e.memory&&t,i=!0,u=a||0,a=0,s=l.length,o=!0;l&&s>u;u++)if(l[u].apply(t[0],t[1])===!1&&e.stopOnFalse){n=!1;break}o=!1,l&&(c?c.length&&f(c.shift()):n?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function r(t){st.each(t,function(t,n){var i=st.type(n);"function"===i?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==i&&r(n)})})(arguments),o?s=l.length:n&&(a=t,f(n))}return this},remove:function(){return l&&st.each(arguments,function(e,t){for(var n;(n=st.inArray(t,l,n))>-1;)l.splice(n,1),o&&(s>=n&&s--,u>=n&&u--)}),this},has:function(e){return st.inArray(e,l)>-1},empty:function(){return l=[],this},disable:function(){return l=c=n=t,this},disabled:function(){return!l},lock:function(){return c=t,n||p.disable(),this},locked:function(){return!c},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!l||i&&!c||(o?c.push(t):f(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},st.extend({Deferred:function(e){var t=[["resolve","done",st.Callbacks("once memory"),"resolved"],["reject","fail",st.Callbacks("once memory"),"rejected"],["notify","progress",st.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return st.Deferred(function(n){st.each(t,function(t,o){var a=o[0],s=st.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&st.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?st.extend(e,r):r}},i={};return r.pipe=r.then,st.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t,n,r,i=0,o=nt.call(arguments),a=o.length,s=1!==a||e&&st.isFunction(e.promise)?a:0,u=1===s?e:st.Deferred(),l=function(e,n,r){return function(i){n[e]=this,r[e]=arguments.length>1?nt.call(arguments):i,r===t?u.notifyWith(n,r):--s||u.resolveWith(n,r)}};if(a>1)for(t=Array(a),n=Array(a),r=Array(a);a>i;i++)o[i]&&st.isFunction(o[i].promise)?o[i].promise().done(l(i,r,o)).fail(u.reject).progress(l(i,n,t)):--s;return s||u.resolveWith(r,o),u.promise()}}),st.support=function(){var n,r,i,o,a,s,u,l,c,f,p=V.createElement("div");if(p.setAttribute("className","t"),p.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",r=p.getElementsByTagName("*"),i=p.getElementsByTagName("a")[0],!r||!i||!r.length)return{};o=V.createElement("select"),a=o.appendChild(V.createElement("option")),s=p.getElementsByTagName("input")[0],i.style.cssText="top:1px;float:left;opacity:.5",n={getSetAttribute:"t"!==p.className,leadingWhitespace:3===p.firstChild.nodeType,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(i.getAttribute("style")),hrefNormalized:"/a"===i.getAttribute("href"),opacity:/^0.5/.test(i.style.opacity),cssFloat:!!i.style.cssFloat,checkOn:!!s.value,optSelected:a.selected,enctype:!!V.createElement("form").enctype,html5Clone:"<:nav></:nav>"!==V.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===V.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},s.checked=!0,n.noCloneChecked=s.cloneNode(!0).checked,o.disabled=!0,n.optDisabled=!a.disabled;try{delete p.test}catch(d){n.deleteExpando=!1}s=V.createElement("input"),s.setAttribute("value",""),n.input=""===s.getAttribute("value"),s.value="t",s.setAttribute("type","radio"),n.radioValue="t"===s.value,s.setAttribute("checked","t"),s.setAttribute("name","t"),u=V.createDocumentFragment(),u.appendChild(s),n.appendChecked=s.checked,n.checkClone=u.cloneNode(!0).cloneNode(!0).lastChild.checked,p.attachEvent&&(p.attachEvent("onclick",function(){n.noCloneEvent=!1}),p.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})p.setAttribute(l="on"+f,"t"),n[f+"Bubbles"]=l in e||p.attributes[l].expando===!1;return p.style.backgroundClip="content-box",p.cloneNode(!0).style.backgroundClip="",n.clearCloneStyle="content-box"===p.style.backgroundClip,st(function(){var r,i,o,a="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",s=V.getElementsByTagName("body")[0];s&&(r=V.createElement("div"),r.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",s.appendChild(r).appendChild(p),p.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=p.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",c=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",n.reliableHiddenOffsets=c&&0===o[0].offsetHeight,p.innerHTML="",p.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",n.boxSizing=4===p.offsetWidth,n.doesNotIncludeMarginInBodyOffset=1!==s.offsetTop,e.getComputedStyle&&(n.pixelPosition="1%"!==(e.getComputedStyle(p,null)||{}).top,n.boxSizingReliable="4px"===(e.getComputedStyle(p,null)||{width:"4px"}).width,i=p.appendChild(V.createElement("div")),i.style.cssText=p.style.cssText=a,i.style.marginRight=i.style.width="0",p.style.width="1px",n.reliableMarginRight=!parseFloat((e.getComputedStyle(i,null)||{}).marginRight)),p.style.zoom!==t&&(p.innerHTML="",p.style.cssText=a+"width:1px;padding:1px;display:inline;zoom:1",n.inlineBlockNeedsLayout=3===p.offsetWidth,p.style.display="block",p.innerHTML="<div></div>",p.firstChild.style.width="5px",n.shrinkWrapBlocks=3!==p.offsetWidth,s.style.zoom=1),s.removeChild(r),r=p=o=i=null)}),r=o=u=a=i=s=null,n}();var wt=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,Nt=/([A-Z])/g;st.extend({cache:{},expando:"jQuery"+(Z+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?st.cache[e[st.expando]]:e[st.expando],!!e&&!s(e)},data:function(e,t,n){return i(e,t,n,!1)},removeData:function(e,t){return o(e,t,!1)},_data:function(e,t,n){return i(e,t,n,!0)},_removeData:function(e,t){return o(e,t,!0)},acceptData:function(e){var t=e.nodeName&&st.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),st.fn.extend({data:function(e,n){var r,i,o=this[0],s=0,u=null;if(e===t){if(this.length&&(u=st.data(o),1===o.nodeType&&!st._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>s;s++)i=r[s].name,i.indexOf("data-")||(i=st.camelCase(i.substring(5)),a(o,i,u[i]));st._data(o,"parsedAttrs",!0)}return u}return"object"==typeof e?this.each(function(){st.data(this,e)}):st.access(this,function(n){return n===t?o?a(o,e,st.data(o,e)):null:(this.each(function(){st.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){st.removeData(this,e)})}}),st.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=st._data(e,n),r&&(!i||st.isArray(r)?i=st._data(e,n,st.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=st.queue(e,t),r=n.length,i=n.shift(),o=st._queueHooks(e,t),a=function(){st.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return st._data(e,n)||st._data(e,n,{empty:st.Callbacks("once memory").add(function(){st._removeData(e,t+"queue"),st._removeData(e,n)})})}}),st.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?st.queue(this[0],e):n===t?this:this.each(function(){var t=st.queue(this,e,n);st._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&st.dequeue(this,e)})},dequeue:function(e){return this.each(function(){st.dequeue(this,e)})},delay:function(e,t){return e=st.fx?st.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=st.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};for("string"!=typeof e&&(n=e,e=t),e=e||"fx";s--;)r=st._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var Ct,kt,Et=/[\t\r\n]/g,St=/\r/g,At=/^(?:input|select|textarea|button|object)$/i,jt=/^(?:a|area)$/i,Dt=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,Lt=/^(?:checked|selected)$/i,Ht=st.support.getSetAttribute,Mt=st.support.input;st.fn.extend({attr:function(e,t){return st.access(this,st.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){st.removeAttr(this,e)})},prop:function(e,t){return st.access(this,st.prop,e,t,arguments.length>1)},removeProp:function(e){return e=st.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,u="string"==typeof e&&e;if(st.isFunction(e))return this.each(function(t){st(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(lt)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(Et," "):" ")){for(o=0;i=t[o++];)0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=st.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(st.isFunction(e))return this.each(function(t){st(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(lt)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(Et," "):"")){for(o=0;i=t[o++];)for(;r.indexOf(" "+i+" ")>=0;)r=r.replace(" "+i+" "," ");n.className=e?st.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return st.isFunction(e)?this.each(function(n){st(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n)for(var i,o=0,a=st(this),s=t,u=e.match(lt)||[];i=u[o++];)s=r?s:!a.hasClass(i),a[s?"addClass":"removeClass"](i);else("undefined"===n||"boolean"===n)&&(this.className&&st._data(this,"__className__",this.className),this.className=this.className||e===!1?"":st._data(this,"__className__")||"")})},hasClass:function(e){for(var t=" "+e+" ",n=0,r=this.length;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(Et," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=st.isFunction(e),this.each(function(r){var o,a=st(this);1===this.nodeType&&(o=i?e.call(this,r,a.val()):e,null==o?o="":"number"==typeof o?o+="":st.isArray(o)&&(o=st.map(o,function(e){return null==e?"":e+""})),n=st.valHooks[this.type]||st.valHooks[this.nodeName.toLowerCase()],n&&"set"in n&&n.set(this,o,"value")!==t||(this.value=o))});if(o)return n=st.valHooks[o.type]||st.valHooks[o.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(o,"value"))!==t?r:(r=o.value,"string"==typeof r?r.replace(St,""):null==r?"":r)}}}),st.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){for(var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,u=0>i?s:o?i:0;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(st.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&st.nodeName(n.parentNode,"optgroup"))){if(t=st(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=st.makeArray(t);return st(e).find("option").each(function(){this.selected=st.inArray(st(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return e.getAttribute===t?st.prop(e,n,r):(a=1!==s||!st.isXMLDoc(e),a&&(n=n.toLowerCase(),o=st.attrHooks[n]||(Dt.test(n)?kt:Ct)),r===t?o&&a&&"get"in o&&null!==(i=o.get(e,n))?i:(e.getAttribute!==t&&(i=e.getAttribute(n)),null==i?t:i):null!==r?o&&a&&"set"in o&&(i=o.set(e,r,n))!==t?i:(e.setAttribute(n,r+""),r):(st.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(lt);if(o&&1===e.nodeType)for(;n=o[i++];)r=st.propFix[n]||n,Dt.test(n)?!Ht&&Lt.test(n)?e[st.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:st.attr(e,n,""),e.removeAttribute(Ht?n:r)},attrHooks:{type:{set:function(e,t){if(!st.support.radioValue&&"radio"===t&&st.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!st.isXMLDoc(e),a&&(n=st.propFix[n]||n,o=st.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):At.test(e.nodeName)||jt.test(e.nodeName)&&e.href?0:t}}}}),kt={get:function(e,n){var r=st.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?Mt&&Ht?null!=i:Lt.test(n)?e[st.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?st.removeAttr(e,n):Mt&&Ht||!Lt.test(n)?e.setAttribute(!Ht&&st.propFix[n]||n,n):e[st.camelCase("default-"+n)]=e[n]=!0,n}},Mt&&Ht||(st.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return st.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t
2 +},set:function(e,n,r){return st.nodeName(e,"input")?(e.defaultValue=n,t):Ct&&Ct.set(e,n,r)}}),Ht||(Ct=st.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},st.attrHooks.contenteditable={get:Ct.get,set:function(e,t,n){Ct.set(e,""===t?!1:t,n)}},st.each(["width","height"],function(e,n){st.attrHooks[n]=st.extend(st.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),st.support.hrefNormalized||(st.each(["href","src","width","height"],function(e,n){st.attrHooks[n]=st.extend(st.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),st.each(["href","src"],function(e,t){st.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),st.support.style||(st.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),st.support.optSelected||(st.propHooks.selected=st.extend(st.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),st.support.enctype||(st.propFix.enctype="encoding"),st.support.checkOn||st.each(["radio","checkbox"],function(){st.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),st.each(["radio","checkbox"],function(){st.valHooks[this]=st.extend(st.valHooks[this],{set:function(e,n){return st.isArray(n)?e.checked=st.inArray(st(e).val(),n)>=0:t}})});var qt=/^(?:input|select|textarea)$/i,_t=/^key/,Ft=/^(?:mouse|contextmenu)|click/,Ot=/^(?:focusinfocus|focusoutblur)$/,Bt=/^([^.]*)(?:\.(.+)|)$/;st.event={global:{},add:function(e,n,r,i,o){var a,s,u,l,c,f,p,d,h,g,m,y=3!==e.nodeType&&8!==e.nodeType&&st._data(e);if(y){for(r.handler&&(a=r,r=a.handler,o=a.selector),r.guid||(r.guid=st.guid++),(l=y.events)||(l=y.events={}),(s=y.handle)||(s=y.handle=function(e){return st===t||e&&st.event.triggered===e.type?t:st.event.dispatch.apply(s.elem,arguments)},s.elem=e),n=(n||"").match(lt)||[""],c=n.length;c--;)u=Bt.exec(n[c])||[],h=m=u[1],g=(u[2]||"").split(".").sort(),p=st.event.special[h]||{},h=(o?p.delegateType:p.bindType)||h,p=st.event.special[h]||{},f=st.extend({type:h,origType:m,data:i,handler:r,guid:r.guid,selector:o,needsContext:o&&st.expr.match.needsContext.test(o),namespace:g.join(".")},a),(d=l[h])||(d=l[h]=[],d.delegateCount=0,p.setup&&p.setup.call(e,i,g,s)!==!1||(e.addEventListener?e.addEventListener(h,s,!1):e.attachEvent&&e.attachEvent("on"+h,s))),p.add&&(p.add.call(e,f),f.handler.guid||(f.handler.guid=r.guid)),o?d.splice(d.delegateCount++,0,f):d.push(f),st.event.global[h]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,m=st.hasData(e)&&st._data(e);if(m&&(u=m.events)){for(t=(t||"").match(lt)||[""],l=t.length;l--;)if(s=Bt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){for(f=st.event.special[d]||{},d=(r?f.delegateType:f.bindType)||d,p=u[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;o--;)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&f.teardown.call(e,h,m.handle)!==!1||st.removeEvent(e,d,m.handle),delete u[d])}else for(d in u)st.event.remove(e,d+t[l],n,r,!0);st.isEmptyObject(u)&&(delete m.handle,st._removeData(e,"events"))}},trigger:function(n,r,i,o){var a,s,u,l,c,f,p,d=[i||V],h=n.type||n,g=n.namespace?n.namespace.split("."):[];if(s=u=i=i||V,3!==i.nodeType&&8!==i.nodeType&&!Ot.test(h+st.event.triggered)&&(h.indexOf(".")>=0&&(g=h.split("."),h=g.shift(),g.sort()),c=0>h.indexOf(":")&&"on"+h,n=n[st.expando]?n:new st.Event(h,"object"==typeof n&&n),n.isTrigger=!0,n.namespace=g.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:st.makeArray(r,[n]),p=st.event.special[h]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!st.isWindow(i)){for(l=p.delegateType||h,Ot.test(l+h)||(s=s.parentNode);s;s=s.parentNode)d.push(s),u=s;u===(i.ownerDocument||V)&&d.push(u.defaultView||u.parentWindow||e)}for(a=0;(s=d[a++])&&!n.isPropagationStopped();)n.type=a>1?l:p.bindType||h,f=(st._data(s,"events")||{})[n.type]&&st._data(s,"handle"),f&&f.apply(s,r),f=c&&s[c],f&&st.acceptData(s)&&f.apply&&f.apply(s,r)===!1&&n.preventDefault();if(n.type=h,!(o||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===h&&st.nodeName(i,"a")||!st.acceptData(i)||!c||!i[h]||st.isWindow(i))){u=i[c],u&&(i[c]=null),st.event.triggered=h;try{i[h]()}catch(m){}st.event.triggered=t,u&&(i[c]=u)}return n.result}},dispatch:function(e){e=st.event.fix(e);var n,r,i,o,a,s=[],u=nt.call(arguments),l=(st._data(this,"events")||{})[e.type]||[],c=st.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){for(s=st.event.handlers.call(this,e,l),n=0;(o=s[n++])&&!e.isPropagationStopped();)for(e.currentTarget=o.elem,r=0;(a=o.handlers[r++])&&!e.isImmediatePropagationStopped();)(!e.namespace_re||e.namespace_re.test(a.namespace))&&(e.handleObj=a,e.data=a.data,i=((st.event.special[a.origType]||{}).handle||a.handler).apply(o.elem,u),i!==t&&(e.result=i)===!1&&(e.preventDefault(),e.stopPropagation()));return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(l.disabled!==!0||"click"!==e.type){for(i=[],r=0;u>r;r++)a=n[r],o=a.selector+" ",i[o]===t&&(i[o]=a.needsContext?st(o,this).index(l)>=0:st.find(o,this,null,[l]).length),i[o]&&i.push(a);i.length&&s.push({elem:l,handlers:i})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[st.expando])return e;var t,n,r=e,i=st.event.fixHooks[e.type]||{},o=i.props?this.props.concat(i.props):this.props;for(e=new st.Event(r),t=o.length;t--;)n=o[t],e[n]=r[n];return e.target||(e.target=r.srcElement||V),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,i.filter?i.filter(e,r):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,a=n.button,s=n.fromElement;return null==e.pageX&&null!=n.clientX&&(r=e.target.ownerDocument||V,i=r.documentElement,o=r.body,e.pageX=n.clientX+(i&&i.scrollLeft||o&&o.scrollLeft||0)-(i&&i.clientLeft||o&&o.clientLeft||0),e.pageY=n.clientY+(i&&i.scrollTop||o&&o.scrollTop||0)-(i&&i.clientTop||o&&o.clientTop||0)),!e.relatedTarget&&s&&(e.relatedTarget=s===e.target?n.toElement:s),e.which||a===t||(e.which=1&a?1:2&a?3:4&a?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return st.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==V.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===V.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=st.extend(new st.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?st.event.trigger(i,null,t):st.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},st.removeEvent=V.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,n,r){var i="on"+n;e.detachEvent&&(e[i]===t&&(e[i]=null),e.detachEvent(i,r))},st.Event=function(e,n){return this instanceof st.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?u:l):this.type=e,n&&st.extend(this,n),this.timeStamp=e&&e.timeStamp||st.now(),this[st.expando]=!0,t):new st.Event(e,n)},st.Event.prototype={isDefaultPrevented:l,isPropagationStopped:l,isImmediatePropagationStopped:l,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=u,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=u,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u,this.stopPropagation()}},st.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){st.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!st.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),st.support.submitBubbles||(st.event.special.submit={setup:function(){return st.nodeName(this,"form")?!1:(st.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=st.nodeName(n,"input")||st.nodeName(n,"button")?n.form:t;r&&!st._data(r,"submitBubbles")&&(st.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),st._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&st.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return st.nodeName(this,"form")?!1:(st.event.remove(this,"._submit"),t)}}),st.support.changeBubbles||(st.event.special.change={setup:function(){return qt.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(st.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),st.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),st.event.simulate("change",this,e,!0)})),!1):(st.event.add(this,"beforeactivate._change",function(e){var t=e.target;qt.test(t.nodeName)&&!st._data(t,"changeBubbles")&&(st.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||st.event.simulate("change",this.parentNode,e,!0)}),st._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return st.event.remove(this,"._change"),!qt.test(this.nodeName)}}),st.support.focusinBubbles||st.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){st.event.simulate(t,e.target,st.event.fix(e),!0)};st.event.special[t]={setup:function(){0===n++&&V.addEventListener(e,r,!0)},teardown:function(){0===--n&&V.removeEventListener(e,r,!0)}}}),st.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(s in e)this.on(s,n,r,e[s],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=l;else if(!i)return this;return 1===o&&(a=i,i=function(e){return st().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=st.guid++)),this.each(function(){st.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,st(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=l),this.each(function(){st.event.remove(this,e,r,n)})},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},trigger:function(e,t){return this.each(function(){st.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?st.event.trigger(e,n,r,!0):t},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),st.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){st.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)},_t.test(t)&&(st.event.fixHooks[t]=st.event.keyHooks),Ft.test(t)&&(st.event.fixHooks[t]=st.event.mouseHooks)}),function(e,t){function n(e){return ht.test(e+"")}function r(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>C.cacheLength&&delete e[t.shift()],e[n]=r}}function i(e){return e[P]=!0,e}function o(e){var t=L.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function a(e,t,n,r){var i,o,a,s,u,l,c,d,h,g;if((t?t.ownerDocument||t:R)!==L&&D(t),t=t||L,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!M&&!r){if(i=gt.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&O(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return Q.apply(n,K.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&W.getByClassName&&t.getElementsByClassName)return Q.apply(n,K.call(t.getElementsByClassName(a),0)),n}if(W.qsa&&!q.test(e)){if(c=!0,d=P,h=t,g=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){for(l=f(e),(c=t.getAttribute("id"))?d=c.replace(vt,"\\$&"):t.setAttribute("id",d),d="[id='"+d+"'] ",u=l.length;u--;)l[u]=d+p(l[u]);h=dt.test(e)&&t.parentNode||t,g=l.join(",")}if(g)try{return Q.apply(n,K.call(h.querySelectorAll(g),0)),n}catch(m){}finally{c||t.removeAttribute("id")}}}return x(e.replace(at,"$1"),t,n,r)}function s(e,t){for(var n=e&&t&&e.nextSibling;n;n=n.nextSibling)if(n===t)return-1;return e?1:-1}function u(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function l(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function c(e){return i(function(t){return t=+t,i(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function f(e,t){var n,r,i,o,s,u,l,c=X[e+" "];if(c)return t?0:c.slice(0);for(s=e,u=[],l=C.preFilter;s;){(!n||(r=ut.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(i=[])),n=!1,(r=lt.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(at," ")}),s=s.slice(n.length));for(o in C.filter)!(r=pt[o].exec(s))||l[o]&&!(r=l[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?a.error(e):X(e,u).slice(0)}function p(e){for(var t=0,n=e.length,r="";n>t;t++)r+=e[t].value;return r}function d(e,t,n){var r=t.dir,i=n&&"parentNode"===t.dir,o=I++;return t.first?function(t,n,o){for(;t=t[r];)if(1===t.nodeType||i)return e(t,n,o)}:function(t,n,a){var s,u,l,c=$+" "+o;if(a){for(;t=t[r];)if((1===t.nodeType||i)&&e(t,n,a))return!0}else for(;t=t[r];)if(1===t.nodeType||i)if(l=t[P]||(t[P]={}),(u=l[r])&&u[0]===c){if((s=u[1])===!0||s===N)return s===!0}else if(u=l[r]=[c],u[1]=e(t,n,a)||N,u[1]===!0)return!0}}function h(e){return e.length>1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function g(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function m(e,t,n,r,o,a){return r&&!r[P]&&(r=m(r)),o&&!o[P]&&(o=m(o,a)),i(function(i,a,s,u){var l,c,f,p=[],d=[],h=a.length,m=i||b(t||"*",s.nodeType?[s]:s,[]),y=!e||!i&&t?m:g(m,p,e,s,u),v=n?o||(i?e:h||r)?[]:a:y;if(n&&n(y,v,s,u),r)for(l=g(v,d),r(l,[],s,u),c=l.length;c--;)(f=l[c])&&(v[d[c]]=!(y[d[c]]=f));if(i){if(o||e){if(o){for(l=[],c=v.length;c--;)(f=v[c])&&l.push(y[c]=f);o(null,v=[],l,u)}for(c=v.length;c--;)(f=v[c])&&(l=o?Z.call(i,f):p[c])>-1&&(i[l]=!(a[l]=f))}}else v=g(v===a?v.splice(h,v.length):v),o?o(null,a,v,u):Q.apply(a,v)})}function y(e){for(var t,n,r,i=e.length,o=C.relative[e[0].type],a=o||C.relative[" "],s=o?1:0,u=d(function(e){return e===t},a,!0),l=d(function(e){return Z.call(t,e)>-1},a,!0),c=[function(e,n,r){return!o&&(r||n!==j)||((t=n).nodeType?u(e,n,r):l(e,n,r))}];i>s;s++)if(n=C.relative[e[s].type])c=[d(h(c),n)];else{if(n=C.filter[e[s].type].apply(null,e[s].matches),n[P]){for(r=++s;i>r&&!C.relative[e[r].type];r++);return m(s>1&&h(c),s>1&&p(e.slice(0,s-1)).replace(at,"$1"),n,r>s&&y(e.slice(s,r)),i>r&&y(e=e.slice(r)),i>r&&p(e))}c.push(n)}return h(c)}function v(e,t){var n=0,r=t.length>0,o=e.length>0,s=function(i,s,u,l,c){var f,p,d,h=[],m=0,y="0",v=i&&[],b=null!=c,x=j,T=i||o&&C.find.TAG("*",c&&s.parentNode||s),w=$+=null==x?1:Math.E;for(b&&(j=s!==L&&s,N=n);null!=(f=T[y]);y++){if(o&&f){for(p=0;d=e[p];p++)if(d(f,s,u)){l.push(f);break}b&&($=w,N=++n)}r&&((f=!d&&f)&&m--,i&&v.push(f))}if(m+=y,r&&y!==m){for(p=0;d=t[p];p++)d(v,h,s,u);if(i){if(m>0)for(;y--;)v[y]||h[y]||(h[y]=G.call(l));h=g(h)}Q.apply(l,h),b&&!i&&h.length>0&&m+t.length>1&&a.uniqueSort(l)}return b&&($=w,j=x),v};return r?i(s):s}function b(e,t,n){for(var r=0,i=t.length;i>r;r++)a(e,t[r],n);return n}function x(e,t,n,r){var i,o,a,s,u,l=f(e);if(!r&&1===l.length){if(o=l[0]=l[0].slice(0),o.length>2&&"ID"===(a=o[0]).type&&9===t.nodeType&&!M&&C.relative[o[1].type]){if(t=C.find.ID(a.matches[0].replace(xt,Tt),t)[0],!t)return n;e=e.slice(o.shift().value.length)}for(i=pt.needsContext.test(e)?-1:o.length-1;i>=0&&(a=o[i],!C.relative[s=a.type]);i--)if((u=C.find[s])&&(r=u(a.matches[0].replace(xt,Tt),dt.test(o[0].type)&&t.parentNode||t))){if(o.splice(i,1),e=r.length&&p(o),!e)return Q.apply(n,K.call(r,0)),n;break}}return S(e,l)(r,t,M,n,dt.test(e)),n}function T(){}var w,N,C,k,E,S,A,j,D,L,H,M,q,_,F,O,B,P="sizzle"+-new Date,R=e.document,W={},$=0,I=0,z=r(),X=r(),U=r(),V=typeof t,Y=1<<31,J=[],G=J.pop,Q=J.push,K=J.slice,Z=J.indexOf||function(e){for(var t=0,n=this.length;n>t;t++)if(this[t]===e)return t;return-1},et="[\\x20\\t\\r\\n\\f]",tt="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",nt=tt.replace("w","w#"),rt="([*^$|!~]?=)",it="\\["+et+"*("+tt+")"+et+"*(?:"+rt+et+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+nt+")|)|)"+et+"*\\]",ot=":("+tt+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+it.replace(3,8)+")*)|.*)\\)|)",at=RegExp("^"+et+"+|((?:^|[^\\\\])(?:\\\\.)*)"+et+"+$","g"),ut=RegExp("^"+et+"*,"+et+"*"),lt=RegExp("^"+et+"*([\\x20\\t\\r\\n\\f>+~])"+et+"*"),ct=RegExp(ot),ft=RegExp("^"+nt+"$"),pt={ID:RegExp("^#("+tt+")"),CLASS:RegExp("^\\.("+tt+")"),NAME:RegExp("^\\[name=['\"]?("+tt+")['\"]?\\]"),TAG:RegExp("^("+tt.replace("w","w*")+")"),ATTR:RegExp("^"+it),PSEUDO:RegExp("^"+ot),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+et+"*(even|odd|(([+-]|)(\\d*)n|)"+et+"*(?:([+-]|)"+et+"*(\\d+)|))"+et+"*\\)|)","i"),needsContext:RegExp("^"+et+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+et+"*((?:-\\d)?\\d*)"+et+"*\\)|)(?=[^-]|$)","i")},dt=/[\x20\t\r\n\f]*[+~]/,ht=/\{\s*\[native code\]\s*\}/,gt=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,mt=/^(?:input|select|textarea|button)$/i,yt=/^h\d$/i,vt=/'|\\/g,bt=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,xt=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,Tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{K.call(H.childNodes,0)[0].nodeType}catch(wt){K=function(e){for(var t,n=[];t=this[e];e++)n.push(t);return n}}E=a.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},D=a.setDocument=function(e){var r=e?e.ownerDocument||e:R;return r!==L&&9===r.nodeType&&r.documentElement?(L=r,H=r.documentElement,M=E(r),W.tagNameNoComments=o(function(e){return e.appendChild(r.createComment("")),!e.getElementsByTagName("*").length}),W.attributes=o(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),W.getByClassName=o(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),W.getByName=o(function(e){e.id=P+0,e.innerHTML="<a name='"+P+"'></a><div name='"+P+"'></div>",H.insertBefore(e,H.firstChild);var t=r.getElementsByName&&r.getElementsByName(P).length===2+r.getElementsByName(P+0).length;return W.getIdNotName=!r.getElementById(P),H.removeChild(e),t}),C.attrHandle=o(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==V&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},W.getIdNotName?(C.find.ID=function(e,t){if(typeof t.getElementById!==V&&!M){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},C.filter.ID=function(e){var t=e.replace(xt,Tt);return function(e){return e.getAttribute("id")===t}}):(C.find.ID=function(e,n){if(typeof n.getElementById!==V&&!M){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==V&&r.getAttributeNode("id").value===e?[r]:t:[]}},C.filter.ID=function(e){var t=e.replace(xt,Tt);return function(e){var n=typeof e.getAttributeNode!==V&&e.getAttributeNode("id");return n&&n.value===t}}),C.find.TAG=W.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==V?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i];i++)1===n.nodeType&&r.push(n);return r}return o},C.find.NAME=W.getByName&&function(e,n){return typeof n.getElementsByName!==V?n.getElementsByName(name):t},C.find.CLASS=W.getByClassName&&function(e,n){return typeof n.getElementsByClassName===V||M?t:n.getElementsByClassName(e)},_=[],q=[":focus"],(W.qsa=n(r.querySelectorAll))&&(o(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||q.push("\\["+et+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||q.push(":checked")}),o(function(e){e.innerHTML="<input type='hidden' i=''/>",e.querySelectorAll("[i^='']").length&&q.push("[*^$]="+et+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),q.push(",.*:")})),(W.matchesSelector=n(F=H.matchesSelector||H.mozMatchesSelector||H.webkitMatchesSelector||H.oMatchesSelector||H.msMatchesSelector))&&o(function(e){W.disconnectedMatch=F.call(e,"div"),F.call(e,"[s!='']:x"),_.push("!=",ot)}),q=RegExp(q.join("|")),_=RegExp(_.join("|")),O=n(H.contains)||H.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},B=H.compareDocumentPosition?function(e,t){var n;return e===t?(A=!0,0):(n=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&n||e.parentNode&&11===e.parentNode.nodeType?e===r||O(R,e)?-1:t===r||O(R,t)?1:0:4&n?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var n,i=0,o=e.parentNode,a=t.parentNode,u=[e],l=[t];if(e===t)return A=!0,0;if(e.sourceIndex&&t.sourceIndex)return(~t.sourceIndex||Y)-(O(R,e)&&~e.sourceIndex||Y);if(!o||!a)return e===r?-1:t===r?1:o?-1:a?1:0;if(o===a)return s(e,t);for(n=e;n=n.parentNode;)u.unshift(n);for(n=t;n=n.parentNode;)l.unshift(n);for(;u[i]===l[i];)i++;return i?s(u[i],l[i]):u[i]===R?-1:l[i]===R?1:0},A=!1,[0,0].sort(B),W.detectDuplicates=A,L):L},a.matches=function(e,t){return a(e,null,null,t)},a.matchesSelector=function(e,t){if((e.ownerDocument||e)!==L&&D(e),t=t.replace(bt,"='$1']"),!(!W.matchesSelector||M||_&&_.test(t)||q.test(t)))try{var n=F.call(e,t);if(n||W.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return a(t,L,null,[e]).length>0},a.contains=function(e,t){return(e.ownerDocument||e)!==L&&D(e),O(e,t)},a.attr=function(e,t){var n;return(e.ownerDocument||e)!==L&&D(e),M||(t=t.toLowerCase()),(n=C.attrHandle[t])?n(e):M||W.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},a.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},a.uniqueSort=function(e){var t,n=[],r=1,i=0;if(A=!W.detectDuplicates,e.sort(B),A){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));for(;i--;)e.splice(n[i],1)}return e},k=a.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=k(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=k(t);return n},C=a.selectors={cacheLength:50,createPseudo:i,match:pt,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(xt,Tt),e[3]=(e[4]||e[5]||"").replace(xt,Tt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||a.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&a.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return pt.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&ct.test(n)&&(t=f(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(xt,Tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=z[e+" "];return t||(t=RegExp("(^|"+et+")"+e+"("+et+"|$)"))&&z(e,function(e){return t.test(e.className||typeof e.getAttribute!==V&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=a.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.substr(i.length-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,p,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s;if(m){if(o){for(;g;){for(f=t;f=f[g];)if(s?f.nodeName.toLowerCase()===y:1===f.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){for(c=m[P]||(m[P]={}),l=c[e]||[],d=l[0]===$&&l[1],p=l[0]===$&&l[2],f=d&&m.childNodes[d];f=++d&&f&&f[g]||(p=d=0)||h.pop();)if(1===f.nodeType&&++p&&f===t){c[e]=[$,d,p];break}}else if(v&&(l=(t[P]||(t[P]={}))[e])&&l[0]===$)p=l[1];else for(;(f=++d&&f&&f[g]||(p=d=0)||h.pop())&&((s?f.nodeName.toLowerCase()!==y:1!==f.nodeType)||!++p||(v&&((f[P]||(f[P]={}))[e]=[$,p]),f!==t)););return p-=i,p===r||0===p%r&&p/r>=0}}},PSEUDO:function(e,t){var n,r=C.pseudos[e]||C.setFilters[e.toLowerCase()]||a.error("unsupported pseudo: "+e);return r[P]?r(t):r.length>1?(n=[e,e,"",t],C.setFilters.hasOwnProperty(e.toLowerCase())?i(function(e,n){for(var i,o=r(e,t),a=o.length;a--;)i=Z.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:i(function(e){var t=[],n=[],r=S(e.replace(at,"$1"));return r[P]?i(function(e,t,n,i){for(var o,a=r(e,null,i,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:i(function(e){return function(t){return a(e,t).length>0}}),contains:i(function(e){return function(t){return(t.textContent||t.innerText||k(t)).indexOf(e)>-1}}),lang:i(function(e){return ft.test(e||"")||a.error("unsupported lang: "+e),e=e.replace(xt,Tt).toLowerCase(),function(t){var n;do if(n=M?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.lang)return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===H},focus:function(e){return e===L.activeElement&&(!L.hasFocus||L.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!C.pseudos.empty(e)},header:function(e){return yt.test(e.nodeName)},input:function(e){return mt.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:c(function(){return[0]}),last:c(function(e,t){return[t-1]}),eq:c(function(e,t,n){return[0>n?n+t:n]}),even:c(function(e,t){for(var n=0;t>n;n+=2)e.push(n);return e}),odd:c(function(e,t){for(var n=1;t>n;n+=2)e.push(n);return e}),lt:c(function(e,t,n){for(var r=0>n?n+t:n;--r>=0;)e.push(r);return e}),gt:c(function(e,t,n){for(var r=0>n?n+t:n;t>++r;)e.push(r);return e})}};for(w in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})C.pseudos[w]=u(w);for(w in{submit:!0,reset:!0})C.pseudos[w]=l(w);S=a.compile=function(e,t){var n,r=[],i=[],o=U[e+" "];if(!o){for(t||(t=f(e)),n=t.length;n--;)o=y(t[n]),o[P]?r.push(o):i.push(o);o=U(e,v(i,r))}return o},C.pseudos.nth=C.pseudos.eq,C.filters=T.prototype=C.pseudos,C.setFilters=new T,D(),a.attr=st.attr,st.find=a,st.expr=a.selectors,st.expr[":"]=st.expr.pseudos,st.unique=a.uniqueSort,st.text=a.getText,st.isXMLDoc=a.isXML,st.contains=a.contains}(e);var Pt=/Until$/,Rt=/^(?:parents|prev(?:Until|All))/,Wt=/^.[^:#\[\.,]*$/,$t=st.expr.match.needsContext,It={children:!0,contents:!0,next:!0,prev:!0};st.fn.extend({find:function(e){var t,n,r;if("string"!=typeof e)return r=this,this.pushStack(st(e).filter(function(){for(t=0;r.length>t;t++)if(st.contains(r[t],this))return!0}));for(n=[],t=0;this.length>t;t++)st.find(e,this[t],n);return n=this.pushStack(st.unique(n)),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=st(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(st.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(f(this,e,!1))},filter:function(e){return this.pushStack(f(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?$t.test(e)?st(e,this.context).index(this[0])>=0:st.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){for(var n,r=0,i=this.length,o=[],a=$t.test(e)||"string"!=typeof e?st(e,t||this.context):0;i>r;r++)for(n=this[r];n&&n.ownerDocument&&n!==t&&11!==n.nodeType;){if(a?a.index(n)>-1:st.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}return this.pushStack(o.length>1?st.unique(o):o)},index:function(e){return e?"string"==typeof e?st.inArray(this[0],st(e)):st.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?st(e,t):st.makeArray(e&&e.nodeType?[e]:e),r=st.merge(this.get(),n);return this.pushStack(st.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),st.fn.andSelf=st.fn.addBack,st.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return st.dir(e,"parentNode")},parentsUntil:function(e,t,n){return st.dir(e,"parentNode",n)},next:function(e){return c(e,"nextSibling")},prev:function(e){return c(e,"previousSibling")
3 +},nextAll:function(e){return st.dir(e,"nextSibling")},prevAll:function(e){return st.dir(e,"previousSibling")},nextUntil:function(e,t,n){return st.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return st.dir(e,"previousSibling",n)},siblings:function(e){return st.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return st.sibling(e.firstChild)},contents:function(e){return st.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:st.merge([],e.childNodes)}},function(e,t){st.fn[e]=function(n,r){var i=st.map(this,t,n);return Pt.test(e)||(r=n),r&&"string"==typeof r&&(i=st.filter(r,i)),i=this.length>1&&!It[e]?st.unique(i):i,this.length>1&&Rt.test(e)&&(i=i.reverse()),this.pushStack(i)}}),st.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?st.find.matchesSelector(t[0],e)?[t[0]]:[]:st.find.matches(e,t)},dir:function(e,n,r){for(var i=[],o=e[n];o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!st(o).is(r));)1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});var zt="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",Xt=/ jQuery\d+="(?:null|\d+)"/g,Ut=RegExp("<(?:"+zt+")[\\s/>]","i"),Vt=/^\s+/,Yt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,Jt=/<([\w:]+)/,Gt=/<tbody/i,Qt=/<|&#?\w+;/,Kt=/<(?:script|style|link)/i,Zt=/^(?:checkbox|radio)$/i,en=/checked\s*(?:[^=]|=\s*.checked.)/i,tn=/^$|\/(?:java|ecma)script/i,nn=/^true\/(.*)/,rn=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,on={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:st.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},an=p(V),sn=an.appendChild(V.createElement("div"));on.optgroup=on.option,on.tbody=on.tfoot=on.colgroup=on.caption=on.thead,on.th=on.td,st.fn.extend({text:function(e){return st.access(this,function(e){return e===t?st.text(this):this.empty().append((this[0]&&this[0].ownerDocument||V).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(st.isFunction(e))return this.each(function(t){st(this).wrapAll(e.call(this,t))});if(this[0]){var t=st(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstChild&&1===e.firstChild.nodeType;)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return st.isFunction(e)?this.each(function(t){st(this).wrapInner(e.call(this,t))}):this.each(function(){var t=st(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=st.isFunction(e);return this.each(function(n){st(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){st.nodeName(this,"body")||st(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){for(var n,r=0;null!=(n=this[r]);r++)(!e||st.filter(e,[n]).length>0)&&(t||1!==n.nodeType||st.cleanData(b(n)),n.parentNode&&(t&&st.contains(n.ownerDocument,n)&&m(b(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){for(var e,t=0;null!=(e=this[t]);t++){for(1===e.nodeType&&st.cleanData(b(e,!1));e.firstChild;)e.removeChild(e.firstChild);e.options&&st.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return st.clone(this,e,t)})},html:function(e){return st.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(Xt,""):t;if(!("string"!=typeof e||Kt.test(e)||!st.support.htmlSerialize&&Ut.test(e)||!st.support.leadingWhitespace&&Vt.test(e)||on[(Jt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(Yt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(st.cleanData(b(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=st.isFunction(e);return t||"string"==typeof e||(e=st(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;(n&&1===this.nodeType||11===this.nodeType)&&(st(this).remove(),t?t.parentNode.insertBefore(e,t):n.appendChild(e))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=et.apply([],e);var i,o,a,s,u,l,c=0,f=this.length,p=this,m=f-1,y=e[0],v=st.isFunction(y);if(v||!(1>=f||"string"!=typeof y||st.support.checkClone)&&en.test(y))return this.each(function(i){var o=p.eq(i);v&&(e[0]=y.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(f&&(i=st.buildFragment(e,this[0].ownerDocument,!1,this),o=i.firstChild,1===i.childNodes.length&&(i=o),o)){for(n=n&&st.nodeName(o,"tr"),a=st.map(b(i,"script"),h),s=a.length;f>c;c++)u=i,c!==m&&(u=st.clone(u,!0,!0),s&&st.merge(a,b(u,"script"))),r.call(n&&st.nodeName(this[c],"table")?d(this[c],"tbody"):this[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,st.map(a,g),c=0;s>c;c++)u=a[c],tn.test(u.type||"")&&!st._data(u,"globalEval")&&st.contains(l,u)&&(u.src?st.ajax({url:u.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):st.globalEval((u.text||u.textContent||u.innerHTML||"").replace(rn,"")));i=o=null}return this}}),st.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){st.fn[e]=function(e){for(var n,r=0,i=[],o=st(e),a=o.length-1;a>=r;r++)n=r===a?this:this.clone(!0),st(o[r])[t](n),tt.apply(i,n.get());return this.pushStack(i)}}),st.extend({clone:function(e,t,n){var r,i,o,a,s,u=st.contains(e.ownerDocument,e);if(st.support.html5Clone||st.isXMLDoc(e)||!Ut.test("<"+e.nodeName+">")?s=e.cloneNode(!0):(sn.innerHTML=e.outerHTML,sn.removeChild(s=sn.firstChild)),!(st.support.noCloneEvent&&st.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||st.isXMLDoc(e)))for(r=b(s),i=b(e),a=0;null!=(o=i[a]);++a)r[a]&&v(o,r[a]);if(t)if(n)for(i=i||b(e),r=r||b(s),a=0;null!=(o=i[a]);a++)y(o,r[a]);else y(e,s);return r=b(s,"script"),r.length>0&&m(r,!u&&b(e,"script")),r=i=o=null,s},buildFragment:function(e,t,n,r){for(var i,o,a,s,u,l,c,f=e.length,d=p(t),h=[],g=0;f>g;g++)if(o=e[g],o||0===o)if("object"===st.type(o))st.merge(h,o.nodeType?[o]:o);else if(Qt.test(o)){for(s=s||d.appendChild(t.createElement("div")),a=(Jt.exec(o)||["",""])[1].toLowerCase(),u=on[a]||on._default,s.innerHTML=u[1]+o.replace(Yt,"<$1></$2>")+u[2],c=u[0];c--;)s=s.lastChild;if(!st.support.leadingWhitespace&&Vt.test(o)&&h.push(t.createTextNode(Vt.exec(o)[0])),!st.support.tbody)for(o="table"!==a||Gt.test(o)?"<table>"!==u[1]||Gt.test(o)?0:s:s.firstChild,c=o&&o.childNodes.length;c--;)st.nodeName(l=o.childNodes[c],"tbody")&&!l.childNodes.length&&o.removeChild(l);for(st.merge(h,s.childNodes),s.textContent="";s.firstChild;)s.removeChild(s.firstChild);s=d.lastChild}else h.push(t.createTextNode(o));for(s&&d.removeChild(s),st.support.appendChecked||st.grep(b(h,"input"),x),g=0;o=h[g++];)if((!r||-1===st.inArray(o,r))&&(i=st.contains(o.ownerDocument,o),s=b(d.appendChild(o),"script"),i&&m(s),n))for(c=0;o=s[c++];)tn.test(o.type||"")&&n.push(o);return s=null,d},cleanData:function(e,n){for(var r,i,o,a,s=0,u=st.expando,l=st.cache,c=st.support.deleteExpando,f=st.event.special;null!=(o=e[s]);s++)if((n||st.acceptData(o))&&(i=o[u],r=i&&l[i])){if(r.events)for(a in r.events)f[a]?st.event.remove(o,a):st.removeEvent(o,a,r.handle);l[i]&&(delete l[i],c?delete o[u]:o.removeAttribute!==t?o.removeAttribute(u):o[u]=null,K.push(i))}}});var un,ln,cn,fn=/alpha\([^)]*\)/i,pn=/opacity\s*=\s*([^)]*)/,dn=/^(top|right|bottom|left)$/,hn=/^(none|table(?!-c[ea]).+)/,gn=/^margin/,mn=RegExp("^("+ut+")(.*)$","i"),yn=RegExp("^("+ut+")(?!px)[a-z%]+$","i"),vn=RegExp("^([+-])=("+ut+")","i"),bn={BODY:"block"},xn={position:"absolute",visibility:"hidden",display:"block"},Tn={letterSpacing:0,fontWeight:400},wn=["Top","Right","Bottom","Left"],Nn=["Webkit","O","Moz","ms"];st.fn.extend({css:function(e,n){return st.access(this,function(e,n,r){var i,o,a={},s=0;if(st.isArray(n)){for(i=ln(e),o=n.length;o>s;s++)a[n[s]]=st.css(e,n[s],!1,i);return a}return r!==t?st.style(e,n,r):st.css(e,n)},e,n,arguments.length>1)},show:function(){return N(this,!0)},hide:function(){return N(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:w(this))?st(this).show():st(this).hide()})}}),st.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=un(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":st.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=st.camelCase(n),l=e.style;if(n=st.cssProps[u]||(st.cssProps[u]=T(l,u)),s=st.cssHooks[n]||st.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=vn.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(st.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||st.cssNumber[u]||(r+="px"),st.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=st.camelCase(n);return n=st.cssProps[u]||(st.cssProps[u]=T(e.style,u)),s=st.cssHooks[n]||st.cssHooks[u],s&&"get"in s&&(o=s.get(e,!0,r)),o===t&&(o=un(e,n,i)),"normal"===o&&n in Tn&&(o=Tn[n]),r?(a=parseFloat(o),r===!0||st.isNumeric(a)?a||0:o):o},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),e.getComputedStyle?(ln=function(t){return e.getComputedStyle(t,null)},un=function(e,n,r){var i,o,a,s=r||ln(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||st.contains(e.ownerDocument,e)||(u=st.style(e,n)),yn.test(u)&&gn.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):V.documentElement.currentStyle&&(ln=function(e){return e.currentStyle},un=function(e,n,r){var i,o,a,s=r||ln(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),yn.test(u)&&!dn.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u}),st.each(["height","width"],function(e,n){st.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&hn.test(st.css(e,"display"))?st.swap(e,xn,function(){return E(e,n,i)}):E(e,n,i):t},set:function(e,t,r){var i=r&&ln(e);return C(e,t,r?k(e,n,r,st.support.boxSizing&&"border-box"===st.css(e,"boxSizing",!1,i),i):0)}}}),st.support.opacity||(st.cssHooks.opacity={get:function(e,t){return pn.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=st.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===st.trim(o.replace(fn,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=fn.test(o)?o.replace(fn,i):o+" "+i)}}),st(function(){st.support.reliableMarginRight||(st.cssHooks.marginRight={get:function(e,n){return n?st.swap(e,{display:"inline-block"},un,[e,"marginRight"]):t}}),!st.support.pixelPosition&&st.fn.position&&st.each(["top","left"],function(e,n){st.cssHooks[n]={get:function(e,r){return r?(r=un(e,n),yn.test(r)?st(e).position()[n]+"px":r):t}}})}),st.expr&&st.expr.filters&&(st.expr.filters.hidden=function(e){return 0===e.offsetWidth&&0===e.offsetHeight||!st.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||st.css(e,"display"))},st.expr.filters.visible=function(e){return!st.expr.filters.hidden(e)}),st.each({margin:"",padding:"",border:"Width"},function(e,t){st.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];4>r;r++)i[e+wn[r]+t]=o[r]||o[r-2]||o[0];return i}},gn.test(e)||(st.cssHooks[e+t].set=C)});var Cn=/%20/g,kn=/\[\]$/,En=/\r?\n/g,Sn=/^(?:submit|button|image|reset)$/i,An=/^(?:input|select|textarea|keygen)/i;st.fn.extend({serialize:function(){return st.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=st.prop(this,"elements");return e?st.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!st(this).is(":disabled")&&An.test(this.nodeName)&&!Sn.test(e)&&(this.checked||!Zt.test(e))}).map(function(e,t){var n=st(this).val();return null==n?null:st.isArray(n)?st.map(n,function(e){return{name:t.name,value:e.replace(En,"\r\n")}}):{name:t.name,value:n.replace(En,"\r\n")}}).get()}}),st.param=function(e,n){var r,i=[],o=function(e,t){t=st.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=st.ajaxSettings&&st.ajaxSettings.traditional),st.isArray(e)||e.jquery&&!st.isPlainObject(e))st.each(e,function(){o(this.name,this.value)});else for(r in e)j(r,e[r],n,o);return i.join("&").replace(Cn,"+")};var jn,Dn,Ln=st.now(),Hn=/\?/,Mn=/#.*$/,qn=/([?&])_=[^&]*/,_n=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Fn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,On=/^(?:GET|HEAD)$/,Bn=/^\/\//,Pn=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Rn=st.fn.load,Wn={},$n={},In="*/".concat("*");try{Dn=Y.href}catch(zn){Dn=V.createElement("a"),Dn.href="",Dn=Dn.href}jn=Pn.exec(Dn.toLowerCase())||[],st.fn.load=function(e,n,r){if("string"!=typeof e&&Rn)return Rn.apply(this,arguments);var i,o,a,s=this,u=e.indexOf(" ");return u>=0&&(i=e.slice(u,e.length),e=e.slice(0,u)),st.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(o="POST"),s.length>0&&st.ajax({url:e,type:o,dataType:"html",data:n}).done(function(e){a=arguments,s.html(i?st("<div>").append(st.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,a||[e.responseText,t,e])}),this},st.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){st.fn[t]=function(e){return this.on(t,e)}}),st.each(["get","post"],function(e,n){st[n]=function(e,r,i,o){return st.isFunction(r)&&(o=o||i,i=r,r=t),st.ajax({url:e,type:n,dataType:o,data:r,success:i})}}),st.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Dn,type:"GET",isLocal:Fn.test(jn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":In,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":st.parseJSON,"text xml":st.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?H(H(e,st.ajaxSettings),t):H(st.ajaxSettings,e)},ajaxPrefilter:D(Wn),ajaxTransport:D($n),ajax:function(e,n){function r(e,n,r,s){var l,f,v,b,T,N=n;2!==x&&(x=2,u&&clearTimeout(u),i=t,a=s||"",w.readyState=e>0?4:0,r&&(b=M(p,w,r)),e>=200&&300>e||304===e?(p.ifModified&&(T=w.getResponseHeader("Last-Modified"),T&&(st.lastModified[o]=T),T=w.getResponseHeader("etag"),T&&(st.etag[o]=T)),304===e?(l=!0,N="notmodified"):(l=q(p,b),N=l.state,f=l.data,v=l.error,l=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),w.status=e,w.statusText=(n||N)+"",l?g.resolveWith(d,[f,N,w]):g.rejectWith(d,[w,N,v]),w.statusCode(y),y=t,c&&h.trigger(l?"ajaxSuccess":"ajaxError",[w,p,l?f:v]),m.fireWith(d,[w,N]),c&&(h.trigger("ajaxComplete",[w,p]),--st.active||st.event.trigger("ajaxStop")))}"object"==typeof e&&(n=e,e=t),n=n||{};var i,o,a,s,u,l,c,f,p=st.ajaxSetup({},n),d=p.context||p,h=p.context&&(d.nodeType||d.jquery)?st(d):st.event,g=st.Deferred(),m=st.Callbacks("once memory"),y=p.statusCode||{},v={},b={},x=0,T="canceled",w={readyState:0,getResponseHeader:function(e){var t;if(2===x){if(!s)for(s={};t=_n.exec(a);)s[t[1].toLowerCase()]=t[2];t=s[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===x?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=b[n]=b[n]||e,v[e]=t),this},overrideMimeType:function(e){return x||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>x)for(t in e)y[t]=[y[t],e[t]];else w.always(e[w.status]);return this},abort:function(e){var t=e||T;return i&&i.abort(t),r(0,t),this}};if(g.promise(w).complete=m.add,w.success=w.done,w.error=w.fail,p.url=((e||p.url||Dn)+"").replace(Mn,"").replace(Bn,jn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=st.trim(p.dataType||"*").toLowerCase().match(lt)||[""],null==p.crossDomain&&(l=Pn.exec(p.url.toLowerCase()),p.crossDomain=!(!l||l[1]===jn[1]&&l[2]===jn[2]&&(l[3]||("http:"===l[1]?80:443))==(jn[3]||("http:"===jn[1]?80:443)))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=st.param(p.data,p.traditional)),L(Wn,p,n,w),2===x)return w;c=p.global,c&&0===st.active++&&st.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!On.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(Hn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=qn.test(o)?o.replace(qn,"$1_="+Ln++):o+(Hn.test(o)?"&":"?")+"_="+Ln++)),p.ifModified&&(st.lastModified[o]&&w.setRequestHeader("If-Modified-Since",st.lastModified[o]),st.etag[o]&&w.setRequestHeader("If-None-Match",st.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&w.setRequestHeader("Content-Type",p.contentType),w.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+In+"; q=0.01":""):p.accepts["*"]);for(f in p.headers)w.setRequestHeader(f,p.headers[f]);if(p.beforeSend&&(p.beforeSend.call(d,w,p)===!1||2===x))return w.abort();T="abort";for(f in{success:1,error:1,complete:1})w[f](p[f]);if(i=L($n,p,n,w)){w.readyState=1,c&&h.trigger("ajaxSend",[w,p]),p.async&&p.timeout>0&&(u=setTimeout(function(){w.abort("timeout")},p.timeout));try{x=1,i.send(v,r)}catch(N){if(!(2>x))throw N;r(-1,N)}}else r(-1,"No Transport");return w},getScript:function(e,n){return st.get(e,t,n,"script")},getJSON:function(e,t,n){return st.get(e,t,n,"json")}}),st.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return st.globalEval(e),e}}}),st.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),st.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=V.head||st("head")[0]||V.documentElement;return{send:function(t,i){n=V.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Xn=[],Un=/(=)\?(?=&|$)|\?\?/;st.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xn.pop()||st.expando+"_"+Ln++;return this[e]=!0,e}}),st.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,u=n.jsonp!==!1&&(Un.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Un.test(n.data)&&"data");return u||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=st.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,u?n[u]=n[u].replace(Un,"$1"+o):n.jsonp!==!1&&(n.url+=(Hn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||st.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Xn.push(o)),s&&st.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Vn,Yn,Jn=0,Gn=e.ActiveXObject&&function(){var e;for(e in Vn)Vn[e](t,!0)};st.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&_()||F()}:_,Yn=st.ajaxSettings.xhr(),st.support.cors=!!Yn&&"withCredentials"in Yn,Yn=st.support.ajax=!!Yn,Yn&&st.ajaxTransport(function(n){if(!n.crossDomain||st.support.cors){var r;return{send:function(i,o){var a,s,u=n.xhr();if(n.username?u.open(n.type,n.url,n.async,n.username,n.password):u.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)u[s]=n.xhrFields[s];n.mimeType&&u.overrideMimeType&&u.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)u.setRequestHeader(s,i[s])}catch(l){}u.send(n.hasContent&&n.data||null),r=function(e,i){var s,l,c,f,p;try{if(r&&(i||4===u.readyState))if(r=t,a&&(u.onreadystatechange=st.noop,Gn&&delete Vn[a]),i)4!==u.readyState&&u.abort();else{f={},s=u.status,p=u.responseXML,c=u.getAllResponseHeaders(),p&&p.documentElement&&(f.xml=p),"string"==typeof u.responseText&&(f.text=u.responseText);try{l=u.statusText}catch(d){l=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=f.text?200:404}}catch(h){i||o(-1,h)}f&&o(s,l,f,c)},n.async?4===u.readyState?setTimeout(r):(a=++Jn,Gn&&(Vn||(Vn={},st(e).unload(Gn)),Vn[a]=r),u.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Qn,Kn,Zn=/^(?:toggle|show|hide)$/,er=RegExp("^(?:([+-])=|)("+ut+")([a-z%]*)$","i"),tr=/queueHooks$/,nr=[W],rr={"*":[function(e,t){var n,r,i=this.createTween(e,t),o=er.exec(t),a=i.cur(),s=+a||0,u=1,l=20;if(o){if(n=+o[2],r=o[3]||(st.cssNumber[e]?"":"px"),"px"!==r&&s){s=st.css(i.elem,e,!0)||n||1;do u=u||".5",s/=u,st.style(i.elem,e,s+r);while(u!==(u=i.cur()/a)&&1!==u&&--l)}i.unit=r,i.start=s,i.end=o[1]?s+(o[1]+1)*n:n}return i}]};st.Animation=st.extend(P,{tweener:function(e,t){st.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");for(var n,r=0,i=e.length;i>r;r++)n=e[r],rr[n]=rr[n]||[],rr[n].unshift(t)},prefilter:function(e,t){t?nr.unshift(e):nr.push(e)}}),st.Tween=$,$.prototype={constructor:$,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(st.cssNumber[n]?"":"px")},cur:function(){var e=$.propHooks[this.prop];return e&&e.get?e.get(this):$.propHooks._default.get(this)},run:function(e){var t,n=$.propHooks[this.prop];return this.pos=t=this.options.duration?st.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):$.propHooks._default.set(this),this}},$.prototype.init.prototype=$.prototype,$.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=st.css(e.elem,e.prop,"auto"),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){st.fx.step[e.prop]?st.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[st.cssProps[e.prop]]||st.cssHooks[e.prop])?st.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},$.propHooks.scrollTop=$.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},st.each(["toggle","show","hide"],function(e,t){var n=st.fn[t];st.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(I(t,!0),e,r,i)}}),st.fn.extend({fadeTo:function(e,t,n,r){return this.filter(w).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=st.isEmptyObject(e),o=st.speed(t,n,r),a=function(){var t=P(this,st.extend({},e),o);a.finish=function(){t.stop(!0)},(i||st._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=st.timers,a=st._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&tr.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&st.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=st._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=st.timers,a=r?r.length:0;for(n.finish=!0,st.queue(this,e,[]),i&&i.cur&&i.cur.finish&&i.cur.finish.call(this),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}}),st.each({slideDown:I("show"),slideUp:I("hide"),slideToggle:I("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){st.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),st.speed=function(e,t,n){var r=e&&"object"==typeof e?st.extend({},e):{complete:n||!n&&t||st.isFunction(e)&&e,duration:e,easing:n&&t||t&&!st.isFunction(t)&&t};return r.duration=st.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in st.fx.speeds?st.fx.speeds[r.duration]:st.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){st.isFunction(r.old)&&r.old.call(this),r.queue&&st.dequeue(this,r.queue)},r},st.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},st.timers=[],st.fx=$.prototype.init,st.fx.tick=function(){var e,n=st.timers,r=0;for(Qn=st.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||st.fx.stop(),Qn=t},st.fx.timer=function(e){e()&&st.timers.push(e)&&st.fx.start()},st.fx.interval=13,st.fx.start=function(){Kn||(Kn=setInterval(st.fx.tick,st.fx.interval))},st.fx.stop=function(){clearInterval(Kn),Kn=null},st.fx.speeds={slow:600,fast:200,_default:400},st.fx.step={},st.expr&&st.expr.filters&&(st.expr.filters.animated=function(e){return st.grep(st.timers,function(t){return e===t.elem}).length}),st.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){st.offset.setOffset(this,e,t)});var n,r,i={top:0,left:0},o=this[0],a=o&&o.ownerDocument;if(a)return n=a.documentElement,st.contains(n,o)?(o.getBoundingClientRect!==t&&(i=o.getBoundingClientRect()),r=z(a),{top:i.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:i.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):i},st.offset={setOffset:function(e,t,n){var r=st.css(e,"position");"static"===r&&(e.style.position="relative");var i,o,a=st(e),s=a.offset(),u=st.css(e,"top"),l=st.css(e,"left"),c=("absolute"===r||"fixed"===r)&&st.inArray("auto",[u,l])>-1,f={},p={};c?(p=a.position(),i=p.top,o=p.left):(i=parseFloat(u)||0,o=parseFloat(l)||0),st.isFunction(t)&&(t=t.call(e,n,s)),null!=t.top&&(f.top=t.top-s.top+i),null!=t.left&&(f.left=t.left-s.left+o),"using"in t?t.using.call(e,f):a.css(f)}},st.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===st.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),st.nodeName(e[0],"html")||(n=e.offset()),n.top+=st.css(e[0],"borderTopWidth",!0),n.left+=st.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-st.css(r,"marginTop",!0),left:t.left-n.left-st.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent||V.documentElement;e&&!st.nodeName(e,"html")&&"static"===st.css(e,"position");)e=e.offsetParent;return e||V.documentElement})}}),st.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);st.fn[e]=function(i){return st.access(this,function(e,i,o){var a=z(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?st(a).scrollLeft():o,r?o:st(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}}),st.each({Height:"height",Width:"width"},function(e,n){st.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){st.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return st.access(this,function(n,r,i){var o;return st.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?st.css(n,r,s):st.style(n,r,i,s)},n,a?i:t,a,null)}})}),e.jQuery=e.$=st,"function"==typeof define&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return st})})(window);
4 +//@ sourceMappingURL=jquery.min.map
\ No newline at end of file
examples/todomvc-backbone/css/base.css new
+414
@@ -0,0 +1,414 @@
1 +html,
2 +body {
3 + margin: 0;
4 + padding: 0;
5 +}
6 +
7 +button {
8 + margin: 0;
9 + padding: 0;
10 + border: 0;
11 + background: none;
12 + font-size: 100%;
13 + vertical-align: baseline;
14 + font-family: inherit;
15 + color: inherit;
16 + -webkit-appearance: none;
17 + /*-moz-appearance: none;*/
18 + -ms-appearance: none;
19 + -o-appearance: none;
20 + appearance: none;
21 +}
22 +
23 +body {
24 + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
25 + line-height: 1.4em;
26 + background: #eaeaea url('bg.png');
27 + color: #4d4d4d;
28 + width: 550px;
29 + margin: 0 auto;
30 + -webkit-font-smoothing: antialiased;
31 + -moz-font-smoothing: antialiased;
32 + -ms-font-smoothing: antialiased;
33 + -o-font-smoothing: antialiased;
34 + font-smoothing: antialiased;
35 +}
36 +
37 +.todoapp {
38 + background: #fff;
39 + background: rgba(255, 255, 255, 0.9);
40 + margin: 130px 0 40px 0;
41 + border: 1px solid #ccc;
42 + position: relative;
43 + border-top-left-radius: 2px;
44 + border-top-right-radius: 2px;
45 + box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
46 + 0 25px 50px 0 rgba(0, 0, 0, 0.15);
47 +}
48 +
49 +.todoapp:before {
50 + content: '';
51 + border-left: 1px solid #f5d6d6;
52 + border-right: 1px solid #f5d6d6;
53 + width: 2px;
54 + position: absolute;
55 + top: 0;
56 + left: 40px;
57 + height: 100%;
58 +}
59 +
60 +.todoapp input::-webkit-input-placeholder {
61 + font-style: italic;
62 +}
63 +
64 +.todoapp input:-moz-placeholder {
65 + font-style: italic;
66 + color: #a9a9a9;
67 +}
68 +
69 +.todoapp h1 {
70 + position: absolute;
71 + top: -120px;
72 + width: 100%;
73 + font-size: 70px;
74 + font-weight: bold;
75 + text-align: center;
76 + color: #b3b3b3;
77 + color: rgba(255, 255, 255, 0.3);
78 + text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
79 + -webkit-text-rendering: optimizeLegibility;
80 + -moz-text-rendering: optimizeLegibility;
81 + -ms-text-rendering: optimizeLegibility;
82 + -o-text-rendering: optimizeLegibility;
83 + text-rendering: optimizeLegibility;
84 +}
85 +
86 +.header {
87 + padding-top: 15px;
88 + border-radius: inherit;
89 +}
90 +
91 +.header:before {
92 + content: '';
93 + position: absolute;
94 + top: 0;
95 + right: 0;
96 + left: 0;
97 + height: 15px;
98 + z-index: 2;
99 + border-bottom: 1px solid #6c615c;
100 + background: #8d7d77;
101 + background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
102 + background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
103 + background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
104 + background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
105 + background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
106 + background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
107 + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
108 + border-top-left-radius: 1px;
109 + border-top-right-radius: 1px;
110 +}
111 +
112 +.new-todo,
113 +.edit {
114 + position: relative;
115 + margin: 0;
116 + width: 100%;
117 + font-size: 24px;
118 + font-family: inherit;
119 + line-height: 1.4em;
120 + border: 0;
121 + outline: none;
122 + color: inherit;
123 + padding: 6px;
124 + border: 1px solid #999;
125 + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
126 + -webkit-box-sizing: border-box;
127 + -moz-box-sizing: border-box;
128 + -ms-box-sizing: border-box;
129 + -o-box-sizing: border-box;
130 + box-sizing: border-box;
131 + -webkit-font-smoothing: antialiased;
132 + -moz-font-smoothing: antialiased;
133 + -ms-font-smoothing: antialiased;
134 + -o-font-smoothing: antialiased;
135 + font-smoothing: antialiased;
136 +}
137 +
138 +.new-todo {
139 + padding: 16px 16px 16px 60px;
140 + border: none;
141 + background: rgba(0, 0, 0, 0.02);
142 + z-index: 2;
143 + box-shadow: none;
144 +}
145 +
146 +.main {
147 + position: relative;
148 + z-index: 2;
149 + border-top: 1px dotted #adadad;
150 +}
151 +
152 +.toggle-all-label {
153 + display: none;
154 +}
155 +
156 +.toggle-all {
157 + position: absolute;
158 + top: -42px;
159 + left: -4px;
160 + width: 40px;
161 + text-align: center;
162 + border: none; /* Mobile Safari */
163 +}
164 +
165 +.toggle-all:before {
166 + content: '»';
167 + font-size: 28px;
168 + color: #d9d9d9;
169 + padding: 0 25px 7px;
170 +}
171 +
172 +.toggle-all:checked:before {
173 + color: #737373;
174 +}
175 +
176 +.todo-list {
177 + margin: 0;
178 + padding: 0;
179 + list-style: none;
180 +}
181 +
182 +.todo-list li {
183 + position: relative;
184 + font-size: 24px;
185 + border-bottom: 1px dotted #ccc;
186 +}
187 +
188 +.todo-list li:last-child {
189 + border-bottom: none;
190 +}
191 +
192 +.todo-list li.editing {
193 + border-bottom: none;
194 + padding: 0;
195 +}
196 +
197 +.todo-list li.editing .edit {
198 + display: block;
199 + width: 506px;
200 + padding: 13px 17px 12px 17px;
201 + margin: 0 0 0 43px;
202 +}
203 +
204 +.todo-list li.editing .view {
205 + display: none;
206 +}
207 +
208 +.todo-list li .toggle {
209 + text-align: center;
210 + width: 40px;
211 + /* auto, since non-WebKit browsers doesn't support input styling */
212 + height: auto;
213 + position: absolute;
214 + top: 0;
215 + bottom: 0;
216 + margin: auto 0;
217 + border: none; /* Mobile Safari */
218 + -webkit-appearance: none;
219 + /*-moz-appearance: none;*/
220 + -ms-appearance: none;
221 + -o-appearance: none;
222 + appearance: none;
223 +}
224 +
225 +.todo-list li .toggle:after {
226 + content: '✔';
227 + line-height: 43px; /* 40 + a couple of pixels visual adjustment */
228 + font-size: 20px;
229 + color: #d9d9d9;
230 + text-shadow: 0 -1px 0 #bfbfbf;
231 +}
232 +
233 +.todo-list li .toggle:checked:after {
234 + color: #85ada7;
235 + text-shadow: 0 1px 0 #669991;
236 + bottom: 1px;
237 + position: relative;
238 +}
239 +
240 +.todo-list li label {
241 + word-break: break-word;
242 + padding: 15px;
243 + margin-left: 45px;
244 + display: block;
245 + line-height: 1.2;
246 + -webkit-transition: color 0.4s;
247 + -moz-transition: color 0.4s;
248 + -ms-transition: color 0.4s;
249 + -o-transition: color 0.4s;
250 + transition: color 0.4s;
251 +}
252 +
253 +.todo-list li.completed label {
254 + color: #a9a9a9;
255 + text-decoration: line-through;
256 +}
257 +
258 +.todo-list li .destroy {
259 + display: none;
260 + position: absolute;
261 + top: 0;
262 + right: 10px;
263 + bottom: 0;
264 + width: 40px;
265 + height: 40px;
266 + margin: auto 0;
267 + font-size: 22px;
268 + color: #a88a8a;
269 + -webkit-transition: all 0.2s;
270 + -moz-transition: all 0.2s;
271 + -ms-transition: all 0.2s;
272 + -o-transition: all 0.2s;
273 + transition: all 0.2s;
274 +}
275 +
276 +.todo-list li .destroy:hover {
277 + text-shadow: 0 0 1px #000,
278 + 0 0 10px rgba(199, 107, 107, 0.8);
279 + -webkit-transform: scale(1.3);
280 + -moz-transform: scale(1.3);
281 + -ms-transform: scale(1.3);
282 + -o-transform: scale(1.3);
283 + transform: scale(1.3);
284 +}
285 +
286 +.todo-list li .destroy:after {
287 + content: '✖';
288 +}
289 +
290 +.todo-list li:hover .destroy {
291 + display: block;
292 +}
293 +
294 +.todo-list li .edit {
295 + display: none;
296 +}
297 +
298 +.todo-list li.editing:last-child {
299 + margin-bottom: -1px;
300 +}
301 +
302 +.footer {
303 + color: #777;
304 + padding: 0 15px;
305 + position: absolute;
306 + right: 0;
307 + bottom: -31px;
308 + left: 0;
309 + height: 20px;
310 + z-index: 1;
311 + text-align: center;
312 +}
313 +
314 +.footer:before {
315 + content: '';
316 + position: absolute;
317 + right: 0;
318 + bottom: 31px;
319 + left: 0;
320 + height: 50px;
321 + z-index: -1;
322 + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
323 + 0 6px 0 -3px rgba(255, 255, 255, 0.8),
324 + 0 7px 1px -3px rgba(0, 0, 0, 0.3),
325 + 0 43px 0 -6px rgba(255, 255, 255, 0.8),
326 + 0 44px 2px -6px rgba(0, 0, 0, 0.2);
327 +}
328 +
329 +.todo-count {
330 + float: left;
331 + text-align: left;
332 +}
333 +
334 +.filters {
335 + margin: 0;
336 + padding: 0;
337 + list-style: none;
338 + position: absolute;
339 + right: 0;
340 + left: 0;
341 +}
342 +
343 +.filters li {
344 + display: inline;
345 +}
346 +
347 +.filters li a {
348 + color: #83756f;
349 + margin: 2px;
350 + text-decoration: none;
351 +}
352 +
353 +.filters li a.selected {
354 + font-weight: bold;
355 +}
356 +
357 +.clear-completed {
358 + float: right;
359 + position: relative;
360 + line-height: 20px;
361 + text-decoration: none;
362 + background: rgba(0, 0, 0, 0.1);
363 + font-size: 11px;
364 + padding: 0 10px;
365 + border-radius: 3px;
366 + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
367 +}
368 +
369 +.clear-completed:hover {
370 + background: rgba(0, 0, 0, 0.15);
371 + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
372 +}
373 +
374 +.info {
375 + margin: 65px auto 0;
376 + color: #a6a6a6;
377 + font-size: 12px;
378 + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
379 + text-align: center;
380 +}
381 +
382 +.info a {
383 + color: inherit;
384 +}
385 +
386 +/*
387 + Hack to remove background from Mobile Safari.
388 + Can't use it globally since it destroys checkboxes in Firefox and Opera
389 +*/
390 +@media screen and (-webkit-min-device-pixel-ratio:0) {
391 + .toggle-all,
392 + .todo-list li .toggle {
393 + background: none;
394 + }
395 +
396 + .todo-list li .toggle {
397 + height: 40px;
398 + }
399 +
400 + .toggle-all {
401 + top: -56px;
402 + left: -15px;
403 + width: 65px;
404 + height: 41px;
405 + -webkit-transform: rotate(90deg);
406 + transform: rotate(90deg);
407 + -webkit-appearance: none;
408 + appearance: none;
409 + }
410 +}
411 +
412 +.hidden{
413 + display:none;
414 +}
examples/todomvc-backbone/css/bg.png
Binary files /dev/null and b/examples/todomvc-backbone/css/bg.png differ
examples/todomvc-backbone/index.html new
+22
@@ -0,0 +1,22 @@
1 +<!doctype html>
2 +<html lang="en">
3 +<head>
4 + <meta charset="utf-8">
5 + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6 + <title>React • TodoMVC</title>
7 + <link rel="stylesheet" href="css/base.css">
8 + <!--[if IE]>
9 + <script src="js/ie.js"></script>
10 + <![endif]-->
11 +</head>
12 +<body>
13 + <div id="todoapp"></div>
14 + <script src="../../build/react.js"></script>
15 + <script src="../../build/JSXTransformer.js"></script>
16 + <script type="text/javascript" src="../shared/thirdparty/jquery.min.js" charset="utf-8"></script>
17 + <script type="text/javascript" src="thirdparty/lodash.min.js" charset="utf-8"></script>
18 + <script type="text/javascript" src="thirdparty/backbone-min.js" charset="utf-8"></script>
19 + <script type="text/javascript" src="thirdparty/backbone.localStorage.js" charset="utf-8"></script>
20 + <script type="text/jsx" src="js/app.js"></script>
21 +</body>
22 +</html>
examples/todomvc-backbone/js/app.js new
+254
@@ -0,0 +1,254 @@
1 +/** @jsx React.DOM */
2 +
3 +function cx(obj) {
4 + var s = '';
5 + for (var key in obj) {
6 + if (!obj.hasOwnProperty(key)) {
7 + continue;
8 + }
9 + if (obj[key]) {
10 + s += key + ' ';
11 + }
12 + }
13 + return s;
14 +}
15 +
16 +var Todo = Backbone.Model.extend({
17 +
18 + // Default attributes for the todo
19 + // and ensure that each todo created has `title` and `completed` keys.
20 + defaults: {
21 + title: '',
22 + completed: false
23 + },
24 +
25 + // Toggle the `completed` state of this todo item.
26 + toggle: function() {
27 + this.save({
28 + completed: !this.get('completed')
29 + });
30 + }
31 +
32 +});
33 +
34 +var TodoList = Backbone.Collection.extend({
35 +
36 + // Reference to this collection's model.
37 + model: Todo,
38 +
39 + // Save all of the todo items under the `"todos"` namespace.
40 + localStorage: new Store('todos-backbone'),
41 +
42 + // Filter down the list of all todo items that are finished.
43 + completed: function() {
44 + return this.filter(function( todo ) {
45 + return todo.get('completed');
46 + });
47 + },
48 +
49 + // Filter down the list to only todo items that are still not finished.
50 + remaining: function() {
51 + return this.without.apply( this, this.completed() );
52 + },
53 +
54 + // We keep the Todos in sequential order, despite being saved by unordered
55 + // GUID in the database. This generates the next order number for new items.
56 + nextOrder: function() {
57 + if ( !this.length ) {
58 + return 1;
59 + }
60 + return this.last().get('order') + 1;
61 + },
62 +
63 + // Todos are sorted by their original insertion order.
64 + comparator: function( todo ) {
65 + return todo.get('order');
66 + }
67 +});
68 +
69 +// Create our global collection of **Todos**.
70 +var Todos = new TodoList();
71 +
72 +var TodoFilter;
73 +
74 +var Workspace = Backbone.Router.extend({
75 + routes:{
76 + '*filter': 'setFilter'
77 + },
78 +
79 + setFilter: function( param ) {
80 + // Set the current filter to be used
81 + TodoFilter = param.trim() || '';
82 +
83 + // Trigger a collection filter event, causing hiding/unhiding
84 + // of Todo view items
85 + Todos.trigger('filter');
86 + }
87 +});
88 +
89 +var TodoRouter = new Workspace();
90 +Backbone.history.start();
91 +
92 +var Utils = {
93 + // https://gist.github.com/1308368
94 + uuid: function(a,b){for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');return b},
95 + pluralize: function( count, word ) {
96 + return count === 1 ? word : word + 's';
97 + },
98 + store: function( namespace, data ) {
99 + if ( arguments.length > 1 ) {
100 + return localStorage.setItem( namespace, JSON.stringify( data ) );
101 + } else {
102 + var store = localStorage.getItem( namespace );
103 + return ( store && JSON.parse( store ) ) || [];
104 + }
105 + }
106 +};
107 +
108 +// Begin React stuff
109 +
110 +var ENTER_KEY = 13;
111 +
112 +var TodoItem = React.createClass({
113 + getInitialState: function() {
114 + return {editValue: this.props.todo.title};
115 + },
116 + onKeyUp: React.autoBind(function(event) {
117 + this.setState({editValue: event.target.value});
118 + var val = event.target.value.trim();
119 + if (event.nativeEvent.keyCode !== ENTER_KEY || !val) {
120 + return;
121 + }
122 + this.props.onSave(val);
123 + }),
124 + onEdit: React.autoBind(function() {
125 + this.props.onEdit();
126 + this.refs.editField.getDOMNode().focus();
127 + }),
128 + render: function() {
129 + return (
130 + <li class={cx({completed: this.props.todo.completed, editing: this.props.editing})}>
131 + <div class="view">
132 + <input
133 + class="toggle"
134 + type="checkbox"
135 + checked={this.props.todo.completed ? 'checked' : null}
136 + onChange={this.props.onToggle}
137 + />
138 + <label onDoubleClick={this.onEdit}>{this.props.todo.title}</label>
139 + <button class="destroy" onClick={this.props.onDestroy} />
140 + </div>
141 + <input ref="editField" class="edit" value={this.state.editValue} onKeyUp={this.onKeyUp} />
142 + </li>
143 + );
144 + }
145 +});
146 +
147 +var TodoFooter = React.createClass({
148 + render: function() {
149 + var activeTodoWord = Utils.pluralize(this.props.count, 'todo');
150 + var clearButton = null;
151 +
152 + if (this.props.completedCount > 0) {
153 + clearButton = (
154 + <button class="clear-completed" onClick={this.props.onClearCompleted}>Clear completed ({this.props.completedCount})</button>
155 + );
156 + }
157 +
158 + return (
159 + <footer class="footer">
160 + <span class="todo-count"><strong>{this.props.count}</strong>{' '}{activeTodoWord}{' '}left</span>
161 + {clearButton}
162 + </footer>
163 + );
164 + }
165 +});
166 +
167 +var TodoApp = React.createClass({
168 + getInitialState: function() {
169 + return {todos: Utils.store('todos-react'), newTodoValue: '', editing: {}};
170 + },
171 + handleKeyUp: React.autoBind(function(event) {
172 + this.setState({newTodoValue: event.target.value});
173 + var val = event.target.value.trim();
174 + if (event.nativeEvent.keyCode !== ENTER_KEY || !val) {
175 + return;
176 + }
177 + var todos = this.state.todos;
178 + todos.push({id: Utils.uuid(), title: val, completed: false});
179 + this.setState({todos: todos, newTodoValue: ''});
180 + }),
181 + toggleAll: function(event) {
182 + var checked = event.nativeEvent.target.checked;
183 + this.state.todos.map(function(todo) {
184 + todo.completed = checked;
185 + });
186 + this.setState({todos: this.state.todos});
187 + },
188 + toggle: function(todo) {
189 + todo.completed = !todo.completed;
190 + this.setState({todos: this.state.todos});
191 + },
192 + destroy: function(todo) {
193 + this.setState({todos: this.state.todos.filter(function(candidate) { return candidate.id !== todo.id; })});
194 + },
195 + edit: function(todo) {
196 + this.state.todos.map(function(todo) { this.state.editing[todo.id] = false; }.bind(this));
197 + this.state.editing[todo.id] = true;
198 + this.setState({editing: this.state.editing});
199 + },
200 + save: function(todo, text) {
201 + todo.title = text;
202 + this.state.editing[todo.id] = false;
203 + this.setState({todos: this.state.todos, editing: this.state.editing});
204 + },
205 + clearCompleted: function() {
206 + this.setState({todos: this.state.todos.filter(function(todo) { return !todo.completed })});
207 + },
208 + render: function() {
209 + Utils.store(this.props.localStorageKey || 'todos-react', this.state.todos);
210 + var footer = null;
211 + var main = null;
212 + var todoItems = this.state.todos.map(function(todo) {
213 + return <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)} />;
214 + }.bind(this));
215 +
216 + var activeTodoCount = this.state.todos.filter(function(todo) { return !todo.completed }).length;
217 + var completedCount = todoItems.length - activeTodoCount;
218 + if (activeTodoCount || completedCount) {
219 + footer = <TodoFooter count={activeTodoCount} completedCount={completedCount} onClearCompleted={this.clearCompleted.bind(this)} />;
220 + }
221 +
222 + if (todoItems.length) {
223 + main = (
224 + <section class="main">
225 + <input class="toggle-all" type="checkbox" onChange={this.toggleAll.bind(this)} />
226 + <label class="toggle-all-label">Mark all as complete</label>
227 + <ul class="todo-list">
228 + {todoItems}
229 + </ul>
230 + </section>
231 + );
232 + }
233 +
234 + return (
235 + <div>
236 + <section class="todoapp">
237 + <header class="header">
238 + <h1>todos</h1>
239 + <input class="new-todo" placeholder="What needs to be done?" autofocus="autofocus" onKeyUp={this.handleKeyUp} value={this.state.newTodoValue} />
240 + </header>
241 + {main}
242 + {footer}
243 + </section>
244 + <footer class="info">
245 + <p>Double-click to edit a todo</p>
246 + <p>Created by{' '}<a href="http://github.com/petehunt/">petehunt</a></p>
247 + <p>Part of{' '}<a href="http://todomvc.com">TodoMVC</a></p>
248 + </footer>
249 + </div>
250 + );
251 + }
252 +});
253 +
254 +React.renderComponent(<TodoApp />, document.getElementById('todoapp'));
examples/todomvc-backbone/js/ie.js new
+34
@@ -0,0 +1,34 @@
1 +/*! HTML5 Shiv pre3.6 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
2 + Uncompressed source: https://github.com/aFarkas/html5shiv */
3 + (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();
4 + 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>";
5 + 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||
6 + "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();
7 + 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);
8 +
9 +/* ES5-shim
10 +https://github.com/kriskowal/es5-shim
11 +Copyright 2009-2012 by contributors, MIT License */
12 +(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)===
13 +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;
14 +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?
15 +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)!=
16 +"[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};
17 +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=
18 +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>>>
19 +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==
20 +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)==
21 +"[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,
22 +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=
23 +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=
24 +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";
25 +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?"-":
26 +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=
27 +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):
28 +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})"+")"+")?)?)?)?"+
29 +"$");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=
30 +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,
31 +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===
32 +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=
33 +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===
34 +"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)}});
examples/todomvc-backbone/thirdparty/backbone-min.js new
+42
@@ -0,0 +1,42 @@
1 +// Backbone.js 0.9.10
2 +
3 +// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
4 +// Backbone may be freely distributed under the MIT license.
5 +// For all details and documentation:
6 +// http://backbonejs.org
7 +(function(){var n=this,B=n.Backbone,h=[],C=h.push,u=h.slice,D=h.splice,g;g="undefined"!==typeof exports?exports:n.Backbone={};g.VERSION="0.9.10";var f=n._;!f&&"undefined"!==typeof require&&(f=require("underscore"));g.$=n.jQuery||n.Zepto||n.ender;g.noConflict=function(){n.Backbone=B;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var v=/\s+/,q=function(a,b,c,d){if(!c)return!0;if("object"===typeof c)for(var e in c)a[b].apply(a,[e,c[e]].concat(d));else if(v.test(c)){c=c.split(v);e=0;for(var f=c.length;e<
8 +f;e++)a[b].apply(a,[c[e]].concat(d))}else return!0},w=function(a,b){var c,d=-1,e=a.length;switch(b.length){case 0:for(;++d<e;)(c=a[d]).callback.call(c.ctx);break;case 1:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0]);break;case 2:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0],b[1]);break;case 3:for(;++d<e;)(c=a[d]).callback.call(c.ctx,b[0],b[1],b[2]);break;default:for(;++d<e;)(c=a[d]).callback.apply(c.ctx,b)}},h=g.Events={on:function(a,b,c){if(!q(this,"on",a,[b,c])||!b)return this;this._events||(this._events=
9 +{});(this._events[a]||(this._events[a]=[])).push({callback:b,context:c,ctx:c||this});return this},once:function(a,b,c){if(!q(this,"once",a,[b,c])||!b)return this;var d=this,e=f.once(function(){d.off(a,e);b.apply(this,arguments)});e._callback=b;this.on(a,e,c);return this},off:function(a,b,c){var d,e,t,g,j,l,k,h;if(!this._events||!q(this,"off",a,[b,c]))return this;if(!a&&!b&&!c)return this._events={},this;g=a?[a]:f.keys(this._events);j=0;for(l=g.length;j<l;j++)if(a=g[j],d=this._events[a]){t=[];if(b||
10 +c){k=0;for(h=d.length;k<h;k++)e=d[k],(b&&b!==e.callback&&b!==e.callback._callback||c&&c!==e.context)&&t.push(e)}this._events[a]=t}return this},trigger:function(a){if(!this._events)return this;var b=u.call(arguments,1);if(!q(this,"trigger",a,b))return this;var c=this._events[a],d=this._events.all;c&&w(c,b);d&&w(d,arguments);return this},listenTo:function(a,b,c){var d=this._listeners||(this._listeners={}),e=a._listenerId||(a._listenerId=f.uniqueId("l"));d[e]=a;a.on(b,"object"===typeof b?this:c,this);
11 +return this},stopListening:function(a,b,c){var d=this._listeners;if(d){if(a)a.off(b,"object"===typeof b?this:c,this),!b&&!c&&delete d[a._listenerId];else{"object"===typeof b&&(c=this);for(var e in d)d[e].off(b,c,this);this._listeners={}}return this}}};h.bind=h.on;h.unbind=h.off;f.extend(g,h);var r=g.Model=function(a,b){var c,d=a||{};this.cid=f.uniqueId("c");this.attributes={};b&&b.collection&&(this.collection=b.collection);b&&b.parse&&(d=this.parse(d,b)||{});if(c=f.result(this,"defaults"))d=f.defaults({},
12 +d,c);this.set(d,b);this.changed={};this.initialize.apply(this,arguments)};f.extend(r.prototype,h,{changed:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},sync:function(){return g.sync.apply(this,arguments)},get:function(a){return this.attributes[a]},escape:function(a){return f.escape(this.get(a))},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e,g,p,j,l,k;if(null==a)return this;"object"===typeof a?(e=a,c=b):(e={})[a]=b;c||(c={});
13 +if(!this._validate(e,c))return!1;g=c.unset;p=c.silent;a=[];j=this._changing;this._changing=!0;j||(this._previousAttributes=f.clone(this.attributes),this.changed={});k=this.attributes;l=this._previousAttributes;this.idAttribute in e&&(this.id=e[this.idAttribute]);for(d in e)b=e[d],f.isEqual(k[d],b)||a.push(d),f.isEqual(l[d],b)?delete this.changed[d]:this.changed[d]=b,g?delete k[d]:k[d]=b;if(!p){a.length&&(this._pending=!0);b=0;for(d=a.length;b<d;b++)this.trigger("change:"+a[b],this,k[a[b]],c)}if(j)return this;
14 +if(!p)for(;this._pending;)this._pending=!1,this.trigger("change",this,c);this._changing=this._pending=!1;return this},unset:function(a,b){return this.set(a,void 0,f.extend({},b,{unset:!0}))},clear:function(a){var b={},c;for(c in this.attributes)b[c]=void 0;return this.set(b,f.extend({},a,{unset:!0}))},hasChanged:function(a){return null==a?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._changing?
15 +this._previousAttributes:this.attributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return null==a||!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=a.success;a.success=function(a,d,e){if(!a.set(a.parse(d,e),e))return!1;b&&b(a,d,e)};return this.sync("read",this,a)},save:function(a,b,c){var d,e,g=this.attributes;
16 +null==a||"object"===typeof a?(d=a,c=b):(d={})[a]=b;if(d&&(!c||!c.wait)&&!this.set(d,c))return!1;c=f.extend({validate:!0},c);if(!this._validate(d,c))return!1;d&&c.wait&&(this.attributes=f.extend({},g,d));void 0===c.parse&&(c.parse=!0);e=c.success;c.success=function(a,b,c){a.attributes=g;var k=a.parse(b,c);c.wait&&(k=f.extend(d||{},k));if(f.isObject(k)&&!a.set(k,c))return!1;e&&e(a,b,c)};a=this.isNew()?"create":c.patch?"patch":"update";"patch"===a&&(c.attrs=d);a=this.sync(a,this,c);d&&c.wait&&(this.attributes=
17 +g);return a},destroy:function(a){a=a?f.clone(a):{};var b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};a.success=function(a,b,e){(e.wait||a.isNew())&&d();c&&c(a,b,e)};if(this.isNew())return a.success(this,null,a),!1;var e=this.sync("delete",this,a);a.wait||d();return e},url:function(){var a=f.result(this,"urlRoot")||f.result(this.collection,"url")||x();return this.isNew()?a:a+("/"===a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},
18 +isNew:function(){return null==this.id},isValid:function(a){return!this.validate||!this.validate(this.attributes,a)},_validate:function(a,b){if(!b.validate||!this.validate)return!0;a=f.extend({},this.attributes,a);var c=this.validationError=this.validate(a,b)||null;if(!c)return!0;this.trigger("invalid",this,c,b||{});return!1}});var s=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);void 0!==b.comparator&&(this.comparator=b.comparator);this.models=[];this._reset();this.initialize.apply(this,
19 +arguments);a&&this.reset(a,f.extend({silent:!0},b))};f.extend(s.prototype,h,{model:r,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},sync:function(){return g.sync.apply(this,arguments)},add:function(a,b){a=f.isArray(a)?a.slice():[a];b||(b={});var c,d,e,g,p,j,l,k,h,m;l=[];k=b.at;h=this.comparator&&null==k&&!1!=b.sort;m=f.isString(this.comparator)?this.comparator:null;c=0;for(d=a.length;c<d;c++)(e=this._prepareModel(g=a[c],b))?(p=this.get(e))?b.merge&&(p.set(g===
20 +e?e.attributes:g,b),h&&(!j&&p.hasChanged(m))&&(j=!0)):(l.push(e),e.on("all",this._onModelEvent,this),this._byId[e.cid]=e,null!=e.id&&(this._byId[e.id]=e)):this.trigger("invalid",this,g,b);l.length&&(h&&(j=!0),this.length+=l.length,null!=k?D.apply(this.models,[k,0].concat(l)):C.apply(this.models,l));j&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=l.length;c<d;c++)(e=l[c]).trigger("add",e,this,b);j&&this.trigger("sort",this,b);return this},remove:function(a,b){a=f.isArray(a)?a.slice():[a];
21 +b||(b={});var c,d,e,g;c=0;for(d=a.length;c<d;c++)if(g=this.get(a[c]))delete this._byId[g.id],delete this._byId[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:this.length},b));return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},
22 +b));return a},shift:function(a){var b=this.at(0);this.remove(b,a);return b},slice:function(a,b){return this.models.slice(a,b)},get:function(a){if(null!=a)return this._idAttr||(this._idAttr=this.model.prototype.idAttribute),this._byId[a.id||a.cid||a[this._idAttr]||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){if(!this.comparator)throw Error("Cannot sort a set without a comparator");
23 +a||(a={});f.isString(this.comparator)||1===this.comparator.length?this.models=this.sortBy(this.comparator,this):this.models.sort(f.bind(this.comparator,this));a.silent||this.trigger("sort",this,a);return this},pluck:function(a){return f.invoke(this.models,"get",a)},update:function(a,b){b=f.extend({add:!0,merge:!0,remove:!0},b);b.parse&&(a=this.parse(a,b));var c,d,e,g,h=[],j=[],l={};f.isArray(a)||(a=a?[a]:[]);if(b.add&&!b.remove)return this.add(a,b);d=0;for(e=a.length;d<e;d++)c=a[d],g=this.get(c),
24 +b.remove&&g&&(l[g.cid]=!0),(b.add&&!g||b.merge&&g)&&h.push(c);if(b.remove){d=0;for(e=this.models.length;d<e;d++)c=this.models[d],l[c.cid]||j.push(c)}j.length&&this.remove(j,b);h.length&&this.add(h,b);return this},reset:function(a,b){b||(b={});b.parse&&(a=this.parse(a,b));for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);b.previousModels=this.models.slice();this._reset();a&&this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=
25 +a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=a.success;a.success=function(a,d,e){a[e.update?"update":"reset"](d,e);b&&b(a,d,e)};return this.sync("read",this,a)},create:function(a,b){b=b?f.clone(b):{};if(!(a=this._prepareModel(a,b)))return!1;b.wait||this.add(a,b);var c=this,d=b.success;b.success=function(a,b,f){f.wait&&c.add(a,f);d&&d(a,b,f)};a.save(null,b);return a},parse:function(a){return a},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0;this.models.length=
26 +0;this._byId={}},_prepareModel:function(a,b){if(a instanceof r)return a.collection||(a.collection=this),a;b||(b={});b.collection=this;var c=new this.model(a,b);return!c._validate(a,b)?!1:c},_removeReference:function(a){this===a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"===a||"remove"===a)&&c!==this||("destroy"===a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],null!=b.id&&(this._byId[b.id]=
27 +b)),this.trigger.apply(this,arguments))},sortedIndex:function(a,b,c){b||(b=this.comparator);var d=f.isFunction(b)?b:function(a){return a.get(b)};return f.sortedIndex(this.models,a,d,c)}});f.each("forEach each map collect reduce foldl inject reduceRight foldr find detect filter select reject every all some any include contains invoke max min toArray size first head take initial rest tail drop last without indexOf shuffle lastIndexOf isEmpty chain".split(" "),function(a){s.prototype[a]=function(){var b=
28 +u.call(arguments);b.unshift(this.models);return f[a].apply(f,b)}});f.each(["groupBy","countBy","sortBy"],function(a){s.prototype[a]=function(b,c){var d=f.isFunction(b)?b:function(a){return a.get(b)};return f[a](this.models,d,c)}});var y=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},E=/\((.*?)\)/g,F=/(\(\?)?:\w+/g,G=/\*\w+/g,H=/[\-{}\[\]+?.,\\\^$|#\s]/g;f.extend(y.prototype,h,{initialize:function(){},route:function(a,b,c){f.isRegExp(a)||
29 +(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));this.trigger("route",b,d);g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b);return this},_bindRoutes:function(){if(this.routes)for(var a,b=f.keys(this.routes);null!=(a=b.pop());)this.route(a,this.routes[a])},_routeToRegExp:function(a){a=a.replace(H,"\\$&").replace(E,"(?:$1)?").replace(F,
30 +function(a,c){return c?a:"([^/]+)"}).replace(G,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl");"undefined"!==typeof window&&(this.location=window.location,this.history=window.history)},z=/^[#\/]|\s+$/g,I=/^\/+|\/+$/g,J=/msie [\w.]+/,K=/\/$/;m.started=!1;f.extend(m.prototype,h,{interval:50,getHash:function(a){return(a=(a||this).location.href.match(/#(.*)$/))?a[1]:""},getFragment:function(a,
31 +b){if(null==a)if(this._hasPushState||!this._wantsHashChange||b){a=this.location.pathname;var c=this.root.replace(K,"");a.indexOf(c)||(a=a.substr(c.length))}else a=this.getHash();return a.replace(z,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this.root=this.options.root;this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=!(!this.options.pushState||
32 +!this.history||!this.history.pushState);a=this.getFragment();var b=document.documentMode,b=J.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b);this.root=("/"+this.root+"/").replace(I,"/");b&&this._wantsHashChange&&(this.iframe=g.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a));if(this._hasPushState)g.$(window).on("popstate",this.checkUrl);else if(this._wantsHashChange&&"onhashchange"in window&&!b)g.$(window).on("hashchange",this.checkUrl);
33 +else this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,this.interval));this.fragment=a;a=this.location;b=a.pathname.replace(/[^\/]$/,"$&/")===this.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),this.location.replace(this.root+this.location.search+"#"+this.fragment),!0;this._wantsPushState&&(this._hasPushState&&b&&a.hash)&&(this.fragment=this.getHash().replace(z,""),this.history.replaceState({},document.title,
34 +this.root+this.fragment+a.search));if(!this.options.silent)return this.loadUrl()},stop:function(){g.$(window).off("popstate",this.checkUrl).off("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a===this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a===this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},
35 +loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};a=this.getFragment(a||"");if(this.fragment!==a){this.fragment=a;var c=this.root+a;if(this._hasPushState)this.history[b.replace?"replaceState":"pushState"]({},document.title,c);else if(this._wantsHashChange)this._updateHash(this.location,a,b.replace),this.iframe&&a!==this.getFragment(this.getHash(this.iframe))&&
36 +(b.replace||this.iframe.document.open().close(),this._updateHash(this.iframe.location,a,b.replace));else return this.location.assign(c);b.trigger&&this.loadUrl(a)}},_updateHash:function(a,b,c){c?(c=a.href.replace(/(javascript:|#).*$/,""),a.replace(c+"#"+b)):a.hash="#"+b}});g.history=new m;var A=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},L=/^(\S+)\s*(.*)$/,M="model collection el id attributes className tagName events".split(" ");
37 +f.extend(A.prototype,h,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();this.stopListening();return this},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof g.$?a:g.$(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=f.result(this,"events"))){this.undelegateEvents();for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);
38 +if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(L),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);if(""===d)this.$el.on(e,c);else this.$el.on(e,d,c)}}},undelegateEvents:function(){this.$el.off(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},f.result(this,"options"),a));f.extend(this,f.pick(a,M));this.options=a},_ensureElement:function(){if(this.el)this.setElement(f.result(this,"el"),!1);else{var a=f.extend({},f.result(this,"attributes"));
39 +this.id&&(a.id=f.result(this,"id"));this.className&&(a["class"]=f.result(this,"className"));a=g.$("<"+f.result(this,"tagName")+">").attr(a);this.setElement(a,!1)}}});var N={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=N[a];f.defaults(c||(c={}),{emulateHTTP:g.emulateHTTP,emulateJSON:g.emulateJSON});var e={type:d,dataType:"json"};c.url||(e.url=f.result(b,"url")||x());if(null==c.data&&b&&("create"===a||"update"===a||"patch"===a))e.contentType="application/json",
40 +e.data=JSON.stringify(c.attrs||b.toJSON(c));c.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(c.emulateHTTP&&("PUT"===d||"DELETE"===d||"PATCH"===d)){e.type="POST";c.emulateJSON&&(e.data._method=d);var h=c.beforeSend;c.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d);if(h)return h.apply(this,arguments)}}"GET"!==e.type&&!c.emulateJSON&&(e.processData=!1);var m=c.success;c.success=function(a){m&&m(b,a,c);b.trigger("sync",b,a,c)};
41 +var j=c.error;c.error=function(a){j&&j(b,a,c);b.trigger("error",b,a,c)};a=c.xhr=g.ajax(f.extend(e,c));b.trigger("request",b,a,c);return a};g.ajax=function(){return g.$.ajax.apply(g.$,arguments)};r.extend=s.extend=y.extend=A.extend=m.extend=function(a,b){var c=this,d;d=a&&f.has(a,"constructor")?a.constructor:function(){return c.apply(this,arguments)};f.extend(d,c,b);var e=function(){this.constructor=d};e.prototype=c.prototype;d.prototype=new e;a&&f.extend(d.prototype,a);d.__super__=c.prototype;return d};
42 +var x=function(){throw Error('A "url" property or function must be specified');}}).call(this);
examples/todomvc-backbone/thirdparty/backbone.localStorage.js new
+130
@@ -0,0 +1,130 @@
1 +/**
2 + * Backbone localStorage Adapter
3 + * https://github.com/jeromegn/Backbone.localStorage
4 + */
5 +
6 +(function(_, Backbone) {
7 +// A simple module to replace `Backbone.sync` with *localStorage*-based
8 +// persistence. Models are given GUIDS, and saved into a JSON object. Simple
9 +// as that.
10 +
11 +// Hold reference to Underscore.js and Backbone.js in the closure in order
12 +// to make things work even if they are removed from the global namespace
13 +
14 +// Generate four random hex digits.
15 +function S4() {
16 + return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
17 +};
18 +
19 +// Generate a pseudo-GUID by concatenating random hexadecimal.
20 +function guid() {
21 + return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
22 +};
23 +
24 +// Our Store is represented by a single JS object in *localStorage*. Create it
25 +// with a meaningful name, like the name you'd give a table.
26 +// window.Store is deprectated, use Backbone.LocalStorage instead
27 +Backbone.LocalStorage = window.Store = function(name) {
28 + this.name = name;
29 + var store = this.localStorage().getItem(this.name);
30 + this.records = (store && store.split(",")) || [];
31 +};
32 +
33 +_.extend(Backbone.LocalStorage.prototype, {
34 +
35 + // Save the current state of the **Store** to *localStorage*.
36 + save: function() {
37 + this.localStorage().setItem(this.name, this.records.join(","));
38 + },
39 +
40 + // Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
41 + // have an id of it's own.
42 + create: function(model) {
43 + if (!model.id) {
44 + model.id = guid();
45 + model.set(model.idAttribute, model.id);
46 + }
47 + this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
48 + this.records.push(model.id.toString());
49 + this.save();
50 + return model.toJSON();
51 + },
52 +
53 + // Update a model by replacing its copy in `this.data`.
54 + update: function(model) {
55 + this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model));
56 + if (!_.include(this.records, model.id.toString())) this.records.push(model.id.toString()); this.save();
57 + return model.toJSON();
58 + },
59 +
60 + // Retrieve a model from `this.data` by id.
61 + find: function(model) {
62 + return JSON.parse(this.localStorage().getItem(this.name+"-"+model.id));
63 + },
64 +
65 + // Return the array of all models currently in storage.
66 + findAll: function() {
67 + return _(this.records).chain()
68 + .map(function(id){return JSON.parse(this.localStorage().getItem(this.name+"-"+id));}, this)
69 + .compact()
70 + .value();
71 + },
72 +
73 + // Delete a model from `this.data`, returning it.
74 + destroy: function(model) {
75 + this.localStorage().removeItem(this.name+"-"+model.id);
76 + this.records = _.reject(this.records, function(record_id){return record_id == model.id.toString();});
77 + this.save();
78 + return model;
79 + },
80 +
81 + localStorage: function() {
82 + return localStorage;
83 + }
84 +
85 +});
86 +
87 +// localSync delegate to the model or collection's
88 +// *localStorage* property, which should be an instance of `Store`.
89 +// window.Store.sync and Backbone.localSync is deprectated, use Backbone.LocalStorage.sync instead
90 +Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) {
91 + var store = model.localStorage || model.collection.localStorage;
92 +
93 + var resp, syncDfd = $.Deferred && $.Deferred(); //If $ is having Deferred - use it.
94 +
95 + switch (method) {
96 + case "read": resp = model.id != undefined ? store.find(model) : store.findAll(); break;
97 + case "create": resp = store.create(model); break;
98 + case "update": resp = store.update(model); break;
99 + case "delete": resp = store.destroy(model); break;
100 + }
101 +
102 + if (resp) {
103 + if (options && options.success) options.success(resp);
104 + if (syncDfd) syncDfd.resolve();
105 + } else {
106 + if (options && options.error) options.error("Record not found");
107 + if (syncDfd) syncDfd.reject();
108 + }
109 +
110 + return syncDfd && syncDfd.promise();
111 +};
112 +
113 +Backbone.ajaxSync = Backbone.sync;
114 +
115 +Backbone.getSyncMethod = function(model) {
116 + if(model.localStorage || (model.collection && model.collection.localStorage))
117 + {
118 + return Backbone.localSync;
119 + }
120 +
121 + return Backbone.ajaxSync;
122 +};
123 +
124 +// Override 'Backbone.sync' to default to localSync,
125 +// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
126 +Backbone.sync = function(method, model, options) {
127 + return Backbone.getSyncMethod(model).apply(this, [method, model, options]);
128 +};
129 +
130 +})(_, Backbone);
examples/todomvc-backbone/thirdparty/lodash.min.js new
+39
@@ -0,0 +1,39 @@
1 +/*!
2 + Lo-Dash 0.8.2 lodash.com/license
3 + Underscore.js 1.4.2 underscorejs.org/LICENSE
4 +*/
5 +;(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
6 +,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
7 +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');"
8 +);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];"+
9 +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
10 +(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[
11 +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.
12 +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(
13 +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){
14 +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+
15 +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)
16 +,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+""
17 +).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
18 +,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"
19 +)[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"
20 +,"\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)"}
21 +,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
22 +e)return i;var t=e.valueOf,n="function"==typeof t&&(n=Y(t))&&Y(n);return n?e==n||Y(e)==n&&!v(e):g(e)}:g,Kt=l({a:"m",i:"[]",h:"t.push(i)"}),Qt={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"},Gt=Vt(Qt),Yt=l(qt,{h:"if(t[i]==null)"+qt.h}),Zt=l(qt),en=l(jt,Ut,zt,{q:i}),tn=l(jt,Ut,zt),nn=l({q:i,a:"m",i:"[]",h:"S(A)&&t.push(i)",e:"t.sort()"}),rn=l({a:"A",i:"true",p:"var H=y.call(A),k=A.length;if(D[H]"+(Nt?"||O(A)":"")+"||(H==X&&k===+k&&S(A.splice)))return!k",h:{k:"return false"}}),sn=ut?function(
23 +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"
24 +)+"([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"
25 +,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]="
26 +:".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=
27 +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=
28 +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,
29 +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(
30 +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
31 +.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=
32 +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=
33 +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===+
34 +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(
35 +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=
36 +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=
37 +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);++
38 +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
39 +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);
\ No newline at end of file
examples/todomvc/css/base.css new
+421
@@ -0,0 +1,421 @@
1 +html,
2 +body {
3 + margin: 0;
4 + padding: 0;
5 +}
6 +
7 +button {
8 + margin: 0;
9 + padding: 0;
10 + border: 0;
11 + background: none;
12 + font-size: 100%;
13 + vertical-align: baseline;
14 + font-family: inherit;
15 + color: inherit;
16 + -webkit-appearance: none;
17 + /*-moz-appearance: none;*/
18 + -ms-appearance: none;
19 + -o-appearance: none;
20 + appearance: none;
21 +}
22 +
23 +body {
24 + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
25 + line-height: 1.4em;
26 + background: #eaeaea url('bg.png');
27 + color: #4d4d4d;
28 + width: 550px;
29 + margin: 0 auto;
30 + -webkit-font-smoothing: antialiased;
31 + -moz-font-smoothing: antialiased;
32 + -ms-font-smoothing: antialiased;
33 + -o-font-smoothing: antialiased;
34 + font-smoothing: antialiased;
35 +}
36 +
37 +.todoapp {
38 + background: #fff;
39 + background: rgba(255, 255, 255, 0.9);
40 + margin: 130px 0 40px 0;
41 + border: 1px solid #ccc;
42 + position: relative;
43 + border-top-left-radius: 2px;
44 + border-top-right-radius: 2px;
45 + box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
46 + 0 25px 50px 0 rgba(0, 0, 0, 0.15);
47 +}
48 +
49 +.todoapp:before {
50 + content: '';
51 + border-left: 1px solid #f5d6d6;
52 + border-right: 1px solid #f5d6d6;
53 + width: 2px;
54 + position: absolute;
55 + top: 0;
56 + left: 40px;
57 + height: 100%;
58 +}
59 +
60 +.todoapp input::-webkit-input-placeholder {
61 + font-style: italic;
62 +}
63 +
64 +.todoapp input:-moz-placeholder {
65 + font-style: italic;
66 + color: #a9a9a9;
67 +}
68 +
69 +.todoapp h1 {
70 + position: absolute;
71 + top: -120px;
72 + width: 100%;
73 + font-size: 70px;
74 + font-weight: bold;
75 + text-align: center;
76 + color: #b3b3b3;
77 + color: rgba(255, 255, 255, 0.3);
78 + text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
79 + -webkit-text-rendering: optimizeLegibility;
80 + -moz-text-rendering: optimizeLegibility;
81 + -ms-text-rendering: optimizeLegibility;
82 + -o-text-rendering: optimizeLegibility;
83 + text-rendering: optimizeLegibility;
84 +}
85 +
86 +.header {
87 + padding-top: 15px;
88 + border-radius: inherit;
89 +}
90 +
91 +.header:before {
92 + content: '';
93 + position: absolute;
94 + top: 0;
95 + right: 0;
96 + left: 0;
97 + height: 15px;
98 + z-index: 2;
99 + border-bottom: 1px solid #6c615c;
100 + background: #8d7d77;
101 + background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
102 + background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
103 + background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
104 + background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
105 + background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
106 + background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
107 + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
108 + border-top-left-radius: 1px;
109 + border-top-right-radius: 1px;
110 +}
111 +
112 +.new-todo,
113 +.edit {
114 + position: relative;
115 + margin: 0;
116 + width: 100%;
117 + font-size: 24px;
118 + font-family: inherit;
119 + line-height: 1.4em;
120 + border: 0;
121 + outline: none;
122 + color: inherit;
123 + padding: 6px;
124 + border: 1px solid #999;
125 + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
126 + -webkit-box-sizing: border-box;
127 + -moz-box-sizing: border-box;
128 + -ms-box-sizing: border-box;
129 + -o-box-sizing: border-box;
130 + box-sizing: border-box;
131 + -webkit-font-smoothing: antialiased;
132 + -moz-font-smoothing: antialiased;
133 + -ms-font-smoothing: antialiased;
134 + -o-font-smoothing: antialiased;
135 + font-smoothing: antialiased;
136 +}
137 +
138 +.new-todo {
139 + padding: 16px 16px 16px 60px;
140 + border: none;
141 + background: rgba(0, 0, 0, 0.02);
142 + z-index: 2;
143 + box-shadow: none;
144 +}
145 +
146 +.main {
147 + position: relative;
148 + z-index: 2;
149 + border-top: 1px dotted #adadad;
150 +}
151 +
152 +.toggle-all-label {
153 + display: none;
154 +}
155 +
156 +.toggle-all {
157 + position: absolute;
158 + top: -42px;
159 + left: -4px;
160 + width: 40px;
161 + text-align: center;
162 + border: none; /* Mobile Safari */
163 +}
164 +
165 +.toggle-all:before {
166 + content: '»';
167 + font-size: 28px;
168 + color: #d9d9d9;
169 + padding: 0 25px 7px;
170 +}
171 +
172 +.toggle-all:checked:before {
173 + color: #737373;
174 +}
175 +
176 +.todo-list {
177 + margin: 0;
178 + padding: 0;
179 + list-style: none;
180 +}
181 +
182 +.todo-list li {
183 + position: relative;
184 + font-size: 24px;
185 + border-bottom: 1px dotted #ccc;
186 +}
187 +
188 +.todo-list li:last-child {
189 + border-bottom: none;
190 +}
191 +
192 +.todo-list li.editing {
193 + border-bottom: none;
194 + padding: 0;
195 +}
196 +
197 +.todo-list li.editing .edit {
198 + display: block;
199 + width: 506px;
200 + padding: 13px 17px 12px 17px;
201 + margin: 0 0 0 43px;
202 +}
203 +
204 +.todo-list li.editing .view {
205 + display: none;
206 +}
207 +
208 +.todo-list li .toggle {
209 + text-align: center;
210 + width: 40px;
211 + /* auto, since non-WebKit browsers doesn't support input styling */
212 + height: auto;
213 + position: absolute;
214 + top: 0;
215 + bottom: 0;
216 + margin: auto 0;
217 + border: none; /* Mobile Safari */
218 + -webkit-appearance: none;
219 + /*-moz-appearance: none;*/
220 + -ms-appearance: none;
221 + -o-appearance: none;
222 + appearance: none;
223 +}
224 +
225 +.todo-list li .toggle:after {
226 + content: '✔';
227 + line-height: 43px; /* 40 + a couple of pixels visual adjustment */
228 + font-size: 20px;
229 + color: #d9d9d9;
230 + text-shadow: 0 -1px 0 #bfbfbf;
231 +}
232 +
233 +.todo-list li .toggle:checked:after {
234 + color: #85ada7;
235 + text-shadow: 0 1px 0 #669991;
236 + bottom: 1px;
237 + position: relative;
238 +}
239 +
240 +.todo-list li label {
241 + word-break: break-word;
242 + padding: 15px;
243 + margin-left: 45px;
244 + display: block;
245 + line-height: 1.2;
246 + -webkit-transition: color 0.4s;
247 + -moz-transition: color 0.4s;
248 + -ms-transition: color 0.4s;
249 + -o-transition: color 0.4s;
250 + transition: color 0.4s;
251 +}
252 +
253 +.todo-list li.completed label {
254 + color: #a9a9a9;
255 + text-decoration: line-through;
256 +}
257 +
258 +.todo-list li .destroy {
259 + display: none;
260 + position: absolute;
261 + top: 0;
262 + right: 10px;
263 + bottom: 0;
264 + width: 40px;
265 + height: 40px;
266 + margin: auto 0;
267 + font-size: 22px;
268 + color: #a88a8a;
269 + -webkit-transition: all 0.2s;
270 + -moz-transition: all 0.2s;
271 + -ms-transition: all 0.2s;
272 + -o-transition: all 0.2s;
273 + transition: all 0.2s;
274 +}
275 +
276 +.todo-list li .destroy:hover {
277 + text-shadow: 0 0 1px #000,
278 + 0 0 10px rgba(199, 107, 107, 0.8);
279 + -webkit-transform: scale(1.3);
280 + -moz-transform: scale(1.3);
281 + -ms-transform: scale(1.3);
282 + -o-transform: scale(1.3);
283 + transform: scale(1.3);
284 +}
285 +
286 +.todo-list li .destroy:after {
287 + content: '✖';
288 +}
289 +
290 +.todo-list li:hover .destroy {
291 + display: block;
292 +}
293 +
294 +.todo-list li .edit {
295 + display: none;
296 +}
297 +
298 +.todo-list li.editing:last-child {
299 + margin-bottom: -1px;
300 +}
301 +
302 +.footer {
303 + color: #777;
304 + padding: 0 15px;
305 + position: absolute;
306 + right: 0;
307 + bottom: -31px;
308 + left: 0;
309 + height: 20px;
310 + z-index: 1;
311 + text-align: center;
312 +}
313 +
314 +.footer:before {
315 + content: '';
316 + position: absolute;
317 + right: 0;
318 + bottom: 31px;
319 + left: 0;
320 + height: 50px;
321 + z-index: -1;
322 + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
323 + 0 6px 0 -3px rgba(255, 255, 255, 0.8),
324 + 0 7px 1px -3px rgba(0, 0, 0, 0.3),
325 + 0 43px 0 -6px rgba(255, 255, 255, 0.8),
326 + 0 44px 2px -6px rgba(0, 0, 0, 0.2);
327 +}
328 +
329 +.todo-count {
330 + float: left;
331 + text-align: left;
332 +}
333 +
334 +.filters {
335 + margin: 0;
336 + padding: 0;
337 + list-style: none;
338 + position: absolute;
339 + right: 0;
340 + left: 0;
341 +}
342 +
343 +.filters li {
344 + display: inline;
345 +}
346 +
347 +.filters li a {
348 + color: #83756f;
349 + margin: 2px;
350 + text-decoration: none;
351 +}
352 +
353 +.filters li a.selected {
354 + font-weight: bold;
355 +}
356 +
357 +.clear-completed {
358 + float: right;
359 + position: relative;
360 + line-height: 20px;
361 + text-decoration: none;
362 + background: rgba(0, 0, 0, 0.1);
363 + font-size: 11px;
364 + padding: 0 10px;
365 + border-radius: 3px;
366 + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
367 +}
368 +
369 +.clear-completed:hover {
370 + background: rgba(0, 0, 0, 0.15);
371 + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
372 +}
373 +
374 +.info {
375 + margin: 65px auto 0;
376 + color: #a6a6a6;
377 + font-size: 12px;
378 + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
379 + text-align: center;
380 +}
381 +
382 +.info a {
383 + color: inherit;
384 +}
385 +
386 +/*
387 + Hack to remove background from Mobile Safari.
388 + Can't use it globally since it destroys checkboxes in Firefox and Opera
389 +*/
390 +@media screen and (-webkit-min-device-pixel-ratio:0) {
391 + .toggle-all,
392 + .todo-list li .toggle {
393 + background: none;
394 + }
395 +
396 + .todo-list li .toggle {
397 + height: 40px;
398 + }
399 +
400 + .toggle-all {
401 + top: -56px;
402 + left: -15px;
403 + width: 65px;
404 + height: 41px;
405 + -webkit-transform: rotate(90deg);
406 + transform: rotate(90deg);
407 + -webkit-appearance: none;
408 + appearance: none;
409 + }
410 +}
411 +
412 +.hidden{
413 + display:none;
414 +}
415 +
416 +#benchmark {
417 + position: absolute;
418 + left: 0;
419 + top: 0;
420 + padding: 10px;
421 +}
\ No newline at end of file
examples/todomvc/css/bg.png
Binary files /dev/null and b/examples/todomvc/css/bg.png differ
examples/todomvc/index.html new
+19
@@ -0,0 +1,19 @@
1 +<!doctype html>
2 +<html lang="en">
3 + <head>
4 + <meta charset="utf-8">
5 + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6 + <title>React • TodoMVC</title>
7 + <link rel="stylesheet" href="css/base.css">
8 + <!--[if IE]>
9 + <script src="js/ie.js"></script>
10 + <![endif]-->
11 + </head>
12 + <body>
13 + <div id="todoapp"></div>
14 + <div id="benchmark"></div>
15 + <script src="../../build/react.js"></script>
16 + <script src="../../build/JSXTransformer.js"></script>
17 + <script type="text/jsx" src="js/app.js"></script>
18 + </body>
19 +</html>
examples/todomvc/js/app.js new
+236
@@ -0,0 +1,236 @@
1 +/**
2 + * @jsx React.DOM
3 + */
4 +
5 +var Utils = {
6 + // https://gist.github.com/1308368
7 + uuid: function(a,b){for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');return b},
8 + pluralize: function( count, word ) {
9 + return count === 1 ? word : word + 's';
10 + },
11 + store: function( namespace, data ) {
12 + if ( arguments.length > 1 ) {
13 + return localStorage.setItem( namespace, JSON.stringify( data ) );
14 + } else {
15 + var store = localStorage.getItem( namespace );
16 + return ( store && JSON.parse( store ) ) || [];
17 + }
18 + }
19 +};
20 +
21 +function cx(obj) {
22 + var s = '';
23 + for (var key in obj) {
24 + if (!obj.hasOwnProperty(key)) {
25 + continue;
26 + }
27 + if (obj[key]) {
28 + s += key + ' ';
29 + }
30 + }
31 + return s;
32 +}
33 +
34 +var ENTER_KEY = 13;
35 +
36 +var TodoItem = React.createClass({
37 + getInitialState: function() {
38 + return {editValue: this.props.todo.title};
39 + },
40 + onKeyUp: function(event) {
41 + this.setState({editValue: event.target.value});
42 + var val = event.target.value.trim();
43 + if (event.nativeEvent.keyCode !== ENTER_KEY || !val) {
44 + return;
45 + }
46 + this.props.onSave(val);
47 + },
48 + onEdit: function() {
49 + this.props.onEdit();
50 + this.refs.editField.getDOMNode().focus();
51 + },
52 + render: function() {
53 + return (
54 + <li class={cx({completed: this.props.todo.completed, editing: this.props.editing})}>
55 + <div class="view">
56 + <input
57 + class="toggle"
58 + type="checkbox"
59 + checked={this.props.todo.completed ? 'checked' : null}
60 + onChange={this.props.onToggle}
61 + />
62 + <label onDoubleClick={this.onEdit.bind(this)}>{this.props.todo.title}</label>
63 + <button class="destroy" onClick={this.props.onDestroy} />
64 + </div>
65 + <input ref="editField" class="edit" value={this.state.editValue} onKeyUp={this.onKeyUp.bind(this)} />
66 + </li>
67 + );
68 + }
69 +});
70 +
71 +var TodoFooter = React.createClass({
72 + render: function() {
73 + var activeTodoWord = Utils.pluralize(this.props.count, 'todo');
74 + var clearButton = null;
75 +
76 + if (this.props.completedCount > 0) {
77 + clearButton = (
78 + <button class="clear-completed" onClick={this.props.onClearCompleted}>Clear completed ({this.props.completedCount})</button>
79 + );
80 + }
81 +
82 + return (
83 + <footer class="footer">
84 + <span class="todo-count"><strong>{this.props.count}</strong>{' '}{activeTodoWord}{' '}left</span>
85 + {clearButton}
86 + </footer>
87 + );
88 + }
89 +});
90 +
91 +var TodoApp = React.createClass({
92 + getInitialState: function() {
93 + return {
94 + todos: Utils.store('todos-react'),
95 + newTodoValue: '',
96 + editing: {}
97 + };
98 + },
99 +
100 + handleKeyUp: function(event) {
101 + this.setState({newTodoValue: event.target.value});
102 + var val = event.target.value.trim();
103 + if (event.nativeEvent.keyCode !== ENTER_KEY || !val) {
104 + return;
105 + }
106 + var todos = this.state.todos;
107 + todos.push({
108 + id: Utils.uuid(),
109 + title: val,
110 + completed: false
111 + });
112 + this.setState({todos: todos, newTodoValue: ''});
113 + },
114 +
115 + toggleAll: function(event) {
116 + var checked = event.nativeEvent.target.checked;
117 + this.state.todos.map(function(todo) {
118 + todo.completed = checked;
119 + });
120 + this.setState({todos: this.state.todos});
121 + },
122 +
123 + toggle: function(todo) {
124 + todo.completed = !todo.completed;
125 + this.setState({todos: this.state.todos});
126 + },
127 +
128 + destroy: function(todo) {
129 + var newTodos = this.state.todos.filter(function(candidate) {
130 + return candidate.id !== todo.id;
131 + });
132 + this.setState({todos: newTodos});
133 + },
134 +
135 + edit: function(todo) {
136 + this.state.todos.map(function(todo) {
137 + this.state.editing[todo.id] = false;
138 + }.bind(this));
139 + this.state.editing[todo.id] = true;
140 + this.setState({editing: this.state.editing});
141 + },
142 +
143 + save: function(todo, text) {
144 + todo.title = text;
145 + this.state.editing[todo.id] = false;
146 + this.setState({todos: this.state.todos, editing: this.state.editing});
147 + },
148 +
149 + clearCompleted: function() {
150 + var newTodos = this.state.todos.filter(function(todo) {
151 + return !todo.completed;
152 + });
153 + this.setState({todos: newTodos});
154 + },
155 +
156 + render: function() {
157 + Utils.store(this.props.localStorageKey || 'todos-react', this.state.todos);
158 + var footer = null;
159 + var main = null;
160 + var todoItems = this.state.todos.map(function(todo) {
161 + return (
162 + <TodoItem
163 + todo={todo}
164 + onToggle={this.toggle.bind(this, todo)}
165 + onDestroy={this.destroy.bind(this, todo)}
166 + onEdit={this.edit.bind(this, todo)}
167 + editing={this.state.editing[todo.id]}
168 + onSave={this.save.bind(this, todo)}
169 + />
170 + );
171 + }.bind(this));
172 +
173 + var activeTodoCount = this.state.todos.filter(function(todo) {
174 + return !todo.completed;
175 + }).length;
176 + var completedCount = todoItems.length - activeTodoCount;
177 + if (activeTodoCount || completedCount) {
178 + footer =
179 + <TodoFooter
180 + count={activeTodoCount}
181 + completedCount={completedCount}
182 + onClearCompleted={this.clearCompleted.bind(this)}
183 + />;
184 + }
185 +
186 + if (todoItems.length) {
187 + main = (
188 + <section class="main">
189 + <input class="toggle-all" type="checkbox" onChange={this.toggleAll.bind(this)} />
190 + <label class="toggle-all-label">Mark all as complete</label>
191 + <ul class="todo-list">
192 + {todoItems}
193 + </ul>
194 + </section>
195 + );
196 + }
197 +
198 + return (
199 + <div>
200 + <section class="todoapp">
201 + <header class="header">
202 + <h1>todos</h1>
203 + <input
204 + class="new-todo"
205 + placeholder="What needs to be done?"
206 + autofocus="autofocus"
207 + onKeyUp={this.handleKeyUp.bind(this)}
208 + value={this.state.newTodoValue}
209 + />
210 + </header>
211 + {main}
212 + {footer}
213 + </section>
214 + <footer class="info">
215 + <p>Double-click to edit a todo</p>
216 + <p>Created by{' '}<a href="http://github.com/petehunt/">petehunt</a></p>
217 + <p>Part of{' '}<a href="http://todomvc.com">TodoMVC</a></p>
218 + </footer>
219 + </div>
220 + );
221 + }
222 +});
223 +
224 +React.renderComponent(<TodoApp />, document.getElementById('todoapp'));
225 +
226 +// Some benchmarking that requires either a custom build of React or more
227 +// modules exposed from React.*
228 +// var initTime = ReactMount.totalInstantiationTime + ReactMount.totalInjectionTime;
229 +// var benchmark = document.getElementById('benchmark');
230 +// setInterval(function() {
231 +// benchmark.innerHTML = (
232 +// 'Init render time = ' + initTime + 'ms' +
233 +// '<br />' +
234 +// 'Post-init render time = ' + (ReactMount.totalInstantiationTime + ReactMount.totalInjectionTime - initTime) + 'ms'
235 +// );
236 +// }, 1000);
examples/todomvc/js/ie.js new
+34
@@ -0,0 +1,34 @@
1 +/*! HTML5 Shiv pre3.6 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
2 + Uncompressed source: https://github.com/aFarkas/html5shiv */
3 + (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();
4 + 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>";
5 + 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||
6 + "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();
7 + 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);
8 +
9 +/* ES5-shim
10 +https://github.com/kriskowal/es5-shim
11 +Copyright 2009-2012 by contributors, MIT License */
12 +(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)===
13 +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;
14 +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?
15 +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)!=
16 +"[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};
17 +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=
18 +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>>>
19 +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==
20 +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)==
21 +"[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,
22 +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=
23 +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=
24 +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";
25 +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?"-":
26 +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=
27 +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):
28 +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})"+")"+")?)?)?)?"+
29 +"$");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=
30 +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,
31 +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===
32 +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=
33 +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===
34 +"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)}});
examples/wrapup/index.html new
+54
@@ -0,0 +1,54 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <meta http-equiv='Content-type' content='text/html; charset=utf-8'>
5 + <title>Basic Example (WrapUp)</title>
6 + <link rel="stylesheet" href="../shared/css/base.css" />
7 + </head>
8 + <body>
9 + <h1>Basic Example (WrapUp)</h1>
10 + <div id="container">
11 + <p>
12 + To install React, follow the instructions on
13 + <a href="http://www.github.com/facebook/react.js/">GitHub</a>.
14 + </p>
15 + <p>
16 + If you can see this, React is not working right.
17 + Make sure to run <code>grunt</code>.
18 + </p>
19 + </div>
20 + <h4>Example Details</h4>
21 + <ul>
22 + <li>
23 + This is built with
24 + <a href="https://github.com/mootools/wrapup">WrapUp</a>.
25 + </li>
26 + </ul>
27 + <p>
28 + </p>
29 + <p>
30 + Learn more at
31 + <a href="http://facebook.github.io/react" target="_blank">facebook.github.io/react</a>.
32 + </p>
33 + <script src="../../build/react.js"></script>
34 + <script>
35 + var ExampleApplication = React.createClass({
36 + render: function() {
37 + var elapsed = Math.round(this.props.elapsed / 100);
38 + var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
39 + var message =
40 + 'React has been successfully running for ' + seconds + ' seconds.';
41 +
42 + return React.DOM.p(null, message);
43 + }
44 + });
45 + var start = new Date().getTime();
46 + setInterval(function() {
47 + React.renderComponent(
48 + ExampleApplication({elapsed: new Date().getTime() - start}),
49 + document.getElementById('container')
50 + );
51 + }, 50);
52 + </script>
53 + </body>
54 +</html>
grunt/config/browserify.js new
+94
@@ -0,0 +1,94 @@
1 +/* jshint multistr:true */
2 +/* jshint -W040 */
3 +
4 +'use strict';
5 +
6 +var grunt = require('grunt');
7 +var UglifyJS = require('uglify-js');
8 +
9 +var SIMPLE_TEMPLATE =
10 +'/**\n\
11 + * @PACKAGE@ v@VERSION@\n\
12 + */';
13 +
14 +var LICENSE_TEMPLATE =
15 +'/**\n\
16 + * @PACKAGE@ v@VERSION@\n\
17 + *\n\
18 + * Copyright 2013 Facebook, Inc.\n\
19 + *\n\
20 + * Licensed under the Apache License, Version 2.0 (the "License");\n\
21 + * you may not use this file except in compliance with the License.\n\
22 + * You may obtain a copy of the License at\n\
23 + *\n\
24 + * http://www.apache.org/licenses/LICENSE-2.0\n\
25 + *\n\
26 + * Unless required by applicable law or agreed to in writing, software\n\
27 + * distributed under the License is distributed on an "AS IS" BASIS,\n\
28 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n\
29 + * See the License for the specific language governing permissions and\n\
30 + * limitations under the License.\n\
31 + */';
32 +
33 +function minify(src) {
34 + return UglifyJS.minify(src, { fromString: true }).code;
35 +}
36 +
37 +// TODO: move this out to another build step maybe.
38 +function bannerify(src) {
39 + var version = grunt.config.data.pkg.version;
40 + return LICENSE_TEMPLATE.replace('@PACKAGE@', this.data.standalone)
41 + .replace('@VERSION@', version) +
42 + '\n' + src;
43 +}
44 +
45 +function simpleBannerify(src) {
46 + var version = grunt.config.data.pkg.version;
47 + return SIMPLE_TEMPLATE.replace('@PACKAGE@', this.data.standalone)
48 + .replace('@VERSION@', version) +
49 + '\n' + src;
50 +}
51 +
52 +// Our basic config which we'll add to to make our other builds
53 +var basic = {
54 + entries: [
55 + './build/modules/React.js'
56 + ],
57 + outfile: './build/react.js',
58 + debug: false,
59 + standalone: 'React',
60 + after: [simpleBannerify]
61 +};
62 +
63 +var min = grunt.util._.merge({}, basic, {
64 + outfile: './build/react.min.js',
65 + debug: false,
66 + after: [minify, bannerify]
67 +});
68 +
69 +var transformer = {
70 + entries:[
71 + './vendor/browser-transforms.js'
72 + ],
73 + outfile: './build/JSXTransformer.js',
74 + debug: false,
75 + standalone: 'JSXTransformer',
76 + after: [simpleBannerify]
77 +};
78 +
79 +var test = {
80 + entries: [
81 + "./build/modules/test/all.js",
82 + "./build/modules/**/__tests__/*-test.js"
83 + ],
84 + outfile: './build/react-test.js',
85 + debug: false,
86 + standalone: false
87 +};
88 +
89 +module.exports = {
90 + basic: basic,
91 + test: test,
92 + min: min,
93 + transformer: transformer
94 +};
grunt/config/compare_size.js new
+17
@@ -0,0 +1,17 @@
1 +'use strict';
2 +
3 +var gzip = require('gzip-js');
4 +
5 +module.exports = {
6 + files: [
7 + 'build/*.js'
8 + ],
9 + options: {
10 + compress: {
11 + gz: function(contents) {
12 + return gzip.zip(contents, {}).length;
13 + }
14 + },
15 + cache: "build/.sizecache.json"
16 + }
17 +};
grunt/config/compress.js new
+16
@@ -0,0 +1,16 @@
1 +'use strict';
2 +
3 +var grunt = require('grunt');
4 +
5 +var version = grunt.config.data.pkg.version;
6 +
7 +module.exports = {
8 + starter: {
9 + options: {
10 + archive: './build/react-' + version + '.zip'
11 + },
12 + files: [
13 + {cwd: './build/starter', src: ['**', '!examples/wrapup/**'], dest: 'react-' + version + '/'}
14 + ]
15 + }
16 +};
\ No newline at end of file
grunt/config/copy.js new
+16
@@ -0,0 +1,16 @@
1 +'use strict';
2 +
3 +module.exports = {
4 +
5 + react_docs: {
6 + files: [
7 + {
8 + src: ['react.min.js', 'JSXTransformer.js'],
9 + dest: 'docs/js/',
10 + cwd: 'build/',
11 + expand: true
12 + }
13 + ]
14 + }
15 +
16 +};
grunt/config/jshint.js new
+27
@@ -0,0 +1,27 @@
1 +'use strict';
2 +
3 +module.exports = {
4 + src: {
5 + options: {
6 + jshintrc: './src/.jshintrc'
7 + },
8 + files: {
9 + // We don't care about src/vendor (we shouldn't be touching this), and
10 + // tests often use JSX, which JSHint can't handle.
11 + src: [
12 + './src/**/*.js',
13 + '!./src/vendor/**',
14 + '!./src/**/__tests__/**',
15 + '!./src/test/**'
16 + ]
17 + }
18 + },
19 + project: {
20 + options: {
21 + jshintrc: './.jshintrc'
22 + },
23 + files: {
24 + src: ['./Gruntfile.js', './grunt/**/*.js', './bin/*.js', './vendor/*.js']
25 + }
26 + }
27 +};
grunt/config/jsx/debug.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "debug": true,
3 + "constants": {
4 + "__DEV__": true
5 + }
6 +}
grunt/config/jsx/jsx.js new
+29
@@ -0,0 +1,29 @@
1 +'use strict';
2 +
3 +var rootIDs = [
4 + "React"
5 +];
6 +
7 +var debug = {
8 + rootIDs: rootIDs,
9 + configFile: "grunt/config/jsx/debug.json"
10 +};
11 +
12 +var test = {
13 + rootIDs: rootIDs.concat([
14 + "test/all.js",
15 + "**/__tests__/*.js"
16 + ]),
17 + configFile: debug.configFile
18 +};
19 +
20 +var release = {
21 + rootIDs: rootIDs,
22 + configFile: "grunt/config/jsx/release.json"
23 +};
24 +
25 +module.exports = {
26 + debug: debug,
27 + test: test,
28 + release: release
29 +};
grunt/config/jsx/release.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "debug": false,
3 + "constants": {
4 + "__DEV__": false
5 + }
6 +}
grunt/config/phantom.js new
+8
@@ -0,0 +1,8 @@
1 +var grunt = require("grunt");
2 +
3 +exports.run = {
4 + port: 8080,
5 + harness: "test/phantom-harness.js",
6 + // Run `grunt test --debug` to enable in-browser testing.
7 + debug: !!grunt.option("debug")
8 +};
grunt/config/version.js new
+1
@@ -0,0 +1 @@
1 +module.exports = '0.6.0';
\ No newline at end of file
grunt/config/wrapup.js new
+31
@@ -0,0 +1,31 @@
1 +'use strict';
2 +
3 +var grunt = require('grunt');
4 +var UglifyJS = require('uglify-js');
5 +
6 +function minify(src) {
7 + return UglifyJS.minify(src, { fromString: true }).code;
8 +}
9 +
10 +// Our basic config which we'll add to to make our other builds
11 +var basic = {
12 + outfile: './build/react-global.js',
13 + requires: {
14 + React: './build/modules/React.js'
15 + },
16 + debug: true
17 +};
18 +
19 +module.exports = {
20 + basic: basic
21 +};
22 +
23 +// Create minified versions of each build
24 +for (var buildName in module.exports) {
25 + var build = module.exports[buildName];
26 + module.exports[buildName + '-min'] = grunt.util._.merge({}, build, {
27 + outfile: build.outfile.replace('.js', '.min.js'),
28 + debug: false,
29 + after: minify
30 + });
31 +}
grunt/tasks/browserify.js new
+56
@@ -0,0 +1,56 @@
1 +'use strict';
2 +
3 +var browserify = require('browserify');
4 +var grunt = require('grunt');
5 +
6 +module.exports = function() {
7 + var config = this.data;
8 +
9 + // This task is async...
10 + var done = this.async();
11 +
12 + // More/better assertions
13 + // grunt.config.requires('outfile');
14 + // grunt.config.requires('entries');
15 + config.requires = config.requires || {};
16 + config.transforms = config.transforms || [];
17 + config.after = config.after || [];
18 + if (typeof config.after === 'function') {
19 + config.after = [config.after];
20 + }
21 +
22 + // create the bundle we'll work with
23 + var entries = grunt.file.expand(config.entries);
24 + var bundle = browserify(entries);
25 +
26 + // Make sure the things that need to be exposed are.
27 + // TODO: support a blob pattern maybe?
28 + for (var name in config.requires) {
29 + bundle.require(config.requires[name], { expose: name });
30 + }
31 +
32 + // Extract other options
33 + var options = {
34 + debug: config.debug, // sourcemaps
35 + standalone: config.standalone // global
36 + };
37 +
38 + // TODO: make sure this works, test with this too
39 + config.transforms.forEach(bundle.transform, this);
40 +
41 + // Actually bundle it up
42 + var _this = this;
43 + bundle.bundle(options, function(err, src) {
44 + if (err) {
45 + grunt.log.error(err);
46 + done();
47 + }
48 +
49 + config.after.forEach(function(fn) {
50 + src = fn.call(_this, src);
51 + });
52 +
53 + grunt.file.write(config.outfile, src);
54 + done();
55 + });
56 +};
grunt/tasks/jsx.js new
+27
@@ -0,0 +1,27 @@
1 +'use strict';
2 +
3 +var exec = require("child_process").exec;
4 +var expand = require("grunt").file.expand;
5 +
6 +module.exports = function() {
7 + var done = this.async();
8 + var config = this.data;
9 +
10 + var args = [
11 + "bin/jsx",
12 + "src",
13 + "build/modules"
14 + ];
15 +
16 + var rootIDs = expand({
17 + nonull: true, // Keep IDs that don't expand to anything.
18 + cwd: "src"
19 + }, config.rootIDs).map(function(id) {
20 + return id.replace(/\.js$/i, "");
21 + });
22 +
23 + args.push.apply(args, rootIDs);
24 + args.push("--config", config.configFile);
25 +
26 + exec(args.join(" "), done);
27 +};
grunt/tasks/phantom.js new
+60
@@ -0,0 +1,60 @@
1 +'use strict';
2 +
3 +var assert = require("assert");
4 +var grunt = require("grunt");
5 +var spawn = grunt.util.spawn;
6 +var semver = require("semver");
7 +var MIN_VERSION = "1.9.0";
8 +var phantomjs = require("phantomjs").path;
9 +
10 +function checkVersion(error, result, code) {
11 + if (error) {
12 + throw error;
13 + }
14 + assert.strictEqual(code, 0);
15 +
16 + var version = result.stdout;
17 +
18 + assert.ok(
19 + semver.valid(version),
20 + "Invalid PhantomJS version: " + version
21 + );
22 +
23 + assert.ok(
24 + semver.gte(version, MIN_VERSION),
25 + "PhantomJS v" + version + " too old; need to install " +
26 + "v" + MIN_VERSION + " or higher."
27 + );
28 +}
29 +
30 +function run(config, done) {
31 + var args = [
32 + config.harness,
33 + "--port", config.port
34 + ];
35 +
36 + if (config.debug) {
37 + args.push("--debug");
38 + }
39 +
40 + var child = spawn({
41 + cmd: phantomjs,
42 + args: args
43 + }, done);
44 +
45 + child.stdout.pipe(process.stdout);
46 + child.stderr.pipe(process.stderr);
47 +}
48 +
49 +module.exports = function() {
50 + var config = this.data;
51 + var done = this.async();
52 +
53 + spawn({
54 + cmd: phantomjs,
55 + args: ["--version"]
56 + }, function(error, result, code) {
57 + checkVersion(error, result, code);
58 + run(config, done);
59 + });
60 +};
grunt/tasks/release.js new
+182
@@ -0,0 +1,182 @@
1 +// function calls get bound so "possible strict mode violations" aren't
2 +// jshint -W040
3 +
4 +'use strict';
5 +
6 +var grunt = require('grunt');
7 +
8 +var BOWER_PATH = '../react-bower/';
9 +var BOWER_GLOB = [BOWER_PATH + '*'];
10 +var BOWER_FILES = ['React.js', 'React.min.js', 'JSXTransformer.js'];
11 +var GH_PAGES_PATH = '../react-gh-pages/';
12 +var GH_PAGES_GLOB = [GH_PAGES_PATH + '*'];
13 +
14 +var EXAMPLES_PATH = 'examples/';
15 +var EXAMPLES_GLOB = [EXAMPLES_PATH + '/**/*.*'];
16 +
17 +var STARTER_PATH = 'starter/';
18 +var STARTER_GLOB = [STARTER_PATH + '/**/*.*'];
19 +
20 +var STARTER_BUILD_PATH = 'build/starter/';
21 +
22 +var JS_PATH = 'build/';
23 +var JS_GLOB = [JS_PATH + '/*.js'];
24 +
25 +var VERSION;
26 +var VERSION_STRING;
27 +
28 +function _gitCommitAndTag(cwd, commitMsg, tag, cb) {
29 + // `git add *` to make sure we catch untracked files
30 + // `git add -u` to make sure we remove deleted files
31 + // `git commit -m {commitMsg}`
32 + // `git tag -a {tag}`
33 + var opts = { cwd: cwd};
34 + var gitAddAll = {
35 + cmd: 'git',
36 + args: ['add', '*'],
37 + opts: opts
38 + };
39 + var gitAddDel = {
40 + cmd: 'git',
41 + args: ['add', '-u'],
42 + opts: opts
43 + };
44 + var gitCommit = {
45 + cmd: 'git',
46 + args: ['commit', '-m', commitMsg],
47 + opts: opts
48 + };
49 + var gitTag = {
50 + cmd: 'git',
51 + args: ['tag', tag],
52 + opts: opts
53 + };
54 + grunt.util.spawn(gitAddAll, function() {
55 + grunt.util.spawn(gitAddDel, function() {
56 + grunt.util.spawn(gitCommit, function() {
57 + if (tag) {
58 + grunt.util.spawn(gitTag, cb);
59 + }
60 + else {
61 + cb();
62 + }
63 + });
64 + });
65 + });
66 +}
67 +
68 +function setup() {
69 + if (!grunt.file.exists(BOWER_PATH)) {
70 + grunt.log.error('Make sure you have the react-bower repository checked ' +
71 + 'out at ../react-bower');
72 + return false;
73 + }
74 +
75 + if (!grunt.file.exists(GH_PAGES_PATH)) {
76 + grunt.log.error('Make sure you have the react gh-pages branch checked ' +
77 + 'out at ../react-gh-pages.');
78 + return false;
79 + }
80 +
81 + VERSION = grunt.config.data.pkg.version;
82 + VERSION_STRING = 'v' + VERSION;
83 +}
84 +
85 +function bower() {
86 + var done = this.async();
87 +
88 + // read current bower.json
89 + var bowerFilePath = BOWER_PATH + 'bower.json';
90 + var bowerInfo = grunt.file.readJSON(bowerFilePath);
91 +
92 + // clean out the bower folder in case we're removing files
93 + var files = grunt.file.expand(BOWER_GLOB);
94 + files.forEach(function(file) {
95 + grunt.file.delete(file, { force: true });
96 + });
97 +
98 + // Update bower package version and save the file back.
99 + bowerInfo.version = VERSION;
100 + var bowerFileContents = JSON.stringify(bowerInfo, null, 2);
101 + grunt.file.write(bowerFilePath, bowerFileContents);
102 +
103 + // Now copy over build files
104 + BOWER_FILES.forEach(function(file) {
105 + grunt.file.copy('build/' + file, BOWER_PATH + file);
106 + });
107 +
108 + // Commit and tag the repo
109 + _gitCommitAndTag(BOWER_PATH, VERSION_STRING, VERSION_STRING, done);
110 +}
111 +
112 +function docs() {
113 + var done = this.async();
114 +
115 + var files = grunt.file.expand(GH_PAGES_GLOB);
116 + files.forEach(function(file) {
117 + grunt.file.delete(file, { force: true });
118 + });
119 +
120 + grunt.file.copy('build/react-' + VERSION + '.zip', 'docs/downloads/react-' + VERSION + '.zip');
121 +
122 + // Build the docs with `rake release`, which will compile the CSS & JS, then
123 + // build jekyll into GH_PAGES_PATH
124 + var rakeOpts = {
125 + cmd: 'rake',
126 + args: ['release'],
127 + opts: { cwd: 'docs' }
128 + };
129 + grunt.util.spawn(rakeOpts, function() {
130 + // Commit the repo. We don't really care about tagging this.
131 + _gitCommitAndTag(GH_PAGES_PATH, VERSION_STRING, null, done);
132 + });
133 +}
134 +
135 +function msg() {
136 + // Just output a friendly reminder message for the rest of the process
137 + grunt.log.subhead('Release *almost* complete...');
138 + [
139 + 'Still todo:',
140 + '* put files on CDN',
141 + '* push changes to git repositories',
142 + '* publish npm module (`npm publish .`)',
143 + '* publish gem (`gem push react-source-' + VERSION + '.gem`)',
144 + '* announce it on FB/Twitter/mailing list'
145 + ].forEach(function(ln) {
146 + grunt.log.writeln(ln);
147 + });
148 +}
149 +
150 +function starter() {
151 + // Copy over examples/ to build/starter/examples/
152 + // and starter/ to build/starter/
153 +
154 + grunt.file.expand(EXAMPLES_GLOB).forEach(function(file) {
155 + grunt.file.copy(
156 + file,
157 + STARTER_BUILD_PATH + file
158 + );
159 + });
160 +
161 + grunt.file.expand(STARTER_GLOB).forEach(function(file) {
162 + grunt.file.copy(
163 + file,
164 + 'build/' + file
165 + );
166 + });
167 +
168 + grunt.file.expand(JS_GLOB).forEach(function(file) {
169 + grunt.file.copy(
170 + file,
171 + STARTER_BUILD_PATH + file
172 + );
173 + });
174 +}
175 +
176 +module.exports = {
177 + setup: setup,
178 + bower: bower,
179 + docs: docs,
180 + msg: msg,
181 + starter: starter
182 +};
grunt/tasks/wrapup.js new
+34
@@ -0,0 +1,34 @@
1 +'use strict';
2 +
3 +var grunt = require('grunt');
4 +
5 +module.exports = function() {
6 + var wrup = require('wrapup')();
7 + var config = this.data;
8 +
9 + // This task is async...
10 + var done = this.async();
11 +
12 + config.requires = config.requires || {};
13 +
14 + Object.keys(config.requires).forEach(function(id) {
15 + wrup = wrup.require(id, config.requires[id]);
16 + });
17 +
18 + wrup.options({
19 + sourcemap: config.debug && config.outfile.replace(/\.js$/i, ".map")
20 + }).up(function(err, src) {
21 + if (err) {
22 + grunt.log.error(err);
23 + done();
24 + }
25 +
26 + // TODO: post processing hooks, scope?
27 + if (config.after) {
28 + src = config.after.call(this, src);
29 + }
30 +
31 + grunt.file.write(config.outfile, src);
32 + done();
33 + });
34 +};
lib/react/source.rb new
+7
@@ -0,0 +1,7 @@
1 +module React
2 + module Source
3 + def self.bundled_path_for(build)
4 + File.expand_path("../../../build/#{build}", __FILE__)
5 + end
6 + end
7 +end
main.js new
+12
@@ -0,0 +1,12 @@
1 +'use strict';
2 +
3 +var React = require('./build/React');
4 +var visitors = require('./vendor/fbtransform/visitors').transformVisitors;
5 +var transform = require('./vendor/fbtransform/lib/transform').transform;
6 +
7 +module.exports = {
8 + React: React,
9 + transform: function(code) {
10 + return transform(visitors.react, code).code;
11 + }
12 +};
package.json new
+57
@@ -0,0 +1,57 @@
1 +{
2 + "name": "react-tools",
3 + "version": "0.3.0",
4 + "keywords": [
5 + "clientside"
6 + ],
7 + "homepage": "https://facebook.github.io/react",
8 + "bugs": "https://github.com/facebook/react/issues",
9 + "licenses": [
10 + {
11 + "type": "Apache-2.0",
12 + "url": "http://www.apache.org/licenses/LICENSE-2.0"
13 + }
14 + ],
15 + "files": [
16 + "README.md",
17 + "main.js",
18 + "bin/jsx",
19 + "build/React.js",
20 + "vendor/fbtransform/",
21 + "vendor/woodchipper.js"
22 + ],
23 + "main": "main.js",
24 + "bin": {
25 + "jsx": "./bin/jsx"
26 + },
27 + "repository": {
28 + "type": "git",
29 + "url": "https://github.com/facebook/react"
30 + },
31 + "scripts": {
32 + "test": "grunt test"
33 + },
34 + "dependencies": {
35 + "base62": "~0.1.1",
36 + "commoner": "~0.6.8",
37 + "esprima": "git://github.com/facebook/esprima#fb-harmony",
38 + "recast": "~0.3.3"
39 + },
40 + "devDependencies": {
41 + "browserify": "~2.14.2",
42 + "wrapup": "~0.12.0",
43 + "grunt": "~0.4.1",
44 + "grunt-contrib-copy": "~0.4.1",
45 + "grunt-contrib-jshint": "~0.5.4",
46 + "optimist": "~0.4.0",
47 + "phantomjs": ">= 1.9.0",
48 + "semver": ">= 1.1.4",
49 + "source-map": "~0.1.22",
50 + "uglify-js": "~2.3.6",
51 + "grunt-contrib-clean": "~0.4.1",
52 + "grunt-compare-size": "~0.4.0",
53 + "gzip-js": "~0.3.2",
54 + "grunt-contrib-compress": "~0.5.1"
55 + },
56 + "preferGlobal": true
57 +}
react-source.gemspec new
+23
@@ -0,0 +1,23 @@
1 +#encoding: utf-8
2 +
3 +require('json')
4 +package = JSON.parse(File.read('package.json'))
5 +
6 +gemspec = Gem::Specification.new do |s|
7 + s.name = 'react-source'
8 + s.version = package['version']
9 + s.license = 'Apache-2.0'
10 +
11 + s.homepage = 'https://github.com/facebook/react.js'
12 + s.summary = 'Ruby bridge to JSX & the React JavaScript library.'
13 +
14 + s.authors = ['Paul O’Shannessy']
15 + s.email = ['paul@oshannessy.com']
16 +
17 + s.files = Dir[
18 + 'build/react.js',
19 + 'build/react.min.js',
20 + 'build/JSXTransformer.js',
21 + 'lib/react/source.rb'
22 + ]
23 +end
src/.jshintrc new
+26
@@ -0,0 +1,26 @@
1 +{
2 + "browser": true,
3 +
4 + "boss": true,
5 + "curly": true,
6 + "devel": true,
7 + "eqnull": true,
8 + "expr": true,
9 + "funcscope": true,
10 + "globalstrict": true,
11 + "loopfunc": true,
12 + "newcap": false,
13 + "noempty": true,
14 + "nonstandard": true,
15 + "onecase": true,
16 + "regexdash": true,
17 + "trailing": true,
18 + "undef": true,
19 + "unused": "vars",
20 +
21 + "globals": {
22 + "__DEV__": false,
23 + "require": false,
24 + "module": false
25 + }
26 +}
src/core/BrowserEnv.js new
+36
@@ -0,0 +1,36 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule BrowserEnv
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * A place to share/cache browser/chrome level computations.
23 + */
24 +var BrowserEnv = {
25 + currentScrollLeft: 0,
26 + currentScrollTop: 0,
27 + browserInfo: null,
28 + refreshAuthoritativeScrollValues: function() {
29 + BrowserEnv.currentScrollLeft =
30 + document.body.scrollLeft + document.documentElement.scrollLeft;
31 + BrowserEnv.currentScrollTop =
32 + document.body.scrollTop + document.documentElement.scrollTop;
33 + }
34 +};
35 +
36 +module.exports = BrowserEnv;
src/core/React.js new
+45
@@ -0,0 +1,45 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule React
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactCompositeComponent = require('ReactCompositeComponent');
22 +var ReactComponent = require('ReactComponent');
23 +var ReactDOM = require('ReactDOM');
24 +var ReactMount = require('ReactMount');
25 +
26 +var ReactDefaultInjection = require('ReactDefaultInjection');
27 +
28 +ReactDefaultInjection.inject();
29 +
30 +var React = {
31 + DOM: ReactDOM,
32 + initializeTouchEvents: function(shouldUseTouch) {
33 + ReactMount.useTouchEvents = shouldUseTouch;
34 + },
35 + autoBind: ReactCompositeComponent.autoBind,
36 + createClass: ReactCompositeComponent.createClass,
37 + createComponentRenderer: ReactMount.createComponentRenderer,
38 + constructAndRenderComponent: ReactMount.constructAndRenderComponent,
39 + constructAndRenderComponentByID: ReactMount.constructAndRenderComponentByID,
40 + renderComponent: ReactMount.renderComponent,
41 + unmountAndReleaseReactRootNode: ReactMount.unmountAndReleaseReactRootNode,
42 + isValidComponent: ReactComponent.isValidComponent
43 +};
44 +
45 +module.exports = React;
src/core/ReactComponent.js new
+404
@@ -0,0 +1,404 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactComponent
17 + */
18 +
19 +"use strict";
20 +
21 +var ExecutionEnvironment = require('ExecutionEnvironment');
22 +var ReactCurrentOwner = require('ReactCurrentOwner');
23 +var ReactDOMIDOperations = require('ReactDOMIDOperations');
24 +var ReactMount = require('ReactMount');
25 +var ReactOwner = require('ReactOwner');
26 +var ReactReconcileTransaction = require('ReactReconcileTransaction');
27 +
28 +var invariant = require('invariant');
29 +var keyMirror = require('keyMirror');
30 +var merge = require('merge');
31 +
32 +/**
33 + * Prop key that references a component's owner.
34 + * @private
35 + */
36 +var OWNER = '{owner}';
37 +
38 +/**
39 + * Every React component is in one of these life cycles.
40 + */
41 +var ComponentLifeCycle = keyMirror({
42 + /**
43 + * Mounted components have a DOM node representation and are capable of
44 + * receiving new props.
45 + */
46 + MOUNTED: null,
47 + /**
48 + * Unmounted components are inactive and cannot receive new props.
49 + */
50 + UNMOUNTED: null
51 +});
52 +
53 +/**
54 + * Components are the basic units of composition in React.
55 + *
56 + * Every component accepts a set of keyed input parameters known as "props" that
57 + * are initialized by the constructor. Once a component is mounted, the props
58 + * can be mutated using `setProps` or `replaceProps`.
59 + *
60 + * Every component is capable of the following operations:
61 + *
62 + * `mountComponent`
63 + * Initializes the component, renders markup, and registers event listeners.
64 + *
65 + * `receiveProps`
66 + * Updates the rendered DOM nodes given a new set of props.
67 + *
68 + * `unmountComponent`
69 + * Releases any resources allocated by this component.
70 + *
71 + * Components can also be "owned" by other components. Being owned by another
72 + * component means being constructed by that component. This is different from
73 + * being the child of a component, which means having a DOM representation that
74 + * is a child of the DOM representation of that component.
75 + *
76 + * @class ReactComponent
77 + */
78 +var ReactComponent = {
79 +
80 + /**
81 + * @param {?object} object
82 + * @return {boolean} True if `object` is a valid component.
83 + * @final
84 + */
85 + isValidComponent: function(object) {
86 + return !!(
87 + object &&
88 + typeof object.mountComponentIntoNode === 'function' &&
89 + typeof object.receiveProps === 'function'
90 + );
91 + },
92 +
93 + /**
94 + * @internal
95 + */
96 + LifeCycle: ComponentLifeCycle,
97 +
98 + /**
99 + * React references `ReactDOMIDOperations` using this property in order to
100 + * allow dependency injection.
101 + *
102 + * @internal
103 + */
104 + DOMIDOperations: ReactDOMIDOperations,
105 +
106 + /**
107 + * React references `ReactReconcileTransaction` using this property in order
108 + * to allow dependency injection.
109 + *
110 + * @internal
111 + */
112 + ReactReconcileTransaction: ReactReconcileTransaction,
113 +
114 + /**
115 + * @param {object} DOMIDOperations
116 + * @final
117 + */
118 + setDOMOperations: function(DOMIDOperations) {
119 + ReactComponent.DOMIDOperations = DOMIDOperations;
120 + },
121 +
122 + /**
123 + * @param {Transaction} ReactReconcileTransaction
124 + * @final
125 + */
126 + setReactReconcileTransaction: function(ReactReconcileTransaction) {
127 + ReactComponent.ReactReconcileTransaction = ReactReconcileTransaction;
128 + },
129 +
130 + /**
131 + * Base functionality for every ReactComponent constructor.
132 + *
133 + * @lends {ReactComponent.prototype}
134 + */
135 + Mixin: {
136 +
137 + /**
138 + * Returns the DOM node rendered by this component.
139 + *
140 + * @return {?DOMElement} The root node of this component.
141 + * @final
142 + * @protected
143 + */
144 + getDOMNode: function() {
145 + invariant(
146 + ExecutionEnvironment.canUseDOM,
147 + 'getDOMNode(): The DOM is not supported in the current environment.'
148 + );
149 + invariant(
150 + this._lifeCycleState === ComponentLifeCycle.MOUNTED,
151 + 'getDOMNode(): A component must be mounted to have a DOM node.'
152 + );
153 + var rootNode = this._rootNode;
154 + if (!rootNode) {
155 + rootNode = document.getElementById(this._rootNodeID);
156 + if (!rootNode) {
157 + // TODO: Log the frequency that we reach this path.
158 + rootNode = ReactMount.findReactRenderedDOMNodeSlow(this._rootNodeID);
159 + }
160 + this._rootNode = rootNode;
161 + }
162 + return rootNode;
163 + },
164 +
165 + /**
166 + * Sets a subset of the props.
167 + *
168 + * @param {object} partialProps Subset of the next props.
169 + * @final
170 + * @public
171 + */
172 + setProps: function(partialProps) {
173 + this.replaceProps(merge(this.props, partialProps));
174 + },
175 +
176 + /**
177 + * Replaces all of the props.
178 + *
179 + * @param {object} props New props.
180 + * @final
181 + * @public
182 + */
183 + replaceProps: function(props) {
184 + invariant(
185 + !this.props[OWNER],
186 + 'replaceProps(...): You called `setProps` or `replaceProps` on a ' +
187 + 'component with an owner. This is an anti-pattern since props will ' +
188 + 'get reactively updated when rendered. Instead, change the owner\'s ' +
189 + '`render` method to pass the correct value as props to the component ' +
190 + 'where it is created.'
191 + );
192 + var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
193 + transaction.perform(this.receiveProps, this, props, transaction);
194 + ReactComponent.ReactReconcileTransaction.release(transaction);
195 + },
196 +
197 + /**
198 + * Base constructor for all React component.
199 + *
200 + * Subclasses that override this method should make sure to invoke
201 + * `ReactComponent.Mixin.construct.call(this, ...)`.
202 + *
203 + * @param {?object} initialProps
204 + * @param {*} children
205 + * @internal
206 + */
207 + construct: function(initialProps, children) {
208 + this.props = initialProps || {};
209 + if (typeof children !== 'undefined') {
210 + this.props.children = children;
211 + }
212 + // Record the component responsible for creating this component.
213 + this.props[OWNER] = ReactCurrentOwner.current;
214 + // All components start unmounted.
215 + this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
216 + },
217 +
218 + /**
219 + * Initializes the component, renders markup, and registers event listeners.
220 + *
221 + * NOTE: This does not insert any nodes into the DOM.
222 + *
223 + * Subclasses that override this method should make sure to invoke
224 + * `ReactComponent.Mixin.mountComponent.call(this, ...)`.
225 + *
226 + * @param {string} rootID DOM ID of the root node.
227 + * @param {ReactReconcileTransaction} transaction
228 + * @return {?string} Rendered markup to be inserted into the DOM.
229 + * @internal
230 + */
231 + mountComponent: function(rootID, transaction) {
232 + invariant(
233 + this._lifeCycleState === ComponentLifeCycle.UNMOUNTED,
234 + 'mountComponent(%s, ...): Can only mount an unmounted component.',
235 + rootID
236 + );
237 + var props = this.props;
238 + if (props.ref != null) {
239 + ReactOwner.addComponentAsRefTo(this, props.ref, props[OWNER]);
240 + }
241 + this._rootNodeID = rootID;
242 + this._lifeCycleState = ComponentLifeCycle.MOUNTED;
243 + // Effectively: return '';
244 + },
245 +
246 + /**
247 + * Releases any resources allocated by `mountComponent`.
248 + *
249 + * NOTE: This does not remove any nodes from the DOM.
250 + *
251 + * Subclasses that override this method should make sure to invoke
252 + * `ReactComponent.Mixin.unmountComponent.call(this)`.
253 + *
254 + * @internal
255 + */
256 + unmountComponent: function() {
257 + invariant(
258 + this._lifeCycleState === ComponentLifeCycle.MOUNTED,
259 + 'unmountComponent(): Can only unmount a mounted component.'
260 + );
261 + var props = this.props;
262 + if (props.ref != null) {
263 + ReactOwner.removeComponentAsRefFrom(this, props.ref, props[OWNER]);
264 + }
265 + this._rootNode = null;
266 + this._rootNodeID = null;
267 + this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
268 + },
269 +
270 + /**
271 + * Updates the rendered DOM nodes given a new set of props.
272 + *
273 + * Subclasses that override this method should make sure to invoke
274 + * `ReactComponent.Mixin.receiveProps.call(this, ...)`.
275 + *
276 + * @param {object} nextProps Next set of properties.
277 + * @param {ReactReconcileTransaction} transaction
278 + * @internal
279 + */
280 + receiveProps: function(nextProps, transaction) {
281 + invariant(
282 + this._lifeCycleState === ComponentLifeCycle.MOUNTED,
283 + 'receiveProps(...): Can only update a mounted component.'
284 + );
285 + var props = this.props;
286 + // If either the owner or a `ref` has changed, make sure the newest owner
287 + // has stored a reference to `this`, and the previous owner (if different)
288 + // has forgotten the reference to `this`.
289 + if (nextProps[OWNER] !== props[OWNER] || nextProps.ref !== props.ref) {
290 + if (props.ref != null) {
291 + ReactOwner.removeComponentAsRefFrom(this, props.ref, props[OWNER]);
292 + }
293 + // Correct, even if the owner is the same, and only the ref has changed.
294 + if (nextProps.ref != null) {
295 + ReactOwner.addComponentAsRefTo(this, nextProps.ref, nextProps[OWNER]);
296 + }
297 + }
298 + },
299 +
300 + /**
301 + * Mounts this component and inserts it into the DOM.
302 + *
303 + * @param {string} rootID DOM ID of the root node.
304 + * @param {DOMElement} container DOM element to mount into.
305 + * @final
306 + * @internal
307 + * @see {ReactMount.renderComponent}
308 + */
309 + mountComponentIntoNode: function(rootID, container) {
310 + var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
311 + transaction.perform(
312 + this._mountComponentIntoNode,
313 + this,
314 + rootID,
315 + container,
316 + transaction
317 + );
318 + ReactComponent.ReactReconcileTransaction.release(transaction);
319 + },
320 +
321 + /**
322 + * @param {string} rootID DOM ID of the root node.
323 + * @param {DOMElement} container DOM element to mount into.
324 + * @param {ReactReconcileTransaction} transaction
325 + * @final
326 + * @private
327 + */
328 + _mountComponentIntoNode: function(rootID, container, transaction) {
329 + var renderStart = Date.now();
330 + var markup = this.mountComponent(rootID, transaction);
331 + ReactMount.totalInstantiationTime += (Date.now() - renderStart);
332 +
333 + var injectionStart = Date.now();
334 + // Asynchronously inject markup by ensuring that the container is not in
335 + // the document when settings its `innerHTML`.
336 + var parent = container.parentNode;
337 + if (parent) {
338 + var next = container.nextSibling;
339 + parent.removeChild(container);
340 + container.innerHTML = markup;
341 + if (next) {
342 + parent.insertBefore(container, next);
343 + } else {
344 + parent.appendChild(container);
345 + }
346 + } else {
347 + container.innerHTML = markup;
348 + }
349 + ReactMount.totalInjectionTime += (Date.now() - injectionStart);
350 + },
351 +
352 + /**
353 + * Unmounts this component and removes it from the DOM.
354 + *
355 + * @param {DOMElement} container DOM element to unmount from.
356 + * @final
357 + * @internal
358 + * @see {ReactMount.unmountAndReleaseReactRootNode}
359 + */
360 + unmountComponentFromNode: function(container) {
361 + this.unmountComponent();
362 + // http://jsperf.com/emptying-a-node
363 + while (container.lastChild) {
364 + container.removeChild(container.lastChild);
365 + }
366 + },
367 +
368 + /**
369 + * Checks if this component is owned by the supplied `owner` component.
370 + *
371 + * @param {ReactComponent} owner Component to check.
372 + * @return {boolean} True if `owners` owns this component.
373 + * @final
374 + * @internal
375 + */
376 + isOwnedBy: function(owner) {
377 + return this.props[OWNER] === owner;
378 + }
379 +
380 + }
381 +
382 +};
383 +
384 +function logDeprecated(msg) {
385 + if (__DEV__) {
386 + throw new Error(msg);
387 + } else {
388 + console && console.warn && console.warn(msg);
389 + }
390 +}
391 +
392 +/**
393 + * @deprecated
394 + */
395 +ReactComponent.Mixin.update = function(props) {
396 + logDeprecated('this.update() is deprecated. Use this.setProps()');
397 + this.setProps(props);
398 +};
399 +ReactComponent.Mixin.updateAll = function(props) {
400 + logDeprecated('this.updateAll() is deprecated. Use this.replaceProps()');
401 + this.replaceProps(props);
402 +};
403 +
404 +module.exports = ReactComponent;
src/core/ReactComponentWithPureRenderMixin.js new
+77
@@ -0,0 +1,77 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 +* @providesModule ReactComponentWithPureRender
17 +*/
18 +
19 +"use strict";
20 +
21 +/**
22 + * If your React component's render function is "pure", e.g. it will render the
23 + * same result given the same props and state, provide this Mixin for a
24 + * considerable performance boost.
25 + *
26 + * Most React components have pure render functions.
27 + *
28 + * Example:
29 + *
30 + * var ReactComponentWithPureRender = require('ReactComponentWithPureRender');
31 + * React.createClass({
32 + * mixins: [ReactComponentWithPureRender],
33 + *
34 + * render: function() {
35 + * return <div className={this.props.className}>foo</div>;
36 + * }
37 + * });
38 + *
39 + * Note: This only checks shallow equality for props and state. If these contain
40 + * complex data structures this mixin may have false-negatives for deeper
41 + * differences. Only mixin to components which have simple props and state, or
42 + * use `forceUpdate()` when you know deep data structures have changed.
43 + */
44 +var ReactComponentWithPureRender = {
45 + shouldComponentUpdate: function(nextProps, nextState) {
46 + return !shallowEqual(this.props, nextProps) ||
47 + !shallowEqual(this.state, nextState);
48 + }
49 +};
50 +
51 +/**
52 + * Performs equality by iterating through keys on an object and returning
53 + * false when any key has values which are not strictly equal between
54 + * objA and objB. Returns true when the values of all keys are strictly equal.
55 + */
56 +function shallowEqual(objA, objB) {
57 + if (objA === objB) {
58 + return true;
59 + }
60 + var key;
61 + // Test for A's keys different from B.
62 + for (key in objA) {
63 + if (objA.hasOwnProperty(key) &&
64 + (!objB.hasOwnProperty(key) || objA[key] !== objB[key])) {
65 + return false;
66 + }
67 + }
68 + // Test for B'a keys missing from A.
69 + for (key in objB) {
70 + if (objB.hasOwnProperty(key) && !objA.hasOwnProperty(key)) {
71 + return false;
72 + }
73 + }
74 + return true;
75 +}
76 +
77 +module.exports = ReactComponentWithPureRender;
src/core/ReactCompositeComponent.js new
+836
@@ -0,0 +1,836 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactCompositeComponent
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactComponent = require('ReactComponent');
22 +var ReactCurrentOwner = require('ReactCurrentOwner');
23 +var ReactOwner = require('ReactOwner');
24 +var ReactPropTransferer = require('ReactPropTransferer');
25 +
26 +var invariant = require('invariant');
27 +var keyMirror = require('keyMirror');
28 +var merge = require('merge');
29 +var mixInto = require('mixInto');
30 +
31 +/**
32 + * Policies that describe methods in `ReactCompositeComponentInterface`.
33 + */
34 +var SpecPolicy = keyMirror({
35 + /**
36 + * These methods may be defined only once by the class specification or mixin.
37 + */
38 + DEFINE_ONCE: null,
39 + /**
40 + * These methods may be defined by both the class specification and mixins.
41 + * Subsequent definitions will be chained. These methods must return void.
42 + */
43 + DEFINE_MANY: null,
44 + /**
45 + * These methods are overriding the base ReactCompositeComponent class.
46 + */
47 + OVERRIDE_BASE: null
48 +});
49 +
50 +/**
51 + * Composite components are higher-level components that compose other composite
52 + * or native components.
53 + *
54 + * To create a new type of `ReactCompositeComponent`, pass a specification of
55 + * your new class to `React.createClass`. The only requirement of your class
56 + * specification is that you implement a `render` method.
57 + *
58 + * var MyComponent = React.createClass({
59 + * render: function() {
60 + * return <div>Hello World</div>;
61 + * }
62 + * });
63 + *
64 + * The class specification supports a specific protocol of methods that have
65 + * special meaning (e.g. `render`). See `ReactCompositeComponentInterface` for
66 + * more the comprehensive protocol. Any other properties and methods in the
67 + * class specification will available on the prototype.
68 + *
69 + * @interface ReactCompositeComponentInterface
70 + * @internal
71 + */
72 +var ReactCompositeComponentInterface = {
73 +
74 + /**
75 + * An array of Mixin objects to include when defining your component.
76 + *
77 + * @type {array}
78 + * @optional
79 + */
80 + mixins: SpecPolicy.DEFINE_MANY,
81 +
82 + /**
83 + * Definition of props for this component.
84 + *
85 + * @type {array}
86 + * @optional
87 + */
88 + props: SpecPolicy.DEFINE_ONCE,
89 +
90 +
91 +
92 + // ==== Definition methods ====
93 +
94 + /**
95 + * Invoked once before the component is mounted. The return value will be used
96 + * as the initial value of `this.state`.
97 + *
98 + * getInitialState: function() {
99 + * return {
100 + * isOn: false,
101 + * fooBaz: new BazFoo()
102 + * }
103 + * }
104 + *
105 + * @return {object}
106 + * @optional
107 + */
108 + getInitialState: SpecPolicy.DEFINE_ONCE,
109 +
110 + /**
111 + * Uses props from `this.props` and state from `this.state` to render the
112 + * structure of the component.
113 + *
114 + * No guarantees are made about when or how often this method is invoked, so
115 + * it must not have side effects.
116 + *
117 + * render: function() {
118 + * var name = this.props.name;
119 + * return <div>Hello, {name}!</div>;
120 + * }
121 + *
122 + * @return {ReactComponent}
123 + * @nosideeffects
124 + * @required
125 + */
126 + render: SpecPolicy.DEFINE_ONCE,
127 +
128 +
129 +
130 + // ==== Delegate methods ====
131 +
132 + /**
133 + * Invoked when the component is initially created and about to be mounted.
134 + * This may have side effects, but any external subscriptions or data created
135 + * by this method must be cleaned up in `componentWillUnmount`.
136 + *
137 + * @optional
138 + */
139 + componentWillMount: SpecPolicy.DEFINE_MANY,
140 +
141 + /**
142 + * Invoked when the component has been mounted and has a DOM representation.
143 + * However, there is no guarantee that the DOM node is in the document.
144 + *
145 + * Use this as an opportunity to operate on the DOM when the component has
146 + * been mounted (initialized and rendered) for the first time.
147 + *
148 + * @param {DOMElement} rootNode DOM element representing the component.
149 + * @optional
150 + */
151 + componentDidMount: SpecPolicy.DEFINE_MANY,
152 +
153 + /**
154 + * Invoked before the component receives new props.
155 + *
156 + * Use this as an opportunity to react to a prop transition by updating the
157 + * state using `this.setState`. Current props are accessed via `this.props`.
158 + *
159 + * componentWillReceiveProps: function(nextProps) {
160 + * this.setState({
161 + * likesIncreasing: nextProps.likeCount > this.props.likeCount
162 + * });
163 + * }
164 + *
165 + * NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop
166 + * transition may cause a state change, but the opposite is not true. If you
167 + * need it, you are probably looking for `componentWillUpdate`.
168 + *
169 + * @param {object} nextProps
170 + * @optional
171 + */
172 + componentWillReceiveProps: SpecPolicy.DEFINE_MANY,
173 +
174 + /**
175 + * Invoked while deciding if the component should be updated as a result of
176 + * receiving new props and state.
177 + *
178 + * Use this as an opportunity to `return false` when you're certain that the
179 + * transition to the new props and state will not require a component update.
180 + *
181 + * shouldComponentUpdate: function(nextProps, nextState) {
182 + * return !equal(nextProps, this.props) || !equal(nextState, this.state);
183 + * }
184 + *
185 + * @param {object} nextProps
186 + * @param {?object} nextState
187 + * @return {boolean} True if the component should update.
188 + * @optional
189 + */
190 + shouldComponentUpdate: SpecPolicy.DEFINE_ONCE,
191 +
192 + /**
193 + * Invoked when the component is about to update due to a transition from
194 + * `this.props` and `this.state` to `nextProps` and `nextState`.
195 + *
196 + * Use this as an opportunity to perform preparation before an update occurs.
197 + *
198 + * NOTE: You **cannot** use `this.setState()` in this method.
199 + *
200 + * @param {object} nextProps
201 + * @param {?object} nextState
202 + * @param {ReactReconcileTransaction} transaction
203 + * @optional
204 + */
205 + componentWillUpdate: SpecPolicy.DEFINE_MANY,
206 +
207 + /**
208 + * Invoked when the component's DOM representation has been updated.
209 + *
210 + * Use this as an opportunity to operate on the DOM when the component has
211 + * been updated.
212 + *
213 + * @param {object} prevProps
214 + * @param {?object} prevState
215 + * @param {DOMElement} rootNode DOM element representing the component.
216 + * @optional
217 + */
218 + componentDidUpdate: SpecPolicy.DEFINE_MANY,
219 +
220 + /**
221 + * Invoked when the component is about to be removed from its parent and have
222 + * its DOM representation destroyed.
223 + *
224 + * Use this as an opportunity to deallocate any external resources.
225 + *
226 + * NOTE: There is no `componentDidUnmount` since your component will have been
227 + * destroyed by that point.
228 + *
229 + * @optional
230 + */
231 + componentWillUnmount: SpecPolicy.DEFINE_MANY,
232 +
233 +
234 +
235 + // ==== Advanced methods ====
236 +
237 + /**
238 + * Updates the component's currently mounted DOM representation.
239 + *
240 + * By default, this implements React's rendering and reconciliation algorithm.
241 + * Sophisticated clients may wish to override this.
242 + *
243 + * @param {ReactReconcileTransaction} transaction
244 + * @internal
245 + * @overridable
246 + */
247 + updateComponent: SpecPolicy.OVERRIDE_BASE
248 +
249 +};
250 +
251 +/**
252 + * Mapping from class specification keys to special processing functions.
253 + *
254 + * Although these are declared in the specification when defining classes
255 + * using `React.createClass`, they will not be on the component's prototype.
256 + */
257 +var RESERVED_SPEC_KEYS = {
258 + displayName: function(Constructor, displayName) {
259 + Constructor.displayName = displayName;
260 + },
261 + mixins: function(Constructor, mixins) {
262 + if (mixins) {
263 + for (var i = 0; i < mixins.length; i++) {
264 + mixSpecIntoComponent(Constructor, mixins[i]);
265 + }
266 + }
267 + },
268 + props: function(Constructor, props) {
269 + Constructor.propDeclarations = props;
270 + }
271 +};
272 +
273 +/**
274 + * Custom version of `mixInto` which handles policy validation and reserved
275 + * specification keys when building `ReactCompositeComponent` classses.
276 + */
277 +function mixSpecIntoComponent(Constructor, spec) {
278 + var proto = Constructor.prototype;
279 + for (var name in spec) {
280 + if (!spec.hasOwnProperty(name)) {
281 + continue;
282 + }
283 + var property = spec[name];
284 + var specPolicy = ReactCompositeComponentInterface[name];
285 +
286 + // Disallow overriding of base class methods unless explicitly allowed.
287 + if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
288 + invariant(
289 + specPolicy === SpecPolicy.OVERRIDE_BASE,
290 + 'ReactCompositeComponentInterface: You are attempting to override ' +
291 + '`%s` from your class specification. Ensure that your method names ' +
292 + 'do not overlap with React methods.',
293 + name
294 + );
295 + }
296 +
297 + // Disallow using `React.autoBind` on internal methods.
298 + if (specPolicy != null) {
299 + invariant(
300 + !property || !property.__reactAutoBind,
301 + 'ReactCompositeComponentInterface: You are attempting to use ' +
302 + '`React.autoBind` on `%s`, a method that is internal to React.' +
303 + 'Internal methods are called with the component as the context.',
304 + name
305 + );
306 + }
307 +
308 + // Disallow defining methods more than once unless explicitly allowed.
309 + if (proto.hasOwnProperty(name)) {
310 + invariant(
311 + specPolicy === SpecPolicy.DEFINE_MANY,
312 + 'ReactCompositeComponentInterface: You are attempting to define ' +
313 + '`%s` on your component more than once. This conflict may be due ' +
314 + 'to a mixin.',
315 + name
316 + );
317 + }
318 +
319 + if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
320 + RESERVED_SPEC_KEYS[name](Constructor, property);
321 + } else if (property && property.__reactAutoBind) {
322 + if (!proto.__reactAutoBindMap) {
323 + proto.__reactAutoBindMap = {};
324 + }
325 + proto.__reactAutoBindMap[name] = property.__reactAutoBind;
326 + } else if (proto.hasOwnProperty(name)) {
327 + // For methods which are defined more than once, call the existing methods
328 + // before calling the new property.
329 + proto[name] = createChainedFunction(proto[name], property);
330 + } else {
331 + proto[name] = property;
332 + }
333 + }
334 +}
335 +
336 +/**
337 + * Creates a function that invokes two functions and ignores their return vales.
338 + *
339 + * @param {function} one Function to invoke first.
340 + * @param {function} two Function to invoke second.
341 + * @return {function} Function that invokes the two argument functions.
342 + * @private
343 + */
344 +function createChainedFunction(one, two) {
345 + return function chainedFunction(a, b, c, d, e, tooMany) {
346 + invariant(
347 + typeof tooMany === 'undefined',
348 + 'Chained function can only take a maximum of 5 arguments.'
349 + );
350 + one.call(this, a, b, c, d, e);
351 + two.call(this, a, b, c, d, e);
352 + };
353 +}
354 +
355 +/**
356 + * `ReactCompositeComponent` maintains an auxiliary life cycle state in
357 + * `this._compositeLifeCycleState` (which can be null).
358 + *
359 + * This is different from the life cycle state maintained by `ReactComponent` in
360 + * `this._lifeCycleState`.
361 + */
362 +var CompositeLifeCycle = keyMirror({
363 + /**
364 + * Components in the process of being mounted respond to state changes
365 + * differently.
366 + */
367 + MOUNTING: null,
368 + /**
369 + * Components in the process of being unmounted are guarded against state
370 + * changes.
371 + */
372 + UNMOUNTING: null,
373 + /**
374 + * Components that are mounted and receiving new props respond to state
375 + * changes differently.
376 + */
377 + RECEIVING_PROPS: null,
378 + /**
379 + * Components that are mounted and receiving new state are guarded against
380 + * additional state changes.
381 + */
382 + RECEIVING_STATE: null
383 +});
384 +
385 +/**
386 + * @lends {ReactCompositeComponent.prototype}
387 + */
388 +var ReactCompositeComponentMixin = {
389 +
390 + /**
391 + * Base constructor for all composite component.
392 + *
393 + * @param {?object} initialProps
394 + * @param {*} children
395 + * @final
396 + * @internal
397 + */
398 + construct: function(initialProps, children) {
399 + ReactComponent.Mixin.construct.call(this, initialProps, children);
400 + this.state = null;
401 + this._pendingState = null;
402 + this._compositeLifeCycleState = null;
403 + },
404 +
405 + /**
406 + * Initializes the component, renders markup, and registers event listeners.
407 + *
408 + * @param {string} rootID DOM ID of the root node.
409 + * @param {ReactReconcileTransaction} transaction
410 + * @return {?string} Rendered markup to be inserted into the DOM.
411 + * @final
412 + * @internal
413 + */
414 + mountComponent: function(rootID, transaction) {
415 + ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
416 +
417 + // Unset `this._lifeCycleState` until after this method is finished.
418 + this._lifeCycleState = ReactComponent.LifeCycle.UNMOUNTED;
419 + this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
420 +
421 + if (this.constructor.propDeclarations) {
422 + this._assertValidProps(this.props);
423 + }
424 +
425 + if (this.__reactAutoBindMap) {
426 + this._bindAutoBindMethods();
427 + }
428 +
429 + this.state = this.getInitialState ? this.getInitialState() : null;
430 + this._pendingState = null;
431 +
432 + if (this.componentWillMount) {
433 + this.componentWillMount();
434 + // When mounting, calls to `setState` by `componentWillMount` will set
435 + // `this._pendingState` without triggering a re-render.
436 + if (this._pendingState) {
437 + this.state = this._pendingState;
438 + this._pendingState = null;
439 + }
440 + }
441 +
442 + if (this.componentDidMount) {
443 + transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
444 + }
445 +
446 + this._renderedComponent = this._renderValidatedComponent();
447 +
448 + // Done with mounting, `setState` will now trigger UI changes.
449 + this._compositeLifeCycleState = null;
450 + this._lifeCycleState = ReactComponent.LifeCycle.MOUNTED;
451 +
452 + return this._renderedComponent.mountComponent(rootID, transaction);
453 + },
454 +
455 + /**
456 + * Releases any resources allocated by `mountComponent`.
457 + *
458 + * @final
459 + * @internal
460 + */
461 + unmountComponent: function() {
462 + this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;
463 + if (this.componentWillUnmount) {
464 + this.componentWillUnmount();
465 + }
466 + this._compositeLifeCycleState = null;
467 +
468 + ReactComponent.Mixin.unmountComponent.call(this);
469 + this._renderedComponent.unmountComponent();
470 + this._renderedComponent = null;
471 +
472 + if (this.refs) {
473 + this.refs = null;
474 + }
475 +
476 + // Some existing components rely on this.props even after they've been
477 + // destroyed (in event handlers).
478 + // TODO: this.props = null;
479 + // TODO: this.state = null;
480 + },
481 +
482 + /**
483 + * Updates the rendered DOM nodes given a new set of props.
484 + *
485 + * @param {object} nextProps Next set of properties.
486 + * @param {ReactReconcileTransaction} transaction
487 + * @final
488 + * @internal
489 + */
490 + receiveProps: function(nextProps, transaction) {
491 + if (this.constructor.propDeclarations) {
492 + this._assertValidProps(nextProps);
493 + }
494 + ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
495 +
496 + this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
497 + if (this.componentWillReceiveProps) {
498 + this.componentWillReceiveProps(nextProps, transaction);
499 + }
500 + this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
501 + // When receiving props, calls to `setState` by `componentWillReceiveProps`
502 + // will set `this._pendingState` without triggering a re-render.
503 + var nextState = this._pendingState || this.state;
504 + this._pendingState = null;
505 + this._receivePropsAndState(nextProps, nextState, transaction);
506 + this._compositeLifeCycleState = null;
507 + },
508 +
509 + /**
510 + * Sets a subset of the state. Always use this or `replaceState` to mutate
511 + * state. You should treat `this.state` as immutable.
512 + *
513 + * There is no guarantee that `this.state` will be immediately updated, so
514 + * accessing `this.state` after calling this method may return the old value.
515 + *
516 + * @param {object} partialState Next partial state to be merged with state.
517 + * @final
518 + * @protected
519 + */
520 + setState: function(partialState) {
521 + // Merge with `_pendingState` if it exists, otherwise with existing state.
522 + this.replaceState(merge(this._pendingState || this.state, partialState));
523 + },
524 +
525 + /**
526 + * Replaces all of the state. Always use this or `setState` to mutate state.
527 + * You should treat `this.state` as immutable.
528 + *
529 + * There is no guarantee that `this.state` will be immediately updated, so
530 + * accessing `this.state` after calling this method may return the old value.
531 + *
532 + * @param {object} completeState Next state.
533 + * @final
534 + * @protected
535 + */
536 + replaceState: function(completeState) {
537 + var compositeLifeCycleState = this._compositeLifeCycleState;
538 + invariant(
539 + this._lifeCycleState === ReactComponent.LifeCycle.MOUNTED ||
540 + compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
541 + 'replaceState(...): Can only update a mounted (or mounting) component.'
542 + );
543 + invariant(
544 + compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
545 + compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
546 + 'replaceState(...): Cannot update while unmounting component or during ' +
547 + 'an existing state transition (such as within `render`).'
548 + );
549 +
550 + this._pendingState = completeState;
551 +
552 + // Do not trigger a state transition if we are in the middle of mounting or
553 + // receiving props because both of those will already be doing this.
554 + if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
555 + compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
556 + this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
557 +
558 + var nextState = this._pendingState;
559 + this._pendingState = null;
560 +
561 + var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
562 + transaction.perform(
563 + this._receivePropsAndState,
564 + this,
565 + this.props,
566 + nextState,
567 + transaction
568 + );
569 + ReactComponent.ReactReconcileTransaction.release(transaction);
570 +
571 + this._compositeLifeCycleState = null;
572 + }
573 + },
574 +
575 + /**
576 + * Receives next props and next state, and negotiates whether or not the
577 + * component should update as a result.
578 + *
579 + * @param {object} nextProps Next object to set as props.
580 + * @param {?object} nextState Next object to set as state.
581 + * @param {ReactReconcileTransaction} transaction
582 + * @private
583 + */
584 + _receivePropsAndState: function(nextProps, nextState, transaction) {
585 + if (!this.shouldComponentUpdate ||
586 + this.shouldComponentUpdate(nextProps, nextState)) {
587 + // Will set `this.props` and `this.state`.
588 + this._performComponentUpdate(nextProps, nextState, transaction);
589 + } else {
590 + // If it's determined that a component should not update, we still want
591 + // to set props and state.
592 + this.props = nextProps;
593 + this.state = nextState;
594 + }
595 + },
596 +
597 + /**
598 + * Merges new props and state, notifies delegate methods of update and
599 + * performs update.
600 + *
601 + * @param {object} nextProps Next object to set as properties.
602 + * @param {?object} nextState Next object to set as state.
603 + * @param {ReactReconcileTransaction} transaction
604 + * @private
605 + */
606 + _performComponentUpdate: function(nextProps, nextState, transaction) {
607 + var prevProps = this.props;
608 + var prevState = this.state;
609 +
610 + if (this.componentWillUpdate) {
611 + this.componentWillUpdate(nextProps, nextState, transaction);
612 + }
613 +
614 + this.props = nextProps;
615 + this.state = nextState;
616 +
617 + this.updateComponent(transaction);
618 +
619 + if (this.componentDidUpdate) {
620 + transaction.getReactOnDOMReady().enqueue(
621 + this,
622 + this.componentDidUpdate.bind(this, prevProps, prevState)
623 + );
624 + }
625 + },
626 +
627 + /**
628 + * Updates the component's currently mounted DOM representation.
629 + *
630 + * By default, this implements React's rendering and reconciliation algorithm.
631 + * Sophisticated clients may wish to override this.
632 + *
633 + * @param {ReactReconcileTransaction} transaction
634 + * @internal
635 + * @overridable
636 + */
637 + updateComponent: function(transaction) {
638 + var currentComponent = this._renderedComponent;
639 + var nextComponent = this._renderValidatedComponent();
640 + if (currentComponent.constructor === nextComponent.constructor) {
641 + if (!nextComponent.props.isStatic) {
642 + currentComponent.receiveProps(nextComponent.props, transaction);
643 + }
644 + } else {
645 + // These two IDs are actually the same! But nothing should rely on that.
646 + var thisID = this._rootNodeID;
647 + var currentComponentID = currentComponent._rootNodeID;
648 + currentComponent.unmountComponent();
649 + var nextMarkup = nextComponent.mountComponent(thisID, transaction);
650 + ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
651 + currentComponentID,
652 + nextMarkup
653 + );
654 + this._renderedComponent = nextComponent;
655 + }
656 + },
657 +
658 + /**
659 + * Forces an update. This should only be invoked when it is known with
660 + * certainty that we are **not** in a DOM transaction.
661 + *
662 + * You may want to call this when you know that some deeper aspect of the
663 + * component's state has changed but `setState` was not called.
664 + *
665 + * This will not invoke `shouldUpdateComponent`, but it will invoke
666 + * `componentWillUpdate` and `componentDidUpdate`.
667 + *
668 + * @final
669 + * @protected
670 + */
671 + forceUpdate: function() {
672 + var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
673 + transaction.perform(
674 + this._performComponentUpdate,
675 + this,
676 + this.props,
677 + this.state,
678 + transaction
679 + );
680 + ReactComponent.ReactReconcileTransaction.release(transaction);
681 + },
682 +
683 + /**
684 + * @private
685 + */
686 + _renderValidatedComponent: function() {
687 + ReactCurrentOwner.current = this;
688 + var renderedComponent = this.render();
689 + ReactCurrentOwner.current = null;
690 + invariant(
691 + ReactComponent.isValidComponent(renderedComponent),
692 + '%s.render(): A valid ReactComponent must be returned.',
693 + this.constructor.displayName || 'ReactCompositeComponent'
694 + );
695 + return renderedComponent;
696 + },
697 +
698 + /**
699 + * @param {object} props
700 + * @private
701 + */
702 + _assertValidProps: function(props) {
703 + var propDeclarations = this.constructor.propDeclarations;
704 + var componentName = this.constructor.displayName;
705 + for (var propName in propDeclarations) {
706 + var checkProp = propDeclarations[propName];
707 + if (checkProp) {
708 + checkProp(props, propName, componentName);
709 + }
710 + }
711 + },
712 +
713 + /**
714 + * @private
715 + */
716 + _bindAutoBindMethods: function() {
717 + for (var autoBindKey in this.__reactAutoBindMap) {
718 + if (!this.__reactAutoBindMap.hasOwnProperty(autoBindKey)) {
719 + continue;
720 + }
721 + var method = this.__reactAutoBindMap[autoBindKey];
722 + this[autoBindKey] = this._bindAutoBindMethod(method);
723 + }
724 + },
725 +
726 + /**
727 + * Binds a method to the component.
728 + *
729 + * @param {function} method Method to be bound.
730 + * @private
731 + */
732 + _bindAutoBindMethod: function(method) {
733 + var component = this;
734 + var hasWarned = false;
735 + function autoBound(a, b, c, d, e, tooMany) {
736 + invariant(
737 + typeof tooMany === 'undefined',
738 + 'React.autoBind(...): Methods can only take a maximum of 5 arguments.'
739 + );
740 + if (component._lifeCycleState === ReactComponent.LifeCycle.MOUNTED) {
741 + return method.call(component, a, b, c, d, e);
742 + } else if (!hasWarned) {
743 + hasWarned = true;
744 + if (__DEV__) {
745 + console.warn(
746 + 'React.autoBind(...): Attempted to invoke an auto-bound method ' +
747 + 'on an unmounted instance of `%s`. You either have a memory leak ' +
748 + 'or an event handler that is being run after unmounting.',
749 + component.constructor.displayName || 'ReactCompositeComponent'
750 + );
751 + }
752 + }
753 + }
754 + return autoBound;
755 + }
756 +
757 +};
758 +
759 +var ReactCompositeComponentBase = function() {};
760 +mixInto(ReactCompositeComponentBase, ReactComponent.Mixin);
761 +mixInto(ReactCompositeComponentBase, ReactOwner.Mixin);
762 +mixInto(ReactCompositeComponentBase, ReactPropTransferer.Mixin);
763 +mixInto(ReactCompositeComponentBase, ReactCompositeComponentMixin);
764 +
765 +/**
766 + * Module for creating composite components.
767 + *
768 + * @class ReactCompositeComponent
769 + * @extends ReactComponent
770 + * @extends ReactOwner
771 + * @extends ReactPropTransferer
772 + */
773 +var ReactCompositeComponent = {
774 +
775 + LifeCycle: CompositeLifeCycle,
776 +
777 + Base: ReactCompositeComponentBase,
778 +
779 + /**
780 + * Creates a composite component class given a class specification.
781 + *
782 + * @param {object} spec Class specification (which must define `render`).
783 + * @return {function} Component constructor function.
784 + * @public
785 + */
786 + createClass: function(spec) {
787 + var Constructor = function(initialProps, children) {
788 + this.construct(initialProps, children);
789 + };
790 + Constructor.prototype = new ReactCompositeComponentBase();
791 + Constructor.prototype.constructor = Constructor;
792 + mixSpecIntoComponent(Constructor, spec);
793 + invariant(
794 + Constructor.prototype.render,
795 + 'createClass(...): Class specification must implement a `render` method.'
796 + );
797 +
798 + var ConvenienceConstructor = function(props, children) {
799 + return new Constructor(props, children);
800 + };
801 + ConvenienceConstructor.componentConstructor = Constructor;
802 + ConvenienceConstructor.originalSpec = spec;
803 + return ConvenienceConstructor;
804 + },
805 +
806 + /**
807 + * Marks the provided method to be automatically bound to the component.
808 + * This means the method's context will always be the component.
809 + *
810 + * React.createClass({
811 + * handleClick: React.autoBind(function() {
812 + * this.setState({jumping: true});
813 + * }),
814 + * render: function() {
815 + * return <a onClick={this.handleClick}>Jump</a>;
816 + * }
817 + * });
818 + *
819 + * @param {function} method Method to be bound.
820 + * @public
821 + */
822 + autoBind: function(method) {
823 + function unbound() {
824 + invariant(
825 + false,
826 + 'React.autoBind(...): Attempted to invoke an auto-bound method that ' +
827 + 'was not correctly defined on the class specification.'
828 + );
829 + }
830 + unbound.__reactAutoBind = method;
831 + return unbound;
832 + }
833 +
834 +};
835 +
836 +module.exports = ReactCompositeComponent;
src/core/ReactCurrentOwner.js new
+37
@@ -0,0 +1,37 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactCurrentOwner
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * Keeps track of the current owner.
23 + *
24 + * The current owner is the component who should own any components that are
25 + * currently being constructed.
26 + */
27 +var ReactCurrentOwner = {
28 +
29 + /**
30 + * @internal
31 + * @type {ReactComponent}
32 + */
33 + current: null
34 +
35 +};
36 +
37 +module.exports = ReactCurrentOwner;
src/core/ReactDOM.js new
+148
@@ -0,0 +1,148 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDOM
17 + * @typechecks
18 + */
19 +
20 +"use strict";
21 +
22 +var ReactNativeComponent = require('ReactNativeComponent');
23 +
24 +var mergeInto = require('mergeInto');
25 +var objMapKeyVal = require('objMapKeyVal');
26 +
27 +/**
28 + * Creates a new React class that is idempotent and capable of containing other
29 + * React components. It accepts event listeners and DOM properties that are
30 + * valid according to `DOMProperty`.
31 + *
32 + * - Event listeners: `onClick`, `onMouseDown`, etc.
33 + * - DOM properties: `className`, `name`, `title`, etc.
34 + *
35 + * The `style` property functions differently from the DOM API. It accepts an
36 + * object mapping of style properties to values.
37 + *
38 + * @param {string} tag Tag name (e.g. `div`).
39 + * @param {boolean} omitClose True if the close tag should be omitted.
40 + * @private
41 + */
42 +function createDOMComponentClass(tag, omitClose) {
43 + var Constructor = function(initialProps, children) {
44 + this.construct(initialProps, children);
45 + };
46 +
47 + Constructor.prototype = new ReactNativeComponent(tag, omitClose);
48 + Constructor.prototype.constructor = Constructor;
49 +
50 + return function(props, children) {
51 + return new Constructor(props, children);
52 + };
53 +}
54 +
55 +/**
56 + * Creates a mapping from supported HTML tags to `ReactNativeComponent` classes.
57 + * This is also accessible via `React.DOM`.
58 + *
59 + * @public
60 + */
61 +var ReactDOM = objMapKeyVal({
62 + a: false,
63 + abbr: false,
64 + address: false,
65 + audio: false,
66 + b: false,
67 + body: false,
68 + br: true,
69 + button: false,
70 + code: false,
71 + col: true,
72 + colgroup: false,
73 + dd: false,
74 + div: false,
75 + section: false,
76 + dl: false,
77 + dt: false,
78 + em: false,
79 + embed: true,
80 + fieldset: false,
81 + footer: false,
82 + // Danger: this gets monkeypatched! See ReactDOMForm for more info.
83 + form: false,
84 + h1: false,
85 + h2: false,
86 + h3: false,
87 + h4: false,
88 + h5: false,
89 + h6: false,
90 + header: false,
91 + hr: true,
92 + i: false,
93 + iframe: false,
94 + img: true,
95 + input: true,
96 + label: false,
97 + legend: false,
98 + li: false,
99 + line: false,
100 + nav: false,
101 + object: false,
102 + ol: false,
103 + optgroup: false,
104 + option: false,
105 + p: false,
106 + param: true,
107 + pre: false,
108 + select: false,
109 + small: false,
110 + source: false,
111 + span: false,
112 + sub: false,
113 + sup: false,
114 + strong: false,
115 + table: false,
116 + tbody: false,
117 + td: false,
118 + textarea: false,
119 + tfoot: false,
120 + th: false,
121 + thead: false,
122 + time: false,
123 + title: false,
124 + tr: false,
125 + u: false,
126 + ul: false,
127 + video: false,
128 + wbr: false,
129 +
130 + // SVG
131 + circle: false,
132 + g: false,
133 + path: false,
134 + polyline: false,
135 + rect: false,
136 + svg: false,
137 + text: false
138 +}, createDOMComponentClass);
139 +
140 +var injection = {
141 + injectComponentClasses: function(componentClasses) {
142 + mergeInto(ReactDOM, componentClasses);
143 + }
144 +};
145 +
146 +ReactDOM.injection = injection;
147 +
148 +module.exports = ReactDOM;
src/core/ReactDOMForm.js new
+52
@@ -0,0 +1,52 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDOMForm
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactCompositeComponent = require('ReactCompositeComponent');
22 +var ReactDOM = require('ReactDOM');
23 +var ReactEvent = require('ReactEvent');
24 +var EventConstants = require('EventConstants');
25 +
26 +// Store a reference to the <form> `ReactNativeComponent`.
27 +var form = ReactDOM.form;
28 +
29 +/**
30 + * Since onSubmit doesn't bubble OR capture on the top level in IE8, we need
31 + * to capture it on the <form> element itself. There are lots of hacks we could
32 + * do to accomplish this, but the most reliable is to make <form> a
33 + * composite component and use `componentDidMount` to attach the event handlers.
34 + */
35 +var ReactDOMForm = ReactCompositeComponent.createClass({
36 + render: function() {
37 + // TODO: Instead of using `ReactDOM` directly, we should use JSX. However,
38 + // `jshint` fails to parse JSX so in order for linting to work in the open
39 + // source repo, we need to just use `ReactDOM.form`.
40 + return this.transferPropsTo(form(null, this.props.children));
41 + },
42 +
43 + componentDidMount: function(node) {
44 + ReactEvent.trapBubbledEvent(
45 + EventConstants.topLevelTypes.topSubmit,
46 + 'submit',
47 + node
48 + );
49 + }
50 +});
51 +
52 +module.exports = ReactDOMForm;
src/core/ReactDOMIDOperations.js new
+166
@@ -0,0 +1,166 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDOMIDOperations
17 + * @typechecks
18 + */
19 +
20 +/*jslint evil: true */
21 +
22 +"use strict";
23 +
24 +var CSSPropertyOperations = require('CSSPropertyOperations');
25 +var DOMChildrenOperations = require('DOMChildrenOperations');
26 +var DOMPropertyOperations = require('DOMPropertyOperations');
27 +var ReactDOMNodeCache = require('ReactDOMNodeCache');
28 +
29 +var getTextContentAccessor = require('getTextContentAccessor');
30 +var invariant = require('invariant');
31 +
32 +/**
33 + * Errors for properties that should not be updated with `updatePropertyById()`.
34 + *
35 + * @type {object}
36 + * @private
37 + */
38 +var INVALID_PROPERTY_ERRORS = {
39 + content: '`content` must be set using `updateTextContentByID()`.',
40 + dangerouslySetInnerHTML:
41 + '`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.',
42 + style: '`style` must be set using `updateStylesByID()`.'
43 +};
44 +
45 +/**
46 + * The DOM property to use when setting text content.
47 + *
48 + * @type {string}
49 + * @private
50 + */
51 +var textContentAccessor = getTextContentAccessor() || 'NA';
52 +
53 +/**
54 + * Operations used to process updates to DOM nodes. This is made injectable via
55 + * `ReactComponent.DOMIDOperations`.
56 + */
57 +var ReactDOMIDOperations = {
58 +
59 + /**
60 + * Updates a DOM node with new property values. This should only be used to
61 + * update DOM properties in `DOMProperty`.
62 + *
63 + * @param {string} id ID of the node to update.
64 + * @param {string} name A valid property name, see `DOMProperty`.
65 + * @param {*} value New value of the property.
66 + * @internal
67 + */
68 + updatePropertyByID: function(id, name, value) {
69 + var node = ReactDOMNodeCache.getCachedNodeByID(id);
70 + invariant(
71 + !INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
72 + 'updatePropertyByID(...): %s',
73 + INVALID_PROPERTY_ERRORS[name]
74 + );
75 + DOMPropertyOperations.setValueForProperty(node, name, value);
76 + },
77 +
78 + /**
79 + * This should almost never be used instead of `updatePropertyByID()` due to
80 + * the extra object allocation required by the API. That said, this is useful
81 + * for batching up several operations across worker thread boundaries.
82 + *
83 + * @param {string} id ID of the node to update.
84 + * @param {object} properties A mapping of valid property names.
85 + * @internal
86 + * @see {ReactDOMIDOperations.updatePropertyByID}
87 + */
88 + updatePropertiesByID: function(id, properties) {
89 + for (var name in properties) {
90 + if (!properties.hasOwnProperty(name)) {
91 + continue;
92 + }
93 + ReactDOMIDOperations.updatePropertiesByID(id, name, properties[name]);
94 + }
95 + },
96 +
97 + /**
98 + * Updates a DOM node with new style values.
99 + *
100 + * @param {string} id ID of the node to update.
101 + * @param {object} styles Mapping from styles to values.
102 + * @internal
103 + */
104 + updateStylesByID: function(id, styles) {
105 + var node = ReactDOMNodeCache.getCachedNodeByID(id);
106 + CSSPropertyOperations.setValueForStyles(node, styles);
107 + },
108 +
109 + /**
110 + * Updates a DOM node's innerHTML set by `props.dangerouslySetInnerHTML`.
111 + *
112 + * @param {string} id ID of the node to update.
113 + * @param {object} html An HTML object with the `__html` property.
114 + * @internal
115 + */
116 + updateInnerHTMLByID: function(id, html) {
117 + var node = ReactDOMNodeCache.getCachedNodeByID(id);
118 + // HACK: IE8- normalize whitespace in innerHTML, removing leading spaces.
119 + // @see quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html
120 + node.innerHTML = (html && html.__html || '').replace(/^ /g, '&nbsp;');
121 + },
122 +
123 + /**
124 + * Updates a DOM node's text content set by `props.content`.
125 + *
126 + * @param {string} id ID of the node to update.
127 + * @param {string} content Text content.
128 + * @internal
129 + */
130 + updateTextContentByID: function(id, content) {
131 + var node = ReactDOMNodeCache.getCachedNodeByID(id);
132 + node[textContentAccessor] = content;
133 + },
134 +
135 + /**
136 + * Replaces a DOM node that exists in the document with markup.
137 + *
138 + * @param {string} id ID of child to be replaced.
139 + * @param {string} markup Dangerous markup to inject in place of child.
140 + * @internal
141 + * @see {Danger.dangerouslyReplaceNodeWithMarkup}
142 + */
143 + dangerouslyReplaceNodeWithMarkupByID: function(id, markup) {
144 + var node = ReactDOMNodeCache.getCachedNodeByID(id);
145 + DOMChildrenOperations.dangerouslyReplaceNodeWithMarkup(node, markup);
146 + ReactDOMNodeCache.purgeEntireCache();
147 + },
148 +
149 + /**
150 + * TODO: We only actually *need* to purge the cache when we remove elements.
151 + * Detect if any elements were removed instead of blindly purging.
152 + */
153 + manageChildrenByParentID: function(parentID, domOperations) {
154 + var parent = ReactDOMNodeCache.getCachedNodeByID(parentID);
155 + DOMChildrenOperations.manageChildren(parent, domOperations);
156 + ReactDOMNodeCache.purgeEntireCache();
157 + },
158 +
159 + setTextNodeValueAtIndexByParentID: function(parentID, index, value) {
160 + var parent = ReactDOMNodeCache.getCachedNodeByID(parentID);
161 + DOMChildrenOperations.setTextNodeValueAtIndex(parent, index, value);
162 + }
163 +
164 +};
165 +
166 +module.exports = ReactDOMIDOperations;
src/core/ReactDOMNodeCache.js new
+52
@@ -0,0 +1,52 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDOMNodeCache
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactMount = require('ReactMount');
22 +
23 +var nodeCache = {};
24 +
25 +/**
26 + * DOM node cache only intended for use by React. Placed into a shared module so
27 + * that both read and write utilities may benefit from a shared cache.
28 + */
29 +var ReactDOMNodeCache = {
30 + /**
31 + * Releases fast id lookups (node/style cache). This implementation is
32 + * aggressive with purging because the bookkeeping associated with doing fine
33 + * grained deleted from the cache may outweight the benefits of the cache. The
34 + * heuristic that should be used to purge is 'any time anything is deleted'.
35 + * Typically this means that a large amount of content is being replaced and
36 + * several elements would need purging regardless. It's also a time when an
37 + * application is likely not in the middle of a "smooth operation" (such as
38 + * animating/scrolling).
39 + */
40 + purgeEntireCache: function() {
41 + nodeCache = {};
42 + return nodeCache;
43 + },
44 + getCachedNodeByID: function(id) {
45 + return nodeCache[id] ||
46 + (nodeCache[id] =
47 + document.getElementById(id) ||
48 + ReactMount.findReactRenderedDOMNodeSlow(id));
49 + }
50 +};
51 +
52 +module.exports = ReactDOMNodeCache;
src/core/ReactDefaultInjection.js new
+59
@@ -0,0 +1,59 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDefaultInjection
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactDOM = require('ReactDOM');
22 +var ReactDOMForm = require('ReactDOMForm');
23 +
24 +var DefaultEventPluginOrder = require('DefaultEventPluginOrder');
25 +var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
26 +var EventPluginHub = require('EventPluginHub');
27 +var ReactInstanceHandles = require('ReactInstanceHandles');
28 +var SimpleEventPlugin = require('SimpleEventPlugin');
29 +
30 +function inject() {
31 + /**
32 + * Inject module for resolving DOM hierarchy and plugin ordering.
33 + */
34 + EventPluginHub.injection.injectEventPluginOrder(DefaultEventPluginOrder);
35 + EventPluginHub.injection.injectInstanceHandle(ReactInstanceHandles);
36 +
37 + /**
38 + * Two important event plugins included by default (without having to require
39 + * them).
40 + */
41 + EventPluginHub.injection.injectEventPluginsByName({
42 + 'SimpleEventPlugin': SimpleEventPlugin,
43 + 'EnterLeaveEventPlugin': EnterLeaveEventPlugin
44 + });
45 +
46 + /*
47 + * This is a bit of a hack. We need to override the <form> element
48 + * to be a composite component because IE8 does not bubble or capture
49 + * submit to the top level. In order to make this work with our
50 + * dependency graph we need to inject it here.
51 + */
52 + ReactDOM.injection.injectComponentClasses({
53 + form: ReactDOMForm
54 + });
55 +}
56 +
57 +module.exports = {
58 + inject: inject
59 +};
src/core/ReactEvent.js new
+321
@@ -0,0 +1,321 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactEvent
17 + */
18 +
19 +"use strict";
20 +
21 +var BrowserEnv = require('BrowserEnv');
22 +var EventConstants = require('EventConstants');
23 +var EventPluginHub = require('EventPluginHub');
24 +var ExecutionEnvironment = require('ExecutionEnvironment');
25 +var NormalizedEventListener = require('NormalizedEventListener');
26 +
27 +var invariant = require('invariant');
28 +var isEventSupported = require('isEventSupported');
29 +
30 +var registrationNames = EventPluginHub.registrationNames;
31 +var topLevelTypes = EventConstants.topLevelTypes;
32 +var listen = NormalizedEventListener.listen;
33 +var capture = NormalizedEventListener.capture;
34 +
35 +/**
36 + * `ReactEvent` is used to attach top-level event listeners. For example:
37 + *
38 + * ReactEvent.putListener('myID', 'onClick', myFunction);
39 + *
40 + * This would allocate a "registration" of `('onClick', myFunction)` on 'myID'.
41 + */
42 +
43 +/**
44 + * Overview of React and the event system:
45 + *
46 + * .
47 + * +-------------+ .
48 + * | DOM | .
49 + * +-------------+ . +-----------+
50 + * + . +--------+|SimpleEvent|
51 + * | . | |Plugin |
52 + * +-----|-------+ . v +-----------+
53 + * | | | . +--------------+ +------------+
54 + * | +------------.---->|EventPluginHub| | Event |
55 + * | | . | | +-----------+ | Propagators|
56 + * | ReactEvent | . | | |TapEvent | |------------|
57 + * | | . | |<---+|Plugin | |other plugin|
58 + * | +------------.---------+ | +-----------+ | utilities |
59 + * | | | . | | | +------------+
60 + * | | | . +---|----------+
61 + * | | | . | ^ +-----------+
62 + * | | | . | | |Enter/Leave|
63 + * +-----| ------+ . | +-------+|Plugin |
64 + * | . v +-----------+
65 + * + . +--------+
66 + * +-------------+ . |callback|
67 + * | application | . |registry|
68 + * |-------------| . +--------+
69 + * | | .
70 + * | | .
71 + * | | .
72 + * | | .
73 + * +-------------+ .
74 + * .
75 + * React Core . General Purpose Event Plugin System
76 + */
77 +
78 +/**
79 + * We listen for bubbled touch events on the document object.
80 + *
81 + * Firefox v8.01 (and possibly others) exhibited strange behavior when mounting
82 + * `onmousemove` events at some node that was not the document element. The
83 + * symptoms were that if your mouse is not moving over something contained
84 + * within that mount point (for example on the background) the top-level
85 + * listeners for `onmousemove` won't be called. However, if you register the
86 + * `mousemove` on the document object, then it will of course catch all
87 + * `mousemove`s. This along with iOS quirks, justifies restricting top-level
88 + * listeners to the document object only, at least for these movement types of
89 + * events and possibly all events.
90 + *
91 + * @see http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
92 + *
93 + * Also, `keyup`/`keypress`/`keydown` do not bubble to the window on IE, but
94 + * they bubble to document.
95 + *
96 + * @see http://www.quirksmode.org/dom/events/keys.html.
97 + */
98 +
99 +var _isListening = false;
100 +
101 +var EVENT_LISTEN_MISUSE;
102 +var WORKER_DISABLE;
103 +
104 +if (__DEV__) {
105 + EVENT_LISTEN_MISUSE =
106 + 'You must register listeners at the top of the document, only once - ' +
107 + 'and only in the main UI thread of a browser - if you are attempting ' +
108 + 'listen in a worker, the framework is probably doing something wrong ' +
109 + 'and you should report this immediately.';
110 + WORKER_DISABLE =
111 + 'Cannot disable event listening in Worker thread. This is likely a ' +
112 + 'bug in the framework. Please report immediately.';
113 +}
114 +
115 +
116 +/**
117 + * Traps top-level events that bubble. Delegates to the main dispatcher
118 + * `handleTopLevel` after performing some basic normalization via
119 + * `TopLevelCallbackCreator.createTopLevelCallback`.
120 + */
121 +function trapBubbledEvent(topLevelType, handlerBaseName, onWhat) {
122 + listen(
123 + onWhat,
124 + handlerBaseName,
125 + ReactEvent.TopLevelCallbackCreator.createTopLevelCallback(topLevelType)
126 + );
127 +}
128 +
129 +/**
130 + * Traps a top-level event by using event capturing.
131 + */
132 +function trapCapturedEvent(topLevelType, handlerBaseName, onWhat) {
133 + capture(
134 + onWhat,
135 + handlerBaseName,
136 + ReactEvent.TopLevelCallbackCreator.createTopLevelCallback(topLevelType)
137 + );
138 +}
139 +
140 +/**
141 + * Listens to document scroll and window resize events that may change the
142 + * document scroll values. We store those results so as to discourage
143 + * application code from asking the DOM itself which could trigger additional
144 + * reflows.
145 + */
146 +function registerDocumentScrollListener() {
147 + listen(window, 'scroll', function(nativeEvent) {
148 + if (nativeEvent.target === window) {
149 + BrowserEnv.refreshAuthoritativeScrollValues();
150 + }
151 + });
152 +}
153 +
154 +function registerDocumentResizeListener() {
155 + listen(window, 'resize', function(nativeEvent) {
156 + if (nativeEvent.target === window) {
157 + BrowserEnv.refreshAuthoritativeScrollValues();
158 + }
159 + });
160 +}
161 +
162 +/**
163 + * Summary of `ReactEvent` event handling:
164 + *
165 + * - We trap low level 'top-level' events.
166 + *
167 + * - We dedupe cross-browser event names into these 'top-level types' so that
168 + * `DOMMouseScroll` or `mouseWheel` both become `topMouseWheel`.
169 + *
170 + * - At this point we have native browser events with the top-level type that
171 + * was used to catch it at the top-level.
172 + *
173 + * - We continuously stream these native events (and their respective top-level
174 + * types) to the event plugin system `EventPluginHub` and ask the plugin
175 + * system if it was able to extract `AbstractEvent` objects. `AbstractEvent`
176 + * objects are the events that applications actually deal with - they are not
177 + * native browser events but cross-browser wrappers.
178 + *
179 + * - When returning the `AbstractEvent` objects, `EventPluginHub` will make
180 + * sure each abstract event is annotated with "dispatches", which are the
181 + * sequence of listeners (and IDs) that care about the event.
182 + *
183 + * - These `AbstractEvent` objects are fed back into the event plugin system,
184 + * which in turn executes these dispatches.
185 + *
186 + * @private
187 + */
188 +function listenAtTopLevel(touchNotMouse) {
189 + invariant(
190 + !_isListening,
191 + 'listenAtTopLevel(...): Cannot setup top-level listener more than once.'
192 + );
193 + var mountAt = document;
194 +
195 + registerDocumentScrollListener();
196 + registerDocumentResizeListener();
197 + trapBubbledEvent(topLevelTypes.topMouseOver, 'mouseover', mountAt);
198 + trapBubbledEvent(topLevelTypes.topMouseDown, 'mousedown', mountAt);
199 + trapBubbledEvent(topLevelTypes.topMouseUp, 'mouseup', mountAt);
200 + trapBubbledEvent(topLevelTypes.topMouseMove, 'mousemove', mountAt);
201 + trapBubbledEvent(topLevelTypes.topMouseOut, 'mouseout', mountAt);
202 + trapBubbledEvent(topLevelTypes.topClick, 'click', mountAt);
203 + trapBubbledEvent(topLevelTypes.topDoubleClick, 'dblclick', mountAt);
204 + trapBubbledEvent(topLevelTypes.topMouseWheel, 'mousewheel', mountAt);
205 + if (touchNotMouse) {
206 + trapBubbledEvent(topLevelTypes.topTouchStart, 'touchstart', mountAt);
207 + trapBubbledEvent(topLevelTypes.topTouchEnd, 'touchend', mountAt);
208 + trapBubbledEvent(topLevelTypes.topTouchMove, 'touchmove', mountAt);
209 + trapBubbledEvent(topLevelTypes.topTouchCancel, 'touchcancel', mountAt);
210 + }
211 + trapBubbledEvent(topLevelTypes.topKeyUp, 'keyup', mountAt);
212 + trapBubbledEvent(topLevelTypes.topKeyPress, 'keypress', mountAt);
213 + trapBubbledEvent(topLevelTypes.topKeyDown, 'keydown', mountAt);
214 + trapBubbledEvent(topLevelTypes.topChange, 'change', mountAt);
215 + trapBubbledEvent(
216 + topLevelTypes.topDOMCharacterDataModified,
217 + 'DOMCharacterDataModified',
218 + mountAt
219 + );
220 +
221 + // Firefox needs to capture a different mouse scroll event.
222 + // @see http://www.quirksmode.org/dom/events/tests/scroll.html
223 + trapBubbledEvent(topLevelTypes.topMouseWheel, 'DOMMouseScroll', mountAt);
224 + // IE < 9 doesn't support capturing so just trap the bubbled event there.
225 + if (isEventSupported('scroll', true)) {
226 + trapCapturedEvent(topLevelTypes.topScroll, 'scroll', mountAt);
227 + } else {
228 + trapBubbledEvent(topLevelTypes.topScroll, 'scroll', window);
229 + }
230 +
231 + if (isEventSupported('focus', true)) {
232 + trapCapturedEvent(topLevelTypes.topFocus, 'focus', mountAt);
233 + trapCapturedEvent(topLevelTypes.topBlur, 'blur', mountAt);
234 + } else if (isEventSupported('focusin')) {
235 + // IE has `focusin` and `focusout` events which bubble.
236 + // @see http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
237 + trapBubbledEvent(topLevelTypes.topFocus, 'focusin', mountAt);
238 + trapBubbledEvent(topLevelTypes.topBlur, 'focusout', mountAt);
239 + }
240 +}
241 +
242 +/**
243 + * This is the heart of `ReactEvent`. It simply streams the top-level native
244 + * events to `EventPluginHub`.
245 + *
246 + * @param {object} topLevelType Record from `EventConstants`.
247 + * @param {Event} nativeEvent A Standard Event with fixed `target` property.
248 + * @param {DOMElement} renderedTarget Element of interest to the framework.
249 + * @param {string} renderedTargetID string ID of `renderedTarget`.
250 + * @internal
251 + */
252 +function handleTopLevel(
253 + topLevelType,
254 + nativeEvent,
255 + renderedTargetID,
256 + renderedTarget) {
257 + var abstractEvents = EventPluginHub.extractAbstractEvents(
258 + topLevelType,
259 + nativeEvent,
260 + renderedTargetID,
261 + renderedTarget
262 + );
263 +
264 + // The event queue being processed in the same cycle allows preventDefault.
265 + EventPluginHub.enqueueAbstractEvents(abstractEvents);
266 + EventPluginHub.processAbstractEventQueue();
267 +}
268 +
269 +function setEnabled(enabled) {
270 + invariant(
271 + ExecutionEnvironment.canUseDOM,
272 + 'setEnabled(...): Cannot toggle event listening in a Worker thread. This ' +
273 + 'is likely a bug in the framework. Please report immediately.'
274 + );
275 + ReactEvent.TopLevelCallbackCreator.setEnabled(enabled);
276 +}
277 +
278 +function isEnabled() {
279 + return ReactEvent.TopLevelCallbackCreator.isEnabled();
280 +}
281 +
282 +/**
283 + * Ensures that top-level event delegation listeners are listening at `mountAt`.
284 + * There are issues with listening to both touch events and mouse events on the
285 + * top-level, so we make the caller choose which one to listen to. (If there's a
286 + * touch top-level listeners, anchors don't receive clicks for some reason, and
287 + * only in some cases).
288 + *
289 + * @param {boolean} touchNotMouse Listen to touch events instead of mouse.
290 + * @param {object} TopLevelCallbackCreator Module that can create top-level
291 + * callback handlers.
292 + * @internal
293 + */
294 +function ensureListening(touchNotMouse, TopLevelCallbackCreator) {
295 + invariant(
296 + ExecutionEnvironment.canUseDOM,
297 + 'ensureListening(...): Cannot toggle event listening in a Worker thread. ' +
298 + 'This is likely a bug in the framework. Please report immediately.'
299 + );
300 + if (!_isListening) {
301 + ReactEvent.TopLevelCallbackCreator = TopLevelCallbackCreator;
302 + listenAtTopLevel(touchNotMouse);
303 + _isListening = true;
304 + }
305 +}
306 +
307 +var ReactEvent = {
308 + TopLevelCallbackCreator: null, // Injectable callback creator.
309 + handleTopLevel: handleTopLevel,
310 + setEnabled: setEnabled,
311 + isEnabled: isEnabled,
312 + ensureListening: ensureListening,
313 + registrationNames: registrationNames,
314 + putListener: EventPluginHub.putListener,
315 + getListener: EventPluginHub.getListener,
316 + deleteAllListeners: EventPluginHub.deleteAllListeners,
317 + trapBubbledEvent: trapBubbledEvent,
318 + trapCapturedEvent: trapCapturedEvent
319 +};
320 +
321 +module.exports = ReactEvent;
src/core/ReactEventTopLevelCallback.js new
+74
@@ -0,0 +1,74 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactEventTopLevelCallback
17 + */
18 +
19 +"use strict";
20 +
21 +var ExecutionEnvironment = require('ExecutionEnvironment');
22 +var ReactEvent = require('ReactEvent');
23 +var ReactInstanceHandles = require('ReactInstanceHandles');
24 +
25 +var getDOMNodeID = require('getDOMNodeID');
26 +
27 +var _topLevelListenersEnabled = true;
28 +
29 +var ReactEventTopLevelCallback = {
30 +
31 + /**
32 + * @param {boolean} enabled Whether or not all callbacks that have ever been
33 + * created with this module should be enabled.
34 + */
35 + setEnabled: function(enabled) {
36 + _topLevelListenersEnabled = !!enabled;
37 + },
38 +
39 + isEnabled: function() {
40 + return _topLevelListenersEnabled;
41 + },
42 +
43 + /**
44 + * For a given `topLevelType`, creates a callback that could be added as a
45 + * listener to the document. That top level callback will simply fix the
46 + * native events before invoking `handleTopLevel`.
47 + *
48 + * - Raw native events cannot be trusted to describe their targets correctly
49 + * so we expect that the argument to the nested function has already been
50 + * fixed. But the `target` property may not be something of interest to
51 + * React, so we find the most suitable target. But even at that point, DOM
52 + * Elements (the target ) can't be trusted to describe their IDs correctly
53 + * so we obtain the ID in a reliable manner and pass it to
54 + * `handleTopLevel`. The target/id that we found to be relevant to our
55 + * framework are called `renderedTarget`/`renderedTargetID` respectively.
56 + */
57 + createTopLevelCallback: function(topLevelType) {
58 + return function(fixedNativeEvent) {
59 + if (!_topLevelListenersEnabled) {
60 + return;
61 + }
62 + var renderedTarget = ReactInstanceHandles.getFirstReactDOM(
63 + fixedNativeEvent.target
64 + ) || ExecutionEnvironment.global;
65 + var renderedTargetID = getDOMNodeID(renderedTarget);
66 + var event = fixedNativeEvent;
67 + var target = renderedTarget;
68 + ReactEvent.handleTopLevel(topLevelType, event, renderedTargetID, target);
69 + };
70 + }
71 +
72 +};
73 +
74 +module.exports = ReactEventTopLevelCallback;
src/core/ReactInputSelection.js new
+180
@@ -0,0 +1,180 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactInputSelection
17 + */
18 +
19 +"use strict";
20 +
21 +// It is not safe to read the document.activeElement property in IE if there's
22 +// nothing focused.
23 +function getActiveElement() {
24 + try {
25 + return document.activeElement;
26 + } catch (e) {
27 + }
28 +}
29 +
30 +/**
31 + * @ReactInputSelection: React input selection module. Based on Selection.js,
32 + * but modified to be suitable for react and has a couple of bug fixes (doesn't
33 + * assume buttons have range selections allowed).
34 + * Input selection module for React.
35 + */
36 +var ReactInputSelection = {
37 +
38 + hasSelectionCapabilities: function(elem) {
39 + return elem && (
40 + (elem.nodeName === 'INPUT' && elem.type === 'text') ||
41 + elem.nodeName === 'TEXTAREA' ||
42 + elem.contentEditable === 'true'
43 + );
44 + },
45 +
46 + getSelectionInformation: function() {
47 + var focusedElem = getActiveElement();
48 + return {
49 + focusedElem: focusedElem,
50 + selectionRange:
51 + ReactInputSelection.hasSelectionCapabilities(focusedElem) ?
52 + ReactInputSelection.getSelection(focusedElem) :
53 + null
54 + };
55 + },
56 +
57 + /**
58 + * @restoreSelection: If any selection information was potentially lost,
59 + * restore it. This is useful when performing operations that could remove dom
60 + * nodes and place them back in, resulting in focus being lost.
61 + */
62 + restoreSelection: function(priorSelectionInformation) {
63 + var curFocusedElem = getActiveElement();
64 + var priorFocusedElem = priorSelectionInformation.focusedElem;
65 + var priorSelectionRange = priorSelectionInformation.selectionRange;
66 + if (curFocusedElem !== priorFocusedElem &&
67 + document.getElementById(priorFocusedElem.id)) {
68 + if (ReactInputSelection.hasSelectionCapabilities(priorFocusedElem)) {
69 + ReactInputSelection.setSelection(
70 + priorFocusedElem,
71 + priorSelectionRange
72 + );
73 + }
74 + priorFocusedElem.focus();
75 + }
76 + },
77 +
78 + /**
79 + * @getSelection: Gets the selection bounds of a textarea or input.
80 + * -@input: Look up selection bounds of this input or textarea
81 + * -@return {start: selectionStart, end: selectionEnd}
82 + */
83 + getSelection: function(input) {
84 + var range;
85 + if (input.contentEditable === 'true' && window.getSelection) {
86 + range = window.getSelection().getRangeAt(0);
87 + var commonAncestor = range.commonAncestorContainer;
88 + if (commonAncestor && commonAncestor.nodeType === 3) {
89 + commonAncestor = commonAncestor.parentNode;
90 + }
91 + if (commonAncestor !== input) {
92 + return {start: 0, end: 0};
93 + } else {
94 + return {start: range.startOffset, end: range.endOffset};
95 + }
96 + }
97 +
98 + if (!document.selection) {
99 + // Mozilla, Safari, etc.
100 + return {start: input.selectionStart, end: input.selectionEnd};
101 + }
102 +
103 + range = document.selection.createRange();
104 + if (range.parentElement() !== input) {
105 + // There can only be one selection per document in IE, so if the
106 + // containing element of the document's selection isn't our text field,
107 + // our text field must have no selection.
108 + return {start: 0, end: 0};
109 + }
110 +
111 + var length = input.value.length;
112 +
113 + if (input.nodeName === 'INPUT') {
114 + return {
115 + start: -range.moveStart('character', -length),
116 + end: -range.moveEnd('character', -length)
117 + };
118 + } else {
119 + var range2 = range.duplicate();
120 + range2.moveToElementText(input);
121 + range2.setEndPoint('StartToEnd', range);
122 + var end = length - range2.text.length;
123 + range2.setEndPoint('StartToStart', range);
124 + return {
125 + start: length - range2.text.length,
126 + end: end
127 + };
128 + }
129 + },
130 +
131 + /**
132 + * @setSelection: Sets the selection bounds of a textarea or input and focuses
133 + * the input.
134 + * -@input Set selection bounds of this input or textarea
135 + * -@rangeObj Object of same form that is returned from get*
136 + */
137 + setSelection: function(input, rangeObj) {
138 + var range;
139 + var start = rangeObj.start;
140 + var end = rangeObj.end;
141 + if (typeof end === 'undefined') {
142 + end = start;
143 + }
144 + if (document.selection) {
145 + // IE is inconsistent about character offsets when it comes to carriage
146 + // returns, so we need to manually take them into account
147 + if (input.tagName === 'TEXTAREA') {
148 + var cr_before =
149 + (input.value.slice(0, start).match(/\r/g) || []).length;
150 + var cr_inside =
151 + (input.value.slice(start, end).match(/\r/g) || []).length;
152 + start -= cr_before;
153 + end -= cr_before + cr_inside;
154 + }
155 + range = input.createTextRange();
156 + range.collapse(true);
157 + range.moveStart('character', start);
158 + range.moveEnd('character', end - start);
159 + range.select();
160 + } else {
161 + if (input.contentEditable === 'true') {
162 + if (input.childNodes.length === 1) {
163 + range = document.createRange();
164 + range.setStart(input.childNodes[0], start);
165 + range.setEnd(input.childNodes[0], end);
166 + var sel = window.getSelection();
167 + sel.removeAllRanges();
168 + sel.addRange(range);
169 + }
170 + } else {
171 + input.selectionStart = start;
172 + input.selectionEnd = Math.min(end, input.value.length);
173 + input.focus();
174 + }
175 + }
176 + }
177 +
178 +};
179 +
180 +module.exports = ReactInputSelection;
src/core/ReactInstanceHandles.js new
+326
@@ -0,0 +1,326 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactInstanceHandles
17 + */
18 +
19 +"use strict";
20 +
21 +var getDOMNodeID = require('getDOMNodeID');
22 +var invariant = require('invariant');
23 +
24 +var SEPARATOR = '.';
25 +var SEPARATOR_LENGTH = SEPARATOR.length;
26 +
27 +/**
28 + * Maximum depth of traversals before we consider the possibility of a bad ID.
29 + */
30 +var MAX_TREE_DEPTH = 100;
31 +
32 +/**
33 + * Checks if a character in the supplied ID is a separator or the end.
34 + *
35 + * @param {string} id A React DOM ID.
36 + * @param {number} index Index of the character to check.
37 + * @return {boolean} True if the character is a separator or end of the ID.
38 + * @private
39 + */
40 +function isMarker(id, index) {
41 + return id.charAt(index) === SEPARATOR || index === id.length;
42 +}
43 +
44 +/**
45 + * Checks if the supplied string is a valid React DOM ID.
46 + *
47 + * @param {string} id A React DOM ID, maybe.
48 + * @return {boolean} True if the string is a valid React DOM ID.
49 + * @private
50 + */
51 +function isValidID(id) {
52 + return id === '' || (
53 + id.charAt(0) === SEPARATOR && id.charAt(id.length - 1) !== SEPARATOR
54 + );
55 +}
56 +
57 +/**
58 + * True if the supplied `node` is rendered by React.
59 + *
60 + * @param {DOMElement} node DOM Element to check.
61 + * @return {boolean} True if the DOM Element appears to be rendered by React.
62 + * @private
63 + */
64 +function isRenderedByReact(node) {
65 + var id = getDOMNodeID(node);
66 + return id && id.charAt(0) === SEPARATOR;
67 +}
68 +
69 +/**
70 + * Gets the parent ID of the supplied React DOM ID, `id`.
71 + *
72 + * @param {string} id ID of a component.
73 + * @return {string} ID of the parent, or an empty string.
74 + * @private
75 + */
76 +function parentID(id) {
77 + return id ? id.substr(0, id.lastIndexOf(SEPARATOR)) : '';
78 +}
79 +
80 +/**
81 + * Traverses the parent path between two IDs (either up or down). The IDs must
82 + * not be the same, and there must exist a parent path between them.
83 + *
84 + * @param {?string} start ID at which to start traversal.
85 + * @param {?string} stop ID at which to end traversal.
86 + * @param {function} cb Callback to invoke each ID with.
87 + * @param {?boolean} skipFirst Whether or not to skip the first node.
88 + * @param {?boolean} skipLast Whether or not to skip the last node.
89 + * @private
90 + */
91 +function traverseParentPath(start, stop, cb, arg, skipFirst, skipLast) {
92 + start = start || '';
93 + stop = stop || '';
94 + invariant(
95 + start !== stop,
96 + 'traverseParentPath(...): Cannot traverse from and to the same ID, `%s`.',
97 + start
98 + );
99 + var ancestorID = ReactInstanceHandles.getFirstCommonAncestorID(start, stop);
100 + var traverseUp = ancestorID === stop;
101 + invariant(
102 + traverseUp || ancestorID === start,
103 + 'traverseParentPath(%s, %s, ...): Cannot traverse from two IDs that do ' +
104 + 'not have a parent path.',
105 + start,
106 + stop
107 + );
108 + // Traverse from `start` to `stop` one depth at a time.
109 + var depth = 0;
110 + var traverse = traverseUp ? parentID : ReactInstanceHandles.nextDescendantID;
111 + for (var id = start; /* until break */; id = traverse(id, stop)) {
112 + if ((!skipFirst || id !== start) && (!skipLast || id !== stop)) {
113 + cb(id, traverseUp, arg);
114 + }
115 + if (id === stop) {
116 + // Only break //after// visiting `stop`.
117 + break;
118 + }
119 + invariant(
120 + depth++ < MAX_TREE_DEPTH,
121 + 'traverseParentPath(%s, %s, ...): Detected an infinite loop while ' +
122 + 'traversing the React DOM ID tree. This may be due to malformed IDs: %s',
123 + start, stop
124 + );
125 + }
126 +}
127 +
128 +/**
129 + * Manages the IDs assigned to DOM representations of React components. This
130 + * uses a specific scheme in order to traverse the DOM efficiently (e.g. in
131 + * order to simulate events).
132 + *
133 + * @internal
134 + */
135 +var ReactInstanceHandles = {
136 +
137 + separator: SEPARATOR,
138 +
139 + /**
140 + * Traverses up the ancestors of the supplied node to find a node that is a
141 + * DOM representation of a React component.
142 + *
143 + * @param {DOMElement} node
144 + * @return {?DOMElement}
145 + * @internal
146 + */
147 + getFirstReactDOM: function(node) {
148 + var current = node;
149 + while (current && current.parentNode !== current) {
150 + if (isRenderedByReact(current)) {
151 + return current;
152 + }
153 + current = current.parentNode;
154 + }
155 + return null;
156 + },
157 +
158 + /**
159 + * Finds a node with the supplied `id` inside of the supplied `ancestorNode`.
160 + * Exploits the ID naming scheme to perform the search quickly.
161 + *
162 + * @param {DOMElement} ancestorNode Search from this root.
163 + * @pararm {string} id ID of the DOM representation of the component.
164 + * @return {?DOMElement} DOM element with the supplied `id`, if one exists.
165 + * @internal
166 + */
167 + findComponentRoot: function(ancestorNode, id) {
168 + var child = ancestorNode.firstChild;
169 + while (child) {
170 + if (id === child.id) {
171 + return child;
172 + } else if (id.indexOf(child.id) === 0) {
173 + return ReactInstanceHandles.findComponentRoot(child, id);
174 + }
175 + child = child.nextSibling;
176 + }
177 + // Effectively: return null;
178 + },
179 +
180 + /**
181 + * Gets the nearest common ancestor ID of two IDs.
182 + *
183 + * Using this ID scheme, the nearest common ancestor ID is the longest common
184 + * prefix of the two IDs that immediately preceded a "marker" in both strings.
185 + *
186 + * @param {string} oneID
187 + * @param {string} twoID
188 + * @return {string} Nearest common ancestor ID, or the empty string if none.
189 + * @internal
190 + */
191 + getFirstCommonAncestorID: function(oneID, twoID) {
192 + var minLength = Math.min(oneID.length, twoID.length);
193 + if (minLength === 0) {
194 + return '';
195 + }
196 + var lastCommonMarkerIndex = 0;
197 + // Use `<=` to traverse until the "EOL" of the shorter string.
198 + for (var i = 0; i <= minLength; i++) {
199 + if (isMarker(oneID, i) && isMarker(twoID, i)) {
200 + lastCommonMarkerIndex = i;
201 + } else if (oneID.charAt(i) !== twoID.charAt(i)) {
202 + break;
203 + }
204 + }
205 + var longestCommonID = oneID.substr(0, lastCommonMarkerIndex);
206 + invariant(
207 + isValidID(longestCommonID),
208 + 'getFirstCommonAncestorID(%s, %s): Expected a valid React DOM ID: %s',
209 + oneID,
210 + twoID,
211 + longestCommonID
212 + );
213 + return longestCommonID;
214 + },
215 + /**
216 + * Creates a DOM ID to use when mounting React components.
217 + *
218 + * @param {number} mountPointCount The count of React renders so far.
219 + * @return {string} React root ID.
220 + * @internal
221 + */
222 + getReactRootID: function(mountPointCount) {
223 + return '.reactRoot[' + mountPointCount + ']';
224 + },
225 +
226 + /**
227 + * Gets the DOM ID of the React component that is the root of the tree that
228 + * contains the React component with the supplied DOM ID.
229 + *
230 + * @param {string} id DOM ID of a React component.
231 + * @return {string} DOM ID of the React component that is the root.
232 + * @internal
233 + */
234 + getReactRootIDFromNodeID: function(id) {
235 + var regexResult = /\.reactRoot\[[^\]]+\]/.exec(id);
236 + return regexResult && regexResult[0];
237 + },
238 +
239 + /**
240 + * Traverses the ID hierarchy and invokes the supplied `cb` on any IDs that
241 + * should would receive a `mouseEnter` or `mouseLeave` event.
242 + *
243 + * NOTE: Does not invoke the callback on the nearest common ancestor because
244 + * nothing "entered" or "left" that element.
245 + *
246 + * @param {string} leaveID ID being left.
247 + * @param {string} enterID ID being entered.
248 + * @param {function} cb Callback to invoke on each entered/left ID.
249 + * @param {*} upArg Argument to invoke the callback with on left IDs.
250 + * @param {*} downArg Argument to invoke the callback with on entered IDs.
251 + * @internal
252 + */
253 + traverseEnterLeave: function(leaveID, enterID, cb, upArg, downArg) {
254 + var longestCommonID = ReactInstanceHandles.getFirstCommonAncestorID(
255 + leaveID,
256 + enterID
257 + );
258 + if (longestCommonID !== leaveID) {
259 + traverseParentPath(leaveID, longestCommonID, cb, upArg, false, true);
260 + }
261 + if (longestCommonID !== enterID) {
262 + traverseParentPath(longestCommonID, enterID, cb, downArg, true, false);
263 + }
264 + },
265 +
266 + /**
267 + * Simulates the traversal of a two-phase, capture/bubble event dispatch.
268 + *
269 + * NOTE: This traversal happens on IDs without touching the DOM.
270 + *
271 + * @param {string} targetID ID of the target node.
272 + * @param {function} cb Callback to invoke.
273 + * @param {*} arg Argument to invoke the callback with.
274 + * @internal
275 + */
276 + traverseTwoPhase: function(targetID, cb, arg) {
277 + if (targetID) {
278 + traverseParentPath('', targetID, cb, arg, true, false);
279 + traverseParentPath(targetID, '', cb, arg, false, true);
280 + }
281 + },
282 +
283 + /**
284 + * Gets the next DOM ID on the tree path from the supplied `ancestorID` to the
285 + * supplied `destinationID`. If they are equal, the ID is returned.
286 + *
287 + * @param {string} ancestorID ID of an ancestor node of `destinationID`.
288 + * @param {string} destinationID ID of the destination node.
289 + * @return {string} Next ID on the path from `ancestorID` to `destinationID`.
290 + * @internal
291 + */
292 + nextDescendantID: function(ancestorID, destinationID) {
293 + invariant(
294 + isValidID(ancestorID) && isValidID(destinationID),
295 + 'nextDescendantID(%s, %s): Received an invalid React DOM ID.',
296 + ancestorID,
297 + destinationID
298 + );
299 + var longestCommonID = ReactInstanceHandles.getFirstCommonAncestorID(
300 + ancestorID,
301 + destinationID
302 + );
303 + invariant(
304 + longestCommonID === ancestorID,
305 + 'nextDescendantID(...): React has made an invalid assumption about the ' +
306 + 'DOM hierarchy. Expected `%s` to be an ancestor of `%s`.',
307 + ancestorID,
308 + destinationID
309 + );
310 + if (ancestorID === destinationID) {
311 + return ancestorID;
312 + }
313 + // Skip over the ancestor and the immediate separator. Traverse until we hit
314 + // another separator or we reach the end of `destinationID`.
315 + var start = ancestorID.length + SEPARATOR_LENGTH;
316 + for (var i = start; i < destinationID.length; i++) {
317 + if (isMarker(destinationID, i)) {
318 + break;
319 + }
320 + }
321 + return destinationID.substr(0, i);
322 + }
323 +
324 +};
325 +
326 +module.exports = ReactInstanceHandles;
src/core/ReactMount.js new
+232
@@ -0,0 +1,232 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactMount
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactEvent = require('ReactEvent');
22 +var ReactInstanceHandles = require('ReactInstanceHandles');
23 +var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
24 +
25 +var $ = require('$');
26 +
27 +var globalMountPointCounter = 0;
28 +
29 +/** Mapping from reactRoot DOM ID to React component instance. */
30 +var instanceByReactRootID = {};
31 +
32 +/** Mapping from reactRoot DOM ID to `container` nodes. */
33 +var containersByReactRootID = {};
34 +
35 +/**
36 + * @param {DOMElement} container DOM element that may contain a React component.
37 + * @return {?string} A "reactRoot" ID, if a React component is rendered.
38 + */
39 +function getReactRootID(container) {
40 + return container.firstChild && container.firstChild.id;
41 +}
42 +
43 +/**
44 + * Mounting is the process of initializing a React component by creatings its
45 + * representative DOM elements and inserting them into a supplied `container`.
46 + * Any prior content inside `container` is destroyed in the process.
47 + *
48 + * ReactMount.renderComponent(component, $('container'));
49 + *
50 + * <div id="container"> <-- Supplied `container`.
51 + * <div id=".reactRoot[3]"> <-- Rendered reactRoot of React component.
52 + * // ...
53 + * </div>
54 + * </div>
55 + *
56 + * Inside of `container`, the first element rendered is the "reactRoot".
57 + */
58 +var ReactMount = {
59 +
60 + /** Time spent generating markup. */
61 + totalInstantiationTime: 0,
62 +
63 + /** Time spent inserting markup into the DOM. */
64 + totalInjectionTime: 0,
65 +
66 + /** Whether support for touch events should be initialized. */
67 + useTouchEvents: false,
68 +
69 + /**
70 + * This is a hook provided to support rendering React components while
71 + * ensuring that the apparent scroll position of its `container` does not
72 + * change.
73 + *
74 + * @param {DOMElement} container The `container` being rendered into.
75 + * @param {function} renderCallback This must be called once to do the render.
76 + */
77 + scrollMonitor: function(container, renderCallback) {
78 + renderCallback();
79 + },
80 +
81 + /**
82 + * Ensures tht the top-level event delegation listener is set up. This will be
83 + * invoked some time before the first time any React component is rendered.
84 + *
85 + * @param {object} TopLevelCallbackCreator
86 + * @private
87 + */
88 + prepareTopLevelEvents: function(TopLevelCallbackCreator) {
89 + ReactEvent.ensureListening(
90 + ReactMount.useTouchEvents,
91 + TopLevelCallbackCreator
92 + );
93 + },
94 +
95 + /**
96 + * Renders a React component into the DOM in the supplied `container`.
97 + *
98 + * If the React component was previously rendered into `container`, this will
99 + * perform an update on it and only mutate the DOM as necessary to reflect the
100 + * latest React component.
101 + *
102 + * @param {ReactComponent} nextComponent Component instance to render.
103 + * @param {DOMElement} container DOM element to render into.
104 + * @return {ReactComponent} Component instance rendered in `container`.
105 + */
106 + renderComponent: function(nextComponent, container) {
107 + var prevComponent = instanceByReactRootID[getReactRootID(container)];
108 + if (prevComponent) {
109 + var nextProps = nextComponent.props;
110 + ReactMount.scrollMonitor(container, function() {
111 + prevComponent.replaceProps(nextProps);
112 + });
113 + return prevComponent;
114 + }
115 +
116 + ReactMount.prepareTopLevelEvents(ReactEventTopLevelCallback);
117 +
118 + var reactRootID = ReactMount.registerContainer(container);
119 + instanceByReactRootID[reactRootID] = nextComponent;
120 + nextComponent.mountComponentIntoNode(reactRootID, container);
121 + return nextComponent;
122 + },
123 +
124 + /**
125 + * Creates a function that accepts a `container` and renders the supplied
126 + * React component instance into it.
127 + *
128 + * var renderInto = ReactMount.createComponentRenderer(component);
129 + * // ...
130 + * var component = renderInto($('container'));
131 + *
132 + * @param {ReactComponent} component Component instance to render.
133 + * @return {function(DOMElement): ReactComponent}
134 + */
135 + createComponentRenderer: function(component) {
136 + return function(container) {
137 + return ReactMount.renderComponent(component, container);
138 + };
139 + },
140 +
141 + /**
142 + * Constructs a component instance of `constructor` with `initialProps` and
143 + * renders it into the supplied `container`.
144 + *
145 + * @param {function} constructor React component constructor.
146 + * @param {?object} props Initial props of the component instance.
147 + * @param {DOMElement} container DOM element to render into.
148 + * @return {ReactComponent} Component instance rendered in `container`.
149 + */
150 + constructAndRenderComponent: function(constructor, props, container) {
151 + return ReactMount.renderComponent(constructor(props), container);
152 + },
153 +
154 + /**
155 + * Constructs a component instance of `constructor` with `initialProps` and
156 + * renders it into a container node identified by supplied `id`.
157 + *
158 + * @param {function} componentConstructor React component constructor
159 + * @param {?object} props Initial props of the component instance.
160 + * @param {string} id ID of the DOM element to render into.
161 + * @return {ReactComponent} Component instance rendered in the container node.
162 + */
163 + constructAndRenderComponentByID: function(constructor, props, id) {
164 + return ReactMount.constructAndRenderComponent(constructor, props, $(id));
165 + },
166 +
167 + /**
168 + * Registers a container node into which React components will be rendered.
169 + * This also creates the "reatRoot" ID that will be assigned to the element
170 + * rendered within.
171 + *
172 + * @param {DOMElement} container DOM element to register as a container.
173 + * @return {string} The "reactRoot" ID of elements rendered within.
174 + */
175 + registerContainer: function(container) {
176 + var reactRootID = getReactRootID(container);
177 + if (reactRootID) {
178 + // If one exists, make sure it is a valid "reactRoot" ID.
179 + reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID);
180 + }
181 + if (!reactRootID) {
182 + // No valid "reactRoot" ID found, create one.
183 + reactRootID = ReactInstanceHandles.getReactRootID(
184 + globalMountPointCounter++
185 + );
186 + }
187 + containersByReactRootID[reactRootID] = container;
188 + return reactRootID;
189 + },
190 +
191 + /**
192 + * Unmounts and destroys the React component rendered in the `container`.
193 + *
194 + * @param {DOMElement} container DOM element containing a React component.
195 + */
196 + unmountAndReleaseReactRootNode: function(container) {
197 + var reactRootID = getReactRootID(container);
198 + var component = instanceByReactRootID[reactRootID];
199 + // TODO: Consider throwing if no `component` was found.
200 + component.unmountComponentFromNode(container);
201 + delete instanceByReactRootID[reactRootID];
202 + delete containersByReactRootID[reactRootID];
203 + },
204 +
205 + /**
206 + * Finds the container DOM element that contains React component to which the
207 + * supplied DOM `id` belongs.
208 + *
209 + * @param {string} id The ID of an element rendered by a React component.
210 + * @return {?DOMElement} DOM element that contains the `id`.
211 + */
212 + findReactContainerForID: function(id) {
213 + var reatRootID = ReactInstanceHandles.getReactRootIDFromNodeID(id);
214 + // TODO: Consider throwing if `id` is not a valid React element ID.
215 + return containersByReactRootID[reatRootID];
216 + },
217 +
218 + /**
219 + * Given the ID of a DOM node rendered by a React component, finds the root
220 + * DOM node of the React component.
221 + *
222 + * @param {string} id ID of a DOM node in the React component.
223 + * @return {?DOMElement} Root DOM node of the React component.
224 + */
225 + findReactRenderedDOMNodeSlow: function(id) {
226 + var reactRoot = ReactMount.findReactContainerForID(id);
227 + return ReactInstanceHandles.findComponentRoot(reactRoot, id);
228 + }
229 +
230 +};
231 +
232 +module.exports = ReactMount;
src/core/ReactMultiChild.js new
+208
@@ -0,0 +1,208 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactMultiChild
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactComponent = require('ReactComponent');
22 +
23 +/**
24 + * Given a `curChild` and `newChild`, determines if `curChild` should be managed
25 + * as it exists, as opposed to being destroyed and/or replaced.
26 + * @param {?ReactComponent} curChild
27 + * @param {?ReactComponent} newChild
28 + * @return {!boolean} Whether or not `curChild` should be updated with
29 + * `newChild`'s props
30 + */
31 +function shouldManageExisting(curChild, newChild) {
32 + return curChild && newChild && curChild.constructor === newChild.constructor;
33 +}
34 +
35 +/**
36 + * `ReactMultiChild` provides common functionality for components that have
37 + * multiple children. Standard `ReactCompositeComponent`s do not currently have
38 + * multiple children. `ReactNativeComponent`s do, however. Other specially
39 + * reconciled components will also have multiple children. Contains three
40 + * internally used properties that are used to keep track of state throughout
41 + * the `updateMultiChild` process.
42 + *
43 + * @class ReactMultiChild
44 + */
45 +
46 +/**
47 + * @lends `ReactMultiChildMixin`.
48 + */
49 +var ReactMultiChildMixin = {
50 +
51 + enqueueMarkupAt: function(markup, insertAt) {
52 + this.domOperations = this.domOperations || [];
53 + this.domOperations.push({insertMarkup: markup, finalIndex: insertAt});
54 + },
55 +
56 + enqueueMove: function(originalIndex, finalIndex) {
57 + this.domOperations = this.domOperations || [];
58 + this.domOperations.push({moveFrom: originalIndex, finalIndex: finalIndex});
59 + },
60 +
61 + enqueueUnmountChildByName: function(name, removeChild) {
62 + if (ReactComponent.isValidComponent(removeChild)) {
63 + this.domOperations = this.domOperations || [];
64 + this.domOperations.push({removeAt: removeChild._domIndex});
65 + removeChild.unmountComponent && removeChild.unmountComponent();
66 + delete this._renderedChildren[name];
67 + }
68 + },
69 +
70 + /**
71 + * Process any pending DOM operations that have been accumulated when updating
72 + * the UI. By default, we execute the injected `DOMIDOperations` module's
73 + * `manageChildrenByParentID` which does executes the DOM operations without
74 + * any animation. It can be used as a reference implementation for special
75 + * animation based implementations.
76 + *
77 + * @abstract
78 + */
79 + processChildDOMOperationsQueue: function() {
80 + if (this.domOperations) {
81 + ReactComponent.DOMIDOperations
82 + .manageChildrenByParentID(this._rootNodeID, this.domOperations);
83 + this.domOperations = null;
84 + }
85 + },
86 +
87 + unmountMultiChild: function() {
88 + var renderedChildren = this._renderedChildren;
89 + for (var name in renderedChildren) {
90 + if (renderedChildren.hasOwnProperty(name) && renderedChildren[name]) {
91 + var renderedChild = renderedChildren[name];
92 + renderedChild.unmountComponent && renderedChild.unmountComponent();
93 + }
94 + }
95 + this._renderedChildren = null;
96 + },
97 +
98 + /**
99 + * Generates markup for a component that holds multiple children. #todo: Allow
100 + * all `ReactMultiChildMixin`s to support having arrays of children without a
101 + * container node. This current implementation may assume that children exist
102 + * at domIndices [0, parentNode.length].
103 + *
104 + * Has side effects of (likely) causing events to be registered. Also, every
105 + * component instance may only be rendered once.
106 + *
107 + * @param {?Object} children Flattened children object.
108 + * @return {!String} The rendered markup.
109 + */
110 + mountMultiChild: function(children, transaction) {
111 + var accum = '';
112 + var index = 0;
113 + for (var name in children) {
114 + var child = children[name];
115 + if (children.hasOwnProperty(name) && child) {
116 + accum += child.mountComponent(
117 + this._rootNodeID + '.' + name,
118 + transaction
119 + );
120 + child._domIndex = index;
121 + index++;
122 + }
123 + }
124 + this._renderedChildren = children; // children are in just the right form!
125 + this.domOperations = null;
126 + return accum;
127 + },
128 +
129 + /**
130 + * Reconciles new children with old children in three phases.
131 + *
132 + * - Adds new content while updating existing children that should remain.
133 + * - Remove children that are no longer present in the next children.
134 + * - As a very last step, moves existing dom structures around.
135 + * - (Comment 1) `curChildrenDOMIndex` is the largest index of the current
136 + * rendered children that appears in the next children and did not need to
137 + * be "moved".
138 + * - (Comment 2) This is the key insight. If any non-removed child's previous
139 + * index is less than `curChildrenDOMIndex` it must be moved.
140 + *
141 + * @param {?Object} children Flattened children object.
142 + */
143 + updateMultiChild: function(nextChildren, transaction) {
144 + if (!nextChildren && !this._renderedChildren) {
145 + return;
146 + } else if (nextChildren && !this._renderedChildren) {
147 + this._renderedChildren = {}; // lazily allocate backing store with nothing
148 + } else if (!nextChildren && this._renderedChildren) {
149 + nextChildren = {};
150 + }
151 + var rootDomIdDot = this._rootNodeID + '.';
152 + var markupBuffer = null; // Accumulate adjacent new children markup.
153 + var numPendingInsert = 0; // How many root nodes are waiting in markupBuffer
154 + var loopDomIndex = 0; // Index of loop through new children.
155 + var curChildrenDOMIndex = 0; // See (Comment 1)
156 + for (var name in nextChildren) {
157 + if (!nextChildren.hasOwnProperty(name)) {continue;}
158 + var curChild = this._renderedChildren[name];
159 + var nextChild = nextChildren[name];
160 + if (shouldManageExisting(curChild, nextChild)) {
161 + if (markupBuffer) {
162 + this.enqueueMarkupAt(markupBuffer, loopDomIndex - numPendingInsert);
163 + markupBuffer = null;
164 + }
165 + numPendingInsert = 0;
166 + if (curChild._domIndex < curChildrenDOMIndex) { // (Comment 2)
167 + this.enqueueMove(curChild._domIndex, loopDomIndex);
168 + }
169 + curChildrenDOMIndex = Math.max(curChild._domIndex, curChildrenDOMIndex);
170 + !nextChild.props.isStatic &&
171 + curChild.receiveProps(nextChild.props, transaction);
172 + curChild._domIndex = loopDomIndex;
173 + } else {
174 + if (curChild) { // !shouldUpdate && curChild => delete
175 + this.enqueueUnmountChildByName(name, curChild);
176 + curChildrenDOMIndex =
177 + Math.max(curChild._domIndex, curChildrenDOMIndex);
178 + }
179 + if (nextChild) { // !shouldUpdate && nextChild => insert
180 + this._renderedChildren[name] = nextChild;
181 + var nextMarkup =
182 + nextChild.mountComponent(rootDomIdDot + name, transaction);
183 + markupBuffer = markupBuffer ? markupBuffer + nextMarkup : nextMarkup;
184 + numPendingInsert++;
185 + nextChild._domIndex = loopDomIndex;
186 + }
187 + }
188 + loopDomIndex = nextChild ? loopDomIndex + 1 : loopDomIndex;
189 + }
190 + if (markupBuffer) {
191 + this.enqueueMarkupAt(markupBuffer, loopDomIndex - numPendingInsert);
192 + }
193 + for (var childName in this._renderedChildren) { // from other direction
194 + if (!this._renderedChildren.hasOwnProperty(childName)) { continue; }
195 + var child = this._renderedChildren[childName];
196 + if (child && !nextChildren[childName]) {
197 + this.enqueueUnmountChildByName(childName, child);
198 + }
199 + }
200 + this.processChildDOMOperationsQueue();
201 + }
202 +};
203 +
204 +var ReactMultiChild = {
205 + Mixin: ReactMultiChildMixin
206 +};
207 +
208 +module.exports = ReactMultiChild;
src/core/ReactNativeComponent.js new
+331
@@ -0,0 +1,331 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactNativeComponent
17 + * @typechecks
18 + */
19 +
20 +"use strict";
21 +
22 +var CSSPropertyOperations = require('CSSPropertyOperations');
23 +var DOMPropertyOperations = require('DOMPropertyOperations');
24 +var ReactComponent = require('ReactComponent');
25 +var ReactEvent = require('ReactEvent');
26 +var ReactMultiChild = require('ReactMultiChild');
27 +
28 +var escapeTextForBrowser = require('escapeTextForBrowser');
29 +var flattenChildren = require('flattenChildren');
30 +var invariant = require('invariant');
31 +var keyOf = require('keyOf');
32 +var merge = require('merge');
33 +var mixInto = require('mixInto');
34 +
35 +var putListener = ReactEvent.putListener;
36 +var registrationNames = ReactEvent.registrationNames;
37 +
38 +// For quickly matching children type, to test if can be treated as content.
39 +var CONTENT_TYPES = {'string': true, 'number': true};
40 +
41 +var CONTENT = keyOf({content: null});
42 +var DANGEROUSLY_SET_INNER_HTML = keyOf({dangerouslySetInnerHTML: null});
43 +var STYLE = keyOf({style: null});
44 +
45 +/**
46 + * @param {?object} props
47 + */
48 +function assertValidProps(props) {
49 + if (!props) {
50 + return;
51 + }
52 + // Note the use of `!=` which checks for null or undefined.
53 + var hasChildren = props.children != null ? 1 : 0;
54 + var hasContent = props.content != null ? 1 : 0;
55 + var hasInnerHTML = props.dangerouslySetInnerHTML != null ? 1 : 0;
56 + invariant(
57 + hasChildren + hasContent + hasInnerHTML <= 1,
58 + 'Can only set one of `children`, `props.content`, or ' +
59 + '`props.dangerouslySetInnerHTML`.'
60 + );
61 + invariant(
62 + props.style == null || typeof props.style === 'object',
63 + 'The `style` prop expects a mapping from style properties to values, ' +
64 + 'not a string.'
65 + );
66 +}
67 +
68 +/**
69 + * @constructor ReactNativeComponent
70 + * @extends ReactComponent
71 + * @extends ReactMultiChild
72 + */
73 +function ReactNativeComponent(tag, omitClose) {
74 + this._tagOpen = '<' + tag + ' ';
75 + this._tagClose = omitClose ? '' : '</' + tag + '>';
76 + this.tagName = tag.toUpperCase();
77 +}
78 +
79 +ReactNativeComponent.Mixin = {
80 +
81 + /**
82 + * Generates root tag markup then recurses. This method has side effects and
83 + * is not idempotent.
84 + *
85 + * @internal
86 + * @param {string} rootID The root DOM ID for this node.
87 + * @param {ReactReconcileTransaction} transaction
88 + * @return {string} The computed markup.
89 + */
90 + mountComponent: function(rootID, transaction) {
91 + ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
92 + assertValidProps(this.props);
93 + return (
94 + this._createOpenTagMarkup() +
95 + this._createContentMarkup(transaction) +
96 + this._tagClose
97 + );
98 + },
99 +
100 + /**
101 + * Creates markup for the open tag and all attributes.
102 + *
103 + * This method has side effects because events get registered.
104 + *
105 + * Iterating over object properties is faster than iterating over arrays.
106 + * @see http://jsperf.com/obj-vs-arr-iteration
107 + *
108 + * @private
109 + * @return {string} Markup of opening tag.
110 + */
111 + _createOpenTagMarkup: function() {
112 + var props = this.props;
113 + var ret = this._tagOpen;
114 +
115 + for (var propKey in props) {
116 + if (!props.hasOwnProperty(propKey)) {
117 + continue;
118 + }
119 + var propValue = props[propKey];
120 + if (propValue == null) {
121 + continue;
122 + }
123 + if (registrationNames[propKey]) {
124 + putListener(this._rootNodeID, propKey, propValue);
125 + } else {
126 + if (propKey === STYLE) {
127 + if (propValue) {
128 + propValue = props.style = merge(props.style);
129 + }
130 + propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
131 + }
132 + var markup =
133 + DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
134 + if (markup) {
135 + ret += ' ' + markup;
136 + }
137 + }
138 + }
139 +
140 + return ret + ' id="' + this._rootNodeID + '">';
141 + },
142 +
143 + /**
144 + * Creates markup for the content between the tags.
145 + *
146 + * @private
147 + * @param {ReactReconcileTransaction} transaction
148 + * @return {string} Content markup.
149 + */
150 + _createContentMarkup: function(transaction) {
151 + // Intentional use of != to avoid catching zero/false.
152 + var innerHTML = this.props.dangerouslySetInnerHTML;
153 + if (innerHTML != null) {
154 + if (innerHTML.__html != null) {
155 + return innerHTML.__html;
156 + }
157 + } else {
158 + var contentToUse = this.props.content != null ? this.props.content :
159 + CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
160 + var childrenToUse = contentToUse != null ? null : this.props.children;
161 + if (contentToUse != null) {
162 + return escapeTextForBrowser(contentToUse);
163 + } else if (childrenToUse != null) {
164 + return this.mountMultiChild(
165 + flattenChildren(childrenToUse),
166 + transaction
167 + );
168 + }
169 + }
170 + return '';
171 + },
172 +
173 + /**
174 + * Controls a native DOM component after it has already been allocated and
175 + * attached to the DOM. Reconciles the root DOM node, then recurses.
176 + *
177 + * @internal
178 + * @param {object} nextProps
179 + * @param {ReactReconcileTransaction} transaction
180 + */
181 + receiveProps: function(nextProps, transaction) {
182 + invariant(
183 + this._rootNodeID,
184 + 'Trying to control a native dom element without a backing id'
185 + );
186 + assertValidProps(nextProps);
187 + ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
188 + this._updateDOMProperties(nextProps);
189 + this._updateDOMChildren(nextProps, transaction);
190 + this.props = nextProps;
191 + },
192 +
193 + /**
194 + * Reconciles the properties by detecting differences in property values and
195 + * updating the DOM as necessary. This function is probably the single most
196 + * critical path for performance optimization.
197 + *
198 + * TODO: Benchmark whether checking for changed values in memory actually
199 + * improves performance (especially statically positioned elements).
200 + * TODO: Benchmark the effects of putting this at the top since 99% of props
201 + * do not change for a given reconciliation.
202 + * TODO: Benchmark areas that can be improved with caching.
203 + *
204 + * @private
205 + * @param {object} nextProps
206 + */
207 + _updateDOMProperties: function(nextProps) {
208 + var lastProps = this.props;
209 + for (var propKey in nextProps) {
210 + var nextProp = nextProps[propKey];
211 + var lastProp = lastProps[propKey];
212 + if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
213 + continue;
214 + }
215 + if (propKey === STYLE) {
216 + if (nextProp) {
217 + nextProp = nextProps.style = merge(nextProp);
218 + }
219 + var styleUpdates;
220 + for (var styleName in nextProp) {
221 + if (!nextProp.hasOwnProperty(styleName)) {
222 + continue;
223 + }
224 + if (!lastProp || lastProp[styleName] !== nextProp[styleName]) {
225 + if (!styleUpdates) {
226 + styleUpdates = {};
227 + }
228 + styleUpdates[styleName] = nextProp[styleName];
229 + }
230 + }
231 + if (styleUpdates) {
232 + ReactComponent.DOMIDOperations.updateStylesByID(
233 + this._rootNodeID,
234 + styleUpdates
235 + );
236 + }
237 + } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
238 + var lastHtml = lastProp && lastProp.__html;
239 + var nextHtml = nextProp && nextProp.__html;
240 + if (lastHtml !== nextHtml) {
241 + ReactComponent.DOMIDOperations.updateInnerHTMLByID(
242 + this._rootNodeID,
243 + nextProp
244 + );
245 + }
246 + } else if (propKey === CONTENT) {
247 + ReactComponent.DOMIDOperations.updateTextContentByID(
248 + this._rootNodeID,
249 + '' + nextProp
250 + );
251 + } else if (registrationNames[propKey]) {
252 + putListener(this._rootNodeID, propKey, nextProp);
253 + } else {
254 + ReactComponent.DOMIDOperations.updatePropertyByID(
255 + this._rootNodeID,
256 + propKey,
257 + nextProp
258 + );
259 + }
260 + }
261 + },
262 +
263 + /**
264 + * Reconciles the children with the various properties that affect the
265 + * children content.
266 + *
267 + * @param {object} nextProps
268 + * @param {ReactReconcileTransaction} transaction
269 + */
270 + _updateDOMChildren: function(nextProps, transaction) {
271 + var thisPropsContentType = typeof this.props.content;
272 + var thisPropsContentEmpty =
273 + this.props.content == null || thisPropsContentType === 'boolean';
274 + var nextPropsContentType = typeof nextProps.content;
275 + var nextPropsContentEmpty =
276 + nextProps.content == null || nextPropsContentType === 'boolean';
277 +
278 + var lastUsedContent = !thisPropsContentEmpty ? this.props.content :
279 + CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
280 +
281 + var contentToUse = !nextPropsContentEmpty ? nextProps.content :
282 + CONTENT_TYPES[typeof nextProps.children] ? nextProps.children : null;
283 +
284 + // Note the use of `!=` which checks for null or undefined.
285 +
286 + var lastUsedChildren =
287 + lastUsedContent != null ? null : this.props.children;
288 + var childrenToUse = contentToUse != null ? null : nextProps.children;
289 +
290 + if (contentToUse != null) {
291 + var childrenRemoved = lastUsedChildren != null && childrenToUse == null;
292 + if (childrenRemoved) {
293 + this.updateMultiChild(null, transaction);
294 + }
295 + if (lastUsedContent !== contentToUse) {
296 + ReactComponent.DOMIDOperations.updateTextContentByID(
297 + this._rootNodeID,
298 + '' + contentToUse
299 + );
300 + }
301 + } else {
302 + var contentRemoved = lastUsedContent != null && contentToUse == null;
303 + if (contentRemoved) {
304 + ReactComponent.DOMIDOperations.updateTextContentByID(
305 + this._rootNodeID,
306 + ''
307 + );
308 + }
309 + this.updateMultiChild(flattenChildren(nextProps.children), transaction);
310 + }
311 + },
312 +
313 + /**
314 + * Destroys all event registrations for this instance. Does not remove from
315 + * the DOM. That must be done by the parent.
316 + *
317 + * @internal
318 + */
319 + unmountComponent: function() {
320 + ReactComponent.Mixin.unmountComponent.call(this);
321 + this.unmountMultiChild();
322 + ReactEvent.deleteAllListeners(this._rootNodeID);
323 + }
324 +
325 +};
326 +
327 +mixInto(ReactNativeComponent, ReactComponent.Mixin);
328 +mixInto(ReactNativeComponent, ReactNativeComponent.Mixin);
329 +mixInto(ReactNativeComponent, ReactMultiChild.Mixin);
330 +
331 +module.exports = ReactNativeComponent;
src/core/ReactOnDOMReady.js new
+95
@@ -0,0 +1,95 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactOnDOMReady
17 + */
18 +
19 +"use strict";
20 +
21 +var PooledClass = require('PooledClass');
22 +
23 +var mixInto = require('mixInto');
24 +
25 +/**
26 + * A specialized pseudo-event module to help keep track of components waiting to
27 + * be notified when their DOM representations are available for use.
28 + *
29 + * This implements `PooledClass`, so you should never need to instantiate this.
30 + * Instead, use `ReactOnDOMReady.getPooled()`.
31 + *
32 + * @param {?array<function>} initialCollection
33 + * @class ReactOnDOMReady
34 + * @implements PooledClass
35 + * @internal
36 + */
37 +function ReactOnDOMReady(initialCollection) {
38 + this._queue = initialCollection || null;
39 +}
40 +
41 +mixInto(ReactOnDOMReady, {
42 +
43 + /**
44 + * Enqueues a callback to be invoked when `notifyAll` is invoked. This is used
45 + * to enqueue calls to `componentDidMount` and `componentDidUpdate`.
46 + *
47 + * @param {ReactComponent} component Component being rendered.
48 + * @param {function(DOMElement)} callback Invoked when `notifyAll` is invoked.
49 + * @internal
50 + */
51 + enqueue: function(component, callback) {
52 + this._queue = this._queue || [];
53 + this._queue.push({component: component, callback: callback});
54 + },
55 +
56 + /**
57 + * Invokes all enqueued callbacks and clears the queue. This is invoked after
58 + * the DOM representation of a component has been created or updated.
59 + *
60 + * @internal
61 + */
62 + notifyAll: function() {
63 + var queue = this._queue;
64 + if (queue) {
65 + this._queue = null;
66 + for (var i = 0, l = queue.length; i < l; i++) {
67 + var component = queue[i].component;
68 + var callback = queue[i].callback;
69 + callback.call(component, component.getDOMNode());
70 + }
71 + queue.length = 0;
72 + }
73 + },
74 +
75 + /**
76 + * Resets the internal queue.
77 + *
78 + * @internal
79 + */
80 + reset: function() {
81 + this._queue = null;
82 + },
83 +
84 + /**
85 + * `PooledClass` looks for this.
86 + */
87 + destructor: function() {
88 + this.reset();
89 + }
90 +
91 +});
92 +
93 +PooledClass.addPoolingTo(ReactOnDOMReady);
94 +
95 +module.exports = ReactOnDOMReady;
src/core/ReactOwner.js new
+146
@@ -0,0 +1,146 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactOwner
17 + */
18 +
19 +"use strict";
20 +
21 +var invariant = require('invariant');
22 +
23 +/**
24 + * ReactOwners are capable of storing references to owned components.
25 + *
26 + * All components are capable of //being// referenced by owner components, but
27 + * only ReactOwner components are capable of //referencing// owned components.
28 + * The named reference is known as a "ref".
29 + *
30 + * Refs are available when mounted and updated during reconciliation.
31 + *
32 + * var MyComponent = React.createClass({
33 + * render: function() {
34 + * return (
35 + * <div onClick={this.handleClick}>
36 + * <CustomComponent ref="custom" />
37 + * </div>
38 + * );
39 + * },
40 + * handleClick: React.autoBind(function() {
41 + * this.refs.custom.handleClick();
42 + * }),
43 + * componentDidMount: function() {
44 + * this.refs.custom.initialize();
45 + * }
46 + * });
47 + *
48 + * Refs should rarely be used. When refs are used, they should only be done to
49 + * control data that is not handled by React's data flow.
50 + *
51 + * @class ReactOwner
52 + */
53 +var ReactOwner = {
54 +
55 + /**
56 + * @param {?object} object
57 + * @return {boolean} True if `object` is a valid owner.
58 + * @final
59 + */
60 + isValidOwner: function(object) {
61 + return !!(
62 + object &&
63 + typeof object.attachRef === 'function' &&
64 + typeof object.detachRef === 'function'
65 + );
66 + },
67 +
68 + /**
69 + * Adds a component by ref to an owner component.
70 + *
71 + * @param {ReactComponent} component Component to reference.
72 + * @param {string} ref Name by which to refer to the component.
73 + * @param {ReactOwner} owner Component on which to record the ref.
74 + * @final
75 + * @internal
76 + */
77 + addComponentAsRefTo: function(component, ref, owner) {
78 + invariant(
79 + ReactOwner.isValidOwner(owner),
80 + 'addComponentAsRefTo(...): Only a ReactOwner can have refs.'
81 + );
82 + owner.attachRef(ref, component);
83 + },
84 +
85 + /**
86 + * Removes a component by ref from an owner component.
87 + *
88 + * @param {ReactComponent} component Component to dereference.
89 + * @param {string} ref Name of the ref to remove.
90 + * @param {ReactOwner} owner Component on which the ref is recorded.
91 + * @final
92 + * @internal
93 + */
94 + removeComponentAsRefFrom: function(component, ref, owner) {
95 + invariant(
96 + ReactOwner.isValidOwner(owner),
97 + 'removeComponentAsRefFrom(...): Only a ReactOwner can have refs.'
98 + );
99 + // Check that `component` is still the current ref because we do not want to
100 + // detach the ref if another component stole it.
101 + if (owner.refs[ref] === component) {
102 + owner.detachRef(ref);
103 + }
104 + },
105 +
106 + /**
107 + * A ReactComponent must mix this in to have refs.
108 + *
109 + * @lends {ReactOwner.prototype}
110 + */
111 + Mixin: {
112 +
113 + /**
114 + * Lazily allocates the refs object and stores `component` as `ref`.
115 + *
116 + * @param {string} ref Reference name.
117 + * @param {component} component Component to store as `ref`.
118 + * @final
119 + * @private
120 + */
121 + attachRef: function(ref, component) {
122 + invariant(
123 + component.isOwnedBy(this),
124 + 'attachRef(%s, ...): Only a component\'s owner can store a ref to it.',
125 + ref
126 + );
127 + var refs = this.refs || (this.refs = {});
128 + refs[ref] = component;
129 + },
130 +
131 + /**
132 + * Detaches a reference name.
133 + *
134 + * @param {string} ref Name to dereference.
135 + * @final
136 + * @private
137 + */
138 + detachRef: function(ref) {
139 + delete this.refs[ref];
140 + }
141 +
142 + }
143 +
144 +};
145 +
146 +module.exports = ReactOwner;
src/core/ReactPropTransferer.js new
+114
@@ -0,0 +1,114 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactPropTransferer
17 + */
18 +
19 +"use strict";
20 +
21 +var emptyFunction = require('emptyFunction');
22 +var joinClasses = require('joinClasses');
23 +var merge = require('merge');
24 +
25 +/**
26 + * Creates a transfer strategy that will merge prop values using the supplied
27 + * `mergeStrategy`. If a prop was previously unset, this just sets it.
28 + *
29 + * @param {function} mergeStrategy
30 + * @return {function}
31 + */
32 +function createTransferStrategy(mergeStrategy) {
33 + return function(props, key, value) {
34 + if (!props.hasOwnProperty(key)) {
35 + props[key] = value;
36 + } else {
37 + props[key] = mergeStrategy(props[key], value);
38 + }
39 + };
40 +}
41 +
42 +/**
43 + * Transfer strategies dictate how props are transferred by `transferPropsTo`.
44 + */
45 +var TransferStrategies = {
46 + /**
47 + * Never transfer the `ref` prop.
48 + */
49 + ref: emptyFunction,
50 + /**
51 + * Transfer the `className` prop by merging them.
52 + */
53 + className: createTransferStrategy(joinClasses),
54 + /**
55 + * Transfer the `style` prop (which is an object) by merging them.
56 + */
57 + style: createTransferStrategy(merge)
58 +};
59 +
60 +/**
61 + * ReactPropTransferer are capable of transferring props to another component
62 + * using a `transferPropsTo` method.
63 + *
64 + * @class ReactPropTransferer
65 + */
66 +var ReactPropTransferer = {
67 +
68 + TransferStrategies: TransferStrategies,
69 +
70 + /**
71 + * @lends {ReactPropTransferer.prototype}
72 + */
73 + Mixin: {
74 +
75 + /**
76 + * Transfer props from this component to a target component.
77 + *
78 + * Props that do not have an explicit transfer strategy will be transferred
79 + * only if the target component does not already have the prop set.
80 + *
81 + * This is usually used to pass down props to a returned root component.
82 + *
83 + * @param {ReactComponent} component Component receiving the properties.
84 + * @return {ReactComponent} The supplied `component`.
85 + * @final
86 + * @protected
87 + */
88 + transferPropsTo: function(component) {
89 + var props = {};
90 + for (var thatKey in component.props) {
91 + if (component.props.hasOwnProperty(thatKey)) {
92 + props[thatKey] = component.props[thatKey];
93 + }
94 + }
95 + for (var thisKey in this.props) {
96 + if (!this.props.hasOwnProperty(thisKey)) {
97 + continue;
98 + }
99 + var transferStrategy = TransferStrategies[thisKey];
100 + if (transferStrategy) {
101 + transferStrategy(props, thisKey, this.props[thisKey]);
102 + } else if (!props.hasOwnProperty(thisKey)) {
103 + props[thisKey] = this.props[thisKey];
104 + }
105 + }
106 + component.props = props;
107 + return component;
108 + }
109 +
110 + }
111 +
112 +};
113 +
114 +module.exports = ReactPropTransferer;
src/core/ReactProps.js new
+157
@@ -0,0 +1,157 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactProps
17 + */
18 +
19 +"use strict";
20 +
21 +var createObjectFrom = require('createObjectFrom');
22 +var invariant = require('invariant');
23 +
24 +/**
25 + * Collection of methods that allow declaration and validation of props that are
26 + * supplied to React components. Example usage:
27 + *
28 + * var Props = require('ReactProps');
29 + * var MyArticle = React.createClass({
30 + * props: {
31 + * // An optional string prop named "description".
32 + * description: Props.string,
33 + *
34 + * // A required enum prop named "category".
35 + * category: Props.oneOf(['News','Photos']).isRequired,
36 + *
37 + * // A prop named "dialog" that requires an instance of Dialog.
38 + * dialog: Props.instanceOf(Dialog).isRequired
39 + * },
40 + * render: function() { ... }
41 + * });
42 + *
43 + * A more formal specification of how these methods are used:
44 + *
45 + * type := array|bool|object|number|string|oneOf([...])|instanceOf(...)
46 + * decl := ReactProps.{type}(.isRequired)?
47 + *
48 + * Each and every declaration produces a function with the same signature. This
49 + * allows the creation of custom validation functions. For example:
50 + *
51 + * var Props = require('ReactProps');
52 + * var MyLink = React.createClass({
53 + * props: {
54 + * // An optional string or URI prop named "href".
55 + * href: function(props, propName, componentName) {
56 + * var propValue = props[propName];
57 + * invariant(
58 + * propValue == null ||
59 + * typeof propValue === string ||
60 + * propValue instanceof URI,
61 + * 'Invalid `%s` supplied to `%s`, expected string or URI.',
62 + * propName,
63 + * componentName
64 + * );
65 + * }
66 + * },
67 + * render: function() { ... }
68 + * });
69 + *
70 + * @internal
71 + */
72 +var Props = {
73 +
74 + array: createPrimitiveTypeChecker('array'),
75 + bool: createPrimitiveTypeChecker('boolean'),
76 + number: createPrimitiveTypeChecker('number'),
77 + object: createPrimitiveTypeChecker('object'),
78 + string: createPrimitiveTypeChecker('string'),
79 +
80 + oneOf: createEnumTypeChecker,
81 +
82 + instanceOf: createInstanceTypeChecker
83 +
84 +};
85 +
86 +var ANONYMOUS = '<<anonymous>>';
87 +
88 +function createPrimitiveTypeChecker(expectedType) {
89 + function validatePrimitiveType(propValue, propName, componentName) {
90 + var propType = typeof propValue;
91 + if (propType === 'object' && Array.isArray(propValue)) {
92 + propType = 'array';
93 + }
94 + invariant(
95 + propType === expectedType,
96 + 'Invalid prop `%s` of type `%s` supplied to `%s`, expected `%s`.',
97 + propName,
98 + propType,
99 + componentName,
100 + expectedType
101 + );
102 + }
103 + return createChainableTypeChecker(validatePrimitiveType);
104 +}
105 +
106 +function createEnumTypeChecker(expectedValues) {
107 + var expectedEnum = createObjectFrom(expectedValues);
108 + function validateEnumType(propValue, propName, componentName) {
109 + invariant(
110 + expectedEnum[propValue],
111 + 'Invalid prop `%s` supplied to `%s`, expected one of %s.',
112 + propName,
113 + componentName,
114 + JSON.stringify(Object.keys(expectedEnum))
115 + );
116 + }
117 + return createChainableTypeChecker(validateEnumType);
118 +}
119 +
120 +function createInstanceTypeChecker(expectedClass) {
121 + function validateInstanceType(propValue, propName, componentName) {
122 + invariant(
123 + propValue instanceof expectedClass,
124 + 'Invalid prop `%s` supplied to `%s`, expected instance of `%s`.',
125 + propName,
126 + componentName,
127 + expectedClass.name || ANONYMOUS
128 + );
129 + }
130 + return createChainableTypeChecker(validateInstanceType);
131 +}
132 +
133 +function createChainableTypeChecker(validate) {
134 + function createTypeChecker(isRequired) {
135 + function checkType(props, propName, componentName) {
136 + var propValue = props[propName];
137 + if (propValue != null) {
138 + // Only validate if there is a value to check.
139 + validate(propValue, propName, componentName || ANONYMOUS);
140 + } else {
141 + invariant(
142 + !isRequired,
143 + 'Required prop `%s` was not specified in `%s`.',
144 + propName,
145 + componentName || ANONYMOUS
146 + );
147 + }
148 + }
149 + if (!isRequired) {
150 + checkType.isRequired = createTypeChecker(true);
151 + }
152 + return checkType;
153 + }
154 + return createTypeChecker(false);
155 +}
156 +
157 +module.exports = Props;
src/core/ReactReconcileTransaction.js new
+161
@@ -0,0 +1,161 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactReconcileTransaction
17 + * @typechecks
18 + */
19 +
20 +"use strict";
21 +
22 +var ExecutionEnvironment = require('ExecutionEnvironment');
23 +var PooledClass = require('PooledClass');
24 +var ReactEvent = require('ReactEvent');
25 +var ReactInputSelection = require('ReactInputSelection');
26 +var ReactOnDOMReady = require('ReactOnDOMReady');
27 +var Transaction = require('Transaction');
28 +
29 +var mixInto = require('mixInto');
30 +
31 +/**
32 + * Ensures that, when possible, the selection range (currently selected text
33 + * input) is not disturbed by performing the transaction.
34 + */
35 +var SELECTION_RESTORATION = {
36 + /**
37 + * @return {Selection} Selection information.
38 + */
39 + initialize: ReactInputSelection.getSelectionInformation,
40 + /**
41 + * @param {Selection} sel Selection information returned from `initialize`.
42 + */
43 + close: ReactInputSelection.restoreSelection
44 +};
45 +
46 +/**
47 + * Suppresses events (blur/focus) that could be inadvertently dispatched due to
48 + * high level DOM manipulations (like temporarily removing a text input from the
49 + * DOM).
50 + */
51 +var EVENT_SUPPRESSION = {
52 + /**
53 + * @return {boolean} The enabled status of `ReactEvent` before the
54 + * reconciliation.
55 + */
56 + initialize: function() {
57 + var currentlyEnabled = ReactEvent.isEnabled();
58 + ReactEvent.setEnabled(false);
59 + return currentlyEnabled;
60 + },
61 +
62 + /**
63 + * @param {boolean} previouslyEnabled The enabled status of `ReactEvent`
64 + * before the reconciliation occured. `close` restores the previous value.
65 + */
66 + close: function(previouslyEnabled) {
67 + ReactEvent.setEnabled(previouslyEnabled);
68 + }
69 +};
70 +
71 +/**
72 + * Provides a `ReactOnDOMReady` queue for collecting `onDOMReady` callbacks
73 + * during the performing of the transaction.
74 + */
75 +var ON_DOM_READY_QUEUEING = {
76 + /**
77 + * Initializes the internal `onDOMReady` queue.
78 + */
79 + initialize: function() {
80 + this.reactOnDOMReady.reset();
81 + },
82 +
83 + /**
84 + * After DOM is flushed, invoke all registered `onDOMReady` callbacks.
85 + */
86 + close: function() {
87 + this.reactOnDOMReady.notifyAll();
88 + }
89 +};
90 +
91 +/**
92 + * Executed within the scope of the `Transaction` instance. Consider these as
93 + * being member methods, but with an implied ordering while being isolated from
94 + * each other.
95 + */
96 +var TRANSACTION_WRAPPERS = [
97 + SELECTION_RESTORATION,
98 + EVENT_SUPPRESSION,
99 + ON_DOM_READY_QUEUEING
100 +];
101 +
102 +/**
103 + * Currently:
104 + * - The order that these are listed in the transaction is critical:
105 + * - Suppresses events.
106 + * - Restores selection range.
107 + *
108 + * Future:
109 + * - Restore document/overflow scroll positions that were unintentionally
110 + * modified via DOM insertions above the top viewport boundary.
111 + * - Implement/integrate with customized constraint based layout system and keep
112 + * track of which dimensions must be remeasured.
113 + *
114 + * @class ReactReconcileTransaction
115 + */
116 +function ReactReconcileTransaction() {
117 + this.reinitializeTransaction();
118 + this.reactOnDOMReady = ReactOnDOMReady.getPooled(null);
119 +}
120 +
121 +var Mixin = {
122 + /**
123 + * @see Transaction
124 + * @abstract
125 + * @final
126 + * @return {array<object>} List of operation wrap proceedures.
127 + * TODO: convert to array<TransactionWrapper>
128 + */
129 + getTransactionWrappers: function() {
130 + if (ExecutionEnvironment.canUseDOM) {
131 + return TRANSACTION_WRAPPERS;
132 + } else {
133 + return [];
134 + }
135 + },
136 +
137 + /**
138 + * @return {object} The queue to collect `onDOMReady` callbacks with.
139 + * TODO: convert to ReactOnDOMReady
140 + */
141 + getReactOnDOMReady: function() {
142 + return this.reactOnDOMReady;
143 + },
144 +
145 + /**
146 + * `PooledClass` looks for this, and will invoke this before allowing this
147 + * instance to be resused.
148 + */
149 + destructor: function() {
150 + ReactOnDOMReady.release(this.reactOnDOMReady);
151 + this.reactOnDOMReady = null;
152 + }
153 +};
154 +
155 +
156 +mixInto(ReactReconcileTransaction, Transaction.Mixin);
157 +mixInto(ReactReconcileTransaction, Mixin);
158 +
159 +PooledClass.addPoolingTo(ReactReconcileTransaction);
160 +
161 +module.exports = ReactReconcileTransaction;
src/core/ReactStateSetters.js new
+111
@@ -0,0 +1,111 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactStateSetters
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactStateSetters = {
22 + /**
23 + * Returns a function that calls the provided function, and uses the result
24 + * of that to set the component's state.
25 + *
26 + * @param {ReactCompositeComponent} component
27 + * @param {function} funcReturningState Returned callback uses this to
28 + * determine how to update state.
29 + * @return {function} callback that when invoked uses funcReturningState to
30 + * determined the object literal to setState.
31 + */
32 + createStateSetter: function(component, funcReturningState) {
33 + return function(a, b, c, d, e, f) {
34 + var partialState = funcReturningState.call(component, a, b, c, d, e, f);
35 + if (partialState) {
36 + component.setState(partialState);
37 + }
38 + };
39 + },
40 +
41 + /**
42 + * Returns a single-argument callback that can be used to update a single
43 + * key in the component's state.
44 + *
45 + * Note: this is memoized function, which makes it inexpensive to call.
46 + *
47 + * @param {ReactCompositeComponent} component
48 + * @param {string} key The key in the state that you should update.
49 + * @return {function} callback of 1 argument which calls setState() with
50 + * the provided keyName and callback argument.
51 + */
52 + createStateKeySetter: function(component, key) {
53 + // Memoize the setters.
54 + var cache = component.__keySetters || (component.__keySetters = {});
55 + return cache[key] || (cache[key] = createStateKeySetter(component, key));
56 + }
57 +};
58 +
59 +function createStateKeySetter(component, key) {
60 + // Partial state is allocated outside of the function closure so it can be
61 + // reused with every call, avoiding memory allocation when this function
62 + // is called.
63 + var partialState = {};
64 + return function stateKeySetter(value) {
65 + partialState[key] = value;
66 + component.setState(partialState);
67 + };
68 +}
69 +
70 +ReactStateSetters.Mixin = {
71 + /**
72 + * Returns a function that calls the provided function, and uses the result
73 + * of that to set the component's state.
74 + *
75 + * For example, these statements are equivalent:
76 + *
77 + * this.setState({x: 1});
78 + * this.createStateSetter(function(xValue) {
79 + * return {x: xValue};
80 + * })(1);
81 + *
82 + * @param {function} funcReturningState Returned callback uses this to
83 + * determine how to update state.
84 + * @return {function} callback that when invoked uses funcReturningState to
85 + * determined the object literal to setState.
86 + */
87 + createStateSetter: function(funcReturningState) {
88 + return ReactStateSetters.createStateSetter(this, funcReturningState);
89 + },
90 +
91 + /**
92 + * Returns a single-argument callback that can be used to update a single
93 + * key in the component's state.
94 + *
95 + * For example, these statements are equivalent:
96 + *
97 + * this.setState({x: 1});
98 + * this.createStateKeySetter('x')(1);
99 + *
100 + * Note: this is memoized function, which makes it inexpensive to call.
101 + *
102 + * @param {string} key The key in the state that you should update.
103 + * @return {function} callback of 1 argument which calls setState() with
104 + * the provided keyName and callback argument.
105 + */
106 + createStateKeySetter: function(key) {
107 + return ReactStateSetters.createStateKeySetter(this, key);
108 + }
109 +};
110 +
111 +module.exports = ReactStateSetters;
src/core/ReactTextComponent.js new
+85
@@ -0,0 +1,85 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactTextComponent
17 + * @typechecks
18 + */
19 +
20 +"use strict";
21 +
22 +var ReactComponent = require('ReactComponent');
23 +
24 +var escapeTextForBrowser = require('escapeTextForBrowser');
25 +var mixInto = require('mixInto');
26 +
27 +/**
28 + * Text nodes violate a couple assumptions that React makes about components:
29 + *
30 + * - When mounting text into the DOM, adjacent text nodes are merged.
31 + * - Text nodes cannot be assigned a React root ID.
32 + *
33 + * This component is used to wrap strings in elements so that they can undergo
34 + * the same reconciliation that is applied to elements.
35 + *
36 + * TODO: Investigate representing React components in the DOM with text nodes.
37 + *
38 + * @class ReactTextComponent
39 + * @extends ReactComponent
40 + * @internal
41 + */
42 +var ReactTextComponent = function(initialText) {
43 + this.construct({text: initialText});
44 +};
45 +
46 +mixInto(ReactTextComponent, ReactComponent.Mixin);
47 +mixInto(ReactTextComponent, {
48 +
49 + /**
50 + * Creates the markup for this text node. This node is not intended to have
51 + * any features besides containing text content.
52 + *
53 + * @param {string} rootID DOM ID of the root node.
54 + * @return {string} Markup for this text node.
55 + * @internal
56 + */
57 + mountComponent: function(rootID) {
58 + ReactComponent.Mixin.mountComponent.call(this, rootID);
59 + return (
60 + '<span id="' + rootID + '">' +
61 + escapeTextForBrowser(this.props.text) +
62 + '</span>'
63 + );
64 + },
65 +
66 + /**
67 + * Updates this component by updating the text content.
68 + *
69 + * @param {object} nextProps Contains the next text content.
70 + * @param {ReactReconcileTransaction} transaction
71 + * @internal
72 + */
73 + receiveProps: function(nextProps, transaction) {
74 + if (nextProps.text !== this.props.text) {
75 + this.props.text = nextProps.text;
76 + ReactComponent.DOMIDOperations.updateTextContentByID(
77 + this._rootNodeID,
78 + nextProps.text
79 + );
80 + }
81 + }
82 +
83 +});
84 +
85 +module.exports = ReactTextComponent;
src/core/__tests__/ReactBind-test.js new
+128
@@ -0,0 +1,128 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +/*global global:true*/
20 +"use strict";
21 +
22 +var mocks = require('mocks');
23 +var React = require('React');
24 +var ReactTestUtils = require('ReactTestUtils');
25 +var reactComponentExpect = require('reactComponentExpect');
26 +
27 +describe('React.autoBind', function() {
28 +
29 + it('Holds reference to instance', function() {
30 +
31 + var mouseDidEnter = mocks.getMockFunction();
32 + var mouseDidLeave = mocks.getMockFunction();
33 + var mouseDidClick = mocks.getMockFunction();
34 + var didBadIdea = mocks.getMockFunction();
35 +
36 + var TestBindComponent = React.createClass({
37 + onMouseEnter: mouseDidEnter,
38 + onMouseLeave: mouseDidLeave,
39 + onClick: React.autoBind(mouseDidClick),
40 +
41 + // autoBind needs to be on the top-level spec.
42 + badIdeas: {
43 + badBind: React.autoBind(didBadIdea)
44 + },
45 +
46 + render: function() {
47 + return (
48 + <div
49 + onMouseEnter={this.onMouseEnter.bind(this)}
50 + onMouseLeave={this.onMouseLeave}
51 + onClick={this.onClick} />
52 + );
53 + }
54 + });
55 +
56 + var instance1 = <TestBindComponent />;
57 + ReactTestUtils.renderIntoDocument(instance1);
58 + var rendered1 = reactComponentExpect(instance1)
59 + .expectRenderedChild()
60 + .instance();
61 +
62 + var instance2 = <TestBindComponent />;
63 + ReactTestUtils.renderIntoDocument(instance2);
64 + var rendered2 = reactComponentExpect(instance2)
65 + .expectRenderedChild()
66 + .instance();
67 +
68 + expect(function() {
69 + var badIdea = instance1.badIdeas.badBind;
70 + badIdea();
71 + }).toThrow();
72 +
73 + expect(instance1.onMouseEnter).toBe(instance2.onMouseEnter);
74 + expect(instance1.onMouseLeave).toBe(instance2.onMouseLeave);
75 + expect(instance1.onClick).not.toBe(instance2.onClick);
76 +
77 + ReactTestUtils.Simulate.click(rendered1);
78 + expect(mouseDidClick.mock.instances.length).toBe(1);
79 + expect(mouseDidClick.mock.instances[0]).toBe(instance1);
80 +
81 + ReactTestUtils.Simulate.click(rendered2);
82 + expect(mouseDidClick.mock.instances.length).toBe(2);
83 + expect(mouseDidClick.mock.instances[1]).toBe(instance2);
84 +
85 + ReactTestUtils.Simulate.mouseOver(rendered1);
86 + expect(mouseDidEnter.mock.instances.length).toBe(1);
87 + expect(mouseDidEnter.mock.instances[0]).toBe(instance1);
88 +
89 + ReactTestUtils.Simulate.mouseOver(rendered2);
90 + expect(mouseDidEnter.mock.instances.length).toBe(2);
91 + expect(mouseDidEnter.mock.instances[1]).toBe(instance2);
92 +
93 + ReactTestUtils.Simulate.mouseOut(rendered1);
94 + expect(mouseDidLeave.mock.instances.length).toBe(1);
95 + expect(mouseDidLeave.mock.instances[0]).toBe(global);
96 +
97 + ReactTestUtils.Simulate.mouseOut(rendered2);
98 + expect(mouseDidLeave.mock.instances.length).toBe(2);
99 + expect(mouseDidLeave.mock.instances[1]).toBe(global);
100 + });
101 +
102 + it('works with mixins', function() {
103 + var mouseDidClick = mocks.getMockFunction();
104 +
105 + var TestMixin = {
106 + onClick: React.autoBind(mouseDidClick)
107 + };
108 +
109 + var TestBindComponent = React.createClass({
110 + mixins: [TestMixin],
111 +
112 + render: function() {
113 + return <div onClick={this.onClick} />;
114 + }
115 + });
116 +
117 + var instance1 = <TestBindComponent />;
118 + ReactTestUtils.renderIntoDocument(instance1);
119 + var rendered1 = reactComponentExpect(instance1)
120 + .expectRenderedChild()
121 + .instance();
122 +
123 + ReactTestUtils.Simulate.click(rendered1);
124 + expect(mouseDidClick.mock.instances.length).toBe(1);
125 + expect(mouseDidClick.mock.instances[0]).toBe(instance1);
126 + });
127 +
128 +});
src/core/__tests__/ReactComponent-test.js new
+98
@@ -0,0 +1,98 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var React;
23 +var ReactTestUtils;
24 +
25 +var reactComponentExpect;
26 +
27 +describe('ReactComponent', function() {
28 + beforeEach(function() {
29 + React = require('React');
30 + ReactTestUtils = require('ReactTestUtils');
31 + reactComponentExpect = require('reactComponentExpect');
32 + });
33 +
34 + it('should throw when supplying a ref outside of render method', function() {
35 + var instance = <div ref="badDiv" />;
36 + expect(function() {
37 + ReactTestUtils.renderIntoDocument(instance);
38 + }).toThrow();
39 + });
40 +
41 + it('should throw when attempting to hijack a ref', function() {
42 + var Component = React.createClass({
43 + render: function() {
44 + var child = this.props.child;
45 + this.attachRef('test', child);
46 + return child;
47 + }
48 + });
49 +
50 + var instance = <Component child={<span />} />;
51 +
52 + expect(function() {
53 + ReactTestUtils.renderIntoDocument(instance);
54 + }).toThrow(
55 + 'Invariant Violation: attachRef(test, ...): Only a component\'s owner ' +
56 + 'can store a ref to it.'
57 + );
58 + });
59 +
60 + it('should support refs on owned components', function() {
61 + var inner, outer;
62 +
63 + var Component = React.createClass({
64 + render: function() {
65 + inner = <div ref="inner" />;
66 + outer = <div ref="outer">{inner}</div>;
67 + return outer;
68 + },
69 + componentDidMount: function() {
70 + expect(this.refs.inner).toEqual(inner);
71 + expect(this.refs.outer).toEqual(outer);
72 + }
73 + });
74 +
75 + var instance = <Component child={<span />} />;
76 + ReactTestUtils.renderIntoDocument(instance);
77 + });
78 +
79 + it('should not have refs on unmounted components', function() {
80 + var Parent = React.createClass({
81 + render: function() {
82 + return <Child><div ref="test" /></Child>;
83 + },
84 + componentDidMount: function() {
85 + expect(this.refs && this.refs.test).toEqual(undefined);
86 + }
87 + });
88 + var Child = React.createClass({
89 + render: function() {
90 + return <div />;
91 + }
92 + });
93 +
94 + var instance = <Parent child={<span />} />;
95 + ReactTestUtils.renderIntoDocument(instance);
96 + });
97 +
98 +});
src/core/__tests__/ReactComponentLifeCycle-test.js new
+433
@@ -0,0 +1,433 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var React;
23 +var ReactTestUtils;
24 +var ReactComponent;
25 +var ReactCompositeComponent;
26 +var ComponentLifeCycle;
27 +var CompositeComponentLifeCycle;
28 +
29 +var clone = function(o) {
30 + return JSON.parse(JSON.stringify(o));
31 +};
32 +
33 +
34 +var GET_INIT_STATE_RETURN_VAL = {
35 + hasWillMountCompleted: false,
36 + hasRenderCompleted: false,
37 + hasDidMountCompleted: false,
38 + hasWillUnmountCompleted: false
39 +};
40 +
41 +var INIT_RENDER_STATE = {
42 + hasWillMountCompleted: true,
43 + hasRenderCompleted: false,
44 + hasDidMountCompleted: false,
45 + hasWillUnmountCompleted: false
46 +};
47 +
48 +var DID_MOUNT_STATE = {
49 + hasWillMountCompleted: true,
50 + hasRenderCompleted: true,
51 + hasDidMountCompleted: false,
52 + hasWillUnmountCompleted: false
53 +};
54 +
55 +var NEXT_RENDER_STATE = {
56 + hasWillMountCompleted: true,
57 + hasRenderCompleted: true,
58 + hasDidMountCompleted: true,
59 + hasWillUnmountCompleted: false
60 +};
61 +
62 +var WILL_UNMOUNT_STATE = {
63 + hasWillMountCompleted: true,
64 + hasDidMountCompleted: true,
65 + hasRenderCompleted: true,
66 + hasWillUnmountCompleted: false
67 +};
68 +
69 +var POST_WILL_UNMOUNT_STATE = {
70 + hasWillMountCompleted: true,
71 + hasDidMountCompleted: true,
72 + hasRenderCompleted: true,
73 + hasWillUnmountCompleted: true
74 +};
75 +
76 +/**
77 + * TODO: We should make any setState calls fail in
78 + * `getInitialState` and `componentWillMount`. They will usually fail
79 + * anyways because `this._renderedComponent` is empty, however, if a component
80 + * is *reused*, then that won't be the case and things will appear to work in
81 + * some cases. Better to just block all updates in initialization.
82 + */
83 +describe('ReactComponentLifeCycle', function() {
84 + beforeEach(function() {
85 + require('mock-modules').dumpCache();
86 + React = require('React');
87 + ReactTestUtils = require('ReactTestUtils');
88 + ReactComponent = require('ReactComponent');
89 + ReactCompositeComponent = require('ReactCompositeComponent');
90 + ComponentLifeCycle = ReactComponent.LifeCycle;
91 + CompositeComponentLifeCycle = ReactCompositeComponent.LifeCycle;
92 + });
93 +
94 + it('should scrub state when reinitialized using getInitialState', function() {
95 + var StatefulComponent = React.createClass({
96 + getInitialState: function() {
97 + return { };
98 + },
99 + addAnotherField: function() {
100 + this.setState({
101 + aField: 'asdf'
102 + });
103 + },
104 + render: function() {
105 + return (
106 + <div> </div>
107 + );
108 + }
109 + });
110 + var instance = <StatefulComponent />;
111 + ReactTestUtils.renderIntoDocument(instance);
112 + instance.addAnotherField();
113 + expect(instance.state.aField).toBe('asdf');
114 + instance.unmountComponent();
115 + ReactTestUtils.renderIntoDocument(instance);
116 + expect(typeof instance.state.aField).toBe('undefined');
117 + });
118 +
119 + /**
120 + * If a state update triggers rerendering that in turn fires an onDOMReady,
121 + * that second onDOMReady should not fail.
122 + */
123 + it('it should fire onDOMReady when already in onDOMReady', function() {
124 +
125 + var _testJournal = [];
126 +
127 + var Child = React.createClass({
128 + componentDidMount: function() {
129 + _testJournal.push('Child:onDOMReady');
130 + },
131 + render: function() {
132 + return <div> </div>;
133 + }
134 + });
135 +
136 + var SwitcherParent = React.createClass({
137 + getInitialState: function() {
138 + _testJournal.push('SwitcherParent:getInitialState');
139 + return {showHasOnDOMReadyComponent: false};
140 + },
141 + componentDidMount: function() {
142 + _testJournal.push('SwitcherParent:onDOMReady');
143 + this.switchIt();
144 + },
145 + switchIt: function() {
146 + this.setState({showHasOnDOMReadyComponent: true});
147 + },
148 + render: function() {
149 + return (
150 + <div>{
151 + this.state.showHasOnDOMReadyComponent ?
152 + <Child /> :
153 + <div> </div>
154 + }</div>
155 + );
156 + }
157 + });
158 +
159 + var instance = <SwitcherParent />;
160 + ReactTestUtils.renderIntoDocument(instance);
161 + expect(_testJournal).toEqual([
162 + 'SwitcherParent:getInitialState',
163 + 'SwitcherParent:onDOMReady',
164 + 'Child:onDOMReady'
165 + ]);
166 + });
167 +
168 +
169 + it('should scrub state when re-initialized', function() {
170 + var StatefulComponent = React.createClass({
171 + addAnotherField: function() {
172 + this.setState({
173 + aField: 'asdf'
174 + });
175 + },
176 + render: function() {
177 + return (
178 + <div> </div>
179 + );
180 + }
181 + });
182 + var instance = <StatefulComponent />;
183 + ReactTestUtils.renderIntoDocument(instance);
184 + instance.addAnotherField();
185 + expect(instance.state.aField).toBe('asdf');
186 + instance.unmountComponent();
187 + ReactTestUtils.renderIntoDocument(instance);
188 + expect(instance.state).toBe(null);
189 + });
190 +
191 + // You could assign state here, but not access members of it, unless you
192 + // had provided a getInitialState method.
193 + it('throws when accessing state in componentWillMount', function() {
194 + var StatefulComponent = React.createClass({
195 + componentWillMount: function() {
196 + this.state.yada;
197 + },
198 + render: function() {
199 + return (
200 + <div> </div>
201 + );
202 + }
203 + });
204 + var instance = <StatefulComponent />;
205 + expect(function() {
206 + ReactTestUtils.renderIntoDocument(instance);
207 + }).toThrow();
208 + });
209 +
210 + it('should allow update state inside of componentWillMount', function() {
211 + var StatefulComponent = React.createClass({
212 + componentWillMount: function() {
213 + this.setState({stateField: 'something'});
214 + },
215 + render: function() {
216 + return (
217 + <div> </div>
218 + );
219 + }
220 + });
221 + var instance = <StatefulComponent />;
222 + expect(function() {
223 + ReactTestUtils.renderIntoDocument(instance);
224 + }).not.toThrow();
225 + });
226 +
227 + it('should allow update state inside of getInitialState', function() {
228 + var StatefulComponent = React.createClass({
229 + getInitialState: function() {
230 + this.setState({stateField: 'something'});
231 +
232 + return {stateField: 'somethingelse'};
233 + },
234 + render: function() {
235 + return (
236 + <div> </div>
237 + );
238 + }
239 + });
240 + var instance = <StatefulComponent />;
241 + expect(function() {
242 + ReactTestUtils.renderIntoDocument(instance);
243 + }).not.toThrow();
244 +
245 + // The return value of getInitialState overrides anything from setState
246 + expect(instance.state.stateField).toEqual('somethingelse');
247 + });
248 +
249 +
250 + it('should carry through each of the phases of setup', function() {
251 + var LifeCycleComponent = React.createClass({
252 + getInitialState: function() {
253 + this._testJournal = {};
254 + var initState = {
255 + hasWillMountCompleted: false,
256 + hasDidMountCompleted: false,
257 + hasRenderCompleted: false,
258 + hasWillUnmountCompleted: false
259 + };
260 + this._testJournal.returnedFromGetInitialState = clone(initState);
261 + this._testJournal.lifeCycleAtStartOfGetInitialState =
262 + this._lifeCycleState;
263 + this._testJournal.compositeLifeCycleAtStartOfGetInitialState =
264 + this._compositeLifeCycleState;
265 + return initState;
266 + },
267 +
268 + componentWillMount: function() {
269 + this._testJournal.stateAtStartOfWillMount = clone(this.state);
270 + this._testJournal.lifeCycleAtStartOfWillMount =
271 + this._lifeCycleState;
272 + this._testJournal.compositeLifeCycleAtStartOfWillMount =
273 + this._compositeLifeCycleState;
274 + this.state.hasWillMountCompleted = true;
275 + },
276 +
277 + componentDidMount: function() {
278 + this._testJournal.stateAtStartOfDidMount = clone(this.state);
279 + this._testJournal.lifeCycleAtStartOfDidMount =
280 + this._lifeCycleState;
281 + this.setState({hasDidMountCompleted: true});
282 + },
283 +
284 + render: function() {
285 + var isInitialRender = !this.state.hasRenderCompleted;
286 + if (isInitialRender) {
287 + this._testJournal.stateInInitialRender = clone(this.state);
288 + this._testJournal.lifeCycleInInitialRender = this._lifeCycleState;
289 + } else {
290 + this._testJournal.stateInLaterRender = clone(this.state);
291 + this._testJournal.lifeCycleInLaterRender = this._lifeCycleState;
292 + }
293 + // you would *NEVER* do anything like this in real code!
294 + this.state.hasRenderCompleted = true;
295 + return (
296 + <div ref="theDiv">
297 + I am the inner DIV
298 + </div>
299 + );
300 + },
301 +
302 + componentWillUnmount: function() {
303 + this._testJournal.stateAtStartOfWillUnmount = clone(this.state);
304 + this._testJournal.lifeCycleAtStartOfWillUnmount =
305 + this._lifeCycleState;
306 + this.state.hasWillUnmountCompleted = true;
307 + }
308 + });
309 +
310 + // A component that is merely "constructed" (as in "constructor") but not
311 + // yet initialized, or rendered.
312 + //
313 + var instance = <LifeCycleComponent />;
314 + expect(instance._lifeCycleState).toBe(ComponentLifeCycle.UNMOUNTED);
315 + ReactTestUtils.renderIntoDocument(instance);
316 +
317 + // getInitialState
318 + expect(instance._testJournal.returnedFromGetInitialState).toEqual(
319 + GET_INIT_STATE_RETURN_VAL
320 + );
321 + expect(instance._testJournal.lifeCycleAtStartOfGetInitialState)
322 + .toBe(ComponentLifeCycle.UNMOUNTED);
323 + expect(instance._testJournal.compositeLifeCycleAtStartOfGetInitialState)
324 + .toBe(CompositeComponentLifeCycle.MOUNTING);
325 +
326 + // componentWillMount
327 + expect(instance._testJournal.stateAtStartOfWillMount).toEqual(
328 + instance._testJournal.returnedFromGetInitialState
329 + );
330 + expect(instance._testJournal.lifeCycleAtStartOfWillMount)
331 + .toBe(ComponentLifeCycle.UNMOUNTED);
332 + expect(instance._testJournal.compositeLifeCycleAtStartOfWillMount)
333 + .toBe(CompositeComponentLifeCycle.MOUNTING);
334 +
335 + // componentDidMount
336 + expect(instance._testJournal.stateAtStartOfDidMount)
337 + .toEqual(DID_MOUNT_STATE);
338 + expect(instance._testJournal.lifeCycleAtStartOfDidMount).toBe(
339 + ComponentLifeCycle.MOUNTED
340 + );
341 +
342 + // render
343 + expect(instance._testJournal.stateInInitialRender)
344 + .toEqual(INIT_RENDER_STATE);
345 + expect(instance._testJournal.lifeCycleInInitialRender).toBe(
346 + ComponentLifeCycle.UNMOUNTED
347 + );
348 +
349 + expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);
350 +
351 + // Now *update the component*
352 + instance.forceUpdate();
353 +
354 + // render 2nd time
355 + expect(instance._testJournal.stateInLaterRender)
356 + .toEqual(NEXT_RENDER_STATE);
357 + expect(instance._testJournal.lifeCycleInLaterRender).toBe(
358 + ComponentLifeCycle.MOUNTED
359 + );
360 +
361 + expect(instance._lifeCycleState).toBe(ComponentLifeCycle.MOUNTED);
362 +
363 + // Now *unmountComponent*
364 + instance.unmountComponent();
365 +
366 + expect(instance._testJournal.stateAtStartOfWillUnmount)
367 + .toEqual(WILL_UNMOUNT_STATE);
368 + // componentWillUnmount called right before unmount.
369 + expect(instance._testJournal.lifeCycleAtStartOfWillUnmount).toBe(
370 + ComponentLifeCycle.MOUNTED
371 + );
372 +
373 + // But the current lifecycle of the component is unmounted.
374 + expect(instance._lifeCycleState).toBe(ComponentLifeCycle.UNMOUNTED);
375 + expect(instance.state).toEqual(POST_WILL_UNMOUNT_STATE);
376 + });
377 +
378 + it('should throw when calling setProps() on an owned component', function() {
379 + /**
380 + * calls setProps in an componentDidMount.
381 + */
382 + var PropsUpdaterInOnDOMReady = React.createClass({
383 + componentDidMount: function() {
384 + this.refs.theSimpleComponent.setProps({
385 + value: this.props.valueToUseInOnDOMReady
386 + });
387 + },
388 + render: function() {
389 + return (
390 + <input
391 + value={this.props.valueToUseInitially}
392 + ref="theSimpleComponent">
393 + </input>
394 + );
395 + }
396 + });
397 + var instance =
398 + <PropsUpdaterInOnDOMReady
399 + valueToUseInitially="hello"
400 + valueToUseInOnDOMReady="goodbye"
401 + />;
402 + expect(ReactTestUtils.renderIntoDocument.bind(ReactTestUtils, instance))
403 + .toThrow();
404 + });
405 +
406 + it('should allow state updates in componentDidMount', function() {
407 + /**
408 + * calls setState in an componentDidMount.
409 + */
410 + var SetStateInComponentDidMount = React.createClass({
411 + getInitialState: function() {
412 + return {
413 + stateField: this.props.valueToUseInitially
414 + };
415 + },
416 + componentDidMount: function() {
417 + this.setState({stateField: this.props.valueToUseInOnDOMReady});
418 + },
419 + render: function() {
420 + return (<div></div>);
421 + }
422 + });
423 + var instance =
424 + <SetStateInComponentDidMount
425 + valueToUseInitially="hello"
426 + valueToUseInOnDOMReady="goodbye"
427 + />;
428 + ReactTestUtils.renderIntoDocument(instance);
429 + expect(instance.state.stateField).toBe('goodbye');
430 + });
431 +
432 +});
433 +
src/core/__tests__/ReactCompositeComponent-test.js new
+226
@@ -0,0 +1,226 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var MorphingComponent;
23 +var MorphingAutoBindComponent;
24 +var ChildUpdates;
25 +var React;
26 +var ReactProps;
27 +var ReactTestUtils;
28 +
29 +var cx;
30 +var reactComponentExpect;
31 +
32 +describe('ReactCompositeComponent', function() {
33 +
34 + beforeEach(function() {
35 + cx = require('cx');
36 + reactComponentExpect = require('reactComponentExpect');
37 + React = require('React');
38 + ReactProps = require('ReactProps');
39 + ReactTestUtils = require('ReactTestUtils');
40 +
41 + MorphingComponent = React.createClass({
42 + getInitialState: function() {
43 + return {activated: false};
44 + },
45 +
46 + _toggleActivatedState: function() {
47 + this.setState({activated: !this.state.activated});
48 + },
49 +
50 + render: function() {
51 + var toggleActivatedState = this._toggleActivatedState.bind(this);
52 + return !this.state.activated ?
53 + <a ref="x" onClick={toggleActivatedState} /> :
54 + <b ref="x" onClick={toggleActivatedState} />;
55 + }
56 + });
57 +
58 + MorphingAutoBindComponent = React.createClass({
59 + getInitialState: function() {
60 + return {activated: false};
61 + },
62 +
63 + _toggleActivatedState: React.autoBind(function() {
64 + this.setState({activated: !this.state.activated});
65 + }),
66 +
67 + render: function() {
68 + return !this.state.activated ?
69 + <a ref="x" onClick={this._toggleActivatedState} /> :
70 + <b ref="x" onClick={this._toggleActivatedState} />;
71 + }
72 + });
73 +
74 + /**
75 + * We'll use this to ensure that an old version is not cached when it is
76 + * reallocated again.
77 + */
78 + ChildUpdates = React.createClass({
79 + getAnchorID: function() {
80 + return this.refs.anch._rootNodeID;
81 + },
82 + render: function() {
83 + var className = cx({'anchorClass': this.props.anchorClassOn});
84 + return this.props.renderAnchor ?
85 + <a ref="anch" className={className}></a> :
86 + <b></b>;
87 + }
88 + });
89 + });
90 +
91 + it('should support rendering to different child types over time', function() {
92 + var instance = <MorphingComponent />;
93 + ReactTestUtils.renderIntoDocument(instance);
94 +
95 + reactComponentExpect(instance)
96 + .expectRenderedChild()
97 + .toBeDOMComponentWithTag('a');
98 +
99 + instance._toggleActivatedState();
100 + reactComponentExpect(instance)
101 + .expectRenderedChild()
102 + .toBeDOMComponentWithTag('b');
103 +
104 + instance._toggleActivatedState();
105 + reactComponentExpect(instance)
106 + .expectRenderedChild()
107 + .toBeDOMComponentWithTag('a');
108 + });
109 +
110 + it('should react to state changes from callbacks', function() {
111 + var instance = <MorphingComponent />;
112 + ReactTestUtils.renderIntoDocument(instance);
113 +
114 + var renderedChild = reactComponentExpect(instance)
115 + .expectRenderedChild()
116 + .instance();
117 +
118 + ReactTestUtils.Simulate.click(renderedChild);
119 + reactComponentExpect(instance)
120 + .expectRenderedChild()
121 + .toBeDOMComponentWithTag('b');
122 + });
123 +
124 + it('should rewire refs when rendering to different child types', function() {
125 + var instance = <MorphingComponent />;
126 + ReactTestUtils.renderIntoDocument(instance);
127 +
128 + reactComponentExpect(instance.refs.x).toBeDOMComponentWithTag('a');
129 + instance._toggleActivatedState();
130 + reactComponentExpect(instance.refs.x).toBeDOMComponentWithTag('b');
131 + instance._toggleActivatedState();
132 + reactComponentExpect(instance.refs.x).toBeDOMComponentWithTag('a');
133 + });
134 +
135 + it('should behave the same with React.autoBind', function() {
136 + var instance = <MorphingAutoBindComponent />;
137 + ReactTestUtils.renderIntoDocument(instance);
138 +
139 + reactComponentExpect(instance)
140 + .expectRenderedChild()
141 + .toBeDOMComponentWithTag('a');
142 +
143 + var renderedChild = reactComponentExpect(instance)
144 + .expectRenderedChild()
145 + .instance();
146 +
147 + ReactTestUtils.Simulate.click(renderedChild);
148 + reactComponentExpect(instance)
149 + .expectRenderedChild()
150 + .toBeDOMComponentWithTag('b');
151 + });
152 +
153 + it('should not cache old DOM nodes when switching constructors', function() {
154 + var instance = <ChildUpdates renderAnchor={true} anchorClassOn={false}/>;
155 + ReactTestUtils.renderIntoDocument(instance);
156 + instance.setProps({anchorClassOn: true}); // Warm any cache
157 + instance.setProps({renderAnchor: false}); // Clear out the anchor
158 + // rerender
159 + instance.setProps({renderAnchor: true, anchorClassOn: false});
160 + var anchorID = instance.getAnchorID();
161 + var actualDOMAnchorNode = document.getElementById(anchorID);
162 + expect(actualDOMAnchorNode.className).toBe('');
163 + });
164 +
165 + it('should auto bind methods and values correctly', function() {
166 + var RETURN_VALUE_AFTER_MOUNT = 'returnValue';
167 + var ComponentClass = React.createClass({
168 + getInitialState: function() {
169 + return {
170 + valueToReturn: RETURN_VALUE_AFTER_MOUNT
171 + };
172 + },
173 + methodBoundOnMount: React.autoBind(function() {
174 + return this.state.valueToReturn;
175 + }),
176 + render: function() {
177 + return <div> </div>;
178 + }
179 + });
180 + var instance = <ComponentClass />;
181 +
182 + // Autobound methods will throw before mounting.
183 + expect(function() {
184 + instance.methodBoundOnMount();
185 + }).toThrow();
186 +
187 + // Next, prove that once mounted, the scope is bound correctly to the actual
188 + // component.
189 + ReactTestUtils.renderIntoDocument(instance);
190 + var retValAfterMount = instance.methodBoundOnMount();
191 + expect(retValAfterMount).toBe(RETURN_VALUE_AFTER_MOUNT);
192 + var retValAfterMountWithCrazyScope =
193 + instance.methodBoundOnMount.call({thisIsACrazyScope:null});
194 + expect(retValAfterMountWithCrazyScope).toBe(RETURN_VALUE_AFTER_MOUNT);
195 + });
196 +
197 + it('should check declared prop types', function() {
198 + var Component = React.createClass({
199 + props: {
200 + key: ReactProps.string.isRequired
201 + },
202 + render: function() {
203 + return <span>{this.props.key}</span>;
204 + }
205 + });
206 +
207 + expect(function() {
208 + ReactTestUtils.renderIntoDocument(<Component />);
209 + }).toThrow(
210 + 'Invariant Violation: Required prop `key` was not specified in ' +
211 + '`Component`.'
212 + );
213 +
214 + expect(function() {
215 + ReactTestUtils.renderIntoDocument(<Component key={42} />);
216 + }).toThrow(
217 + 'Invariant Violation: Invalid prop `key` of type `number` supplied to ' +
218 + '`Component`, expected `string`.'
219 + );
220 +
221 + expect(function() {
222 + ReactTestUtils.renderIntoDocument(<Component key="string" />);
223 + }).not.toThrow();
224 + });
225 +
226 +});
src/core/__tests__/ReactCompositeComponentDOMMinimalism-test.js new
+115
@@ -0,0 +1,115 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +// Requires
23 +var React;
24 +var ReactTestUtils;
25 +var reactComponentExpect;
26 +
27 +// Test components
28 +var LowerLevelComposite;
29 +var MyCompositeComponent;
30 +
31 +var expectSingleChildlessDiv;
32 +
33 +/**
34 + * Integration test, testing the combination of JSX with our unit of
35 + * abstraction, `ReactCompositeComponent` does not ever add superfluous DOM
36 + * nodes.
37 + */
38 +describe('ReactCompositeComponentDOMMinimalism', function() {
39 +
40 + beforeEach(function() {
41 + reactComponentExpect = require('reactComponentExpect');
42 + React = require('React');
43 + ReactTestUtils = require('ReactTestUtils');
44 +
45 + LowerLevelComposite = React.createClass({
46 + render: function() {
47 + return (
48 + <div>
49 + {this.props.children}
50 + </div>
51 + );
52 + }
53 + });
54 +
55 + MyCompositeComponent = React.createClass({
56 + render: function() {
57 + return (
58 + <LowerLevelComposite>
59 + {this.props.children}
60 + </LowerLevelComposite>
61 + );
62 + }
63 + });
64 +
65 + expectSingleChildlessDiv = function(instance) {
66 + reactComponentExpect(instance)
67 + .expectRenderedChild()
68 + .toBeCompositeComponentWithType(LowerLevelComposite)
69 + .expectRenderedChild()
70 + .toBeDOMComponentWithTag('div')
71 + .toBeDOMComponentWithNoChildren();
72 + };
73 + });
74 +
75 + it('should not render extra nodes for non-interpolated text', function() {
76 + var instance = (
77 + <MyCompositeComponent>
78 + A string child
79 + </MyCompositeComponent>
80 + );
81 + ReactTestUtils.renderIntoDocument(instance);
82 + expectSingleChildlessDiv(instance);
83 + });
84 +
85 + it('should not render extra nodes for non-interpolated text', function() {
86 + var instance = (
87 + <MyCompositeComponent>
88 + {'Interpolated String Child'}
89 + </MyCompositeComponent>
90 + );
91 + ReactTestUtils.renderIntoDocument(instance);
92 + expectSingleChildlessDiv(instance);
93 + });
94 +
95 + it('should not render extra nodes for non-interpolated text', function() {
96 + var instance = (
97 + <MyCompositeComponent>
98 + <ul>
99 + This text causes no children in ul, just innerHTML
100 + </ul>
101 + </MyCompositeComponent>
102 + );
103 + ReactTestUtils.renderIntoDocument(instance);
104 + reactComponentExpect(instance)
105 + .expectRenderedChild()
106 + .toBeCompositeComponentWithType(LowerLevelComposite)
107 + .expectRenderedChild()
108 + .toBeDOMComponentWithTag('div')
109 + .toBeDOMComponentWithChildCount(1)
110 + .expectRenderedChildAt(0)
111 + .toBeDOMComponentWithTag('ul')
112 + .toBeDOMComponentWithNoChildren();
113 + });
114 +
115 +});
src/core/__tests__/ReactCompositeComponentMixin-test.js new
+82
@@ -0,0 +1,82 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var mocks = require('mocks');
23 +
24 +var React;
25 +var ReactTestUtils;
26 +var reactComponentExpect;
27 +
28 +var TestComponent;
29 +
30 +describe('ReactCompositeComponent-mixin', function() {
31 +
32 + beforeEach(function() {
33 + React = require('React');
34 + ReactTestUtils = require('ReactTestUtils');
35 + reactComponentExpect = require('reactComponentExpect');
36 +
37 + var MixinA = {
38 + componentDidMount: function() {
39 + this.props.listener('MixinA didMount');
40 + }
41 + };
42 +
43 + var MixinB = {
44 + mixins: [MixinA],
45 + componentDidMount: function() {
46 + this.props.listener('MixinB didMount');
47 + }
48 + };
49 +
50 + var MixinC = {
51 + componentDidMount: function() {
52 + this.props.listener('MixinC didMount');
53 + }
54 + };
55 +
56 + TestComponent = React.createClass({
57 + mixins: [MixinB, MixinC],
58 +
59 + componentDidMount: function() {
60 + this.props.listener('Component didMount');
61 + },
62 +
63 + render: function() {
64 + return <div />;
65 + }
66 + });
67 +
68 + });
69 +
70 + it('should support chaining delegate functions', function() {
71 + var listener = mocks.getMockFunction();
72 + var instance = <TestComponent listener={listener} />;
73 + ReactTestUtils.renderIntoDocument(instance);
74 +
75 + expect(listener.mock.calls).toEqual([
76 + ['MixinA didMount'],
77 + ['MixinB didMount'],
78 + ['MixinC didMount'],
79 + ['Component didMount']
80 + ]);
81 + });
82 +});
src/core/__tests__/ReactCompositeComponentSpec-test.js new
+71
@@ -0,0 +1,71 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var mocks = require('mocks');
23 +
24 +var React;
25 +var ReactTestUtils;
26 +var reactComponentExpect;
27 +
28 +describe('ReactCompositeComponent-spec', function() {
29 +
30 + beforeEach(function() {
31 + React = require('React');
32 + ReactTestUtils = require('ReactTestUtils');
33 + reactComponentExpect = require('reactComponentExpect');
34 + });
35 +
36 + it('should throw when `render` is not specified', function() {
37 + expect(function() {
38 + React.createClass({});
39 + }).toThrow(
40 + 'Invariant Violation: createClass(...): Class specification must ' +
41 + 'implement a `render` method.'
42 + );
43 + });
44 +
45 + it('should copy `displayName` onto the Constructor', function() {
46 + var TestComponent = React.createClass({
47 + render: function() {
48 + return <div />;
49 + }
50 + });
51 +
52 + expect(TestComponent.componentConstructor.displayName)
53 + .toBe('TestComponent');
54 + });
55 +
56 + it('should copy prop declarations onto the Constructor', function() {
57 + var propValidator = mocks.getMockFunction();
58 + var TestComponent = React.createClass({
59 + props: {
60 + value: propValidator
61 + },
62 + render: function() {
63 + return <div />;
64 + }
65 + });
66 +
67 + expect(TestComponent.componentConstructor.propDeclarations).toBeDefined();
68 + expect(TestComponent.componentConstructor.propDeclarations.value)
69 + .toBe(propValidator);
70 + });
71 +});
src/core/__tests__/ReactCompositeComponentState-test.js new
+169
@@ -0,0 +1,169 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var mocks = require('mocks');
23 +
24 +var React;
25 +var ReactTestUtils;
26 +var reactComponentExpect;
27 +
28 +var TestComponent;
29 +
30 +describe('ReactCompositeComponent-state', function() {
31 +
32 + beforeEach(function() {
33 + React = require('React');
34 + ReactTestUtils = require('ReactTestUtils');
35 + reactComponentExpect = require('reactComponentExpect');
36 +
37 + TestComponent = React.createClass({
38 + peekAtState: function(from, state) {
39 + if (state) {
40 + this.props.stateListener(from, state && state.color);
41 + } else {
42 + this.props.stateListener(
43 + from,
44 + this.state && this.state.color,
45 + this._pendingState && this._pendingState.color
46 + );
47 + }
48 + },
49 +
50 + setFavoriteColor: function(nextColor) {
51 + this.setState({color: nextColor});
52 + },
53 +
54 + getInitialState: function() {
55 + this.peekAtState('getInitialState');
56 + return {color: 'red'};
57 + },
58 +
59 + render: function() {
60 + this.peekAtState('render');
61 + return <div>{this.state.color}</div>;
62 + },
63 +
64 + componentWillMount: function() {
65 + this.peekAtState('componentWillMount-start');
66 + this.setState({color: 'sunrise'});
67 + this.peekAtState('componentWillMount-after-sunrise');
68 + this.setState({color: 'orange'});
69 + this.peekAtState('componentWillMount-end');
70 + },
71 +
72 + componentDidMount: function() {
73 + this.peekAtState('componentDidMount-start');
74 + this.setState({color: 'yellow'});
75 + this.peekAtState('componentDidMount-end');
76 + },
77 +
78 + componentWillReceiveProps: function(newProps) {
79 + this.peekAtState('componentWillReceiveProps-start');
80 + if (newProps.nextColor) {
81 + this.setState({color: newProps.nextColor});
82 + }
83 + this.peekAtState('componentWillReceiveProps-end');
84 + },
85 +
86 + shouldComponentUpdate: function(nextProps, nextState) {
87 + this.peekAtState('shouldComponentUpdate-currentState');
88 + this.peekAtState('shouldComponentUpdate-nextState', nextState);
89 + return true;
90 + },
91 +
92 + componentWillUpdate: function(nextProps, nextState) {
93 + this.peekAtState('componentWillUpdate-currentState');
94 + this.peekAtState('componentWillUpdate-nextState', nextState);
95 + },
96 +
97 + componentDidUpdate: function(prevProps, prevState) {
98 + this.peekAtState('componentDidUpdate-currentState');
99 + this.peekAtState('componentDidUpdate-prevState', prevState);
100 + },
101 +
102 + componentWillUnmount: function() {
103 + this.peekAtState('componentWillUnmount');
104 + }
105 + });
106 +
107 + });
108 +
109 + it('should support setting state', function() {
110 + var stateListener = mocks.getMockFunction();
111 + var instance = <TestComponent stateListener={stateListener} />;
112 + ReactTestUtils.renderIntoDocument(instance);
113 + instance.setProps({nextColor: 'green'});
114 + instance.setFavoriteColor('blue');
115 + instance.forceUpdate();
116 + instance.unmountComponent();
117 +
118 + expect(stateListener.mock.calls).toEqual([
119 + // there is no state when getInitialState() is called
120 + [ 'getInitialState', null, null ],
121 + [ 'componentWillMount-start', 'red', null ],
122 + // setState() only enqueues a pending state.
123 + [ 'componentWillMount-after-sunrise', 'red', 'sunrise' ],
124 + [ 'componentWillMount-end', 'red', 'orange' ],
125 + // pending state has been applied
126 + [ 'render', 'orange', null ],
127 + [ 'componentDidMount-start', 'orange', null ],
128 + // componentDidMount() called setState({color:'yellow'}), currently this
129 + // occurs inline.
130 + // In a future where setState() is async, this test result will change.
131 + [ 'shouldComponentUpdate-currentState', 'orange', null ],
132 + [ 'shouldComponentUpdate-nextState', 'yellow' ],
133 + [ 'componentWillUpdate-currentState', 'orange', null ],
134 + [ 'componentWillUpdate-nextState', 'yellow' ],
135 + [ 'render', 'yellow', null ],
136 + [ 'componentDidUpdate-currentState', 'yellow', null ],
137 + [ 'componentDidUpdate-prevState', 'orange' ],
138 + // componentDidMount() finally closes.
139 + [ 'componentDidMount-end', 'yellow', null ],
140 + [ 'componentWillReceiveProps-start', 'yellow', null ],
141 + // setState({color:'green'}) only enqueues a pending state.
142 + [ 'componentWillReceiveProps-end', 'yellow', 'green' ],
143 + [ 'shouldComponentUpdate-currentState', 'yellow', null ],
144 + [ 'shouldComponentUpdate-nextState', 'green' ],
145 + [ 'componentWillUpdate-currentState', 'yellow', null ],
146 + [ 'componentWillUpdate-nextState', 'green' ],
147 + [ 'render', 'green', null ],
148 + [ 'componentDidUpdate-currentState', 'green', null ],
149 + [ 'componentDidUpdate-prevState', 'yellow' ],
150 + // setFavoriteColor('blue')
151 + [ 'shouldComponentUpdate-currentState', 'green', null ],
152 + [ 'shouldComponentUpdate-nextState', 'blue' ],
153 + [ 'componentWillUpdate-currentState', 'green', null ],
154 + [ 'componentWillUpdate-nextState', 'blue' ],
155 + [ 'render', 'blue', null ],
156 + [ 'componentDidUpdate-currentState', 'blue', null ],
157 + [ 'componentDidUpdate-prevState', 'green' ],
158 + // forceUpdate()
159 + [ 'componentWillUpdate-currentState', 'blue', null ],
160 + [ 'componentWillUpdate-nextState', 'blue' ],
161 + [ 'render', 'blue', null ],
162 + [ 'componentDidUpdate-currentState', 'blue', null ],
163 + [ 'componentDidUpdate-prevState', 'blue' ],
164 + // unmountComponent()
165 + // state is available within `componentWillUnmount()`
166 + [ 'componentWillUnmount', 'blue', null ]
167 + ]);
168 + });
169 +});
src/core/__tests__/ReactCompositeComponentTransferProps-test.js new
+114
@@ -0,0 +1,114 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var React;
23 +var ReactTestUtils;
24 +var reactComponentExpect;
25 +
26 +var TestComponent;
27 +
28 +describe('ReactCompositeComponent-transferProps', function() {
29 +
30 + beforeEach(function() {
31 + React = require('React');
32 + ReactTestUtils = require('ReactTestUtils');
33 + reactComponentExpect = require('reactComponentExpect');
34 +
35 + TestComponent = React.createClass({
36 + render: function() {
37 + return this.transferPropsTo(
38 + <input
39 + className="textinput"
40 + style={{display: 'block'}}
41 + type="text"
42 + value=""
43 + />
44 + );
45 + }
46 + });
47 + });
48 +
49 + it('should leave explicitly specified properties intact', function() {
50 + var instance = <TestComponent type="radio" />;
51 + ReactTestUtils.renderIntoDocument(instance);
52 +
53 + reactComponentExpect(instance)
54 + .expectRenderedChild(instance)
55 + .toBeDOMComponentWithTag('input')
56 + .scalarPropsEqual({
57 + className: 'textinput',
58 + style: {display: 'block'},
59 + type: 'text',
60 + value: ''
61 + });
62 + });
63 +
64 + it('should transfer unspecified properties', function() {
65 + var instance = <TestComponent placeholder="Type here..." />;
66 + ReactTestUtils.renderIntoDocument(instance);
67 +
68 + reactComponentExpect(instance)
69 + .expectRenderedChild(instance)
70 + .toBeDOMComponentWithTag('input')
71 + .scalarPropsEqual({placeholder: 'Type here...'});
72 + });
73 +
74 + it('should transfer using merge strategies', function() {
75 + var instance =
76 + <TestComponent
77 + className="hidden_elem"
78 + style={{width: '100%'}}
79 + />;
80 + ReactTestUtils.renderIntoDocument(instance);
81 +
82 + reactComponentExpect(instance)
83 + .expectRenderedChild(instance)
84 + .toBeDOMComponentWithTag('input')
85 + .scalarPropsEqual({
86 + className: 'textinput hidden_elem',
87 + style: {
88 + display: 'block',
89 + width: '100%'
90 + }
91 + });
92 + });
93 +
94 + it('should not transfer ref', function() {
95 + var RefTestComponent = React.createClass({
96 + render: function() {
97 + expect(this.props.ref).toBeUndefined();
98 + return <div />;
99 + }
100 + });
101 + var OuterRefTestComponent = React.createClass({
102 + render: function() {
103 + return this.transferPropsTo(<RefTestComponent />);
104 + }
105 + });
106 + var OuterOuterRefTestComponent = React.createClass({
107 + render: function() {
108 + return <OuterRefTestComponent ref="testref" />;
109 + }
110 + });
111 +
112 + ReactTestUtils.renderIntoDocument(<OuterOuterRefTestComponent />);
113 + });
114 +});
src/core/__tests__/ReactDOM-test.js new
+112
@@ -0,0 +1,112 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var React = require('React');
23 +var ReactDOM = require('ReactDOM');
24 +var ReactTestUtils = require('ReactTestUtils');
25 +var React = require('React');
26 +
27 +describe('ref swapping', function() {
28 + // TODO: uncomment this test once we can run in phantom, which
29 + // supports real submit events.
30 + /*
31 + it('should bubble onSubmit', function() {
32 + var count = 0;
33 + var form;
34 + var Parent = React.createClass({
35 + handleSubmit: function() {
36 + count++;
37 + return false;
38 + },
39 + render: function() {
40 + return <Child />;
41 + }
42 + });
43 + var Child = React.createClass({
44 + render: function() {
45 + return <form><input type="submit" value="Submit" /></form>;
46 + },
47 + componentDidMount: function(node) {
48 + form = node;
49 + }
50 + });
51 + var instance = ReactTestUtils.renderIntoDocument(<Parent />);
52 + form.submit();
53 + expect(count).toEqual(1);
54 + });
55 + */
56 +
57 + it("should allow children to be passed as an argument", function() {
58 + var argDiv = ReactTestUtils.renderIntoDocument(
59 + ReactDOM.div(null, 'child')
60 + );
61 + var argNode = document.getElementById(argDiv._rootNodeID);
62 + expect(argNode.innerHTML).toBe('child');
63 + });
64 +
65 + it("should overwrite props.children with children argument", function() {
66 + var conflictDiv = ReactTestUtils.renderIntoDocument(
67 + ReactDOM.div({children: 'fakechild'}, 'child')
68 + );
69 + var conflictNode = document.getElementById(conflictDiv._rootNodeID);
70 + expect(conflictNode.innerHTML).toBe('child');
71 + });
72 +
73 + /**
74 + * We need to make sure that updates occur to the actual node that's in the
75 + * DOM, instead of a stale cache.
76 + */
77 + it("should purge the DOM cache when removing nodes", function() {
78 + var myDiv = ReactTestUtils.renderIntoDocument(
79 + <div>{{
80 + theDog: <div class="dog" />,
81 + theBird: <div class="bird" />
82 + }}</div>
83 + );
84 + myDiv.setProps({
85 + children: {
86 + theDog: <div class="dogbeforedelete" />, // Warm the cache with theDog
87 + theBird: <div class="bird" />
88 + }
89 + });
90 + myDiv.setProps({
91 + children: { // This better purge that cache
92 + theBird: <div class="bird" />
93 + }
94 + });
95 + // Now, put the dog back.
96 + myDiv.setProps({
97 + children: {
98 + theDog: <div class="dog" />, // This is a different node than before
99 + theBird: <div class="bird" />
100 + }
101 + });
102 + myDiv.setProps({
103 + children: { // className changed to bigdog.
104 + theDog: <div class="bigdog" />, // but will it use the proper element
105 + theBird: <div class="bird" />
106 + }
107 + });
108 + var root = document.getElementById(myDiv._rootNodeID);
109 + var dog = root.childNodes[0];
110 + expect(dog.className).toBe('bigdog');
111 + });
112 +});
src/core/__tests__/ReactDOMIDOperations-test.js new
+65
@@ -0,0 +1,65 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +/*jslint evil: true */
20 +
21 +"use strict";
22 +
23 +describe('ReactDOMIDOperations', function() {
24 + var DOMPropertyOperations = require('DOMPropertyOperations');
25 + var ReactDOMIDOperations = require('ReactDOMIDOperations');
26 + var ReactDOMNodeCache = require('ReactDOMNodeCache');
27 + var keyOf = require('keyOf');
28 +
29 + it('should disallow updating special properties', function() {
30 + spyOn(ReactDOMNodeCache, "getCachedNodeByID");
31 + spyOn(DOMPropertyOperations, "setValueForProperty");
32 +
33 + expect(function() {
34 + ReactDOMIDOperations.updatePropertyByID(
35 + 'testID',
36 + keyOf({content: null}),
37 + 'testContent'
38 + );
39 + }).toThrow();
40 +
41 + expect(
42 + ReactDOMNodeCache.getCachedNodeByID.argsForCall[0][0]
43 + ).toBe('testID');
44 +
45 + expect(
46 + DOMPropertyOperations.setValueForProperty.callCount
47 + ).toBe(0);
48 + });
49 +
50 + it('should update innerHTML and special-case whitespace', function() {
51 + var stubNode = document.createElement('div');
52 + spyOn(ReactDOMNodeCache, "getCachedNodeByID").andReturn(stubNode);
53 +
54 + ReactDOMIDOperations.updateInnerHTMLByID(
55 + 'testID',
56 + {__html: ' testContent'}
57 + );
58 +
59 + expect(
60 + ReactDOMNodeCache.getCachedNodeByID.argsForCall[0][0]
61 + ).toBe('testID');
62 +
63 + expect(stubNode.innerHTML).toBe('&nbsp;testContent');
64 + });
65 +});
src/core/__tests__/ReactEvent-test.js new
+335
@@ -0,0 +1,335 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +require('mock-modules')
22 + .dontMock('BrowserScroll')
23 + .dontMock('CallbackRegistry')
24 + .dontMock('EventPluginHub')
25 + .dontMock('ReactEvent')
26 + .dontMock('ReactInstanceHandles')
27 + .dontMock('EventPluginHub')
28 + .dontMock('TapEventPlugin')
29 + .dontMock('TouchEventUtils')
30 + .dontMock('keyOf');
31 +
32 +
33 +var keyOf = require('keyOf');
34 +var mocks = require('mocks');
35 +
36 +var EventPluginHub;
37 +var ReactEvent;
38 +var ReactEventTopLevelCallback;
39 +var ReactTestUtils;
40 +var TapEventPlugin;
41 +
42 +var tapMoveThreshold;
43 +var idCallOrder = [];
44 +var recordID = function(id) {
45 + idCallOrder.push(id);
46 +};
47 +var recordIDAndStopPropagation = function(id, event) {
48 + recordID(id);
49 + event.stopPropagation();
50 +};
51 +var recordIDAndReturnFalse = function(id, event) {
52 + recordID(id);
53 + return false;
54 +};
55 +var LISTENER = mocks.getMockFunction();
56 +var ON_CLICK_KEY = keyOf({onClick: null});
57 +var ON_TOUCH_TAP_KEY = keyOf({onTouchTap: null});
58 +
59 +
60 +/**
61 + * Since `ReactEvent` is fairly well separated from the DOM, we can test almost
62 + * all of `ReactEvent` without ever rendering anything in the DOM. As long as we
63 + * provide IDs that follow `React's` conventional id namespace hierarchy.
64 + * The only reason why we create these DOM nodes, is so that when we feed them
65 + * into `ReactEvent` (through `ReactTestUtils`), the event handlers may receive
66 + * a DOM node to inspect.
67 + */
68 +var CHILD = document.createElement('div');
69 +var PARENT = document.createElement('div');
70 +var GRANDPARENT = document.createElement('div');
71 +CHILD.id = '.reactRoot.[0].[0].[0]';
72 +PARENT.id = '.reactRoot.[0].[0]';
73 +GRANDPARENT.id = '.reactRoot.[0]';
74 +
75 +function registerSimpleTestHandler() {
76 + ReactEvent.putListener(CHILD.id, ON_CLICK_KEY, LISTENER);
77 + var listener = ReactEvent.getListener(CHILD.id, ON_CLICK_KEY);
78 + expect(listener).toEqual(LISTENER);
79 + return ReactEvent.getListener(CHILD.id, ON_CLICK_KEY);
80 +}
81 +
82 +
83 +describe('ReactEvent', function() {
84 + beforeEach(function() {
85 + require('mock-modules').dumpCache();
86 + EventPluginHub = require('EventPluginHub');
87 + TapEventPlugin = require('TapEventPlugin');
88 + ReactEvent = require('ReactEvent');
89 + ReactTestUtils = require('ReactTestUtils');
90 + ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
91 + idCallOrder = [];
92 + tapMoveThreshold = TapEventPlugin.tapMoveThreshold;
93 + ReactEvent.ensureListening(false, ReactEventTopLevelCallback);
94 + EventPluginHub.injection.injectEventPluginsByName({
95 + TapEventPlugin: TapEventPlugin
96 + });
97 + });
98 +
99 + it('should store a listener correctly', function() {
100 + registerSimpleTestHandler();
101 + var listener = ReactEvent.getListener(CHILD.id, ON_CLICK_KEY);
102 + expect(listener).toBe(LISTENER);
103 + });
104 +
105 + it('should retrieve a listener correctly', function() {
106 + registerSimpleTestHandler();
107 + var listener = ReactEvent.getListener(CHILD.id, ON_CLICK_KEY);
108 + expect(listener).toEqual(LISTENER);
109 + });
110 +
111 + it('should clear all handlers when asked to', function() {
112 + registerSimpleTestHandler();
113 + ReactEvent.deleteAllListeners(CHILD.id);
114 + var listener = ReactEvent.getListener(CHILD.id, ON_CLICK_KEY);
115 + expect(listener).toBe(undefined);
116 + });
117 +
118 + it('should invoke a simple handler registered on a node', function() {
119 + registerSimpleTestHandler();
120 + ReactTestUtils.Simulate.click(CHILD);
121 + expect(LISTENER.mock.calls.length).toBe(1);
122 + });
123 +
124 + it('should not invoke handlers when ReactEvent is disabled', function() {
125 + registerSimpleTestHandler();
126 + ReactEvent.setEnabled(false);
127 + ReactTestUtils.Simulate.click(CHILD);
128 + expect(LISTENER.mock.calls.length).toBe(0);
129 + ReactEvent.setEnabled(true);
130 + ReactTestUtils.Simulate.click(CHILD);
131 + expect(LISTENER.mock.calls.length).toBe(1);
132 + });
133 +
134 + it('should bubble simply', function() {
135 + ReactEvent.putListener(
136 + CHILD.id,
137 + ON_CLICK_KEY,
138 + recordID.bind(null, CHILD.id)
139 + );
140 + ReactEvent.putListener(
141 + PARENT.id,
142 + ON_CLICK_KEY,
143 + recordID.bind(null, PARENT.id)
144 + );
145 + ReactEvent.putListener(
146 + GRANDPARENT.id,
147 + ON_CLICK_KEY,
148 + recordID.bind(null, GRANDPARENT.id)
149 + );
150 + ReactTestUtils.Simulate.click(CHILD);
151 + expect(idCallOrder.length).toBe(3);
152 + expect(idCallOrder[0]).toBe(CHILD.id);
153 + expect(idCallOrder[1]).toBe(PARENT.id);
154 + expect(idCallOrder[2]).toBe(GRANDPARENT.id);
155 + });
156 +
157 + it('should support stopPropagation()', function() {
158 + ReactEvent.putListener(
159 + CHILD.id,
160 + ON_CLICK_KEY,
161 + recordID.bind(null, CHILD.id)
162 + );
163 + ReactEvent.putListener(
164 + PARENT.id,
165 + ON_CLICK_KEY,
166 + recordIDAndStopPropagation.bind(null, PARENT.id)
167 + );
168 + ReactEvent.putListener(
169 + GRANDPARENT.id,
170 + ON_CLICK_KEY,
171 + recordID.bind(null, GRANDPARENT.id)
172 + );
173 + ReactTestUtils.Simulate.click(CHILD);
174 + expect(idCallOrder.length).toBe(2);
175 + expect(idCallOrder[0]).toBe(CHILD.id);
176 + expect(idCallOrder[1]).toBe(PARENT.id);
177 + });
178 +
179 + it('should stop after first dispatch if stopPropagation', function() {
180 + ReactEvent.putListener(
181 + CHILD.id,
182 + ON_CLICK_KEY,
183 + recordIDAndStopPropagation.bind(null, CHILD.id)
184 + );
185 + ReactEvent.putListener(
186 + PARENT.id,
187 + ON_CLICK_KEY,
188 + recordID.bind(null, PARENT.id)
189 + );
190 + ReactEvent.putListener(
191 + GRANDPARENT.id,
192 + ON_CLICK_KEY,
193 + recordID.bind(null, GRANDPARENT.id)
194 + );
195 + ReactTestUtils.Simulate.click(CHILD);
196 + expect(idCallOrder.length).toBe(1);
197 + expect(idCallOrder[0]).toBe(CHILD.id);
198 + });
199 +
200 + it('should stopPropagation if false is returned', function() {
201 + ReactEvent.putListener(
202 + CHILD.id,
203 + ON_CLICK_KEY,
204 + recordIDAndReturnFalse.bind(null, CHILD.id)
205 + );
206 + ReactEvent.putListener(
207 + PARENT.id,
208 + ON_CLICK_KEY,
209 + recordID.bind(null, PARENT.id)
210 + );
211 + ReactEvent.putListener(
212 + GRANDPARENT.id,
213 + ON_CLICK_KEY,
214 + recordID.bind(null, GRANDPARENT.id)
215 + );
216 + ReactTestUtils.Simulate.click(CHILD);
217 + expect(idCallOrder.length).toBe(1);
218 + expect(idCallOrder[0]).toBe(CHILD.id);
219 + });
220 +
221 + /**
222 + * The entire event registration state of the world should be "locked-in" at
223 + * the time the event occurs. This is to resolve many edge cases that come
224 + * about from a listener on a lower-in-DOM node causing structural changes at
225 + * places higher in the DOM. If this lower-in-DOM node causes new content to
226 + * be rendered at a place higher-in-DOM, we need to be careful not to invoke
227 + * these new listeners.
228 + */
229 +
230 + it('should invoke handlers that were removed while bubbling', function() {
231 + var handleParentClick = mocks.getMockFunction();
232 + var handleChildClick = function(abstractEvent) {
233 + ReactEvent.deleteAllListeners(PARENT.id);
234 + };
235 + ReactEvent.putListener(CHILD.id, ON_CLICK_KEY, handleChildClick);
236 + ReactEvent.putListener(PARENT.id, ON_CLICK_KEY, handleParentClick);
237 + ReactTestUtils.Simulate.click(CHILD);
238 + expect(handleParentClick.mock.calls.length).toBe(1);
239 + });
240 +
241 + it('should not invoke newly inserted handlers while bubbling', function() {
242 + var handleParentClick = mocks.getMockFunction();
243 + var handleChildClick = function(abstractEvent) {
244 + ReactEvent.putListener(PARENT.id, ON_CLICK_KEY, handleParentClick);
245 + };
246 + ReactEvent.putListener(CHILD.id, ON_CLICK_KEY, handleChildClick);
247 + ReactTestUtils.Simulate.click(CHILD);
248 + expect(handleParentClick.mock.calls.length).toBe(0);
249 + });
250 +
251 + it('should infer onTouchTap from a touchStart/End', function() {
252 + ReactEvent.putListener(
253 + CHILD.id,
254 + ON_TOUCH_TAP_KEY,
255 + recordID.bind(null, CHILD.id)
256 + );
257 + ReactTestUtils.Simulate.touchStart(
258 + CHILD,
259 + ReactTestUtils.nativeTouchData(0, 0)
260 + );
261 + ReactTestUtils.Simulate.touchEnd(
262 + CHILD,
263 + ReactTestUtils.nativeTouchData(0, 0)
264 + );
265 + expect(idCallOrder.length).toBe(1);
266 + expect(idCallOrder[0]).toBe(CHILD.id);
267 + });
268 +
269 + it('should infer onTouchTap from when dragging below threshold', function() {
270 + ReactEvent.putListener(
271 + CHILD.id,
272 + ON_TOUCH_TAP_KEY,
273 + recordID.bind(null, CHILD.id)
274 + );
275 + ReactTestUtils.Simulate.touchStart(
276 + CHILD,
277 + ReactTestUtils.nativeTouchData(0, 0)
278 + );
279 + ReactTestUtils.Simulate.touchEnd(
280 + CHILD,
281 + ReactTestUtils.nativeTouchData(0, tapMoveThreshold - 1)
282 + );
283 + expect(idCallOrder.length).toBe(1);
284 + expect(idCallOrder[0]).toBe(CHILD.id);
285 + });
286 +
287 + it('should not onTouchTap from when dragging beyond threshold', function() {
288 + ReactEvent.putListener(
289 + CHILD.id,
290 + ON_TOUCH_TAP_KEY,
291 + recordID.bind(null, CHILD.id)
292 + );
293 + ReactTestUtils.Simulate.touchStart(
294 + CHILD,
295 + ReactTestUtils.nativeTouchData(0, 0)
296 + );
297 + ReactTestUtils.Simulate.touchEnd(
298 + CHILD,
299 + ReactTestUtils.nativeTouchData(0, tapMoveThreshold + 1)
300 + );
301 + expect(idCallOrder.length).toBe(0);
302 + });
303 +
304 +
305 + it('should bubble onTouchTap', function() {
306 + ReactEvent.putListener(
307 + CHILD.id,
308 + ON_TOUCH_TAP_KEY,
309 + recordID.bind(null, CHILD.id)
310 + );
311 + ReactEvent.putListener(
312 + PARENT.id,
313 + ON_TOUCH_TAP_KEY,
314 + recordID.bind(null, PARENT.id)
315 + );
316 + ReactEvent.putListener(
317 + GRANDPARENT.id,
318 + ON_TOUCH_TAP_KEY,
319 + recordID.bind(null, GRANDPARENT.id)
320 + );
321 + ReactTestUtils.Simulate.touchStart(
322 + CHILD,
323 + ReactTestUtils.nativeTouchData(0, 0)
324 + );
325 + ReactTestUtils.Simulate.touchEnd(
326 + CHILD,
327 + ReactTestUtils.nativeTouchData(0, 0)
328 + );
329 + expect(idCallOrder.length).toBe(3);
330 + expect(idCallOrder[0]).toBe(CHILD.id);
331 + expect(idCallOrder[1]).toBe(PARENT.id);
332 + expect(idCallOrder[2]).toBe(GRANDPARENT.id);
333 + });
334 +
335 +});
src/core/__tests__/ReactInstanceHandles-test.js new
+314
@@ -0,0 +1,314 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +require('mock-modules')
23 + .dontMock('ReactInstanceHandles');
24 +
25 +var React = require('React');
26 +var ReactComponent = require('ReactComponent');
27 +var ReactInstanceHandles = require('ReactInstanceHandles');
28 +var ReactTestUtils = require('ReactTestUtils');
29 +
30 +var reactComponentExpect= require('reactComponentExpect');
31 +
32 +/**
33 + * Ensure that all callbacks are invoked, passing this unique argument.
34 + */
35 +var ARG = {arg: true};
36 +var ARG2 = {arg2: true};
37 +
38 +var ChildComponent = React.createClass({
39 + render: function() {
40 + return (
41 + <div ref="DIV">
42 + <div ref="DIV_1" />
43 + <div ref="DIV_2" />
44 + </div>
45 + );
46 + }
47 +});
48 +
49 +var ParentComponent = React.createClass({
50 + render: function() {
51 + return (
52 + <div ref="P">
53 + <div ref="P_P1">
54 + <ChildComponent ref="P_P1_C1" />
55 + <ChildComponent ref="P_P1_C2" />
56 + </div>
57 + <div ref="P_OneOff" />
58 + </div>
59 + );
60 + }
61 +});
62 +
63 +function renderParentIntoDocument() {
64 + return ReactTestUtils.renderIntoDocument(<ParentComponent />);
65 +}
66 +
67 +var aggregatedArgs = [];
68 +function argAggregator(id, isUp, arg) {
69 + aggregatedArgs.push({
70 + id: id,
71 + isUp: isUp,
72 + arg: arg
73 + });
74 +}
75 +
76 +var nextDescendantID;
77 +var traverseTwoPhase;
78 +var traverseEnterLeave;
79 +var getFirstCommonAncestorID;
80 +
81 +describe('ReactInstanceHandles traversal', function() {
82 + beforeEach(function() {
83 + require('mock-modules').dumpCache();
84 + aggregatedArgs = [];
85 + ReactInstanceHandles = require('ReactInstanceHandles');
86 + nextDescendantID = ReactInstanceHandles.nextDescendantID;
87 + getFirstCommonAncestorID = ReactInstanceHandles.getFirstCommonAncestorID;
88 + traverseTwoPhase = ReactInstanceHandles.traverseTwoPhase;
89 + traverseEnterLeave = ReactInstanceHandles.traverseEnterLeave;
90 + });
91 +
92 + it("should return next descendent from window", function() {
93 + var parent = renderParentIntoDocument();
94 + expect(nextDescendantID('', parent.refs.P_P1._rootNodeID)).toBe(
95 + parent.refs.P._rootNodeID
96 + );
97 + });
98 +
99 + it("should return window for next descendent towards window", function() {
100 + expect(nextDescendantID('', '')).toBe('');
101 + });
102 +
103 + it("should return self for next descendent towards self", function() {
104 + var parent = renderParentIntoDocument();
105 + expect(
106 + nextDescendantID(
107 + parent.refs.P_P1._rootNodeID,
108 + parent.refs.P_P1._rootNodeID
109 + )
110 + ).toBe(parent.refs.P_P1._rootNodeID);
111 + });
112 +
113 + it("should not traverse when traversing outside DOM", function() {
114 + var targetID = '';
115 + var expectedAggregation = [];
116 + traverseTwoPhase(targetID, argAggregator, ARG);
117 + expect(aggregatedArgs).toEqual(expectedAggregation);
118 + });
119 +
120 + it("should not traverse when enter/leaving outside DOM", function() {
121 + var targetID = '';
122 + var expectedAggregation = [];
123 + traverseEnterLeave(targetID, targetID, argAggregator, ARG, ARG2);
124 + expect(aggregatedArgs).toEqual(expectedAggregation);
125 + });
126 +
127 + it("should not traverse if enter/leave the same node", function() {
128 + var parent = renderParentIntoDocument();
129 + var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
130 + var enterID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
131 + var expectedAggregation = [];
132 + traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
133 + expect(aggregatedArgs).toEqual(expectedAggregation);
134 + });
135 +
136 + it("should traverse two phase across component boundary", function() {
137 + var parent = renderParentIntoDocument();
138 + var targetID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
139 + var expectedAggregation = [
140 + {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG},
141 + {id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG},
142 + {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG},
143 + {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: false, arg: ARG},
144 +
145 + {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG},
146 + {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
147 + {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
148 + {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
149 + ];
150 + traverseTwoPhase(targetID, argAggregator, ARG);
151 + expect(aggregatedArgs).toEqual(expectedAggregation);
152 + });
153 +
154 + it("should traverse two phase at shallowest node", function() {
155 + var parent = renderParentIntoDocument();
156 + var targetID = parent.refs.P._rootNodeID;
157 + var expectedAggregation = [
158 + {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG},
159 + {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
160 + ];
161 + traverseTwoPhase(targetID, argAggregator, ARG);
162 + expect(aggregatedArgs).toEqual(expectedAggregation);
163 + });
164 +
165 + it("should traverse enter/leave to sibling - avoids parent", function() {
166 + var parent = renderParentIntoDocument();
167 + var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
168 + var enterID = parent.refs.P_P1_C1.refs.DIV_2._rootNodeID;
169 + var expectedAggregation = [
170 + {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG},
171 + // enter/leave shouldn't fire antyhing on the parent
172 + {id: parent.refs.P_P1_C1.refs.DIV_2._rootNodeID, isUp: false, arg: ARG2}
173 + ];
174 + traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
175 + expect(aggregatedArgs).toEqual(expectedAggregation);
176 + });
177 +
178 + it("should traverse enter/leave to parent - avoids parent", function() {
179 + var parent = renderParentIntoDocument();
180 + var leaveID = parent.refs.P_P1_C1.refs.DIV_1._rootNodeID;
181 + var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
182 + var expectedAggregation = [
183 + {id: parent.refs.P_P1_C1.refs.DIV_1._rootNodeID, isUp: true, arg: ARG}
184 + ];
185 + traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
186 + expect(aggregatedArgs).toEqual(expectedAggregation);
187 + });
188 +
189 + it("should enter from the window", function() {
190 + var parent = renderParentIntoDocument();
191 + var leaveID = ''; // From the window or outside of the React sandbox.
192 + var enterID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
193 + var expectedAggregation = [
194 + {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2},
195 + {id: parent.refs.P_P1._rootNodeID, isUp: false, arg: ARG2},
196 + {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: false, arg: ARG2}
197 + ];
198 + traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
199 + expect(aggregatedArgs).toEqual(expectedAggregation);
200 + });
201 +
202 + it("should enter from the window to the shallowest", function() {
203 + var parent = renderParentIntoDocument();
204 + var leaveID = ''; // From the window or outside of the React sandbox.
205 + var enterID = parent.refs.P._rootNodeID;
206 + var expectedAggregation = [
207 + {id: parent.refs.P._rootNodeID, isUp: false, arg: ARG2}
208 + ];
209 + traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
210 + expect(aggregatedArgs).toEqual(expectedAggregation);
211 + });
212 +
213 + it("should leave to the window", function() {
214 + var parent = renderParentIntoDocument();
215 + var enterID = ''; // From the window or outside of the React sandbox.
216 + var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
217 + var expectedAggregation = [
218 + {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
219 + {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
220 + {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
221 + ];
222 + traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
223 + expect(aggregatedArgs).toEqual(expectedAggregation);
224 + });
225 +
226 + it("should leave to the window from the shallowest", function() {
227 + var parent = renderParentIntoDocument();
228 + var enterID = ''; // From the window or outside of the React sandbox.
229 + var leaveID = parent.refs.P_P1_C1.refs.DIV._rootNodeID;
230 + var expectedAggregation = [
231 + {id: parent.refs.P_P1_C1.refs.DIV._rootNodeID, isUp: true, arg: ARG},
232 + {id: parent.refs.P_P1._rootNodeID, isUp: true, arg: ARG},
233 + {id: parent.refs.P._rootNodeID, isUp: true, arg: ARG}
234 + ];
235 + traverseEnterLeave(leaveID, enterID, argAggregator, ARG, ARG2);
236 + expect(aggregatedArgs).toEqual(expectedAggregation);
237 + });
238 +
239 + it("should determine the first common ancestor correctly", function() {
240 + var parent = renderParentIntoDocument();
241 + var ancestors = [
242 + // Common ancestor from window to deep element is ''.
243 + { one: {_rootNodeID: ''},
244 + two: parent.refs.P_P1_C1.refs.DIV_1,
245 + com: {_rootNodeID: ''}
246 + },
247 + // Same as previous - reversed direction.
248 + { one: parent.refs.P_P1_C1.refs.DIV_1,
249 + two: {_rootNodeID: ''},
250 + com: {_rootNodeID: ''}
251 + },
252 + // Common ancestor from window to shallow id is ''.
253 + { one: parent.refs.P,
254 + two: {_rootNodeID: ''},
255 + com: {_rootNodeID: ''}
256 + },
257 + // Common ancestor with self is self.
258 + { one: parent.refs.P_P1_C1.refs.DIV_1,
259 + two: parent.refs.P_P1_C1.refs.DIV_1,
260 + com: parent.refs.P_P1_C1.refs.DIV_1
261 + },
262 + // Common ancestor with self is self - even if topmost DOM.
263 + { one: parent.refs.P, two: parent.refs.P, com: parent.refs.P },
264 + // Siblings
265 + {
266 + one: parent.refs.P_P1_C1.refs.DIV_1,
267 + two: parent.refs.P_P1_C1.refs.DIV_2,
268 + com: parent.refs.P_P1_C1.refs.DIV
269 + },
270 + // Common ancestor with parent is the parent.
271 + {
272 + one: parent.refs.P_P1_C1.refs.DIV_1,
273 + two: parent.refs.P_P1_C1.refs.DIV,
274 + com: parent.refs.P_P1_C1.refs.DIV
275 + },
276 + // Common ancestor with grandparent is the grandparent.
277 + {
278 + one: parent.refs.P_P1_C1.refs.DIV_1,
279 + two: parent.refs.P_P1_C1,
280 + com: parent.refs.P_P1_C1
281 + },
282 + // Grantparent across subcomponent boundaries.
283 + {
284 + one: parent.refs.P_P1_C1.refs.DIV_1,
285 + two: parent.refs.P_P1_C2.refs.DIV_1,
286 + com: parent.refs.P_P1
287 + },
288 + // Something deep with something one-off.
289 + {
290 + one: parent.refs.P_P1_C1.refs.DIV_1,
291 + two: parent.refs.P_OneOff,
292 + com: parent.refs.P
293 + }
294 + ];
295 + var i;
296 + for (i = 0; i < ancestors.length; i++) {
297 + var plan = ancestors[i];
298 + var firstCommon = getFirstCommonAncestorID(
299 + plan.one._rootNodeID,
300 + plan.two._rootNodeID
301 + );
302 + expect(firstCommon).toBe(plan.com._rootNodeID);
303 + }
304 + });
305 +});
306 +
307 +describe('ReactInstanceHandles.getReactRootIDFromNodeID', function() {
308 + it('should support strings', function() {
309 + var test = '.reactRoot[s_0_1][0]..[1]';
310 + var expected = '.reactRoot[s_0_1]';
311 + var actual = ReactInstanceHandles.getReactRootIDFromNodeID(test);
312 + expect(actual).toEqual(expected);
313 + });
314 +});
src/core/__tests__/ReactMountDestruction-test.js new
+64
@@ -0,0 +1,64 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var ReactMount = require('ReactMount');
23 +var React = require('React');
24 +var ReactTestUtils = require('ReactTestUtils');
25 +var ReactMount = require('ReactMount');
26 +
27 +var reactComponentExpect = require('reactComponentExpect');
28 +
29 +describe('ReactMount', function() {
30 + it("should destroy a react root upon request", function() {
31 + var mainContainerDiv = document.createElement('div');
32 + document.documentElement.appendChild(mainContainerDiv);
33 +
34 + var instanceOne = (
35 + <div className="firstReactDiv">
36 + </div>
37 + );
38 + var firstRootDiv = document.createElement('div');
39 + mainContainerDiv.appendChild(firstRootDiv);
40 + React.renderComponent(instanceOne, firstRootDiv);
41 +
42 + var instanceTwo = (
43 + <div className="secondReactDiv">
44 + </div>
45 + );
46 + var secondRootDiv = document.createElement('div');
47 + mainContainerDiv.appendChild(secondRootDiv);
48 + React.renderComponent(instanceTwo, secondRootDiv);
49 +
50 + // Test that two react roots are rendered in isolation
51 + expect(firstRootDiv.getElementsByClassName('firstReactDiv').length)
52 + .toBe(1);
53 + expect(secondRootDiv.getElementsByClassName('secondReactDiv').length)
54 + .toBe(1);
55 +
56 + // Test that after unmounting each, they are no longer in the document.
57 + React.unmountAndReleaseReactRootNode(firstRootDiv);
58 + expect(firstRootDiv.getElementsByClassName('firstReactDiv').length)
59 + .toBe(0);
60 + React.unmountAndReleaseReactRootNode(secondRootDiv);
61 + expect(secondRootDiv.getElementsByClassName('secondReactDiv').length)
62 + .toBe(0);
63 + });
64 +});
src/core/__tests__/ReactMultiChildReconcile-test.js new
+769
@@ -0,0 +1,769 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +require('mock-modules');
23 +
24 +var React = require('React');
25 +var ReactTestUtils = require('ReactTestUtils');
26 +
27 +var objMapKeyVal = require('objMapKeyVal');
28 +
29 +var stripEmptyValues = function(obj) {
30 + var ret = {};
31 + var name;
32 + for (name in obj) {
33 + if (!obj.hasOwnProperty(name)) {
34 + continue;
35 + }
36 + if (obj[name] !== null && obj[name] !== undefined) {
37 + ret[name] = obj[name];
38 + }
39 + }
40 + return ret;
41 +};
42 +
43 +/**
44 + * Children are currently named '{<providedKey>}' so we can retrieve the
45 + * originally provided key by stripping out the braces. This relies on a tiny
46 + * implementation detail of the rendering system.
47 + */
48 +var getOriginalKey = function(childName) {
49 + return childName.substr(1, childName.length - 2);
50 +};
51 +
52 +/**
53 + * Contains internal static internal state in order to test that updates to
54 + * existing children won't reinitialize components, when moving children -
55 + * reusing existing DOM/memory resources.
56 + */
57 +var StatusDisplay = React.createClass({
58 + getInitialState: function() {
59 + return { internalState: Math.random() };
60 + },
61 +
62 + getStatus: function() {
63 + return this.props.status;
64 + },
65 +
66 + getInternalState: function() {
67 + return this.state.internalState;
68 + },
69 +
70 + render: function() {
71 + return (
72 + <div>
73 + {this.state.internalState}
74 + </div>
75 + );
76 + }
77 +});
78 +
79 +/**
80 + * Displays friends statuses.
81 + */
82 +var FriendsStatusDisplay = React.createClass({
83 + /**
84 + * Retrieves the rendered children in a nice format for comparing to the input
85 + * `this.props.usernameToStatus`. Gets the order directly from each rendered
86 + * child's `index` field. Refs are not maintained in the rendered order, and
87 + * neither is `this._renderedChildren` (surprisingly).
88 + */
89 + getStatusDisplays: function() {
90 + var name;
91 + var orderOfUsernames = [];
92 + var statusDisplays = this._renderedComponent._renderedChildren;
93 + for (name in statusDisplays) {
94 + var child = statusDisplays[name];
95 + var isPresent = !!child;
96 + if (isPresent) {
97 + orderOfUsernames[child._domIndex] = getOriginalKey(name);
98 + }
99 + }
100 + var res = {};
101 + var i;
102 + for (i = 0; i < orderOfUsernames.length; i++) {
103 + var key = orderOfUsernames[i];
104 + res[key] = this.refs[key];
105 + }
106 + return res;
107 + },
108 + render: function() {
109 + var children = null;
110 + var key;
111 + for (key in this.props.usernameToStatus) {
112 + var status = this.props.usernameToStatus[key];
113 + children = children || {};
114 + children[key] = !status ? null :
115 + <StatusDisplay ref={key} status={status} />;
116 + }
117 + return (
118 + <div>
119 + {children}
120 + </div>
121 + );
122 + }
123 +});
124 +
125 +
126 +function getInteralStateByUserName(statusDisplays) {
127 + return objMapKeyVal(statusDisplays, function(key, statusDisplay) {
128 + return statusDisplay.getInternalState();
129 + });
130 +}
131 +
132 +/**
133 + * Verifies that the rendered `StatusDisplay` instances match the `props` that
134 + * were responsible for allocating them. Checks the content of the user's status
135 + * message as well as the order of them.
136 + */
137 +function verifyStatuses(statusDisplays, props) {
138 + var nonEmptyStatusDisplays = stripEmptyValues(statusDisplays);
139 + var nonEmptyStatusProps = stripEmptyValues(props.usernameToStatus);
140 + var username;
141 + expect(Object.keys(nonEmptyStatusDisplays).length)
142 + .toEqual(Object.keys(nonEmptyStatusProps).length);
143 + for (username in nonEmptyStatusDisplays) {
144 + if (!nonEmptyStatusDisplays.hasOwnProperty(username)) {
145 + continue;
146 + }
147 + expect(nonEmptyStatusDisplays[username].getStatus())
148 + .toEqual(nonEmptyStatusProps[username]);
149 + }
150 +
151 + // now go the other way to make sure we got them all.
152 + for (username in nonEmptyStatusProps) {
153 + if (!nonEmptyStatusProps.hasOwnProperty(username)) {
154 + continue;
155 + }
156 + expect(nonEmptyStatusDisplays[username].getStatus())
157 + .toEqual(nonEmptyStatusProps[username]);
158 + }
159 +
160 + expect(Object.keys(nonEmptyStatusDisplays))
161 + .toEqual(Object.keys(nonEmptyStatusProps));
162 +}
163 +
164 +/**
165 + * For all statusDisplays that existed in the previous iteration of the
166 + * sequence, verify that the state has been preserved. `StatusDisplay` contains
167 + * a unique number that allows us to track internal state across ordering
168 + * movements.
169 + */
170 +function verifyStatesPreserved(lastInternalStates, statusDisplays) {
171 + var key;
172 + for (key in statusDisplays) {
173 + if (!statusDisplays.hasOwnProperty(key)) {
174 + continue;
175 + }
176 + if (lastInternalStates[key]) {
177 + expect(lastInternalStates[key])
178 + .toEqual(statusDisplays[key].getInternalState());
179 + }
180 + }
181 +}
182 +
183 +
184 +/**
185 + * Verifies that the internal representation of a set of `renderedChildren`
186 + * accurately reflects what is in the DOM.
187 + */
188 +function verifyDomOrderingAccurate(parentInstance, statusDisplays) {
189 + var containerNode = parentInstance.getDOMNode();
190 + var statusDisplayNodes = containerNode.childNodes;
191 + var i;
192 + var orderedDomIds = [];
193 + for (i=0; i < statusDisplayNodes.length; i++) {
194 + orderedDomIds.push(statusDisplayNodes[i].id);
195 + }
196 +
197 + var orderedLogicalIds = [];
198 + var username;
199 + for (username in statusDisplays) {
200 + if (!statusDisplays.hasOwnProperty(username)) {
201 + continue;
202 + }
203 + var statusDisplay = statusDisplays[username];
204 + orderedLogicalIds.push(statusDisplay._rootNodeID);
205 + }
206 + expect(orderedDomIds).toEqual(orderedLogicalIds);
207 +}
208 +
209 +/**
210 + * Todo: Check that internal state is preserved across transitions
211 + */
212 +function testPropsSequence(sequence) {
213 + var i;
214 + var parentInstance =
215 + ReactTestUtils.renderIntoDocument(FriendsStatusDisplay(sequence[0]));
216 + var statusDisplays = parentInstance.getStatusDisplays();
217 + var lastInternalStates = getInteralStateByUserName(statusDisplays);
218 + verifyStatuses(statusDisplays, sequence[0]);
219 +
220 + for (i = 1; i < sequence.length; i++) {
221 + parentInstance.replaceProps(sequence[i]);
222 + statusDisplays = parentInstance.getStatusDisplays();
223 + verifyStatuses(statusDisplays, sequence[i]);
224 + verifyStatesPreserved(lastInternalStates, statusDisplays);
225 + verifyDomOrderingAccurate(parentInstance, statusDisplays);
226 +
227 + lastInternalStates = getInteralStateByUserName(statusDisplays);
228 + }
229 +}
230 +
231 +describe('ReactMultiChildReconcile', function() {
232 + beforeEach(function() {
233 + require('mock-modules').dumpCache();
234 + });
235 +
236 + it('should reset internal state if removed then readded', function() {
237 + // Test basics.
238 + var props = {
239 + usernameToStatus: {
240 + jcw: 'jcwStatus'
241 + }
242 + };
243 +
244 + var parentInstance =
245 + ReactTestUtils.renderIntoDocument(FriendsStatusDisplay(props));
246 + var statusDisplays = parentInstance.getStatusDisplays();
247 + var startingInternalState = statusDisplays.jcw.getInternalState();
248 +
249 + // Now remove the child.
250 + parentInstance.replaceProps({ usernameToStatus: {} });
251 + statusDisplays = parentInstance.getStatusDisplays();
252 + expect(statusDisplays.jcw).toBeFalsy();
253 +
254 + // Now reset the props that cause there to be a child
255 + parentInstance.replaceProps(props);
256 + statusDisplays = parentInstance.getStatusDisplays();
257 + expect(statusDisplays.jcw).toBeTruthy();
258 + expect(statusDisplays.jcw.getInternalState())
259 + .toNotBe(startingInternalState);
260 + });
261 +
262 + it('should create unique identity', function() {
263 + // Test basics.
264 + var usernameToStatus = {
265 + jcw: 'jcwStatus',
266 + awalke: 'awalkeStatus',
267 + bob: 'bobStatus'
268 + };
269 +
270 + testPropsSequence([ { usernameToStatus: usernameToStatus } ]);
271 + });
272 +
273 + it('should preserve order if children order has not changed', function() {
274 + var PROPS_SEQUENCE = [
275 + {
276 + usernameToStatus: {
277 + jcw: 'jcwStatus',
278 + jordanjcw: 'jordanjcwStatus'
279 + }
280 + },
281 + {
282 + usernameToStatus: {
283 + jcw: 'jcwstatus2',
284 + jordanjcw: 'jordanjcwstatus2'
285 + }
286 + }
287 + ];
288 + testPropsSequence(PROPS_SEQUENCE);
289 + });
290 +
291 + it('should transition from zero to one children correctly', function() {
292 + var PROPS_SEQUENCE = [
293 + { usernameToStatus: {} },
294 + {
295 + usernameToStatus: {
296 + first: 'firstStatus'
297 + }
298 + }
299 + ];
300 + testPropsSequence(PROPS_SEQUENCE);
301 + });
302 +
303 + it('should transition from one to zero children correctly', function() {
304 + var PROPS_SEQUENCE = [
305 + {
306 + usernameToStatus: {
307 + first: 'firstStatus'
308 + }
309 + },
310 + { usernameToStatus: {} }
311 + ];
312 + testPropsSequence(PROPS_SEQUENCE);
313 + });
314 +
315 + it('should transition from one child to null children', function() {
316 + testPropsSequence([
317 + {
318 + usernameToStatus: {
319 + first: 'firstStatus'
320 + }
321 + },
322 + { }
323 + ]);
324 + });
325 +
326 + it('should transition from null children to one child', function() {
327 + testPropsSequence([
328 + { },
329 + {
330 + usernameToStatus: {
331 + first: 'firstStatus'
332 + }
333 + }
334 + ]);
335 + });
336 +
337 + it('should transition from zero children to null children', function() {
338 + testPropsSequence([
339 + {
340 + usernameToStatus: { }
341 + },
342 + { }
343 + ]);
344 + });
345 +
346 + it('should transition from null children to zero children', function() {
347 + testPropsSequence([
348 + { },
349 + {
350 + usernameToStatus: { }
351 + }
352 + ]);
353 + });
354 +
355 +
356 +
357 + /**
358 + * `FriendsStatusDisplay` renders nulls as empty children (it's a convention
359 + * of `FriendsStatusDisplay`, nothing related to React or these test cases.
360 + */
361 + it('should remove nulled out children at the beginning', function() {
362 + var PROPS_SEQUENCE = [
363 + {
364 + usernameToStatus: {
365 + jcw: 'jcwStatus',
366 + jordanjcw: 'jordanjcwStatus'
367 + }
368 + },
369 + {
370 + usernameToStatus: {
371 + jcw: null,
372 + jordanjcw: 'jordanjcwstatus2'
373 + }
374 + }
375 + ];
376 + testPropsSequence(PROPS_SEQUENCE);
377 + });
378 +
379 + it('should remove nulled out children at the end', function() {
380 + var PROPS_SEQUENCE = [
381 + {
382 + usernameToStatus: {
383 + jcw: 'jcwStatus',
384 + jordanjcw: 'jordanjcwStatus'
385 + }
386 + },
387 + {
388 + usernameToStatus: {
389 + jcw: 'jcwstatus2',
390 + jordanjcw: null
391 + }
392 + }
393 + ];
394 + testPropsSequence(PROPS_SEQUENCE);
395 + });
396 +
397 + it('should reverse the order of two children', function() {
398 + var PROPS_SEQUENCE = [
399 + {
400 + usernameToStatus: {
401 + userOne: 'userOneStatus',
402 + userTwo: 'userTwoStatus'
403 + }
404 + },
405 + {
406 + usernameToStatus: {
407 + userTwo: 'userTwoStatus',
408 + userOne: 'userOneStatus'
409 + }
410 + }
411 + ];
412 + testPropsSequence(PROPS_SEQUENCE);
413 + });
414 +
415 + it('should reverse the order of more than two children', function() {
416 + var PROPS_SEQUENCE = [
417 + {
418 + usernameToStatus: {
419 + userOne: 'userOneStatus',
420 + userTwo: 'userTwoStatus',
421 + userThree: 'userThreeStatus'
422 + }
423 + },
424 + {
425 + usernameToStatus: {
426 + userThree: 'userThreeStatus',
427 + userTwo: 'userTwoStatus',
428 + userOne: 'userOneStatus'
429 + }
430 + }
431 + ];
432 + testPropsSequence(PROPS_SEQUENCE);
433 + });
434 +
435 + it('should cycle order correctly', function() {
436 + var PROPS_SEQUENCE = [
437 + {
438 + usernameToStatus: {
439 + userOne: 'userOneStatus',
440 + userTwo: 'userTwoStatus',
441 + userThree: 'userThreeStatus',
442 + userFour: 'userFourStatus'
443 + }
444 + },
445 + {
446 + usernameToStatus: {
447 + userTwo: 'userTwoStatus',
448 + userThree: 'userThreeStatus',
449 + userFour: 'userFourStatus',
450 + userOne: 'userOneStatus'
451 + }
452 + },
453 + {
454 + usernameToStatus: {
455 + userThree: 'userThreeStatus',
456 + userFour: 'userFourStatus',
457 + userOne: 'userOneStatus',
458 + userTwo: 'userTwoStatus'
459 + }
460 + },
461 + {
462 + usernameToStatus: {
463 + userFour: 'userFourStatus',
464 + userOne: 'userOneStatus',
465 + userTwo: 'userTwoStatus',
466 + userThree: 'userThreeStatus'
467 + }
468 + },
469 + {
470 + usernameToStatus: { // Full circle!
471 + userOne: 'userOneStatus',
472 + userTwo: 'userTwoStatus',
473 + userThree: 'userThreeStatus',
474 + userFour: 'userFourStatus'
475 + }
476 + }
477 + ];
478 + testPropsSequence(PROPS_SEQUENCE);
479 + });
480 +
481 + it('should cycle order correctly in the other direction', function() {
482 + var PROPS_SEQUENCE = [
483 + {
484 + usernameToStatus: {
485 + userOne: 'userOneStatus',
486 + userTwo: 'userTwoStatus',
487 + userThree: 'userThreeStatus',
488 + userFour: 'userFourStatus'
489 + }
490 + },
491 + {
492 + usernameToStatus: {
493 + userFour: 'userFourStatus',
494 + userOne: 'userOneStatus',
495 + userTwo: 'userTwoStatus',
496 + userThree: 'userThreeStatus'
497 + }
498 + },
499 + {
500 + usernameToStatus: {
501 + userThree: 'userThreeStatus',
502 + userFour: 'userFourStatus',
503 + userOne: 'userOneStatus',
504 + userTwo: 'userTwoStatus'
505 + }
506 + },
507 + {
508 + usernameToStatus: {
509 + userTwo: 'userTwoStatus',
510 + userThree: 'userThreeStatus',
511 + userFour: 'userFourStatus',
512 + userOne: 'userOneStatus'
513 + }
514 + },
515 + {
516 + usernameToStatus: { // Full circle!
517 + userOne: 'userOneStatus',
518 + userTwo: 'userTwoStatus',
519 + userThree: 'userThreeStatus',
520 + userFour: 'userFourStatus'
521 + }
522 + }
523 + ];
524 + testPropsSequence(PROPS_SEQUENCE);
525 + });
526 +
527 +
528 + it('should remove nulled out children and ignore ' +
529 + 'new null children', function() {
530 + var PROPS_SEQUENCE = [
531 + {
532 + usernameToStatus: {
533 + jcw: 'jcwStatus',
534 + jordanjcw: 'jordanjcwStatus'
535 + }
536 + },
537 + {
538 + usernameToStatus: {
539 + jordanjcw: 'jordanjcwstatus2',
540 + jcw: null,
541 + another: null
542 + }
543 + }
544 + ];
545 + testPropsSequence(PROPS_SEQUENCE);
546 + });
547 +
548 + it('should remove nulled out children and reorder remaining', function() {
549 + var PROPS_SEQUENCE = [
550 + {
551 + usernameToStatus: {
552 + jcw: 'jcwStatus',
553 + jordanjcw: 'jordanjcwStatus',
554 + john: 'johnStatus', // john will go away
555 + joe: 'joeStatus'
556 + }
557 + },
558 + {
559 + usernameToStatus: {
560 + jordanjcw: 'jordanjcwStatus',
561 + joe: 'joeStatus',
562 + jcw: 'jcwStatus'
563 + }
564 + }
565 + ];
566 + testPropsSequence(PROPS_SEQUENCE);
567 + });
568 +
569 + it('should append children to the end', function() {
570 + var PROPS_SEQUENCE = [
571 + {
572 + usernameToStatus: {
573 + jcw: 'jcwStatus',
574 + jordanjcw: 'jordanjcwStatus'
575 + }
576 + },
577 + {
578 + usernameToStatus: {
579 + jcw: 'jcwStatus',
580 + jordanjcw: 'jordanjcwStatus',
581 + jordanjcwnew: 'jordanjcwnewStatus'
582 + }
583 + }
584 + ];
585 + testPropsSequence(PROPS_SEQUENCE);
586 + });
587 +
588 + it('should append multiple children to the end', function() {
589 + var PROPS_SEQUENCE = [
590 + {
591 + usernameToStatus: {
592 + jcw: 'jcwStatus',
593 + jordanjcw: 'jordanjcwStatus'
594 + }
595 + },
596 + {
597 + usernameToStatus: {
598 + jcw: 'jcwStatus',
599 + jordanjcw: 'jordanjcwStatus',
600 + jordanjcwnew: 'jordanjcwnewStatus',
601 + jordanjcwnew2: 'jordanjcwnewStatus2'
602 + }
603 + }
604 + ];
605 + testPropsSequence(PROPS_SEQUENCE);
606 + });
607 +
608 + it('should prepend children to the beginning', function() {
609 + var PROPS_SEQUENCE = [
610 + {
611 + usernameToStatus: {
612 + jcw: 'jcwStatus',
613 + jordanjcw: 'jordanjcwStatus'
614 + }
615 + },
616 + {
617 + usernameToStatus: {
618 + newUsername: 'newUsernameStatus',
619 + jcw: 'jcwStatus',
620 + jordanjcw: 'jordanjcwStatus'
621 + }
622 + }
623 + ];
624 + testPropsSequence(PROPS_SEQUENCE);
625 + });
626 +
627 + it('should prepend multiple children to the beginning', function() {
628 + var PROPS_SEQUENCE = [
629 + {
630 + usernameToStatus: {
631 + jcw: 'jcwStatus',
632 + jordanjcw: 'jordanjcwStatus'
633 + }
634 + },
635 + {
636 + usernameToStatus: {
637 + newNewUsername: 'newNewUsernameStatus',
638 + newUsername: 'newUsernameStatus',
639 + jcw: 'jcwStatus',
640 + jordanjcw: 'jordanjcwStatus'
641 + }
642 + }
643 + ];
644 + testPropsSequence(PROPS_SEQUENCE);
645 + });
646 +
647 + it('should not prepend an empty child to the beginning', function() {
648 + var PROPS_SEQUENCE = [
649 + {
650 + usernameToStatus: {
651 + jcw: 'jcwStatus',
652 + jordanjcw: 'jordanjcwStatus'
653 + }
654 + },
655 + {
656 + usernameToStatus: {
657 + emptyUsername: null,
658 + jcw: 'jcwStatus',
659 + jordanjcw: 'jordanjcwStatus'
660 + }
661 + }
662 + ];
663 + testPropsSequence(PROPS_SEQUENCE);
664 + });
665 +
666 + it('should not append an empty child to the end', function() {
667 + var PROPS_SEQUENCE = [
668 + {
669 + usernameToStatus: {
670 + jcw: 'jcwStatus',
671 + jordanjcw: 'jordanjcwStatus'
672 + }
673 + },
674 + {
675 + usernameToStatus: {
676 + jcw: 'jcwStatus',
677 + jordanjcw: 'jordanjcwStatus',
678 + emptyUsername: null
679 + }
680 + }
681 + ];
682 + testPropsSequence(PROPS_SEQUENCE);
683 + });
684 +
685 + it('should not insert empty children in the middle', function() {
686 + var PROPS_SEQUENCE = [
687 + {
688 + usernameToStatus: {
689 + jcw: 'jcwStatus',
690 + jordanjcw: 'jordanjcwStatus'
691 + }
692 + },
693 + {
694 + usernameToStatus: {
695 + jcw: 'jcwstatus2',
696 + skipOverMe: null,
697 + skipOverMeToo: null,
698 + definitelySkipOverMe: null,
699 + jordanjcw: 'jordanjcwstatus2'
700 + }
701 + }
702 + ];
703 + testPropsSequence(PROPS_SEQUENCE);
704 + });
705 +
706 + it('should insert one new child in the middle', function() {
707 + var PROPS_SEQUENCE = [
708 + {
709 + usernameToStatus: {
710 + jcw: 'jcwStatus',
711 + jordanjcw: 'jordanjcwStatus'
712 + }
713 + },
714 + {
715 + usernameToStatus: {
716 + jcw: 'jcwstatus2',
717 + insertThis: 'insertThisStatus',
718 + jordanjcw: 'jordanjcwstatus2'
719 + }
720 + }
721 + ];
722 + testPropsSequence(PROPS_SEQUENCE);
723 + });
724 +
725 + it('should insert multiple new truthy children in the middle', function() {
726 + var PROPS_SEQUENCE = [
727 + {
728 + usernameToStatus: {
729 + jcw: 'jcwStatus',
730 + jordanjcw: 'jordanjcwStatus'
731 + }
732 + },
733 + {
734 + usernameToStatus: {
735 + jcw: 'jcwstatus2',
736 + insertThis: 'insertThisStatus',
737 + insertThisToo: 'insertThisTooStatus',
738 + definitelyInsertThisToo: 'definitelyInsertThisTooStatus',
739 + jordanjcw: 'jordanjcwstatus2'
740 + }
741 + }
742 + ];
743 + testPropsSequence(PROPS_SEQUENCE);
744 + });
745 +
746 + it('should insert non-empty children in middle where nulls were', function() {
747 + var PROPS_SEQUENCE = [
748 + {
749 + usernameToStatus: {
750 + jcw: 'jcwStatus',
751 + insertThis: null,
752 + insertThisToo: null,
753 + definitelyInsertThisToo: null,
754 + jordanjcw: 'jordanjcwStatus'
755 + }
756 + },
757 + {
758 + usernameToStatus: {
759 + jcw: 'jcwstatus2',
760 + insertThis: 'insertThisStatus',
761 + insertThisToo: 'insertThisTooStatus',
762 + definitelyInsertThisToo: 'definitelyInsertThisTooStatus',
763 + jordanjcw: 'jordanjcwstatus2'
764 + }
765 + }
766 + ];
767 + testPropsSequence(PROPS_SEQUENCE);
768 + });
769 +});
src/core/__tests__/ReactMultiChildText-test.js new
+309
@@ -0,0 +1,309 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +require('mock-modules');
23 +
24 +var React = require('React');
25 +var ReactTestUtils = require('ReactTestUtils');
26 +var ReactTextComponent = require('ReactTextComponent');
27 +
28 +var reactComponentExpect = require('reactComponentExpect');
29 +
30 +var assertNodeText = function(instance, text) {
31 + expect(instance.getDOMNode().childNodes.length).toBe(1);
32 + expect(instance.getDOMNode().innerHTML).toBe('' + text);
33 +};
34 +
35 +var assertEmptyNode = function(instance) {
36 + expect(instance.getDOMNode().childNodes.length).toBe(0);
37 +};
38 +
39 +var assertMultiChild = function(instance, textOne, textTwo) {
40 + expect(instance.getDOMNode().childNodes.length).toBe(2);
41 + var firstTextDOMNode =
42 + reactComponentExpect(instance)
43 + .expectRenderedChildAt(0)
44 + .toBeTextComponent()
45 + .instance()
46 + .getDOMNode();
47 + expect(firstTextDOMNode.childNodes.length).toBe(textOne === '' ? 0 : 1);
48 + expect(firstTextDOMNode.innerHTML).toBe('' + textOne);
49 +
50 + var secondTextDOMNode =
51 + reactComponentExpect(instance)
52 + .expectRenderedChildAt(1)
53 + .toBeTextComponent()
54 + .instance()
55 + .getDOMNode();
56 + expect(secondTextDOMNode.childNodes.length).toBe(textTwo === '' ? 0 : 1);
57 + expect(secondTextDOMNode.innerHTML).toBe('' + textTwo);
58 +};
59 +
60 +var assertSingleChild = function(instance, text) {
61 + expect(instance.getDOMNode().childNodes.length).toBe(1);
62 + var textDOMNode =
63 + reactComponentExpect(instance)
64 + .expectRenderedChildAt(0)
65 + .toBeTextComponent()
66 + .instance()
67 + .getDOMNode();
68 + expect(textDOMNode.childNodes.length).toBe(1);
69 + expect(textDOMNode.innerHTML).toBe('' + text);
70 +};
71 +
72 +// Helpers
73 +var renderSingleContentChild = function(text) {
74 + var d = ReactTestUtils.renderIntoDocument(<div content={text} />);
75 + return d;
76 +};
77 +var renderSingleTextChild = function(text) {
78 + var d = ReactTestUtils.renderIntoDocument(<div>{text}</div>);
79 + return d;
80 +};
81 +var renderMultipleTextChildren = function(textOne, textTwo) {
82 + var d = ReactTestUtils.renderIntoDocument(<div>{textOne}{textTwo}</div>);
83 + return d;
84 +};
85 +
86 +var TestCompositeComponent = React.createClass({
87 + render: function() {
88 + return (
89 + <div> </div>
90 + );
91 + }
92 +});
93 +
94 +/**
95 + * ReactMultiChild DOM integration test. In ReactDOM components, we make sure
96 + * that single children that are strings are treated as "content" which is much
97 + * faster to render and update.
98 + */
99 +describe('ReactMultiChildText', function() {
100 + it('should render null as empty', function() {
101 + var d = renderSingleTextChild(null);
102 + // false should act exactly as a null child
103 + assertEmptyNode(d);
104 + });
105 +
106 + it('should render undefined as empty', function() {
107 + var d = renderSingleTextChild(undefined);
108 + // false should act exactly as a null child
109 + assertEmptyNode(d);
110 + });
111 +
112 + it('should render null as empty then switch to text node', function() {
113 + var d = renderSingleTextChild(null);
114 + // false should act exactly as a null child
115 + assertEmptyNode(d);
116 + d.replaceProps({children: 'hello'});
117 + assertNodeText(d, 'hello');
118 + });
119 +
120 + it('should render undefined as empty then switch to text node', function() {
121 + var d = renderSingleTextChild(undefined);
122 + // false should act exactly as a null child
123 + assertEmptyNode(d);
124 + d.replaceProps({children: 'hello'});
125 + assertNodeText(d, 'hello');
126 + });
127 +
128 + it('should render null as empty then switch to span children', function() {
129 + var d = renderSingleTextChild(null);
130 + // false should act exactly as a null child
131 + assertEmptyNode(d);
132 + d.replaceProps({children: ['hello', 'goodbye']});
133 + assertMultiChild(d, 'hello', 'goodbye');
134 + });
135 +
136 + it('should render null as empty then switch to span children', function() {
137 + var d = renderSingleTextChild(undefined);
138 + // false should act exactly as a null child
139 + assertEmptyNode(d);
140 + d.replaceProps({children: ['hello', 'goodbye']});
141 + assertMultiChild(d, 'hello', 'goodbye');
142 + });
143 +
144 + it('should render zero string as text node then switch to spans', function() {
145 + var d = renderSingleTextChild('0');
146 + // false should act exactly as a null child
147 + assertNodeText(d, '0');
148 + d.replaceProps({children: ['hello', 'goodbye']});
149 + assertMultiChild(d, 'hello', 'goodbye');
150 + });
151 +
152 + it('should render zero number as text node then switch to spans', function() {
153 + var d = renderSingleTextChild('0');
154 + // false should act exactly as a null child
155 + assertNodeText(d, 0);
156 + d.replaceProps({children: ['hello', 'goodbye']});
157 + assertMultiChild(d, 'hello', 'goodbye');
158 + });
159 +
160 + it('should render content to single text node', function() {
161 + var d = renderSingleContentChild('hello');
162 + assertNodeText(d, 'hello');
163 + });
164 +
165 + it('should render a single text child to a single text node', function() {
166 + var d = renderSingleTextChild('hello');
167 + assertNodeText(d, 'hello');
168 + });
169 +
170 + it('should render two string children to two spans', function() {
171 + var d = renderMultipleTextChildren('hello', 'goodbye');
172 + assertMultiChild(d, 'hello', 'goodbye');
173 + });
174 +
175 + it('should render false as a null child', function() {
176 + var d = renderMultipleTextChildren(false, 234.2);
177 + // false should act exactly as a null child
178 + assertSingleChild(d, '234.2');
179 + });
180 +
181 + it('should render true as a null child', function() {
182 + var d = renderMultipleTextChildren(true, 234.2);
183 + // false should act exactly as a null child
184 + assertSingleChild(d, '234.2');
185 + });
186 +
187 + it('should render true as a null child', function() {
188 + var d = renderMultipleTextChildren(true, 234.2);
189 + // false should act exactly as a null child
190 + assertSingleChild(d, '234.2');
191 + });
192 +
193 + it('should render one true as no children', function() {
194 + var d = renderSingleTextChild(true);
195 + assertEmptyNode(d);
196 + });
197 +
198 + it('should render one false as no children', function() {
199 + var d = renderSingleTextChild(false);
200 + assertEmptyNode(d);
201 + });
202 +
203 + it('should render empty string as no children', function() {
204 + var d = renderSingleTextChild('');
205 + assertEmptyNode(d);
206 + });
207 +
208 + it('should render two empty strings as two empty spans', function() {
209 + var d = renderMultipleTextChildren('', '');
210 + assertMultiChild(d, '', '');
211 + });
212 +
213 + it('should render empty string and string as two spans', function() {
214 + var d = renderMultipleTextChildren('', 'yo');
215 + assertMultiChild(d, '', 'yo');
216 + });
217 +
218 + it('should render child string zero as text node', function() {
219 + var d = renderSingleTextChild('0');
220 + // false should act exactly as a null child
221 + assertNodeText(d, '0');
222 + });
223 +
224 + it('should render child number zero as text node', function() {
225 + var d = renderSingleTextChild(0);
226 + // false should act exactly as a null child
227 + assertNodeText(d, '0');
228 + });
229 +
230 + it('should render content string zero as text node', function() {
231 + var d = renderSingleTextChild('0');
232 + // false should act exactly as a null child
233 + assertNodeText(d, '0');
234 + });
235 +
236 + it('should render content number zero as text node', function() {
237 + var d = renderSingleContentChild(0);
238 + // false should act exactly as a null child
239 + assertNodeText(d, '0');
240 + });
241 +
242 + it('should render zero string as string child', function() {
243 + var d = renderMultipleTextChildren('0', 234.2);
244 + // false should act exactly as a null child
245 + assertMultiChild(d, '0', '234.2');
246 + });
247 +
248 + it('should render zero string as string child then text node', function() {
249 + var d = renderMultipleTextChildren('0', 234.2);
250 + // false should act exactly as a null child
251 + assertMultiChild(d, '0', '234.2');
252 + d.replaceProps({content: '0'});
253 + assertNodeText(d, '0');
254 + });
255 +
256 + it('should render zero number as string child then text node', function() {
257 + var d = renderMultipleTextChildren(0, 234.2);
258 + // false should act exactly as a null child
259 + assertMultiChild(d, '0', '234.2');
260 + d.replaceProps({content: 0});
261 + // BELOW REVEALS A BUG IN JSDOM
262 + // assertNodeText(d, '0'); // This works in the browser.
263 + });
264 +
265 + it('should render multiple children then switch to inline', function() {
266 + var d = renderMultipleTextChildren('hello', 'goodbye');
267 + assertMultiChild(d, 'hello', 'goodbye');
268 + d.replaceProps({content: 'hello'});
269 + assertNodeText(d, 'hello');
270 + });
271 +
272 + it('should render multiple children then switch to inline child', function() {
273 + var d = renderMultipleTextChildren('hello', 'goodbye');
274 + assertMultiChild(d, 'hello', 'goodbye');
275 + // Even when switching from content to a single child, it should render
276 + // that single child as inline content.
277 + d.replaceProps({children: 'hello'});
278 + assertNodeText(d, 'hello');
279 + });
280 +
281 + it('should render inline child, then switch to text components ', function() {
282 + var d = renderSingleTextChild('hello');
283 + assertNodeText(d, 'hello');
284 + d.replaceProps({children: ['hello', 'goodbye']});
285 + assertMultiChild(d, 'hello', 'goodbye');
286 + });
287 +
288 + it('should render content, then switch to text components ', function() {
289 + var d = renderSingleContentChild('hello');
290 + assertNodeText(d, 'hello');
291 + d.replaceProps({children: ['hello', 'goodbye']});
292 + assertMultiChild(d, 'hello', 'goodbye');
293 + });
294 +
295 + it('should render inline child, then switch to composite', function() {
296 + var d = renderSingleTextChild('hello');
297 + assertNodeText(d, 'hello');
298 + d.replaceProps({children: <TestCompositeComponent />});
299 + reactComponentExpect(d)
300 + .expectRenderedChildAt(0)
301 + .toBeCompositeComponentWithType(TestCompositeComponent);
302 + });
303 +
304 + it('should throw if rendering both content and children', function() {
305 + expect(function() {
306 + ReactTestUtils.renderIntoDocument(<div content="asdf">ghjkl</div>);
307 + }).toThrow();
308 + });
309 +});
src/core/__tests__/ReactNativeComponent-test.js new
+242
@@ -0,0 +1,242 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +/*jslint evil: true */
21 +
22 +"use strict";
23 +
24 +describe('ReactNativeComponent', function() {
25 +
26 + describe('updateDOM', function() {
27 + var React;
28 + var ReactTestUtils;
29 + var transaction;
30 +
31 + beforeEach(function() {
32 + React = require('React');
33 + ReactTestUtils = require('ReactTestUtils');
34 +
35 + var ReactReconcileTransaction = require('ReactReconcileTransaction');
36 + transaction = new ReactReconcileTransaction();
37 + });
38 +
39 + it("should handle className", function() {
40 + var stub = ReactTestUtils.renderIntoDocument(<div style={{}} />);
41 +
42 + stub.receiveProps({ className: 'foo' }, transaction);
43 + expect(stub.getDOMNode().className).toEqual('foo');
44 + stub.receiveProps({ className: 'bar' }, transaction);
45 + expect(stub.getDOMNode().className).toEqual('bar');
46 + stub.receiveProps({ className: null }, transaction);
47 + expect(stub.getDOMNode().className).toEqual('');
48 + });
49 +
50 + it("should gracefully handle various style value types", function() {
51 + var stub = ReactTestUtils.renderIntoDocument(<div style={{}} />);
52 + var stubStyle = stub.getDOMNode().style;
53 +
54 + // set initial style
55 + var setup = { display: 'block', left: '1', top: 2, fontFamily: 'Arial' };
56 + stub.receiveProps({ style: setup }, transaction);
57 + expect(stubStyle.display).toEqual('block');
58 + expect(stubStyle.left).toEqual('1px');
59 + expect(stubStyle.fontFamily).toEqual('Arial');
60 +
61 + // reset the style to their default state
62 + var reset = { display: '', left: null, top: false, fontFamily: true };
63 + stub.receiveProps({ style: reset }, transaction);
64 + expect(stubStyle.display).toEqual('');
65 + expect(stubStyle.left).toEqual('');
66 + expect(stubStyle.top).toEqual('');
67 + expect(stubStyle.fontFamily).toEqual('');
68 + });
69 +
70 + it("should update styles when mutating style object", function() {
71 + var styles = { display: 'none', fontFamily: 'Arial' };
72 + var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
73 +
74 + var stubStyle = stub.getDOMNode().style;
75 + stubStyle.display = styles.display;
76 + stubStyle.fontFamily = styles.fontFamily;
77 +
78 + styles.display = 'block';
79 +
80 + stub.receiveProps({ style: styles }, transaction);
81 + expect(stubStyle.display).toEqual('block');
82 + expect(stubStyle.fontFamily).toEqual('Arial');
83 +
84 + styles.fontFamily = 'Helvetica';
85 +
86 + stub.receiveProps({ style: styles }, transaction);
87 + expect(stubStyle.display).toEqual('block');
88 + expect(stubStyle.fontFamily).toEqual('Helvetica');
89 + });
90 +
91 + it("should update styles if initially null", function() {
92 + var styles = null;
93 + var stub = ReactTestUtils.renderIntoDocument(<div style={styles} />);
94 +
95 + var stubStyle = stub.getDOMNode().style;
96 +
97 + styles = {display: 'block'};
98 +
99 + stub.receiveProps({ style: styles }, transaction);
100 + expect(stubStyle.display).toEqual('block');
101 + });
102 +
103 + });
104 +
105 + describe('createOpenTagMarkup', function() {
106 + var genMarkup;
107 +
108 + function quoteRegexp(str) {
109 + return (str+'').replace(/([.?*+\^$\[\]\\(){}|-])/g, "\\$1");
110 + }
111 +
112 + beforeEach(function() {
113 + require('mock-modules').dumpCache();
114 +
115 + var mixInto = require('mixInto');
116 + var ReactNativeComponent = require('ReactNativeComponent');
117 +
118 + var NodeStub = function(initialProps) {
119 + this.props = initialProps || {};
120 + this._rootNodeID = 'test';
121 + };
122 + mixInto(NodeStub, ReactNativeComponent.Mixin);
123 +
124 + genMarkup = function(props) {
125 + return (new NodeStub(props))._createOpenTagMarkup();
126 + };
127 +
128 + this.addMatchers({
129 + toHaveAttribute: function(attr, value) {
130 + var expected = '(?:^|\\s)' + attr + '=[\\\'"]';
131 + if (typeof value != 'undefined') {
132 + expected += quoteRegexp(value) + '[\\\'"]';
133 + }
134 + return this.actual.match(new RegExp(expected));
135 + }
136 + });
137 + });
138 +
139 + it("should handle className", function() {
140 + expect(genMarkup({ className: 'a' })).toHaveAttribute('class', 'a');
141 + expect(genMarkup({ className: 'a b' })).toHaveAttribute('class', 'a b');
142 + expect(genMarkup({ className: '' })).toHaveAttribute('class', '');
143 + });
144 + });
145 +
146 + describe('createContentMarkup', function() {
147 + var genMarkup;
148 +
149 + function quoteRegexp(str) {
150 + return (str+'').replace(/([.?*+\^$\[\]\\(){}|-])/g, "\\$1");
151 + }
152 +
153 + beforeEach(function() {
154 + require('mock-modules').dumpCache();
155 +
156 + var mixInto = require('mixInto');
157 + var ReactNativeComponent = require('ReactNativeComponent');
158 + var ReactReconcileTransaction = require('ReactReconcileTransaction');
159 +
160 + var NodeStub = function(initialProps) {
161 + this.props = initialProps || {};
162 + this._rootNodeID = 'test';
163 + };
164 + mixInto(NodeStub, ReactNativeComponent.Mixin);
165 +
166 + genMarkup = function(props) {
167 + var transaction = new ReactReconcileTransaction();
168 + return (new NodeStub(props))._createContentMarkup(transaction);
169 + };
170 +
171 + this.addMatchers({
172 + toHaveInnerhtml: function(html) {
173 + var expected = '^' + quoteRegexp(html) + '$';
174 + return this.actual.match(new RegExp(expected));
175 + }
176 + });
177 + });
178 +
179 + it("should handle dangerouslySetInnerHTML", function() {
180 + var innerHTML = {__html: 'testContent'};
181 + expect(
182 + genMarkup({ dangerouslySetInnerHTML: innerHTML })
183 + ).toHaveInnerhtml('testContent');
184 + });
185 + });
186 +
187 + describe('mountComponent', function() {
188 + var mountComponent;
189 +
190 + beforeEach(function() {
191 + require('mock-modules').dumpCache();
192 +
193 + var mixInto = require('mixInto');
194 + var ReactComponent = require('ReactComponent');
195 + var ReactNativeComponent = require('ReactNativeComponent');
196 + var ReactReconcileTransaction = require('ReactReconcileTransaction');
197 +
198 + var NodeStub = function(initialProps) {
199 + ReactComponent.Mixin.construct.call(this, initialProps);
200 + };
201 + mixInto(NodeStub, ReactNativeComponent.Mixin);
202 +
203 + mountComponent = function(props) {
204 + var transaction = new ReactReconcileTransaction();
205 + return (new NodeStub(props)).mountComponent('test', transaction);
206 + };
207 + });
208 +
209 + it("should validate against multiple children props", function() {
210 + expect(function() {
211 + mountComponent({ content: '', children: '' });
212 + }).toThrow(
213 + 'Invariant Violation: Can only set one of `children`, ' +
214 + '`props.content`, or `props.dangerouslySetInnerHTML`.'
215 + );
216 +
217 + expect(function() {
218 + mountComponent({ content: '', dangerouslySetInnerHTML: '' });
219 + }).toThrow(
220 + 'Invariant Violation: Can only set one of `children`, ' +
221 + '`props.content`, or `props.dangerouslySetInnerHTML`.'
222 + );
223 +
224 + expect(function() {
225 + mountComponent({ children: '', dangerouslySetInnerHTML: '' });
226 + }).toThrow(
227 + 'Invariant Violation: Can only set one of `children`, ' +
228 + '`props.content`, or `props.dangerouslySetInnerHTML`.'
229 + );
230 + });
231 +
232 + it("should validate against invalid styles", function() {
233 + expect(function() {
234 + mountComponent({ style: 'display: none' });
235 + }).toThrow(
236 + 'Invariant Violation: The `style` prop expects a mapping from style ' +
237 + 'properties to values, not a string.'
238 + );
239 + });
240 + });
241 +
242 +});
src/core/__tests__/ReactProps-test.js new
+142
@@ -0,0 +1,142 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var Props = require('ReactProps');
23 +
24 +function typeCheck(declaration, value) {
25 + var props = {};
26 + if (arguments.length > 1) {
27 + props.testProp = value;
28 + }
29 + return declaration.bind(this, props, 'testProp', 'testComponent');
30 +}
31 +
32 +describe('Primitive Types', function() {
33 + beforeEach(function() {
34 + require('mock-modules').dumpCache();
35 + });
36 +
37 + it("should throw for invalid strings", function() {
38 + expect(typeCheck(Props.string, [])).toThrow(
39 + 'Invariant Violation: Invalid prop `testProp` of type `array` ' +
40 + 'supplied to `testComponent`, expected `string`.'
41 + );
42 + expect(typeCheck(Props.string, false)).toThrow(
43 + 'Invariant Violation: Invalid prop `testProp` of type `boolean` ' +
44 + 'supplied to `testComponent`, expected `string`.'
45 + );
46 + expect(typeCheck(Props.string, 1)).toThrow(
47 + 'Invariant Violation: Invalid prop `testProp` of type `number` ' +
48 + 'supplied to `testComponent`, expected `string`.'
49 + );
50 + expect(typeCheck(Props.string, {})).toThrow(
51 + 'Invariant Violation: Invalid prop `testProp` of type `object` ' +
52 + 'supplied to `testComponent`, expected `string`.'
53 + );
54 + });
55 +
56 + it("should not throw for valid values", function() {
57 + expect(typeCheck(Props.array, [])).not.toThrow();
58 + expect(typeCheck(Props.bool, false)).not.toThrow();
59 + expect(typeCheck(Props.number, 0)).not.toThrow();
60 + expect(typeCheck(Props.object, {})).not.toThrow();
61 + expect(typeCheck(Props.string, '')).not.toThrow();
62 + });
63 +
64 + it("should be implicitly optional and not throw without values", function() {
65 + expect(typeCheck(Props.string, null)).not.toThrow();
66 + expect(typeCheck(Props.string, undefined)).not.toThrow();
67 + });
68 +
69 + it("should throw for missing required values", function() {
70 + expect(typeCheck(Props.string.isRequired, null)).toThrow(
71 + 'Invariant Violation: Required prop `testProp` was not specified in ' +
72 + '`testComponent`.'
73 + );
74 + expect(typeCheck(Props.string.isRequired, undefined)).toThrow(
75 + 'Invariant Violation: Required prop `testProp` was not specified in ' +
76 + '`testComponent`.'
77 + );
78 + });
79 +});
80 +
81 +describe('Enum Types', function() {
82 + beforeEach(function() {
83 + require('mock-modules').dumpCache();
84 + });
85 +
86 + it("should throw for invalid strings", function() {
87 + expect(typeCheck(Props.oneOf(['red', 'blue']), true)).toThrow(
88 + 'Invariant Violation: Invalid prop `testProp` supplied to ' +
89 + '`testComponent`, expected one of ["blue","red"].'
90 + );
91 + expect(typeCheck(Props.oneOf(['red', 'blue']), [])).toThrow(
92 + 'Invariant Violation: Invalid prop `testProp` supplied to ' +
93 + '`testComponent`, expected one of ["blue","red"].'
94 + );
95 + expect(typeCheck(Props.oneOf(['red', 'blue']), '')).toThrow(
96 + 'Invariant Violation: Invalid prop `testProp` supplied to ' +
97 + '`testComponent`, expected one of ["blue","red"].'
98 + );
99 + });
100 +
101 + it("should not throw for valid values", function() {
102 + expect(typeCheck(Props.oneOf(['red', 'blue']), 'red')).not.toThrow();
103 + expect(typeCheck(Props.oneOf(['red', 'blue']), 'blue')).not.toThrow();
104 + });
105 +
106 + it("should be implicitly optional and not throw without values", function() {
107 + expect(typeCheck(Props.oneOf(['red', 'blue']), null)).not.toThrow();
108 + expect(typeCheck(Props.oneOf(['red', 'blue']), undefined)).not.toThrow();
109 + });
110 +});
111 +
112 +describe('Instance Types', function() {
113 + beforeEach(function() {
114 + require('mock-modules').dumpCache();
115 + });
116 +
117 + it("should throw for invalid instances", function() {
118 + function Person() {}
119 +
120 + expect(typeCheck(Props.instanceOf(Person), false)).toThrow(
121 + 'Invariant Violation: Invalid prop `testProp` supplied to ' +
122 + '`testComponent`, expected instance of `Person`.'
123 + );
124 + expect(typeCheck(Props.instanceOf(Person), {})).toThrow(
125 + 'Invariant Violation: Invalid prop `testProp` supplied to ' +
126 + '`testComponent`, expected instance of `Person`.'
127 + );
128 + expect(typeCheck(Props.instanceOf(Person), '')).toThrow(
129 + 'Invariant Violation: Invalid prop `testProp` supplied to ' +
130 + '`testComponent`, expected instance of `Person`.'
131 + );
132 + });
133 +
134 + it("should not throw for valid values", function() {
135 + function Person() {}
136 + function Engineer() {}
137 + Engineer.prototype = new Person();
138 +
139 + expect(typeCheck(Props.instanceOf(Person), new Person())).not.toThrow();
140 + expect(typeCheck(Props.instanceOf(Person), new Engineer())).not.toThrow();
141 + });
142 +});
src/core/__tests__/ReactStateSetters-test.js new
+162
@@ -0,0 +1,162 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var React = require('React');
23 +var ReactStateSetters = require('ReactStateSetters');
24 +var ReactTestUtils = require('ReactTestUtils');
25 +
26 +var TestComponent;
27 +var TestComponentWithMixin;
28 +
29 +describe('ReactStateSetters', function() {
30 + beforeEach(function() {
31 + require('mock-modules').dumpCache();
32 +
33 + TestComponent = React.createClass({
34 + getInitialState: function() {
35 + return {foo: 'foo'};
36 + },
37 +
38 + render: function() {
39 + return <div />;
40 + }
41 + });
42 +
43 + TestComponentWithMixin = React.createClass({
44 + mixins: [ReactStateSetters.Mixin],
45 +
46 + getInitialState: function() {
47 + return {foo: 'foo'};
48 + },
49 +
50 + render: function() {
51 + return <div />;
52 + }
53 + });
54 + });
55 +
56 + it('createStateSetter should update state', function() {
57 + var instance = <TestComponent />;
58 + ReactTestUtils.renderIntoDocument(instance);
59 + expect(instance.state).toEqual({foo: 'foo'});
60 +
61 + var setter = ReactStateSetters.createStateSetter(
62 + instance,
63 + function(a, b, c) {
64 + return {
65 + foo: a + b + c,
66 + bar: a * b * c
67 + };
68 + }
69 + );
70 + expect(instance.state).toEqual({foo: 'foo'});
71 +
72 + setter(1, 2, 3);
73 + expect(instance.state).toEqual({foo: 6, bar: 6});
74 +
75 + setter(10, 11, 12);
76 + expect(instance.state).toEqual({foo: 33, bar: 1320});
77 + });
78 +
79 + it('createStateKeySetter should update state', function() {
80 + var instance = <TestComponent />;
81 + ReactTestUtils.renderIntoDocument(instance);
82 + expect(instance.state).toEqual({foo: 'foo'});
83 +
84 + var setter = ReactStateSetters.createStateKeySetter(instance, 'foo');
85 +
86 + expect(instance.state).toEqual({foo: 'foo'});
87 +
88 + setter('bar');
89 + expect(instance.state).toEqual({foo: 'bar'});
90 +
91 + setter('baz');
92 + expect(instance.state).toEqual({foo: 'baz'});
93 + });
94 +
95 + it('createStateKeySetter is memoized', function() {
96 + var instance = <TestComponent />;
97 + ReactTestUtils.renderIntoDocument(instance);
98 + expect(instance.state).toEqual({foo: 'foo'});
99 +
100 + var foo1 = ReactStateSetters.createStateKeySetter(instance, 'foo');
101 + var bar1 = ReactStateSetters.createStateKeySetter(instance, 'bar');
102 +
103 + var foo2 = ReactStateSetters.createStateKeySetter(instance, 'foo');
104 + var bar2 = ReactStateSetters.createStateKeySetter(instance, 'bar');
105 +
106 + expect(foo2).toBe(foo1);
107 + expect(bar2).toBe(bar1);
108 + });
109 +
110 + it('createStateSetter should update state from mixin', function() {
111 + var instance = <TestComponentWithMixin />;
112 + ReactTestUtils.renderIntoDocument(instance);
113 + expect(instance.state).toEqual({foo: 'foo'});
114 +
115 + var setter = instance.createStateSetter(
116 + function(a, b, c) {
117 + return {
118 + foo: a + b + c,
119 + bar: a * b * c
120 + };
121 + }
122 + );
123 + expect(instance.state).toEqual({foo: 'foo'});
124 +
125 + setter(1, 2, 3);
126 + expect(instance.state).toEqual({foo: 6, bar: 6});
127 +
128 + setter(10, 11, 12);
129 + expect(instance.state).toEqual({foo: 33, bar: 1320});
130 + });
131 +
132 + it('createStateKeySetter should update state with mixin', function() {
133 + var instance = <TestComponentWithMixin />;
134 + ReactTestUtils.renderIntoDocument(instance);
135 + expect(instance.state).toEqual({foo: 'foo'});
136 +
137 + var setter = instance.createStateKeySetter('foo');
138 +
139 + expect(instance.state).toEqual({foo: 'foo'});
140 +
141 + setter('bar');
142 + expect(instance.state).toEqual({foo: 'bar'});
143 +
144 + setter('baz');
145 + expect(instance.state).toEqual({foo: 'baz'});
146 + });
147 +
148 + it('createStateKeySetter is memoized with mixin', function() {
149 + var instance = <TestComponentWithMixin />;
150 + ReactTestUtils.renderIntoDocument(instance);
151 + expect(instance.state).toEqual({foo: 'foo'});
152 +
153 + var foo1 = instance.createStateKeySetter('foo');
154 + var bar1 = instance.createStateKeySetter('bar');
155 +
156 + var foo2 = instance.createStateKeySetter('foo');
157 + var bar2 = instance.createStateKeySetter('bar');
158 +
159 + expect(foo2).toBe(foo1);
160 + expect(bar2).toBe(bar1);
161 + });
162 +});
src/core/__tests__/refs-destruction-test.js new
+60
@@ -0,0 +1,60 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var React = require('React');
23 +var ReactTestUtils = require('ReactTestUtils');
24 +var reactComponentExpect= require('reactComponentExpect');
25 +
26 +var TestComponent = React.createClass({
27 + render: function() {
28 + return (
29 + <div>
30 + <div ref="theInnerDiv">
31 + Lets try to destroy this.
32 + </div>
33 + </div>
34 + );
35 + }
36 +});
37 +
38 +describe('refs-destruction', function() {
39 + beforeEach(function() {
40 + require('mock-modules').dumpCache();
41 + });
42 +
43 + it("should remove refs when destroying the parent", function() {
44 + var testInstance = ReactTestUtils.renderIntoDocument(<TestComponent />);
45 + reactComponentExpect(testInstance.refs.theInnerDiv)
46 + .toBeDOMComponentWithTag('div');
47 + expect(Object.keys(testInstance.refs || {}).length).toEqual(1);
48 + testInstance.unmountComponent();
49 + expect(Object.keys(testInstance.refs || {}).length).toEqual(0);
50 + });
51 +
52 + it("should remove refs when destroying the child", function() {
53 + var testInstance = ReactTestUtils.renderIntoDocument(<TestComponent />);
54 + reactComponentExpect(testInstance.refs.theInnerDiv)
55 + .toBeDOMComponentWithTag('div');
56 + expect(Object.keys(testInstance.refs || {}).length).toEqual(1);
57 + testInstance.refs.theInnerDiv.unmountComponent();
58 + expect(Object.keys(testInstance.refs || {}).length).toEqual(0);
59 + });
60 +});
src/core/__tests__/refs-test.js new
+234
@@ -0,0 +1,234 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +var React = require('React');
23 +var ReactTestUtils = require('ReactTestUtils');
24 +
25 +var reactComponentExpect= require('reactComponentExpect');
26 +
27 +
28 +/**
29 + * Counts clicks and has a renders an item for each click. Each item rendered
30 + * has a ref of the form "clickLogN".
31 + */
32 +var ClickCounter = React.createClass({
33 + getInitialState: function() {
34 + return {count: this.props.initialCount};
35 + },
36 + triggerReset: function() {
37 + this.setState({count: this.props.initialCount});
38 + },
39 + render: function() {
40 + var children = [];
41 + var i;
42 + for (i=0; i < this.state.count; i++) {
43 + children.push(
44 + <div className="clickLogDiv" ref={"clickLog" + i} />
45 + );
46 + }
47 + return (
48 + <span className="clickIncrementer"
49 + onClick={this.setState.bind(this, {count: this.state.count + 1})}>
50 + {children}
51 + </span>
52 + );
53 + }
54 +});
55 +
56 +/**
57 + * Only purpose is to test that refs are tracked even when applied to a
58 + * component that is injected down several layers. Ref systems are difficult to
59 + * build in such a way that ownership is maintained in an airtight manner.
60 + */
61 +var GeneralContainerComponent = React.createClass({
62 + render: function() {
63 + return <div> {this.props.children} </div>;
64 + }
65 +});
66 +
67 +/**
68 + * Notice how refs ownership is maintained even when injecting a component
69 + * into a different parent.
70 + */
71 +var TestRefsComponent = React.createClass({
72 + doReset: function() {
73 + this.refs.myCounter.triggerReset();
74 + },
75 + render: function() {
76 + return (
77 + <div>
78 + <div ref="resetDiv" onClick={this.doReset.bind(this)}>
79 + Reset Me By Clicking This.
80 + </div>
81 + <GeneralContainerComponent ref="myContainer">
82 + <ClickCounter ref="myCounter" initialCount={1}/>
83 + </GeneralContainerComponent>
84 + </div>
85 + );
86 + }
87 +});
88 +
89 +/**
90 + * Render a TestRefsComponent and ensure that the main refs are wired up.
91 + */
92 +var renderTestRefsComponent = function() {
93 + var testRefsComponent =
94 + ReactTestUtils.renderIntoDocument(<TestRefsComponent />);
95 +
96 + reactComponentExpect(testRefsComponent)
97 + .toBeCompositeComponentWithType(TestRefsComponent);
98 +
99 + var generalContainer = testRefsComponent.refs.myContainer;
100 + var counter = testRefsComponent.refs.myCounter;
101 +
102 + reactComponentExpect(generalContainer)
103 + .toBeCompositeComponentWithType(GeneralContainerComponent);
104 + reactComponentExpect(counter)
105 + .toBeCompositeComponentWithType(ClickCounter);
106 +
107 + return testRefsComponent;
108 +};
109 +
110 +
111 +var expectClickLogsLengthToBe = function(instance, length) {
112 + var clickLogs =
113 + ReactTestUtils.scryRenderedDOMComponentsWithClass(instance, 'clickLogDiv');
114 + expect(clickLogs.length).toBe(length);
115 + expect(Object.keys(instance.refs.myCounter.refs).length).toBe(length);
116 +};
117 +
118 +describe('reactiverefs', function() {
119 + beforeEach(function() {
120 + require('mock-modules').dumpCache();
121 + });
122 +
123 + /**
124 + * Ensure that for every click log there is a corresponding ref (from the
125 + * perspective of the injected ClickCounter component.
126 + */
127 + it("Should increase refs with an increase in divs", function() {
128 + var testRefsComponent = renderTestRefsComponent();
129 + var clickIncrementer =
130 + ReactTestUtils.findRenderedDOMComponentWithClass(
131 + testRefsComponent,
132 + 'clickIncrementer'
133 + );
134 +
135 + expectClickLogsLengthToBe(testRefsComponent, 1);
136 +
137 + // After clicking the reset, there should still only be one click log ref.
138 + ReactTestUtils.Simulate.click(testRefsComponent.refs.resetDiv);
139 + expectClickLogsLengthToBe(testRefsComponent, 1);
140 +
141 + // Begin incrementing clicks (and therefore refs).
142 + ReactTestUtils.Simulate.click(clickIncrementer);
143 + expectClickLogsLengthToBe(testRefsComponent, 2);
144 +
145 + ReactTestUtils.Simulate.click(clickIncrementer);
146 + expectClickLogsLengthToBe(testRefsComponent, 3);
147 +
148 + // Now reset again
149 + ReactTestUtils.Simulate.click(testRefsComponent.refs.resetDiv);
150 + expectClickLogsLengthToBe(testRefsComponent, 1);
151 +
152 + });
153 +
154 +});
155 +
156 +
157 +
158 +/**
159 + * Tests that when a ref hops around children, we can track that correctly.
160 + */
161 +describe('ref swapping', function() {
162 + beforeEach(function() {
163 + require('mock-modules').dumpCache();
164 + });
165 +
166 + var RefHopsAround = React.createClass({
167 + getInitialState: function() {
168 + return {count: 0};
169 + },
170 + moveRef: function() {
171 + this.setState({ count: this.state.count + 1 });
172 + },
173 + render: function() {
174 + var count = this.state.count;
175 + /**
176 + * What we have here, is three divs with refs (div1/2/3), but a single
177 + * moving cursor ref `hopRef` that "hops" around the three. We'll call the
178 + * `moveRef()` function several times and make sure that the hop ref
179 + * points to the correct divs.
180 + */
181 + return (
182 + <div>
183 + <div
184 + className="first"
185 + ref={count % 3 === 0 ? 'hopRef' : 'divOneRef'}
186 + />
187 + <div
188 + className="second"
189 + ref={count % 3 === 1 ? 'hopRef' : 'divTwoRef'}
190 + />
191 + <div
192 + className="third"
193 + ref={count % 3 === 2 ? 'hopRef' : 'divThreeRef'}
194 + />
195 + </div>
196 + );
197 + }
198 + });
199 +
200 + it("Allow refs to hop around children correctly", function() {
201 + var refHopsAround = ReactTestUtils.renderIntoDocument(<RefHopsAround />);
202 +
203 + var firstDiv =
204 + ReactTestUtils.findRenderedDOMComponentWithClass(refHopsAround, 'first');
205 + var secondDiv =
206 + ReactTestUtils.findRenderedDOMComponentWithClass(refHopsAround, 'second');
207 + var thirdDiv =
208 + ReactTestUtils.findRenderedDOMComponentWithClass(refHopsAround, 'third');
209 +
210 + expect(refHopsAround.refs.hopRef).toEqual(firstDiv);
211 + expect(refHopsAround.refs.divTwoRef).toEqual(secondDiv);
212 + expect(refHopsAround.refs.divThreeRef).toEqual(thirdDiv);
213 +
214 + refHopsAround.moveRef();
215 + expect(refHopsAround.refs.divOneRef).toEqual(firstDiv);
216 + expect(refHopsAround.refs.hopRef).toEqual(secondDiv);
217 + expect(refHopsAround.refs.divThreeRef).toEqual(thirdDiv);
218 +
219 + refHopsAround.moveRef();
220 + expect(refHopsAround.refs.divOneRef).toEqual(firstDiv);
221 + expect(refHopsAround.refs.divTwoRef).toEqual(secondDiv);
222 + expect(refHopsAround.refs.hopRef).toEqual(thirdDiv);
223 +
224 + /**
225 + * Make sure that after the third, we're back to where we started and the
226 + * refs are completely restored.
227 + */
228 + refHopsAround.moveRef();
229 + expect(refHopsAround.refs.hopRef).toEqual(firstDiv);
230 + expect(refHopsAround.refs.divTwoRef).toEqual(secondDiv);
231 + expect(refHopsAround.refs.divThreeRef).toEqual(thirdDiv);
232 + });
233 +});
234 +
src/domUtils/CSSProperty.js new
+38
@@ -0,0 +1,38 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule CSSProperty
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * CSS properties for which we do not append "px".
23 + */
24 +var isNumber = {
25 + fillOpacity: true,
26 + fontWeight: true,
27 + opacity: true,
28 + orphans: true,
29 + textDecoration: true,
30 + zIndex: true,
31 + zoom: true
32 +};
33 +
34 +var CSSProperty = {
35 + isNumber: isNumber
36 +};
37 +
38 +module.exports = CSSProperty;
src/domUtils/CSSPropertyOperations.js new
+81
@@ -0,0 +1,81 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule CSSPropertyOperations
17 + * @typechecks
18 + */
19 +
20 +"use strict";
21 +
22 +var dangerousStyleValue = require('dangerousStyleValue');
23 +var escapeTextForBrowser = require('escapeTextForBrowser');
24 +var hyphenate = require('hyphenate');
25 +var memoizeStringOnly = require('memoizeStringOnly');
26 +
27 +var processStyleName = memoizeStringOnly(function(styleName) {
28 + return escapeTextForBrowser(hyphenate(styleName));
29 +});
30 +
31 +/**
32 + * Operations for dealing with CSS properties.
33 + */
34 +var CSSPropertyOperations = {
35 +
36 + /**
37 + * Serializes a mapping of style properties for use as inline styles:
38 + *
39 + * > createMarkupForStyles({width: '200px', height: 0})
40 + * "width:200px;height:0;"
41 + *
42 + * Undefined values are ignored so that declarative programming is easier.
43 + *
44 + * @param {object} styles
45 + * @return {string}
46 + */
47 + createMarkupForStyles: function(styles) {
48 + var serialized = '';
49 + for (var styleName in styles) {
50 + if (!styles.hasOwnProperty(styleName)) {
51 + continue;
52 + }
53 + var styleValue = styles[styleName];
54 + if (typeof styleValue !== 'undefined') {
55 + serialized += processStyleName(styleName) + ':';
56 + serialized += dangerousStyleValue(styleName, styleValue) + ';';
57 + }
58 + }
59 + return serialized;
60 + },
61 +
62 + /**
63 + * Sets the value for multiple styles on a node.
64 + *
65 + * @param {DOMElement} node
66 + * @param {object} styles
67 + */
68 + setValueForStyles: function(node, styles) {
69 + var style = node.style;
70 + for (var styleName in styles) {
71 + if (!styles.hasOwnProperty(styleName)) {
72 + continue;
73 + }
74 + var styleValue = styles[styleName];
75 + style[styleName] = dangerousStyleValue(styleName, styleValue);
76 + }
77 + }
78 +
79 +};
80 +
81 +module.exports = CSSPropertyOperations;
src/domUtils/DOMChildrenOperations.js new
+146
@@ -0,0 +1,146 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule DOMChildrenOperations
17 + */
18 +
19 +"use strict";
20 +
21 +var Danger = require('Danger');
22 +
23 +var insertNodeAt = require('insertNodeAt');
24 +var keyOf = require('keyOf');
25 +var throwIf = require('throwIf');
26 +
27 +var NON_INCREASING_OPERATIONS;
28 +if (__DEV__) {
29 + NON_INCREASING_OPERATIONS =
30 + 'DOM child management operations must be provided in order ' +
31 + 'of increasing destination index. This is likely an issue with ' +
32 + 'the core framework.';
33 +}
34 +
35 +var MOVE_NODE_AT_ORIG_INDEX = keyOf({moveFrom: null});
36 +var INSERT_MARKUP = keyOf({insertMarkup: null});
37 +var REMOVE_AT = keyOf({removeAt: null});
38 +
39 +/**
40 + * In order to carry out movement of DOM nodes without knowing their IDs, we
41 + * have to first store knowledge about nodes' original indices before beginning
42 + * to carry out the sequence of operations. Once we begin the sequence, the DOM
43 + * indices in future instructions are no longer valid.
44 + *
45 + * @param {Element} parent Parent DOM node.
46 + * @param {Object} childOperations Description of child operations.
47 + * @returns {Array?} Sparse array containing elements by their current index in
48 + * the DOM.
49 + */
50 +var _getNodesByOriginalIndex = function(parent, childOperations) {
51 + var nodesByOriginalIndex; // Sparse array.
52 + var childOperation;
53 + var origIndex;
54 + for (var i = 0; i < childOperations.length; i++) {
55 + childOperation = childOperations[i];
56 + if (MOVE_NODE_AT_ORIG_INDEX in childOperation) {
57 + nodesByOriginalIndex = nodesByOriginalIndex || [];
58 + origIndex = childOperation.moveFrom;
59 + nodesByOriginalIndex[origIndex] = parent.childNodes[origIndex];
60 + } else if (REMOVE_AT in childOperation) {
61 + nodesByOriginalIndex = nodesByOriginalIndex || [];
62 + origIndex = childOperation.removeAt;
63 + nodesByOriginalIndex[origIndex] = parent.childNodes[origIndex];
64 + }
65 + }
66 + return nodesByOriginalIndex;
67 +};
68 +
69 +/**
70 + * Removes DOM elements from their parent, or moved.
71 + * @param {Element} parent Parent DOM node.
72 + * @param {Array} nodesByOriginalIndex Child nodes by their original index
73 + * (potentially sparse.)
74 + */
75 +var _removeChildrenByOriginalIndex = function(parent, nodesByOriginalIndex) {
76 + for (var j = 0; j < nodesByOriginalIndex.length; j++) {
77 + var nodeToRemove = nodesByOriginalIndex[j];
78 + if (nodeToRemove) { // We used a sparse array.
79 + parent.removeChild(nodesByOriginalIndex[j]);
80 + }
81 + }
82 +};
83 +
84 +/**
85 + * Once all nodes that will be removed or moved - are removed from the parent
86 + * node, we can begin the process of placing nodes into their final locations.
87 + * We must perform all operations in the order of the final DOM index -
88 + * otherwise, we couldn't count on the fact that an insertion at index X, will
89 + * remain at index X. This will iterate through the child operations, adding
90 + * content where needed, skip over removals (they've already been removed) and
91 + * insert "moved" Elements that were previously removed. The "moved" elements
92 + * are only temporarily removed from the parent, so that index calculations can
93 + * be manageable and perform well in the cases that matter.
94 + */
95 +var _placeNodesAtDestination =
96 + function(parent, childOperations, nodesByOriginalIndex) {
97 + var origNode;
98 + var finalIndex;
99 + var lastFinalIndex = -1;
100 + var childOperation;
101 + for (var k = 0; k < childOperations.length; k++) {
102 + childOperation = childOperations[k];
103 + if (MOVE_NODE_AT_ORIG_INDEX in childOperation) {
104 + origNode = nodesByOriginalIndex[childOperation.moveFrom];
105 + finalIndex = childOperation.finalIndex;
106 + insertNodeAt(parent, origNode, finalIndex);
107 + if (__DEV__) {
108 + throwIf(finalIndex <= lastFinalIndex, NON_INCREASING_OPERATIONS);
109 + lastFinalIndex = finalIndex;
110 + }
111 + } else if (REMOVE_AT in childOperation) {
112 + } else if (INSERT_MARKUP in childOperation) {
113 + finalIndex = childOperation.finalIndex;
114 + var markup = childOperation.insertMarkup;
115 + Danger.dangerouslyInsertMarkupAt(parent, markup, finalIndex);
116 + if (__DEV__) {
117 + throwIf(finalIndex <= lastFinalIndex, NON_INCREASING_OPERATIONS);
118 + lastFinalIndex = finalIndex;
119 + }
120 + }
121 + }
122 + };
123 +
124 +var manageChildren = function(parent, childOperations) {
125 + var nodesByOriginalIndex = _getNodesByOriginalIndex(parent, childOperations);
126 + if (nodesByOriginalIndex) {
127 + _removeChildrenByOriginalIndex(parent, nodesByOriginalIndex);
128 + }
129 + _placeNodesAtDestination(parent, childOperations, nodesByOriginalIndex);
130 +};
131 +
132 +var setTextNodeValueAtIndex = function(parent, index, val) {
133 + parent.childNodes[index].nodeValue = val;
134 +};
135 +
136 +/**
137 + * Also reexport all of the dangerous functions. It helps to have all dangerous
138 + * functions located in a single module `Danger`.
139 + */
140 +var DOMChildrenOperations = {
141 + dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup,
142 + manageChildren: manageChildren,
143 + setTextNodeValueAtIndex: setTextNodeValueAtIndex
144 +};
145 +
146 +module.exports = DOMChildrenOperations;
src/domUtils/DOMProperty.js new
+260
@@ -0,0 +1,260 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule DOMProperty
17 + * @typechecks
18 + */
19 +
20 +/*jslint bitwise: true */
21 +
22 +"use strict";
23 +
24 +var invariant = require('invariant');
25 +
26 +/**
27 + * DOMProperty exports lookup objects that can be used like functions:
28 + *
29 + * > DOMProperty.isValid['id']
30 + * true
31 + * > DOMProperty.isValid['foobar']
32 + * undefined
33 + *
34 + * Although this may be confusing, it performs better in general.
35 + *
36 + * @see http://jsperf.com/key-exists
37 + * @see http://jsperf.com/key-missing
38 + */
39 +var DOMProperty = {
40 +
41 + /**
42 + * Checks whether a property name is a standard property.
43 + * @type {Object}
44 + */
45 + isStandardName: {},
46 +
47 + /**
48 + * Mapping from normalized names to attribute names that differ. Attribute
49 + * names are used when rendering markup or with `*Attribute()`.
50 + * @type {Object}
51 + */
52 + getAttributeName: {},
53 +
54 + /**
55 + * Mapping from normalized names to properties on DOM node instances.
56 + * (This includes properties that mutate due to external factors.)
57 + * @type {Object}
58 + */
59 + getPropertyName: {},
60 +
61 + /**
62 + * Mapping from normalized names to mutation methods. This will only exist if
63 + * mutation cannot be set simply by the property or `setAttribute()`.
64 + * @type {Object}
65 + */
66 + getMutationMethod: {},
67 +
68 + /**
69 + * Whether the property must be accessed and mutated as an object property.
70 + * @type {Object}
71 + */
72 + mustUseAttribute: {},
73 +
74 + /**
75 + * Whether the property must be accessed and mutated using `*Attribute()`.
76 + * (This includes anything that fails `<propName> in <element>`.)
77 + * @type {Object}
78 + */
79 + mustUseProperty: {},
80 +
81 + /**
82 + * Whether the property should be removed when set to a falsey value.
83 + * @type {Object}
84 + */
85 + hasBooleanValue: {},
86 +
87 + /**
88 + * Whether or not setting a value causes side effects such as triggering
89 + * resources to be loaded or text selection changes. We must ensure that
90 + * the value is only set if it has changed.
91 + * @type {Object}
92 + */
93 + hasSideEffects: {},
94 +
95 + /**
96 + * Checks whether a property name is a custom attribute.
97 + * @method
98 + */
99 + isCustomAttribute: RegExp.prototype.test.bind(
100 + /^(data|aria)-[a-z_][a-z\d_.\-]*$/
101 + )
102 +};
103 +
104 +/**
105 + * Mapping from normalized, camelcased property names to a configuration that
106 + * specifies how the associated DOM property should be accessed or rendered.
107 + */
108 +var MustUseAttribute = 0x1;
109 +var MustUseProperty = 0x2;
110 +var HasBooleanValue = 0x4;
111 +var HasSideEffects = 0x8;
112 +
113 +var Properties = {
114 + /**
115 + * Standard Properties
116 + */
117 + accept: null,
118 + action: null,
119 + ajaxify: MustUseAttribute,
120 + allowFullScreen: MustUseAttribute | HasBooleanValue,
121 + alt: null,
122 + autoComplete: null,
123 + autoplay: HasBooleanValue,
124 + cellPadding: null,
125 + cellSpacing: null,
126 + checked: MustUseProperty | HasBooleanValue,
127 + className: MustUseProperty,
128 + colSpan: null,
129 + contentEditable: null,
130 + controls: MustUseProperty | HasBooleanValue,
131 + data: null, // For `<object />` acts as `src`.
132 + dir: null,
133 + disabled: MustUseProperty | HasBooleanValue,
134 + enctype: null,
135 + height: null,
136 + href: null,
137 + htmlFor: null,
138 + method: null,
139 + multiple: MustUseProperty | HasBooleanValue,
140 + name: null,
141 + poster: null,
142 + preload: null,
143 + placeholder: null,
144 + rel: null,
145 + required: HasBooleanValue,
146 + role: MustUseAttribute,
147 + scrollLeft: MustUseProperty,
148 + scrollTop: MustUseProperty,
149 + selected: MustUseProperty | HasBooleanValue,
150 + spellCheck: null,
151 + src: null,
152 + style: null,
153 + tabIndex: null,
154 + target: null,
155 + title: null,
156 + type: null,
157 + value: MustUseProperty | HasSideEffects,
158 + width: null,
159 + wmode: MustUseAttribute,
160 + /**
161 + * SVG Properties
162 + */
163 + cx: MustUseProperty,
164 + cy: MustUseProperty,
165 + d: MustUseProperty,
166 + fill: MustUseProperty,
167 + fx: MustUseProperty,
168 + fy: MustUseProperty,
169 + points: MustUseProperty,
170 + r: MustUseProperty,
171 + stroke: MustUseProperty,
172 + strokeLinecap: MustUseProperty,
173 + strokeWidth: MustUseProperty,
174 + transform: MustUseProperty,
175 + x: MustUseProperty,
176 + x1: MustUseProperty,
177 + x2: MustUseProperty,
178 + version: MustUseProperty,
179 + viewBox: MustUseProperty,
180 + y: MustUseProperty,
181 + y1: MustUseProperty,
182 + y2: MustUseProperty,
183 + spreadMethod: MustUseProperty,
184 + offset: MustUseProperty,
185 + stopColor: MustUseProperty,
186 + stopOpacity: MustUseProperty,
187 + gradientUnits: MustUseProperty,
188 + gradientTransform: MustUseProperty
189 +};
190 +
191 +/**
192 + * Attribute names not specified use the **lowercase** normalized name.
193 + */
194 +var DOMAttributeNames = {
195 + className: 'class',
196 + htmlFor: 'for',
197 + strokeLinecap: 'stroke-linecap',
198 + strokeWidth: 'stroke-width',
199 + stopColor: 'stop-color',
200 + stopOpacity: 'stop-opacity'
201 +};
202 +
203 +/**
204 + * Property names not specified use the normalized name.
205 + */
206 +var DOMPropertyNames = {
207 + autoComplete: 'autocomplete',
208 + spellCheck: 'spellcheck'
209 +};
210 +
211 +/**
212 + * Properties that require special mutation methods.
213 + */
214 +var DOMMutationMethods = {
215 + /**
216 + * Setting `className` to null may cause it to be set to the string "null".
217 + *
218 + * @param {DOMElement} node
219 + * @param {*} value
220 + */
221 + className: function(node, value) {
222 + node.className = value || '';
223 + }
224 +};
225 +
226 +for (var propName in Properties) {
227 + DOMProperty.isStandardName[propName] = true;
228 +
229 + DOMProperty.getAttributeName[propName] =
230 + DOMAttributeNames[propName] || propName.toLowerCase();
231 +
232 + DOMProperty.getPropertyName[propName] =
233 + DOMPropertyNames[propName] || propName;
234 +
235 + var mutationMethod = DOMMutationMethods[propName];
236 + if (mutationMethod) {
237 + DOMProperty.getMutationMethod[propName] = mutationMethod;
238 + }
239 +
240 + var propConfig = Properties[propName];
241 + DOMProperty.mustUseAttribute[propName] = propConfig & MustUseAttribute;
242 + DOMProperty.mustUseProperty[propName] = propConfig & MustUseProperty;
243 + DOMProperty.hasBooleanValue[propName] = propConfig & HasBooleanValue;
244 + DOMProperty.hasSideEffects[propName] = propConfig & HasSideEffects;
245 +
246 + invariant(
247 + !DOMProperty.mustUseAttribute[propName] ||
248 + !DOMProperty.mustUseProperty[propName],
249 + 'DOMProperty: Cannot use require using both attribute and property: %s',
250 + propName
251 + );
252 + invariant(
253 + DOMProperty.mustUseProperty[propName] ||
254 + !DOMProperty.hasSideEffects[propName],
255 + 'DOMProperty: Properties that have side effects must use property: %s',
256 + propName
257 + );
258 +}
259 +
260 +module.exports = DOMProperty;
src/domUtils/DOMPropertyOperations.js new
+93
@@ -0,0 +1,93 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule DOMPropertyOperations
17 + * @typechecks
18 + */
19 +
20 +"use strict";
21 +
22 +var DOMProperty = require('DOMProperty');
23 +
24 +var escapeTextForBrowser = require('escapeTextForBrowser');
25 +var memoizeStringOnly = require('memoizeStringOnly');
26 +
27 +var processAttributeNameAndPrefix = memoizeStringOnly(function(name) {
28 + return escapeTextForBrowser(name) + '="';
29 +});
30 +
31 +/**
32 + * Operations for dealing with DOM properties.
33 + */
34 +var DOMPropertyOperations = {
35 +
36 + /**
37 + * Creates markup for a property.
38 + *
39 + * @param {string} name
40 + * @param {*} value
41 + * @return {?string} Markup string, or null if the property was invalid.
42 + */
43 + createMarkupForProperty: function(name, value) {
44 + if (DOMProperty.isStandardName[name]) {
45 + if (value == null || DOMProperty.hasBooleanValue[name] && !value) {
46 + return '';
47 + }
48 + var attributeName = DOMProperty.getAttributeName[name];
49 + return processAttributeNameAndPrefix(attributeName) +
50 + escapeTextForBrowser(value) + '"';
51 + } else if (DOMProperty.isCustomAttribute(name)) {
52 + if (value == null) {
53 + return '';
54 + }
55 + return processAttributeNameAndPrefix(name) +
56 + escapeTextForBrowser(value) + '"';
57 + } else {
58 + return null;
59 + }
60 + },
61 +
62 + /**
63 + * Sets the value for a property on a node.
64 + *
65 + * @param {DOMElement} node
66 + * @param {string} name
67 + * @param {*} value
68 + */
69 + setValueForProperty: function(node, name, value) {
70 + if (DOMProperty.isStandardName[name]) {
71 + var mutationMethod = DOMProperty.getMutationMethod[name];
72 + if (mutationMethod) {
73 + mutationMethod(node, value);
74 + } else if (DOMProperty.mustUseAttribute[name]) {
75 + if (DOMProperty.hasBooleanValue[name] && !value) {
76 + node.removeAttribute(DOMProperty.getAttributeName[name]);
77 + } else {
78 + node.setAttribute(DOMProperty.getAttributeName[name], value);
79 + }
80 + } else {
81 + var propName = DOMProperty.getPropertyName[name];
82 + if (!DOMProperty.hasSideEffects[name] || node[propName] !== value) {
83 + node[propName] = value;
84 + }
85 + }
86 + } else if (DOMProperty.isCustomAttribute(name)) {
87 + node.setAttribute(name, value);
88 + }
89 + }
90 +
91 +};
92 +
93 +module.exports = DOMPropertyOperations;
src/domUtils/Danger.js new
+158
@@ -0,0 +1,158 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule Danger
17 + */
18 +
19 +"use strict";
20 +
21 +var ExecutionEnvironment = require('ExecutionEnvironment');
22 +
23 +var throwIf = require('throwIf');
24 +
25 +var DOM_UNSUPPORTED;
26 +var NO_MARKUP_PARENT;
27 +var NO_MULTI_MARKUP;
28 +if (__DEV__) {
29 + DOM_UNSUPPORTED =
30 + 'You may not insert markup into the document while you are in a worker ' +
31 + 'thread. It\'s not you, it\'s me. This is likely the fault of the ' +
32 + 'framework. Please report this immediately.';
33 + NO_MARKUP_PARENT =
34 + 'You have attempted to inject markup without a suitable parent. This is ' +
35 + 'likely the fault of the framework - please report immediately.';
36 + NO_MULTI_MARKUP =
37 + 'The framework has attempted to either insert zero or multiple markup ' +
38 + 'roots into a single location when it should not. This is a serious ' +
39 + 'error - a fault of the framework - please report immediately.';
40 +}
41 +
42 +var validateMarkupParams;
43 +if (__DEV__) {
44 + validateMarkupParams = function(parentNode, markup) {
45 + throwIf(!ExecutionEnvironment.canUseDOM, DOM_UNSUPPORTED);
46 + throwIf(!parentNode || !parentNode.tagName, NO_MARKUP_PARENT);
47 + throwIf(!markup, NO_MULTI_MARKUP);
48 + };
49 +}
50 +
51 +var dummies = {};
52 +
53 +function getParentDummy(parent) {
54 + var parentTag = parent.tagName;
55 + return dummies[parentTag] ||
56 + (dummies[parentTag] = document.createElement(parentTag));
57 +}
58 +
59 +/**
60 + * Inserts node after 'after'. If 'after' is null, inserts it after nothing,
61 + * which is inserting it at the beginning.
62 + *
63 + * @param {Element} elem Parent element.
64 + * @param {Element} insert Element to insert.
65 + * @param {Element} after Element to insert after.
66 + * @returns {Element} Element that was inserted.
67 + */
68 +function insertNodeAfterNode(elem, insert, after) {
69 + if (__DEV__) {
70 + throwIf(!ExecutionEnvironment.canUseDOM, DOM_UNSUPPORTED);
71 + }
72 + if (after) {
73 + if (after.nextSibling) {
74 + return elem.insertBefore(insert, after.nextSibling);
75 + } else {
76 + return elem.appendChild(insert);
77 + }
78 + } else {
79 + return elem.insertBefore(insert, elem.firstChild);
80 + }
81 +}
82 +
83 +/**
84 + * Slow: Should only be used when it is known there are a few (or one) element
85 + * in the node list.
86 + * @param {Element} parentRootDomNode Parent element.
87 + * @param {HTMLCollection} htmlCollection HTMLCollection to insert.
88 + * @param {Element} after Element to insert the node list after.
89 + */
90 +function inefficientlyInsertHTMLCollectionAfter(
91 + parentRootDomNode,
92 + htmlCollection,
93 + after) {
94 +
95 + if (__DEV__) {
96 + throwIf(!ExecutionEnvironment.canUseDOM, DOM_UNSUPPORTED);
97 + }
98 + var ret;
99 + var originalLength = htmlCollection.length;
100 + // Access htmlCollection[0] because htmlCollection shrinks as we remove items.
101 + // `insertNodeAfterNode` will remove items from the htmlCollection.
102 + for (var i = 0; i < originalLength; i++) {
103 + ret =
104 + insertNodeAfterNode(parentRootDomNode, htmlCollection[0], ret || after);
105 + }
106 +}
107 +
108 +/**
109 + * Super-dangerously inserts markup into existing DOM structure. Seriously, you
110 + * don't want to use this module unless you are building a framework. This
111 + * requires that the markup that you are inserting represents the root of a
112 + * tree. We do not support the case where there `markup` represents several
113 + * roots.
114 + *
115 + * @param {Element} parentNode Parent DOM element.
116 + * @param {string} markup Markup to dangerously insert.
117 + * @param {number} index Position to insert markup at.
118 + */
119 +function dangerouslyInsertMarkupAt(parentNode, markup, index) {
120 + if (__DEV__) {
121 + validateMarkupParams(parentNode, markup);
122 + }
123 + var parentDummy = getParentDummy(parentNode);
124 + parentDummy.innerHTML = markup;
125 + var htmlCollection = parentDummy.childNodes;
126 + var afterNode = index ? parentNode.childNodes[index - 1] : null;
127 + inefficientlyInsertHTMLCollectionAfter(parentNode, htmlCollection, afterNode);
128 +}
129 +
130 +/**
131 + * Replaces a node with a string of markup at its current position within its
132 + * parent. `childNode` must be in the document (or at least within a parent
133 + * node). The string of markup must represent a tree of markup with a single
134 + * root.
135 + *
136 + * @param {Element} childNode Child node to replace.
137 + * @param {string} markup Markup to dangerously replace child with.
138 + */
139 +function dangerouslyReplaceNodeWithMarkup(childNode, markup) {
140 + var parentNode = childNode.parentNode;
141 + if (__DEV__) {
142 + validateMarkupParams(parentNode, markup);
143 + }
144 + var parentDummy = getParentDummy(parentNode);
145 + parentDummy.innerHTML = markup;
146 + var htmlCollection = parentDummy.childNodes;
147 + if (__DEV__) {
148 + throwIf(htmlCollection.length !== 1, NO_MULTI_MARKUP);
149 + }
150 + parentNode.replaceChild(htmlCollection[0], childNode);
151 +}
152 +
153 +var Danger = {
154 + dangerouslyInsertMarkupAt: dangerouslyInsertMarkupAt,
155 + dangerouslyReplaceNodeWithMarkup: dangerouslyReplaceNodeWithMarkup
156 +};
157 +
158 +module.exports = Danger;
src/domUtils/__tests__/CSSPropertyOperations-test.js new
+52
@@ -0,0 +1,52 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +describe('CSSPropertyOperations', function() {
23 + var CSSPropertyOperations;
24 +
25 + beforeEach(function() {
26 + require('mock-modules').dumpCache();
27 + CSSPropertyOperations = require('CSSPropertyOperations');
28 + });
29 +
30 + it('should create markup for simple styles', function() {
31 + expect(CSSPropertyOperations.createMarkupForStyles({
32 + backgroundColor: '#3b5998',
33 + display: 'none'
34 + })).toBe('background-color:#3b5998;display:none;');
35 + });
36 +
37 + it('should ignore undefined styles', function() {
38 + expect(CSSPropertyOperations.createMarkupForStyles({
39 + backgroundColor: undefined,
40 + display: 'none'
41 + })).toBe('display:none;');
42 + });
43 +
44 + it('should automatically append `px` to relevant styles', function() {
45 + expect(CSSPropertyOperations.createMarkupForStyles({
46 + margin: 16,
47 + opacity: 0.5,
48 + padding: '4px'
49 + })).toBe('margin:16px;opacity:0.5;padding:4px;');
50 + });
51 +
52 +});
src/domUtils/__tests__/DOMPropertyOperations-test.js new
+129
@@ -0,0 +1,129 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +describe('DOMPropertyOperations', function() {
23 + var DOMPropertyOperations;
24 +
25 + beforeEach(function() {
26 + require('mock-modules').dumpCache();
27 + DOMPropertyOperations = require('DOMPropertyOperations');
28 + });
29 +
30 + describe('createMarkupForProperty', function() {
31 +
32 + it('should create markup for simple properties', function() {
33 + expect(DOMPropertyOperations.createMarkupForProperty(
34 + 'name',
35 + 'simple'
36 + )).toBe('name="simple"');
37 +
38 + expect(DOMPropertyOperations.createMarkupForProperty(
39 + 'name',
40 + false
41 + )).toBe('name="false"');
42 +
43 + expect(DOMPropertyOperations.createMarkupForProperty(
44 + 'name',
45 + null
46 + )).toBe('');
47 + });
48 +
49 + it('should create markup for boolean properties', function() {
50 + expect(DOMPropertyOperations.createMarkupForProperty(
51 + 'checked',
52 + 'simple'
53 + )).toBe('checked="simple"');
54 +
55 + expect(DOMPropertyOperations.createMarkupForProperty(
56 + 'checked',
57 + true
58 + )).toBe('checked="true"');
59 +
60 + expect(DOMPropertyOperations.createMarkupForProperty(
61 + 'checked',
62 + false
63 + )).toBe('');
64 + });
65 +
66 + it('should create markup for custom attributes', function() {
67 + expect(DOMPropertyOperations.createMarkupForProperty(
68 + 'aria-label',
69 + 'simple'
70 + )).toBe('aria-label="simple"');
71 +
72 + expect(DOMPropertyOperations.createMarkupForProperty(
73 + 'aria-label',
74 + false
75 + )).toBe('aria-label="false"');
76 +
77 + expect(DOMPropertyOperations.createMarkupForProperty(
78 + 'aria-label',
79 + null
80 + )).toBe('');
81 + });
82 +
83 + });
84 +
85 + describe('setValueForProperty', function() {
86 + var stubNode;
87 +
88 + beforeEach(function() {
89 + stubNode = document.createElement('div');
90 + });
91 +
92 + it('should set values as properties by default', function() {
93 + DOMPropertyOperations.setValueForProperty(stubNode, 'title', 'Tip!');
94 + expect(stubNode.title).toBe('Tip!');
95 + });
96 +
97 + it('should set values as attributes if necessary', function() {
98 + DOMPropertyOperations.setValueForProperty(stubNode, 'ajaxify', '#');
99 + expect(stubNode.getAttribute('ajaxify')).toBe('#');
100 + expect(stubNode.ajaxify).toBeUndefined();
101 + });
102 +
103 + it('should remove for falsey boolean properties', function() {
104 + DOMPropertyOperations.setValueForProperty(
105 + stubNode,
106 + 'allowFullScreen',
107 + false
108 + );
109 + expect(stubNode.hasAttribute('allowFullScreen')).toBe(false);
110 + });
111 +
112 + it('should use mutation method where applicable', function() {
113 + DOMPropertyOperations.setValueForProperty(
114 + stubNode,
115 + 'className',
116 + 'selected'
117 + );
118 + expect(stubNode.className).toBe('selected');
119 +
120 + DOMPropertyOperations.setValueForProperty(
121 + stubNode,
122 + 'className',
123 + null
124 + );
125 + expect(stubNode.className).toBe('');
126 + });
127 +
128 + });
129 +});
src/domUtils/dangerousStyleValue.js new
+51
@@ -0,0 +1,51 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule dangerousStyleValue
17 + * @typechecks
18 + */
19 +
20 +"use strict";
21 +
22 +var CSSProperty = require('CSSProperty');
23 +
24 +/**
25 + * Convert a value into the proper css writable value. The `styleName` name
26 + * name should be logical (no hyphens), as specified in `CSSProperty.isNumber`.
27 + *
28 + * @param {string} styleName CSS property name such as `topMargin`.
29 + * @param {*} value CSS property value such as `10px`.
30 + * @return {string} Normalized style value with dimensions applied.
31 + */
32 +function dangerousStyleValue(styleName, value) {
33 + // Note that we've removed escapeTextForBrowser() calls here since the
34 + // whole string will be escaped when the attribute is injected into
35 + // the markup. If you provide unsafe user data here they can inject
36 + // arbitrary CSS which may be problematic (I couldn't repro this):
37 + // https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
38 + // http://www.thespanner.co.uk/2007/11/26/ultimate-xss-css-injection/
39 + // This is not an XSS hole but instead a potential CSS injection issue
40 + // which has lead to a greater discussion about how we're going to
41 + // trust URLs moving forward. See #2115901
42 + if (value === null || value === false || value === true || value === '') {
43 + return '';
44 + }
45 + if (isNaN(value)) {
46 + return !value ? '' : '' + value;
47 + }
48 + return CSSProperty.isNumber[styleName] ? '' + value : (value + 'px');
49 +}
50 +
51 +module.exports = dangerousStyleValue;
src/domUtils/getDOMNodeID.js new
+38
@@ -0,0 +1,38 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule getDOMNodeID
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * Accessing "id" or calling getAttribute('id') on a form element can return its
23 + * control whose name or ID is "id". However, not all DOM nodes support
24 + * `getAttributeNode` (document - which is not a form) so that is checked first.
25 + *
26 + * @param {Element} domNode DOM node element to return ID of.
27 + * @returns {string} The ID of `domNode`.
28 + */
29 +function getDOMNodeID(domNode) {
30 + if (domNode.getAttributeNode) {
31 + var attributeNode = domNode.getAttributeNode('id');
32 + return attributeNode && attributeNode.value || '';
33 + } else {
34 + return domNode.id || '';
35 + }
36 +}
37 +
38 +module.exports = getDOMNodeID;
src/domUtils/getTextContentAccessor.js new
+40
@@ -0,0 +1,40 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule getTextContentAccessor
17 + */
18 +
19 +"use strict";
20 +
21 +var ExecutionEnvironment = require('ExecutionEnvironment');
22 +
23 +var contentKey = null;
24 +
25 +/**
26 + * Gets the key used to access text content on a DOM node.
27 + *
28 + * @return {?string} Key used to access text content.
29 + * @internal
30 + */
31 +function getTextContentAccessor() {
32 + if (!contentKey && ExecutionEnvironment.canUseDOM) {
33 + contentKey = 'innerText' in document.createElement('div') ?
34 + 'innerText' :
35 + 'textContent';
36 + }
37 + return contentKey;
38 +}
39 +
40 +module.exports = getTextContentAccessor;
src/domUtils/insertNodeAt.js new
+46
@@ -0,0 +1,46 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule insertNodeAt
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * Inserts `node` at a particular child index. Other nodes move to make room.
23 + * @param {!Element} root The parent root node to insert into.
24 + * @param {!node} node The node to insert.
25 + * @param {!number} atIndex The index in `root` that `node` should exist at.
26 + */
27 +function insertNodeAt(root, node, atIndex) {
28 + var childNodes = root.childNodes;
29 + // Remove from parent so that if node is already child of root,
30 + // `childNodes[atIndex]` already takes into account the removal.
31 + var curAtIndex = root.childNodes[atIndex];
32 + if (curAtIndex === node) {
33 + return node;
34 + }
35 + if (node.parentNode) {
36 + node.parentNode.removeChild(node);
37 + }
38 + if (atIndex >= childNodes.length) {
39 + root.appendChild(node);
40 + } else {
41 + root.insertBefore(node, childNodes[atIndex]);
42 + }
43 + return node;
44 +}
45 +
46 +module.exports = insertNodeAt;
src/domUtils/isEventSupported.js new
+62
@@ -0,0 +1,62 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule isEventSupported
17 + */
18 +
19 +"use strict";
20 +
21 +var ExecutionEnvironment = require('ExecutionEnvironment');
22 +
23 +var testNode;
24 +if (ExecutionEnvironment.canUseDOM) {
25 + testNode = document.createElement('div');
26 +}
27 +
28 +/**
29 + * Checks if an event is supported in the current execution environment.
30 + *
31 + * NOTE: This will not work correctly for non-generic events such as `change`,
32 + * `reset`, `load`, `error`, and `select`.
33 + *
34 + * Borrows from Modernizr.
35 + *
36 + * @param {string} eventNameSuffix Event name, e.g. "click".
37 + * @param {?boolean} capture Check if the capture phase is supported.
38 + * @return {boolean} True if the event is supported.
39 + * @internal
40 + * @license Modernizr 3.0.0pre (Custom Build) | MIT
41 + */
42 +function isEventSupported(eventNameSuffix, capture) {
43 + if (!testNode || (capture && !testNode.addEventListener)) {
44 + return false;
45 + }
46 + var element = document.createElement('div');
47 + var eventName = 'on' + eventNameSuffix;
48 + var isSupported = eventName in element;
49 +
50 + if (!isSupported) {
51 + element.setAttribute(eventName, '');
52 + isSupported = typeof element[eventName] === 'function';
53 + if (typeof element[eventName] !== 'undefined') {
54 + element[eventName] = undefined;
55 + }
56 + element.removeAttribute(eventName);
57 + }
58 + element = null;
59 + return isSupported;
60 +}
61 +
62 +module.exports = isEventSupported;
src/environment/ExecutionEnvironment.js new
+43
@@ -0,0 +1,43 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ExecutionEnvironment
17 + */
18 +
19 +/*jslint evil: true */
20 +
21 +"use strict";
22 +
23 +var canUseDOM = typeof window !== 'undefined';
24 +
25 +/**
26 + * Simple, lightweight module assisting with the detection and context of
27 + * Worker. Helps avoid circular dependencies and allows code to reason about
28 + * whether or not they are in a Worker, even if they never include the main
29 + * `ReactWorker` dependency.
30 + */
31 +var ExecutionEnvironment = {
32 +
33 + canUseDOM: canUseDOM,
34 +
35 + canUseWorkers: typeof Worker !== 'undefined',
36 +
37 + isInWorker: !canUseDOM, // For now, this is true - might change in the future.
38 +
39 + global: new Function('return this;')()
40 +
41 +};
42 +
43 +module.exports = ExecutionEnvironment;
src/event/AbstractEvent.js new
+276
@@ -0,0 +1,276 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule AbstractEvent
17 + */
18 +
19 +"use strict";
20 +
21 +var BrowserEnv = require('BrowserEnv');
22 +var PooledClass = require('PooledClass');
23 +var TouchEventUtils = require('TouchEventUtils');
24 +
25 +var throwIf = require('throwIf');
26 +
27 +
28 +// Only accessed in __DEV__
29 +var CLONE_TYPE_ERR;
30 +if (__DEV__) {
31 + CLONE_TYPE_ERR =
32 + 'You may only clone instances of AbstractEvent for ' +
33 + 'persistent references. Check yourself.';
34 +}
35 +var MAX_POOL_SIZE = 20;
36 +
37 +/**
38 + * AbstractEvent copy constructor. @see `PooledClass`. Provides a single place
39 + * to define all cross browser normalization of DOM events. Does not attempt to
40 + * extend a native event, rather creates a completely new object that has a
41 + * reference to the nativeEvent through .nativeEvent member. The property .data
42 + * should hold all data that is extracted from the event in a cross browser
43 + * manner. Application code should use the data field when possible, not the
44 + * unreliable native event.
45 + */
46 +function AbstractEvent(
47 + abstractEventType,
48 + abstractTargetID, // Allows the abstract target to differ from native.
49 + originatingTopLevelEventType,
50 + nativeEvent,
51 + data) {
52 + this.type = abstractEventType;
53 + this.abstractTargetID = abstractTargetID || '';
54 + this.originatingTopLevelEventType = originatingTopLevelEventType;
55 + this.nativeEvent = nativeEvent;
56 + this.data = data;
57 + // TODO: Deprecate storing target - doesn't always make sense for some types
58 + this.target = nativeEvent && nativeEvent.target;
59 +
60 + /**
61 + * As a performance optimization, we tag the existing event with the listeners
62 + * (or listener [singular] if only one). This avoids having to package up an
63 + * abstract event along with the set of listeners into a wrapping "dispatch"
64 + * object. No one should ever read this property except event system and
65 + * plugin/dispatcher code. We also tag the abstract event with a parallel
66 + * ID array. _dispatchListeners[i] is being dispatched to a DOM node at ID
67 + * _dispatchIDs[i]. The lengths should never, ever, ever be different.
68 + */
69 + this._dispatchListeners = null;
70 + this._dispatchIDs = null;
71 +
72 + this.isPropagationStopped = false;
73 +}
74 +
75 +/** `PooledClass` looks for this. */
76 +AbstractEvent.poolSize = MAX_POOL_SIZE;
77 +
78 +/**
79 + * `PooledClass` looks for `destructor` on each instance it releases. We need to
80 + * ensure that we remove all references to listeners which could trap large
81 + * amounts of memory in their closures.
82 + */
83 +AbstractEvent.prototype.destructor = function() {
84 + this.target = null;
85 + this._dispatchListeners = null;
86 + this._dispatchIDs = null;
87 +};
88 +
89 +/**
90 + * Enhance the `AbstractEvent` class to have pooling abilities. We instruct
91 + * `PooledClass` that our copy constructor accepts five arguments (this is just
92 + * a performance optimization). These objects are instantiated frequently.
93 + */
94 +PooledClass.addPoolingTo(AbstractEvent, PooledClass.fiveArgumentPooler);
95 +
96 +AbstractEvent.prototype.stopPropagation = function() {
97 + this.isPropagationStopped = true;
98 + if (this.nativeEvent.stopPropagation) {
99 + this.nativeEvent.stopPropagation();
100 + }
101 + // IE8 only understands cancelBubble, not stopPropagation().
102 + this.nativeEvent.cancelBubble = true;
103 +};
104 +
105 +AbstractEvent.prototype.preventDefault = function() {
106 + AbstractEvent.preventDefaultOnNativeEvent(this.nativeEvent);
107 +};
108 +
109 +/**
110 + * Utility function for preventing default in cross browser manner.
111 + */
112 +AbstractEvent.preventDefaultOnNativeEvent = function(nativeEvent) {
113 + if (nativeEvent.preventDefault) {
114 + nativeEvent.preventDefault();
115 + } else {
116 + nativeEvent.returnValue = false;
117 + }
118 +};
119 +
120 +/**
121 + * @param {Element} target The target element.
122 + */
123 +AbstractEvent.normalizeScrollDataFromTarget = function(target) {
124 + return {
125 + scrollTop: target.scrollTop,
126 + scrollLeft: target.scrollLeft,
127 + clientWidth: target.clientWidth,
128 + clientHeight: target.clientHeight,
129 + scrollHeight: target.scrollHeight,
130 + scrollWidth: target.scrollWidth
131 + };
132 +};
133 +
134 +/*
135 + * There are some normalizations that need to happen for various browsers. In
136 + * addition to replacing the general event fixing with a framework such as
137 + * jquery, we need to normalize mouse events here. Code below is mostly borrowed
138 + * from: jScrollPane/script/jquery.mousewheel.js
139 + */
140 +AbstractEvent.normalizeMouseWheelData = function(nativeEvent) {
141 + var delta = 0;
142 + var deltaX = 0;
143 + var deltaY = 0;
144 +
145 + /* traditional scroll wheel data */
146 + if ( nativeEvent.wheelDelta ) { delta = nativeEvent.wheelDelta/120; }
147 + if ( nativeEvent.detail ) { delta = -nativeEvent.detail/3; }
148 +
149 + /* Multidimensional scroll (touchpads) with deltas */
150 + deltaY = delta;
151 +
152 + /* Gecko based browsers */
153 + if (nativeEvent.axis !== undefined &&
154 + nativeEvent.axis === nativeEvent.HORIZONTAL_AXIS ) {
155 + deltaY = 0;
156 + deltaX = -delta;
157 + }
158 +
159 + /* Webkit based browsers */
160 + if (nativeEvent.wheelDeltaY !== undefined ) {
161 + deltaY = nativeEvent.wheelDeltaY/120;
162 + }
163 + if (nativeEvent.wheelDeltaX !== undefined ) {
164 + deltaX = -nativeEvent.wheelDeltaX/120;
165 + }
166 +
167 + return { delta: delta, deltaX: deltaX, deltaY: deltaY };
168 +};
169 +
170 +/**
171 + * I <3 Quirksmode.org:
172 + * http://www.quirksmode.org/js/events_properties.html
173 + */
174 +AbstractEvent.isNativeClickEventRightClick = function(nativeEvent) {
175 + return nativeEvent.which ? nativeEvent.which === 3 :
176 + nativeEvent.button ? nativeEvent.button === 2 :
177 + false;
178 +};
179 +
180 +AbstractEvent.normalizePointerData = function(nativeEvent) {
181 + return {
182 + globalX: AbstractEvent.eventPageX(nativeEvent),
183 + globalY: AbstractEvent.eventPageY(nativeEvent),
184 + rightMouseButton:
185 + AbstractEvent.isNativeClickEventRightClick(nativeEvent)
186 + };
187 +};
188 +
189 +AbstractEvent.normalizeDragEventData =
190 + function(nativeEvent, globalX, globalY, startX, startY) {
191 + return {
192 + globalX: globalX,
193 + globalY: globalY,
194 + startX: startX,
195 + startY: startY
196 + };
197 + };
198 +
199 +/**
200 + * Warning: It is possible to move your finger on a touch surface, yet not
201 + * effect the `eventPageX/Y` because the touch had caused a scroll that
202 + * compensated for your movement. To track movements across the page, prevent
203 + * default to avoid scrolling, and control scrolling in javascript.
204 + */
205 +
206 +/**
207 + * Gets the exact position of a touch/mouse event on the page with respect to
208 + * the document body. The only reason why this method is needed instead of using
209 + * `TouchEventUtils.extractSingleTouch` is to support IE8-. Mouse events in all
210 + * browsers except IE8- contain a pageY. IE8 and below require clientY
211 + * computation:
212 + *
213 + * @param {Event} nativeEvent Native event, possibly touch or mouse.
214 + * @return {number} Coordinate with respect to document body.
215 + */
216 +AbstractEvent.eventPageY = function(nativeEvent) {
217 + var singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent);
218 + if (singleTouch) {
219 + return singleTouch.pageY;
220 + } else if (typeof nativeEvent.pageY !== 'undefined') {
221 + return nativeEvent.pageY;
222 + } else {
223 + return nativeEvent.clientY + BrowserEnv.currentPageScrollTop;
224 + }
225 +};
226 +
227 +/**
228 + * @see `AbstractEvent.eventPageY`.
229 + *
230 + * @param {Event} nativeEvent Native event, possibly touch or mouse.
231 + * @return {number} Coordinate with respect to document body.
232 + */
233 +AbstractEvent.eventPageX = function(nativeEvent) {
234 + var singleTouch = TouchEventUtils.extractSingleTouch(nativeEvent);
235 + if (singleTouch) {
236 + return singleTouch.pageX;
237 + } else if (typeof nativeEvent.pageX !== 'undefined') {
238 + return nativeEvent.pageX;
239 + } else {
240 + return nativeEvent.clientX + BrowserEnv.currentPageScrollLeft;
241 + }
242 +};
243 +
244 +/**
245 + * A semantic API around cloning an event for use in another event loop. We
246 + * clear out all dispatched `AbstractEvent`s after each event loop, adding them
247 + * back into the pool. This allows a way to hold onto a reference that won't be
248 + * added back into the pool. Please note that `AbstractEvent.nativeEvent` is
249 + * *not* cloned and you will run into problems in IE if you assume that it will
250 + * be! The moral of that story is to always normalize any data you need into the
251 + * `.data` field. The data field is not cloned either, but there won't be any
252 + * issues related to use of `.data` in a future event cycle so long as no part
253 + * of your application mutates it. We don't clone the private fields because
254 + * your application should never be accessing them.
255 + *
256 + * - TODO: In __DEV__ when "releasing" events, don't put them back into the
257 + * pool. Instead add ES5 getters on all their fields that throw errors so you
258 + * can detect any application that's hanging onto events and reusing them.
259 + * In prod - we can put them back into the pool for reuse.
260 + */
261 +AbstractEvent.persistentCloneOf = function(abstractEvent) {
262 + if (__DEV__) {
263 + throwIf(!(abstractEvent instanceof AbstractEvent), CLONE_TYPE_ERR);
264 + }
265 + return new AbstractEvent(
266 + abstractEvent.type,
267 + abstractEvent.abstractTargetID,
268 + abstractEvent.originatingTopLevelEventType,
269 + abstractEvent.nativeEvent,
270 + abstractEvent.data,
271 + abstractEvent.target
272 + );
273 +};
274 +
275 +module.exports = AbstractEvent;
276 +
src/event/CallbackRegistry.js new
+72
@@ -0,0 +1,72 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule CallbackRegistry
17 + */
18 +
19 +"use strict";
20 +
21 +var listenerBank = {};
22 +
23 +/**
24 + * Stores "listeners" by `registrationName`/`id`. There should be at most one
25 + * "listener" per `registrationName/id` in the `listenerBank`.
26 + * Access listeners via `listenerBank[registrationName][id]`
27 + *
28 + * @constructor CallbackRegistry
29 + */
30 +var CallbackRegistry = {
31 +
32 + /**
33 + * Stores `listener` at `listenerBank[registrationName][id]. Is idempotent.
34 + * @param {string} domID The id of the DOM node.
35 + * @param {string} registrationName The name of listener (`onClick` etc).
36 + * @param {Function} listener The callback to to store.
37 + */
38 + putListener: function(id, registrationName, listener) {
39 + var bankForRegistrationName =
40 + listenerBank[registrationName] || (listenerBank[registrationName] = {});
41 + bankForRegistrationName[id] = listener;
42 + },
43 +
44 + /**
45 + * @param {string} id.
46 + * @param {string} registrationName Name of registration (`onClick` etc).
47 + * @return {Function?} The Listener
48 + */
49 + getListener: function(id, registrationName) {
50 + var bankForRegistrationName = listenerBank[registrationName];
51 + return bankForRegistrationName && bankForRegistrationName[id];
52 + },
53 +
54 + /**
55 + * Deletes the listener from the registration bank.
56 + * @param {string} id
57 + * @param {string} registrationName (`onClick` etc).
58 + */
59 + deleteListener: function(id, registrationName) {
60 + var bankForRegistrationName = listenerBank[registrationName];
61 + if (bankForRegistrationName) {
62 + delete bankForRegistrationName[id];
63 + }
64 + },
65 +
66 + // This is needed for tests only. Do not use in real life
67 + __purge: function() {
68 + listenerBank = {};
69 + }
70 +};
71 +
72 +module.exports = CallbackRegistry;
src/event/EventConstants.js new
+57
@@ -0,0 +1,57 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule EventConstants
17 + */
18 +
19 +"use strict";
20 +
21 +var keyMirror = require('keyMirror');
22 +
23 +var PropagationPhases = keyMirror({bubbled: null, captured: null});
24 +
25 +/**
26 + * Types of raw signals from the browser caught at the top level.
27 + */
28 +var topLevelTypes = keyMirror({
29 + topBlur: null,
30 + topChange: null,
31 + topClick: null,
32 + topDOMCharacterDataModified: null,
33 + topDoubleClick: null,
34 + topFocus: null,
35 + topKeyDown: null,
36 + topKeyPress: null,
37 + topKeyUp: null,
38 + topMouseDown: null,
39 + topMouseMove: null,
40 + topMouseOut: null,
41 + topMouseOver: null,
42 + topMouseUp: null,
43 + topMouseWheel: null,
44 + topScroll: null,
45 + topSubmit: null,
46 + topTouchCancel: null,
47 + topTouchEnd: null,
48 + topTouchMove: null,
49 + topTouchStart: null
50 +});
51 +
52 +var EventConstants = {
53 + topLevelTypes: topLevelTypes,
54 + PropagationPhases: PropagationPhases
55 +};
56 +
57 +module.exports = EventConstants;
src/event/EventPluginHub.js new
+324
@@ -0,0 +1,324 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule EventPluginHub
17 + */
18 +
19 +"use strict";
20 +
21 +var AbstractEvent = require('AbstractEvent');
22 +var CallbackRegistry = require('CallbackRegistry');
23 +var EventPluginUtils = require('EventPluginUtils');
24 +var EventPropagators = require('EventPropagators');
25 +var ExecutionEnvironment = require('ExecutionEnvironment');
26 +
27 +var accumulate = require('accumulate');
28 +var forEachAccumulated = require('forEachAccumulated');
29 +var keyMirror = require('keyMirror');
30 +var merge = require('merge');
31 +var throwIf = require('throwIf');
32 +
33 +var deleteListener = CallbackRegistry.deleteListener;
34 +
35 +var ERRORS = keyMirror({
36 + DOUBLE_REGISTER: null,
37 + DOUBLE_ENQUEUE: null,
38 + DEPENDENCY_ERROR: null
39 +});
40 +
41 +if (__DEV__) {
42 + ERRORS.DOUBLE_REGISTER =
43 + 'You\'ve included an event plugin that extracts an ' +
44 + 'event type with the exact same or identity as an event that ' +
45 + 'had previously been injected - or, one of the registration names ' +
46 + 'used by an plugin has already been used.';
47 + ERRORS.DOUBLE_ENQUEUE =
48 + 'During the processing of events, more events were enqueued. This ' +
49 + 'is strange and should not happen. Please report immediately. ';
50 + ERRORS.DEPENDENCY_ERROR =
51 + 'You have either attempted to load an event plugin that has no ' +
52 + 'entry in EventPluginOrder, or have attempted to extract events ' +
53 + 'when some critical dependencies have not yet been injected.';
54 +}
55 +
56 +
57 +/**
58 + * EventPluginHub: To see a diagram and explanation of the overall architecture
59 + * of the plugin hub system, @see ReactEvents.js
60 + */
61 +
62 +/**
63 + * Injected Dependencies:
64 + */
65 +var injection = {
66 + /**
67 + * [required] Dependency of `EventPropagators`.
68 + */
69 + injectInstanceHandle: function(InjectedInstanceHandle) {
70 + EventPropagators.injection.injectInstanceHandle(InjectedInstanceHandle);
71 + },
72 +
73 + /**
74 + * `EventPluginOrder`: [optional] Provides deterministic ordering of
75 + * `EventPlugin`s. Ordering decoupled from the injection of actual
76 + * plugins so that there is always a deterministic and permanent ordering
77 + * regardless of the plugins that happen to become packaged, or applications
78 + * that happen to inject on-the-fly event plugins.
79 + */
80 + EventPluginOrder: null,
81 + injectEventPluginOrder: function(InjectedEventPluginOrder) {
82 + injection.EventPluginOrder = InjectedEventPluginOrder;
83 + injection._recomputePluginsList();
84 + },
85 +
86 + /**
87 + * `EventPlugins`: [optional][lazy-loadable] Plugins that must be listed in
88 + * the `EventPluginOrder` injected dependency. The list of plugins may grow as
89 + * custom plugins are injected into the system at page as part of the
90 + * initialization process, or even after the initialization process (on-the-
91 + * fly). Plugins injected into the hub have an opportunity to infer abstract
92 + * events when top level events are streamed through the `EventPluginHub`.
93 + */
94 + plugins: [],
95 + injectEventPluginsByName: function(pluginsByName) {
96 + injection.pluginsByName = merge(injection.pluginsByName, pluginsByName);
97 + injection._recomputePluginsList();
98 + },
99 +
100 + /**
101 + * A reference of all injected plugins by their name. Both plugins and
102 + * `EventPluginOrder` can be injected on-the-fly. Any time either dependency
103 + * is (re)injected, the resulting list of plugins in correct order is
104 + * recomputed.
105 + */
106 + pluginsByName: {},
107 + _recomputePluginsList: function() {
108 + var injectPluginByName = function(name, PluginModule) {
109 + var pluginIndex = injection.EventPluginOrder.indexOf(name);
110 + throwIf(pluginIndex === -1, ERRORS.DEPENDENCY_ERROR + name);
111 + if (!injection.plugins[pluginIndex]) {
112 + injection.plugins[pluginIndex] = PluginModule;
113 + for (var eventName in PluginModule.abstractEventTypes) {
114 + var eventType = PluginModule.abstractEventTypes[eventName];
115 + recordAllRegistrationNames(eventType, PluginModule);
116 + }
117 + }
118 + };
119 + if (injection.EventPluginOrder) { // Else, do when plugin order injected
120 + var injectedPluginsByName = injection.pluginsByName;
121 + for (var name in injectedPluginsByName) {
122 + injectPluginByName(name, injectedPluginsByName[name]);
123 + }
124 + }
125 + }
126 +};
127 +
128 +
129 +/**
130 + * `renderedTarget`: We'll refer to the concept of a "rendered target". Any
131 + * framework code that uses the `EventPluginHub` will stream events to the
132 + * `EventPluginHub`. In order for registered plugins to make sense of the native
133 + * events, it helps to allow plugins to not only have access to the native
134 + * `Event` object but also the notion of a "rendered target", which is the
135 + * conceptual target `Element` of the `Event` that the framework (user of
136 + * `EventPluginHub`) deems as being the "important" target.
137 + */
138 +/**
139 + * Keeps track of all valid "registration names" (`onClick` etc). We expose
140 + * these structures to other modules, who will always see updates that we apply
141 + * to these structures. They should never have a desire to mutate these.
142 + */
143 +var registrationNames = {};
144 +
145 +/**
146 + * This prevents the need for clients to call `Object.keys(registrationNames)`
147 + * every time they want to loop through the possible registration names.
148 + */
149 +var registrationNamesArr = [];
150 +
151 +/**
152 + * Internal queue of events that have accumulated their dispatches and are
153 + * waiting to have their dispatches executed.
154 + */
155 +var abstractEventQueue = [];
156 +
157 +/**
158 + * Records all the registration names that the event plugin makes available
159 + * to the general event system. These are things like
160 + * `onClick`/`onClickCapture`.
161 + */
162 +function recordAllRegistrationNames(eventType, PluginModule) {
163 + var phaseName;
164 + var phasedRegistrationNames = eventType.phasedRegistrationNames;
165 + if (phasedRegistrationNames) {
166 + for (phaseName in phasedRegistrationNames) {
167 + if (!phasedRegistrationNames.hasOwnProperty(phaseName)) {
168 + continue;
169 + }
170 + if (__DEV__) {
171 + throwIf(
172 + registrationNames[phasedRegistrationNames[phaseName]],
173 + ERRORS.DOUBLE_REGISTER
174 + );
175 + }
176 + registrationNames[phasedRegistrationNames[phaseName]] = PluginModule;
177 + registrationNamesArr.push(phasedRegistrationNames[phaseName]);
178 + }
179 + } else if (eventType.registrationName) {
180 + if (__DEV__) {
181 + throwIf(
182 + registrationNames[eventType.registrationName],
183 + ERRORS.DOUBLE_REGISTER
184 + );
185 + }
186 + registrationNames[eventType.registrationName] = PluginModule;
187 + registrationNamesArr.push(eventType.registrationName);
188 + }
189 +}
190 +
191 +/**
192 + * A hacky way to reverse engineer which event plugin module created an
193 + * AbstractEvent.
194 + * @param {AbstractEvent} abstractEvent to look at
195 + */
196 +function getPluginModuleForAbstractEvent(abstractEvent) {
197 + if (abstractEvent.type.registrationName) {
198 + return registrationNames[abstractEvent.type.registrationName];
199 + } else {
200 + for (var phase in abstractEvent.type.phasedRegistrationNames) {
201 + if (!abstractEvent.type.phasedRegistrationNames.hasOwnProperty(phase)) {
202 + continue;
203 + }
204 + var PluginModule = registrationNames[
205 + abstractEvent.type.phasedRegistrationNames[phase]
206 + ];
207 + if (PluginModule) {
208 + return PluginModule;
209 + }
210 + }
211 + }
212 + return null;
213 +}
214 +
215 +var deleteAllListeners = function(domID) {
216 + var ii;
217 + for (ii = 0; ii < registrationNamesArr.length; ii++) {
218 + deleteListener(domID, registrationNamesArr[ii]);
219 + }
220 +};
221 +
222 +/**
223 + * Accepts the stream of top level native events, and gives every registered
224 + * plugin an opportunity to extract `AbstractEvent`s with annotated dispatches.
225 + *
226 + * @param {Enum} topLevelType Record from `EventConstants`.
227 + * @param {Event} nativeEvent A Standard Event with fixed `target` property.
228 + * @param {Element} renderedTarget Element of interest to the framework, usually
229 + * the same as `nativeEvent.target` but occasionally an element immediately
230 + * above `nativeEvent.target` (the first DOM node recognized as one "rendered"
231 + * by the framework at hand.)
232 + * @param {string} renderedTargetID string ID of `renderedTarget`.
233 + */
234 +var extractAbstractEvents =
235 + function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
236 + var abstractEvents;
237 + var plugins = injection.plugins;
238 + var len = plugins.length;
239 + for (var i = 0; i < len; i++) {
240 + // Not every plugin in the ordering may be loaded at runtime.
241 + var possiblePlugin = plugins[i];
242 + var extractedAbstractEvents =
243 + possiblePlugin &&
244 + possiblePlugin.extractAbstractEvents(
245 + topLevelType,
246 + nativeEvent,
247 + renderedTargetID,
248 + renderedTarget
249 + );
250 + if (extractedAbstractEvents) {
251 + abstractEvents = accumulate(abstractEvents, extractedAbstractEvents);
252 + }
253 + }
254 + return abstractEvents;
255 + };
256 +
257 +var enqueueAbstractEvents = function(abstractEvents) {
258 + if (abstractEvents) {
259 + abstractEventQueue = accumulate(abstractEventQueue, abstractEvents);
260 + }
261 +};
262 +
263 +/**
264 + * Executes a single abstract event dispatch. Returns a value, but this return
265 + * value doesn't make much sense when executing dispatches for a list of a
266 + * events. However, if a plugin executes a single dispatch, mostly bypassing
267 + * `EventPluginHub`, it can execute dispatches directly and assign special
268 + * meaning to the return value. So this will return the result of executing the
269 + * dispatch, though for most use cases, it gets dropped.
270 + */
271 +var executeDispatchesAndRelease = function(abstractEvent) {
272 + if (abstractEvent) {
273 + var PluginModule = getPluginModuleForAbstractEvent(abstractEvent);
274 + var pluginExecuteDispatch = PluginModule && PluginModule.executeDispatch;
275 + EventPluginUtils.executeDispatchesInOrder(
276 + abstractEvent,
277 + pluginExecuteDispatch || EventPluginUtils.executeDispatch
278 + );
279 + AbstractEvent.release(abstractEvent);
280 + }
281 +};
282 +
283 +/**
284 + * Sets `abstractEventQueue` to null before processing it, so that we can tell
285 + * if in the process of processing events, more were enqueued. We throw if we
286 + * find that any were enqueued though this use case could be supported in the
287 + * future. For now, throwing an error as it's something we don't expect to
288 + * occur.
289 + */
290 +var processAbstractEventQueue = function() {
291 + var processingAbstractEventQueue = abstractEventQueue;
292 + abstractEventQueue = null;
293 + forEachAccumulated(processingAbstractEventQueue, executeDispatchesAndRelease);
294 + if (__DEV__) {
295 + throwIf(abstractEventQueue, ERRORS.DOUBLE_ENQUEUE);
296 + }
297 +};
298 +
299 +/**
300 + * Provides a unified interface for an arbitrary and dynamic set of event
301 + * plugins. Loosely, a hub, where several "event plugins" may act as a single
302 + * event plugin behind the facade of `EventPluginHub`. Each event plugin injects
303 + * themselves into the HUB, and will immediately become operable upon injection.
304 + *
305 + * @constructor EventPluginHub
306 + */
307 +var EventPluginHub = {
308 + registrationNames: registrationNames,
309 + registrationNamesArr: registrationNamesArr,
310 + putListener: CallbackRegistry.putListener,
311 + getListener: CallbackRegistry.getListener,
312 + deleteAllListeners: deleteAllListeners,
313 + extractAbstractEvents: extractAbstractEvents,
314 + enqueueAbstractEvents: enqueueAbstractEvents,
315 + processAbstractEventQueue: processAbstractEventQueue,
316 + injection: injection
317 +};
318 +
319 +
320 +if (ExecutionEnvironment.canUseDOM) {
321 + window.EventPluginHub = EventPluginHub;
322 +}
323 +
324 +module.exports = EventPluginHub;
src/event/EventPluginUtils.js new
+206
@@ -0,0 +1,206 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule EventPluginUtils
17 + */
18 +
19 +"use strict";
20 +
21 +var EventConstants = require('EventConstants');
22 +var AbstractEvent = require('AbstractEvent');
23 +
24 +var invariant = require('invariant');
25 +
26 +var topLevelTypes = EventConstants.topLevelTypes;
27 +
28 +function isEndish(topLevelType) {
29 + return topLevelType === topLevelTypes.topMouseUp ||
30 + topLevelType === topLevelTypes.topTouchEnd ||
31 + topLevelType === topLevelTypes.topTouchCancel;
32 +}
33 +
34 +function isMoveish(topLevelType) {
35 + return topLevelType === topLevelTypes.topMouseMove ||
36 + topLevelType === topLevelTypes.topTouchMove;
37 +}
38 +function isStartish(topLevelType) {
39 + return topLevelType === topLevelTypes.topMouseDown ||
40 + topLevelType === topLevelTypes.topTouchStart;
41 +}
42 +
43 +function storePageCoordsIn(obj, nativeEvent) {
44 + var pageX = AbstractEvent.eventPageX(nativeEvent);
45 + var pageY = AbstractEvent.eventPageY(nativeEvent);
46 + obj.pageX = pageX;
47 + obj.pageY = pageY;
48 +}
49 +
50 +function eventDistance(coords, nativeEvent) {
51 + var pageX = AbstractEvent.eventPageX(nativeEvent);
52 + var pageY = AbstractEvent.eventPageY(nativeEvent);
53 + return Math.pow(
54 + Math.pow(pageX - coords.pageX, 2) + Math.pow(pageY - coords.pageY, 2),
55 + 0.5
56 + );
57 +}
58 +
59 +var validateEventDispatches;
60 +if (__DEV__) {
61 + validateEventDispatches = function(abstractEvent) {
62 + var dispatchListeners = abstractEvent._dispatchListeners;
63 + var dispatchIDs = abstractEvent._dispatchIDs;
64 +
65 + var listenersIsArr = Array.isArray(dispatchListeners);
66 + var idsIsArr = Array.isArray(dispatchIDs);
67 + var IDsLen = idsIsArr ? dispatchIDs.length : dispatchIDs ? 1 : 0;
68 + var listenersLen = listenersIsArr ?
69 + dispatchListeners.length :
70 + dispatchListeners ? 1 : 0;
71 +
72 + invariant(
73 + idsIsArr === listenersIsArr && IDsLen === listenersLen,
74 + 'EventPluginUtils: Invalid `abstractEvent`.'
75 + );
76 + };
77 +}
78 +
79 +/**
80 + * Invokes `cb(abstractEvent, listener, id)`. Avoids using call if no scope is
81 + * provided. The `(listener,id)` pair effectively forms the "dispatch" but are
82 + * kept separate to conserve memory.
83 + */
84 +function forEachEventDispatch(abstractEvent, cb) {
85 + var dispatchListeners = abstractEvent._dispatchListeners;
86 + var dispatchIDs = abstractEvent._dispatchIDs;
87 + if (__DEV__) {
88 + validateEventDispatches(abstractEvent);
89 + }
90 + if (Array.isArray(dispatchListeners)) {
91 + var i;
92 + for (
93 + i = 0;
94 + i < dispatchListeners.length && !abstractEvent.isPropagationStopped;
95 + i++) {
96 + // Listeners and IDs are two parallel arrays that are always in sync.
97 + cb(abstractEvent, dispatchListeners[i], dispatchIDs[i]);
98 + }
99 + } else if (dispatchListeners) {
100 + cb(abstractEvent, dispatchListeners, dispatchIDs);
101 + }
102 +}
103 +
104 +/**
105 + * Default implementation of PluginModule.executeDispatch().
106 + * @param {AbstractEvent} AbstractEvent to handle
107 + * @param {function} Application-level callback
108 + * @param {string} domID DOM id to pass to the callback.
109 + */
110 +function executeDispatch(abstractEvent, listener, domID) {
111 + listener(abstractEvent, domID);
112 +}
113 +
114 +/**
115 + * Standard/simple iteration through an event's collected dispatches.
116 + */
117 +function executeDispatchesInOrder(abstractEvent, executeDispatch) {
118 + forEachEventDispatch(abstractEvent, executeDispatch);
119 + abstractEvent._dispatchListeners = null;
120 + abstractEvent._dispatchIDs = null;
121 +}
122 +
123 +/**
124 + * Standard/simple iteration through an event's collected dispatches, but stops
125 + * at the first dispatch execution returning true, and returns that id.
126 + *
127 + * @returns id of the first dispatch execution who's listener returns true, or
128 + * null if no listener returned true.
129 + */
130 +function executeDispatchesInOrderStopAtTrue(abstractEvent) {
131 + var dispatchListeners = abstractEvent._dispatchListeners;
132 + var dispatchIDs = abstractEvent._dispatchIDs;
133 + if (__DEV__) {
134 + validateEventDispatches(abstractEvent);
135 + }
136 + if (Array.isArray(dispatchListeners)) {
137 + var i;
138 + for (
139 + i = 0;
140 + i < dispatchListeners.length && !abstractEvent.isPropagationStopped;
141 + i++) {
142 + // Listeners and IDs are two parallel arrays that are always in sync.
143 + if (dispatchListeners[i](abstractEvent, dispatchIDs[i])) {
144 + return dispatchIDs[i];
145 + }
146 + }
147 + } else if (dispatchListeners) {
148 + if (dispatchListeners(abstractEvent, dispatchIDs)) {
149 + return dispatchIDs;
150 + }
151 + }
152 + return null;
153 +}
154 +
155 +/**
156 + * Execution of a "direct" dispatch - there must be at most one dispatch
157 + * accumulated on the event or it is considered an error. It doesn't really make
158 + * sense for an event with multiple dispatches (bubbled) to keep track of the
159 + * return values at each dispatch execution, but it does tend to make sense when
160 + * dealing with "direct" dispatches.
161 + *
162 + * @returns The return value of executing the single dispatch.
163 + */
164 +function executeDirectDispatch(abstractEvent) {
165 + if (__DEV__) {
166 + validateEventDispatches(abstractEvent);
167 + }
168 + var dispatchListener = abstractEvent._dispatchListeners;
169 + var dispatchID = abstractEvent._dispatchIDs;
170 + invariant(
171 + !Array.isArray(dispatchListener),
172 + 'executeDirectDispatch(...): Invalid `abstractEvent`.'
173 + );
174 + var res = dispatchListener ?
175 + dispatchListener(abstractEvent, dispatchID) :
176 + null;
177 + abstractEvent._dispatchListeners = null;
178 + abstractEvent._dispatchIDs = null;
179 + return res;
180 +}
181 +
182 +/**
183 + * @param {AbstractEvent} abstractEvent
184 + * @returns {bool} True iff number of dispatches accumulated is greater than 0.
185 + */
186 +function hasDispatches(abstractEvent) {
187 + return !!abstractEvent._dispatchListeners;
188 +}
189 +
190 +/**
191 + * General utilities that are useful in creating custom Event Plugins.
192 + */
193 +var EventPluginUtils = {
194 + isEndish: isEndish,
195 + isMoveish: isMoveish,
196 + isStartish: isStartish,
197 + storePageCoordsIn: storePageCoordsIn,
198 + eventDistance: eventDistance,
199 + executeDispatchesInOrder: executeDispatchesInOrder,
200 + executeDispatchesInOrderStopAtTrue: executeDispatchesInOrderStopAtTrue,
201 + executeDirectDispatch: executeDirectDispatch,
202 + hasDispatches: hasDispatches,
203 + executeDispatch: executeDispatch
204 +};
205 +
206 +module.exports = EventPluginUtils;
src/event/EventPropagators.js new
+180
@@ -0,0 +1,180 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule EventPropagators
17 + */
18 +
19 +"use strict";
20 +
21 +var CallbackRegistry = require('CallbackRegistry');
22 +var EventConstants = require('EventConstants');
23 +
24 +var accumulate = require('accumulate');
25 +var forEachAccumulated = require('forEachAccumulated');
26 +var getListener = CallbackRegistry.getListener;
27 +var PropagationPhases = EventConstants.PropagationPhases;
28 +
29 +/**
30 + * Injected dependencies:
31 + */
32 +
33 +/**
34 + * - `InstanceHandle`: [required] Module that performs logical traversals of DOM
35 + * hierarchy given ids of the logical DOM elements involved.
36 + */
37 +var injection = {
38 + InstanceHandle: null,
39 + injectInstanceHandle: function(InjectedInstanceHandle) {
40 + injection.InstanceHandle = InjectedInstanceHandle;
41 + if (__DEV__) {
42 + injection.validate();
43 + }
44 + },
45 + validate: function() {
46 + var invalid = !injection.InstanceHandle||
47 + !injection.InstanceHandle.traverseTwoPhase ||
48 + !injection.InstanceHandle.traverseEnterLeave;
49 + if (invalid) {
50 + throw new Error('InstanceHandle not injected before use!');
51 + }
52 + }
53 +};
54 +
55 +/**
56 + * Some event types have a notion of different registration names for different
57 + * "phases" of propagation. This finds listeners by a given phase.
58 + */
59 +function listenerAtPhase(id, abstractEvent, propagationPhase) {
60 + var registrationName =
61 + abstractEvent.type.phasedRegistrationNames[propagationPhase];
62 + return getListener(id, registrationName);
63 +}
64 +
65 +/**
66 + * Tags an `AbstractEvent` with dispatched listeners. Creating this function
67 + * here, allows us to not have to bind or create functions for each event.
68 + * Mutating the event's members allows us to not have to create a wrapping
69 + * "dispatch" object that pairs the event with the listener.
70 + */
71 +function accumulateDirectionalDispatches(domID, upwards, abstractEvent) {
72 + if (__DEV__) {
73 + if (!domID) {
74 + throw new Error('Dispatching id must not be null');
75 + }
76 + injection.validate();
77 + }
78 + var phase = upwards ? PropagationPhases.bubbled : PropagationPhases.captured;
79 + var listener = listenerAtPhase(domID, abstractEvent, phase);
80 + if (listener) {
81 + abstractEvent._dispatchListeners =
82 + accumulate(abstractEvent._dispatchListeners, listener);
83 + abstractEvent._dispatchIDs = accumulate(abstractEvent._dispatchIDs, domID);
84 + }
85 +}
86 +
87 +/**
88 + * Collect dispatches (must be entirely collected before dispatching - see unit
89 + * tests). Lazily allocate the array to conserve memory. We must loop through
90 + * each event and perform the traversal for each one. We can not perform a
91 + * single traversal for the entire collection of events because each event may
92 + * have a different target.
93 + */
94 +function accumulateTwoPhaseDispatchesSingle(abstractEvent) {
95 + if (abstractEvent && abstractEvent.type.phasedRegistrationNames) {
96 + injection.InstanceHandle.traverseTwoPhase(
97 + abstractEvent.abstractTargetID,
98 + accumulateDirectionalDispatches,
99 + abstractEvent
100 + );
101 + }
102 +}
103 +
104 +
105 +/**
106 + * Accumulates without regard to direction, does not look for phased
107 + * registration names. Same as `accumulateDirectDispatchesSingle` but without
108 + * requiring that the `abstractTargetID` be the same as the dispatched ID.
109 + */
110 +function accumulateDispatches(id, ignoredDirection, abstractEvent) {
111 + if (abstractEvent && abstractEvent.type.registrationName) {
112 + var listener = getListener(id, abstractEvent.type.registrationName);
113 + if (listener) {
114 + abstractEvent._dispatchListeners =
115 + accumulate(abstractEvent._dispatchListeners, listener);
116 + abstractEvent._dispatchIDs = accumulate(abstractEvent._dispatchIDs, id);
117 + }
118 + }
119 +}
120 +
121 +/**
122 + * Accumulates dispatches on an `AbstractEvent`, but only for the
123 + * `abstractTargetID`.
124 + * @param {AbstractEvent} abstractEvent
125 + */
126 +function accumulateDirectDispatchesSingle(abstractEvent) {
127 + if (abstractEvent && abstractEvent.type.registrationName) {
128 + accumulateDispatches(abstractEvent.abstractTargetID, null, abstractEvent);
129 + }
130 +}
131 +
132 +function accumulateTwoPhaseDispatches(abstractEvents) {
133 + if (__DEV__) {
134 + injection.validate();
135 + }
136 + forEachAccumulated(abstractEvents, accumulateTwoPhaseDispatchesSingle);
137 +}
138 +
139 +function accumulateEnterLeaveDispatches(leave, enter, fromID, toID) {
140 + if (__DEV__) {
141 + injection.validate();
142 + }
143 + injection.InstanceHandle.traverseEnterLeave(
144 + fromID,
145 + toID,
146 + accumulateDispatches,
147 + leave,
148 + enter
149 + );
150 +}
151 +
152 +
153 +function accumulateDirectDispatches(abstractEvents) {
154 + if (__DEV__) {
155 + injection.validate();
156 + }
157 + forEachAccumulated(abstractEvents, accumulateDirectDispatchesSingle);
158 +}
159 +
160 +
161 +
162 +/**
163 + * A small set of propagation patterns, each of which will accept a small amount
164 + * of information, and generate a set of "dispatch ready event objects" - which
165 + * are sets of events that have already been annotated with a set of dispatched
166 + * listener functions/ids. The API is designed this way to discourage these
167 + * propagation strategies from actually executing the dispatches, since we
168 + * always want to collect the entire set of dispatches before executing event a
169 + * single one.
170 + *
171 + * @constructor EventPropagators
172 + */
173 +var EventPropagators = {
174 + accumulateTwoPhaseDispatches: accumulateTwoPhaseDispatches,
175 + accumulateDirectDispatches: accumulateDirectDispatches,
176 + accumulateEnterLeaveDispatches: accumulateEnterLeaveDispatches,
177 + injection: injection
178 +};
179 +
180 +module.exports = EventPropagators;
src/event/NormalizedEventListener.js new
+88
@@ -0,0 +1,88 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule NormalizedEventListener
17 + */
18 +
19 +"use strict";
20 +
21 +var EventListener = require('EventListener');
22 +
23 +/**
24 + * @param {?Event} eventParam Event parameter from an attached listener.
25 + * @return {Event} Normalized event object.
26 + * @private
27 + */
28 +function normalizeEvent(eventParam) {
29 + var normalized = eventParam || window.event;
30 + // In some browsers (OLD FF), setting the target throws an error. A good way
31 + // to tell if setting the target will throw an error, is to check if the event
32 + // has a `target` property. Safari events have a `target` but it's not always
33 + // normalized. Even if a `target` property exists, it's good to only set the
34 + // target property if we realize that a change will actually take place.
35 + var hasTargetProperty = 'target' in normalized;
36 + var eventTarget = normalized.target || normalized.srcElement || window;
37 + // Safari may fire events on text nodes (Node.TEXT_NODE is 3)
38 + // @see http://www.quirksmode.org/js/events_properties.html
39 + var textNodeNormalizedTarget =
40 + (eventTarget.nodeType === 3) ? eventTarget.parentNode : eventTarget;
41 + if (!hasTargetProperty || normalized.target !== textNodeNormalizedTarget) {
42 + // Create an object that inherits from the native event so that we can set
43 + // `target` on it. (It is read-only and setting it throws in strict mode).
44 + normalized = Object.create(normalized);
45 + normalized.target = textNodeNormalizedTarget;
46 + }
47 + return normalized;
48 +}
49 +
50 +function createNormalizedCallback(cb) {
51 + return function(unfixedNativeEvent) {
52 + cb(normalizeEvent(unfixedNativeEvent));
53 + };
54 +}
55 +
56 +var NormalizedEventListener = {
57 +
58 + /**
59 + * Listens to bubbled events on a DOM node.
60 + *
61 + * NOTE: The listener will be invoked with a normalized event object.
62 + *
63 + * @param {DOMElement} el DOM element to register listener on.
64 + * @param {string} handlerBaseName Event name, e.g. "click".
65 + * @param {function} cb Callback function.
66 + * @public
67 + */
68 + listen: function(el, handlerBaseName, cb) {
69 + EventListener.listen(el, handlerBaseName, createNormalizedCallback(cb));
70 + },
71 +
72 + /**
73 + * Listens to captured events on a DOM node.
74 + *
75 + * NOTE: The listener will be invoked with a normalized event object.
76 + *
77 + * @param {DOMElement} el DOM element to register listener on.
78 + * @param {string} handlerBaseName Event name, e.g. "click".
79 + * @param {function} cb Callback function.
80 + * @public
81 + */
82 + capture: function(el, handlerBaseName, cb) {
83 + EventListener.capture(el, handlerBaseName, createNormalizedCallback(cb));
84 + }
85 +
86 +};
87 +
88 +module.exports = NormalizedEventListener;
src/eventPlugins/AnalyticsEventPluginFactory.js new
+225
@@ -0,0 +1,225 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule AnalyticsEventPluginFactory
17 + *
18 + * This module provides a factory method to create the AnalyticsEventPlugin that
19 + * can be used to track the usage of React components that are of interest to
20 + * the user.
21 + *
22 + * In order to enable a component for analytics tracking, you need to specify
23 + * two additional attributes to the component when you describe the structure of
24 + * your component in the render() method:
25 + *
26 + * 1. 'data-analytics-id': This represents a unique ID that the analytics module
27 + * will use to identify this component in all the analytics data. Note that
28 + * this is independent of the ref or the DOM id of the element. Over the
29 + * lifetime of the product, even if the component id or ref needs to be
30 + * changed, as long as you can ensure the analytics ID doesnt change, the
31 + * historical data can be correlated. Also note that React does NOT do
32 + * anything to guarantee or enforce uniqueness of this ID. If its not unique
33 + * the analytics data reported will be incorrect.
34 + *
35 + * 2. 'data-analytics-events': This is a comma separated list of DOM events that
36 + * you want analytics on. React currently supports tracking only on a
37 + * distinct set of events (See topLevelTypesToAnalyticsEvent).
38 + * If the list contains an event that React does not recognize for analytics
39 + * tracking, in __DEV__, an error will be thrown. Note that it is case
40 + * sensitive and space sensitive.
41 + *
42 + * By default the AnalyticsEventPlugin is NOT enabled in React. To use it, you
43 + * need to create the plugin using the factory method and add it to the list of
44 + * Plugins maintained in the EventPluginHub before your component is rendered.
45 + * As creation parameters you can specify two arguments:
46 + *
47 + * 1. callback: This is a required parameter. In __DEV__, an error will be
48 + * thrown if this param is missing. The callback will be called with the
49 + * analyticsData as an argument. The analyticsData will contain one property
50 + * per every React component, identified by its data-analytics-id. The value
51 + * of this property will be an object containing properties corresponding to
52 + * each of the comma separated events specified in data-analytics-events.
53 + *
54 + * For example, if you have:
55 + * <Button ...
56 + * data-analytics-id="createButton"
57 + * data-analytics-events="click"
58 + * />
59 + * and
60 + * <TextBox ...
61 + * data-analytics-id="disclaimerBox"
62 + * data-analytics-events="focus,scroll"
63 + * />
64 + * analyticsData will be something like:
65 + * '{"createButton":{"click":50}, "disclaimerBox":{"focus":15, "scroll":5}}'
66 + *
67 + * DO NOT mutate the data that you get in the callback. Mutating it will lead
68 + * to errors and unstable behavior.
69 + *
70 + * The React component will be included for analytics as long as some user
71 + * interaction has happened with that component. If no user interaction has
72 + * happened with any of the components tracked for analytics, the callback
73 + * will not be called.
74 + *
75 + * 2. interval (in milliseconds): This is an optional parameter to specify the
76 + * interval at which the callback needs to be called. It needs to be greater
77 + * than the 2 minutes (which is the default value if this parameter is not
78 + * specified or a value less than 2 minutes is specified)
79 + *
80 + * Please refer to the unit tests AnalyticsEventPlugin-test.js for details on
81 + * usage.
82 + */
83 +
84 +"use strict";
85 +
86 +var ExecutionEnvironment = require('ExecutionEnvironment');
87 +
88 +var emptyFunction = require('emptyFunction');
89 +var invariant = require('invariant');
90 +var topLevelTypes = require('EventConstants').topLevelTypes;
91 +
92 +var ANALYTICS_ID = 'data-analytics-id';
93 +var ANALYTICS_EVENTS = 'data-analytics-events';
94 +var DEFAULT_INTERVAL_MS = 2 * 60 * 1000; // 2 minutes
95 +
96 +var analyticsData = {};
97 +
98 +// List of topLevel event types that React supports for analytics tracking
99 +var topLevelTypesToAnalyticsEvent = {
100 + topClick: 'click',
101 + topDoubleClick: 'doubleClick',
102 + topMouseWheel: 'mouseWheel',
103 + topTouchStart: 'touchStart',
104 + topTouchEnd: 'touchEnd',
105 + topTouchMove: 'touchMove',
106 + topTouchCancel: 'touchCancel',
107 + topKeyUp: 'keyUp',
108 + topKeyPress: 'keyPress',
109 + topKeyDown: 'keyDown',
110 + topFocus: 'focus',
111 + topBlur: 'blur',
112 + topScroll: 'scroll',
113 + topChange: 'change'
114 +};
115 +
116 +if (__DEV__) {
117 + var analyticsEventNameToTopLevelType = {
118 + 'click': topLevelTypes.topClick,
119 + 'doubleClick': topLevelTypes.topDoubleClick,
120 + 'mouseWheel': topLevelTypes.topMouseWheel,
121 + 'touchStart': topLevelTypes.topTouchStart,
122 + 'touchEnd': topLevelTypes.topTouchEnd,
123 + 'touchMove': topLevelTypes.topTouchMove,
124 + 'touchCancel': topLevelTypes.topTouchCancel,
125 + 'keyUp': topLevelTypes.topKeyUp,
126 + 'keyPress': topLevelTypes.topKeyPress,
127 + 'keyDown': topLevelTypes.topKeyDown,
128 + 'focus': topLevelTypes.topFocus,
129 + 'blur': topLevelTypes.topBlur,
130 + 'scroll': topLevelTypes.topScroll,
131 + 'change': topLevelTypes.topChange
132 + };
133 +}
134 +
135 +/**
136 + * This plugin does not really extract any abstract events. Rather it just looks
137 + * at the top level event and bumps up counters as appropriate
138 + *
139 + * @see EventPluginHub.extractAbstractEvents
140 + */
141 +function extractAbstractEvents(
142 + topLevelType,
143 + nativeEvent,
144 + renderedTargetID,
145 + renderedTarget) {
146 + var currentEvent = topLevelTypesToAnalyticsEvent[topLevelType];
147 + if (!currentEvent || !renderedTarget || !renderedTarget.attributes) {
148 + return null;
149 + }
150 +
151 + var analyticsIDAttribute = renderedTarget.attributes[ANALYTICS_ID];
152 + var analyticsEventsAttribute = renderedTarget.attributes[ANALYTICS_EVENTS];
153 + if(!analyticsIDAttribute || !analyticsEventsAttribute) {
154 + return null;
155 + }
156 +
157 + var analyticsEventsArr = analyticsEventsAttribute.value.split(",");
158 + var analyticsID = analyticsIDAttribute.value;
159 + if (!analyticsData[analyticsID]) {
160 + initAnalyticsDataForID(analyticsID, analyticsEventsArr);
161 + }
162 +
163 + if (analyticsEventsArr.indexOf(currentEvent) !== -1) {
164 + analyticsData[analyticsID][currentEvent]++;
165 + }
166 +
167 + return null;
168 +}
169 +
170 +/**
171 + * Initialize the analytics data for a specific element identified by the
172 + * analyticsID - Create an entry in the analyticsData object for the element and
173 + * initialize all counters for that element to 0.
174 + */
175 +function initAnalyticsDataForID(analyticsID, analyticsEventsArr) {
176 + analyticsData[analyticsID] = {};
177 + analyticsEventsArr.forEach(function(analyticsEvent) {
178 + if (__DEV__) {
179 + invariant(
180 + analyticsEventNameToTopLevelType[analyticsEvent],
181 + 'Invalid analyticsEvent:%s for analyticsID:%s',
182 + analyticsEvent,
183 + analyticsID
184 + );
185 + }
186 + analyticsData[analyticsID][analyticsEvent] = 0;
187 + });
188 +}
189 +
190 +/**
191 + * Returns the analytics event plugin given the callback that needs to be
192 + * invoked for reporting analytics and the interval at which the callback needs
193 + * to be invoked. This interval has to be atleast DEFAULT_INTERVAL_MS.
194 + */
195 +var createAnalyticsPlugin = function(cb, interval) {
196 + invariant(
197 + ExecutionEnvironment.canUseDOM,
198 + 'createAnalyticsPlugin(...): The DOM is not supported in the execution ' +
199 + 'environment.'
200 + );
201 +
202 + if (__DEV__) {
203 + invariant(cb, 'createAnalyticsPlugin(...): You must provide a callback.');
204 + }
205 + cb = cb || emptyFunction;
206 +
207 + setInterval(
208 + function() {
209 + if (Object.keys(analyticsData).length) {
210 + // Invoke the callback with a clone of analyticsData, otherwise our
211 + // analyticsData will be dirtied by user changes
212 + cb(analyticsData);
213 + }
214 + },
215 + interval > DEFAULT_INTERVAL_MS ? interval : DEFAULT_INTERVAL_MS
216 + );
217 +
218 + return {extractAbstractEvents: extractAbstractEvents};
219 +};
220 +
221 +var AnalyticsEventPluginFactory = {
222 + createAnalyticsPlugin: createAnalyticsPlugin
223 +};
224 +
225 +module.exports = AnalyticsEventPluginFactory;
src/eventPlugins/DefaultEventPluginOrder.js new
+40
@@ -0,0 +1,40 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule DefaultEventPluginOrder
17 + */
18 +
19 +"use strict";
20 +
21 + var keyOf = require('keyOf');
22 +
23 +/**
24 + * Module that is injectable into `EventPluginHub`, that specifies a
25 + * deterministic ordering of `EventPlugin`s. A convenient way to reason about
26 + * plugins, without having to package every one of them. This is better than
27 + * having plugins be ordered in the same order that they are injected because
28 + * that ordering would be influenced by the packaging order.
29 + * `ResponderEventPlugin` must occur before `SimpleEventPlugin` so that
30 + * preventing default on events is convenient in `SimpleEventPlugin` handlers.
31 + */
32 +var DefaultEventPluginOrder = [
33 + keyOf({ResponderEventPlugin: null}),
34 + keyOf({SimpleEventPlugin: null}),
35 + keyOf({TapEventPlugin: null}),
36 + keyOf({EnterLeaveEventPlugin: null}),
37 + keyOf({AnalyticsEventPlugin: null})
38 +];
39 +
40 +module.exports = DefaultEventPluginOrder;
src/eventPlugins/EnterLeaveEventPlugin.js new
+102
@@ -0,0 +1,102 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule EnterLeaveEventPlugin
17 + */
18 +
19 +"use strict";
20 +
21 +var EventPropagators = require('EventPropagators');
22 +var ExecutionEnvironment = require('ExecutionEnvironment');
23 +var AbstractEvent = require('AbstractEvent');
24 +var EventConstants = require('EventConstants');
25 +var ReactInstanceHandles = require('ReactInstanceHandles');
26 +
27 +var getDOMNodeID = require('getDOMNodeID');
28 +var keyOf = require('keyOf');
29 +
30 +var topLevelTypes = EventConstants.topLevelTypes;
31 +var getFirstReactDOM = ReactInstanceHandles.getFirstReactDOM;
32 +
33 +var abstractEventTypes = {
34 + mouseEnter: {registrationName: keyOf({onMouseEnter: null})},
35 + mouseLeave: {registrationName: keyOf({onMouseLeave: null})}
36 +};
37 +
38 +/**
39 + * For almost every interaction we care about, there will be a top level
40 + * `mouseOver` and `mouseOut` event that occur so we can usually only pay
41 + * attention to one of the two (we'll pay attention to the `mouseOut` event) to
42 + * avoid extracting a duplicate event. However, there's one interaction where
43 + * there will be no `mouseOut` event to rely on - mousing from outside the
44 + * browser *into* the chrome. We detect this scenario and only in that case, we
45 + * use the `mouseOver` event.
46 + *
47 + * @see EventPluginHub.extractAbstractEvents
48 + */
49 +var extractAbstractEvents = function(
50 + topLevelType,
51 + nativeEvent,
52 + renderedTargetID,
53 + renderedTarget) {
54 +
55 + if (topLevelType === topLevelTypes.topMouseOver &&
56 + (nativeEvent.relatedTarget || nativeEvent.fromElement)) {
57 + return;
58 + }
59 + if (topLevelType !== topLevelTypes.topMouseOut &&
60 + topLevelType !== topLevelTypes.topMouseOver){
61 + return null; // Must not be a mouse in or mouse out - ignoring.
62 + }
63 +
64 + var to, from;
65 + if (topLevelType === topLevelTypes.topMouseOut) {
66 + to = getFirstReactDOM(nativeEvent.relatedTarget || nativeEvent.toElement) ||
67 + ExecutionEnvironment.global;
68 + from = renderedTarget;
69 + } else {
70 + to = renderedTarget;
71 + from = ExecutionEnvironment.global;
72 + }
73 +
74 + // Nothing pertains to our managed components.
75 + if (from === to ) {
76 + return;
77 + }
78 +
79 + var fromID = from ? getDOMNodeID(from) : '';
80 + var toID = to ? getDOMNodeID(to) : '';
81 + var leave = AbstractEvent.getPooled(
82 + abstractEventTypes.mouseLeave,
83 + fromID,
84 + topLevelType,
85 + nativeEvent
86 + );
87 + var enter = AbstractEvent.getPooled(
88 + abstractEventTypes.mouseEnter,
89 + toID,
90 + topLevelType,
91 + nativeEvent
92 + );
93 + EventPropagators.accumulateEnterLeaveDispatches(leave, enter, fromID, toID);
94 + return [leave, enter];
95 +};
96 +
97 +var EnterLeaveEventPlugin = {
98 + abstractEventTypes: abstractEventTypes,
99 + extractAbstractEvents: extractAbstractEvents
100 +};
101 +
102 +module.exports = EnterLeaveEventPlugin;
src/eventPlugins/ResponderEventPlugin.js new
+312
@@ -0,0 +1,312 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ResponderEventPlugin
17 + */
18 +
19 +"use strict";
20 +
21 +var AbstractEvent = require('AbstractEvent');
22 +var EventConstants = require('EventConstants');
23 +var EventPluginUtils = require('EventPluginUtils');
24 +var EventPropagators = require('EventPropagators');
25 +
26 +var accumulate = require('accumulate');
27 +var keyOf = require('keyOf');
28 +
29 +var isStartish = EventPluginUtils.isStartish;
30 +var isMoveish = EventPluginUtils.isMoveish;
31 +var isEndish = EventPluginUtils.isEndish;
32 +var executeDirectDispatch = EventPluginUtils.executeDirectDispatch;
33 +var hasDispatches = EventPluginUtils.hasDispatches;
34 +var executeDispatchesInOrderStopAtTrue =
35 + EventPluginUtils.executeDispatchesInOrderStopAtTrue;
36 +
37 +/**
38 + * ID of element that should respond to touch/move types of interactions, as
39 + * indicated explicitly by relevant callbacks.
40 + */
41 +var responderID = null;
42 +var isPressing = false;
43 +
44 +var getResponderID = function() {
45 + return responderID;
46 +};
47 +
48 +var abstractEventTypes = {
49 + /**
50 + * On a `touchStart`/`mouseDown`, is it desired that this element become the
51 + * responder?
52 + */
53 + startShouldSetResponder: {
54 + phasedRegistrationNames: {
55 + bubbled: keyOf({onStartShouldSetResponder: null}),
56 + captured: keyOf({onStartShouldSetResponderCapture: null})
57 + }
58 + },
59 +
60 + /**
61 + * On a `scroll`, is it desired that this element become the responder? This
62 + * is usually not needed, but should be used to retroactively infer that a
63 + * `touchStart` had occured during momentum scroll. During a momentum scroll,
64 + * a touch start will be immediately followed by a scroll event if the view is
65 + * currently scrolling.
66 + */
67 + scrollShouldSetResponder: {
68 + phasedRegistrationNames: {
69 + bubbled: keyOf({onScrollShouldSetResponder: null}),
70 + captured: keyOf({onScrollShouldSetResponderCapture: null})
71 + }
72 + },
73 +
74 + /**
75 + * On a `touchMove`/`mouseMove`, is it desired that this element become the
76 + * responder?
77 + */
78 + moveShouldSetResponder: {
79 + phasedRegistrationNames: {
80 + bubbled: keyOf({onMoveShouldSetResponder: null}),
81 + captured: keyOf({onMoveShouldSetResponderCapture: null})
82 + }
83 + },
84 +
85 + /**
86 + * Direct responder events dispatched directly to responder. Do not bubble.
87 + */
88 + responderMove: {registrationName: keyOf({onResponderMove: null})},
89 + responderRelease: {registrationName: keyOf({onResponderRelease: null})},
90 + responderTerminationRequest: {
91 + registrationName: keyOf({onResponderTerminationRequest: null})
92 + },
93 + responderGrant: {registrationName: keyOf({onResponderGrant: null})},
94 + responderReject: {registrationName: keyOf({onResponderReject: null})},
95 + responderTerminate: {registrationName: keyOf({onResponderTerminate: null})}
96 +};
97 +
98 +/**
99 + * Performs negotiation between any existing/current responder, checks to see if
100 + * any new entity is interested in becoming responder, performs that handshake
101 + * and returns any events that must be emitted to notify the relevant parties.
102 + *
103 + * A note about event ordering in the `EventPluginHub`.
104 + *
105 + * Suppose plugins are injected in the following order:
106 + *
107 + * `[R, S, C]`
108 + *
109 + * To help illustrate the example, assume `S` is `SimpleEventPlugin` (for
110 + * `onClick` etc) and `R` is `ResponderEventPlugin`.
111 + *
112 + * "Deferred-Dispatched Events":
113 + *
114 + * - The current event plugin system will traverse the list of injected plugins,
115 + * in order, and extract events by collecting the plugin's return value of
116 + * `extractAbstractEvents()`.
117 + * - These events that are returned from `extractAbstractEvents` are "deferred
118 + * dispatched events".
119 + * - When returned from `extractAbstractEvents`, deferred dispatched events
120 + * contain an "accumulation" of deferred dispatches.
121 + * -- These deferred dispatches are accumulated/collected before they are
122 + * returned, but processed at a later time by the `EventPluginHub` (hence the
123 + * name deferred).
124 + *
125 + * In the process of returning their deferred dispatched events, event plugins
126 + * themselves can dispatch events on-demand without returning them from
127 + * `extractAbstractEvents`. Plugins might want to do this, so that they can use
128 + * event dispatching as a tool that helps them decide which events should be
129 + * extracted in the first place.
130 + *
131 + * "On-Demand-Dispatched Events":
132 + *
133 + * - On-demand dispatched are not returned from `extractAbstractEvents`.
134 + * - On-demand dispatched events are dispatched during the process of returning
135 + * the deferred-dispatched events.
136 + * - They should not have side effects.
137 + * - They should be avoided, and/or eventually be replaced with another
138 + * abstraction that allows event plugins to perform multiple "rounds" of event
139 + * extraction.
140 + *
141 + * Therefore, the sequence of event dispatches becomes:
142 + *
143 + * - `R`s on-demand events (if any) (dispatched by `R` on-demand)
144 + * - `S`s on-demand events (if any) (dispatched by `S` on-demand)
145 + * - `C`s on-demand events (if any) (dispatched by `C` on-demand)
146 + * - `R`s extracted events (if any) (dispatched by `EventPluginHub`)
147 + * - `S`s extracted events (if any) (dispatched by `EventPluginHub`)
148 + * - `C`s extracted events (if any) (dispatched by `EventPluginHub`)
149 + *
150 + * In the case of `ResponderEventPlugin`: If the `startShouldSetResponder`
151 + * on-demand dispatch returns `true` (and some other details are satisfied) the
152 + * `onResponderGrant` deferred dispatched event is returned from
153 + * `extractAbstractEvents`. The sequence of dispatch executions in this case
154 + * will appear as follows:
155 + *
156 + * - `startShouldSetResponder` (`ResponderEventPlugin` dispatches on-demand)
157 + * - `touchStartCapture` (`EventPluginHub` dispatches as usual)
158 + * - `touchStart` (`EventPluginHub` dispatches as usual)
159 + * - `responderGrant/Reject` (`EventPluginHub` dispatches as usual)
160 + *
161 + * @returns {Accumulation<AbstractEvent>}
162 + */
163 +
164 +/**
165 + * @param {TopLevelTypes} topLevelType Top level event type being examined.
166 + * @param {DOMEvent} nativeEvent Native DOM event.
167 + * @param {string} renderedTargetID ID of deepest React rendered element.
168 + *
169 + * @return {Accumulation<AbstractEvent>} Extracted events.
170 + */
171 +var setResponderAndExtractTransfer =
172 + function(topLevelType, nativeEvent, renderedTargetID) {
173 + var type;
174 + var shouldSetEventType =
175 + isStartish(topLevelType) ? abstractEventTypes.startShouldSetResponder :
176 + isMoveish(topLevelType) ? abstractEventTypes.moveShouldSetResponder :
177 + abstractEventTypes.scrollShouldSetResponder;
178 +
179 + var bubbleShouldSetFrom = responderID || renderedTargetID;
180 + var shouldSetEvent = AbstractEvent.getPooled(
181 + shouldSetEventType,
182 + bubbleShouldSetFrom,
183 + topLevelType,
184 + nativeEvent,
185 + AbstractEvent.normalizePointerData(nativeEvent)
186 + );
187 + EventPropagators.accumulateTwoPhaseDispatches(shouldSetEvent);
188 + var wantsResponderID = executeDispatchesInOrderStopAtTrue(shouldSetEvent);
189 + AbstractEvent.release(shouldSetEvent);
190 +
191 + if (!wantsResponderID || wantsResponderID === responderID) {
192 + return null;
193 + }
194 + var extracted;
195 + var grantEvent = AbstractEvent.getPooled(
196 + abstractEventTypes.responderGrant,
197 + wantsResponderID,
198 + topLevelType,
199 + nativeEvent
200 + );
201 +
202 + EventPropagators.accumulateDirectDispatches(grantEvent);
203 + if (responderID) {
204 + type = abstractEventTypes.responderTerminationRequest;
205 + var terminationRequestEvent = AbstractEvent.getPooled(type, responderID);
206 + EventPropagators.accumulateDirectDispatches(terminationRequestEvent);
207 + var shouldSwitch = !hasDispatches(terminationRequestEvent) ||
208 + executeDirectDispatch(terminationRequestEvent);
209 + AbstractEvent.release(terminationRequestEvent);
210 + if (shouldSwitch) {
211 + var terminateType = abstractEventTypes.responderTerminate;
212 + var terminateEvent = AbstractEvent.getPooled(
213 + terminateType,
214 + responderID,
215 + topLevelType,
216 + nativeEvent
217 + );
218 + EventPropagators.accumulateDirectDispatches(terminateEvent);
219 + extracted = accumulate(extracted, [grantEvent, terminateEvent]);
220 + responderID = wantsResponderID;
221 + } else {
222 + var rejectEvent = AbstractEvent.getPooled(
223 + abstractEventTypes.responderReject,
224 + wantsResponderID,
225 + topLevelType,
226 + nativeEvent
227 + );
228 + EventPropagators.accumulateDirectDispatches(rejectEvent);
229 + extracted = accumulate(extracted, rejectEvent);
230 + }
231 + } else {
232 + extracted = accumulate(extracted, grantEvent);
233 + responderID = wantsResponderID;
234 + }
235 + return extracted;
236 + };
237 +
238 +
239 +/**
240 + * A transfer is a negotiation between a currently set responder and the next
241 + * element to claim responder status. Any start event could trigger a transfer
242 + * of responderID. Any move event could trigger a transfer, so long as there is
243 + * currently a responder set (in other words as long as the user is pressing
244 + * down).
245 + *
246 + * @param {EventConstants.topLevelTypes} topLevelType
247 + * @return {boolean} Whether or not a transfer of responder could possibly
248 + * occur.
249 + */
250 +function canTriggerTransfer(topLevelType) {
251 + return topLevelType === EventConstants.topLevelTypes.topScroll ||
252 + isStartish(topLevelType) ||
253 + (isPressing && isMoveish(topLevelType));
254 +}
255 +
256 +var extractAbstractEvents =
257 + function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
258 + var extracted;
259 + // Must have missed an end event - reset the state here.
260 + if (responderID && isStartish(topLevelType)) {
261 + responderID = null;
262 + }
263 + if (isStartish(topLevelType)) {
264 + isPressing = true;
265 + } else if (isEndish(topLevelType)) {
266 + isPressing = false;
267 + }
268 + if (canTriggerTransfer(topLevelType)) {
269 + var transfer = setResponderAndExtractTransfer(
270 + topLevelType,
271 + nativeEvent,
272 + renderedTargetID,
273 + renderedTarget
274 + );
275 + if (transfer) {
276 + extracted = accumulate(extracted, transfer);
277 + }
278 + }
279 + // Now that we know the responder is set correctly, we can dispatch
280 + // responder type events (directly to the responder).
281 + var type = isMoveish(topLevelType) ? abstractEventTypes.responderMove :
282 + isEndish(topLevelType) ? abstractEventTypes.responderRelease :
283 + isStartish(topLevelType) ? abstractEventTypes.responderStart : null;
284 + if (type) {
285 + var data = AbstractEvent.normalizePointerData(nativeEvent);
286 + var gesture = AbstractEvent.getPooled(
287 + type,
288 + responderID,
289 + topLevelType,
290 + nativeEvent,
291 + data
292 + );
293 + EventPropagators.accumulateDirectDispatches(gesture);
294 + extracted = accumulate(extracted, gesture);
295 + }
296 + if (type === abstractEventTypes.responderRelease) {
297 + responderID = null;
298 + }
299 + return extracted;
300 + };
301 +
302 +/**
303 + * Event plugin for formalizing the negotiation between claiming locks on
304 + * receiving touches.
305 + */
306 +var ResponderEventPlugin = {
307 + abstractEventTypes: abstractEventTypes,
308 + extractAbstractEvents: extractAbstractEvents,
309 + getResponderID: getResponderID
310 +};
311 +
312 +module.exports = ResponderEventPlugin;
src/eventPlugins/SimpleEventPlugin.js new
+232
@@ -0,0 +1,232 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule SimpleEventPlugin
17 + */
18 +
19 +"use strict";
20 +
21 +var AbstractEvent = require('AbstractEvent');
22 +var EventConstants = require('EventConstants');
23 +var EventPropagators = require('EventPropagators');
24 +
25 +var keyOf = require('keyOf');
26 +
27 +var topLevelTypes = EventConstants.topLevelTypes;
28 +
29 +var SimpleEventPlugin = {
30 + abstractEventTypes: {
31 + // Note: We do not allow listening to mouseOver events. Instead, use the
32 + // onMouseEnter/onMouseLeave created by `EnterLeaveEventPlugin`.
33 + mouseDown: {
34 + phasedRegistrationNames: {
35 + bubbled: keyOf({onMouseDown: true}),
36 + captured: keyOf({onMouseDownCapture: true})
37 + }
38 + },
39 + mouseUp: {
40 + phasedRegistrationNames: {
41 + bubbled: keyOf({onMouseUp: true}),
42 + captured: keyOf({onMouseUpCapture: true})
43 + }
44 + },
45 + mouseMove: {
46 + phasedRegistrationNames: {
47 + bubbled: keyOf({onMouseMove: true}),
48 + captured: keyOf({onMouseMoveCapture: true})
49 + }
50 + },
51 + doubleClick: {
52 + phasedRegistrationNames: {
53 + bubbled: keyOf({onDoubleClick: true}),
54 + captured: keyOf({onDoubleClickCapture: true})
55 + }
56 + },
57 + click: {
58 + phasedRegistrationNames: {
59 + bubbled: keyOf({onClick: true}),
60 + captured: keyOf({onClickCapture: true})
61 + }
62 + },
63 + mouseWheel: {
64 + phasedRegistrationNames: {
65 + bubbled: keyOf({onMouseWheel: true}),
66 + captured: keyOf({onMouseWheelCapture: true})
67 + }
68 + },
69 + touchStart: {
70 + phasedRegistrationNames: {
71 + bubbled: keyOf({onTouchStart: true}),
72 + captured: keyOf({onTouchStartCapture: true})
73 + }
74 + },
75 + touchEnd: {
76 + phasedRegistrationNames: {
77 + bubbled: keyOf({onTouchEnd: true}),
78 + captured: keyOf({onTouchEndCapture: true})
79 + }
80 + },
81 + touchCancel: {
82 + phasedRegistrationNames: {
83 + bubbled: keyOf({onTouchCancel: true}),
84 + captured: keyOf({onTouchCancelCapture: true})
85 + }
86 + },
87 + touchMove: {
88 + phasedRegistrationNames: {
89 + bubbled: keyOf({onTouchMove: true}),
90 + captured: keyOf({onTouchMoveCapture: true})
91 + }
92 + },
93 + keyUp: {
94 + phasedRegistrationNames: {
95 + bubbled: keyOf({onKeyUp: true}),
96 + captured: keyOf({onKeyUpCapture: true})
97 + }
98 + },
99 + keyPress: {
100 + phasedRegistrationNames: {
101 + bubbled: keyOf({onKeyPress: true}),
102 + captured: keyOf({onKeyPressCapture: true})
103 + }
104 + },
105 + keyDown: {
106 + phasedRegistrationNames: {
107 + bubbled: keyOf({onKeyDown: true}),
108 + captured: keyOf({onKeyDownCapture: true})
109 + }
110 + },
111 + focus: {
112 + phasedRegistrationNames: {
113 + bubbled: keyOf({onFocus: true}),
114 + captured: keyOf({onFocusCapture: true})
115 + }
116 + },
117 + blur: {
118 + phasedRegistrationNames: {
119 + bubbled: keyOf({onBlur: true}),
120 + captured: keyOf({onBlurCapture: true})
121 + }
122 + },
123 + scroll: {
124 + phasedRegistrationNames: {
125 + bubbled: keyOf({onScroll: true}),
126 + captured: keyOf({onScrollCapture: true})
127 + }
128 + },
129 + change: {
130 + phasedRegistrationNames: {
131 + bubbled: keyOf({onChange: true}),
132 + captured: keyOf({onChangeCapture: true})
133 + }
134 + },
135 + submit: {
136 + phasedRegistrationNames: {
137 + bubbled: keyOf({onSubmit: true}),
138 + captured: keyOf({onSubmitCapture: true})
139 + }
140 + },
141 + DOMCharacterDataModified: {
142 + phasedRegistrationNames: {
143 + bubbled: keyOf({onDOMCharacterDataModified: true}),
144 + captured: keyOf({onDOMCharacterDataModifiedCapture: true})
145 + }
146 + }
147 + },
148 +
149 + /**
150 + * Same as the default implementation, except cancels the event when return
151 + * value is false.
152 + * @param {AbstractEvent} AbstractEvent to handle
153 + * @param {function} Application-level callback
154 + * @param {string} domID DOM id to pass to the callback.
155 + */
156 + executeDispatch: function(abstractEvent, listener, domID) {
157 + var returnValue = listener(abstractEvent, domID);
158 + if (returnValue === false) {
159 + abstractEvent.stopPropagation();
160 + abstractEvent.preventDefault();
161 + }
162 + },
163 +
164 + /**
165 + * @see EventPluginHub.extractAbstractEvents
166 + */
167 + extractAbstractEvents:
168 + function(topLevelType, nativeEvent, renderedTargetID, renderedTarget) {
169 + var data;
170 + var abstractEventType =
171 + SimpleEventPlugin.topLevelTypesToAbstract[topLevelType];
172 + if (!abstractEventType) {
173 + return null;
174 + }
175 + switch(topLevelType) {
176 + case topLevelTypes.topMouseWheel:
177 + data = AbstractEvent.normalizeMouseWheelData(nativeEvent);
178 + break;
179 + case topLevelTypes.topScroll:
180 + data = AbstractEvent.normalizeScrollDataFromTarget(renderedTarget);
181 + break;
182 + case topLevelTypes.topClick:
183 + case topLevelTypes.topDoubleClick:
184 + case topLevelTypes.topChange:
185 + case topLevelTypes.topDOMCharacterDataModified:
186 + case topLevelTypes.topMouseDown:
187 + case topLevelTypes.topMouseUp:
188 + case topLevelTypes.topMouseMove:
189 + case topLevelTypes.topTouchMove:
190 + case topLevelTypes.topTouchStart:
191 + case topLevelTypes.topTouchEnd:
192 + data = AbstractEvent.normalizePointerData(nativeEvent);
193 + break;
194 + default:
195 + data = null;
196 + }
197 + var abstractEvent = AbstractEvent.getPooled(
198 + abstractEventType,
199 + renderedTargetID,
200 + topLevelType,
201 + nativeEvent,
202 + data
203 + );
204 + EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
205 + return abstractEvent;
206 + }
207 +};
208 +
209 +SimpleEventPlugin.topLevelTypesToAbstract = {
210 + topMouseDown: SimpleEventPlugin.abstractEventTypes.mouseDown,
211 + topMouseUp: SimpleEventPlugin.abstractEventTypes.mouseUp,
212 + topMouseMove: SimpleEventPlugin.abstractEventTypes.mouseMove,
213 + topClick: SimpleEventPlugin.abstractEventTypes.click,
214 + topDoubleClick: SimpleEventPlugin.abstractEventTypes.doubleClick,
215 + topMouseWheel: SimpleEventPlugin.abstractEventTypes.mouseWheel,
216 + topTouchStart: SimpleEventPlugin.abstractEventTypes.touchStart,
217 + topTouchEnd: SimpleEventPlugin.abstractEventTypes.touchEnd,
218 + topTouchMove: SimpleEventPlugin.abstractEventTypes.touchMove,
219 + topTouchCancel: SimpleEventPlugin.abstractEventTypes.touchCancel,
220 + topKeyUp: SimpleEventPlugin.abstractEventTypes.keyUp,
221 + topKeyPress: SimpleEventPlugin.abstractEventTypes.keyPress,
222 + topKeyDown: SimpleEventPlugin.abstractEventTypes.keyDown,
223 + topFocus: SimpleEventPlugin.abstractEventTypes.focus,
224 + topBlur: SimpleEventPlugin.abstractEventTypes.blur,
225 + topScroll: SimpleEventPlugin.abstractEventTypes.scroll,
226 + topChange: SimpleEventPlugin.abstractEventTypes.change,
227 + topSubmit: SimpleEventPlugin.abstractEventTypes.submit,
228 + topDOMCharacterDataModified:
229 + SimpleEventPlugin.abstractEventTypes.DOMCharacterDataModified
230 +};
231 +
232 +module.exports = SimpleEventPlugin;
src/eventPlugins/TapEventPlugin.js new
+89
@@ -0,0 +1,89 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule TapEventPlugin
17 + */
18 +
19 +"use strict";
20 +
21 +var AbstractEvent = require('AbstractEvent');
22 +var EventPluginUtils = require('EventPluginUtils');
23 +var EventPropagators = require('EventPropagators');
24 +
25 +var keyOf = require('keyOf');
26 +
27 +var isStartish = EventPluginUtils.isStartish;
28 +var isEndish = EventPluginUtils.isEndish;
29 +var storePageCoordsIn = EventPluginUtils.storePageCoordsIn;
30 +var eventDistance = EventPluginUtils.eventDistance;
31 +
32 +/**
33 + * The number of pixels that are tolerated in between a touchStart and
34 + * touchEnd in order to still be considered a 'tap' event.
35 + */
36 +var tapMoveThreshold = 10;
37 +var startCoords = {x: null, y: null};
38 +
39 +var abstractEventTypes = {
40 + touchTap: {
41 + phasedRegistrationNames: {
42 + bubbled: keyOf({onTouchTap: null}),
43 + captured: keyOf({onTouchTapCapture: null})
44 + }
45 + }
46 +};
47 +
48 +/**
49 + * @see EventPluginHub.extractAbstractEvents
50 + */
51 +var extractAbstractEvents = function(
52 + topLevelType,
53 + nativeEvent,
54 + renderedTargetID,
55 + renderedTarget) {
56 +
57 + if (!isStartish(topLevelType) && !isEndish(topLevelType)) {
58 + return;
59 + }
60 + var abstractEvent;
61 + var dist = eventDistance(startCoords, nativeEvent);
62 + if (isEndish(topLevelType) && dist < tapMoveThreshold) {
63 + var type = abstractEventTypes.touchTap;
64 + var abstractTargetID = renderedTargetID;
65 + abstractEvent = AbstractEvent.getPooled(
66 + type,
67 + abstractTargetID,
68 + topLevelType,
69 + nativeEvent
70 + );
71 + }
72 + if (isStartish(topLevelType)) {
73 + storePageCoordsIn(startCoords, nativeEvent);
74 + } else if (isEndish(topLevelType)) {
75 + startCoords.x = 0;
76 + startCoords.y = 0;
77 + }
78 + EventPropagators.accumulateTwoPhaseDispatches(abstractEvent);
79 + return abstractEvent;
80 +};
81 +
82 +var TapEventPlugin = {
83 + tapMoveThreshold: tapMoveThreshold,
84 + startCoords: startCoords,
85 + abstractEventTypes: abstractEventTypes,
86 + extractAbstractEvents: extractAbstractEvents
87 +};
88 +
89 +module.exports = TapEventPlugin;
src/eventPlugins/__tests__/AnalyticsEventPlugin-test.js new
+140
@@ -0,0 +1,140 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +require('mock-modules')
23 + .dontMock('AnalyticsEventPluginFactory')
24 + .dontMock('EventPluginHub')
25 + .dontMock('React')
26 + .dontMock('ReactEvent')
27 + .dontMock('ReactEventTopLevelCallback')
28 + .dontMock('ReactInstanceHandles')
29 + .dontMock('ReactTestUtils');
30 +
31 +var AnalyticsEventPluginFactory = require('AnalyticsEventPluginFactory');
32 +var EventPluginHub = require('EventPluginHub');
33 +var mocks = require('mocks');
34 +var React = require('React');
35 +var ReactEvent = require('ReactEvent');
36 +var ReactEventTopLevelCallback = require('ReactEventTopLevelCallback');
37 +var ReactTestUtils = require('ReactTestUtils');
38 +
39 +ReactEvent.ensureListening(false, ReactEventTopLevelCallback);
40 +
41 +describe('AnalyticsEventPlugin', function() {
42 + it('should count events correctly', function() {
43 + var numClickEvents = 5;
44 + var numDoubleClickEvents = 7;
45 + var TEST_ANALYTICS_ID = 'test_analytics_id';
46 + var TestValidEvents = React.createClass({
47 + render: function() {
48 + return (
49 + <div ref="testDiv"
50 + data-analytics-id={TEST_ANALYTICS_ID}
51 + data-analytics-events='click,doubleClick'>
52 + Test
53 + </div>
54 + );
55 + }
56 + });
57 + var renderedComponent =
58 + ReactTestUtils.renderIntoDocument(<TestValidEvents />);
59 +
60 + var cb = mocks.getMockFunction().mockImplementation(
61 + function(analyticsData) {
62 + expect(Object.keys(analyticsData).length).toBe(1);
63 + expect(Object.keys(analyticsData[TEST_ANALYTICS_ID]).length).toBe(2);
64 + expect(analyticsData[TEST_ANALYTICS_ID].click).toBe(numClickEvents);
65 + expect(analyticsData[TEST_ANALYTICS_ID].doubleClick).toBe(
66 + numDoubleClickEvents
67 + );
68 + }
69 + );
70 +
71 + EventPluginHub.injection.injectEventPluginsByName({
72 + AnalyticsEventPlugin: AnalyticsEventPluginFactory.createAnalyticsPlugin(
73 + cb
74 + )
75 + });
76 +
77 + // Simulate some clicks
78 + for (var i = 0; i < numClickEvents; i++) {
79 + ReactTestUtils.Simulate.click(renderedComponent.refs.testDiv);
80 + }
81 + // Simulate some double clicks
82 + for (i = 0; i < numDoubleClickEvents; i++) {
83 + ReactTestUtils.Simulate.doubleClick(renderedComponent.refs.testDiv);
84 + }
85 + // Simulate some other events not being tracked for analytics
86 + ReactTestUtils.Simulate.focus(renderedComponent.refs.testDiv);
87 +
88 + window.mockRunTimersOnce();
89 + expect(cb).toBeCalled();
90 +
91 + });
92 +
93 + it('error non no callback', function() {
94 + var error = false;
95 + try {
96 + EventPluginHub.injection.injectEventPluginsByName({
97 + AnalyticsEventPlugin: AnalyticsEventPluginFactory.createAnalyticsPlugin(
98 + null
99 + )
100 + });
101 + } catch(e) {
102 + error = true;
103 + }
104 +
105 + expect(error).toBe(true);
106 + });
107 +
108 + it('error on invalid analytics events', function() {
109 + var TestInvalidEvents = React.createClass({
110 + render: function() {
111 + return (
112 + <div ref="testDiv"
113 + data-analytics-id='test_invalid_events'
114 + data-analytics-events='click,123'>
115 + Test
116 + </div>
117 + );
118 + }
119 + });
120 + var renderedComponent =
121 + ReactTestUtils.renderIntoDocument(<TestInvalidEvents />);
122 +
123 + var cb = mocks.getMockFunction();
124 +
125 + EventPluginHub.injection.injectEventPluginsByName({
126 + AnalyticsEventPlugin: AnalyticsEventPluginFactory.createAnalyticsPlugin(
127 + cb
128 + )
129 + });
130 +
131 + var error = false;
132 + try {
133 + ReactTestUtils.Simulate.click(renderedComponent.refs.testDiv);
134 + } catch(e) {
135 + error = true;
136 + }
137 +
138 + expect(error).toBe(true);
139 + });
140 +});
src/eventPlugins/__tests__/ResponderEventPlugin-test.js new
+501
@@ -0,0 +1,501 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +var CallbackRegistry;
22 +var EventConstants;
23 +var EventPropagators;
24 +var ReactInstanceHandles;
25 +var ResponderEventPlugin;
26 +var AbstractEvent;
27 +
28 +var GRANDPARENT_ID = '.reactRoot[0]';
29 +var PARENT_ID = '.reactRoot[0].0';
30 +var CHILD_ID = '.reactRoot[0].0.0';
31 +
32 +var topLevelTypes;
33 +var responderAbstractEventTypes;
34 +var spies;
35 +
36 +var DUMMY_NATIVE_EVENT = {};
37 +var DUMMY_RENDERED_TARGET = {};
38 +
39 +var onStartShouldSetResponder = function(id, cb, capture) {
40 + var registrationNames = responderAbstractEventTypes
41 + .startShouldSetResponder
42 + .phasedRegistrationNames;
43 + CallbackRegistry.putListener(
44 + id,
45 + capture ? registrationNames.captured : registrationNames.bubbled,
46 + cb
47 + );
48 +};
49 +
50 +var onScrollShouldSetResponder = function(id, cb, capture) {
51 + var registrationNames = responderAbstractEventTypes
52 + .scrollShouldSetResponder
53 + .phasedRegistrationNames;
54 + CallbackRegistry.putListener(
55 + id,
56 + capture ? registrationNames.captured : registrationNames.bubbled,
57 + cb
58 + );
59 +};
60 +
61 +var onMoveShouldSetResponder = function(id, cb, capture) {
62 + var registrationNames = responderAbstractEventTypes
63 + .moveShouldSetResponder
64 + .phasedRegistrationNames;
65 + CallbackRegistry.putListener(
66 + id,
67 + capture ? registrationNames.captured : registrationNames.bubbled,
68 + cb
69 + );
70 +};
71 +
72 +
73 +var onResponderGrant = function(id, cb) {
74 + CallbackRegistry.putListener(
75 + id,
76 + responderAbstractEventTypes.responderGrant.registrationName,
77 + cb
78 + );
79 +};
80 +
81 +var extractForTouchStart = function(renderedTargetID) {
82 + return ResponderEventPlugin.extractAbstractEvents(
83 + topLevelTypes.topTouchStart,
84 + DUMMY_NATIVE_EVENT,
85 + renderedTargetID,
86 + DUMMY_RENDERED_TARGET
87 + );
88 +};
89 +
90 +var extractForTouchMove = function(renderedTargetID) {
91 + return ResponderEventPlugin.extractAbstractEvents(
92 + topLevelTypes.topTouchMove,
93 + DUMMY_NATIVE_EVENT,
94 + renderedTargetID,
95 + DUMMY_RENDERED_TARGET
96 + );
97 +};
98 +
99 +var extractForTouchEnd = function(renderedTargetID) {
100 + return ResponderEventPlugin.extractAbstractEvents(
101 + topLevelTypes.topTouchEnd,
102 + DUMMY_NATIVE_EVENT,
103 + renderedTargetID,
104 + DUMMY_RENDERED_TARGET
105 + );
106 +};
107 +
108 +var extractForMouseDown = function(renderedTargetID) {
109 + return ResponderEventPlugin.extractAbstractEvents(
110 + topLevelTypes.topMouseDown,
111 + DUMMY_NATIVE_EVENT,
112 + renderedTargetID,
113 + DUMMY_RENDERED_TARGET
114 + );
115 +};
116 +
117 +var extractForMouseMove = function(renderedTargetID) {
118 + return ResponderEventPlugin.extractAbstractEvents(
119 + topLevelTypes.topMouseMove,
120 + DUMMY_NATIVE_EVENT,
121 + renderedTargetID,
122 + DUMMY_RENDERED_TARGET
123 + );
124 +};
125 +
126 +
127 +var extractForMouseUp = function(renderedTargetID) {
128 + return ResponderEventPlugin.extractAbstractEvents(
129 + topLevelTypes.topMouseUp,
130 + DUMMY_NATIVE_EVENT,
131 + renderedTargetID,
132 + DUMMY_RENDERED_TARGET
133 + );
134 +};
135 +
136 +var extractForScroll = function(renderedTargetID) {
137 + return ResponderEventPlugin.extractAbstractEvents(
138 + topLevelTypes.topScroll,
139 + DUMMY_NATIVE_EVENT,
140 + renderedTargetID,
141 + DUMMY_RENDERED_TARGET
142 + );
143 +};
144 +
145 +
146 +var onGrantChild;
147 +var onGrantParent;
148 +var onGrantGrandParent;
149 +
150 +
151 +var existsInExtraction = function(extracted, test) {
152 + if (Array.isArray(extracted)) {
153 + for (var i = 0; i < extracted.length; i++) {
154 + if (test(extracted[i])) {
155 + return true;
156 + }
157 + }
158 + } else if (extracted) {
159 + return test(extracted);
160 + }
161 + return false;
162 +};
163 +
164 +/**
165 + * Helper validators.
166 + */
167 +function assertGrantEvent(id, extracted) {
168 + var test = function(abstractEvent) {
169 + return abstractEvent instanceof AbstractEvent &&
170 + abstractEvent.type === responderAbstractEventTypes.responderGrant &&
171 + abstractEvent.abstractTargetID === id;
172 + };
173 + expect(ResponderEventPlugin.getResponderID()).toBe(id);
174 + expect(existsInExtraction(extracted, test)).toBe(true);
175 +}
176 +
177 +function assertResponderMoveEvent(id, extracted) {
178 + var test = function(abstractEvent) {
179 + return abstractEvent instanceof AbstractEvent &&
180 + abstractEvent.type === responderAbstractEventTypes.responderMove &&
181 + abstractEvent.abstractTargetID === id;
182 + };
183 + expect(ResponderEventPlugin.getResponderID()).toBe(id);
184 + expect(existsInExtraction(extracted, test)).toBe(true);
185 +}
186 +
187 +function assertTerminateEvent(id, extracted) {
188 + var test = function(abstractEvent) {
189 + return abstractEvent instanceof AbstractEvent &&
190 + abstractEvent.type === responderAbstractEventTypes.responderTerminate &&
191 + abstractEvent.abstractTargetID === id;
192 + };
193 + expect(ResponderEventPlugin.getResponderID()).not.toBe(id);
194 + expect(existsInExtraction(extracted, test)).toBe(true);
195 +}
196 +
197 +function assertRelease(id, extracted) {
198 + var test = function(abstractEvent) {
199 + return abstractEvent instanceof AbstractEvent &&
200 + abstractEvent.type === responderAbstractEventTypes.responderRelease &&
201 + abstractEvent.abstractTargetID === id;
202 + };
203 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
204 + expect(existsInExtraction(extracted, test)).toBe(true);
205 +}
206 +
207 +
208 +function assertNothingExtracted(extracted) {
209 + expect(Array.isArray(extracted)).toBe(false); // No grant events.
210 + expect(Array.isArray(extracted)).toBeFalsy();
211 +}
212 +
213 +
214 +/**
215 + * TODO:
216 + * - Test that returning false from `responderTerminationRequest` will never
217 + * cause the responder to be lost.
218 + * - Automate some of this testing by providing config data - generalize.
219 + */
220 +
221 +describe('ResponderEventPlugin', function() {
222 + beforeEach(function() {
223 + require('mock-modules').dumpCache();
224 +
225 + AbstractEvent = require('AbstractEvent');
226 + CallbackRegistry = require('CallbackRegistry');
227 + EventConstants = require('EventConstants');
228 + EventPropagators = require('EventPropagators');
229 + ReactInstanceHandles = require('ReactInstanceHandles');
230 + ResponderEventPlugin = require('ResponderEventPlugin');
231 + EventPropagators.injection.injectInstanceHandle(ReactInstanceHandles);
232 +
233 + // dumpCache, in open-source tests, only resets existing mocks. It does not
234 + // reset module-state though -- so we need to do this explicitly in the test
235 + // for now. Once that's no longer the case, we can delete this line.
236 + CallbackRegistry.__purge();
237 +
238 + topLevelTypes = EventConstants.topLevelTypes;
239 + responderAbstractEventTypes = ResponderEventPlugin.abstractEventTypes;
240 +
241 + spies = {
242 + onStartShouldSetResponderChild: function() {},
243 + onStartShouldSetResponderParent: function() {},
244 + onStartShouldSetResponderParentCapture: function() {},
245 + onStartShouldSetResponderGrandParent: function() {},
246 + onMoveShouldSetResponderParent: function() {},
247 + onScrollShouldSetResponderParent: function() {}
248 + };
249 +
250 + onGrantChild = function() {};
251 + onGrantParent = function() {};
252 + onGrantGrandParent = function() {};
253 + });
254 +
255 + it('should not auto-set responder on touch start', function() {
256 + // Notice we're not registering the startShould* handler.
257 + var extracted = extractForTouchStart(CHILD_ID);
258 + assertNothingExtracted(extracted);
259 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
260 + });
261 +
262 + it('should not auto-set responder on mouse down', function() {
263 + // Notice we're not registering the startShould* handler.
264 + var extracted = extractForMouseDown(CHILD_ID);
265 + assertNothingExtracted(extracted);
266 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
267 + extractForMouseUp(CHILD_ID); // Let up!
268 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
269 +
270 + // Register `onMoveShould*` handler.
271 + spyOn(spies, 'onMoveShouldSetResponderParent').andReturn(true);
272 + onMoveShouldSetResponder(PARENT_ID, spies.onMoveShouldSetResponderParent);
273 + onResponderGrant(PARENT_ID, onGrantParent);
274 + // Move mouse while not pressing down
275 + extracted = extractForMouseMove(CHILD_ID);
276 + assertNothingExtracted(extracted);
277 + // Not going to call `onMoveShould`* if not touching.
278 + expect(spies.onMoveShouldSetResponderParent.callCount).toBe(0);
279 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
280 +
281 + // Now try the move extraction again, this time while holding down, and not
282 + // letting up.
283 + extracted = extractForMouseDown(CHILD_ID);
284 + assertNothingExtracted(extracted);
285 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
286 +
287 + // Now moving can set the responder, if pressing down, even if there is no
288 + // current responder.
289 + extracted = extractForMouseMove(CHILD_ID);
290 + expect(spies.onMoveShouldSetResponderParent.callCount).toBe(1);
291 + expect(ResponderEventPlugin.getResponderID()).toBe(PARENT_ID);
292 + assertGrantEvent(PARENT_ID, extracted);
293 +
294 + extractForMouseUp(CHILD_ID);
295 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
296 + });
297 +
298 + it('should not extract a grant/release event if double start', function() {
299 + // Return true - we should become the responder.
300 + var extracted;
301 + spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
302 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
303 + onResponderGrant(CHILD_ID, onGrantChild);
304 +
305 + extracted = extractForTouchStart(CHILD_ID);
306 + assertGrantEvent(CHILD_ID, extracted);
307 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
308 +
309 + // Now we do *not* clear out the touch via a simulated touch end. This mocks
310 + // out an environment that likely will never happen, but could in some odd
311 + // error state so it's nice to make sure we recover gracefully.
312 + // extractForTouchEnd(CHILD_ID); // Clear the responder
313 + extracted = extractForTouchStart(CHILD_ID);
314 + assertNothingExtracted();
315 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(2);
316 + });
317 +
318 + it('should bubble/capture responder on start', function() {
319 + // Return true - we should become the responder.
320 + var extracted;
321 + spyOn(spies, 'onStartShouldSetResponderParent').andReturn(true);
322 + spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
323 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
324 + onStartShouldSetResponder(PARENT_ID, spies.onStartShouldSetResponderParent);
325 + onResponderGrant(CHILD_ID, onGrantChild);
326 + onResponderGrant(PARENT_ID, onGrantParent);
327 +
328 + // Nothing extracted if no responder.
329 + extracted = extractForTouchMove(GRANDPARENT_ID);
330 + assertNothingExtracted(extracted);
331 +
332 + extracted = extractForTouchStart(CHILD_ID);
333 + assertGrantEvent(CHILD_ID, extracted);
334 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
335 + expect(spies.onStartShouldSetResponderParent.callCount).toBe(0);
336 +
337 + // Even if moving on the grandparent, the child will receive responder moves
338 + // (This is even true for mouse interactions - which we should absolutely
339 + // test)
340 + extracted = extractForTouchMove(GRANDPARENT_ID);
341 + assertResponderMoveEvent(CHILD_ID, extracted);
342 + extracted = extractForTouchMove(CHILD_ID); // Test move on child node too.
343 + assertResponderMoveEvent(CHILD_ID, extracted);
344 +
345 + // Reset the responder - id passed here shouldn't matter:
346 + // TODO: Test varying the id here.
347 + extracted = extractForTouchEnd(GRANDPARENT_ID); // Clear the responder
348 + assertRelease(CHILD_ID, extracted);
349 +
350 + // Now make sure the parent requests responder on capture.
351 + spyOn(spies, 'onStartShouldSetResponderParentCapture').andReturn(true);
352 + onStartShouldSetResponder(
353 + PARENT_ID,
354 + spies.onStartShouldSetResponderParent,
355 + true // Capture
356 + );
357 + onResponderGrant(PARENT_ID, onGrantGrandParent);
358 + extracted = extractForTouchStart(PARENT_ID);
359 + expect(ResponderEventPlugin.getResponderID()).toBe(PARENT_ID);
360 + assertGrantEvent(PARENT_ID, extracted);
361 + // Now move on various nodes, ensuring that the responder move is emitted to
362 + // the parent node.
363 + extracted = extractForTouchMove(GRANDPARENT_ID);
364 + assertResponderMoveEvent(PARENT_ID, extracted);
365 + extracted = extractForTouchMove(CHILD_ID); // Test move on child node too.
366 + assertResponderMoveEvent(PARENT_ID, extracted);
367 +
368 + // Reset the responder - id passed here shouldn't matter:
369 + // TODO: Test varying the id here.
370 + extracted = extractForTouchEnd(GRANDPARENT_ID); // Clear the responder
371 + assertRelease(PARENT_ID, extracted);
372 +
373 + });
374 +
375 + it('should invoke callback to ask if responder is desired', function() {
376 + // Return true - we should become the responder.
377 + spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
378 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
379 +
380 + var extracted = extractForTouchStart(CHILD_ID);
381 + assertNothingExtracted(extracted);
382 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
383 + expect(ResponderEventPlugin.getResponderID()).toBe(CHILD_ID);
384 + extractForTouchEnd(CHILD_ID); // Clear the responder
385 +
386 + // Now try returning false - we should not become the responder.
387 + spies.onStartShouldSetResponderChild.andReturn(false);
388 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
389 + extracted = extractForTouchStart(CHILD_ID);
390 + assertNothingExtracted(extracted);
391 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(2);
392 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
393 + extractForTouchEnd(CHILD_ID);
394 + expect(ResponderEventPlugin.getResponderID()).toBe(null); // Still null
395 +
396 + // Same thing as before but return true from "shouldSet".
397 + spies.onStartShouldSetResponderChild.andReturn(true);
398 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
399 + onResponderGrant(CHILD_ID, onGrantChild);
400 + extracted = extractForTouchStart(CHILD_ID);
401 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(3);
402 + assertGrantEvent(CHILD_ID, extracted);
403 + extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
404 + assertRelease(CHILD_ID, extracted);
405 + });
406 +
407 + it('should give up responder to parent on move iff allowed', function() {
408 + // Return true - we should become the responder.
409 + var extracted;
410 + spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
411 + spyOn(spies, 'onMoveShouldSetResponderParent').andReturn(true);
412 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
413 + onMoveShouldSetResponder(PARENT_ID, spies.onMoveShouldSetResponderParent);
414 + onResponderGrant(CHILD_ID, onGrantChild);
415 + onResponderGrant(PARENT_ID, onGrantParent);
416 +
417 + spies.onStartShouldSetResponderChild.andReturn(true);
418 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
419 + extracted = extractForTouchStart(CHILD_ID);
420 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
421 + expect(spies.onMoveShouldSetResponderParent.callCount).toBe(0); // none yet
422 + assertGrantEvent(CHILD_ID, extracted); // Child is the current responder
423 +
424 + extracted = extractForTouchMove(CHILD_ID);
425 + expect(spies.onMoveShouldSetResponderParent.callCount).toBe(1);
426 + assertGrantEvent(PARENT_ID, extracted);
427 + assertTerminateEvent(CHILD_ID, extracted);
428 +
429 + extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
430 + assertRelease(PARENT_ID, extracted);
431 + });
432 +
433 + it('should responder move only on direct responder', function() {
434 + // Return true - we should become the responder.
435 + spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
436 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
437 +
438 + var extracted = extractForTouchStart(CHILD_ID);
439 + assertNothingExtracted(extracted);
440 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
441 + expect(ResponderEventPlugin.getResponderID()).toBe(CHILD_ID);
442 + extractForTouchEnd(CHILD_ID); // Clear the responder
443 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
444 +
445 + // Now try returning false - we should not become the responder.
446 + spies.onStartShouldSetResponderChild.andReturn(false);
447 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
448 + extracted = extractForTouchStart(CHILD_ID);
449 + assertNothingExtracted(extracted);
450 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(2);
451 + expect(ResponderEventPlugin.getResponderID()).toBe(null);
452 + extractForTouchEnd(CHILD_ID); // Clear the responder
453 +
454 + // Same thing as before but return true from "shouldSet".
455 + spies.onStartShouldSetResponderChild.andReturn(true);
456 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
457 + onResponderGrant(CHILD_ID, onGrantChild);
458 + extracted = extractForTouchStart(CHILD_ID);
459 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(3);
460 + assertGrantEvent(CHILD_ID, extracted);
461 + extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
462 + assertRelease(CHILD_ID, extracted);
463 + });
464 +
465 + it('should give up responder to parent on scroll iff allowed', function() {
466 + // Return true - we should become the responder.
467 + var extracted;
468 + spyOn(spies, 'onStartShouldSetResponderChild').andReturn(true);
469 + spyOn(spies, 'onMoveShouldSetResponderParent').andReturn(false);
470 + spyOn(spies, 'onScrollShouldSetResponderParent').andReturn(true);
471 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
472 + onMoveShouldSetResponder(PARENT_ID, spies.onMoveShouldSetResponderParent);
473 + onScrollShouldSetResponder(
474 + PARENT_ID,
475 + spies.onScrollShouldSetResponderParent
476 + );
477 + onResponderGrant(CHILD_ID, onGrantChild);
478 + onResponderGrant(PARENT_ID, onGrantParent);
479 +
480 + spies.onStartShouldSetResponderChild.andReturn(true);
481 + onStartShouldSetResponder(CHILD_ID, spies.onStartShouldSetResponderChild);
482 + extracted = extractForTouchStart(CHILD_ID);
483 + expect(spies.onStartShouldSetResponderChild.callCount).toBe(1);
484 + expect(spies.onMoveShouldSetResponderParent.callCount).toBe(0); // none yet
485 + assertGrantEvent(CHILD_ID, extracted); // Child is the current responder
486 +
487 + extracted = extractForTouchMove(CHILD_ID);
488 + expect(spies.onMoveShouldSetResponderParent.callCount).toBe(1);
489 + assertNothingExtracted(extracted);
490 +
491 + extracted = extractForScroll(CHILD_ID); // Could have been parent here too.
492 + expect(spies.onScrollShouldSetResponderParent.callCount).toBe(1);
493 + assertGrantEvent(PARENT_ID, extracted);
494 + assertTerminateEvent(CHILD_ID, extracted);
495 +
496 + extracted = extractForTouchEnd(CHILD_ID); // Clear the responder
497 + assertRelease(PARENT_ID, extracted);
498 + });
499 +
500 +
501 +});
src/package.json new
+6
@@ -0,0 +1,6 @@
1 +{
2 + "name": "react",
3 + "jstest": {
4 + "useAutoMock": false
5 + }
6 +}
src/test/HtmlReporter.js new
+166
@@ -0,0 +1,166 @@
1 +exports.HtmlReporter =
2 +jasmine.HtmlReporter = function(_doc) {
3 + var self = this;
4 + var doc = _doc || window.document;
5 +
6 + var reporterView;
7 +
8 + var dom = {};
9 +
10 + // Jasmine Reporter Public Interface
11 + self.logRunningSpecs = false;
12 +
13 + self.reportRunnerStarting = function(runner) {
14 + var specs = runner.specs() || [];
15 +
16 + if (specs.length == 0) {
17 + return;
18 + }
19 +
20 + createReporterDom(runner.env.versionString());
21 + doc.body.appendChild(dom.reporter);
22 + setExceptionHandling();
23 +
24 + reporterView = new jasmine.HtmlReporter.ReporterView(dom);
25 + reporterView.addSpecs(specs, self.specFilter);
26 + };
27 +
28 + self.reportRunnerResults = function(runner) {
29 + reporterView && reporterView.complete();
30 + };
31 +
32 + self.reportSuiteResults = function(suite) {
33 + reporterView.suiteComplete(suite);
34 + };
35 +
36 + self.reportSpecStarting = function(spec) {
37 + if (self.logRunningSpecs) {
38 + self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
39 + }
40 + };
41 +
42 + self.reportSpecResults = function(spec) {
43 + reporterView.specComplete(spec);
44 + };
45 +
46 + self.log = function() {
47 + var console = jasmine.getGlobal().console;
48 + if (console && console.log) {
49 + if (console.log.apply) {
50 + console.log.apply(console, arguments);
51 + } else {
52 + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
53 + }
54 + }
55 + };
56 +
57 + self.specFilter = function(spec) {
58 + if (!focusedSpecName()) {
59 + return true;
60 + }
61 +
62 + return spec.getFullName().indexOf(focusedSpecName()) === 0;
63 + };
64 +
65 + return self;
66 +
67 + function focusedSpecName() {
68 + var specName;
69 +
70 + (function memoizeFocusedSpec() {
71 + if (specName) {
72 + return;
73 + }
74 +
75 + var paramMap = [];
76 + var params = jasmine.HtmlReporter.parameters(doc);
77 +
78 + for (var i = 0; i < params.length; i++) {
79 + var p = params[i].split('=');
80 + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
81 + }
82 +
83 + specName = paramMap.spec;
84 + })();
85 +
86 + return specName;
87 + }
88 +
89 + function createReporterDom(version) {
90 + dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
91 + dom.banner = self.createDom('div', { className: 'banner' },
92 + self.createDom('span', { className: 'title' }, "Jasmine "),
93 + self.createDom('span', { className: 'version' }, version)),
94 +
95 + dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
96 + dom.alert = self.createDom('div', {className: 'alert'},
97 + self.createDom('span', { className: 'exceptions' },
98 + self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
99 + self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
100 + dom.results = self.createDom('div', {className: 'results'},
101 + dom.summary = self.createDom('div', { className: 'summary' }),
102 + dom.details = self.createDom('div', { id: 'details' }))
103 + );
104 + }
105 +
106 + function noTryCatch() {
107 + return window.location.search.match(/catch=false/);
108 + }
109 +
110 + function searchWithCatch() {
111 + var params = jasmine.HtmlReporter.parameters(window.document);
112 + var removed = false;
113 + var i = 0;
114 +
115 + while (!removed && i < params.length) {
116 + if (params[i].match(/catch=/)) {
117 + params.splice(i, 1);
118 + removed = true;
119 + }
120 + i++;
121 + }
122 + if (jasmine.CATCH_EXCEPTIONS) {
123 + params.push("catch=false");
124 + }
125 +
126 + return params.join("&");
127 + }
128 +
129 + function setExceptionHandling() {
130 + var chxCatch = document.getElementById('no_try_catch');
131 +
132 + if (noTryCatch()) {
133 + chxCatch.setAttribute('checked', true);
134 + jasmine.CATCH_EXCEPTIONS = false;
135 + }
136 + chxCatch.onclick = function() {
137 + window.location.search = searchWithCatch();
138 + };
139 + }
140 +};
141 +jasmine.HtmlReporter.parameters = function(doc) {
142 + var paramStr = doc.location.search.substring(1);
143 + var params = [];
144 +
145 + if (paramStr.length > 0) {
146 + params = paramStr.split('&');
147 + }
148 + return params;
149 +}
150 +jasmine.HtmlReporter.sectionLink = function(sectionName) {
151 + var link = '?';
152 + var params = [];
153 +
154 + if (sectionName) {
155 + params.push('spec=' + encodeURIComponent(sectionName));
156 + }
157 + if (!jasmine.CATCH_EXCEPTIONS) {
158 + params.push("catch=false");
159 + }
160 + if (params.length > 0) {
161 + link += params.join("&");
162 + }
163 +
164 + return link;
165 +};
166 +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
src/test/PrintReporter.js new
+127
@@ -0,0 +1,127 @@
1 +var diff = require('./diff');
2 +
3 +var red = '\u001b[1;41m';
4 +var reset = '\u001b[0m';
5 +
6 +function highlightDifferences(a, b) {
7 + var changes = diff.diffChars(a, b);
8 +
9 + var ret = {a: '', b: ''};
10 + var change;
11 + for (var i = 0, il = changes.length; i < il; i++) {
12 + change = changes[i];
13 + if (change.added) {
14 + ret.b += red + change.value + reset;
15 + } else if (change.removed) {
16 + ret.a += red + change.value + reset;
17 + } else {
18 + ret.a += change.value;
19 + ret.b += change.value;
20 + }
21 + }
22 + return ret;
23 +}
24 +
25 +function PrintReporter(name, root_directory) {
26 + this.skipCount = 0;
27 + this.failCount = 0;
28 + this.expectCount = 0;
29 + this.specCount = 0;
30 + this.name = name;
31 + this.rootDirectory = root_directory;
32 +}
33 +exports.PrintReporter =
34 +jasmine.PrintReporter = PrintReporter;
35 +
36 +PrintReporter.prototype = new jasmine.Reporter();
37 +// Set verbose=true on the test instance to enable additional logging
38 +PrintReporter.prototype.verbose = false;
39 +PrintReporter.prototype._didSpecName = false;
40 +
41 +PrintReporter.prototype.reportSpecStarting = function(spec) {
42 + this._didSpecName = false;
43 + this._spec = spec;
44 +};
45 +
46 +PrintReporter.prototype.reportRunnerResults = function(runner) {
47 + // Don't print out the spec name at the end
48 + this._didSpecName = true;
49 +
50 + this.log([
51 + this.specCount + " spec",
52 + this.expectCount + " expect",
53 + this.skipCount + " skip",
54 + this.failCount + " fail"
55 + ].join(" "));
56 +
57 + require("test/phantom").exit(this.failCount);
58 +};
59 +
60 +
61 +PrintReporter.prototype.reportSuiteResults = function(suite) {
62 + var results = suite.results();
63 + if (this.verbose) {
64 + this.log('Suite "' + suite.description + '": ' +
65 + results.passedCount + '/' + results.totalCount + ' passed.');
66 + }
67 +
68 + // If suite is nested, only count the root suite
69 + if (!suite.parentSuite) {
70 + this.failCount += results.totalCount - results.passedCount;
71 + this.expectCount += results.totalCount;
72 + }
73 +};
74 +
75 +PrintReporter.prototype.reportSpecResults = function(spec) {
76 + if (this.verbose) {
77 + this.log('it... ' +
78 + spec.description + ': ' + (spec.results().passed() ? 'pass' : 'fail'));
79 + }
80 +
81 + var self = this;
82 + var results = spec.results();
83 +
84 + this.specCount += 1;
85 +
86 + if (results.skipped)
87 + this.skipCount += 1;
88 +
89 + results.getItems().forEach(function(result) {
90 + if (result.type == 'log') {
91 + self.log(result.toString());
92 + } else if (result.type == 'expect' && result.passed && !result.passed()) {
93 + var ppActual = jasmine.pp(result.actual);
94 + var ppExpected = jasmine.pp(result.expected);
95 +
96 + var colorDiff = highlightDifferences(ppActual, ppExpected);
97 +
98 + var message = result.message;
99 + message = message.replace(ppActual, colorDiff.a);
100 + message = message.replace(ppExpected, colorDiff.b);
101 +
102 + self.log(message);
103 + if (result.trace.stack) {
104 + self.log();
105 + result.trace.stack.split('\n').forEach(function(stackFrame) {
106 + if (stackFrame.indexOf('/jasmine/lib/jasmine-core') > -1) {
107 + return;
108 + }
109 + if (this.rootDirectory) {
110 + stackFrame = stackFrame.replace(this.rootDirectory + '/', '');
111 + }
112 + self.log(' ' + stackFrame);
113 + }.bind(this));
114 + }
115 + }
116 + });
117 +};
118 +
119 +PrintReporter.prototype.log = function(str) {
120 + if (!this._didSpecName) {
121 + console.log('');
122 + console.log(
123 + this._spec.suite.description + ': it ' + this._spec.description);
124 + this._didSpecName = true;
125 + }
126 + console.log(str || '');
127 +};
src/test/ReactTestUtils.js new
+284
@@ -0,0 +1,284 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactTestUtils
17 + */
18 +
19 +var EventConstants = require('EventConstants');
20 +var React = require('React');
21 +var ReactComponent = require('ReactComponent');
22 +var ReactEvent = require('ReactEvent');
23 +var ReactTextComponent = require('ReactTextComponent');
24 +
25 +var ge = require('ge');
26 +var mergeInto = require('mergeInto');
27 +
28 +var topLevelTypes = EventConstants.topLevelTypes;
29 +
30 +function Event(suffix) {}
31 +
32 +/**
33 + * @class ReactTestUtils
34 + */
35 +
36 +/**
37 + * Todo: Support the entire DOM.scry query syntax. For now, these simple
38 + * utilities will suffice for testing purposes.
39 + * @lends ReactTestUtils
40 + */
41 +var ReactTestUtils = {
42 + renderIntoDocument: function(instance) {
43 + var div = document.createElement('div');
44 + document.documentElement.appendChild(div);
45 + return React.renderComponent(instance, div);
46 + },
47 +
48 + isDOMComponent: function(inst) {
49 + return !!(inst &&
50 + ReactComponent.isValidComponent(inst) &&
51 + !!inst.tagName);
52 + },
53 +
54 + isCompositeComponent: function(inst) {
55 + return !!(
56 + inst &&
57 + ReactComponent.isValidComponent(inst) &&
58 + typeof inst.render === 'function' &&
59 + typeof inst.setState === 'function' &&
60 + typeof inst.updateComponent === 'function'
61 + );
62 + },
63 +
64 + isCompositeComponentWithType: function(inst, type) {
65 + return !!(ReactTestUtils.isCompositeComponent(inst) &&
66 + (inst.constructor === type.componentConstructor ||
67 + inst.constructor === type));
68 + },
69 +
70 + isTextComponent: function(inst) {
71 + return inst instanceof ReactTextComponent;
72 + },
73 +
74 + findAllInRenderedTree: function(inst, test) {
75 + if (!inst) {
76 + return [];
77 + }
78 + var ret = test(inst) ? [inst] : [];
79 + if (ReactTestUtils.isDOMComponent(inst)) {
80 + var renderedChildren = inst._renderedChildren;
81 + var key;
82 + for (key in renderedChildren) {
83 + if (!renderedChildren.hasOwnProperty(key)) {
84 + continue;
85 + }
86 + ret = ret.concat(
87 + ReactTestUtils.findAllInRenderedTree(renderedChildren[key], test)
88 + );
89 + }
90 + } else if (ReactTestUtils.isCompositeComponent(inst)) {
91 + ret = ret.concat(
92 + ReactTestUtils.findAllInRenderedTree(inst._renderedComponent, test)
93 + );
94 + }
95 + return ret;
96 + },
97 +
98 + /**
99 + * Finds all instance of components in the rendered tree that are DOM
100 + * components with the class name matching `className`.
101 + * @return an array of all the matches.
102 + */
103 + scryRenderedDOMComponentsWithClass: function(root, className) {
104 + return ReactTestUtils.findAllInRenderedTree(root, function(inst) {
105 + var instClassName = inst.props.className;
106 + return ReactTestUtils.isDOMComponent(inst) && (
107 + instClassName &&
108 + (' ' + instClassName + ' ').indexOf(' ' + className + ' ') !== -1
109 + );
110 + });
111 + },
112 +
113 + /**
114 + * Like scryRenderedDOMComponentsWithClass but expects there to be one result,
115 + * and returns that one result, or throws exception if there is any other
116 + * number of matches besides one.
117 + * @return {!ReactNativeComponent} The one match.
118 + */
119 + findRenderedDOMComponentWithClass: function(root, className) {
120 + var all =
121 + ReactTestUtils.scryRenderedDOMComponentsWithClass(root, className);
122 + if (all.length !== 1) {
123 + throw new Error('Did not find exactly one match for class:' + className);
124 + }
125 + return all[0];
126 + },
127 +
128 +
129 + /**
130 + * Finds all instance of components in the rendered tree that are DOM
131 + * components with the tag name matching `tagName`.
132 + * @return an array of all the matches.
133 + */
134 + scryRenderedDOMComponentsWithTag: function(root, tagName) {
135 + return ReactTestUtils.findAllInRenderedTree(root, function(inst) {
136 + return ReactTestUtils.isDOMComponent(inst) &&
137 + inst.tagName === tagName.toUpperCase();
138 + });
139 + },
140 +
141 + /**
142 + * Like scryRenderedDOMComponentsWithTag but expects there to be one result,
143 + * and returns that one result, or throws exception if there is any other
144 + * number of matches besides one.
145 + * @return {!ReactNativeComponent} The one match.
146 + */
147 + findRenderedDOMComponentWithTag: function(root, tagName) {
148 + var all = ReactTestUtils.scryRenderedDOMComponentsWithTag(root, tagName);
149 + if (all.length !== 1) {
150 + throw new Error('Did not find exactly one match for tag:' + tagName);
151 + }
152 + return all[0];
153 + },
154 +
155 +
156 + /**
157 + * Finds all instances of components with type equal to `componentType`.
158 + * @return an array of all the matches.
159 + */
160 + scryRenderedComponentsWithType: function(root, componentType) {
161 + return ReactTestUtils.findAllInRenderedTree(root, function(inst) {
162 + return ReactTestUtils.isCompositeComponentWithType(inst, componentType);
163 + });
164 + },
165 +
166 + /**
167 + * Same as `scryRenderedComponentsWithType` but expects there to be one result
168 + * and returns that one result, or throws exception if there is any other
169 + * number of matches besides one.
170 + * @return {!ReactComponent} The one match.
171 + */
172 + findRenderedComponentWithType: function(root, componentType) {
173 + var all = ReactTestUtils.scryRenderedComponentsWithType(
174 + root,
175 + componentType
176 + );
177 + if (all.length !== 1) {
178 + throw new Error(
179 + 'Did not find exactly one match for componentType:' + componentType
180 + );
181 + }
182 + return all[0];
183 + },
184 +
185 + /**
186 + * Simulates a top level event being dispatched from a raw event that occured
187 + * on and `Element` node.
188 + * @param topLevelType {Object} A type from `EventConstants.topLevelTypes`
189 + * @param {!Element} node The dom to simulate an event occurring on.
190 + * @param {?Event} fakeNativeEvent Fake native event to pass to ReactEvent.
191 + */
192 + simulateEventOnNode: function(topLevelType, node, fakeNativeEvent) {
193 + var virtualHandler =
194 + ReactEvent.TopLevelCallbackCreator.createTopLevelCallback(topLevelType);
195 + fakeNativeEvent.target = node;
196 + virtualHandler(fakeNativeEvent);
197 + },
198 +
199 + /**
200 + * Simulates a top level event being dispatched from a raw event that occured
201 + * on the `ReactNativeComponent` `comp`.
202 + * @param topLevelType {Object} A type from `EventConstants.topLevelTypes`.
203 + * @param comp {!ReactNativeComponent}
204 + * @param {?Event} fakeNativeEvent Fake native event to pass to ReactEvent.
205 + */
206 + simulateEventOnDOMComponent: function(topLevelType, comp, fakeNativeEvent) {
207 + var reactRootID = comp._rootNodeID || comp._rootDomId;
208 + if (!reactRootID) {
209 + throw new Error('Simulating event on non-rendered component');
210 + }
211 + var virtualHandler =
212 + ReactEvent.TopLevelCallbackCreator.createTopLevelCallback(topLevelType);
213 + var node = ge(reactRootID);
214 + fakeNativeEvent.target = node;
215 + /* jsdom is returning nodes without id's - fixing that issue here. */
216 + node.id = reactRootID;
217 + virtualHandler(fakeNativeEvent);
218 + },
219 +
220 + nativeTouchData: function(x, y) {
221 + return {
222 + touches: [
223 + {pageX: x, pageY: y}
224 + ]
225 + };
226 + },
227 +
228 + Simulate: null // Will populate
229 +};
230 +
231 +/**
232 + * Exports:
233 + *
234 + * - `ReactTestUtils.Simulate.click(Element/ReactNativeComponent)`
235 + * - `ReactTestUtils.Simulate.mouseMove(Element/ReactNativeComponent)`
236 + * - `ReactTestUtils.Simulate.mouseIn/ReactNativeComponent)`
237 + * - `ReactTestUtils.Simulate.mouseOut(Element/ReactNativeComponent)`
238 + * - ... (All keys from `EventConstants.topLevelTypes`)
239 + *
240 + * Note: Top level event types are a subset of the entire set of handler types
241 + * (which include a broader set of "synthetic" events). For example, onDragDone
242 + * is a synthetic event. You certainly may write test cases for these event
243 + * types, but it doesn't make sense to simulate them at this low of a level. In
244 + * this case, the way you test an `onDragDone` event is by simulating a series
245 + * of `mouseMove`/ `mouseDown`/`mouseUp` events - Then, a synthetic event of
246 + * type `onDragDone` will be constructed and dispached through your system
247 + * automatically.
248 + */
249 +
250 +function makeSimulator(eventType) {
251 + return function(domComponentOrNode, nativeEventData) {
252 + var fakeNativeEvent = new Event(eventType);
253 + mergeInto(fakeNativeEvent, nativeEventData);
254 + if (ReactTestUtils.isDOMComponent(domComponentOrNode)) {
255 + ReactTestUtils.simulateEventOnDOMComponent(
256 + eventType,
257 + domComponentOrNode,
258 + fakeNativeEvent
259 + );
260 + } else if (!!domComponentOrNode.tagName) {
261 + // Will allow on actual dom nodes.
262 + ReactTestUtils.simulateEventOnNode(
263 + eventType,
264 + domComponentOrNode,
265 + fakeNativeEvent
266 + );
267 + }
268 + };
269 +}
270 +
271 +ReactTestUtils.Simulate = {};
272 +var eventType;
273 +for (eventType in topLevelTypes) {
274 + // Event type is stored as 'topClick' - we transform that to 'click'
275 + var convenienceName = eventType.indexOf('top') === 0 ?
276 + eventType.charAt(3).toLowerCase() + eventType.substr(4) : eventType;
277 + /**
278 + * @param {!Element || ReactNativeComponent} domComponentOrNode
279 + * @param {?Event} nativeEventData Fake native event to pass to ReactEvent.
280 + */
281 + ReactTestUtils.Simulate[convenienceName] = makeSimulator(eventType);
282 +}
283 +
284 +module.exports = ReactTestUtils;
src/test/ReporterView.js new
+164
@@ -0,0 +1,164 @@
1 +jasmine.HtmlReporter.ReporterView = function(dom) {
2 + this.startedAt = new Date();
3 + this.runningSpecCount = 0;
4 + this.completeSpecCount = 0;
5 + this.passedCount = 0;
6 + this.failedCount = 0;
7 + this.skippedCount = 0;
8 +
9 + this.createResultsMenu = function() {
10 + this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
11 + this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
12 + ' | ',
13 + this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
14 +
15 + this.summaryMenuItem.onclick = function() {
16 + dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
17 + };
18 +
19 + this.detailsMenuItem.onclick = function() {
20 + showDetails();
21 + };
22 + };
23 +
24 + this.addSpecs = function(specs, specFilter) {
25 + this.totalSpecCount = specs.length;
26 +
27 + this.views = {
28 + specs: {},
29 + suites: {}
30 + };
31 +
32 + for (var i = 0; i < specs.length; i++) {
33 + var spec = specs[i];
34 + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
35 + if (specFilter(spec)) {
36 + this.runningSpecCount++;
37 + }
38 + }
39 + };
40 +
41 + this.specComplete = function(spec) {
42 + this.completeSpecCount++;
43 +
44 + if (isUndefined(this.views.specs[spec.id])) {
45 + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
46 + }
47 +
48 + var specView = this.views.specs[spec.id];
49 +
50 + switch (specView.status()) {
51 + case 'passed':
52 + this.passedCount++;
53 + break;
54 +
55 + case 'failed':
56 + this.failedCount++;
57 + break;
58 +
59 + case 'skipped':
60 + this.skippedCount++;
61 + break;
62 + }
63 +
64 + specView.refresh();
65 + this.refresh();
66 + };
67 +
68 + this.suiteComplete = function(suite) {
69 + var suiteView = this.views.suites[suite.id];
70 + if (isUndefined(suiteView)) {
71 + return;
72 + }
73 + suiteView.refresh();
74 + };
75 +
76 + this.refresh = function() {
77 +
78 + if (isUndefined(this.resultsMenu)) {
79 + this.createResultsMenu();
80 + }
81 +
82 + // currently running UI
83 + if (isUndefined(this.runningAlert)) {
84 + this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
85 + dom.alert.appendChild(this.runningAlert);
86 + }
87 + this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
88 +
89 + // skipped specs UI
90 + if (isUndefined(this.skippedAlert)) {
91 + this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
92 + }
93 +
94 + this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
95 +
96 + if (this.skippedCount === 1 && isDefined(dom.alert)) {
97 + dom.alert.appendChild(this.skippedAlert);
98 + }
99 +
100 + // passing specs UI
101 + if (isUndefined(this.passedAlert)) {
102 + this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
103 + }
104 + this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
105 +
106 + // failing specs UI
107 + if (isUndefined(this.failedAlert)) {
108 + this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
109 + }
110 + this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
111 +
112 + if (this.failedCount === 1 && isDefined(dom.alert)) {
113 + dom.alert.appendChild(this.failedAlert);
114 + dom.alert.appendChild(this.resultsMenu);
115 + }
116 +
117 + // summary info
118 + this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
119 + this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
120 + };
121 +
122 + this.complete = function() {
123 + dom.alert.removeChild(this.runningAlert);
124 +
125 + this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
126 +
127 + if (this.failedCount === 0) {
128 + dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
129 + } else {
130 + showDetails();
131 + }
132 +
133 + dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
134 + };
135 +
136 + return this;
137 +
138 + function showDetails() {
139 + if (dom.reporter.className.search(/showDetails/) === -1) {
140 + dom.reporter.className += " showDetails";
141 + }
142 + }
143 +
144 + function isUndefined(obj) {
145 + return typeof obj === 'undefined';
146 + }
147 +
148 + function isDefined(obj) {
149 + return !isUndefined(obj);
150 + }
151 +
152 + function specPluralizedFor(count) {
153 + var str = count + " spec";
154 + if (count > 1) {
155 + str += "s"
156 + }
157 + return str;
158 + }
159 +
160 +};
161 +
162 +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
163 +
164 +
src/test/SpecView.js new
+79
@@ -0,0 +1,79 @@
1 +jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
2 + this.spec = spec;
3 + this.dom = dom;
4 + this.views = views;
5 +
6 + this.symbol = this.createDom('li', { className: 'pending' });
7 + this.dom.symbolSummary.appendChild(this.symbol);
8 +
9 + this.summary = this.createDom('div', { className: 'specSummary' },
10 + this.createDom('a', {
11 + className: 'description',
12 + href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
13 + title: this.spec.getFullName()
14 + }, this.spec.description)
15 + );
16 +
17 + this.detail = this.createDom('div', { className: 'specDetail' },
18 + this.createDom('a', {
19 + className: 'description',
20 + href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
21 + title: this.spec.getFullName()
22 + }, this.spec.getFullName())
23 + );
24 +};
25 +
26 +jasmine.HtmlReporter.SpecView.prototype.status = function() {
27 + return this.getSpecStatus(this.spec);
28 +};
29 +
30 +jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
31 + this.symbol.className = this.status();
32 +
33 + switch (this.status()) {
34 + case 'skipped':
35 + break;
36 +
37 + case 'passed':
38 + this.appendSummaryToSuiteDiv();
39 + break;
40 +
41 + case 'failed':
42 + this.appendSummaryToSuiteDiv();
43 + this.appendFailureDetail();
44 + break;
45 + }
46 +};
47 +
48 +jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
49 + this.summary.className += ' ' + this.status();
50 + this.appendToSummary(this.spec, this.summary);
51 +};
52 +
53 +jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
54 + this.detail.className += ' ' + this.status();
55 +
56 + var resultItems = this.spec.results().getItems();
57 + var messagesDiv = this.createDom('div', { className: 'messages' });
58 +
59 + for (var i = 0; i < resultItems.length; i++) {
60 + var result = resultItems[i];
61 +
62 + if (result.type == 'log') {
63 + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
64 + } else if (result.type == 'expect' && result.passed && !result.passed()) {
65 + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
66 +
67 + if (result.trace.stack) {
68 + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
69 + }
70 + }
71 + }
72 +
73 + if (messagesDiv.childNodes.length > 0) {
74 + this.detail.appendChild(messagesDiv);
75 + this.dom.details.appendChild(this.detail);
76 + }
77 +};
78 +
79 +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);
\ No newline at end of file
src/test/SuiteView.js new
+22
@@ -0,0 +1,22 @@
1 +jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
2 + this.suite = suite;
3 + this.dom = dom;
4 + this.views = views;
5 +
6 + this.element = this.createDom('div', { className: 'suite' },
7 + this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
8 + );
9 +
10 + this.appendToSummary(this.suite, this.element);
11 +};
12 +
13 +jasmine.HtmlReporter.SuiteView.prototype.status = function() {
14 + return this.getSpecStatus(this.suite);
15 +};
16 +
17 +jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
18 + this.element.className += " " + this.status();
19 +};
20 +
21 +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
22 +
src/test/all.js new
+18
@@ -0,0 +1,18 @@
1 +// This file exists both to give a single entry point for all the utility
2 +// modules in src/test and to specify an ordering on those modules, since
3 +// some still have implicit dependencies on others.
4 +
5 +require("./phantom");
6 +require("./console");
7 +require("ReactTestUtils");
8 +require("reactComponentExpect");
9 +require("./diff");
10 +require("./PrintReporter");
11 +require("./HtmlReporter");
12 +require("./ReporterView");
13 +require("./SpecView");
14 +require("./SuiteView");
15 +require("./jasmine-support");
16 +require("mocks");
17 +require("mock-modules");
18 +require("./mock-timers");
src/test/console.js new
+92
@@ -0,0 +1,92 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * This module allows interception of console.* method calls, so that
17 + * listeners other than the native console object can consume logged
18 + * messages.
19 + */
20 +
21 +var global = Function("return this")();
22 +var Ap = Array.prototype;
23 +var slice = Ap.slice;
24 +
25 +var nativeConsole;
26 +if ("console" in global) {
27 + var gc = global.console;
28 + try { delete global.console } catch (ignored) {}
29 + nativeConsole = global.console || gc;
30 + global.console = gc;
31 +}
32 +
33 +// Provide a reliable console interface in all browsers.
34 +global.console = exports;
35 +
36 +function makeLogger(method) {
37 + var logger = exports[method] = function() {
38 + var args = slice.call(arguments);
39 + listeners.forEach(function(listener) {
40 + listener(method, args, logger);
41 + });
42 + };
43 +}
44 +
45 +makeLogger("info");
46 +makeLogger("log");
47 +makeLogger("debug");
48 +makeLogger("warn");
49 +makeLogger("error");
50 +makeLogger("dir");
51 +makeLogger("dirxml");
52 +makeLogger("trace");
53 +makeLogger("assert");
54 +makeLogger("count");
55 +makeLogger("markTimeline");
56 +makeLogger("profile");
57 +makeLogger("profileEnd");
58 +makeLogger("time");
59 +makeLogger("timeEnd");
60 +makeLogger("timeStamp");
61 +makeLogger("group");
62 +makeLogger("groupCollapsed");
63 +makeLogger("groupEnd");
64 +makeLogger("clear");
65 +
66 +var listeners = [];
67 +function addListener(listener) {
68 + listeners.push(listener);
69 +}
70 +exports.addListener = addListener
71 +
72 +function clean(arg) {
73 + if (typeof arg === "string") {
74 + // Remove any xterm-color escape sequences.
75 + // TODO Display colors in console if possible.
76 + arg = arg.replace(/\033\[\d+m/g, "");
77 + }
78 + return arg;
79 +}
80 +
81 +if (nativeConsole) {
82 + // If present, the native console object becomes a listener just like
83 + // any other.
84 + addListener(function(method, args) {
85 + var method = nativeConsole[method];
86 + if (method) {
87 + method.apply(nativeConsole, args.map(clean));
88 + } else {
89 + // Ignore this call.
90 + }
91 + });
92 +}
src/test/diff.js new
+349
@@ -0,0 +1,349 @@
1 +/* See LICENSE file for terms of use */
2 +
3 +/*
4 + * Text diff implementation.
5 + *
6 + * This library supports the following APIS:
7 + * JsDiff.diffChars: Character by character diff
8 + * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace
9 + * JsDiff.diffLines: Line based diff
10 + *
11 + * JsDiff.diffCss: Diff targeted at CSS content
12 + *
13 + * These methods are based on the implementation proposed in
14 + * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986).
15 + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927
16 + */
17 +var JsDiff = (function() {
18 + function clonePath(path) {
19 + return { newPos: path.newPos, components: path.components.slice(0) };
20 + }
21 + function removeEmpty(array) {
22 + var ret = [];
23 + for (var i = 0; i < array.length; i++) {
24 + if (array[i]) {
25 + ret.push(array[i]);
26 + }
27 + }
28 + return ret;
29 + }
30 + function escapeHTML(s) {
31 + var n = s;
32 + n = n.replace(/&/g, "&amp;");
33 + n = n.replace(/</g, "&lt;");
34 + n = n.replace(/>/g, "&gt;");
35 + n = n.replace(/"/g, "&quot;");
36 +
37 + return n;
38 + }
39 +
40 + var fbDiff = function(ignoreWhitespace) {
41 + this.ignoreWhitespace = ignoreWhitespace;
42 + };
43 + fbDiff.prototype = {
44 + diff: function(oldString, newString) {
45 + // Handle the identity case (this is due to unrolling editLength == 0
46 + if (newString == oldString) {
47 + return [{ value: newString }];
48 + }
49 + if (!newString) {
50 + return [{ value: oldString, removed: true }];
51 + }
52 + if (!oldString) {
53 + return [{ value: newString, added: true }];
54 + }
55 +
56 + newString = this.tokenize(newString);
57 + oldString = this.tokenize(oldString);
58 +
59 + var newLen = newString.length, oldLen = oldString.length;
60 + var maxEditLength = newLen + oldLen;
61 + var bestPath = [{ newPos: -1, components: [] }];
62 +
63 + // Seed editLength = 0
64 + var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
65 + if (bestPath[0].newPos+1 >= newLen && oldPos+1 >= oldLen) {
66 + return bestPath[0].components;
67 + }
68 +
69 + for (var editLength = 1; editLength <= maxEditLength; editLength++) {
70 + for (var diagonalPath = -1*editLength; diagonalPath <= editLength; diagonalPath+=2) {
71 + var basePath;
72 + var addPath = bestPath[diagonalPath-1],
73 + removePath = bestPath[diagonalPath+1];
74 + oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
75 + if (addPath) {
76 + // No one else is going to attempt to use this value, clear it
77 + bestPath[diagonalPath-1] = undefined;
78 + }
79 +
80 + var canAdd = addPath && addPath.newPos+1 < newLen;
81 + var canRemove = removePath && 0 <= oldPos && oldPos < oldLen;
82 + if (!canAdd && !canRemove) {
83 + bestPath[diagonalPath] = undefined;
84 + continue;
85 + }
86 +
87 + // Select the diagonal that we want to branch from. We select the prior
88 + // path whose position in the new string is the farthest from the origin
89 + // and does not pass the bounds of the diff graph
90 + if (!canAdd || (canRemove && addPath.newPos < removePath.newPos)) {
91 + basePath = clonePath(removePath);
92 + this.pushComponent(basePath.components, oldString[oldPos], undefined, true);
93 + } else {
94 + basePath = clonePath(addPath);
95 + basePath.newPos++;
96 + this.pushComponent(basePath.components, newString[basePath.newPos], true, undefined);
97 + }
98 +
99 + var oldPos = this.extractCommon(basePath, newString, oldString, diagonalPath);
100 +
101 + if (basePath.newPos+1 >= newLen && oldPos+1 >= oldLen) {
102 + return basePath.components;
103 + } else {
104 + bestPath[diagonalPath] = basePath;
105 + }
106 + }
107 + }
108 + },
109 +
110 + pushComponent: function(components, value, added, removed) {
111 + var last = components[components.length-1];
112 + if (last && last.added === added && last.removed === removed) {
113 + // We need to clone here as the component clone operation is just
114 + // as shallow array clone
115 + components[components.length-1] =
116 + {value: this.join(last.value, value), added: added, removed: removed };
117 + } else {
118 + components.push({value: value, added: added, removed: removed });
119 + }
120 + },
121 + extractCommon: function(basePath, newString, oldString, diagonalPath) {
122 + var newLen = newString.length,
123 + oldLen = oldString.length,
124 + newPos = basePath.newPos,
125 + oldPos = newPos - diagonalPath;
126 + while (newPos+1 < newLen && oldPos+1 < oldLen && this.equals(newString[newPos+1], oldString[oldPos+1])) {
127 + newPos++;
128 + oldPos++;
129 +
130 + this.pushComponent(basePath.components, newString[newPos], undefined, undefined);
131 + }
132 + basePath.newPos = newPos;
133 + return oldPos;
134 + },
135 +
136 + equals: function(left, right) {
137 + var reWhitespace = /\S/;
138 + if (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) {
139 + return true;
140 + } else {
141 + return left == right;
142 + }
143 + },
144 + join: function(left, right) {
145 + return left + right;
146 + },
147 + tokenize: function(value) {
148 + return value;
149 + }
150 + };
151 +
152 + var CharDiff = new fbDiff();
153 +
154 + var WordDiff = new fbDiff(true);
155 + WordDiff.tokenize = function(value) {
156 + return removeEmpty(value.split(/(\s+|\b)/));
157 + };
158 +
159 + var CssDiff = new fbDiff(true);
160 + CssDiff.tokenize = function(value) {
161 + return removeEmpty(value.split(/([{}:;,]|\s+)/));
162 + };
163 +
164 + var LineDiff = new fbDiff();
165 + LineDiff.tokenize = function(value) {
166 + return value.split(/^/m);
167 + };
168 +
169 + return {
170 + diffChars: function(oldStr, newStr) { return CharDiff.diff(oldStr, newStr); },
171 + diffWords: function(oldStr, newStr) { return WordDiff.diff(oldStr, newStr); },
172 + diffLines: function(oldStr, newStr) { return LineDiff.diff(oldStr, newStr); },
173 +
174 + diffCss: function(oldStr, newStr) { return CssDiff.diff(oldStr, newStr); },
175 +
176 + createPatch: function(fileName, oldStr, newStr, oldHeader, newHeader) {
177 + var ret = [];
178 +
179 + ret.push("Index: " + fileName);
180 + ret.push("===================================================================");
181 + ret.push("--- " + fileName + (typeof oldHeader === "undefined" ? "" : "\t" + oldHeader));
182 + ret.push("+++ " + fileName + (typeof newHeader === "undefined" ? "" : "\t" + newHeader));
183 +
184 + var diff = LineDiff.diff(oldStr, newStr);
185 + if (!diff[diff.length-1].value) {
186 + diff.pop(); // Remove trailing newline add
187 + }
188 + diff.push({value: "", lines: []}); // Append an empty value to make cleanup easier
189 +
190 + function contextLines(lines) {
191 + return lines.map(function(entry) { return ' ' + entry; });
192 + }
193 + function eofNL(curRange, i, current) {
194 + var last = diff[diff.length-2],
195 + isLast = i === diff.length-2,
196 + isLastOfType = i === diff.length-3 && (current.added !== last.added || current.removed !== last.removed);
197 +
198 + // Figure out if this is the last line for the given file and missing NL
199 + if (!/\n$/.test(current.value) && (isLast || isLastOfType)) {
200 + curRange.push('\\ No newline at end of file');
201 + }
202 + }
203 +
204 + var oldRangeStart = 0, newRangeStart = 0, curRange = [],
205 + oldLine = 1, newLine = 1;
206 + for (var i = 0; i < diff.length; i++) {
207 + var current = diff[i],
208 + lines = current.lines || current.value.replace(/\n$/, "").split("\n");
209 + current.lines = lines;
210 +
211 + if (current.added || current.removed) {
212 + if (!oldRangeStart) {
213 + var prev = diff[i-1];
214 + oldRangeStart = oldLine;
215 + newRangeStart = newLine;
216 +
217 + if (prev) {
218 + curRange = contextLines(prev.lines.slice(-4));
219 + oldRangeStart -= curRange.length;
220 + newRangeStart -= curRange.length;
221 + }
222 + }
223 + curRange.push.apply(curRange, lines.map(function(entry) { return (current.added?"+":"-") + entry; }));
224 + eofNL(curRange, i, current);
225 +
226 + if (current.added) {
227 + newLine += lines.length;
228 + } else {
229 + oldLine += lines.length;
230 + }
231 + } else {
232 + if (oldRangeStart) {
233 + // Close out any changes that have been output (or join overlapping)
234 + if (lines.length <= 8 && i < diff.length-2) {
235 + // Overlapping
236 + curRange.push.apply(curRange, contextLines(lines));
237 + } else {
238 + // end the range and output
239 + var contextSize = Math.min(lines.length, 4);
240 + ret.push(
241 + "@@ -" + oldRangeStart + "," + (oldLine-oldRangeStart+contextSize)
242 + + " +" + newRangeStart + "," + (newLine-newRangeStart+contextSize)
243 + + " @@");
244 + ret.push.apply(ret, curRange);
245 + ret.push.apply(ret, contextLines(lines.slice(0, contextSize)));
246 + if (lines.length <= 4) {
247 + eofNL(ret, i, current);
248 + }
249 +
250 + oldRangeStart = 0; newRangeStart = 0; curRange = [];
251 + }
252 + }
253 + oldLine += lines.length;
254 + newLine += lines.length;
255 + }
256 + }
257 +
258 + return ret.join('\n') + '\n';
259 + },
260 +
261 + applyPatch: function(oldStr, uniDiff) {
262 + var diffstr = uniDiff.split("\n");
263 + var diff = [];
264 + var remEOFNL = false,
265 + addEOFNL = false;
266 +
267 + for (var i = (diffstr[0][0]=="I"?4:0); i < diffstr.length; i++) {
268 + if(diffstr[i][0] == "@") {
269 + var meh = diffstr[i].split(/@@ -(\d+),(\d+) \+(\d+),(\d+) @@/);
270 + diff.unshift({
271 + start:meh[3],
272 + oldlength:meh[2],
273 + oldlines:[],
274 + newlength:meh[4],
275 + newlines:[]
276 + });
277 + } else if(diffstr[i][0] == '+') {
278 + diff[0].newlines.push(diffstr[i].substr(1));
279 + } else if(diffstr[i][0] == '-') {
280 + diff[0].oldlines.push(diffstr[i].substr(1));
281 + } else if(diffstr[i][0] == ' ') {
282 + diff[0].newlines.push(diffstr[i].substr(1));
283 + diff[0].oldlines.push(diffstr[i].substr(1));
284 + } else if(diffstr[i][0] == '\\') {
285 + if (diffstr[i-1][0] == '+') {
286 + remEOFNL = true;
287 + } else if(diffstr[i-1][0] == '-') {
288 + addEOFNL = true;
289 + }
290 + }
291 + }
292 +
293 + var str = oldStr.split("\n");
294 + for (var i = diff.length - 1; i >= 0; i--) {
295 + var d = diff[i];
296 + for (var j = 0; j < d.oldlength; j++) {
297 + if(str[d.start-1+j] != d.oldlines[j]) {
298 + return false;
299 + }
300 + }
301 + Array.prototype.splice.apply(str,[d.start-1,+d.oldlength].concat(d.newlines));
302 + }
303 +
304 + if (remEOFNL) {
305 + while (!str[str.length-1]) {
306 + str.pop();
307 + }
308 + } else if (addEOFNL) {
309 + str.push('');
310 + }
311 + return str.join('\n');
312 + },
313 +
314 + convertChangesToXML: function(changes){
315 + var ret = [];
316 + for ( var i = 0; i < changes.length; i++) {
317 + var change = changes[i];
318 + if (change.added) {
319 + ret.push("<ins>");
320 + } else if (change.removed) {
321 + ret.push("<del>");
322 + }
323 +
324 + ret.push(escapeHTML(change.value));
325 +
326 + if (change.added) {
327 + ret.push("</ins>");
328 + } else if (change.removed) {
329 + ret.push("</del>");
330 + }
331 + }
332 + return ret.join("");
333 + },
334 +
335 + // See: http://code.google.com/p/google-diff-match-patch/wiki/API
336 + convertChangesToDMP: function(changes){
337 + var ret = [], change;
338 + for ( var i = 0; i < changes.length; i++) {
339 + change = changes[i];
340 + ret.push([(change.added ? 1 : change.removed ? -1 : 0), change.value]);
341 + }
342 + return ret;
343 + }
344 + };
345 +})();
346 +
347 +if (typeof module !== "undefined") {
348 + module.exports = JsDiff;
349 +}
src/test/jasmine-support.js new
+134
@@ -0,0 +1,134 @@
1 +var global = Function("return this")();
2 +var jasmine = global.jasmine;
3 +var spec = false; // TODO
4 +
5 +// Add some matcher for mock functions
6 +jasmine.getEnv().beforeEach(function() {
7 + this.addMatchers({
8 + toBeCalled: function() {
9 + if (this.actual.mock === undefined) {
10 + throw Error('toBeCalled() should be used on a mock function');
11 + }
12 + return this.actual.mock.calls.length !== 0;
13 + },
14 +
15 + lastCalledWith: function() {
16 + if (this.actual.mock === undefined) {
17 + throw Error('toBeCalled() should be used on a mock function');
18 + }
19 + var calls = this.actual.mock.calls;
20 + var args = Array.prototype.slice.call(arguments);
21 + return this.env.equals_(calls[calls.length - 1], args);
22 + },
23 +
24 + toBeCalledWith: function() {
25 + if (this.actual.mock === undefined) {
26 + throw Error('toBeCalled() should be used on a mock function');
27 + }
28 + var args = Array.prototype.slice.call(arguments);
29 + return this.actual.mock.calls.some(function(call) {
30 + return this.env.equals_(call, args);
31 + }.bind(this));
32 + }
33 + });
34 +});
35 +
36 +var _it = jasmine.Env.prototype.it;
37 +var _xit = jasmine.Env.prototype.xit;
38 +jasmine.Env.prototype.it = function(desc, func) {
39 + // If spec is provided, only run matching specs
40 + if (!spec || desc.match(new RegExp(spec, 'i'))) {
41 + return _it.bind(this, desc, func)();
42 + } else {
43 + return this.xit(desc, func);
44 + }
45 +};
46 +jasmine.Env.prototype.xit = function(desc, func) {
47 + if (typeof func == 'function') {
48 + var matches = func.toString().match(/.*\W?expect\(/g);
49 + if (matches) {
50 + this.reporter.subReporters_[0].skipCount += matches.length;
51 + this.reporter.subReporters_[0].totalCount += matches.length;
52 + }
53 + }
54 + return _xit.bind(this, desc, func)();
55 +}
56 +
57 +// Mainline Jasmine sets __Jasmine_been_here_before__ on each object to detect
58 +// cycles, but that doesn't work on frozen objects so we use a WeakMap instead.
59 +// We can only do this if if WeakMap is available, but if WeakMap is
60 +// unavailable, then objects probably can't be frozen anyway.
61 +
62 +if (typeof WeakMap !== "undefined") {
63 + var _comparedObjects = new WeakMap;
64 +
65 + jasmine.Env.prototype.compareObjects_ =
66 + function(a, b, mismatchKeys, mismatchValues) {
67 + if (_comparedObjects.get(a) === b && _comparedObjects.get(b) === a) {
68 + return true;
69 + }
70 + var areArrays = jasmine.isArray_(a) && jasmine.isArray_(b);
71 +
72 + _comparedObjects.set(a, b);
73 + _comparedObjects.set(b, a);
74 +
75 + var hasKey = function(obj, keyName) {
76 + return obj != null && obj[keyName] !== jasmine.undefined;
77 + };
78 +
79 + for (var property in b) {
80 + if (areArrays && typeof b[property] == 'function') {
81 + continue;
82 + }
83 + if (!hasKey(a, property) && hasKey(b, property)) {
84 + mismatchKeys.push(
85 + "expected has key '" + property + "', but missing from actual."
86 + );
87 + }
88 + }
89 + for (property in a) {
90 + if (areArrays && typeof a[property] == 'function') {
91 + continue;
92 + }
93 + if (!hasKey(b, property) && hasKey(a, property)) {
94 + mismatchKeys.push(
95 + "expected missing key '" + property + "', but present in actual."
96 + );
97 + }
98 + }
99 + for (property in b) {
100 + // The only different implementation from the original jasmine
101 + if (areArrays &&
102 + (typeof a[property] == 'function' ||
103 + typeof b[property] == 'function')) {
104 + continue;
105 + }
106 + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues))
107 + {
108 + mismatchValues.push(
109 + "'" + property + "' was '" + (b[property] ?
110 + jasmine.util.htmlEscape(b[property].toString()) :
111 + b[property]) +
112 + "' in expected, but was '" + (a[property] ?
113 + jasmine.util.htmlEscape(a[property].toString()) :
114 + a[property]) + "' in actual."
115 + );
116 + }
117 + }
118 +
119 + if (areArrays &&
120 + a.length != b.length) {
121 + mismatchValues.push("arrays were not the same length");
122 + }
123 +
124 + _comparedObjects.delete(a);
125 + _comparedObjects.delete(b);
126 + return (mismatchKeys.length == 0 && mismatchValues.length == 0);
127 + };
128 +}
129 +
130 +var HtmlReporter = require("./HtmlReporter").HtmlReporter;
131 +var PrintReporter = require("./PrintReporter").PrintReporter;
132 +
133 +jasmine.getEnv().addReporter(new HtmlReporter);
134 +jasmine.getEnv().addReporter(new PrintReporter);
src/test/mock-modules.js new
+33
@@ -0,0 +1,33 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule mock-modules
17 + */
18 +
19 +var global = Function("return this")();
20 +require('test/mock-timers').installMockTimers(global);
21 +
22 +exports.dumpCache = function() {
23 + require("mocks").clear();
24 + return exports;
25 +};
26 +
27 +exports.dontMock = function() {
28 + return exports;
29 +};
30 +
31 +exports.mock = function() {
32 + return exports;
33 +};
src/test/mock-timers.js new
+226
@@ -0,0 +1,226 @@
1 +var mocks = require('mocks');
2 +
3 +var timers = {};
4 +// Keep a fake timestamp
5 +// move on the time when runTimersToTime() is called
6 +var now = 0;
7 +
8 +// add a timer of type either 'timeout' or 'interval'
9 +function _setTimer(type, callback, delay) {
10 + var token = null;
11 + do {
12 + token = Math.floor(Math.random() * 4294967296) + 1;
13 + } while (timers[token]);
14 + timers[token] = {
15 + type: type,
16 + callback: callback,
17 + // Add some random msecs to the delay to mimic code execution time.
18 + time: now + delay + Math.floor(Math.random() * 5),
19 + interval: (type == 'interval' ? delay : 0)
20 + };
21 + return token;
22 +}
23 +
24 +// clear a timer of type either 'timeout' or 'interval'
25 +function _clearTimer(type, token) {
26 + if (timers[token] && timers[token].type == type) {
27 + delete timers[token];
28 + }
29 +}
30 +
31 +function _setTimeout(callback, delay) {
32 + return _setTimer('timeout', callback, delay);
33 +}
34 +
35 +function _setInterval(callback, delay) {
36 + return _setTimer('interval', callback, delay);
37 +}
38 +
39 +function _clearTimeout(token) {
40 + _clearTimer('timeout', token);
41 +}
42 +
43 +function _clearInterval(token) {
44 + _clearTimer('interval', token);
45 +}
46 +
47 +// Run timer of given token
48 +function _runTimer(token) {
49 + // Skip non-existing token
50 + if (timers[token]) {
51 + if (timers[token].type == 'timeout') {
52 + // for 'timeout', run callback and delete the timer
53 + var callback = timers[token].callback;
54 + delete timers[token];
55 + callback();
56 + } else if (timers[token].type == 'interval') {
57 + // for 'interval', run callback and set the next invoke time
58 + // Add some random msecs to the delay to mimic code execution time.
59 + timers[token].time =
60 + now + timers[token].interval + Math.floor(Math.random() * 5);
61 + timers[token].callback();
62 + }
63 + }
64 +}
65 +
66 +function _runTimersOnce() {
67 + // Invoke all timers once regardsless of the delay
68 + for (var token in timers) {
69 + _runTimer(token);
70 + }
71 +}
72 +
73 +function _runTimersToTime(delay) {
74 + var toRunToken = _getNextTimerToken();
75 + if (!toRunToken) {
76 + return;
77 + }
78 +
79 + var minTime = timers[toRunToken].time;
80 + if (now + delay < minTime) {
81 + // Termination when there's no more timers to invoke
82 + now += delay;
83 + } else {
84 + // Recursively invoke the next to-run timer
85 + delay -= (minTime - now);
86 + now = minTime;
87 + _runTimer(toRunToken);
88 + _runTimersToTime(delay);
89 + }
90 +}
91 +
92 +function _runTimersRepeatedly() {
93 + // Only run a generous 1000 timers and then bail, since we may have entered
94 + // a loop if we have more than that.
95 + var max_timers = 1000;
96 +
97 + var token;
98 + for (var ii = 0; ii < max_timers; ii++) {
99 + token = _getNextTimerToken();
100 +
101 + if (!token) {
102 + break;
103 + }
104 +
105 + _runTimer(token);
106 + }
107 +
108 + if (ii === max_timers) {
109 + throw new Error("More timers still exist after " + max_timers + " timers!");
110 + }
111 +}
112 +
113 +function _clearTimers() {
114 + for (var token in timers) {
115 + delete timers[token];
116 + }
117 +}
118 +
119 +function _getNextTimerToken() {
120 + var nextTimerToken = null;
121 + var minTime = 31536000000; // One year
122 + // Find the next to invoke timer
123 + for (var token in timers) {
124 + if (timers[token].time < minTime) {
125 + nextTimerToken = token;
126 + minTime = timers[token].time;
127 + }
128 + }
129 + return nextTimerToken;
130 +}
131 +
132 +var mockTimers = {
133 + setTimeout: _setTimeout,
134 + clearTimeout: _clearTimeout,
135 + setInterval: _setInterval,
136 + clearInterval: _clearInterval,
137 +
138 + /**
139 + * Copyright 2013 Facebook, Inc.
140 + *
141 + * Licensed under the Apache License, Version 2.0 (the "License");
142 + * you may not use this file except in compliance with the License.
143 + * You may obtain a copy of the License at
144 + *
145 + * http://www.apache.org/licenses/LICENSE-2.0
146 + *
147 + * Unless required by applicable law or agreed to in writing, software
148 + * distributed under the License is distributed on an "AS IS" BASIS,
149 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
150 + * See the License for the specific language governing permissions and
151 + * limitations under the License.
152 + *
153 + * Iteratively run callbacks in time order during the time from now to
154 + * now + delay.
155 + * If one callback register another timer which should be run during now to
156 + * now + delay, the new timer will also be run in the right order.
157 + *
158 + * @param delay
159 + */
160 + runTimersToTime: _runTimersToTime,
161 +
162 + /**
163 + * Run all registered timer once. Newly registered timers will not be run.
164 + */
165 + runTimersOnce: _runTimersOnce,
166 +
167 + /**
168 + * Iteratively run callbacks until there are no timers left to call. Will
169 + * stop after a maximum number of iterations to avoid infinite loop.
170 + *
171 + * @param maximum iterations (optional)
172 + */
173 + runTimersRepeatedly: _runTimersRepeatedly,
174 +
175 + /**
176 + * Clear all timers
177 + */
178 + clearTimers: _clearTimers,
179 +
180 + /**
181 + * Get the number of remaining timers
182 + */
183 + getTimersCount: function() {
184 + return Object.keys(timers).length;
185 + }
186 +};
187 +
188 +module.exports.installMockTimers = function(window) {
189 + window._originalTimeouts = {
190 + setTimeout: window.setTimeout,
191 + clearTimeout: window.clearTimeout,
192 + setInterval: window.setInterval,
193 + clearInterval: window.clearInterval
194 + };
195 + window.setTimeout =
196 + mocks.getMockFunction().mockImplementation(mockTimers.setTimeout);
197 + window.clearTimeout =
198 + mocks.getMockFunction().mockImplementation(mockTimers.clearTimeout);
199 + window.setInterval =
200 + mocks.getMockFunction().mockImplementation(mockTimers.setInterval);
201 + window.clearInterval =
202 + mocks.getMockFunction().mockImplementation(mockTimers.clearInterval);
203 + window.mockRunTimersOnce =
204 + mocks.getMockFunction().mockImplementation(mockTimers.runTimersOnce);
205 + window.mockRunTimersToTime =
206 + mocks.getMockFunction().mockImplementation(mockTimers.runTimersToTime);
207 + window.mockRunTimersRepeatedly =
208 + mocks.getMockFunction().mockImplementation(mockTimers.runTimersRepeatedly);
209 + window.mockClearTimers =
210 + mocks.getMockFunction().mockImplementation(mockTimers.clearTimers);
211 + window.mockGetTimersCount =
212 + mocks.getMockFunction().mockImplementation(mockTimers.getTimersCount);
213 +};
214 +
215 +module.exports.uninstallMockTimers = function(window) {
216 + window.setTimeout = window._originalTimeouts.setTimeout;
217 + window.clearTimeout = window._originalTimeouts.clearTimeout;
218 + window.setInterval = window._originalTimeouts.setInterval;
219 + window.clearInterval = window._originalTimeouts.clearInterval;
220 + window._originalTimeouts = undefined;
221 + window.mockRunTimersOnce = undefined;
222 + window.mockRunTimersToTime = undefined;
223 + window.mockRunTimersRepeatedly = undefined;
224 + window.mockClearTimers = undefined;
225 + window.mockGetTimersCount = undefined;
226 +};
src/test/mocks.js new
+431
@@ -0,0 +1,431 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule mocks
17 + */
18 +
19 +function isA(typeName, value) {
20 + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
21 +}
22 +
23 +function getType(ref) {
24 + if (isA('RegExp', ref)) {
25 + return 'regexp';
26 + }
27 +
28 + if (isA('Array', ref)) {
29 + return 'array';
30 + }
31 +
32 + if (isA('Function', ref)) {
33 + return 'function';
34 + }
35 +
36 + if (isA('Object', ref)) {
37 + return 'object';
38 + }
39 +
40 + // consider number and string fields to be constants that we want to
41 + // pick up as they are
42 + if (isA('Number', ref) || isA('String', ref)) {
43 + return 'constant';
44 + }
45 +
46 + return null;
47 +}
48 +
49 +function makeComponent(metadata) {
50 + switch (metadata.type) {
51 + case 'object':
52 + return {};
53 +
54 + case 'array':
55 + return [];
56 +
57 + case 'regexp':
58 + return new RegExp();
59 +
60 + case 'constant':
61 + return metadata.value;
62 +
63 + case 'function':
64 + var defaultReturnValue;
65 + var specificReturnValues = [];
66 + var mockImpl;
67 + var isReturnValueLastSet = false;
68 + var calls = [];
69 + var instances = [];
70 + var prototype =
71 + (metadata.members && metadata.members.prototype &&
72 + metadata.members.prototype.members) || {};
73 +
74 + var f = function() {
75 + dirtyMocks.push(f);
76 +
77 + instances.push(this);
78 + calls.push(Array.prototype.slice.call(arguments));
79 + if (this instanceof arguments.callee) {
80 + // This is probably being called as a constructor
81 + for (var slot in prototype) {
82 + // Copy prototype methods to the instance to make
83 + // it easier to interact with mock instance call and
84 + // return values
85 + if (prototype[slot].type == 'function') {
86 + var protoImpl = this[slot];
87 + this[slot] = generateFromMetadata(prototype[slot]);
88 + this[slot]._protoImpl = protoImpl;
89 + }
90 + }
91 +
92 + // Run the mock constructor implementation
93 + mockImpl && mockImpl.apply(this, arguments);
94 + return;
95 + }
96 +
97 + var returnValue;
98 + // If return value is last set, either specific or default, i.e.
99 + // mockReturnValue()/mockDefaultReturnValue() is called and no
100 + // mockImplementation() is called after that.
101 + // use the set return value.
102 + if (isReturnValueLastSet) {
103 + returnValue = specificReturnValues.shift();
104 + if (returnValue === undefined) {
105 + returnValue = defaultReturnValue;
106 + }
107 + }
108 +
109 + // If mockImplementation() is last set, or specific return values
110 + // are used up, use the mock implementation.
111 + if (mockImpl && returnValue === undefined) {
112 + return mockImpl.apply(this, arguments);
113 + }
114 +
115 + // Otherwise use prototype implementation
116 + if (returnValue === undefined && arguments.callee._protoImpl) {
117 + return arguments.callee._protoImpl.apply(this, arguments);
118 + }
119 +
120 + return returnValue;
121 + };
122 +
123 + f._isMockFunction = true;
124 +
125 + f.mock = {
126 + calls : calls,
127 + instances : instances
128 + };
129 +
130 + f.mockClear = function() {
131 + calls.length = 0;
132 + instances.length = 0;
133 + };
134 +
135 + f.mockReturnValue = function(value) {
136 + // next function call will return this value or default return value
137 + isReturnValueLastSet = true;
138 + specificReturnValues.push(value);
139 + return f;
140 + };
141 +
142 + f.mockDefaultReturnValue = function(value) {
143 + // next function call will return specified return value or this one
144 + isReturnValueLastSet = true;
145 + defaultReturnValue = value;
146 + return f;
147 + };
148 +
149 + f.mockImplementation = function(fn) {
150 + // next function call will use mock implementation return value
151 + isReturnValueLastSet = false;
152 + mockImpl = fn;
153 + return f;
154 + };
155 +
156 + f.mockReturnThis = function() {
157 + return f.mockImplementation(function() {
158 + return this;
159 + });
160 + };
161 +
162 + f._getMockImplementation = function() {
163 + return mockImpl;
164 + };
165 +
166 + if (metadata.mockImpl) {
167 + f.mockImplementation(metadata.mockImpl);
168 + }
169 +
170 + return f;
171 + }
172 +
173 + throw new Error('Unrecognized type ' + metadata.type);
174 +}
175 +
176 +function generateFromMetadata(_metadata) {
177 + var callbacks = [];
178 + var refs = {};
179 +
180 + function generateMock(metadata) {
181 + var mock = makeComponent(metadata);
182 + if (metadata.ref_id != null) {
183 + refs[metadata.ref_id] = mock;
184 + }
185 +
186 + function getRefCallback(slot, ref) {
187 + return function() {
188 + mock[slot] = refs[ref];
189 + };
190 + }
191 +
192 + for (var slot in metadata.members) {
193 + var slotMetadata = metadata.members[slot];
194 + if (slotMetadata.ref != null) {
195 + callbacks.push(getRefCallback(slot, slotMetadata.ref));
196 + } else {
197 + mock[slot] = generateMock(slotMetadata);
198 + }
199 + }
200 +
201 + return mock;
202 + }
203 +
204 + var mock = generateMock(_metadata);
205 + callbacks.forEach(function(setter) {
206 + setter();
207 + });
208 +
209 + return mock;
210 +}
211 +
212 +
213 +function _getMetadata(component, _refs) {
214 + var refs = _refs || [];
215 +
216 + // This is a potential performance drain, since the whole list is scanned
217 + // for every component
218 + var ref = refs.indexOf(component);
219 + if (ref > -1) {
220 + return {ref: ref};
221 + }
222 +
223 + var type = getType(component);
224 + if (!type) {
225 + return null;
226 + }
227 +
228 + var metadata = {type : type};
229 + if (type == 'constant') {
230 + metadata.value = component;
231 + return metadata;
232 + } else if (type == 'function') {
233 + if (component._isMockFunction) {
234 + metadata.mockImpl = component._getMockImplementation();
235 + }
236 + }
237 +
238 + metadata.ref_id = refs.length;
239 + refs.push(component);
240 +
241 + var members = null;
242 +
243 + function addMember(slot, data) {
244 + if (!data) {
245 + return;
246 + }
247 + if (!members) {
248 + members = {};
249 + }
250 + members[slot] = data;
251 + }
252 +
253 + // Leave arrays alone
254 + if (type != 'array') {
255 + for (var slot in component) {
256 + if (slot.charAt(0) == '_' ||
257 + (type == 'function' && component._isMockFunction &&
258 + slot.match(/^mock/))) {
259 + continue;
260 + }
261 +
262 + if (component.hasOwnProperty(slot) ||
263 + (type == 'object' && component[slot] != Object.prototype[slot])) {
264 + addMember(slot, _getMetadata(component[slot], refs));
265 + }
266 + }
267 +
268 + // If component is native code function, prototype might be undefined
269 + if (type == 'function' && component.prototype) {
270 + var prototype = _getMetadata(component.prototype, refs);
271 + if (prototype && prototype.members) {
272 + addMember('prototype', prototype);
273 + }
274 + }
275 + }
276 +
277 + if (members) {
278 + metadata.members = members;
279 + }
280 +
281 + return metadata;
282 +}
283 +
284 +function removeUnusedRefs(metadata) {
285 + function visit(md, f) {
286 + f(md);
287 + if (md.members) {
288 + for (var slot in md.members) {
289 + visit(md.members[slot], f);
290 + }
291 + }
292 + }
293 +
294 + var usedRefs = {};
295 + visit(metadata, function(md) {
296 + if (md.ref != null) {
297 + usedRefs[md.ref] = true;
298 + }
299 + });
300 +
301 + visit(metadata, function(md) {
302 + if (!usedRefs[md.ref_id]) {
303 + delete md.ref_id;
304 + }
305 + });
306 +}
307 +
308 +var dirtyMocks = [];
309 +
310 +module.exports = {
311 + /**
312 + * Invokes the .mockClear method of all function mocks that have been
313 + * called since the last time clear was called.
314 + */
315 + clear: function() {
316 + var old = dirtyMocks;
317 + dirtyMocks = [];
318 + old.forEach(function(mock) {
319 + mock.mockClear();
320 + });
321 + },
322 +
323 + /**
324 + * Generates a mock based on the given metadata. Mocks treat functions
325 + * specially, and all mock functions have additional members, described in the
326 + * documentation for getMockFunction in this module.
327 + *
328 + * One important note: function prototoypes are handled specially by this
329 + * mocking framework. For functions with prototypes, when called as a
330 + * constructor, the mock will install mocked function members on the instance.
331 + * This allows different instances of the same constructor to have different
332 + * values for its mocks member and its return values.
333 + *
334 + * @param metadata Metadata for the mock in the schema returned by the
335 + * getMetadata method of this module.
336 + *
337 + */
338 + generateFromMetadata: generateFromMetadata,
339 +
340 + /**
341 + * Inspects the argument and returns its schema in the following recursive
342 + * format:
343 + * {
344 + * type: ...
345 + * members : {}
346 + * }
347 + *
348 + * Where type is one of 'array', 'object', 'function', or 'ref', and members
349 + * is an optional dictionary where the keys are member names and the values
350 + * are metadata objects. Function prototypes are defined simply by defining
351 + * metadata for the member.prototype of the function. The type of a function
352 + * prototype should always be "object". For instance, a simple class might be
353 + * defined like this:
354 + *
355 + * {
356 + * type: 'function',
357 + * members: {
358 + * staticMethod: {type: 'function'},
359 + * prototype: {
360 + * type: 'object',
361 + * members: {
362 + * instanceMethod: {type: 'function'}
363 + * }
364 + * }
365 + * }
366 + * }
367 + *
368 + * Metadata may also contain references to other objects defined within the
369 + * same metadata object. The metadata for the referent must be marked with
370 + * 'ref_id' key and an arbitrary value. The referer must be marked with a
371 + * 'ref' key that has the same value as object with ref_id that it refers to.
372 + * For instance, this metadata blob:
373 + * {
374 + * type: 'object',
375 + * ref_id: 1,
376 + * members: {
377 + * self: {ref: 1}
378 + * }
379 + * }
380 + *
381 + * defines an object with a slot named 'self' that refers back to the object.
382 + *
383 + * @param component The component for which to retrieve metadata.
384 + */
385 + getMetadata: function(component) {
386 + var metadata = _getMetadata(component);
387 + // to make it easier to work with mock metadata, only preserve references
388 + // that are actually used
389 + removeUnusedRefs(metadata);
390 + return metadata;
391 + },
392 +
393 + /**
394 + * Generates a stand-alone function with members that help drive unit tests or
395 + * confirm expectations. Specifically, functions returned by this method have
396 + * the following members:
397 + *
398 + * .mock:
399 + * An object with two members, "calls", and "instances", which are both
400 + * lists. The items in the "calls" list are the arguments with which the
401 + * function was called. The "instances" list stores the value of 'this' for
402 + * each call to the function. This is useful for retrieving instances from a
403 + * constructor.
404 + *
405 + * .mockReturnValue(value)
406 + * Pushes the given value onto a FIFO queue of return values for the
407 + * function.
408 + *
409 + * .mockDefaultReturnValue(value)
410 + * Sets the default return value for the function.
411 + *
412 + * .mockImplementation(function)
413 + * Sets a mock implementation for the function.
414 + *
415 + * .mockReturnThis()
416 + * Syntactic sugar for .mockImplementation(function() {return this;})
417 + *
418 + * In case both mockImplementation() and
419 + * mockReturnValue()/mockDefaultReturnValue() are called. The priority of
420 + * which to use is based on what is the last call:
421 + * - if the last call is mockRetrunValue() or mockDefaultReturnValue(),
422 + * use the specific return specific return value or default return value.
423 + * If specific return values are used up or no default return value is set,
424 + * fall back to try mockImplementation();
425 + * - if the last call is mockImplementation(), run the given implementation
426 + * and return the result.
427 + */
428 + getMockFunction: function() {
429 + return makeComponent({type: 'function'});
430 + }
431 +};
src/test/phantom.js new
+64
@@ -0,0 +1,64 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * This module provides a bridge from content-land to phantom-land via the
17 + * window.callPhantom interface.
18 + */
19 +
20 +var console = require("./console");
21 +var global = Function("return this")();
22 +var Ap = Array.prototype;
23 +var slice = Ap.slice;
24 +var Fp = Function.prototype;
25 +
26 +if (!Fp.bind) {
27 + // PhantomJS doesn't support Function.prototype.bind natively, so
28 + // polyfill it whenever this module is required.
29 + Fp.bind = function(context) {
30 + var func = this;
31 + var args = slice.call(arguments, 1);
32 + return args.length > 0 ? function() {
33 + return func.apply(
34 + context || this,
35 + args.concat(slice.call(arguments))
36 + );
37 + } : function() {
38 + return func.apply(context || this, arguments);
39 + };
40 + };
41 +}
42 +
43 +if (global.callPhantom) {
44 + // Phantom's onConsoleMessage support is lacking (only one argument
45 + // supported, and it must be a string), so we use callPhantom to
46 + // forward console messages instead.
47 + console.addListener(function(method, args) {
48 + global.callPhantom({
49 + type: "console",
50 + method: method,
51 + args: args
52 + });
53 + });
54 +
55 + exports.exit = function(code) {
56 + global.callPhantom({
57 + type: "exit",
58 + code: code
59 + });
60 + };
61 +
62 +} else {
63 + exports.exit = function() {};
64 +}
src/test/reactComponentExpect.js new
+190
@@ -0,0 +1,190 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule reactComponentExpect
17 + * @nolint
18 + */
19 +
20 +var ReactComponent = require('ReactComponent');
21 +var ReactTestUtils = require('ReactTestUtils');
22 +
23 +var copyProperties = require('copyProperties');
24 +
25 +function reactComponentExpect(instance) {
26 + if (instance instanceof reactComponentExpect) {
27 + return instance;
28 + }
29 +
30 + if (!(this instanceof reactComponentExpect)) {
31 + return new reactComponentExpect(instance);
32 + }
33 +
34 + this._instance = instance;
35 + this.toBeValidReactComponent();
36 +}
37 +
38 +copyProperties(reactComponentExpect.prototype, {
39 + // Getters -------------------------------------------------------------------
40 +
41 + /**
42 + * @instance: Retrieves the backing instance.
43 + */
44 + instance: function() {
45 + return this._instance;
46 + },
47 +
48 + /**
49 + * There are two types of components in the world.
50 + * - A component created via React.createClass() - Has a single child
51 + * subComponent - the return value from the .render() function. This
52 + * function @subComponent expects that this._instance is component created
53 + * with React.createClass().
54 + * - A primitive DOM component - which has many renderedChildren, each of
55 + * which may have a name that is unique with respect to its siblings. This
56 + * method will fail if this._instance is a primitive component.
57 + *
58 + * TL;DR: An instance may have a subComponent (this._renderedComponent) or
59 + * renderedChildren, but never both. Neither will actually show up until you
60 + * render the component (simply instantiating is not enough).
61 + */
62 + expectRenderedChild: function() {
63 + this.toBeCompositeComponent();
64 + return new reactComponentExpect(this.instance()._renderedComponent);
65 + },
66 +
67 + /**
68 + * The nth child of a DOMish component instance that is not falsy.
69 + */
70 + expectRenderedChildAt: function(childIndex) {
71 + // Currently only dom components have arrays of children, but that will
72 + // change soon.
73 + this.toBeDOMComponent();
74 + var renderedChildren = this.instance()._renderedChildren || {};
75 + var nonEmptyCount = 0;
76 + var name;
77 + for (name in renderedChildren) {
78 + if (!renderedChildren.hasOwnProperty(name)) {
79 + continue;
80 + }
81 + if (renderedChildren[name]) {
82 + if (nonEmptyCount === childIndex) {
83 + return new reactComponentExpect(renderedChildren[name]);
84 + }
85 + nonEmptyCount++;
86 + }
87 + }
88 + throw new Error('Child:' + childIndex + ' is not found');
89 + },
90 +
91 + toBeDOMComponentWithChildCount: function(n) {
92 + this.toBeDOMComponent();
93 + expect(this.instance()._renderedChildren).toBeTruthy();
94 + var len = Object.keys(this.instance()._renderedChildren).length;
95 + expect(len).toBe(n);
96 + return this;
97 + },
98 +
99 + toBeDOMComponentWithNoChildren: function() {
100 + this.toBeDOMComponent();
101 + expect(this.instance()._renderedChildren).toBeFalsy();
102 + return this;
103 + },
104 +
105 + // Matchers ------------------------------------------------------------------
106 +
107 + /**
108 + * A component that is created with React.createClass. Just duck typing
109 + * here.
110 + */
111 + toBeCompositeComponent: function() {
112 + this.toBeValidReactComponent();
113 + expect(ReactTestUtils.isCompositeComponent(this.instance())).toBe(true);
114 + return this;
115 + },
116 +
117 + toBeCompositeComponentWithType: function(convenienceConstructor) {
118 + expect(ReactTestUtils.isCompositeComponentWithType(
119 + this.instance(),
120 + convenienceConstructor
121 + )).toBe(true);
122 + return this;
123 + },
124 +
125 + toBeTextComponent: function() {
126 + expect(ReactTestUtils.isTextComponent(this.instance())).toBe(true);
127 + return this;
128 + },
129 +
130 + /**
131 + * Falsy values are valid components - the vanished component that is.
132 + */
133 + toBeValidReactComponent: function() {
134 + expect(ReactComponent.isValidComponent(this.instance())).toBe(true);
135 + return this;
136 + },
137 +
138 + toBePresent: function() {
139 + expect(this.instance()).toBeTruthy();
140 + return this;
141 + },
142 +
143 + /**
144 + * A terminal type of component representing some virtual dom node. Just duck
145 + * typing here.
146 + */
147 + toBeDOMComponent: function() {
148 + expect(ReactTestUtils.isDOMComponent(this.instance())).toBe(true);
149 + return this;
150 + },
151 +
152 + toBeDOMComponentWithTag: function(tag) {
153 + this.toBeDOMComponent();
154 + expect(this.instance().tagName).toBe(tag.toUpperCase());
155 + return this;
156 + },
157 +
158 + /**
159 + * Check that internal state values are equal to a state of expected values.
160 + */
161 + scalarStateEqual: function(stateNameToExpectedValue) {
162 + expect(this.instance()).toBeTruthy();
163 + for (var stateName in stateNameToExpectedValue) {
164 + if (!stateNameToExpectedValue.hasOwnProperty(stateName)) {
165 + continue;
166 + }
167 + expect(this.instance().state[stateName])
168 + .toEqual(stateNameToExpectedValue[stateName]);
169 + }
170 + return this;
171 + },
172 +
173 + /**
174 + * Check a set of props are equal to a set of expected values - only works
175 + * with scalars.
176 + */
177 + scalarPropsEqual: function(propNameToExpectedValue) {
178 + expect(this.instance()).toBeTruthy();
179 + for (var propName in propNameToExpectedValue) {
180 + if (!propNameToExpectedValue.hasOwnProperty(propName)) {
181 + continue;
182 + }
183 + expect(this.instance().props[propName])
184 + .toEqual(propNameToExpectedValue[propName]);
185 + }
186 + return this;
187 + }
188 +});
189 +
190 +module.exports = reactComponentExpect;
src/utils/ImmutableObject.js new
+184
@@ -0,0 +1,184 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ImmutableObject
17 + */
18 +
19 +"use strict";
20 +
21 +var keyMirror = require('keyMirror');
22 +var merge = require('merge');
23 +var mergeInto = require('mergeInto');
24 +var mergeHelpers = require('mergeHelpers');
25 +var throwIf = require('throwIf');
26 +
27 +var checkMergeObjectArgs = mergeHelpers.checkMergeObjectArgs;
28 +var isTerminal = mergeHelpers.isTerminal;
29 +
30 +/**
31 + * Simple wrapper around javascript key/value objects that provide a dev time
32 + * guarantee of immutability (assuming all modules that may potentially mutate
33 + * include a "use strict" declaration). Retaining immutability requires CPU
34 + * cycles (in order to perform the freeze), but this computation can be avoided
35 + * in production. The fact that mutation attempts in __DEV__ will be caught,
36 + * allows us to reasonably assume that mutation on those objects won't even be
37 + * attempted in production. This means that an object being an instanceof
38 + * `ImmutableObject` implies that the object may never change.
39 + *
40 + * TODO: Require strict mode in source files that use ImmutableObject (lint
41 + * rule).
42 + *
43 + * @class ImmutableObject
44 + */
45 +
46 +var ERRORS;
47 +var ImmutableObject;
48 +
49 +if (__DEV__) {
50 + ERRORS = {
51 + INVALID_MAP_SET_ARG: 'You have attempted to set fields on an object that ' +
52 + 'is not an instance of ImmutableObject'
53 + };
54 +
55 + /**
56 + * Constructs an instance of `ImmutableObject`.
57 + *
58 + * @param {!Object} initMap The initial set of properties.
59 + * @constructor
60 + */
61 + ImmutableObject = function(initMap) {
62 + mergeInto(this, initMap);
63 + deepFreeze(this, initMap);
64 + };
65 +
66 + /**
67 + * Objects that are instances of `ImmutableObject` are assumed to be deep
68 + * frozen.
69 + * @param {!Object} o The object to deep freeze.
70 + * @return {!boolean} Whether or not deep freeze is needed.
71 + */
72 + var shouldRecurseFreeze = function(o) {
73 + return (typeof o) === 'object' &&
74 + !(o instanceof ImmutableObject) && o !== null;
75 + };
76 +
77 + /**
78 + * Freezes an object `o` deeply. Invokes `shouldRecurseFreeze` to determine if
79 + * further freezing is needed.
80 + *
81 + * @param {!Object} o The object to freeze.
82 + */
83 + var deepFreeze = function(o) {
84 + var prop;
85 + Object.freeze(o); // First freeze the object.
86 + for (prop in o) {
87 + var field = o[prop];
88 + if (o.hasOwnProperty(prop) && shouldRecurseFreeze(field)) {
89 + deepFreeze(field);
90 + }
91 + }
92 + };
93 +
94 + /**
95 + * Returns a new ImmutableObject that is that is the same as the parameter
96 + * `immutableObject` but with the differences specified in `put`.
97 + * @param {!ImmutableObject} ImmutableObject The ImmutableObject object to set
98 + * fields on.
99 + * @param {!Object} put Subset of fields to merge into the returned result.
100 + * @return {!ImmutableObject} The result of merging in `put` fields.
101 + */
102 + ImmutableObject.set = function(immutableObject, put) {
103 + throwIf(
104 + !(immutableObject instanceof ImmutableObject),
105 + ERRORS.INVALID_MAP_SET_ARG
106 + );
107 + var totalNewFields = merge(immutableObject, put);
108 + return new ImmutableObject(totalNewFields);
109 + };
110 +
111 +} else {
112 + ERRORS = keyMirror({INVALID_MAP_SET_ARG: null});
113 +
114 + ImmutableObject = function(initMap) {
115 + mergeInto(this, initMap);
116 + };
117 +
118 + ImmutableObject.set = function(immutableObject, put) {
119 + throwIf(
120 + !(immutableObject instanceof ImmutableObject),
121 + ERRORS.INVALID_MAP_SET_ARG
122 + );
123 + var newMap = new ImmutableObject(immutableObject);
124 + mergeInto(newMap, put);
125 + return newMap;
126 + };
127 +}
128 +
129 +/**
130 + * Sugar for `ImmutableObject.set(ImmutableObject, {fieldName: putField})`
131 + * @see ImmutableObject.set
132 + */
133 +ImmutableObject.setField = function(immutableObject, fieldName, putField) {
134 + var put = {};
135 + put[fieldName] = putField;
136 + return ImmutableObject.set(immutableObject, put);
137 +};
138 +
139 +/**
140 + * Returns a new ImmutableObject that is that is the same as the parameter
141 + * `immutableObject` but with the differences specified in `put` recursively
142 + * applied.
143 + */
144 +ImmutableObject.setDeep = function(immutableObject, put) {
145 + throwIf(
146 + !(immutableObject instanceof ImmutableObject),
147 + ERRORS.INVALID_MAP_SET_ARG
148 + );
149 + return _setDeep(immutableObject, put);
150 +};
151 +
152 +function _setDeep(object, put) {
153 + checkMergeObjectArgs(object, put);
154 + var totalNewFields = {};
155 +
156 + // To maintain the order of the keys, copy the base object's entries first.
157 + var keys = Object.keys(object);
158 + for (var ii = 0; ii < keys.length; ii++) {
159 + var key = keys[ii];
160 + if (!put.hasOwnProperty(key)) {
161 + totalNewFields[key] = object[key];
162 + } else if (isTerminal(object[key]) || isTerminal(put[key])) {
163 + totalNewFields[key] = put[key];
164 + } else {
165 + totalNewFields[key] = _setDeep(object[key], put[key]);
166 + }
167 + }
168 +
169 + // Apply any new keys that the base object didn't have.
170 + var newKeys = Object.keys(put);
171 + for (ii = 0; ii < newKeys.length; ii++) {
172 + var newKey = newKeys[ii];
173 + if (object.hasOwnProperty(newKey)) {
174 + continue;
175 + }
176 + totalNewFields[newKey] = put[newKey];
177 + }
178 +
179 + return (object instanceof ImmutableObject || put instanceof ImmutableObject) ?
180 + new ImmutableObject(totalNewFields) :
181 + totalNewFields;
182 +}
183 +
184 +module.exports = ImmutableObject;
src/utils/OrderedMap.js new
+514
@@ -0,0 +1,514 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule OrderedMap
17 + */
18 +
19 +"use strict";
20 +
21 +var mixInto = require('mixInto');
22 +var throwIf = require('throwIf');
23 +
24 +var PREFIX = 'key:';
25 +
26 +var ARRAY_MUST_CB = 'ARRAY_MUST_CB';
27 +var INVALID_KEY = 'INVALID_KEY';
28 +var DUPLICATE_KEY = 'DUPLICATE_KEY';
29 +var OPERATION_ARGS = 'OPERATION_ARGS';
30 +var RANGE_ARGS = 'RANGE_ARGS';
31 +var NO_EXIST = 'NO_EXIST';
32 +
33 +if (__DEV__) {
34 + ARRAY_MUST_CB =
35 + 'If providing an array to an OrderedMap constructor, you must provide ' +
36 + 'a callback that may determine the uniqueue key for each entry. ' +
37 + 'The key that you return from the callback should uniquely define that ' +
38 + 'entity. What is returned from that function must answer the question: ' +
39 + '"If you were to shuffle the array of entities, how would you be able to ' +
40 + 'determine the identity of the Array entry?"';
41 + INVALID_KEY =
42 + 'Key must be non-empty, non-null, string or number';
43 + DUPLICATE_KEY =
44 + 'At creation time, you must extract out IDs that are unique.';
45 + OPERATION_ARGS =
46 + 'Invalid argument type for construction or operation on OrderedMap. ' +
47 + 'from/merge accepts another OrderedMap. fromArray accepts an Array ' +
48 + 'and a callback to extract a unique ID.';
49 + RANGE_ARGS =
50 + 'OrderedMap.mapRange requires end of range to be >= start, start to be ' +
51 + '>= 0 and end < length.';
52 + NO_EXIST =
53 + 'The requested key does not exist in the OrderedMap';
54 +}
55 +
56 +/**
57 + * Utility to extract a backing object from an initialization `Array`, allowing
58 + * the caller to assist in resolving the unique ID for each entry via the
59 + * `keyExtractor` callback. The `keyExtractor` must extract non-empty strings or
60 + * numbers.
61 + * @param {Array<Object!>} arr Array of items.
62 + * @param {function} keyExtractor Extracts a unique key from each item.
63 + * @return {Object} Map from unique key to originating value that the key was
64 + * extracted from.
65 + * @throws Exception if the initialization array has duplicate extracted keys.
66 + */
67 +function extractObjectFromArray(arr, keyExtractor) {
68 + var normalizedObj = {};
69 + for (var i=0; i < arr.length; i++) {
70 + var item = arr[i];
71 + var key = keyExtractor(item);
72 + validatePublicKey(key);
73 + var normalizedKey = PREFIX + key;
74 + throwIf(normalizedKey in normalizedObj, DUPLICATE_KEY);
75 + normalizedObj[normalizedKey] = item;
76 + }
77 + return normalizedObj;
78 +}
79 +
80 +/**
81 + * Utility class for mappings with ordering. This class is to be used in an
82 + * immutable manner. A `OrderedMap` is very much like the native JavaScript
83 + * object, where keys map to values via the `get()` function. Also, like the
84 + * native JavaScript object, there is an ordering associated with the mapping.
85 + * This class is helpful because it eliminates many of the pitfalls that come
86 + * with the native JavaScript ordered mappings. Specifically, there are
87 + * inconsistencies with numeric keys in some JavaScript implementations
88 + * (enumeration ordering). This class protects against those pitfalls and
89 + * provides functional utilities for dealing with these `OrderedMap`s.
90 + *
91 + * - TODO:
92 + * - orderedMergeExclusive: Merges mutually exclusive `OrderedMap`s.
93 + * - mapReverse().
94 + *
95 + * @class {OrderedMap}
96 + * @constructor {OrderedMap}
97 + * @param {Object} normalizedObj Object that is known to be a defensive copy of
98 + * caller supplied data. We require a defensive copy to guard against callers
99 + * mutating. It is also assumed that the keys of `normalizedObj` have been
100 + * normalized and do not contain any numeric-appearing strings.
101 + * @param {number} computedLength The precomputed length of `_normalizedObj`
102 + * keys.
103 + * @private
104 + */
105 +function OrderedMapImpl(normalizedObj, computedLength) {
106 + this._normalizedObj = normalizedObj;
107 + this._computedPositions = null;
108 + this.length = computedLength;
109 +}
110 +
111 +/**
112 + * Validates a "public" key - that is, one that the public facing API supplies.
113 + * The key is then normalized for internal storage. In order to be considered
114 + * valid, all keys must be non-empty, defined, non-null strings or numbers.
115 + * Since this already costs a function invocation, will avoid additional call to
116 + * `throwIf`.
117 + *
118 + * @param {string?} key Validates that the key is suitable for use in a
119 + * `OrderedMap`.
120 + * @throws Error if key is not appropriate for use in `OrderedMap`.
121 + */
122 +function validatePublicKey(key) {
123 + var isEmpty = key === '';
124 + var correctKeyType = typeof key === 'string' || typeof key === 'number';
125 + if (isEmpty || !correctKeyType) {
126 + throw new Error(INVALID_KEY);
127 + }
128 +}
129 +
130 +/**
131 + * Validates that arguments to range operations are within the correct limits.
132 + *
133 + * @param {number} start Start of range.
134 + * @param {number} length Length of range.
135 + * @param {number} actualLen Actual length of range that should not be
136 + * exceeded.
137 + * @return {void} description
138 + */
139 +function validateRangeIndices(start, length, actualLen) {
140 + var startType = typeof start;
141 + var lengthType = typeof length;
142 + var invalid =
143 + startType !== 'number' || lengthType !== 'number' ||
144 + length < 0 || start < 0 || start + length > actualLen;
145 + if (invalid) {
146 + throw new Error(RANGE_ARGS);
147 + }
148 +}
149 +
150 +/**
151 + * Merges two "normalized" objects (objects who's key have been normalized) into
152 + * a `OrderedMap`.
153 + *
154 + * @param {Object} a Object of key value pairs.
155 + * @param {Object} b Object of key value pairs.
156 + * @return {OrderedMap} new `OrderedMap` that results in merging `a` and `b`.
157 + */
158 +function _fromNormalizedObjects(a, b) {
159 + // Second optional, both must be a Plain Old JavaScript Object.
160 + var invalidArgs =
161 + !a || a.constructor !== Object || (b && b.constructor !== Object);
162 + throwIf(invalidArgs, OPERATION_ARGS);
163 + var newSet = {};
164 + var length = 0;
165 + var key;
166 + for (key in a) {
167 + if (a.hasOwnProperty(key)) {
168 + newSet[key] = a[key];
169 + length++;
170 + }
171 + }
172 +
173 + for (key in b) {
174 + if (b.hasOwnProperty(key)) {
175 + // Increment length if not already added via first object (a)
176 + if (!(key in newSet)) {
177 + length++;
178 + }
179 + newSet[key] = b[key];
180 + }
181 + }
182 + return new OrderedMapImpl(newSet, length);
183 +}
184 +
185 +/**
186 + * Methods for `OrderedMap` instances.
187 + *
188 + * @lends OrderedMap.prototype
189 + * TODO: Make this data structure lazy, unify with LazyArray.
190 + * TODO: Unify this with ImmutableObject - it is to be used immutably.
191 + * TODO: If so, consider providing `fromObject` API.
192 + * TODO: Create faster implementation of merging/mapping from original Array,
193 + * without having to first create an object - simply for the sake of merging.
194 + */
195 +var OrderedMapMethods = {
196 +
197 + /**
198 + * Returns whether or not a given key is present in the map.
199 + *
200 + * @param {string} key Valid string key to lookup membership for.
201 + * @return {boolean} Whether or not `key` is a member of the map.
202 + * @throws Error if provided known invalid key.
203 + */
204 + has: function(key) {
205 + validatePublicKey(key);
206 + var normalizedKey = PREFIX + key;
207 + return normalizedKey in this._normalizedObj;
208 + },
209 +
210 + /**
211 + * Returns the object for a given key, or `undefined` if not present. To
212 + * distinguish a undefined entry vs not being in the set, use `has()`.
213 + *
214 + * @param {string} key String key to lookup the value for.
215 + * @return {Object?} Object at key `key`, or undefined if not in map.
216 + * @throws Error if provided known invalid key.
217 + */
218 + get: function(key) {
219 + validatePublicKey(key);
220 + var normalizedKey = PREFIX + key;
221 + return this.has(key) ? this._normalizedObj[normalizedKey] : undefined;
222 + },
223 +
224 + /**
225 + * Merges, appending new keys to the end of the ordering. Keys in `orderedMap`
226 + * that are redundant with `this`, maintain the same ordering index that they
227 + * had in `this`. This is how standard JavaScript object merging would work.
228 + * If you wish to prepend a `OrderedMap` to the beginning of another
229 + * `OrderedMap` then simply reverse the order of operation. This is the analog
230 + * to `merge(x, y)`.
231 + *
232 + * @param {OrderedMap} orderedMap OrderedMap to merge onto the end.
233 + * @return {OrderedMap} New OrderedMap that represents the result of the
234 + * merge.
235 + */
236 + merge: function(orderedMap) {
237 + throwIf(!(orderedMap instanceof OrderedMapImpl), OPERATION_ARGS);
238 + return _fromNormalizedObjects(
239 + this._normalizedObj,
240 + orderedMap._normalizedObj
241 + );
242 + },
243 +
244 + /**
245 + * Functional map API. Returns a new `OrderedMap`.
246 + *
247 + * @param {Function} cb Callback to invoke for each item.
248 + * @param {Object?=} context Context to invoke callback from.
249 + * @returns {OrderedMap} OrderedMap that results from mapping.
250 + */
251 + map: function(cb, context) {
252 + return this.mapRange(cb, 0, this.length, context);
253 + },
254 +
255 + /**
256 + * The callback `cb` is invoked with the arguments (item, key,
257 + * indexInOriginal).
258 + *
259 + * @param {Function} cb Determines result for each item.
260 + * @param {number} start Start index of map range.
261 + * @param {end} end End index of map range.
262 + * @param {*!?} context Context of callback invocation.
263 + * @return {OrderedMap} OrderedMap resulting from mapping the range.
264 + */
265 + mapRange: function(cb, start, length, context) {
266 + var thisSet = this._normalizedObj;
267 + var newSet = {};
268 + var i = 0;
269 + validateRangeIndices(start, length, this.length);
270 + var end = start + length - 1;
271 + for (var key in thisSet) {
272 + if (thisSet.hasOwnProperty(key)) {
273 + if (i >= start) {
274 + if (i > end) {
275 + break;
276 + }
277 + var item = thisSet[key];
278 + newSet[key] = cb.call(context, item, key.substr(PREFIX.length), i);
279 + }
280 + i++;
281 + }
282 + }
283 + return new OrderedMapImpl(newSet, length);
284 + },
285 +
286 + /**
287 + * Function filter API. Returns new `OrderedMap`.
288 + *
289 + * @param {Function} cb Callback to invoke for each item.
290 + * @param {Object?=} context Context to invoke callback from.
291 + * @returns {OrderedMap} OrderedMap that results from filtering.
292 + */
293 + filter: function(cb, context) {
294 + return this.filterRange(cb, 0, this.length, context);
295 + },
296 +
297 + /**
298 + * The callback `cb` is invoked with the arguments (item, key,
299 + * indexInOriginal).
300 + *
301 + * @param {Function} cb Returns true if item should be in result.
302 + * @param {number} start Start index of filter range.
303 + * @param {number} end End index of map range.
304 + * @param {*!?} context Context of callback invocation.
305 + * @return {OrderedMap} OrderedMap resulting from filtering the range.
306 + */
307 + filterRange: function(cb, start, length, context) {
308 + var newSet = {};
309 + var newSetLength = 0;
310 + this.forEachRange(function(item, key, originalIndex) {
311 + if (cb.call(context, item, key, originalIndex)) {
312 + var normalizedKey = PREFIX + key;
313 + newSet[normalizedKey] = item;
314 + newSetLength++;
315 + }
316 + }, start, length);
317 + return new OrderedMapImpl(newSet, newSetLength);
318 + },
319 +
320 + forEach: function(cb, context) {
321 + this.forEachRange(cb, 0, this.length, context);
322 + },
323 +
324 + forEachRange: function(cb, start, length, context) {
325 + validateRangeIndices(start, length, this.length);
326 + var thisSet = this._normalizedObj;
327 + var i = 0;
328 + var end = start + length - 1;
329 + for (var key in thisSet) {
330 + if (thisSet.hasOwnProperty(key)) {
331 + if (i >= start) {
332 + if (i > end) {
333 + break;
334 + }
335 + var item = thisSet[key];
336 + cb.call(context, item, key.substr(PREFIX.length), i);
337 + }
338 + i++;
339 + }
340 + }
341 + },
342 +
343 + /**
344 + * Even though `mapRange`/`forEachKeyRange` allow zero length mappings, we'll
345 + * impose an additional restriction here that the length of mapping be greater
346 + * than zero - the only reason is that there are many ways to express length
347 + * zero in terms of two keys and that is confusing.
348 + */
349 + mapKeyRange: function(cb, startKey, endKey, context) {
350 + var startIndex = this.indexOfKey(startKey);
351 + var endIndex = this.indexOfKey(endKey);
352 + if (endIndex < startIndex) {
353 + throw new Error(RANGE_ARGS);
354 + }
355 + return this.mapRange(cb, startIndex, (endIndex - startIndex) + 1, context);
356 + },
357 +
358 + forEachKeyRange: function(cb, startKey, endKey, context) {
359 + var startIndex = this.indexOfKey(startKey);
360 + var endIndex = this.indexOfKey(endKey);
361 + if (endIndex < startIndex) {
362 + throw new Error(RANGE_ARGS);
363 + }
364 + this.forEachRange(cb, startIndex, (endIndex - startIndex) + 1, context);
365 + },
366 +
367 + /**
368 + * @param {number} pos Index to search for key at.
369 + * @return {string|undefined} Either the key at index `pos` or undefined if
370 + * not in map.
371 + */
372 + keyAtIndex: function(pos) {
373 + var computedPositions = this._getOrComputePositions();
374 + var keyAtPos = computedPositions.keyByIndex[pos];
375 + return keyAtPos ? keyAtPos.substr(PREFIX.length) : undefined;
376 + },
377 +
378 + /**
379 + * @param {string} key String key from which to find the next key.
380 + * @return {string|undefined} Either the next key, or undefined if there is no
381 + * next key.
382 + * @throws Error if `key` is not in this `OrderedMap`.
383 + */
384 + keyAfter: function(key) {
385 + return this.nthKeyAfter(key, 1);
386 + },
387 +
388 + /**
389 + * @param {string} key String key from which to find the preceding key.
390 + * @return {string|undefined} Either the preceding key, or undefined if there
391 + * is no preceding.key.
392 + * @throws Error if `key` is not in this `OrderedMap`.
393 + */
394 + keyBefore: function(key) {
395 + return this.nthKeyBefore(key, 1);
396 + },
397 +
398 + /**
399 + * @param {string} key String key from which to find a following key.
400 + * @param {number} n Distance to scan forward after `key`.
401 + * @return {string|undefined} Either the nth key after `key`, or undefined if
402 + * there is no next key.
403 + * @throws Error if `key` is not in this `OrderedMap`.
404 + */
405 + nthKeyAfter: function(key, n) {
406 + var curIndex = this.indexOfKey(key);
407 + throwIf(curIndex === undefined, NO_EXIST);
408 + return this.keyAtIndex(curIndex + n);
409 + },
410 +
411 + /**
412 + * @param {string} key String key from which to find a preceding key.
413 + * @param {number} n Distance to scan backwards before `key`.
414 + * @return {string|undefined} Either the nth key before `key`, or undefined if
415 + * there is no previous key.
416 + * @throws Error if `key` is not in this `OrderedMap`.
417 + */
418 + nthKeyBefore: function(key, n) {
419 + return this.nthKeyAfter(key, -n);
420 + },
421 +
422 + /**
423 + * @param {string} key Key to find the index of.
424 + * @return {number|undefined} Index of the provided key, or `undefined` if the
425 + * key is not found.
426 + */
427 + indexOfKey: function(key) {
428 + validatePublicKey(key);
429 + var normalizedKey = PREFIX + key;
430 + var computedPositions = this._getOrComputePositions();
431 + var computedPosition = computedPositions.indexByKey[normalizedKey];
432 + // Just writing it this way to make it clear this is intentional.
433 + return computedPosition === undefined ? undefined : computedPosition;
434 + },
435 +
436 + /**
437 + * @return {Array} An ordered array of this object's values.
438 + */
439 + toArray: function() {
440 + var result = [];
441 + var thisSet = this._normalizedObj;
442 + for (var key in thisSet) {
443 + if (thisSet.hasOwnProperty(key)) {
444 + result.push(thisSet[key]);
445 + }
446 + }
447 + return result;
448 + },
449 +
450 + /**
451 + * Finds the key at a given position, or indicates via `undefined` that that
452 + * position does not exist in the `OrderedMap`. It is appropriate to return
453 + * undefined, indicating that the key doesn't exist in the `OrderedMap`
454 + * because `undefined` is not ever a valid `OrderedMap` key.
455 + *
456 + * @private
457 + * @param {number} pos Position for which we're querying the name.
458 + * @return {string?} Name of the item at position `pos`, or `undefined` if
459 + * there is no item at that position.
460 + */
461 + _getOrComputePositions: function() {
462 + // TODO: Entertain computing this at construction time in some less
463 + // performance critical paths.
464 + var computedPositions = this._computedPositions;
465 + if (!computedPositions) {
466 + this._computePositions();
467 + }
468 + return this._computedPositions;
469 + },
470 +
471 + /**
472 + * Precomputes the index/key mapping for future lookup. Since `OrderedMap`s
473 + * are immutable, there is only ever a need to perform this once.
474 + * @private
475 + */
476 + _computePositions: function() {
477 + this._computedPositions = {
478 + keyByIndex: {},
479 + indexByKey: {}
480 + };
481 + var keyByIndex = this._computedPositions.keyByIndex;
482 + var indexByKey = this._computedPositions.indexByKey;
483 + var index = 0;
484 + var thisSet = this._normalizedObj;
485 + for (var key in thisSet) {
486 + if (thisSet.hasOwnProperty(key)) {
487 + keyByIndex[index] = key;
488 + indexByKey[key] = index;
489 + index++;
490 + }
491 + }
492 + }
493 +};
494 +
495 +mixInto(OrderedMapImpl, OrderedMapMethods);
496 +
497 +var OrderedMap = {
498 + from: function(orderedMap) {
499 + var invalidArg = !(orderedMap instanceof OrderedMapImpl);
500 + throwIf(invalidArg, OPERATION_ARGS);
501 + return _fromNormalizedObjects(orderedMap._normalizedObj, null);
502 + },
503 +
504 + fromArray: function(arr, keyExtractor) {
505 + throwIf(!Array.isArray(arr), OPERATION_ARGS);
506 + throwIf(!keyExtractor, ARRAY_MUST_CB);
507 + return new OrderedMapImpl(
508 + extractObjectFromArray(arr, keyExtractor),
509 + arr.length
510 + );
511 + }
512 +};
513 +
514 +module.exports = OrderedMap;
src/utils/PooledClass.js new
+101
@@ -0,0 +1,101 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule PooledClass
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * Static poolers. Several custom versions for each potential number of
23 + * arguments. A completely generic pooler is easy to implement, but would
24 + * require accessing the `arguments` object. In each of these, `this` refers to
25 + * the Class itself, not an instance. If any others are needed, simply add them
26 + * here, or in their own files.
27 + */
28 +var oneArgumentPooler = function(copyFieldsFrom) {
29 + var Klass = this;
30 + if (Klass.instancePool.length) {
31 + var instance = Klass.instancePool.pop();
32 + Klass.call(instance, copyFieldsFrom);
33 + return instance;
34 + } else {
35 + return new Klass(copyFieldsFrom);
36 + }
37 +};
38 +
39 +var twoArgumentPooler = function(a1, a2) {
40 + var Klass = this;
41 + if (Klass.instancePool.length) {
42 + var instance = Klass.instancePool.pop();
43 + Klass.call(instance, a1, a2);
44 + return instance;
45 + } else {
46 + return new Klass(a1, a2);
47 + }
48 +};
49 +
50 +var fiveArgumentPooler = function(a1, a2, a3, a4, a5) {
51 + var Klass = this;
52 + if (Klass.instancePool.length) {
53 + var instance = Klass.instancePool.pop();
54 + Klass.call(instance, a1, a2, a3, a4, a5);
55 + return instance;
56 + } else {
57 + return new Klass(a1, a2, a3, a4, a5);
58 + }
59 +};
60 +
61 +var standardReleaser = function(instance) {
62 + var Klass = this;
63 + if (instance.destructor) {
64 + instance.destructor();
65 + }
66 + if (Klass.instancePool.length < Klass.poolSize) {
67 + Klass.instancePool.push(instance);
68 + }
69 +};
70 +
71 +var DEFAULT_POOL_SIZE = 10;
72 +var DEFAULT_POOLER = oneArgumentPooler;
73 +
74 +/**
75 + * Augments `CopyConstructor` to be a poolable class, augmenting only the class
76 + * itself (statically) not adding any prototypical fields. Any CopyConstructor
77 + * you give this may have a `poolSize` property, and will look for a
78 + * prototypical `destructor` on instances (optional).
79 + *
80 + * @param {Function} CopyConstructor Constructor that can be used to reset.
81 + * @param {Function} pooler Customizable pooler.
82 + */
83 +var addPoolingTo = function(CopyConstructor, pooler) {
84 + var NewKlass = CopyConstructor;
85 + NewKlass.instancePool = [];
86 + NewKlass.getPooled = pooler || DEFAULT_POOLER;
87 + if (!NewKlass.poolSize) {
88 + NewKlass.poolSize = DEFAULT_POOL_SIZE;
89 + }
90 + NewKlass.release = standardReleaser;
91 + return NewKlass;
92 +};
93 +
94 +var PooledClass = {
95 + addPoolingTo: addPoolingTo,
96 + oneArgumentPooler: oneArgumentPooler,
97 + twoArgumentPooler: twoArgumentPooler,
98 + fiveArgumentPooler: fiveArgumentPooler
99 +};
100 +
101 +module.exports = PooledClass;
src/utils/Transaction.js new
+252
@@ -0,0 +1,252 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule Transaction
17 + */
18 +
19 +"use strict";
20 +
21 +var throwIf = require('throwIf');
22 +
23 +var DUAL_TRANSACTION = 'DUAL_TRANSACTION';
24 +var MISSING_TRANSACTION = 'MISSING_TRANSACTION';
25 +if (__DEV__) {
26 + DUAL_TRANSACTION =
27 + 'Cannot initialize transaction when there is already an outstanding ' +
28 + 'transaction. Common causes of this are trying to render a component ' +
29 + 'when you are already rendering a component or attempting a state ' +
30 + 'transition while in a render function. Another possibility is that ' +
31 + 'you are rendering new content (or state transitioning) in a ' +
32 + 'componentDidRender callback. If this is not the case, please report the ' +
33 + 'issue immediately.';
34 +
35 + MISSING_TRANSACTION =
36 + 'Cannot close transaction when there is none open.';
37 +}
38 +
39 +/**
40 + * `Transaction` creates a black box that is able to wrap any method such that
41 + * certain invariants are maintained before and after the method is invoked
42 + * (Even if an exception is thrown while invoking the wrapped method). Whoever
43 + * instantiates a transaction can provide enforcers of the invariants at
44 + * creation time. The `Transaction` class itself will supply one additional
45 + * automatic invariant for you - the invariant that any transaction instance
46 + * should not be ran while it is already being ran. You would typically create a
47 + * single instance of a `Transaction` for reuse multiple times, that potentially
48 + * is used to wrap several different methods. Wrappers are extremely simple -
49 + * they only require implementing two methods.
50 + *
51 + * <pre>
52 + * wrappers (injected at creation time)
53 + * + +
54 + * | |
55 + * +-----------------|--------|--------------+
56 + * | v | |
57 + * | +---------------+ | |
58 + * | +--| wrapper1 |---|----+ |
59 + * | | +---------------+ v | |
60 + * | | +-------------+ | |
61 + * | | +----| wrapper2 |--------+ |
62 + * | | | +-------------+ | | |
63 + * | | | | | |
64 + * | v v v v | wrapper
65 + * | +---+ +---+ +---------+ +---+ +---+ | invariants
66 + * perform(anyMethod) | | | | | | | | | | | | maintained
67 + * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
68 + * | | | | | | | | | | | |
69 + * | | | | | | | | | | | |
70 + * | | | | | | | | | | | |
71 + * | +---+ +---+ +---------+ +---+ +---+ |
72 + * | initialize close |
73 + * +-----------------------------------------+
74 + * </pre>
75 + *
76 + * Bonus:
77 + * - Reports timing metrics by method name and wrapper index.
78 + *
79 + * Use cases:
80 + * - Preserving the input selection ranges before/after reconciliation.
81 + * Restoring selection even in the event of an unexpected error.
82 + * - Deactivating events while rearranging the DOM, preventing blurs/focuses,
83 + * while guaranteeing that afterwards, the event system is reactivated.
84 + * - Flushing a queue of collected DOM mutations to the main UI thread after a
85 + * reconciliation takes place in a worker thread.
86 + * - Invoking any collected `componentDidRender` callbacks after rendering new
87 + * content.
88 + * - (Future use case): Wrapping particular flushes of the `ReactWorker` queue
89 + * to preserve the `scrollTop` (an automatic scroll aware DOM).
90 + * - (Future use case): Layout calculations before and after DOM upates.
91 + *
92 + * Transactional plugin API:
93 + * - A module that has an `initialize` method that returns any precomputation.
94 + * - and a `close` method that accepts the precomputation. `close` is invoked
95 + * when the wrapped process is completed, or has failed.
96 + *
97 + * @param {Array<TransactionalWrapper>} transactionWrapper Wrapper modules
98 + * that implement `initialize` and `close`.
99 + * @return {Transaction} Single transaction for reuse in thread.
100 + *
101 + * @class Transaction
102 + */
103 +var Mixin = {
104 + /**
105 + * Sets up this instance so that it is prepared for collecting metrics. Does
106 + * so such that this setup method may be used on an instance that is already
107 + * initialized, in a way that does not consume additional memory upon reuse.
108 + * That can be useful if you decide to make your subclass of this mixin a
109 + * "PooledClass".
110 + */
111 + reinitializeTransaction: function() {
112 + this.transactionWrappers = this.getTransactionWrappers();
113 + if (!this.wrapperInitData) {
114 + this.wrapperInitData = [];
115 + } else {
116 + this.wrapperInitData.length = 0;
117 + }
118 + if (!this.timingMetrics) {
119 + this.timingMetrics = {};
120 + }
121 + this.timingMetrics.methodInvocationTime = 0;
122 + if (!this.timingMetrics.wrapperInitTimes) {
123 + this.timingMetrics.wrapperInitTimes = [];
124 + } else {
125 + this.timingMetrics.wrapperInitTimes.length = 0;
126 + }
127 + if (!this.timingMetrics.wrapperCloseTimes) {
128 + this.timingMetrics.wrapperCloseTimes = [];
129 + } else {
130 + this.timingMetrics.wrapperCloseTimes.length = 0;
131 + }
132 + this._isInTransaction = false;
133 + },
134 +
135 + _isInTransaction: false,
136 +
137 + /**
138 + * @abstract
139 + * @return {Array<TransactionWrapper>} Array of transaction wrappers.
140 + */
141 + getTransactionWrappers: null,
142 +
143 + isInTransaction: function() {
144 + return !!this._isInTransaction;
145 + },
146 +
147 + /**
148 + * Executes the function within a safety window. Use this for the top level
149 + * methods that result in large amounts of computation/mutations that would
150 + * need to be safety checked.
151 + *
152 + * @param {function} method Member of scope to call.
153 + * @param {Object} scope Scope to invoke from.
154 + * @param {Object?=} args... Arguments to pass to the method (optional).
155 + * Helps prevent need to bind in many cases.
156 + * @returns Return value from `method`.
157 + */
158 + perform: function(method, scope, a, b, c, d, e, f) {
159 + throwIf(this.isInTransaction(), DUAL_TRANSACTION);
160 + var memberStart = Date.now();
161 + var err = null;
162 + var ret;
163 + try {
164 + this.initializeAll();
165 + ret = method.call(scope, a, b, c, d, e, f);
166 + } catch (ie_requires_catch) {
167 + err = ie_requires_catch;
168 + } finally {
169 + var memberEnd = Date.now();
170 + this.methodInvocationTime += (memberEnd - memberStart);
171 + try {
172 + this.closeAll();
173 + } catch (closeAllErr) {
174 + err = err || closeAllErr;
175 + }
176 + }
177 + if (err) {
178 + throw err;
179 + }
180 + return ret;
181 + },
182 +
183 + initializeAll: function() {
184 + this._isInTransaction = true;
185 + var transactionWrappers = this.transactionWrappers;
186 + var wrapperInitTimes = this.timingMetrics.wrapperInitTimes;
187 + var err = null;
188 + for (var i = 0; i < transactionWrappers.length; i++) {
189 + var initStart = Date.now();
190 + var wrapper = transactionWrappers[i];
191 + try {
192 + this.wrapperInitData[i] =
193 + wrapper.initialize ? wrapper.initialize.call(this) : null;
194 + } catch (initErr) {
195 + err = err || initErr; // Remember the first error.
196 + this.wrapperInitData[i] = Transaction.OBSERVED_ERROR;
197 + } finally {
198 + var curInitTime = wrapperInitTimes[i];
199 + var initEnd = Date.now();
200 + wrapperInitTimes[i] = (curInitTime || 0) + (initEnd - initStart);
201 + }
202 + }
203 + if (err) {
204 + throw err;
205 + }
206 + },
207 +
208 + /**
209 + * Invokes each of `this.transactionWrappers.close[i]` functions, passing into
210 + * them the respective return values of `this.transactionWrappers.init[i]`
211 + * (`close`rs that correspond to initializers that failed will not be
212 + * invoked).
213 + */
214 + closeAll: function() {
215 + throwIf(!this.isInTransaction(), MISSING_TRANSACTION);
216 + var transactionWrappers = this.transactionWrappers;
217 + var wrapperCloseTimes = this.timingMetrics.wrapperCloseTimes;
218 + var err = null;
219 + for (var i = 0; i < transactionWrappers.length; i++) {
220 + var wrapper = transactionWrappers[i];
221 + var closeStart = Date.now();
222 + var initData = this.wrapperInitData[i];
223 + try {
224 + if (initData !== Transaction.OBSERVED_ERROR) {
225 + wrapper.close && wrapper.close.call(this, initData);
226 + }
227 + } catch (closeErr) {
228 + err = err || closeErr; // Remember the first error.
229 + } finally {
230 + var closeEnd = Date.now();
231 + var curCloseTime = wrapperCloseTimes[i];
232 + wrapperCloseTimes[i] = (curCloseTime || 0) + (closeEnd - closeStart);
233 + }
234 + }
235 + this.wrapperInitData.length = 0;
236 + this._isInTransaction = false;
237 + if (err) {
238 + throw err;
239 + }
240 + }
241 +};
242 +
243 +var Transaction = {
244 + Mixin: Mixin,
245 + /**
246 + * Token to look for to determine if an error occured.
247 + */
248 + OBSERVED_ERROR: {}
249 +
250 +};
251 +
252 +module.exports = Transaction;
src/utils/__tests__/ImmutableObject-test.js new
+289
@@ -0,0 +1,289 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +require('mock-modules')
22 + .dontMock('ImmutableObject');
23 +
24 +var ImmutableObject;
25 +
26 +var expectSeriallyEqual = function(one, two) {
27 +};
28 +
29 +/**
30 + * To perform performance testing of using `ImmutableObject` vs. not using
31 + * `ImmutableObject`, such testing must be done with __DEV__ set to false.
32 + */
33 +describe('ImmutableObject', function() {
34 + var message;
35 + beforeEach(function() {
36 + require('mock-modules').dumpCache();
37 + ImmutableObject = require('ImmutableObject');
38 + this.addMatchers({
39 + /**
40 + * Equivalent with respect to serialization. Must stringify because
41 + * constructors are different and other comparison methods will not
42 + * consider them structurally equal. Probably not useful for use outside
43 + * of this test module.
44 + */
45 + toBeSeriallyEqualTo: function(expected) {
46 + var actual = this.actual;
47 + var notText = this.isNot ? " not" : "";
48 + this.message = function () {
49 + return "Expected " + JSON.stringify(actual) + notText +
50 + " to be serially equal to " + JSON.stringify(expected);
51 + };
52 +
53 + return JSON.stringify(actual) === JSON.stringify(expected);
54 + }
55 + });
56 + });
57 +
58 + /**
59 + * We are in __DEV__ by default.
60 + */
61 + var testDev = function(message, testFunc) {
62 + it(message, testFunc);
63 + };
64 +
65 + var testProd = function(message, testFunc) {
66 + // Temporarily enter production mode
67 + window.__DEV__ = false;
68 + it(message, testFunc);
69 + window.__DEV__ = true;
70 + };
71 +
72 + var testDevAndProd = function(message, testFunc) {
73 + testDev(message + ':DEV', testFunc);
74 + testProd(message + ':PROD', testFunc);
75 + };
76 +
77 + testDev('should be running in DEV', function() {
78 + expect(window.__DEV__).toBe(true);
79 + });
80 +
81 + testDev('should require initial map to be an object', function() {
82 + expect(function() {
83 + new ImmutableObject([1,2,3]);
84 + }).toThrow();
85 +
86 + expect(function() {
87 + new ImmutableObject('asdf');
88 + }).toThrow();
89 +
90 + expect(function() {
91 + new ImmutableObject({oldField: 'asdf', fieldTwo: null});
92 + }).not.toThrow();
93 + });
94 +
95 + testDevAndProd('should not throw when not mutating directly', function() {
96 + var io = new ImmutableObject({oldField: 'asdf'});
97 + expect(function() {
98 + ImmutableObject.set(io, {newField: null}); // not a mutation!
99 + }).not.toThrow();
100 + });
101 +
102 + testDev('should prevent shallow field addition when strict', function() {
103 + expect(function() {
104 + var io = new ImmutableObject({oldField: 'asdf'});
105 + io.newField = 'this will not work';
106 + }).toThrow();
107 + });
108 +
109 + testDev('should prevent shallow field mutation when strict', function() {
110 + expect(function() {
111 + var io = new ImmutableObject({oldField: 'asdf'});
112 + io.oldField = 'this will not work!';
113 + }).toThrow();
114 + });
115 +
116 + testDev('should prevent deep field addition when strict', function() {
117 + expect(function() {
118 + var io =
119 + new ImmutableObject({shallowField: {deepField: {oldField: null}}});
120 + io.shallowField.deepField.oldField = 'this will not work!';
121 + }).toThrow();
122 + });
123 +
124 + testDev('should prevent deep field mutation when strict', function() {
125 + expect(function() {
126 + var io =
127 + new ImmutableObject({shallowField: {deepField: {oldField: null}}});
128 + io.shallowField.deepField.newField = 'this will not work!';
129 + }).toThrow();
130 + });
131 +
132 + testDevAndProd(
133 + 'should create object with same structure when set with {}',
134 + function() {
135 + var beforeIO =
136 + new ImmutableObject({shallowField: {deepField: {oldField: null}}});
137 + var afterIO = ImmutableObject.set(beforeIO, {});
138 + expect(afterIO).toBeSeriallyEqualTo(beforeIO);
139 + expect(afterIO).not.toBe(beforeIO);
140 + }
141 + );
142 +
143 + testDevAndProd(
144 + 'should create distinct object with shallow field insertion',
145 + function() {
146 + var beforeStructure = {
147 + oldShallowField: {
148 + deepField: {
149 + oldField: null
150 + }
151 + }
152 + };
153 +
154 + var delta = {
155 + newShallowField: 'newShallowFieldHere'
156 + };
157 +
158 + var expectedAfterStructure = {
159 + oldShallowField: {
160 + deepField: {
161 + oldField: null
162 + }
163 + },
164 + newShallowField: 'newShallowFieldHere'
165 + };
166 +
167 + var beforeIO = new ImmutableObject(beforeStructure);
168 + var afterIO = ImmutableObject.set(beforeIO, delta);
169 + expect(afterIO).toBeSeriallyEqualTo(expectedAfterStructure);
170 + expect(afterIO).not.toBe(beforeIO);
171 + }
172 + );
173 +
174 + testDevAndProd(
175 + 'should create distinct object with shallow field mutation',
176 + function() {
177 + var beforeStructure = {
178 + oldShallowField: {
179 + deepField: {
180 + oldField: null
181 + }
182 + }
183 + };
184 +
185 + var delta = {
186 + oldShallowField: 'this will clobber the old field'
187 + };
188 +
189 + var expectedAfterStructure = {
190 + oldShallowField: 'this will clobber the old field'
191 + };
192 +
193 + var beforeIO = new ImmutableObject(beforeStructure);
194 + var afterIO = ImmutableObject.set(beforeIO, delta);
195 + expect(afterIO).toBeSeriallyEqualTo(expectedAfterStructure);
196 + expect(afterIO).not.toBe(beforeIO);
197 + }
198 + );
199 +
200 + message = 'should create distinct object with deep field insertion';
201 + testDevAndProd(message, function() {
202 + var beforeStructure = {
203 + oldShallowField: {
204 + deepField: {
205 + oldField: null
206 + }
207 + }
208 + };
209 +
210 + var delta = {
211 + oldShallowField: {newDeepField: 'hello'}
212 + };
213 +
214 + // ImmutableObject does not yet support deep merging with
215 + // ImmutableObject.set().
216 + var expectedAfterStructure = {
217 + oldShallowField: {newDeepField: 'hello'}
218 + };
219 +
220 + var beforeIO = new ImmutableObject(beforeStructure);
221 + var afterIO = ImmutableObject.set(beforeIO, delta);
222 + expect(afterIO).toBeSeriallyEqualTo(expectedAfterStructure);
223 + expect(afterIO).not.toBe(beforeIO);
224 + });
225 +
226 + message =
227 + 'should tolerate arrays at deeper levels and prevent mutation on them';
228 + testDevAndProd(message, function() {
229 + if (window.callPhantom) {
230 + // PhantomJS has a bug with Object.freeze and Arrays.
231 + // https://github.com/ariya/phantomjs/issues/10817
232 + return;
233 + }
234 + var beforeStructure = {
235 + shallowField: [1,'second field',3]
236 + };
237 + var io = new ImmutableObject(beforeStructure);
238 + expect(function() {
239 + io.newField = 'nope!';
240 + }).toThrow();
241 + expect(function() {
242 + io.shallowField[0] = 'nope!';
243 + }).toThrow();
244 + expect(io.shallowField[1]).toEqual('second field');
245 + });
246 +
247 + message = 'should provide a setField interface as sugar for set()';
248 + testDevAndProd(message, function() {
249 + var beforeIO = new ImmutableObject({initialField: null});
250 + var afterIO =
251 + ImmutableObject.setField(beforeIO, 'anotherField', 'anotherValue');
252 + expect(afterIO).toBeSeriallyEqualTo({
253 + initialField: null,
254 + anotherField: 'anotherValue'
255 + });
256 + expect(afterIO).not.toBe(beforeIO);
257 + });
258 +
259 + message = 'should recursively create distinct objects when deep copying';
260 + testDevAndProd(message, function() {
261 + var beforeIO = new ImmutableObject({
262 + a: {b: 'b', c: {}, d: 'd', e: new ImmutableObject({f: 'f'}) }
263 + });
264 + var afterIO = ImmutableObject.setDeep(beforeIO, {
265 + a: {b: {}, c: 'C', e: {f: 'F', g: 'G'}, h: 'H'}
266 + });
267 + expect(afterIO).toBeSeriallyEqualTo({
268 + a: {b: {}, c: 'C', d: 'd', e: {f: 'F', g: 'G'}, h: 'H'}
269 + })
270 + expect(afterIO).not.toBe(beforeIO);
271 + expect(afterIO.a).not.toBe(beforeIO.a);
272 + expect(afterIO.a.e).not.toBe(beforeIO.a.e);
273 + });
274 +
275 + testDevAndProd('should deep copy member immutability', function() {
276 + var beforeIO = new ImmutableObject({
277 + a: {b: new ImmutableObject({c: 'c'}), e: {f: 'f'}}
278 + });
279 + var afterIO = ImmutableObject.setDeep(beforeIO, {
280 + a: {b: {d: 'D'}, e: new ImmutableObject({g: 'G'})}
281 + });
282 + expect(afterIO).toBeSeriallyEqualTo({
283 + a: {b: {c: 'c', d: 'D'}, e: {f: 'f', g: 'G'}}
284 + });
285 + expect(afterIO instanceof ImmutableObject).toBe(true);
286 + expect(afterIO.a.b instanceof ImmutableObject).toBe(true);
287 + expect(afterIO.a.e instanceof ImmutableObject).toBe(true);
288 + });
289 +});
src/utils/__tests__/OrderedMap-test.js new
+944
@@ -0,0 +1,944 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +var OrderedMap;
22 +
23 +/**
24 + * Shared, reusable objects.
25 + */
26 +var hasEmptyStringKey = {
27 + 'thisKeyIsFine': {data: []},
28 + '': {thisShouldCauseAFailure: []},
29 + 'thisKeyIsAlsoFine': {data: []}
30 +};
31 +
32 +/**
33 + * Used as map/forEach callback.
34 + */
35 +var duplicate = function(itm, key, count) {
36 + return {
37 + uniqueID: itm.uniqueID,
38 + val: itm.val + key + count + this.justToTestScope
39 + };
40 +};
41 +
42 +// Should not be allowed - because then null/'null' become impossible to
43 +// distinguish. Every key MUST be a string period!
44 +var hasNullAndUndefStringKey = [
45 + {uniqueID: 'undefined', val: 'thisIsUndefined'},
46 + {uniqueID: 'null', val: 'thisIsNull'}
47 +];
48 +var hasNullKey = [
49 + {uniqueID: 'thisKeyIsFine', data: []},
50 + {uniqueID: 'thisKeyIsAlsoFine', data: []},
51 + {uniqueID: null, data: []}
52 +];
53 +
54 +var hasObjectKey = [
55 + {uniqueID: 'thisKeyIsFine', data: []},
56 + {uniqueID: 'thisKeyIsAlsoFine', data: []},
57 + {uniqueID: {}, data: []}
58 +];
59 +
60 +var hasArrayKey = [
61 + {uniqueID: 'thisKeyIsFine', data: []},
62 + {uniqueID: 'thisKeyIsAlsoFine', data: []},
63 + {uniqueID: [], data: []}
64 +];
65 +
66 +// This should be allowed
67 +var hasNullStringKey = [
68 + {uniqueID: 'thisKeyIsFine', data: []},
69 + {uniqueID: 'thisKeyIsAlsoFine', data: []},
70 + {uniqueID: 'null', data: []}
71 +];
72 +
73 +var hasUndefinedKey = [
74 + {uniqueID: 'thisKeyIsFine', data: []},
75 + {uniqueID: 'thisKeyIsAlsoFine', data: []},
76 + {uniqueID: undefined, data: []}
77 +];
78 +
79 +var hasUndefinedStringKey = [
80 + {uniqueID: 'thisKeyIsFine', data: []},
81 + {uniqueID: 'thisKeyIsAlsoFine', data: []},
82 + {uniqueID: 'undefined', data: []}
83 +];
84 +
85 +var hasPositiveNumericKey = [
86 + {uniqueID: 'notANumber', data: []},
87 + {uniqueID: '5', data: []},
88 + {uniqueID: 'notAnotherNumber',data: []}
89 +];
90 +
91 +var hasZeroStringKey = [
92 + {uniqueID: 'greg', data: 'grego'},
93 + {uniqueID: '0', data: '0o'},
94 + {uniqueID: 'tom', data: 'tomo'}
95 +];
96 +
97 +var hasZeroNumberKey = [
98 + {uniqueID: 'greg', data: 'grego'},
99 + {uniqueID: 0, data: '0o'},
100 + {uniqueID: 'tom', data: 'tomo'}
101 +];
102 +
103 +var hasAllNumericStringKeys = [
104 + {uniqueID: '0', name: 'Gregory'},
105 + {uniqueID: '2', name: 'James'},
106 + {uniqueID: '1', name: 'Tom'}
107 +];
108 +
109 +var hasAllNumericKeys = [
110 + {uniqueID: 0, name: 'Gregory'},
111 + {uniqueID: 2, name: 'James'},
112 + {uniqueID: 1, name: 'Tom'}
113 +];
114 +
115 +var hasAllValidKeys = [
116 + {uniqueID: 'keyOne', value: 'valueOne'},
117 + {uniqueID: 'keyTwo', value: 'valueTwo'}
118 +];
119 +
120 +var hasDuplicateKeys = [
121 + {uniqueID: 'keyOne', value: 'valueOne'},
122 + {uniqueID: 'keyTwo', value: 'valueTwo'},
123 + {uniqueID: 'keyOne', value: 'valueThree'}
124 +];
125 +
126 +var idEntities = [
127 + {uniqueID: 'greg', name: 'Gregory'},
128 + {uniqueID: 'james', name: 'James'},
129 + {uniqueID: 'tom', name: 'Tom'}
130 +];
131 +
132 +var hasEmptyKey = [
133 + {uniqueID: 'greg', name: 'Gregory'},
134 + {uniqueID: '', name: 'James'},
135 + {uniqueID: 'tom', name: 'Tom'}
136 +];
137 +
138 +var extractUniqueID = function(entity) {
139 + return entity.uniqueID;
140 +};
141 +
142 +describe('OrderedMap', function() {
143 + beforeEach(function() {
144 + require('mock-modules').dumpCache();
145 + OrderedMap = require('OrderedMap');
146 + });
147 +
148 + it('should create according to simple object with keys', function() {
149 + OrderedMap.fromArray(hasAllValidKeys, extractUniqueID);
150 + // Iterate over and ensure key order.
151 + });
152 +
153 + it('should create from array when providing an identity CB', function() {
154 + expect(function() {
155 + OrderedMap.fromArray(idEntities, extractUniqueID);
156 + }).not.toThrow();
157 + });
158 +
159 + it('should throw if constructing from Array without identity CB', function() {
160 + OrderedMap.fromArray(idEntities, extractUniqueID);
161 + // Iterate and ensure key order
162 + });
163 +
164 + it('should not throw when fromArray extracts a numeric key', function() {
165 + expect(function() {
166 + OrderedMap.fromArray(hasPositiveNumericKey, extractUniqueID);
167 + }).not.toThrow();
168 +
169 + });
170 +
171 + it('should throw when any key is the empty string', function() {
172 + expect(function() {
173 + OrderedMap.fromArray(hasEmptyKey, extractUniqueID);
174 + }).toThrow();
175 + });
176 +
177 + it('should not throw when a key is the string "undefined" or "null"',
178 + function() {
179 + var om = OrderedMap.fromArray(hasNullAndUndefStringKey, extractUniqueID);
180 + expect(om.length).toBe(2);
181 + expect(om.indexOfKey('undefined')).toBe(0);
182 + expect(om.indexOfKey('null')).toBe(1);
183 + expect(om.keyAfter('undefined')).toBe('null');
184 + expect(om.keyAfter('null')).toBe(undefined);
185 + expect(om.keyBefore('undefined')).toBe(undefined);
186 + expect(om.has('undefined')).toBe(true);
187 + expect(om.has('null')).toBe(true);
188 + expect(om.get('undefined').val).toBe('thisIsUndefined');
189 + expect(om.get('null').val).toBe('thisIsNull');
190 + });
191 +
192 +
193 + /**
194 + * Numeric keys are cast to strings.
195 + */
196 + it('should not throw when a key is the number zero', function() {
197 + var om = OrderedMap.fromArray(hasZeroNumberKey, extractUniqueID);
198 + expect(om.length).toBe(3);
199 + expect(om.indexOfKey('0')).toBe(1);
200 + expect(om.indexOfKey(0)).toBe(1);
201 + });
202 +
203 + it('should throw when any key is falsey', function() {
204 + expect(function() {
205 + OrderedMap.fromArray(hasEmptyStringKey, extractUniqueID);
206 + }).toThrow();
207 +
208 + expect(function() {
209 + OrderedMap.fromArray(hasNullKey, extractUniqueID);
210 + }).toThrow();
211 +
212 + expect(function() {
213 + OrderedMap.fromArray(hasUndefinedKey, extractUniqueID);
214 + }).toThrow();
215 + });
216 +
217 + it('should not throw on string keys "undefined/null"', function() {
218 + expect(function() {
219 + OrderedMap.fromArray(hasNullStringKey, extractUniqueID);
220 + }).not.toThrow();
221 +
222 + expect(function() {
223 + OrderedMap.fromArray(hasUndefinedStringKey, extractUniqueID);
224 + }).not.toThrow();
225 + });
226 +
227 + it('should throw on extracting keys that are not strings/nums', function() {
228 + expect(function() {
229 + OrderedMap.fromArray(hasObjectKey, extractUniqueID);
230 + }).toThrow();
231 +
232 + expect(function() {
233 + OrderedMap.fromArray(hasArrayKey, extractUniqueID);
234 + }).toThrow();
235 + });
236 +
237 + it('should throw if instantiating with duplicate key', function() {
238 + expect(function() {
239 + OrderedMap.fromArray(hasDuplicateKeys, extractUniqueID);
240 + }).toThrow();
241 + });
242 +
243 + it('should not throw when a key is the string "0"', function() {
244 + var verifyOM = function(om) {
245 + expect(om.length).toBe(3);
246 + expect(om.indexOfKey('greg')).toBe(0);
247 + expect(om.indexOfKey('0')).toBe(1);
248 + expect(om.indexOfKey(0)).toBe(1); // Casts on writes and reads.
249 + expect(om.indexOfKey('tom')).toBe(2);
250 + expect(om.keyAfter('greg')).toBe('0');
251 + expect(om.keyAfter('0')).toBe('tom');
252 + expect(om.keyAfter(0)).toBe('tom');
253 + expect(om.keyAfter('tom')).toBe(undefined);
254 + expect(om.keyBefore('greg')).toBe(undefined);
255 + expect(om.keyBefore(0)).toBe('greg');
256 + expect(om.keyBefore('0')).toBe('greg');
257 + expect(om.keyBefore('tom')).toBe('0');
258 + expect(om.has('undefined')).toBe(false);
259 + expect(om.has(0)).toBe(true);
260 + expect(om.has('0')).toBe(true);
261 + };
262 + var om = OrderedMap.fromArray(hasZeroStringKey, extractUniqueID);
263 + verifyOM(om);
264 + om = OrderedMap.fromArray(hasZeroNumberKey, extractUniqueID);
265 + verifyOM(om);
266 + });
267 +
268 + it('should throw when getting invalid public key', function() {
269 + var om = OrderedMap.fromArray(hasAllValidKeys, extractUniqueID);
270 + expect(function() {
271 + om.has(undefined);
272 + }).toThrow();
273 + expect(function() {
274 + om.get(undefined);
275 + }).toThrow();
276 + expect(function() {
277 + om.has(null);
278 + }).toThrow();
279 + expect(function() {
280 + om.get(null);
281 + }).toThrow();
282 + expect(function() {
283 + om.has('');
284 + }).toThrow();
285 + expect(function() {
286 + om.get('');
287 + }).toThrow();
288 + });
289 +
290 + it('should throw when any key is falsey', function() {
291 + expect(function() {
292 + OrderedMap.fromArray(hasEmptyStringKey, extractUniqueID);
293 + }).toThrow();
294 +
295 + expect(function() {
296 + OrderedMap.fromArray(hasNullKey, extractUniqueID);
297 + }).toThrow();
298 +
299 + expect(function() {
300 + OrderedMap.fromArray(hasUndefinedKey, extractUniqueID);
301 + }).toThrow();
302 + });
303 +
304 +
305 + it('should throw when fromArray is passed crazy args', function() {
306 + // Test passing another OrderedMap (when it expects a plain object.)
307 + // This is probably not what you meant to do! We should error.
308 + var validOM = OrderedMap.fromArray(hasAllValidKeys, extractUniqueID);
309 + expect(function() {
310 + OrderedMap.fromArray({uniqueID: 'asdf'}, extractUniqueID);
311 + }).toThrow();
312 + expect(function() {
313 + OrderedMap.fromArray(validOM, extractUniqueID);
314 + }).toThrow();
315 + });
316 +
317 + it('should throw when fromArray is passed crazy things', function() {
318 + expect(function() {
319 + OrderedMap.fromArray(null, extractUniqueID);
320 + }).toThrow();
321 + expect(function() {
322 + OrderedMap.fromArray('stringgg', extractUniqueID);
323 + }).toThrow();
324 + expect(function() {
325 + OrderedMap.fromArray(undefined, extractUniqueID);
326 + }).toThrow();
327 + expect(function() {
328 + OrderedMap.fromArray(new Date(), extractUniqueID);
329 + }).toThrow();
330 + expect(function() {
331 + OrderedMap.fromArray({}, extractUniqueID);
332 + }).toThrow();
333 +
334 + // Test failure without extractor
335 + expect(function() {
336 + OrderedMap.fromArray(idEntities);
337 + }).toThrow();
338 + expect(function() {
339 + OrderedMap.fromArray(idEntities, extractUniqueID);
340 + }).not.toThrow();
341 + });
342 +
343 + // Testing methods that accept other `OrderedMap`s.
344 + it('should throw when from/merge is passed an non-OrderedMap.', function() {
345 + // Test passing an array to construction.
346 + expect(function() {
347 + OrderedMap.from(idEntities, extractUniqueID);
348 + }).toThrow();
349 +
350 + // Test passing an array to merge.
351 + expect(function() {
352 + OrderedMap.fromArray(idEntities, extractUniqueID)
353 + .merge(idEntities, extractUniqueID);
354 + }).toThrow();
355 +
356 +
357 + // Test passing a plain object to merge.
358 + expect(function() {
359 + OrderedMap.fromArray(
360 + idEntities,
361 + extractUniqueID
362 + ).merge({blah: 'willFail'});
363 + }).toThrow();
364 + });
365 +
366 + it('should throw when accessing key before/after of non-key', function() {
367 + var om = OrderedMap.fromArray([
368 + {uniqueID: 'first'},
369 + {uniqueID: 'two'}], extractUniqueID
370 + );
371 + expect(function() {
372 + om.keyBefore('dog');
373 + }).toThrow();
374 + expect(function() {
375 + om.keyAfter('cat');
376 + }).toThrow();
377 + expect(function() {
378 + om.keyAfter(null);
379 + }).toThrow();
380 + expect(function() {
381 + om.keyAfter(undefined);
382 + }).toThrow();
383 + });
384 +
385 + it('should throw passing invalid/not-present-keys to before/after',
386 + function() {
387 + var om = OrderedMap.fromArray([
388 + {uniqueID: 'one', val: 'first'},
389 + {uniqueID: 'two', val: 'second'},
390 + {uniqueID: 'three', val: 'third'},
391 + {uniqueID: 'four', val: 'fourth'}
392 + ], extractUniqueID);
393 +
394 + expect(function() {
395 + om.keyBefore('');
396 + }).toThrow();
397 + expect(function() {
398 + om.keyBefore(null);
399 + }).toThrow();
400 + expect(function() {
401 + om.keyBefore(undefined);
402 + }).toThrow();
403 + expect(function() {
404 + om.keyBefore('notInTheOrderedMap!');
405 + }).toThrow();
406 +
407 + expect(function() {
408 + om.keyAfter('');
409 + }).toThrow();
410 + expect(function() {
411 + om.keyAfter(null);
412 + }).toThrow();
413 + expect(function() {
414 + om.keyAfter(undefined);
415 + }).toThrow();
416 + expect(function() {
417 + om.keyAfter('notInTheOrderedMap!');
418 + }).toThrow();
419 +
420 + expect(function() {
421 + om.nthKeyAfter('', 1);
422 + }).toThrow();
423 + expect(function() {
424 + om.nthKeyAfter(null, 1);
425 + }).toThrow();
426 + expect(function() {
427 + om.nthKeyAfter(undefined, 1);
428 + }).toThrow();
429 + expect(function() {
430 + om.nthKeyAfter('notInTheOrderedMap!', 1);
431 + }).toThrow();
432 +
433 + expect(function() {
434 + om.nthKeyBefore('', 1);
435 + }).toThrow();
436 + expect(function() {
437 + om.nthKeyBefore(null, 1);
438 + }).toThrow();
439 + expect(function() {
440 + om.nthKeyBefore(undefined, 1);
441 + }).toThrow();
442 + expect(function() {
443 + om.nthKeyBefore('notInTheOrderedMap!', 1);
444 + }).toThrow();
445 + });
446 +
447 + it('should correctly determine the nth key after before', function() {
448 + var om = OrderedMap.fromArray([
449 + {uniqueID: 'one', val: 'first'},
450 + {uniqueID: 'two', val: 'second'},
451 + {uniqueID: 'three', val: 'third'},
452 + {uniqueID: 'four', val: 'fourth'}
453 + ], extractUniqueID);
454 + expect(om.keyBefore('one')).toBe(undefined); // first key
455 + expect(om.keyBefore('two')).toBe('one');
456 + expect(om.keyBefore('three')).toBe('two');
457 + expect(om.keyBefore('four')).toBe('three');
458 +
459 + expect(om.keyAfter('one')).toBe('two'); // first key
460 + expect(om.keyAfter('two')).toBe('three');
461 + expect(om.keyAfter('three')).toBe('four');
462 + expect(om.keyAfter('four')).toBe(undefined);
463 +
464 + expect(om.nthKeyBefore('one', 0)).toBe('one'); // first key
465 + expect(om.nthKeyBefore('one', 1)).toBe(undefined);
466 + expect(om.nthKeyBefore('one', 2)).toBe(undefined);
467 + expect(om.nthKeyBefore('two', 0)).toBe('two');
468 + expect(om.nthKeyBefore('two', 1)).toBe('one');
469 + expect(om.nthKeyBefore('four', 0)).toBe('four');
470 + expect(om.nthKeyBefore('four', 1)).toBe('three');
471 +
472 + expect(om.nthKeyAfter('one', 0)).toBe('one');
473 + expect(om.nthKeyAfter('one', 1)).toBe('two');
474 + expect(om.nthKeyAfter('one', 2)).toBe('three');
475 + expect(om.nthKeyAfter('two', 0)).toBe('two');
476 + expect(om.nthKeyAfter('two', 1)).toBe('three');
477 + expect(om.nthKeyAfter('four', 0)).toBe('four');
478 + expect(om.nthKeyAfter('four', 1)).toBe(undefined);
479 + });
480 +
481 + it('should compute key indices correctly', function() {
482 + var om = OrderedMap.fromArray([
483 + {uniqueID: 'one', val: 'first'},
484 + {uniqueID: 'two', val: 'second'}
485 + ], extractUniqueID);
486 + expect(om.keyAtIndex(0)).toBe('one');
487 + expect(om.keyAtIndex(1)).toBe('two');
488 + expect(om.keyAtIndex(2)).toBe(undefined);
489 + expect(om.indexOfKey('one')).toBe(0);
490 + expect(om.indexOfKey('two')).toBe(1);
491 + expect(om.indexOfKey('nope')).toBe(undefined);
492 + expect(function() {
493 + om.indexOfKey(null);
494 + }).toThrow();
495 + expect(function() {
496 + om.indexOfKey(undefined);
497 + }).toThrow();
498 + expect(function() {
499 + om.indexOfKey(''); // Empty key is not allowed
500 + }).toThrow();
501 + });
502 +
503 + it('should compute indices on array that extracted numeric ids', function() {
504 + var om = OrderedMap.fromArray(hasZeroStringKey, extractUniqueID);
505 + expect(om.keyAtIndex(0)).toBe('greg');
506 + expect(om.keyAtIndex(1)).toBe('0');
507 + expect(om.keyAtIndex(2)).toBe('tom');
508 + expect(om.indexOfKey('greg')).toBe(0);
509 + expect(om.indexOfKey('0')).toBe(1);
510 + expect(om.indexOfKey('tom')).toBe(2);
511 +
512 +
513 + var verifyNumericKeys = function(om) {
514 + expect(om.keyAtIndex(0)).toBe('0');
515 + expect(om.keyAtIndex(1)).toBe('2');
516 + expect(om.keyAtIndex(2)).toBe('1');
517 + expect(om.indexOfKey('0')).toBe(0);
518 + expect(om.indexOfKey('2')).toBe(1); // Proove these are not ordered by
519 + expect(om.indexOfKey('1')).toBe(2); // their keys
520 + };
521 + var omStringNumberKeys =
522 + OrderedMap.fromArray(hasAllNumericStringKeys, extractUniqueID);
523 + verifyNumericKeys(omStringNumberKeys);
524 + var omNumericKeys =
525 + OrderedMap.fromArray(hasAllNumericKeys, extractUniqueID);
526 + verifyNumericKeys(omNumericKeys);
527 + });
528 +
529 + it('should compute indices on mutually exclusive merge', function() {
530 + var om = OrderedMap.fromArray([
531 + {uniqueID: 'one', val: 'first'},
532 + {uniqueID: 'two', val: 'second'}
533 + ], extractUniqueID);
534 + var om2 = OrderedMap.fromArray([
535 + {uniqueID: 'three', val: 'third'}
536 + ], extractUniqueID);
537 + var res = om.merge(om2);
538 +
539 + expect(res.length).toBe(3);
540 +
541 + expect(res.keyAtIndex(0)).toBe('one');
542 + expect(res.keyAtIndex(1)).toBe('two');
543 + expect(res.keyAtIndex(2)).toBe('three');
544 + expect(res.keyAtIndex(3)).toBe(undefined);
545 +
546 + expect(res.indexOfKey('one')).toBe(0);
547 + expect(res.indexOfKey('two')).toBe(1);
548 + expect(res.indexOfKey('three')).toBe(2);
549 + expect(res.indexOfKey('dog')).toBe(undefined);
550 +
551 + expect(res.has('one')).toBe(true);
552 + expect(res.has('two')).toBe(true);
553 + expect(res.has('three')).toBe(true);
554 + expect(res.has('dog')).toBe(false);
555 +
556 + expect(res.get('one').val).toBe('first');
557 + expect(res.get('two').val).toBe('second');
558 + expect(res.get('three').val).toBe('third');
559 + expect(res.get('dog')).toBe(undefined);
560 + });
561 +
562 + it('should compute indices on intersected merge', function() {
563 + var oneTwo = OrderedMap.fromArray([
564 + {uniqueID: 'one', val: 'first'},
565 + {uniqueID: 'two', val: 'secondOM1'}
566 + ], extractUniqueID);
567 +
568 + var testOneTwoMergedWithTwoThree = function(res) {
569 + expect(res.length).toBe(3);
570 + expect(res.keyAtIndex(0)).toBe('one');
571 + expect(res.keyAtIndex(1)).toBe('two');
572 + expect(res.keyAtIndex(2)).toBe('three');
573 + expect(res.keyAtIndex(3)).toBe(undefined);
574 + expect(res.indexOfKey('one')).toBe(0);
575 + expect(res.indexOfKey('two')).toBe(1);
576 + expect(res.indexOfKey('three')).toBe(2);
577 + expect(res.indexOfKey('dog')).toBe(undefined);
578 + expect(res.has('one')).toBe(true);
579 + expect(res.has('two')).toBe(true);
580 + expect(res.has('three')).toBe(true);
581 + expect(res.has('dog')).toBe(false);
582 + expect(res.get('one').val).toBe('first');
583 + expect(res.get('two').val).toBe('secondOM2');
584 + expect(res.get('three').val).toBe('third');
585 + expect(res.get('dog')).toBe(undefined);
586 + };
587 +
588 + var result =
589 + oneTwo.merge(OrderedMap.fromArray([
590 + {uniqueID: 'two', val: 'secondOM2'},
591 + {uniqueID: 'three', val: 'third'}
592 + ], extractUniqueID));
593 + testOneTwoMergedWithTwoThree(result);
594 +
595 + // Everything should be exactly as before, since the ordering of `two` was
596 + // already determined by `om`.
597 + result = oneTwo.merge(
598 + OrderedMap.fromArray([
599 + {uniqueID: 'three', val: 'third'},
600 + {uniqueID: 'two', val:'secondOM2'}
601 + ], extractUniqueID)
602 + );
603 + testOneTwoMergedWithTwoThree(result);
604 +
605 +
606 + var testTwoThreeMergedWithOneTwo = function(res) {
607 + expect(res.length).toBe(3);
608 + expect(res.keyAtIndex(0)).toBe('two');
609 + expect(res.keyAtIndex(1)).toBe('three');
610 + expect(res.keyAtIndex(2)).toBe('one');
611 + expect(res.keyAtIndex(3)).toBe(undefined);
612 + expect(res.indexOfKey('two')).toBe(0);
613 + expect(res.indexOfKey('three')).toBe(1);
614 + expect(res.indexOfKey('one')).toBe(2);
615 + expect(res.indexOfKey('cat')).toBe(undefined);
616 + expect(res.has('two')).toBe(true);
617 + expect(res.has('three')).toBe(true);
618 + expect(res.has('one')).toBe(true);
619 + expect(res.has('dog')).toBe(false);
620 + expect(res.get('one').val).toBe('first');
621 + expect(res.get('two').val).toBe('secondOM1');
622 + expect(res.get('three').val).toBe('third');
623 + expect(res.get('dog')).toBe(undefined);
624 + };
625 + result = OrderedMap.fromArray([
626 + {uniqueID: 'two', val: 'secondOM2'},
627 + {uniqueID: 'three', val: 'third'}
628 + ], extractUniqueID).merge(oneTwo);
629 + testTwoThreeMergedWithOneTwo(result);
630 +
631 + });
632 +
633 + it('should merge mutually exclusive keys to the end.', function() {
634 + var om = OrderedMap.fromArray([
635 + {uniqueID: 'one', val: 'first'},
636 + {uniqueID: 'two', val: 'second'}
637 + ], extractUniqueID);
638 + var om2 = OrderedMap.fromArray([
639 + {uniqueID: 'three', val: 'first'},
640 + {uniqueID: 'four', val: 'second'}
641 + ], extractUniqueID);
642 + var res = om.merge(om2);
643 + expect(res.length).toBe(4);
644 +
645 + });
646 +
647 + it('should map correctly', function() {
648 + var om = OrderedMap.fromArray([
649 + {uniqueID: 'x', val: 'xx'},
650 + {uniqueID: 'y', val: 'yy'},
651 + {uniqueID: 'z', val: 'zz'}
652 + ], extractUniqueID);
653 + var scope = {justToTestScope: 'justTestingScope'};
654 + var verifyResult = function(omResult) {
655 + expect(omResult.length).toBe(3);
656 + expect(omResult.keyAtIndex(0)).toBe('x');
657 + expect(omResult.keyAtIndex(1)).toBe('y');
658 + expect(omResult.keyAtIndex(2)).toBe('z');
659 + expect(omResult.get('x').val).toBe('xxx0justTestingScope');
660 + expect(omResult.get('y').val).toBe('yyy1justTestingScope');
661 + expect(omResult.get('z').val).toBe('zzz2justTestingScope');
662 + };
663 + var resultOM = om.map(function(itm, key, count) {
664 + return {
665 + uniqueID: itm.uniqueID,
666 + val: itm.val + key + count + this.justToTestScope
667 + };
668 + }, scope);
669 + verifyResult(resultOM);
670 +
671 + var resArray = [];
672 + om.forEach(function(itm, key, count) {
673 + resArray.push({
674 + uniqueID: itm.uniqueID,
675 + val: itm.val + key + count + this.justToTestScope
676 + });
677 + }, scope);
678 + resultOM = OrderedMap.fromArray(resArray, extractUniqueID);
679 + verifyResult(resultOM);
680 + });
681 +
682 + it('should filter correctly', function() {
683 + var om = OrderedMap.fromArray([
684 + {uniqueID: 'x', val: 'xx'},
685 + {uniqueID: 'y', val: 'yy'},
686 + {uniqueID: 'z', val: 'zz'}
687 + ], extractUniqueID);
688 + var scope = {justToTestScope: 'justTestingScope'};
689 +
690 + var filteringCallback = function(item, key, indexInOriginal) {
691 + expect(this).toBe(scope);
692 + expect(key === 'x' || key === 'y' || key === 'z').toBe(true);
693 + if (key === 'x') {
694 + expect(item.val).toBe('xx');
695 + expect(indexInOriginal).toBe(0);
696 + return false;
697 + } else if (key === 'y') {
698 + expect(item.val).toBe('yy');
699 + expect(indexInOriginal).toBe(1);
700 + return true;
701 + } else {
702 + expect(item.val).toBe('zz');
703 + expect(indexInOriginal).toBe(2);
704 + return true;
705 + }
706 + };
707 +
708 + var verifyResult = function(omResult) {
709 + expect(omResult.length).toBe(2);
710 + expect(omResult.keyAtIndex(0)).toBe('y');
711 + expect(omResult.keyAtIndex(1)).toBe('z');
712 + expect(omResult.has('x')).toBe(false);
713 + expect(omResult.has('z')).toBe(true);
714 + expect(omResult.get('z').val).toBe('zz');
715 + expect(omResult.has('y')).toBe(true);
716 + expect(omResult.get('y').val).toBe('yy');
717 + };
718 +
719 + var resultOM = om.filter(filteringCallback, scope);
720 + verifyResult(resultOM);
721 + });
722 +
723 + it('should throw when providing invalid ranges to ranging', function() {
724 + var om = OrderedMap.fromArray([
725 + {uniqueID: 'x', val: 'xx'},
726 + {uniqueID: 'y', val: 'yy'},
727 + {uniqueID: 'z', val: 'zz'}
728 + ], extractUniqueID);
729 + var scope = {justToTestScope: 'justTestingScope'};
730 +
731 + expect(function() {
732 + om.mapRange(duplicate, 0, 3, scope);
733 + }).not.toThrow();
734 + expect(function() {
735 + om.filterRange(duplicate, 0, 3, scope);
736 + }).not.toThrow();
737 + expect(function() {
738 + om.forEachRange(duplicate, 0, 3, scope);
739 + }).not.toThrow();
740 + expect(function() {
741 + om.mapKeyRange(duplicate, 'x' , 3, scope);
742 + }).not.toThrow();
743 + expect(function() {
744 + om.forEachKeyRange(duplicate, 'x', 3, scope);
745 + }).not.toThrow();
746 +
747 + expect(function() {
748 + om.mapRange(duplicate, 0, 4, scope);
749 + }).toThrow();
750 + expect(function() {
751 + om.filterRange(duplicate, 0, 4, scope);
752 + }).toThrow();
753 + expect(function() {
754 + om.forEachRange(duplicate, 0, 4, scope);
755 + }).toThrow();
756 + expect(function() {
757 + om.mapKeyRange(duplicate, 'x', null, scope);
758 + }).toThrow();
759 + expect(function() {
760 + om.forEachKeyRange(duplicate, 'x', null, scope);
761 + }).toThrow();
762 +
763 + expect(function() {
764 + om.mapRange(duplicate, -1, 1, scope);
765 + }).toThrow();
766 + expect(function() {
767 + om.filterRange(duplicate, -1, 1, scope);
768 + }).toThrow();
769 + expect(function() {
770 + om.forEachRange(duplicate, -1, 1, scope);
771 + }).toThrow();
772 + expect(function() {
773 + om.mapKeyRange(duplicate, null, 'y', scope);
774 + }).toThrow();
775 + expect(function() {
776 + om.forEachKeyRange(duplicate, null, 'y', scope);
777 + }).toThrow();
778 +
779 + expect(function() {
780 + om.mapRange(duplicate, 0, 0, scope);
781 + }).not.toThrow();
782 + expect(function() {
783 + om.filterRange(duplicate, 0, 0, scope);
784 + }).not.toThrow();
785 + expect(function() {
786 + om.forEachRange(duplicate, 0, 0, scope);
787 + }).not.toThrow();
788 + expect(function() {
789 + om.mapKeyRange(duplicate, 'x', 'x', scope);
790 + }).not.toThrow();
791 + expect(function() {
792 + om.forEachKeyRange(duplicate, 'x', 'x', scope);
793 + }).not.toThrow();
794 +
795 + expect(function() {
796 + om.mapRange(duplicate, 0, -1, scope);
797 + }).toThrow();
798 + expect(function() {
799 + om.filterRange(duplicate, 0, -1, scope);
800 + }).toThrow();
801 + expect(function() {
802 + om.forEachRange(duplicate, 0, -1, scope);
803 + }).toThrow();
804 + expect(function() {
805 + om.mapKeyRange(duplicate, 'x', null, scope);
806 + }).toThrow();
807 + expect(function() {
808 + om.forEachKeyRange(duplicate, 'x', null, scope);
809 + }).toThrow();
810 +
811 + expect(function() {
812 + om.mapRange(duplicate, 2, 1, scope);
813 + }).not.toThrow();
814 + expect(function() {
815 + om.filterRange(duplicate, 2, 1, scope);
816 + }).not.toThrow();
817 + expect(function() {
818 + om.forEachRange(duplicate, 2, 1, scope);
819 + }).not.toThrow();
820 + expect(function() {
821 + om.mapKeyRange(duplicate, 'z', 'z', scope);
822 + }).not.toThrow();
823 + expect(function() {
824 + om.forEachKeyRange(duplicate, 'z', 'z', scope);
825 + }).not.toThrow();
826 +
827 + expect(function() {
828 + om.mapRange(duplicate, 2, 2, scope);
829 + }).toThrow();
830 + expect(function() {
831 + om.filterRange(duplicate, 2, 2, scope);
832 + }).toThrow();
833 + expect(function() {
834 + om.forEachRange(duplicate, 2, 2, scope);
835 + }).toThrow();
836 + expect(function() {
837 + om.mapKeyRange(duplicate, 'z', null, scope);
838 + }).toThrow();
839 + expect(function() {
840 + om.forEachKeyRange(duplicate, 'z', null, scope);
841 + }).toThrow();
842 +
843 + // Provide keys in reverse order - should throw.
844 + expect(function() {
845 + om.mapKeyRange(duplicate, 'y', 'x', scope);
846 + }).toThrow();
847 + expect(function() {
848 + om.forEachKeyRange(duplicate, 'y', 'x', scope);
849 + }).toThrow();
850 + });
851 +
852 + // TEST length zero map, or keyrange start===end
853 +
854 + it('should map range correctly', function() {
855 + var om = OrderedMap.fromArray([
856 + {uniqueID: 'x', val: 'xx'},
857 + {uniqueID: 'y', val: 'yy'},
858 + {uniqueID: 'z', val: 'zz'}
859 + ], extractUniqueID);
860 + var scope = {justToTestScope: 'justTestingScope'};
861 + var verifyThreeItems = function(omResult) {
862 + expect(omResult.length).toBe(3);
863 + expect(omResult.keyAtIndex(0)).toBe('x');
864 + expect(omResult.keyAtIndex(1)).toBe('y');
865 + expect(omResult.keyAtIndex(2)).toBe('z');
866 + expect(omResult.get('x').val).toBe('xxx0justTestingScope');
867 + expect(omResult.get('y').val).toBe('yyy1justTestingScope');
868 + expect(omResult.get('z').val).toBe('zzz2justTestingScope');
869 + };
870 + var verifyFirstTwoItems = function(omResult) {
871 + expect(omResult.length).toBe(2);
872 + expect(omResult.keyAtIndex(0)).toBe('x');
873 + expect(omResult.keyAtIndex(1)).toBe('y');
874 + expect(omResult.get('x').val).toBe('xxx0justTestingScope');
875 + expect(omResult.get('y').val).toBe('yyy1justTestingScope');
876 + };
877 +
878 + var verifyLastTwoItems = function(omResult) {
879 + expect(omResult.length).toBe(2);
880 + expect(omResult.keyAtIndex(0)).toBe('y');
881 + expect(omResult.keyAtIndex(1)).toBe('z');
882 + expect(omResult.get('y').val).toBe('yyy1justTestingScope');
883 + expect(omResult.get('z').val).toBe('zzz2justTestingScope');
884 + };
885 +
886 + var verifyMiddleItem = function(omResult) {
887 + expect(omResult.length).toBe(1);
888 + expect(omResult.keyAtIndex(0)).toBe('y');
889 + expect(omResult.get('y').val).toBe('yyy1justTestingScope');
890 + };
891 +
892 + var verifyEmpty = function(omResult) {
893 + expect(omResult.length).toBe(0);
894 + };
895 +
896 + var omResultThree = om.mapRange(duplicate, 0, 3, scope);
897 + verifyThreeItems(omResultThree);
898 + var resArray = [];
899 + var pushToResArray = function(itm, key, count) {
900 + resArray.push({
901 + uniqueID: itm.uniqueID,
902 + val: itm.val + key + count + this.justToTestScope
903 + });
904 + };
905 +
906 + om.forEachRange(pushToResArray, 0, 3, scope);
907 + omResultThree = OrderedMap.fromArray(resArray, extractUniqueID);
908 + verifyThreeItems(omResultThree);
909 +
910 + var omResultFirstTwo = om.mapRange(duplicate, 0, 2, scope);
911 + verifyFirstTwoItems(omResultFirstTwo);
912 + resArray = [];
913 + om.forEachRange(pushToResArray, 0, 2, scope);
914 + omResultFirstTwo = OrderedMap.fromArray(resArray, extractUniqueID);
915 + verifyFirstTwoItems(omResultFirstTwo);
916 +
917 + var omResultLastTwo = om.mapRange(duplicate, 1, 2, scope);
918 + verifyLastTwoItems(omResultLastTwo);
919 + resArray = [];
920 + om.forEachRange(pushToResArray, 1, 2, scope);
921 + omResultLastTwo = OrderedMap.fromArray(resArray, extractUniqueID);
922 + verifyLastTwoItems(omResultLastTwo);
923 +
924 + var omResultMiddle = om.mapRange(duplicate, 1, 1, scope);
925 + verifyMiddleItem(omResultMiddle);
926 + resArray = [];
927 + om.forEachRange(pushToResArray, 1, 1, scope);
928 + omResultMiddle = OrderedMap.fromArray(resArray, extractUniqueID);
929 + verifyMiddleItem(omResultMiddle);
930 +
931 + var omResultNone = om.mapRange(duplicate, 1, 0, scope);
932 + verifyEmpty(omResultNone);
933 + });
934 +
935 + it('should extract the original array correctly', function() {
936 + var sourceArray = [
937 + {uniqueID: 'x', val: 'xx'},
938 + {uniqueID: 'y', val: 'yy'},
939 + {uniqueID: 'z', val: 'zz'}
940 + ];
941 + var om = OrderedMap.fromArray(sourceArray, extractUniqueID);
942 + expect(om.toArray()).toEqual(sourceArray);
943 + });
944 +});
src/utils/__tests__/Transaction-test.js new
+314
@@ -0,0 +1,314 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +var Transaction;
22 +var mixInto;
23 +
24 +var INIT_ERRORED = 'initErrored'; // Just a dummy value to check for.
25 +describe('Transaction', function() {
26 + beforeEach(function() {
27 + require('mock-modules').dumpCache();
28 + Transaction = require('Transaction');
29 + mixInto = require('mixInto');
30 + });
31 +
32 + /**
33 + * We should not invoke closers for inits that failed. We should pass init
34 + * return values to closers when those inits are successful. We should not
35 + * invoke the actual method when any of the initializers fail.
36 + */
37 + it('should invoke closers with/only-with init returns', function() {
38 + var throwInInit = function() {
39 + throw new Error('close[0] should receive Transaction.OBSERVED_ERROR');
40 + };
41 +
42 + var performSideEffect;
43 + var dontPerformThis = function() {
44 + performSideEffect = 'This should never be set to this';
45 + };
46 +
47 + /**
48 + * New test Transaction subclass.
49 + */
50 + var TestTransaction = function() {
51 + this.reinitializeTransaction();
52 + this.firstCloseParam = INIT_ERRORED; // WON'T be set to something else
53 + this.secondCloseParam = INIT_ERRORED; // WILL be set to something else
54 + this.lastCloseParam = INIT_ERRORED; // WON'T be set to something else
55 + };
56 + mixInto(TestTransaction, Transaction.Mixin);
57 + TestTransaction.prototype.getTransactionWrappers = function() {
58 + return [
59 + {
60 + initialize: throwInInit,
61 + close: function(initResult) {
62 + this.firstCloseParam = initResult;
63 + }
64 + },
65 + {
66 + initialize: function() { return 'asdf'; },
67 + close: function(initResult) {
68 + this.secondCloseParam = initResult;
69 + }
70 + },
71 + {
72 + initialize: throwInInit,
73 + close: function(initResult) {
74 + this.lastCloseParam = initResult;
75 + }
76 + }
77 + ];
78 + };
79 +
80 + var transaction = new TestTransaction();
81 +
82 + expect(function() {
83 + transaction.perform(dontPerformThis);
84 + }).toThrow();
85 +
86 + expect(performSideEffect).toBe(undefined);
87 + expect(transaction.firstCloseParam).toBe(INIT_ERRORED);
88 + expect(transaction.secondCloseParam).toBe('asdf');
89 + expect(transaction.lastCloseParam).toBe(INIT_ERRORED);
90 + expect(transaction.isInTransaction()).toBe(false);
91 + });
92 +
93 + it('should invoke closers and wrapped method when inits success', function() {
94 +
95 + var performSideEffect;
96 + /**
97 + * New test Transaction subclass.
98 + */
99 + var TestTransaction = function() {
100 + this.reinitializeTransaction();
101 + this.firstCloseParam = INIT_ERRORED; // WON'T be set to something else
102 + this.secondCloseParam = INIT_ERRORED; // WILL be set to something else
103 + this.lastCloseParam = INIT_ERRORED; // WON'T be set to something else
104 + };
105 + mixInto(TestTransaction, Transaction.Mixin);
106 + TestTransaction.prototype.getTransactionWrappers = function() {
107 + return [
108 + {
109 + initialize: function() {
110 + return 'firstResult';
111 + },
112 + close: function(initResult) {
113 + this.firstCloseParam = initResult;
114 + }
115 + },
116 + {
117 + initialize: function() {
118 + return 'secondResult';
119 + },
120 + close: function(initResult) {
121 + this.secondCloseParam = initResult;
122 + }
123 + },
124 + {
125 + initialize: function() {
126 + return 'thirdResult';
127 + },
128 + close: function(initResult) {
129 + this.lastCloseParam = initResult;
130 + }
131 + }
132 + ];
133 + };
134 +
135 + var transaction = new TestTransaction();
136 +
137 + transaction.perform(function() {
138 + performSideEffect = 'SIDE_EFFECT';
139 + });
140 +
141 + expect(performSideEffect).toBe('SIDE_EFFECT');
142 + expect(transaction.firstCloseParam).toBe('firstResult');
143 + expect(transaction.secondCloseParam).toBe('secondResult');
144 + expect(transaction.lastCloseParam).toBe('thirdResult');
145 + expect(transaction.isInTransaction()).toBe(false);
146 + });
147 +
148 + /**
149 + * When the operation throws, the transaction should throw, but all of the
150 + * error-free closers should execute gracefully without issue. If a closer
151 + * throws an error, the transaction should prefer to throw the error
152 + * encountered earlier in the operation.
153 + */
154 + it('should throw when wrapped operation throws', function() {
155 +
156 + var performSideEffect;
157 + /**
158 + * New test Transaction subclass.
159 + */
160 + var TestTransaction = function() {
161 + this.reinitializeTransaction();
162 + this.firstCloseParam = INIT_ERRORED; // WON'T be set to something else
163 + this.secondCloseParam = INIT_ERRORED; // WILL be set to something else
164 + this.lastCloseParam = INIT_ERRORED; // WON'T be set to something else
165 + };
166 + mixInto(TestTransaction, Transaction.Mixin);
167 + // Now, none of the close/inits throw, but the operation we wrap will throw.
168 + TestTransaction.prototype.getTransactionWrappers = function() {
169 + return [
170 + {
171 + initialize: function() {
172 + return 'firstResult';
173 + },
174 + close: function(initResult) {
175 + this.firstCloseParam = initResult;
176 + }
177 + },
178 + {
179 + initialize: function() {
180 + return 'secondResult';
181 + },
182 + close: function(initResult) {
183 + this.secondCloseParam = initResult;
184 + }
185 + },
186 + {
187 + initialize: function() {
188 + return 'thirdResult';
189 + },
190 + close: function(initResult) {
191 + this.lastCloseParam = initResult;
192 + }
193 + },
194 + {
195 + initialize: function() {
196 + return 'fourthResult';
197 + },
198 + close: function(initResult) {
199 + throw new Error('The transaction should throw a TypeError.');
200 + }
201 + }
202 + ];
203 + };
204 +
205 + var transaction = new TestTransaction();
206 +
207 + expect(function() {
208 + var isTypeError = false;
209 + try {
210 + transaction.perform(function() {
211 + throw new TypeError("Thrown in main wrapped operation");
212 + });
213 + } catch (err) {
214 + isTypeError = (err instanceof TypeError);
215 + }
216 + return isTypeError;
217 + }()).toBe(true);
218 +
219 + expect(performSideEffect).toBe(undefined);
220 + expect(transaction.firstCloseParam).toBe('firstResult');
221 + expect(transaction.secondCloseParam).toBe('secondResult');
222 + expect(transaction.lastCloseParam).toBe('thirdResult');
223 + expect(transaction.isInTransaction()).toBe(false);
224 + });
225 +
226 + it('should throw errors in transaction close', function() {
227 + var TestTransaction = function() {
228 + this.reinitializeTransaction();
229 + };
230 + mixInto(TestTransaction, Transaction.Mixin);
231 + var exceptionMsg = 'This exception should throw.';
232 + TestTransaction.prototype.getTransactionWrappers = function() {
233 + return [
234 + {
235 + close: function(initResult) {
236 + throw new Error(exceptionMsg);
237 + }
238 + }
239 + ];
240 + };
241 +
242 + var transaction = new TestTransaction();
243 + expect(function() {
244 + transaction.perform(function() {});
245 + }).toThrow(exceptionMsg);
246 + });
247 +
248 + it('should allow nesting of transactions', function() {
249 + var performSideEffect;
250 + var nestedPerformSideEffect;
251 + /**
252 + * New test Transaction subclass.
253 + */
254 + var TestTransaction = function() {
255 + this.reinitializeTransaction();
256 + this.firstCloseParam = INIT_ERRORED; // WON'T be set to something else
257 + this.secondCloseParam = INIT_ERRORED; // WILL be set to something else
258 + this.lastCloseParam = INIT_ERRORED; // WON'T be set to something else
259 + };
260 + mixInto(TestTransaction, Transaction.Mixin);
261 + TestTransaction.prototype.getTransactionWrappers = function() {
262 + return [
263 + {
264 + initialize: function() {
265 + return 'firstResult';
266 + },
267 + close: function(initResult) {
268 + this.firstCloseParam = initResult;
269 + }
270 + },
271 + {
272 + initialize: function() {
273 + this.nestedTransaction = new NestedTransaction();
274 + },
275 + close: function() {
276 + // Test performing a transaction in another transaction's close()
277 + this.nestedTransaction.perform(function() {
278 + nestedPerformSideEffect = 'NESTED_SIDE_EFFECT';
279 + });
280 + }
281 + }
282 + ];
283 + };
284 +
285 + var NestedTransaction = function() {
286 + this.reinitializeTransaction();
287 + };
288 + mixInto(NestedTransaction, Transaction.Mixin);
289 + NestedTransaction.prototype.getTransactionWrappers = function() {
290 + return [{
291 + initialize: function() {
292 + this.hasInitializedNested = true;
293 + },
294 + close: function() {
295 + this.hasClosedNested = true;
296 + }
297 + }];
298 + };
299 +
300 + var transaction = new TestTransaction();
301 +
302 + transaction.perform(function() {
303 + performSideEffect = 'SIDE_EFFECT';
304 + });
305 +
306 + expect(performSideEffect).toBe('SIDE_EFFECT');
307 + expect(nestedPerformSideEffect).toBe('NESTED_SIDE_EFFECT');
308 + expect(transaction.firstCloseParam).toBe('firstResult');
309 + expect(transaction.isInTransaction()).toBe(false);
310 + expect(transaction.nestedTransaction.hasClosedNested).toBe(true);
311 + expect(transaction.nestedTransaction.hasInitializedNested).toBe(true);
312 + expect(transaction.nestedTransaction.isInTransaction()).toBe(false);
313 + });
314 +});
src/utils/__tests__/joinClasses-test.js new
+57
@@ -0,0 +1,57 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +require('mock-modules').dontMock('joinClasses');
22 +
23 +var joinClasses = require('joinClasses');
24 +
25 +describe('joinClasses', function() {
26 +
27 + it('should return a single className', function() {
28 + expect(joinClasses('aaa')).toEqual('aaa');
29 + });
30 +
31 + it('should join two classes together', function() {
32 + var aaa = 'aaa';
33 + var bbb = 'bbb';
34 + expect(joinClasses(aaa, bbb)).toEqual('aaa bbb');
35 + });
36 +
37 + it('should join many classes together', function() {
38 + var aaa = 'aaa';
39 + var bbb = 'bbb';
40 + var ccc = 'ccc';
41 + var ddd = 'ddd';
42 + var eee = 'eee';
43 + expect(joinClasses(aaa, bbb, ccc, ddd, eee)).toEqual('aaa bbb ccc ddd eee');
44 + });
45 +
46 + it('should omit undefined and empty classes', function() {
47 + var aaa = 'aaa';
48 + var bbb;
49 + var ccc = null;
50 + var ddd = '';
51 + var eee = 'eee';
52 + expect(joinClasses(bbb)).toEqual('');
53 + expect(joinClasses(bbb, bbb, bbb)).toEqual('');
54 + expect(joinClasses(aaa, bbb, ccc, ddd, eee)).toEqual('aaa eee');
55 + });
56 +
57 +});
src/utils/__tests__/keyMirror-test.js new
+71
@@ -0,0 +1,71 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +require('mock-modules').dontMock('keyMirror');
22 +
23 +var keyMirror = require('keyMirror');
24 +
25 +describe('keyMirror', function() {
26 + it('should create an object with values matching keys provided', function() {
27 + var mirror = keyMirror({
28 + foo: null,
29 + bar: true,
30 + "baz": { some: "object" },
31 + qux: undefined
32 + });
33 + expect('foo' in mirror).toBe(true);
34 + expect(mirror.foo).toBe('foo');
35 + expect('bar' in mirror).toBe(true);
36 + expect(mirror.bar).toBe('bar');
37 + expect('baz' in mirror).toBe(true);
38 + expect(mirror.baz).toBe('baz');
39 + expect('qux' in mirror).toBe(true);
40 + expect(mirror.qux).toBe('qux');
41 + });
42 +
43 + it('should not use properties from prototypes', function() {
44 + function Klass() {
45 + this.useMeToo = true;
46 + }
47 + Klass.prototype.doNotUse = true;
48 + var instance = new Klass();
49 + instance.useMe = true;
50 +
51 + var mirror = keyMirror(instance);
52 +
53 + expect('doNotUse' in mirror).toBe(false);
54 + expect('useMe' in mirror).toBe(true);
55 + expect('useMeToo' in mirror).toBe(true);
56 + });
57 +
58 + it('should throw when a non-object argument is used', function() {
59 + [null, undefined, 0, 7, ['uno'], true, "string"].forEach(function(testVal) {
60 + expect(keyMirror.bind(null, testVal)).toThrow();
61 + });
62 + });
63 +
64 + it('should work when "constructor" is a key', function() {
65 + var obj = { constructor: true };
66 + expect(keyMirror.bind(null, obj)).not.toThrow();
67 + var mirror = keyMirror(obj);
68 + expect('constructor' in mirror).toBe(true);
69 + });
70 +});
71 +
src/utils/__tests__/memoizeStringOnly-test.js new
+37
@@ -0,0 +1,37 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +describe('memoizeStringOnly', function() {
22 + var memoizeStringOnly;
23 +
24 + beforeEach(function() {
25 + require('mock-modules').dumpCache();
26 + memoizeStringOnly = require('memoizeStringOnly');
27 + });
28 +
29 + it('should be transparent to callers', function() {
30 + var callback = function(string) {
31 + return string;
32 + };
33 + var memoized = memoizeStringOnly(callback);
34 +
35 + expect(memoized('foo'), callback('foo'));
36 + });
37 +});
src/utils/__tests__/mergeFuncs-test.js new
+983
@@ -0,0 +1,983 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +require('mock-modules')
22 + .dontMock('mergeHelpers')
23 + .dontMock('merge')
24 + .dontMock('mergeDeep')
25 + .dontMock('mergeInto')
26 + .dontMock('mergeDeepInto');
27 +
28 +var mergeHelpers;
29 +var merge;
30 +var mergeInto;
31 +var mergeDeep;
32 +var mergeDeepInto;
33 +
34 +var isTerminal;
35 +var MAX_MERGE_DEPTH;
36 +
37 +/**
38 + * Simple helper to traverse an object, calling the callback (cb) on every
39 + * nonTerminal node it finds. It stops once it reaches the maximum depth,
40 + * because we know that the merge functions shouldn't merge past that depth
41 + * anyways. If you don't "stop" once reaching the MAX_MERGE_DEPTH, there won't
42 + * be a practical way to traverse circular structures.
43 + */
44 +var traverseUntilLeaves = function(obj, cb, depth) {
45 + if ((depth || 0) >= MAX_MERGE_DEPTH) {
46 + return;
47 + }
48 + var key;
49 + if (!isTerminal(obj) && !Array.isArray(obj)) {
50 + cb(obj);
51 + for (key in obj) {
52 + if (!obj.hasOwnProperty(key)) {
53 + continue;
54 + }
55 + traverseUntilLeaves(obj[key], cb, (depth || 0) + 1);
56 + }
57 + }
58 +};
59 +
60 +/*
61 + * Checks that the structure of two merged objects are identical. Checks
62 + * MAX_LEVEL because we don't want to block other unit tests if I have an
63 + * infinite loop.
64 + */
65 +var MAX_LEVEL = 50;
66 +var structureEquals = function(one, two, levelParam) {
67 + var level = levelParam || 0;
68 + var oneKey;
69 + var twoKey;
70 + if (level > MAX_LEVEL) {
71 + throw new Error('structure equals computation explored too many levels');
72 + }
73 + if (isTerminal(one) && isTerminal(two)) {
74 + return one === two;
75 + } else if (!isTerminal(one) && !isTerminal(two)) {
76 + var allInOneAreInTwo = true;
77 + for (oneKey in one) {
78 + if (!one.hasOwnProperty(oneKey)) {
79 + continue;
80 + }
81 + if (!structureEquals(one[oneKey], two[oneKey], level + 1)) {
82 + allInOneAreInTwo = false;
83 + }
84 + }
85 + var allInTwoAreInOne = true;
86 + for (twoKey in two) {
87 + if (!two.hasOwnProperty(twoKey)) {
88 + continue;
89 + }
90 + if (!structureEquals(two[twoKey], one[twoKey], level + 1)) {
91 + allInTwoAreInOne = false;
92 + }
93 + }
94 + // Sure, the computations overlap a bit.
95 + return allInOneAreInTwo && allInTwoAreInOne;
96 + } else {
97 + return false;
98 + }
99 +};
100 +
101 +
102 +var makeCircular = function() {
103 + var obj = {};
104 + obj.self = obj;
105 + return obj;
106 +};
107 +
108 +
109 +
110 +/**
111 + * Not a symmetric function. Checks if there are any node references
112 + * (nonTerminal [non-string, non-null, etc]) that exist in inThis. See
113 + * sharesOnlyTerminalMemory.
114 + */
115 +var hasNoNodeReferencesIn = function(obj, inThis) {
116 + traverseUntilLeaves(obj, function(nonTerm) {
117 + traverseUntilLeaves(inThis, function(inThisNonTerm) {
118 + if (inThisNonTerm === nonTerm) {
119 + return false;
120 + }
121 + });
122 + });
123 + return true;
124 +};
125 +
126 +/**
127 + * Checks that two objects share no memory references with each other (except
128 + * possibly at the terminal nodes (such as strings etc)). This is used to test
129 + * isolation of memory. When certain merge operations occur we need to ensure
130 + * that we did not leak references across objects.
131 + */
132 +var sharesOnlyTerminalMemory = function(a, b) {
133 + return hasNoNodeReferencesIn(a, b) && hasNoNodeReferencesIn(b, a);
134 +};
135 +
136 +/**
137 + * Tests all of the merging functions. You provide it data that describes what
138 + * a deep merge looks like and what a shallow merge looks like. It will make
139 + * sure that various input/output contracts are adhered to, in addition to
140 + * ensuring that various guarantees about side-effects are upheld.
141 + * The test data must provide functions createOne/createTwo as a means to
142 + * create test data for objects one/two respsectively - each time these
143 + * functions must return pristine copies so that we can test for side effects
144 + * correctly.
145 + */
146 +var testAllMerges = function(testData) {
147 + var createOne = testData.createOne;
148 + var createTwo = testData.createTwo;
149 + var one;
150 + var two;
151 +
152 + /*
153 + * Test merge()
154 + */
155 + one = createOne();
156 + two = createTwo();
157 + if (testData.mergeShouldThrow) {
158 + expect(function() {
159 + merge(one, two);
160 + }).toThrow();
161 + } else {
162 + var mergeRes = merge(one, two);
163 + if (testData.shallowResultShouldBe) {
164 + expect(structureEquals(
165 + mergeRes,
166 + testData.shallowResultShouldBe)
167 + ).toBe(true);
168 + }
169 + // Ensure the result is not the exact same object memory reference.
170 + expect(mergeRes === one).toBe(false);
171 + }
172 +
173 + /*
174 + * Test mergeInto()
175 + */
176 + one = createOne();
177 + two = createTwo();
178 + if (testData.mergeIntoShouldThrow) {
179 + expect(function() {
180 + mergeInto(one, two);
181 + }).toThrow();
182 + } else {
183 + var mergeIntoRes = mergeInto(one, two);
184 + if (testData.shallowResultShouldBe) {
185 + expect(structureEquals(
186 + one,
187 + testData.shallowResultShouldBe)
188 + ).toBe(true);
189 + }
190 + /*
191 + * Ensure it's the exact same object reference
192 + * TODO: We should ensure that for every object path p in one points to
193 + * the original object reference in one before the mutation.
194 + */
195 + expect(mergeIntoRes === undefined).toBe(true);
196 + }
197 +
198 + /*
199 + * Test mergeDeep()
200 + */
201 + one = createOne();
202 + two = createTwo();
203 + if (testData.mergeDeepShouldThrow) {
204 + expect(function() {
205 + mergeDeep(one, two, testData.arrayStrategy);
206 + }).toThrow();
207 + } else {
208 + var mergeDeepRes = mergeDeep(one, two, testData.arrayStrategy);
209 + if (testData.deepResultShouldBe) {
210 + expect(structureEquals(
211 + mergeDeepRes,
212 + testData.deepResultShouldBe)
213 + ).toBe(true);
214 + }
215 + expect(sharesOnlyTerminalMemory(mergeDeepRes, one)).toBe(true);
216 + expect(sharesOnlyTerminalMemory(mergeDeepRes, two)).toBe(true);
217 + }
218 +
219 + /*
220 + * Test mergeDeepInto()
221 + */
222 + one = createOne();
223 + two = createTwo();
224 + if (testData.mergeDeepIntoShouldThrow) {
225 + expect(function() {
226 + mergeDeepInto(one, two, testData.arrayStrategy);
227 + }).toThrow();
228 + } else {
229 + var mergeDeepIntoRes = mergeDeepInto(one, two, testData.arrayStrategy);
230 + expect(mergeDeepIntoRes).toBe(undefined);
231 + if (testData.deepResultShouldBe) {
232 + expect(structureEquals(
233 + one,
234 + testData.deepResultShouldBe)
235 + ).toBe(true);
236 + }
237 + expect(mergeDeepIntoRes === undefined).toBe(true);
238 + expect(sharesOnlyTerminalMemory(one, two)).toBe(true);
239 + }
240 +
241 +};
242 +
243 +/**
244 + * ReactTextWithEntities is not a React component, but rather a helper function
245 + * that returns an array. You can use that array as a helper function when
246 + * constructing a structure.
247 + */
248 +describe('mergeFuncs', function() {
249 + beforeEach(function() {
250 + require('mock-modules').dumpCache();
251 + mergeHelpers = require('mergeHelpers');
252 + merge = require('merge');
253 + mergeInto = require('mergeInto');
254 + mergeDeep = require('mergeDeep');
255 + mergeDeepInto = require('mergeDeepInto');
256 + isTerminal = mergeHelpers.isTerminal;
257 + MAX_MERGE_DEPTH = mergeHelpers.MAX_MERGE_DEPTH;
258 + });
259 +
260 + var twoLevelsDeep = function() {
261 + return {
262 + shallowObject: {
263 + deepObject: {
264 + a: 'a',
265 + b: 'b'
266 + },
267 + deepObject2: {
268 + one: 'one',
269 + two: 'two'
270 + }
271 + }
272 + };
273 + };
274 +
275 + it('should not throw if first argument is circular', function() {
276 + testAllMerges({
277 + createOne: makeCircular,
278 + createTwo: function() { return {thisOneIsNotCircular: true}; },
279 + mergeShouldThrow: false,
280 + mergeIntoShouldThrow: false,
281 + mergeDeepShouldThrow: true,
282 + mergeDeepIntoShouldThrow: false
283 + });
284 + });
285 +
286 + it('should throw if second argument is circular', function() {
287 + testAllMerges({
288 + createOne: function() { return {thisOneIsNotCircular: true}; },
289 + createTwo: makeCircular,
290 + mergeShouldThrow: false,
291 + mergeIntoShouldThrow: false,
292 + mergeDeepShouldThrow: true,
293 + mergeDeepIntoShouldThrow: true
294 + });
295 + });
296 +
297 + it('should throw if something deep is circular in second param', function() {
298 + testAllMerges({
299 + createOne: function() { return {thisOneIsNotCircular: true}; },
300 + createTwo: function() {
301 + return {
302 + deeper: {
303 + deeper: makeCircular()
304 + }
305 + };
306 + },
307 + mergeShouldThrow: false,
308 + mergeIntoShouldThrow: false,
309 + mergeDeepShouldThrow: true,
310 + mergeDeepIntoShouldThrow: true
311 + });
312 + });
313 +
314 + it('should throw if something deep is circular in first param', function() {
315 + testAllMerges({
316 + createOne: function() {
317 + return {
318 + deeper: {
319 + deeper: makeCircular()
320 + }
321 + };
322 + },
323 + createTwo: function() { return {thisOneIsNotCircular: true}; },
324 + mergeShouldThrow: false,
325 + mergeIntoShouldThrow: false,
326 + mergeDeepShouldThrow: true,
327 + mergeDeepIntoShouldThrow: false
328 + });
329 + });
330 +
331 +
332 + it('should throw exceptions on invalid data/params', function() {
333 + testAllMerges({
334 + createOne: function() { return []; },
335 + createTwo: function() { return {}; },
336 + mergeShouldThrow: true,
337 + mergeIntoShouldThrow: true,
338 + mergeDeepShouldThrow: true,
339 + mergeDeepIntoShouldThrow: true
340 + });
341 + testAllMerges({
342 + createOne: function() { return {}; },
343 + createTwo: function() { return []; },
344 + mergeShouldThrow: true,
345 + mergeIntoShouldThrow: true,
346 + mergeDeepShouldThrow: true,
347 + mergeDeepIntoShouldThrow: true
348 + });
349 + testAllMerges({
350 + createOne: function() { return []; },
351 + createTwo: function() { return {hasDeepStructure: 'yes'}; },
352 + mergeShouldThrow: true,
353 + mergeIntoShouldThrow: true,
354 + mergeDeepShouldThrow: true,
355 + mergeDeepIntoShouldThrow: true
356 + });
357 + testAllMerges({
358 + createOne: function() { return {hasDeepStructure: 'yes'}; },
359 + createTwo: function() { return []; },
360 + mergeShouldThrow: true,
361 + mergeIntoShouldThrow: true,
362 + mergeDeepShouldThrow: true,
363 + mergeDeepIntoShouldThrow: true
364 + });
365 + testAllMerges({
366 + createOne: function() { return {hasDeepStructure: 'yes'}; },
367 + createTwo: function() { return []; },
368 + mergeShouldThrow: true,
369 + mergeIntoShouldThrow: true,
370 + mergeDeepShouldThrow: true,
371 + mergeDeepIntoShouldThrow: true
372 + });
373 + /*
374 + * mergeInto functions do not accept an empty first parameter. There would
375 + * be nothing to mutate!
376 + */
377 + testAllMerges({
378 + createOne: function() { return null; },
379 + createTwo: function() { return {field: 'yes'}; },
380 + mergeIntoShouldThrow: true,
381 + mergeDeepIntoShouldThrow: true,
382 + shallowResultShouldBe: {field: 'yes'},
383 + deepResultShouldBe: {field: 'yes'}
384 + });
385 + testAllMerges({
386 + createOne: function() { return undefined; },
387 + createTwo: function() { return {field: 'yes'}; },
388 + mergeIntoShouldThrow: true,
389 + mergeDeepIntoShouldThrow: true,
390 + shallowResultShouldBe: {field: 'yes'},
391 + deepResultShouldBe: {field: 'yes'}
392 + });
393 + // Every merge function accepts an empty second parameter.
394 + testAllMerges({
395 + createOne: function() { return {field: 'yes'}; },
396 + createTwo: function() { return null; },
397 + shallowResultShouldBe: {field: 'yes'},
398 + deepResultShouldBe: {field: 'yes'}
399 + });
400 + testAllMerges({
401 + createOne: function() { return null; },
402 + createTwo: function() { return null; },
403 + deepResultShouldBe: {},
404 + shallowResultShouldBe: {},
405 + mergeIntoShouldThrow: true,
406 + mergeDeepIntoShouldThrow: true
407 + });
408 + testAllMerges({
409 + createOne: function() { return {field: 'yes'}; },
410 + createTwo: function() { return undefined; },
411 + shallowResultShouldBe: {field: 'yes'},
412 + deepResultShouldBe: {field: 'yes'}
413 + });
414 + testAllMerges({
415 + createOne: function() { return 0; },
416 + createTwo: function() { return {field: 'yes'}; },
417 + mergeShouldThrow: true,
418 + mergeIntoShouldThrow: true,
419 + mergeDeepShouldThrow: true,
420 + mergeDeepIntoShouldThrow: true
421 + });
422 + /*
423 + * The fact that the second param is empty shouldn't change that we still
424 + * error on arrays.
425 + */
426 + testAllMerges({
427 + createOne: function() { return []; },
428 + createTwo: function() { return undefined; },
429 + mergeShouldThrow: true,
430 + mergeIntoShouldThrow: true,
431 + mergeDeepShouldThrow: true,
432 + mergeDeepIntoShouldThrow: true
433 + });
434 + testAllMerges({
435 + createOne: function() { return {field: 'yes'}; },
436 + createTwo: function() { return null; },
437 + shallowResultShouldBe: {field: 'yes'},
438 + deepResultShouldBe: {field: 'yes'}
439 + });
440 + });
441 +
442 +
443 + it('should merge very deep objects correctly.',
444 + function() {
445 + testAllMerges({
446 + createOne: function() {
447 + return {
448 + shouldBeClobberedByNull: {
449 + color: 'blue'
450 + },
451 + shouldGetTwoFieldsWhenDeepMerge: {
452 + firstField: { color: 'red' }
453 + },
454 + shouldRemainUndefined: undefined,
455 + shouldRemainFour: 4,
456 + shouldRemainZero: 0,
457 + shouldBeClobberedByObj: 0
458 + };
459 + },
460 + createTwo: function() {
461 + return {
462 + shouldBeClobberedByNull: null,
463 + shouldGetTwoFieldsWhenDeepMerge: {
464 + secondField: { color: 'black' }
465 + },
466 + shouldBeClobberedByObj: { thisObj: true }
467 + };
468 + },
469 + shallowResultShouldBe: {
470 + shouldBeClobberedByNull: null,
471 + // only gets one field when merged shallowly
472 + shouldGetTwoFieldsWhenDeepMerge: {
473 + secondField: { color: 'black' }
474 + },
475 + shouldRemainUndefined: undefined,
476 + shouldRemainFour: 4,
477 + shouldRemainZero: 0,
478 + shouldBeClobberedByObj: { thisObj: true }
479 + },
480 + deepResultShouldBe: {
481 + shouldRemainUndefined: undefined,
482 + shouldRemainFour: 4,
483 + shouldRemainZero: 0,
484 + shouldBeClobberedByNull: null,
485 + shouldGetTwoFieldsWhenDeepMerge: {
486 + firstField: { color: 'red' },
487 + secondField: { color: 'black' }
488 + },
489 + shouldBeClobberedByObj: { thisObj: true }
490 + }
491 + });
492 + }
493 + );
494 +
495 + it(
496 + 'should not throw with one non-colliding arrays with no arrayStrategy',
497 + function() {
498 + testAllMerges({
499 + createOne: function() { return { hasDeepStructure: 'yes' }; },
500 + createTwo: function() { return { hasArrayDeeper: [] }; },
501 + mergeShouldThrow: false,
502 + mergeIntoShouldThrow: false,
503 + mergeDeepShouldThrow: false,
504 + mergeDeepIntoShouldThrow: false,
505 + shallowResultShouldBe: {
506 + hasDeepStructure: 'yes',
507 + hasArrayDeeper: []
508 + },
509 + deepResultShouldBe: {
510 + hasDeepStructure: 'yes',
511 + hasArrayDeeper: []
512 + }
513 + });
514 + }
515 + );
516 +
517 + it(
518 + 'should not throw with two non-colliding arrays with no arrayStrategy',
519 + function() {
520 + testAllMerges({
521 + createOne: function() {
522 + return {
523 + arrayOne: [1]
524 + };
525 + },
526 + createTwo: function() {
527 + return {
528 + arrayTwo: [2]
529 + };
530 + },
531 + arrayStrategy: mergeHelpers.ArrayStrategies.Clobber,
532 + mergeShouldThrow: false,
533 + mergeIntoShouldThrow: false,
534 + mergeDeepShouldThrow: false,
535 + mergeDeepIntoShouldThrow: false
536 + });
537 + }
538 + ),
539 +
540 + it('should throw if no arrayStrategy supplied on Array merges', function() {
541 + testAllMerges({
542 + createOne: function() {
543 + return {
544 + arrayOne: [1]
545 + };
546 + },
547 + createTwo: function() {
548 + return {
549 + arrayOne: [9]
550 + };
551 + },
552 + shallowResultShouldBe: {
553 + arrayOne: [9]
554 + },
555 + // Doesn't throw on the shallow versions.
556 + mergeShouldThrow: false,
557 + mergeIntoShouldThrow: false,
558 + mergeDeepShouldThrow: true,
559 + mergeDeepIntoShouldThrow: true
560 + });
561 + }),
562 +
563 + it('should not throw if never forced to merge two Arrays', function() {
564 + testAllMerges({
565 + createOne: function() {
566 + return {
567 + arrayOne: [1]
568 + };
569 + },
570 + createTwo: function() {
571 + return {
572 + arrayTwo: [9] // <-- See, never forced to merge with arrayOne
573 + };
574 + },
575 + shallowResultShouldBe: {
576 + arrayOne: [1],
577 + arrayTwo: [9]
578 + },
579 + // Doesn't throw on the shallow versions.
580 + mergeShouldThrow: false,
581 + mergeIntoShouldThrow: false,
582 + mergeDeepShouldThrow: false,
583 + mergeDeepIntoShouldThrow: false
584 + });
585 + }),
586 +
587 + it(
588 + 'should correctly deep merge Arrays with arrayStrategy clobber',
589 + function() {
590 + testAllMerges({
591 + createOne: function() {
592 + return {
593 + deep: {
594 + array: [1],
595 + nonArray: {
596 + hi: 'hi'
597 + }
598 + }
599 + };
600 + },
601 + createTwo: function() {
602 + return {
603 + deep: {
604 + array: [10,11, {deepFieldInArray: 'yes'}],
605 + nonArray: {
606 + bye: 'bye'
607 + }
608 + }
609 + };
610 + },
611 + shallowResultShouldBe: {
612 + deep: {
613 + array: [10,11, {deepFieldInArray: 'yes'}],
614 + nonArray: {
615 + bye: 'bye'
616 + }
617 + }
618 + },
619 + deepResultShouldBe: {
620 + deep: {
621 + array: [10,11, {deepFieldInArray: 'yes'}],
622 + nonArray: {
623 + hi: 'hi',
624 + bye: 'bye'
625 + }
626 + }
627 + },
628 + arrayStrategy: mergeHelpers.ArrayStrategies.Clobber,
629 + mergeShouldThrow: false,
630 + mergeIntoShouldThrow: false,
631 + mergeDeepShouldThrow: false,
632 + mergeDeepIntoShouldThrow: false
633 + });
634 + }
635 + ),
636 +
637 +
638 + it(
639 + 'should correctly deep merge Arrays with arrayStrategy indexByIndex',
640 + function() {
641 + testAllMerges({
642 + createOne: function() {
643 + return {
644 + deep: {
645 + array: [{
646 + atIndexZero: {
647 + indexZeroGreeting: 'You Are At Index Zero',
648 + indexZeroStatus: false
649 + }
650 + }]
651 + }
652 + };
653 + },
654 + createTwo: function() {
655 + return {
656 + deep: {
657 + array: [{
658 + atIndexZero: {
659 + indexZeroStatus: true,
660 + newFieldAtIndexZero: 'newField'
661 + }
662 + },
663 + {
664 + atIndexOne: {
665 + nothingOriginallyAtThisIndex: true
666 + }
667 + }]
668 + }
669 + };
670 + },
671 + shallowResultShouldBe: {
672 + deep: {
673 + array: [{
674 + atIndexZero: {
675 + indexZeroStatus: true,
676 + newFieldAtIndexZero: 'newField'
677 + }
678 + },
679 + {
680 + atIndexOne: {
681 + nothingOriginallyAtThisIndex: true
682 + }
683 + }]
684 + }
685 + },
686 + deepResultShouldBe: {
687 + deep: {
688 + array: [{
689 + atIndexZero: {
690 + indexZeroGreeting: 'You Are At Index Zero', // <-- See!?
691 + indexZeroStatus: true,
692 + newFieldAtIndexZero: 'newField'
693 + }
694 + },
695 + {
696 + atIndexOne: {
697 + nothingOriginallyAtThisIndex: true
698 + }
699 + }]
700 + }
701 + },
702 + arrayStrategy: mergeHelpers.ArrayStrategies.IndexByIndex,
703 + mergeShouldThrow: false,
704 + mergeIntoShouldThrow: false,
705 + mergeDeepShouldThrow: false,
706 + mergeDeepIntoShouldThrow: false
707 + }
708 + );
709 + }),
710 +
711 + it('should merge deep data correctly', function() {
712 + var mergeData = {
713 + shallowObject: {
714 + deepObject: {
715 + c: 'c',
716 + d: 'd'
717 + },
718 + deepObject2: {
719 + three: 'three',
720 + four: 'four'
721 + }
722 + }
723 + };
724 + var deepResult = {
725 + shallowObject: {
726 + deepObject: {
727 + a: 'a',
728 + b: 'b',
729 + c: 'c',
730 + d: 'd'
731 + },
732 + deepObject2: {
733 + one: 'one',
734 + two: 'two',
735 + three: 'three',
736 + four: 'four'
737 + }
738 + }
739 + };
740 + var shallowResult = mergeData; // The same
741 +
742 + testAllMerges({
743 + createOne: function() { return twoLevelsDeep(); },
744 + createTwo: function() { return mergeData; },
745 + mergeShouldThrow: false,
746 + mergeIntoShouldThrow: false,
747 + mergeDeepShouldThrow: false,
748 + mergeDeepIntoShouldThrow: false,
749 + deepResultShouldBe: deepResult,
750 + shallowResultShouldBe: shallowResult
751 + });
752 + });
753 +
754 + it('should fill in complementing regions of data trees', function() {
755 + var mergeData = {
756 + shallowObject: {
757 + newDepth: {
758 + hello: 'goodbye'
759 + }
760 + }
761 + };
762 + var deepResult = {
763 + shallowObject: {
764 + deepObject: {
765 + a: 'a',
766 + b: 'b'
767 + },
768 + deepObject2: {
769 + one: 'one',
770 + two: 'two'
771 + },
772 + // This is added!
773 + newDepth: {
774 + hello: 'goodbye'
775 + }
776 + }
777 + };
778 + var shallowResult = mergeData; // The same
779 +
780 + testAllMerges({
781 + createOne: function() { return twoLevelsDeep(); },
782 + createTwo: function() { return mergeData; },
783 + // The deep merge of the two
784 + deepResultShouldBe: deepResult,
785 + shallowResultShouldBe: shallowResult
786 + });
787 + });
788 +
789 + /**
790 + * Arrays and Objects wipe out terminals (and vice versa).
791 + */
792 + it('should clobber terminals with objects/arrays vice versa', function() {
793 + var makeOne = function() {
794 + return {
795 + thisFieldDoesNothing: {},
796 + deep: {
797 + // Objects
798 + thisShouldSwitchFromObjectToTerminal: {hello: 'goodbye'},
799 + thisShouldSwitchFromTerminalToObject: 'yes',
800 +
801 + // Arrays
802 + thisShouldSwitchFromArrayToTerminal: ['goodbye'],
803 + thisShouldSwitchFromTerminalToArray: 'yes'
804 + }
805 + };
806 + };
807 + var makeTwo = function() {
808 + return {
809 + deep: {
810 + // Objects
811 + thisShouldSwitchFromObjectToTerminal: 'terminal',
812 + thisShouldSwitchFromTerminalToObject: {nowItsAnObject: 'yay'},
813 +
814 + // Arrays
815 + thisShouldSwitchFromArrayToTerminal: 'terminal',
816 + thisShouldSwitchFromTerminalToArray: ['yay']
817 + }
818 + };
819 + };
820 + var deepResult = {
821 + thisFieldDoesNothing: {
822 +
823 + },
824 + deep: {
825 + // Objects
826 + thisShouldSwitchFromObjectToTerminal: 'terminal',
827 + thisShouldSwitchFromTerminalToObject: {nowItsAnObject: 'yay'},
828 +
829 + // Arrays
830 + thisShouldSwitchFromArrayToTerminal: 'terminal',
831 + thisShouldSwitchFromTerminalToArray: ['yay']
832 + }
833 + };
834 +
835 + testAllMerges({
836 + createOne: makeOne,
837 + createTwo: makeTwo,
838 + deepResultShouldBe: deepResult, // The deep merge of the two
839 + shallowResultShouldBe: deepResult // The same this time.
840 + });
841 + });
842 +
843 + /**
844 + * Arrays wipe out Objects and Objects wipe out Arrays.
845 + */
846 + it('should clobber terminals with objects/arrays vice versa', function() {
847 + var makeOne = function() {
848 + return {
849 + thisFieldDoesNothing: {},
850 + deep: {
851 + thisShouldSwitchFromObjectToArray: {hello: 'goodbye'},
852 + thisShouldSwitchFromArrayToObject: [1,2,3]
853 + }
854 + };
855 + };
856 + var makeTwo = function() {
857 + return {
858 + deep: {
859 + thisShouldSwitchFromObjectToArray: [7,8,9],
860 + thisShouldSwitchFromArrayToObject: {iUsedToBeAnArray:true}
861 + }
862 + };
863 + };
864 + var deepResult = {
865 + thisFieldDoesNothing: {},
866 + deep: {
867 + thisShouldSwitchFromObjectToArray: [7,8,9],
868 + thisShouldSwitchFromArrayToObject: {iUsedToBeAnArray:true}
869 + }
870 + };
871 +
872 + testAllMerges({
873 + createOne: makeOne,
874 + createTwo: makeTwo,
875 + deepResultShouldBe: deepResult, // The deep merge of the two
876 + shallowResultShouldBe: deepResult // The same this time.
877 + });
878 + });
879 +
880 + it(
881 + 'should not clobber terminals/objects/arrays that are not over-written',
882 + function() {
883 + testAllMerges({
884 + createOne: function() {
885 + return {
886 + deep: {
887 + terminal: 'I am a terminal',
888 + object: {iAmAnObject: true},
889 + array: [1,2,3]
890 + }
891 + };
892 + },
893 + createTwo: function() {
894 + return {
895 + deep: {
896 + completelyDifferentField: 'no collision here!'
897 + }
898 + };
899 + },
900 + shallowResultShouldBe: {
901 + deep: {
902 + completelyDifferentField: 'no collision here!'
903 + }
904 + },
905 + deepResultShouldBe: {
906 + deep: {
907 + completelyDifferentField: 'no collision here!',
908 + terminal: 'I am a terminal',
909 + object: {iAmAnObject: true},
910 + array: [1,2,3]
911 + }
912 + },
913 + mergeShouldThrow: false,
914 + mergeIntoShouldThrow: false,
915 + mergeDeepShouldThrow: false,
916 + mergeDeepIntoShouldThrow: false
917 + }
918 + );
919 + }),
920 +
921 + it('should correctly clobber terminals with cloned arrays', function() {
922 + testAllMerges({
923 + createOne: function() {
924 + return {
925 + deep: {
926 + changesFromTermToArray: 'I am a terminal'
927 + }
928 + };
929 + },
930 + createTwo: function() {
931 + return {
932 + deep: {
933 + changesFromTermToArray: ['I', 'am', 'an', 'array']
934 + }
935 + };
936 + },
937 + shallowResultShouldBe: {
938 + deep: {
939 + changesFromTermToArray: ['I', 'am', 'an', 'array']
940 + }
941 + },
942 + deepResultShouldBe: {
943 + deep: {
944 + changesFromTermToArray: ['I', 'am', 'an', 'array']
945 + }
946 + },
947 + mergeShouldThrow: false,
948 + mergeIntoShouldThrow: false,
949 + mergeDeepShouldThrow: false,
950 + mergeDeepIntoShouldThrow: false
951 + });
952 + }),
953 +
954 + it('should detect terminals correctly in merge* functions', function() {
955 + var boolResFalse = isTerminal(false);
956 + expect(boolResFalse).toBe(true);
957 +
958 + var boolResTrue = isTerminal(true);
959 + expect(boolResTrue).toBe(true);
960 +
961 + var numRes = isTerminal(123);
962 + expect(numRes).toBe(true);
963 +
964 + var zeroRes = isTerminal(0);
965 + expect(zeroRes).toBe(true);
966 +
967 + var stringRes = isTerminal('i am a string');
968 + expect(stringRes).toBe(true);
969 +
970 + var objLiteralRes = isTerminal({somethingHere: true});
971 + expect(objLiteralRes).toBe(false);
972 +
973 + var undefRes = isTerminal(undefined);
974 + expect(undefRes).toBe(true);
975 +
976 + var nullRes = isTerminal(null);
977 + expect(nullRes).toBe(true);
978 +
979 + var arrRes = isTerminal([]);
980 + expect(arrRes).toBe(false);
981 + });
982 +});
983 +
src/utils/__tests__/objFilter-test.js new
+88
@@ -0,0 +1,88 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +var objFilter = require('objFilter');
22 +
23 +describe('objFilter', function() {
24 +
25 + var obj = {
26 + A: 'apple',
27 + B: 'banana',
28 + C: 'coconut',
29 + D: 'durian',
30 + E: 'elderberry',
31 + F: 'fig',
32 + G: 'guava',
33 + H: 'hackberry'
34 + };
35 +
36 + it('should accept null', function() {
37 + var filtered = objFilter(null, function() {});
38 + expect(filtered).toBe(null);
39 + });
40 +
41 + it('should return true to create copy', function() {
42 + var filtered = objFilter(obj, function() {
43 + return true;
44 + });
45 + expect(filtered).not.toBe(obj);
46 + expect(filtered).toEqual(obj);
47 + });
48 +
49 + it('should return empty object for a falsey func', function() {
50 + var filtered = objFilter(obj, function() {
51 + return false;
52 + });
53 + expect(filtered).toEqual({});
54 + });
55 +
56 + it('should filter based on value', function() {
57 + var filtered = objFilter(obj, function(value) {
58 + return value.indexOf('berry') !== -1;
59 + });
60 + expect(filtered).toEqual({
61 + E: 'elderberry',
62 + H: 'hackberry'
63 + });
64 + });
65 +
66 + it('should filter based on key', function() {
67 + var filtered = objFilter(obj, function(value, key) {
68 + return (/[AEIOU]/).test(key);
69 + });
70 + expect(filtered).toEqual({
71 + A: 'apple',
72 + E: 'elderberry'
73 + });
74 + });
75 +
76 + it('should filter based on iteration', function() {
77 + var filtered = objFilter(obj, function(value, key, iteration) {
78 + return iteration % 2;
79 + });
80 + expect(filtered).toEqual({
81 + B: 'banana',
82 + D: 'durian',
83 + F: 'fig',
84 + H: 'hackberry'
85 + });
86 + });
87 +
88 +});
src/utils/__tests__/objMap-test.js new
+113
@@ -0,0 +1,113 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @emails react-core
17 + */
18 +
19 +"use strict";
20 +
21 +var objMap = require('objMap');
22 +
23 +describe('objMap', function() {
24 +
25 + var obj = {
26 + A: 'apple',
27 + B: 'banana',
28 + C: 'coconut',
29 + D: 'durian',
30 + E: 'elderberry',
31 + F: 'fig',
32 + G: 'guava',
33 + H: 'hackberry'
34 + };
35 +
36 + it('should accept null', function() {
37 + var mapped = objMap(null, function() {});
38 + expect(mapped).toBe(null);
39 + });
40 +
41 + it('should return value to create copy', function() {
42 + var mapped = objMap(obj, function(value) {
43 + return value;
44 + });
45 + expect(mapped).not.toBe(obj);
46 + expect(mapped).toEqual(obj);
47 + });
48 +
49 + it('should always retain keys', function() {
50 + var mapped = objMap(obj, function() {
51 + return null;
52 + });
53 + expect(mapped).toEqual({
54 + A: null,
55 + B: null,
56 + C: null,
57 + D: null,
58 + E: null,
59 + F: null,
60 + G: null,
61 + H: null
62 + });
63 + });
64 +
65 + it('should map values', function() {
66 + var mapped = objMap(obj, function (value) {
67 + return value.toUpperCase();
68 + });
69 + expect(mapped).toEqual({
70 + A: 'APPLE',
71 + B: 'BANANA',
72 + C: 'COCONUT',
73 + D: 'DURIAN',
74 + E: 'ELDERBERRY',
75 + F: 'FIG',
76 + G: 'GUAVA',
77 + H: 'HACKBERRY'
78 + });
79 + });
80 +
81 + it('should map keys', function() {
82 + var mapped = objMap(obj, function (value, key) {
83 + return key;
84 + });
85 + expect(mapped).toEqual({
86 + A: 'A',
87 + B: 'B',
88 + C: 'C',
89 + D: 'D',
90 + E: 'E',
91 + F: 'F',
92 + G: 'G',
93 + H: 'H'
94 + });
95 + });
96 +
97 + it('should map iterations', function() {
98 + var mapped = objMap(obj, function (value, key, iteration) {
99 + return iteration;
100 + });
101 + expect(mapped).toEqual({
102 + A: 0,
103 + B: 1,
104 + C: 2,
105 + D: 3,
106 + E: 4,
107 + F: 5,
108 + G: 6,
109 + H: 7
110 + });
111 + });
112 +
113 +});
src/utils/accumulate.js new
+70
@@ -0,0 +1,70 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule accumulate
17 + */
18 +
19 +"use strict";
20 +
21 +var throwIf = require('throwIf');
22 +
23 +var INVALID_ARGS = 'INVALID_ACCUM_ARGS';
24 +
25 +if (__DEV__) {
26 + INVALID_ARGS =
27 + 'accumulate requires non empty (non-null, defined) next ' +
28 + 'values. All arrays accumulated must not contain any empty items.';
29 +}
30 +
31 +/**
32 + * Accumulates items that must never be empty, into a result in a manner that
33 + * conserves memory - avoiding allocation of arrays until they are needed. The
34 + * accumulation may start and/or end up being a single element or an array
35 + * depending on the total count (if greater than one, an array is allocated).
36 + * Handles most common case first (starting with an empty current value and
37 + * acquiring one).
38 + * @returns {Accumulation} An accumulation which is either a single item or an
39 + * Array of items.
40 + */
41 +function accumulate(cur, next) {
42 + var curValIsEmpty = cur == null; // Will test for emptiness (null/undef)
43 + var nextValIsEmpty = next === null;
44 + if (__DEV__) {
45 + throwIf(nextValIsEmpty, INVALID_ARGS);
46 + }
47 + if (nextValIsEmpty) {
48 + return cur;
49 + } else {
50 + if (curValIsEmpty) {
51 + return next;
52 + } else {
53 + // Both are not empty. Warning: Never call x.concat(y) when you are not
54 + // certain that x is an Array (x could be a string with concat method).
55 + var curIsArray = Array.isArray(cur);
56 + var nextIsArray = Array.isArray(next);
57 + if (curIsArray) {
58 + return cur.concat(next);
59 + } else {
60 + if (nextIsArray) {
61 + return [cur].concat(next);
62 + } else {
63 + return [cur, next];
64 + }
65 + }
66 + }
67 + }
68 +}
69 +
70 +module.exports = accumulate;
src/utils/bindNoArgs.js new
+30
@@ -0,0 +1,30 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule bindNoArgs
17 + */
18 +
19 +"use strict";
20 +
21 +var bindNoArgs = function (func, context) {
22 + if (!func) {
23 + return null;
24 + }
25 + return function () {
26 + return func.call(context);
27 + };
28 +};
29 +
30 +module.exports = bindNoArgs;
src/utils/curryOnly.js new
+37
@@ -0,0 +1,37 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule curryOnly
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * When the function who's first parameter you are currying accepts only a
23 + * single argument, and you want to curry it, use this function for performance
24 + * reasons, as it will never access 'arguments'. It would be an interesting
25 + * project to detect at static analysis time, calls to F.curry that could be
26 + * transformed to one of the two optimized versions seen here.
27 + */
28 +var curryOnly = function(func, val, context) {
29 + if (!func) {
30 + return null;
31 + }
32 + return function() {
33 + return func.call(context, val);
34 + };
35 +};
36 +
37 +module.exports = curryOnly;
src/utils/eachKeyVal.js new
+39
@@ -0,0 +1,39 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule eachKeyVal
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * Invokes fun for each own property in obj. Invokes `fun(key, val, obj, i)`.
23 + * @param {?Object} obj The object to iterate over.
24 + * @param {?Function} fun The function to invoke.
25 + * @param {?context=} context The context to call from.
26 + */
27 +function eachKeyVal(obj, fun, context) {
28 + if (!obj || !fun) {
29 + return;
30 + }
31 + // Object.keys only returns the "own" properties.
32 + var objKeys = Object.keys(obj);
33 + var i;
34 + for (i=0; i < objKeys.length; i++) {
35 + fun.call(context, objKeys[i], obj[objKeys[i]], obj, i);
36 + }
37 +}
38 +
39 +module.exports = eachKeyVal;
src/utils/escapeTextForBrowser.js new
+61
@@ -0,0 +1,61 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule escapeTextForBrowser
17 + */
18 +
19 +"use strict";
20 +
21 +var throwIf = require('throwIf');
22 +
23 +var ESCAPE_TYPE_ERR;
24 +
25 +if (__DEV__) {
26 + ESCAPE_TYPE_ERR =
27 + 'The React core has attempted to escape content that is of a ' +
28 + 'mysterious type (object etc) Escaping only works on numbers and strings';
29 +}
30 +
31 +var ESCAPE_LOOKUP = {
32 + "&": "&amp;",
33 + ">": "&gt;",
34 + "<": "&lt;",
35 + "\"": "&quot;",
36 + "'": "&#x27;",
37 + "/": "&#x2f;"
38 +};
39 +
40 +function escaper(match) {
41 + return ESCAPE_LOOKUP[match];
42 +}
43 +
44 +var escapeTextForBrowser = function (text) {
45 + var type = typeof text;
46 + var invalid = type === 'object';
47 + if (__DEV__) {
48 + throwIf(invalid, ESCAPE_TYPE_ERR);
49 + }
50 + if (text === '' || invalid) {
51 + return '';
52 + } else {
53 + if (type === 'string') {
54 + return text.replace(/[&><"'\/]/g, escaper);
55 + } else {
56 + return (''+text).replace(/[&><"'\/]/g, escaper);
57 + }
58 + }
59 +};
60 +
61 +module.exports = escapeTextForBrowser;
src/utils/flattenChildren.js new
+93
@@ -0,0 +1,93 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule flattenChildren
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactTextComponent = require('ReactTextComponent');
22 +
23 +var escapeTextForBrowser = require('escapeTextForBrowser');
24 +
25 +var throwIf = require('throwIf');
26 +
27 +/**
28 + * @polyFill Array.isArray
29 + */
30 +
31 +
32 +var INVALID_CHILD = 'INVALID_CHILD';
33 +if (__DEV__) {
34 + INVALID_CHILD =
35 + 'You may not pass a child of that type to a React component. It ' +
36 + 'is a common mistake to try to pass a standard browser DOM element ' +
37 + 'as a child of a React component.';
38 +}
39 +
40 +/**
41 + * If there is only a single child, it still needs a name.
42 + */
43 +var ONLY_CHILD_NAME = '0';
44 +
45 +var flattenChildrenImpl = function(res, children, nameSoFar) {
46 + if (Array.isArray(children)) {
47 + for (var i = 0; i < children.length; i++) {
48 + flattenChildrenImpl(res, children[i], nameSoFar + '[' + i + ']');
49 + }
50 + } else {
51 + var type = typeof children;
52 + var isOnlyChild = nameSoFar === '';
53 + var storageName = isOnlyChild ? ONLY_CHILD_NAME : nameSoFar;
54 + if (children === null || children === undefined || type === 'boolean') {
55 + res[storageName] = null;
56 + } else if (children.mountComponentIntoNode) {
57 + /* We found a component instance */
58 + res[storageName] = children;
59 + } else {
60 + if (type === 'object') {
61 + throwIf(children && children.nodeType === 1, INVALID_CHILD);
62 + for (var key in children) {
63 + if (children.hasOwnProperty(key)) {
64 + flattenChildrenImpl(
65 + res,
66 + children[key],
67 + nameSoFar + '{' + escapeTextForBrowser(key) + '}'
68 + );
69 + }
70 + }
71 + } else if (type === 'string') {
72 + res[storageName] = new ReactTextComponent(children);
73 + } else if (type === 'number') {
74 + res[storageName] = new ReactTextComponent('' + children);
75 + }
76 + }
77 + }
78 +};
79 +
80 +/**
81 + * Flattens children that are typically specified as `props.children`.
82 + * @return {!Object} flattened children keyed by name.
83 + */
84 +function flattenChildren(children) {
85 + if (children === null || children === undefined) {
86 + return children;
87 + }
88 + var result = {};
89 + flattenChildrenImpl(result, children, '');
90 + return result;
91 +}
92 +
93 +module.exports = flattenChildren;
src/utils/forEachAccumulated.js new
+36
@@ -0,0 +1,36 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule forEachAccumulated
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * @param {array} an "accumulation" of items which is either an Array or
23 + * a single item. Useful when paired with the `accumulate` module. This is a
24 + * simple utility that allows us to reason about a collection of items, but
25 + * handling the case when there is exactly one item (and we do not need to
26 + * allocate an array).
27 + */
28 +var forEachAccumulated = function(arr, cb, scope) {
29 + if (Array.isArray(arr)) {
30 + arr.forEach(cb, scope);
31 + } else if (arr) {
32 + cb.call(scope, arr);
33 + }
34 +};
35 +
36 +module.exports = forEachAccumulated;
src/utils/joinClasses.js new
+44
@@ -0,0 +1,44 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule joinClasses
17 + * @typechecks
18 + */
19 +
20 +"use strict";
21 +
22 +/**
23 + * Combines multiple className strings into one.
24 + * http://jsperf.com/joinclasses-args-vs-array
25 + *
26 + * @param {...?string} classes
27 + * @return {string}
28 + */
29 +function joinClasses(className/*, ... */) {
30 + if (!className) {
31 + className = '';
32 + }
33 + var nextClass;
34 + var argLength = arguments.length;
35 + if (argLength > 1) {
36 + for (var ii = 1; ii < argLength; ii++) {
37 + nextClass = arguments[ii];
38 + nextClass && (className += ' ' + nextClass);
39 + }
40 + }
41 + return className;
42 +}
43 +
44 +module.exports = joinClasses;
src/utils/keyMirror.js new
+56
@@ -0,0 +1,56 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule keyMirror
17 + */
18 +
19 +"use strict";
20 +
21 +var throwIf = require('throwIf');
22 +
23 +var NOT_OBJECT_ERROR = 'NOT_OBJECT_ERROR';
24 +if (__DEV__) {
25 + NOT_OBJECT_ERROR = 'keyMirror only works on objects';
26 +}
27 +
28 +/**
29 + * Utility for constructing enums with keys being equal to the associated
30 + * values, even when using advanced key crushing. This is useful for debugging,
31 + * but also for using the values themselves as lookups into the enum.
32 + * Example:
33 + * var COLORS = keyMirror({blue: null, red: null});
34 + * var myColor = COLORS.blue;
35 + * var isColorValid = !!COLORS[myColor]
36 + * The last line could not be performed if the values of the generated enum were
37 + * not equal to their keys.
38 + * Input: {key1: val1, key2: val2}
39 + * Output: {key1: key1, key2: key2}
40 + */
41 +var keyMirror = function(obj) {
42 + var ret = {};
43 + var key;
44 +
45 + throwIf(!(obj instanceof Object) || Array.isArray(obj), NOT_OBJECT_ERROR);
46 +
47 + for (key in obj) {
48 + if (!obj.hasOwnProperty(key)) {
49 + continue;
50 + }
51 + ret[key] = key;
52 + }
53 + return ret;
54 +};
55 +
56 +module.exports = keyMirror;
src/utils/keys.js new
+34
@@ -0,0 +1,34 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule reactKeys
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * Similar to Object.keys() but does not fail on empty parameter.
23 + */
24 +var keys = function(obj) {
25 + var ret = [], aKey;
26 + for (aKey in obj) {
27 + if (obj.hasOwnProperty(aKey)) {
28 + ret.push(aKey);
29 + }
30 + }
31 + return ret;
32 +};
33 +
34 +module.exports = keys;
src/utils/memoizeStringOnly.js new
+39
@@ -0,0 +1,39 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule memoizeStringOnly
17 + * @typechecks
18 + */
19 +
20 +"use strict";
21 +
22 +/**
23 + * Memoizes the return value of a function that accepts one string argument.
24 + *
25 + * @param {function} callback
26 + * @return {function}
27 + */
28 +function memoizeStringOnly(callback) {
29 + var cache = {};
30 + return function(string) {
31 + if (cache.hasOwnProperty(string)) {
32 + return cache[string];
33 + } else {
34 + return cache[string] = callback.call(this, string);
35 + }
36 + };
37 +}
38 +
39 +module.exports = memoizeStringOnly;
src/utils/merge.js new
+37
@@ -0,0 +1,37 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule merge
17 + */
18 +
19 +"use strict";
20 +
21 +var mergeInto = require('mergeInto');
22 +
23 +/**
24 + * Shallow merges two structures into a return value, without mutating either.
25 + *
26 + * @param {?object} one Optional object with properties to merge from.
27 + * @param {?object} two Optional object with properties to merge from.
28 + * @return {object} The shallow extension of one by two.
29 + */
30 +var merge = function(one, two) {
31 + var result = {};
32 + mergeInto(result, one);
33 + mergeInto(result, two);
34 + return result;
35 +};
36 +
37 +module.exports = merge;
src/utils/mergeDeep.js new
+53
@@ -0,0 +1,53 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule mergeDeep
17 + */
18 +
19 +"use strict";
20 +
21 +var mergeHelpers = require('mergeHelpers');
22 +var mergeDeepInto = require('mergeDeepInto');
23 +
24 +var checkArrayStrategy = mergeHelpers.checkArrayStrategy;
25 +var checkMergeObjectArgs = mergeHelpers.checkMergeObjectArgs;
26 +var normalizeMergeArg = mergeHelpers.normalizeMergeArg;
27 +
28 +/**
29 + * @see mergeDeepImpl comments.
30 + *
31 + * @param {!Object} one returned will be an structural extension of this.
32 + * @param {!Object} two values from two take precedence over values in one.
33 + * @param {?Enum=} arrayStrategy One of `arrayStrategies`.
34 + * @return {!Object} the extension of one by two.
35 + */
36 +var mergeDeep = function(oneParam, twoParam, arrayStrategy) {
37 + var one = normalizeMergeArg(oneParam);
38 + var two = normalizeMergeArg(twoParam);
39 + checkMergeObjectArgs(one, two);
40 + checkArrayStrategy(arrayStrategy);
41 + var newObj = {};
42 + // TODO: This is horribly inefficient. Even when some deep object in `one`
43 + // will be clobbered by another entity in `two`, we perform a clone of that
44 + // entire structure. To make this code more efficient, we'll need to either
45 + // enhance `mergeDeepInto` to accept multiple arguments or mirror that code
46 + // here.
47 + mergeDeepInto(newObj, one, arrayStrategy);
48 + mergeDeepInto(newObj, two, arrayStrategy);
49 + return newObj;
50 +};
51 +
52 +
53 +module.exports = mergeDeep;
src/utils/mergeDeepInto.js new
+238
@@ -0,0 +1,238 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule mergeDeepInto
17 + */
18 +
19 +"use strict";
20 +
21 +var keyMirror = require('keyMirror');
22 +var mergeHelpers = require('mergeHelpers');
23 +var throwIf = require('throwIf');
24 +
25 +var ArrayStrategies = mergeHelpers.ArrayStrategies;
26 +var checkArrayStrategy = mergeHelpers.checkArrayStrategy;
27 +var checkMergeArrayArgs = mergeHelpers.checkMergeArrayArgs;
28 +var checkMergeLevel = mergeHelpers.checkMergeLevel;
29 +var checkMergeObjectArgs = mergeHelpers.checkMergeObjectArgs;
30 +var isTerminal = mergeHelpers.isTerminal;
31 +var normalizeMergeArg = mergeHelpers.normalizeMergeArg;
32 +
33 +var ERRORS = keyMirror({
34 + RUN_TIME_ARRAY_MERGE_FAIL: null
35 +});
36 +
37 +if (__DEV__) {
38 + ERRORS = {
39 + RUN_TIME_ARRAY_MERGE_FAIL:
40 + "The caller has not supplied an ArrayStrategy. This is supported as " +
41 + "long as the data structures being merged do not contain two Arrays " +
42 + "that must be merged together, which is exactly what has just " +
43 + "happened. Change the call site to supply an Array merge resolver."
44 + };
45 +}
46 +
47 +/**
48 + * Every deep merge function must handle merging in each of the following cases
49 + * at every level. We may refer to letters below in implementations. For each
50 + * case listed, the "Result" listed describes the *value* of the result, but
51 + * does not specify anything about the memory graph of the result. In other
52 + * words the Results listed below will be the same for `merge` and `mergeInto`
53 + * but `merge` may have different guarantees about mutation/cloning. In the
54 + * table, "Object" refers to a non- Array, non-terminal object. One result is
55 + * undefined and requires that the caller specify the resolution policy (only
56 + * when trying to merge two arrays).
57 + *
58 + * Scenario Result
59 + * --------------------------------------------------------------
60 + * [A]: (terminal, terminal) right terminal
61 + * [B]: (terminal, Array) right Array
62 + * [C]: (terminal, Object) right Object
63 + * [D]: (terminal, not present) left terminal
64 + * [E]: (Array, terminal) right terminal
65 + * [F]: (Array, Array) UNDEFINED - MUST SPECIFY POLICY
66 + * [G]: (Array, Object) right Object
67 + * [H]: (Array, not present) left Array
68 + * [I]: (Object, terminal) right terminal
69 + * [J]: (Object, Array) right Array
70 + * [K]: (Object, Object) merge of left and right Objects
71 + * [L]: (Object, not present) left Object
72 + * [M]: (not present, terminal) right terminal
73 + * [N]: (not present, Array) right Array
74 + * [O]: (not present, Object) right Object
75 + *
76 + * All merge functions are only expected to reason about "own" properties and,
77 + * any prototypical properties should have no effect on the result. At the first
78 + * level of recursion in deep merges, we may choose to normalize arguments
79 + * (convert undefined to empty objects etc.) The above chart does not describe
80 + * the result of those top level operations which behave specially due to the
81 + * normalization.
82 + */
83 +
84 +/**
85 + * Deep merge implementation for non-Array, non-terminal Objects.
86 + *
87 + * @param {!Object} one non-Array, non-terminal Object to be mutated deeply.
88 + * @param {!Object} two non-Array, non-terminal taking precedence over `one`.
89 + * @param {?Enum=} arrayStrategy one of `arrayStrategies`.
90 + * @param {!number} level The level of recursion.
91 + */
92 +var mergeDeepIntoObjects = function(one, two, arrayStrategy, level) {
93 + checkMergeObjectArgs(one, two);
94 + checkMergeLevel(level);
95 + var twoKeys = two ? Object.keys(two) : [];
96 + for (var i = 0; i < twoKeys.length; i++) {
97 + var twoKey = twoKeys[i];
98 + mergeSingleFieldDeep(one, two, twoKey, arrayStrategy, level);
99 + }
100 +};
101 +
102 +/**
103 + * Deep merge implementation for Arrays.
104 + *
105 + * @param {!Array} one Array to deep merge.
106 + * @param {!Array} two Array to deep merge "into" `one`.
107 + * @param {?Enum=} arrayStrategy one of `arrayStrategies`.
108 + * @param {!number} level Level of recursion.
109 + */
110 +var mergeDeepIntoArrays = function(one, two, arrayStrategy, level) {
111 + checkMergeArrayArgs(one, two);
112 + checkMergeLevel(level);
113 +
114 + var maxLen = Math.max(one.length, two.length);
115 + for (var i = 0; i < maxLen; i++) {
116 + mergeSingleFieldDeep(one, two, i, arrayStrategy, level);
117 + }
118 +};
119 +
120 +/**
121 + * Given two truthy containers `one` and `two`, and a `key`, performs a deep
122 + * merge on a logical field.
123 + *
124 + * @param {!Array|Object} one Container to merge into.
125 + * @param {!Array|Object} two Container to merge from.
126 + * @param {!string} key Key of field that should be merged.
127 + * @param {lEnum=} arrayStrategy One of `arrayStrategies`.
128 + * @param {!number} level Current level of recursion.
129 + */
130 +var mergeSingleFieldDeep = function(one, two, key, arrayStrategy, level) {
131 + var twoVal = two[key];
132 + var twoValIsPresent = two.hasOwnProperty(key);
133 + var twoValIsTerminal = twoValIsPresent && isTerminal(twoVal);
134 + var twoValIsArray = twoValIsPresent && Array.isArray(twoVal);
135 + var twoValIsProperObject =
136 + twoValIsPresent && !twoValIsArray && !twoValIsArray;
137 +
138 + var oneVal = one[key];
139 + var oneValIsPresent = one.hasOwnProperty(key);
140 + var oneValIsTerminal = oneValIsPresent && isTerminal(oneVal);
141 + var oneValIsArray = oneValIsPresent && Array.isArray(oneVal);
142 + var oneValIsProperObject =
143 + oneValIsPresent && !oneValIsArray && !oneValIsArray;
144 +
145 + if (oneValIsTerminal) {
146 + if (twoValIsTerminal) { // [A]
147 + one[key] = twoVal;
148 + } else if (twoValIsArray) { // [B]
149 + one[key] = [];
150 + mergeDeepIntoArrays(one[key], twoVal, arrayStrategy, level + 1);
151 + } else if (twoValIsProperObject) { // [C]
152 + one[key] = {};
153 + mergeDeepIntoObjects(one[key], twoVal, arrayStrategy, level + 1);
154 + } else if (!twoValIsPresent) { // [D]
155 + one[key] = oneVal;
156 + }
157 + } else if (oneValIsArray) {
158 + if (twoValIsTerminal) { // [E]
159 + one[key] = twoVal;
160 + } else if (twoValIsArray) { // [F]
161 + throwIf(
162 + !ArrayStrategies[arrayStrategy],
163 + ERRORS.RUN_TIME_ARRAY_MERGE_FAIL
164 + );
165 + // Else: At this point, the only other valid option is `IndexByIndex`
166 + if (arrayStrategy === ArrayStrategies.Clobber) {
167 + oneVal.length = 0;
168 + }
169 + mergeDeepIntoArrays(oneVal, twoVal, arrayStrategy, level + 1);
170 + } else if (twoValIsProperObject) { // [G]
171 + one[key] = {};
172 + mergeDeepIntoObjects(one[key], twoVal, arrayStrategy, level + 1);
173 + } else if (!twoValIsPresent) { // [H]
174 + // Leave the left Array alone
175 + }
176 + } else if (oneValIsProperObject) {
177 + if (twoValIsTerminal) { // [I]
178 + one[key] = twoVal;
179 + } else if (twoValIsArray) { // [J]
180 + one[key] = [];
181 + mergeDeepIntoArrays(one[key], twoVal, arrayStrategy, level + 1);
182 + } else if (twoValIsProperObject) { // [K]
183 + mergeDeepIntoObjects(oneVal, twoVal, arrayStrategy, level + 1);
184 + } else if (!twoValIsPresent) { // [L]
185 + // Leave the left Object alone
186 + }
187 + } else if (!oneValIsPresent) {
188 + if (twoValIsTerminal) { // [M]
189 + one[key] = twoVal;
190 + } else if (twoValIsArray) { // [N]
191 + one[key] = [];
192 + mergeDeepIntoArrays(one[key], twoVal, arrayStrategy, level + 1);
193 + } else if (twoValIsProperObject) { // [O]
194 + one[key] = {};
195 + mergeDeepIntoObjects(one[key], twoVal, arrayStrategy, level + 1);
196 + } else if (!twoValIsPresent) {
197 + // Could/should never happen
198 + }
199 + }
200 +};
201 +
202 +
203 +
204 +/**
205 + * Provides same functionality as mergeInto, but merges by mutating the first
206 + * argument. Will never mutate the second argument.
207 + *
208 + * mergeDeepInto provides two guarantees:
209 + * 1. In the process of mutating one, will not mutate two.
210 + * 2. Will not cause any nonTerminal memory to be shared between one and two.
211 + * This means that no further mutations to one will effect two (unless some
212 + * other part of your application forms shared memory between one and two).
213 + *
214 + * mergeDeepInto does not guarantee that it will *preserve* any shared memory
215 + * between two and one that existed before invocation. After calling
216 + * mergeDeepInto, there will be less (or equal) amount of shared memory between
217 + * one and two.
218 + *
219 + * Will tolerate a circular structure as the first parameter, but not the
220 + * second.
221 + *
222 + * Requires that first parameter be a non-Array, non-terminal `Object`, and the
223 + * second argument either be an `Object` or `null/undefined`. Arrays may exist
224 + * in the depths of either `Object` as long as an `arrayStrategy` is supplied.
225 + *
226 + * The third parameter indicates how merging two `Array`s should be performed.
227 + *
228 + * @param {!Object} one Object to be mutated deeply.
229 + * @param {?Object} two Values from two take precedence over values in one.
230 + * @param {?Enum=} arrayStrategy One of arrayStrategy
231 + */
232 +var mergeDeepInto = function(one, twoParam, arrayStrategy) {
233 + var two = normalizeMergeArg(twoParam);
234 + checkArrayStrategy(arrayStrategy); // Will be checked twice, for now
235 + mergeDeepIntoObjects(one, two, arrayStrategy, 0);
236 +};
237 +
238 +module.exports = mergeDeepInto;
src/utils/mergeHelpers.js new
+162
@@ -0,0 +1,162 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule mergeHelpers
17 + *
18 + * requiresPolyfills: Array.isArray
19 + */
20 +
21 +"use strict";
22 +
23 +var keyMirror = require('keyMirror');
24 +var throwIf = require('throwIf');
25 +
26 +/*
27 + * Maximum number of levels to traverse. Will catch circular structures.
28 + * @const
29 + */
30 +var MAX_MERGE_DEPTH = 36;
31 +
32 +var ERRORS = keyMirror({
33 + MERGE_ARRAY_FAIL: null,
34 + MERGE_CORE_FAILURE: null,
35 + MERGE_TYPE_USAGE_FAILURE: null,
36 + MERGE_DEEP_MAX_LEVELS: null,
37 + MERGE_DEEP_NO_ARR_STRATEGY: null
38 +});
39 +
40 +if (__DEV__) {
41 + ERRORS = {
42 + MERGE_ARRAY_FAIL:
43 + 'Unsupported type passed to a merge function. You may have passed a ' +
44 + 'structure that contains an array and the merge function does not know ' +
45 + 'how to merge arrays. ',
46 +
47 + MERGE_CORE_FAILURE:
48 + 'Critical assumptions about the merge functions have been violated. ' +
49 + 'This is the fault of the merge functions themselves, not necessarily ' +
50 + 'the callers.',
51 +
52 + MERGE_TYPE_USAGE_FAILURE:
53 + 'Calling merge function with invalid types. You may call merge ' +
54 + 'functions (non-array non-terminal) OR (null/undefined) arguments. ' +
55 + 'mergeInto functions have the same requirements but with an added ' +
56 + 'restriction that the first parameter must not be null/undefined.',
57 +
58 + MERGE_DEEP_MAX_LEVELS:
59 + 'Maximum deep merge depth exceeded. You may attempting to merge ' +
60 + 'circular structures in an unsupported way.',
61 + MERGE_DEEP_NO_ARR_STRATEGY:
62 + 'You must provide an array strategy to deep merge functions to ' +
63 + 'instruct the deep merge how to resolve merging two arrays.'
64 + };
65 +}
66 +
67 +/**
68 + * We won't worry about edge cases like new String('x') or new Boolean(true).
69 + * Functions are considered terminals, and arrays are not.
70 + * @param {*} o The item/object/value to test.
71 + * @return {boolean} true iff the argument is a terminal.
72 + */
73 +var isTerminal = function(o) {
74 + return typeof o !== 'object' || o === null;
75 +};
76 +
77 +var mergeHelpers = {
78 +
79 + MAX_MERGE_DEPTH: MAX_MERGE_DEPTH,
80 +
81 + isTerminal: isTerminal,
82 +
83 + /**
84 + * Converts null/undefined values into empty object.
85 + *
86 + * @param {?Object=} arg Argument to be normalized (nullable optional)
87 + * @return {!Object}
88 + */
89 + normalizeMergeArg: function(arg) {
90 + return arg === undefined || arg === null ? {} : arg;
91 + },
92 +
93 + /**
94 + * If merging Arrays, a merge strategy *must* be supplied. If not, it is
95 + * likely the caller's fault. If this function is ever called with anything
96 + * but `one` and `two` being `Array`s, it is the fault of the merge utilities.
97 + *
98 + * @param {*} one Array to merge into.
99 + * @param {*} two Array to merge from.
100 + */
101 + checkMergeArrayArgs: function(one, two) {
102 + throwIf(
103 + !Array.isArray(one) || !Array.isArray(two),
104 + ERRORS.MERGE_CORE_FAILURE
105 + );
106 + },
107 +
108 + /**
109 + * @param {*} one Object to merge into.
110 + * @param {*} two Object to merge from.
111 + */
112 + checkMergeObjectArgs: function(one, two) {
113 + mergeHelpers.checkMergeObjectArg(one);
114 + mergeHelpers.checkMergeObjectArg(two);
115 + },
116 +
117 + /**
118 + * @param {*} arg
119 + */
120 + checkMergeObjectArg: function(arg) {
121 + throwIf(isTerminal(arg) || Array.isArray(arg), ERRORS.MERGE_CORE_FAILURE);
122 + },
123 +
124 + /**
125 + * Checks that a merge was not given a circular object or an object that had
126 + * too great of depth.
127 + *
128 + * @param {number} Level of recursion to validate against maximum.
129 + */
130 + checkMergeLevel: function(level) {
131 + throwIf(level >= MAX_MERGE_DEPTH, ERRORS.MERGE_DEEP_MAX_LEVELS);
132 + },
133 +
134 + /**
135 + * Checks that a merge was not given a circular object or an object that had
136 + * too great of depth.
137 + *
138 + * @param {number} Level of recursion to validate against maximum.
139 + */
140 + checkArrayStrategy: function(strategy) {
141 + throwIf(
142 + strategy !== undefined && !(strategy in mergeHelpers.ArrayStrategies),
143 + ERRORS.MERGE_DEEP_NO_ARR_STRATEGY
144 + );
145 + },
146 +
147 + /**
148 + * Set of possible behaviors of merge algorithms when encountering two Arrays
149 + * that must be merged together.
150 + * - `clobber`: The left `Array` is ignored.
151 + * - `indexByIndex`: The result is achieved by recursively deep merging at
152 + * each index. (not yet supported.)
153 + */
154 + ArrayStrategies: keyMirror({
155 + Clobber: true,
156 + IndexByIndex: true
157 + }),
158 +
159 + ERRORS: ERRORS
160 +};
161 +
162 +module.exports = mergeHelpers;
src/utils/mergeInto.js new
+45
@@ -0,0 +1,45 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule mergeInto
17 + * @typechecks
18 + */
19 +
20 +"use strict";
21 +
22 +var mergeHelpers = require('mergeHelpers');
23 +
24 +var checkMergeObjectArg = mergeHelpers.checkMergeObjectArg;
25 +
26 +/**
27 + * Shallow merges two structures by mutating the first parameter.
28 + *
29 + * @param {object} one Object to be merged into.
30 + * @param {?object} two Optional object with properties to merge from.
31 + */
32 +function mergeInto(one, two) {
33 + checkMergeObjectArg(one);
34 + if (two != null) {
35 + checkMergeObjectArg(two);
36 + for (var key in two) {
37 + if (!two.hasOwnProperty(key)) {
38 + continue;
39 + }
40 + one[key] = two[key];
41 + }
42 + }
43 +}
44 +
45 +module.exports = mergeInto;
src/utils/mixInto.js new
+34
@@ -0,0 +1,34 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule mixInto
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * Simply copies properties to the prototype.
23 + */
24 +var mixInto = function(constructor, methodBag) {
25 + var methodName;
26 + for (methodName in methodBag) {
27 + if (!methodBag.hasOwnProperty(methodName)) {
28 + continue;
29 + }
30 + constructor.prototype[methodName] = methodBag[methodName];
31 + }
32 +};
33 +
34 +module.exports = mixInto;
src/utils/objFilter.js new
+48
@@ -0,0 +1,48 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule objFilter
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * For each key/value pair, invokes callback func and constructs a resulting
23 + * object which contains each key/value pair which produces a truthy result
24 + * from invoking the function:
25 + *
26 + * func(value, key, iteration)
27 + *
28 + * @param {?object} obj Object to map keys over
29 + * @param {function} func Invoked for each key/val pair.
30 + * @param {?*} context
31 + * @return {?object} Result of filtering or null if obj is falsey
32 + */
33 +function objFilter(obj, func, context) {
34 + if (!obj) {
35 + return null;
36 + }
37 + var i = 0;
38 + var ret = {};
39 + for (var key in obj) {
40 + if (obj.hasOwnProperty(key) &&
41 + func.call(context, obj[key], key, i++)) {
42 + ret[key] = obj[key];
43 + }
44 + }
45 + return ret;
46 +}
47 +
48 +module.exports = objFilter;
src/utils/objMap.js new
+47
@@ -0,0 +1,47 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule objMap
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * For each key/value pair, invokes callback func and constructs a resulting
23 + * object which contains, for every key in obj, values that are the result of
24 + * of invoking the function:
25 + *
26 + * func(value, key, iteration)
27 + *
28 + * @param {?object} obj Object to map keys over
29 + * @param {function} func Invoked for each key/val pair.
30 + * @param {?*} context
31 + * @return {?object} Result of mapping or null if obj is falsey
32 + */
33 +function objMap(obj, func, context) {
34 + if (!obj) {
35 + return null;
36 + }
37 + var i = 0;
38 + var ret = {};
39 + for (var key in obj) {
40 + if (obj.hasOwnProperty(key)) {
41 + ret[key] = func.call(context, obj[key], key, i++);
42 + }
43 + }
44 + return ret;
45 +}
46 +
47 +module.exports = objMap;
src/utils/objMapKeyVal.js new
+47
@@ -0,0 +1,47 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule objMapKeyVal
17 + */
18 +
19 +"use strict";
20 +
21 +/**
22 + * Behaves the same as `objMap` but invokes func with the key first, and value
23 + * second. Use `objMap` unless you need this special case.
24 + * Invokes func as:
25 + *
26 + * func(key, value, iteration)
27 + *
28 + * @param {?object} obj Object to map keys over
29 + * @param {!function} func Invoked for each key/val pair.
30 + * @param {?*} context
31 + * @return {?object} Result of mapping or null if obj is falsey
32 + */
33 +function objMapKeyVal(obj, func, context) {
34 + if (!obj) {
35 + return null;
36 + }
37 + var i = 0;
38 + var ret = {};
39 + for (var key in obj) {
40 + if (obj.hasOwnProperty(key)) {
41 + ret[key] = func.call(context, key, obj[key], i++);
42 + }
43 + }
44 + return ret;
45 +}
46 +
47 +module.exports = objMapKeyVal;
src/utils/throwIf.js new
+27
@@ -0,0 +1,27 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule throwIf
17 + */
18 +
19 +"use strict";
20 +
21 +var throwIf = function(condition, err) {
22 + if (condition) {
23 + throw new Error(err);
24 + }
25 +};
26 +
27 +module.exports = throwIf;
src/vendor/core/$.js new
+46
@@ -0,0 +1,46 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule $
17 + */
18 +
19 +var ge = require('ge');
20 +
21 +/**
22 + * Find a node by ID.
23 + *
24 + * If your application code depends on the existence of the element, use $,
25 + * which will throw if the element doesn't exist.
26 + *
27 + * If you're not sure whether or not the element exists, use ge instead, and
28 + * manually check for the element's existence in your application code.
29 + */
30 +function $(arg) {
31 + var element = ge(arg);
32 + if (!element) {
33 + if (typeof arg == 'undefined') {
34 + arg = 'undefined';
35 + } else if (arg === null) {
36 + arg = 'null';
37 + }
38 + throw new Error(
39 + 'Tried to get element "' + arg.toString() + '" but it is not present ' +
40 + 'on the page.'
41 + );
42 + }
43 + return element;
44 +}
45 +
46 +module.exports = $;
src/vendor/core/TouchEventUtils.js new
+42
@@ -0,0 +1,42 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule TouchEventUtils
17 + */
18 +
19 +var TouchEventUtils = {
20 + /**
21 + * Utility function for common case of extracting out the primary touch from a
22 + * touch event.
23 + * - `touchEnd` events usually do not have the `touches` property.
24 + * http://stackoverflow.com/questions/3666929/
25 + * mobile-sarai-touchend-event-not-firing-when-last-touch-is-removed
26 + *
27 + * @param {Event} nativeEvent Native event that may or may not be a touch.
28 + * @return {TouchesObject?} an object with pageX and pageY or null.
29 + */
30 + extractSingleTouch: function(nativeEvent) {
31 + var touches = nativeEvent.touches;
32 + var changedTouches = nativeEvent.changedTouches;
33 + var hasTouches = touches && touches.length > 0;
34 + var hasChangedTouches = changedTouches && changedTouches.length > 0;
35 +
36 + return !hasTouches && hasChangedTouches ? changedTouches[0] :
37 + hasTouches ? touches[0] :
38 + nativeEvent;
39 + }
40 +};
41 +
42 +module.exports = TouchEventUtils;
src/vendor/core/copyProperties.js new
+54
@@ -0,0 +1,54 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule copyProperties
17 + */
18 +
19 +/**
20 + * Copy properties from one or more objects (up to 5) into the first object.
21 + * This is a shallow copy. It mutates the first object and also returns it.
22 + *
23 + * NOTE: `arguments` has a very significant performance penalty, which is why
24 + * we don't support unlimited arguments.
25 + */
26 +function copyProperties(obj, a, b, c, d, e, f) {
27 + obj = obj || {};
28 +
29 + if (__DEV__) {
30 + if (f) {
31 + throw new Error('Too many arguments passed to copyProperties');
32 + }
33 + }
34 +
35 + var args = [a, b, c, d, e];
36 + var ii = 0, v;
37 + while (args[ii]) {
38 + v = args[ii++];
39 + for (var k in v) {
40 + obj[k] = v[k];
41 + }
42 +
43 + // IE ignores toString in object iteration.. See:
44 + // webreflection.blogspot.com/2007/07/quick-fix-internet-explorer-and.html
45 + if (v.hasOwnProperty && v.hasOwnProperty('toString') &&
46 + (typeof v.toString != 'undefined') && (obj.toString !== v.toString)) {
47 + obj.toString = v.toString;
48 + }
49 + }
50 +
51 + return obj;
52 +}
53 +
54 +module.exports = copyProperties;
src/vendor/core/createObjectFrom.js new
+63
@@ -0,0 +1,63 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule createObjectFrom
17 + */
18 +
19 +var hasArrayNature = require('hasArrayNature');
20 +
21 +/**
22 + * Construct an object from an array of keys
23 + * and optionally specified value or list of values.
24 + *
25 + * >>> createObjectFrom(['a','b','c']);
26 + * {a: true, b: true, c: true}
27 + *
28 + * >>> createObjectFrom(['a','b','c'], false);
29 + * {a: false, b: false, c: false}
30 + *
31 + * >>> createObjectFrom(['a','b','c'], 'monkey');
32 + * {c:'monkey', b:'monkey' c:'monkey'}
33 + *
34 + * >>> createObjectFrom(['a','b','c'], [1,2,3]);
35 + * {a: 1, b: 2, c: 3}
36 + *
37 + * >>> createObjectFrom(['women', 'men'], [true, false]);
38 + * {women: true, men: false}
39 + *
40 + * @param Array list of keys
41 + * @param mixed optional value or value array. defaults true.
42 + * @returns object
43 + */
44 +function createObjectFrom(keys, values /* = true */) {
45 + if (__DEV__) {
46 + if (!hasArrayNature(keys)) {
47 + throw new TypeError('Must pass an array of keys.');
48 + }
49 + }
50 +
51 + var object = {};
52 + var is_array = hasArrayNature(values);
53 + if (typeof values == 'undefined') {
54 + values = true;
55 + }
56 +
57 + for (var ii = keys.length; ii--;) {
58 + object[keys[ii]] = is_array ? values[ii] : values;
59 + }
60 + return object;
61 +}
62 +
63 +module.exports = createObjectFrom;
src/vendor/core/emptyFunction.js new
+50
@@ -0,0 +1,50 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule emptyFunction
17 + */
18 +
19 +var copyProperties = require('copyProperties');
20 +
21 +function makeEmptyFunction(arg) {
22 + return function() {
23 + return arg;
24 + };
25 +}
26 +
27 +/**
28 + * This function accepts and discards inputs; it has no side effects. This is
29 + * primarily useful idiomatically for overridable function endpoints which
30 + * always need to be callable, since JS lacks a null-call idiom ala Cocoa.
31 + */
32 +function emptyFunction() {}
33 +
34 +copyProperties(emptyFunction, {
35 + thatReturns: makeEmptyFunction,
36 + thatReturnsFalse: makeEmptyFunction(false),
37 + thatReturnsTrue: makeEmptyFunction(true),
38 + thatReturnsNull: makeEmptyFunction(null),
39 + thatReturnsThis: function() { return this; },
40 + thatReturnsArgument: function(arg) { return arg; },
41 + mustImplement: function(module, property) {
42 + return function() {
43 + if (__DEV__) {
44 + throw new Error(module + '.' + property + ' must be implemented!');
45 + }
46 + };
47 + }
48 +});
49 +
50 +module.exports = emptyFunction;
src/vendor/core/ge.js new
+76
@@ -0,0 +1,76 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ge
17 + */
18 +
19 +/**
20 + * Find a node by ID. Optionally search a sub-tree outside of the document
21 + *
22 + * Use ge if you're not sure whether or not the element exists. You can test
23 + * for existence yourself in your application code.
24 + *
25 + * If your application code depends on the existence of the element, use $
26 + * instead, which will throw in DEV if the element doesn't exist.
27 + */
28 +function ge(arg, root, tag) {
29 + return typeof arg != 'string' ? arg :
30 + !root ? document.getElementById(arg) :
31 + _geFromSubtree(arg, root, tag);
32 +}
33 +
34 +function _geFromSubtree(id, root, tag) {
35 + var elem, children, ii;
36 +
37 + if (_getNodeID(root) == id) {
38 + return root;
39 + } else if (root.getElementsByTagName) {
40 + // All Elements implement this, which does an iterative DFS, which is
41 + // faster than recursion and doesn't run into stack depth issues.
42 + children = root.getElementsByTagName(tag || '*');
43 + for (ii = 0; ii < children.length; ii++) {
44 + if (_getNodeID(children[ii]) == id) {
45 + return children[ii];
46 + }
47 + }
48 + } else {
49 + // DocumentFragment does not implement getElementsByTagName, so
50 + // recurse over its children. Its children must be Elements, so
51 + // each child will use the getElementsByTagName case instead.
52 + children = root.childNodes;
53 + for (ii = 0; ii < children.length; ii++) {
54 + elem = _geFromSubtree(id, children[ii]);
55 + if (elem) {
56 + return elem;
57 + }
58 + }
59 + }
60 +
61 + return null;
62 +}
63 +
64 +/**
65 + * Return the ID value for a given node. This allows us to avoid issues
66 + * with forms that contain inputs with name="id".
67 + *
68 + * @return string (null if attribute not set)
69 + */
70 +function _getNodeID(node) {
71 + // #document and #document-fragment do not have getAttributeNode.
72 + var id = node.getAttributeNode && node.getAttributeNode('id');
73 + return id ? id.value : null;
74 +}
75 +
76 +module.exports = ge;
src/vendor/core/hasArrayNature.js new
+53
@@ -0,0 +1,53 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule hasArrayNature
17 + */
18 +
19 +/**
20 + * Perform a heuristic test to determine if an object is "array-like".
21 + *
22 + * A monk asked Joshu, a Zen master, "Has a dog Buddha nature?"
23 + * Joshu replied: "Mu."
24 + *
25 + * This function determines if its argument has "array nature": it returns
26 + * true if the argument is an actual array, an `arguments' object, or an
27 + * HTMLCollection (e.g. node.childNodes or node.getElementsByTagName()).
28 + *
29 + * @param obj An object to test.
30 + * @return bool True if the object is array-like.
31 + */
32 +function hasArrayNature(obj) {
33 + return (
34 + // not null/false
35 + !!obj &&
36 + // arrays are objects, NodeLists are functions in Safari
37 + (typeof obj == 'object' || typeof obj == 'function') &&
38 + // quacks like an array
39 + ('length' in obj) &&
40 + // not window
41 + !('setInterval' in obj) &&
42 + (
43 + // a real array
44 + Object.prototype.toString.call(obj) === "[object Array]" ||
45 + // arguments
46 + ('callee' in obj) ||
47 + // HTMLCollection/NodeList
48 + ('item' in obj)
49 + )
50 + );
51 +}
52 +
53 +module.exports = hasArrayNature;
src/vendor/core/hyphenate.js new
+35
@@ -0,0 +1,35 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule hyphenate
17 + * @typechecks
18 + */
19 +
20 +var _uppercasePattern = /([A-Z])/g;
21 +
22 +/**
23 + * Hyphenates a camelcased string, for example:
24 + *
25 + * > hyphenate('backgroundColor')
26 + * < "background-color"
27 + *
28 + * @param {string} string
29 + * @return {string}
30 + */
31 +function hyphenate(string) {
32 + return string.replace(_uppercasePattern, '-$1').toLowerCase();
33 +}
34 +
35 +module.exports = hyphenate;
src/vendor/core/invariant.js new
+54
@@ -0,0 +1,54 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule invariant
17 + */
18 +
19 +/**
20 + * Use invariant() to assert state which your program assumes to be true.
21 + *
22 + * Provide sprintf style format and arguments to provide information about
23 + * what broke and what you were expecting.
24 + *
25 + * The invariant message will be stripped in production, but the invariant
26 + * will remain to ensure logic does not differ in production.
27 + */
28 +
29 +function invariant(condition) {
30 + if (!condition) {
31 + throw new Error('Invariant Violation');
32 + }
33 +}
34 +
35 +module.exports = invariant;
36 +
37 +if (__DEV__) {
38 + var invariantDev = function(condition, format, a, b, c, d, e, f) {
39 + if (format === undefined) {
40 + throw new Error('invariant requires an error message argument');
41 + }
42 +
43 + if (!condition) {
44 + var args = [a, b, c, d, e, f];
45 + var argIndex = 0;
46 + throw new Error(
47 + 'Invariant Violation: ' +
48 + format.replace(/%s/g, function() { return args[argIndex++]; })
49 + );
50 + }
51 + };
52 +
53 + module.exports = invariantDev;
54 +}
src/vendor/core/keyOf.js new
+41
@@ -0,0 +1,41 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule keyOf
17 + */
18 +
19 +/**
20 + * Allows extraction of a minified key. Let's the build system minify keys
21 + * without loosing the ability to dynamically use key strings as values
22 + * themselves. Pass in an object with a single key/val pair and it will return
23 + * you the string key of that single record. Suppose you want to grab the
24 + * value for a key 'className' inside of an object. Key/val minification may
25 + * have aliased that key to be 'xa12'. keyOf({className: null}) will return
26 + * 'xa12' in that case. Resolve keys you want to use once at startup time, then
27 + * reuse those resolutions.
28 + */
29 +var keyOf = function(oneKeyObj) {
30 + var key;
31 + for (key in oneKeyObj) {
32 + if (!oneKeyObj.hasOwnProperty(key)) {
33 + continue;
34 + }
35 + return key;
36 + }
37 + return null;
38 +};
39 +
40 +
41 +module.exports = keyOf;
src/vendor/error/__tests__/erx-test.js new
+58
@@ -0,0 +1,58 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * Copyright 2004-present Facebook. All Rights Reserved.
17 + *
18 + * @emails catchen@fb.com
19 + * @emails javascript@lists.facebook.com
20 + */
21 +
22 +require('mock-modules')
23 + .dontMock('erx')
24 + .dontMock('ex');
25 +
26 +describe('erx', function() {
27 + var erx = require('erx');
28 + var ex = require('ex');
29 +
30 + it('should always reverse ex if placeholder and argument match', function() {
31 + expect(erx(ex('zero argument'))).toEqual(['zero argument']);
32 + expect(erx(ex('one argument: %s', 'one')))
33 + .toEqual(['one argument: %s', 'one']);
34 + expect(erx(ex('two arguments: %s, %s', 'one', 2)))
35 + .toEqual(['two arguments: %s, %s', 'one', '2']);
36 + expect(erx(ex('three arguments: %s, %s, %s', 'one', 2, { value: 3 })))
37 + .toEqual(['three arguments: %s, %s, %s', 'one', '2', '[object Object]']);
38 + });
39 +
40 + it('should be idempotent', function() {
41 + var messageWithParams = erx(ex('one argument: %s', 'one'));
42 + expect(messageWithParams).toEqual(erx(messageWithParams));
43 + });
44 +
45 + it('should treat plain text as it was ex-ed with no argument', function() {
46 + expect(erx('plain text')).toEqual(['plain text']);
47 + });
48 +
49 + it('should work when text was appended/prepended to ex-ed text', function() {
50 + expect(erx('prepended text|' + ex('zero argument') + '|appended text'))
51 + .toEqual(['prepended text|zero argument|appended text']);
52 + expect(erx(
53 + 'prepended text|' +
54 + ex('one argument: %s', 'one') +
55 + '|appended text'
56 + )).toEqual(['prepended text|one argument: %s|appended text', 'one']);
57 + });
58 +});
src/vendor/error/__tests__/ex-test.js new
+43
@@ -0,0 +1,43 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * Copyright 2004-present Facebook. All Rights Reserved.
17 + *
18 + * @emails catchen@fb.com
19 + * @emails javascript@lists.facebook.com
20 + */
21 +
22 +require('mock-modules')
23 + .dontMock('ex');
24 +
25 +describe('ex', function() {
26 + var ex = require('ex');
27 +
28 + it('should convert whatever arguments into one string', function() {
29 + expect(typeof ex('zero argument')).toEqual('string');
30 + expect(typeof ex('one argument: %s', 'one')).toEqual('string');
31 + expect(typeof ex('two arguments: %s, %s', 'one', 2))
32 + .toEqual('string');
33 + expect(typeof ex('three arguments: %s, %s, %s', 'one', 2, { value: 3 }))
34 + .toEqual('string');
35 + });
36 +
37 + it('should handle placeholder and argument number mismatch', function() {
38 + expect(typeof ex('zero placeholder and one argument', 'one'))
39 + .toEqual('string');
40 + expect(typeof ex('one placeholder and zero argument: %s'))
41 + .toEqual('string');
42 + });
43 +});
src/vendor/error/erx.js new
+71
@@ -0,0 +1,71 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule erx
17 + * @typechecks
18 + * @nostacktrace
19 + */
20 +
21 +var ex = require('ex');
22 +
23 +/**
24 + * This function reverse transform done by `ex()`.
25 + *
26 + * This function only transforms plain text message back to error message with
27 + * arguments. If the error message is minified by the server, it has to be
28 + * looked up on the server.
29 + *
30 + * This function is designed to be idempotent, so erx(erx(whatever)) will be
31 + * the same as erx(whatever).
32 + *
33 + * @param {string|array<string>} transformedMessage
34 + */
35 +
36 +var erx = function(transformedMessage) {
37 + if (typeof transformedMessage !== 'string') {
38 + // possibly double `erx`ed and return original value
39 + return transformedMessage;
40 + }
41 +
42 + var prefixLeft = transformedMessage.indexOf(ex._prefix);
43 + var suffixLeft = transformedMessage.lastIndexOf(ex._suffix);
44 + if (prefixLeft < 0 || suffixLeft < 0) {
45 + // plain error message without any transformation
46 + return [transformedMessage];
47 + }
48 +
49 + var prefixRight = prefixLeft + ex._prefix.length;
50 + var suffixRight = suffixLeft + ex._suffix.length;
51 + if (prefixRight >= suffixLeft) {
52 + return ['erx slice failure: %s', transformedMessage];
53 + }
54 +
55 + var leftSlice = transformedMessage.substring(0, prefixLeft);
56 + var rightSlice = transformedMessage.substring(suffixRight);
57 + transformedMessage = transformedMessage.substring(prefixRight, suffixLeft);
58 +
59 + var messageWithParams;
60 + try {
61 + messageWithParams = JSON.parse(transformedMessage);
62 + messageWithParams[0] =
63 + leftSlice + messageWithParams[0] + rightSlice;
64 + } catch(err) {
65 + return ['erx parse failure: %s', transformedMessage];
66 + }
67 +
68 + return messageWithParams;
69 +};
70 +
71 +module.exports = erx;
src/vendor/error/ex.js new
+49
@@ -0,0 +1,49 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ex
17 + * @typechecks
18 + * @nostacktrace
19 + */
20 +
21 +/**
22 + * This function transforms error message with arguments into plain text error
23 + * message, so that it can be passed to window.onerror without losing anything.
24 + * It can then be transformed back by `erx()` function.
25 + *
26 + * Usage:
27 + * throw new Error(ex('Error %s from %s', errorCode, userID));
28 + *
29 + * @param {string} errorMessage
30 + */
31 +
32 +var ex = function(errorMessage/*, arg1, arg2, ...*/) {
33 + var args = Array.prototype.slice.call(arguments).map(function(arg) {
34 + return String(arg);
35 + });
36 + var expectedLength = errorMessage.split('%s').length - 1;
37 +
38 + if (expectedLength !== args.length - 1) {
39 + // something wrong with the formatting string
40 + return ex('ex args number mismatch: %s', JSON.stringify(args));
41 + }
42 +
43 + return ex._prefix + JSON.stringify(args) + ex._suffix;
44 +};
45 +
46 +ex._prefix = '<![EX[';
47 +ex._suffix = ']]>';
48 +
49 +module.exports = ex;
src/vendor/stubs/Env.js new
+20
@@ -0,0 +1,20 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule Env
17 + */
18 +
19 +module.exports = {};
20 +
src/vendor/stubs/EventListener.js new
+59
@@ -0,0 +1,59 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule EventListener
17 + */
18 +
19 +/**
20 + * Upstream version of event listener. Does not take into account specific
21 + * nature of platform.
22 + */
23 +var EventListener = {
24 + /**
25 + * Listens to bubbled events on a DOM node.
26 + *
27 + * @param {Element} el DOM element to register listener on.
28 + * @param {string} handlerBaseName 'click'/'mouseover'
29 + * @param {Function!} cb Callback function
30 + */
31 + listen: function(el, handlerBaseName, cb) {
32 + if (el.addEventListener) {
33 + el.addEventListener(handlerBaseName, cb, false);
34 + } else if (el.attachEvent) {
35 + el.attachEvent('on' + handlerBaseName, cb);
36 + }
37 + },
38 +
39 + /**
40 + * Listens to captured events on a DOM node.
41 + *
42 + * @see `EventListener.listen` for params.
43 + * @throws Exception if addEventListener is not supported.
44 + */
45 + capture: function(el, handlerBaseName, cb) {
46 + if (!el.addEventListener) {
47 + console.error(
48 + 'You are attempting to use addEventlistener ' +
49 + 'in a browser that does not support it support it.' +
50 + 'This likely means that you will not receive events that ' +
51 + 'your application relies on (such as scroll).');
52 + return;
53 + } else {
54 + el.addEventListener(handlerBaseName, cb, true);
55 + }
56 + }
57 +};
58 +
59 +module.exports = EventListener;
src/vendor/stubs/README new
+8
@@ -0,0 +1,8 @@
1 +This project defines stubs for platform-specific explicit dependencies used by
2 +other upstream modules.
3 +
4 +Do not check this project into www or m downstreams.
5 +
6 +All downstreams that want to use the any static_upstream project who has a
7 +dependency on something in this project should implement their own versions of
8 +the modules in this project.
src/vendor/stubs/URI.js new
+26
@@ -0,0 +1,26 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule URI
17 + */
18 +
19 +/**
20 + * We expect downstream reps to implement URI as an extension of URIBase.
21 + * URIBase in turn is not used by other upstream modules but rather URI is used.
22 + * This stubs exists so that tests for the other upstream modules run correctly.
23 + */
24 +var URIBase = require('URIBase');
25 +
26 +module.exports = URIBase;
src/vendor/stubs/cx.js new
+44
@@ -0,0 +1,44 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule cx
17 + */
18 +
19 +/**
20 + * This function is used to mark string literals representing CSS class names
21 + * so that they can be transformed statically. This allows for modularization
22 + * and minification of CSS class names.
23 + *
24 + * In static_upstream, this function is actually implemented, but it should
25 + * eventually be replaced with something more descriptive, and the transform
26 + * that is used in the main stack should be ported for use elsewhere.
27 + *
28 + * @param string|object className to modularize, or an object of key/values.
29 + * In the object case, the values are conditions that
30 + * determine if the className keys should be included.
31 + * @param [string ...] Variable list of classNames in the string case.
32 + * @return string Renderable space-separated CSS className.
33 + */
34 +function cx(classNames) {
35 + if (typeof classNames == 'object') {
36 + return Object.keys(classNames).map(function(className) {
37 + return classNames[className] ? className : '';
38 + }).join(' ');
39 + } else {
40 + return Array.prototype.join.call(arguments, ' ');
41 + }
42 +}
43 +
44 +module.exports = cx;
starter/README.md new
+11
@@ -0,0 +1,11 @@
1 +# Welcome to React!
2 +
3 +You've just downloaded the React starter kit; congratulations!
4 +
5 +To begin, check out the `examples/` directory for a bunch of examples, or check out [Getting Started](http://facebook.github.io/react/docs/getting-started.html) for more information.
6 +
7 +In some browsers our examples won't work from the local file system; run `python -m SimpleHTTPServer` and visit `http://localhost:8000/` to view them.
8 +
9 +Want to start your own app? Just copy `examples/basic-jsx-external` and start hacking! Remember: before launching you'll want to precompile your JSX code as we do in `examples/basic-jsx-precompiled`; this requires doing `npm install -g react-tools` and then running our `jsx` tool. See [Getting Started](http://facebook.github.io/react/docs/getting-started.html) for more information.
10 +
11 +Happy hacking!
test/index.html new
+16
@@ -0,0 +1,16 @@
1 +<!DOCTYPE html>
2 +<html>
3 + <head>
4 + <link rel="stylesheet" type="text/css" href="jasmine.css" />
5 + <script src="jasmine.js"></script>
6 + <script src="jasmine-html.js"></script>
7 + <script src="react-test.js"></script>
8 + <script>
9 + window.onload = function() {
10 + jasmine.getEnv().execute();
11 + };
12 + </script>
13 + </head>
14 + <body>
15 + </body>
16 +</html>
test/phantom-harness.js new
+116
@@ -0,0 +1,116 @@
1 +var slice = Array.prototype.slice;
2 +var argv = slice.call(require("system").args);
3 +
4 +// Hard to believe PhantomJS has no equivalent of Node's "path" module.
5 +var fs = require("fs");
6 +var splat = [fs.workingDirectory, argv[0]]
7 + .join(fs.separator)
8 + .split(fs.separator);
9 +
10 +var harness = splat.pop();
11 +if (harness !== "phantom-harness.js") {
12 + console.error("wrong harness: " + harness);
13 + phantom.exit(-1);
14 +}
15 +
16 +var cwd = splat.join(fs.separator);
17 +fs.changeWorkingDirectory(cwd);
18 +
19 +// Hard to believe PhantomJS has no option parsing module.
20 +var port = 8080;
21 +var debug = false;
22 +var lastArg;
23 +while (argv.length > 0) {
24 + var arg = argv.pop();
25 + if (arg === "--port") {
26 + port = +lastArg;
27 + } else if (arg === "--debug") {
28 + debug = true;
29 + }
30 + lastArg = arg;
31 +}
32 +
33 +var server = require("webserver").create();
34 +server.listen(port, function(req, res) {
35 + var file = req.url.replace(/^\/+/, "");
36 +
37 + switch (file) {
38 + case "":
39 + default:
40 + file = "index.html";
41 + break;
42 +
43 + case "react-test.js":
44 + file = "../build/" + file;
45 + break;
46 +
47 + case "jasmine.css":
48 + case "jasmine.js":
49 + case "jasmine-html.js":
50 + file = "../vendor/jasmine/" + file;
51 + break;
52 + }
53 +
54 + if (/\.css$/i.test(file)) {
55 + res.setHeader("Content-Type", "text/css");
56 + } else if (/\.js/i.test(file)) {
57 + res.setHeader("Content-Type", "text/javascript");
58 + } else {
59 + res.setHeader("Content-Type", "text/html");
60 + }
61 +
62 + res.statusCode = 200;
63 + res.write(fs.read(file));
64 + res.close();
65 +});
66 +
67 +var url = "http://localhost:" + port;
68 +var green = "\033[32m";
69 +var cyan = "\033[36m";
70 +var reset = "\033[0m";
71 +
72 +if (debug) {
73 + console.log(green);
74 + console.log("PhantomJS received the " + cyan + "--debug" + green + " option.");
75 + console.log("Load " + cyan + url + green + " in your browser to execute " +
76 + "the test suite.");
77 + console.log("Type " + cyan + "control-C" + green + " to terminate the " +
78 + "PhantomJS process.");
79 + console.log(reset);
80 +
81 + // Leave PhantomJS running until killed with control-C...
82 +
83 +} else {
84 + var page = require("webpage").create();
85 + var timeoutSecs = 60;
86 +
87 + page.onCallback = function(data) {
88 + switch (data.type) {
89 + case "console":
90 + console[data.method].apply(console, data.args);
91 + break;
92 +
93 + case "exit":
94 + // PhantomJS crashes sometimes unless we call phantom.exit in its own
95 + // event loop tick.
96 + setTimeout(function() {
97 + phantom.exit(data.code);
98 + }, 10);
99 + break;
100 + }
101 + };
102 +
103 + page.open(url, function(status) {
104 + if (status !== "success") {
105 + console.error("failed to open " + url);
106 + phantom.exit(-1);
107 + }
108 +
109 + setTimeout(function() {
110 + console.error(
111 + "PhantomJS tests timed out after " +
112 + timeoutSecs + " seconds.");
113 + phantom.exit(-1);
114 + }, timeoutSecs * 1e3);
115 + });
116 +}
vendor/browser-transforms.js new
+110
@@ -0,0 +1,110 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +/* jshint browser: true */
17 +/* jslint evil: true */
18 +
19 +'use strict';
20 +var runScripts;
21 +
22 +var transform = require('./fbtransform/lib/transform').transform;
23 +var visitors = require('./fbtransform/visitors').transformVisitors;
24 +var transform = transform.bind(null, visitors.react);
25 +var docblock = require('./fbtransform/lib/docblock');
26 +
27 +exports.transform = transform;
28 +exports.exec = function(code) {
29 + return eval(transform(code));
30 +};
31 +var run = exports.run = function(code) {
32 + var moduleName =
33 + docblock.parseAsObject(docblock.extract(code)).providesModule;
34 + var jsx =
35 + docblock.parseAsObject(docblock.extract(code)).jsx;
36 +
37 + window.moduleLoads = (window.moduleLoads || []).concat(moduleName);
38 + window.startTime = Date.now();
39 + var functionBody = jsx ? transform(code).code : code;
40 + Function('require', 'module', 'exports', functionBody)(require, module, exports);
41 + window.endTime = Date.now();
42 + require[moduleName] = module.exports;
43 +};
44 +
45 +if (typeof window === "undefined" || window === null) {
46 + return;
47 +}
48 +
49 +var load = exports.load = function(url, callback) {
50 + var xhr;
51 + xhr = window.ActiveXObject ? new window.ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest();
52 + xhr.open('GET', url, false);
53 + if ('overrideMimeType' in xhr) {
54 + xhr.overrideMimeType('text/plain');
55 + }
56 + xhr.onreadystatechange = function() {
57 + var _ref;
58 + if (xhr.readyState === 4) {
59 + if ((_ref = xhr.status) === 0 || _ref === 200) {
60 + run(xhr.responseText);
61 + } else {
62 + throw new Error("Could not load " + url);
63 + }
64 + if (callback) {
65 + return callback();
66 + }
67 + }
68 + };
69 + return xhr.send(null);
70 +};
71 +
72 +runScripts = function() {
73 + var jsxes, execute, index, length, s, scripts;
74 + scripts = document.getElementsByTagName('script');
75 + jsxes = (function() {
76 + var _i, _len, _results;
77 + _results = [];
78 + for (_i = 0, _len = scripts.length; _i < _len; _i++) {
79 + s = scripts[_i];
80 + if (s.type === 'text/jsx') {
81 + _results.push(s);
82 + }
83 + }
84 + return _results;
85 + })();
86 + index = 0;
87 + length = jsxes.length;
88 + (execute = function(j) {
89 + var script;
90 + script = jsxes[j];
91 + if ((script != null ? script.type : void 0) === 'text/jsx') {
92 + if (script.src) {
93 + return load(script.src, execute);
94 + } else {
95 + run(script.innerHTML);
96 + return execute();
97 + }
98 + }
99 + });
100 + for (var i = 0; i < jsxes.length; i++) {
101 + execute(i);
102 + }
103 + return null;
104 +};
105 +
106 +if (window.addEventListener) {
107 + window.addEventListener('DOMContentLoaded', runScripts, false);
108 +} else {
109 + window.attachEvent('onload', runScripts);
110 +}
vendor/fbtransform/lib/docblock.js new
+86
@@ -0,0 +1,86 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +var docblockRe = /^\s*(\/\*\*(.|\n)*?\*\/)/;
18 +var ltrimRe = /^\s*/;
19 +/**
20 + * @param {String} contents
21 + * @return {String}
22 + */
23 +function extract(contents) {
24 + var match = contents.match(docblockRe);
25 + if (match) {
26 + return match[0].replace(ltrimRe, '') || '';
27 + }
28 + return '';
29 +}
30 +
31 +
32 +var commentStartRe = /^\/\*\*?/;
33 +var commentEndRe = /\*\/$/;
34 +var wsRe = /[\t ]+/g;
35 +var stringStartRe = /(\n|^) *\*/g;
36 +var multilineRe = /(?:^|\n) *(@[^\n]*?) *\n *([^@\n\s][^@\n]+?) *\n/g;
37 +var propertyRe = /(?:^|\n) *@(\S+) *([^\n]*)/g;
38 +
39 +/**
40 + * @param {String} contents
41 + * @return {Array}
42 + */
43 +function parse(docblock) {
44 + docblock = docblock
45 + .replace(commentStartRe, '')
46 + .replace(commentEndRe, '')
47 + .replace(wsRe, ' ')
48 + .replace(stringStartRe, '$1');
49 +
50 + // Normalize multi-line directives
51 + var prev = '';
52 + while (prev != docblock) {
53 + prev = docblock;
54 + docblock = docblock.replace(multilineRe, "\n$1 $2\n");
55 + }
56 + docblock = docblock.trim();
57 +
58 + var result = [];
59 + var match;
60 + while (match = propertyRe.exec(docblock)) {
61 + result.push([match[1], match[2]]);
62 + }
63 +
64 + return result;
65 +}
66 +
67 +/**
68 + * Same as parse but returns an object of prop: value instead of array of paris
69 + * If a property appers more than once the last one will be returned
70 + *
71 + * @param {String} contents
72 + * @return {Object}
73 + */
74 +function parseAsObject(docblock) {
75 + var pairs = parse(docblock);
76 + var result = {};
77 + for (var i = 0; i < pairs.length; i++) {
78 + result[pairs[i][0]] = pairs[i][1];
79 + }
80 + return result;
81 +}
82 +
83 +
84 +exports.extract = extract;
85 +exports.parse = parse;
86 +exports.parseAsObject = parseAsObject;
vendor/fbtransform/lib/transform.js new
+135
@@ -0,0 +1,135 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +/*global exports:true*/
17 +/*jslint node: true*/
18 +"use strict";
19 +
20 +/**
21 + * Syntax transfomer for javascript. Takes the source in, spits the source
22 + * out. Tries to maintain readability and preserve whitespace and line numbers
23 + * where posssible.
24 + *
25 + * Support
26 + * - ES6 class transformation + private property munging, see ./classes.js
27 + * - React XHP style syntax transformations, see ./react.js
28 + * - Bolt XHP style syntax transformations, see ./bolt.js
29 + *
30 + * The general flow is the following:
31 + * - Parse the source with our customized esprima-parser
32 + * https://github.com/voloko/esprima. We have to customize the parser to
33 + * support non-standard XHP-style syntax. We parse the source range: true
34 + * option that forces esprima to return positions in the source within
35 + * resulting parse tree.
36 + *
37 + * - Traverse resulting syntax tree, trying to apply a set of visitors to each
38 + * node. Each visitor should provide a .test() function that tests if the
39 + * visitor can process a given node.
40 + *
41 + * - Visitor is responsible for code generation for a given syntax node.
42 + * Generated code is stored in state.g.buffer that is passed to every
43 + * visitor. It's up to the visitor to process the code the way it sees fit.
44 + * All of the current visitors however use both the node and the original
45 + * source to generate transformed code. They use nodes to generate new
46 + * code and they copy the original source, preserving whitespace and comments,
47 + * for the parts they don't care about.
48 + */
49 +var esprima = require('esprima');
50 +
51 +var createState = require('./utils').createState;
52 +var catchup = require('./utils').catchup;
53 +
54 +/**
55 + * @param {object} object
56 + * @param {function} visitor
57 + * @param {array} path
58 + * @param {object} state
59 + */
60 +function traverse(object, path, state) {
61 + var key, child;
62 +
63 + if (walker(traverse, object, path, state) === false) {
64 + return;
65 + }
66 + path.unshift(object);
67 + for (key in object) {
68 + // skip obviously wrong attributes
69 + if (key === 'range' || key === 'loc') {
70 + continue;
71 + }
72 + if (object.hasOwnProperty(key)) {
73 + child = object[key];
74 + if (typeof child === 'object' && child !== null) {
75 + child.range && catchup(child.range[0], state);
76 + traverse(child, path, state);
77 + child.range && catchup(child.range[1], state);
78 + }
79 + }
80 + }
81 + path.shift();
82 +}
83 +
84 +function walker(traverse, object, path, state) {
85 + var visitors = state.g.visitors;
86 + for (var i = 0; i < visitors.length; i++) {
87 + if (visitors[i].test(object, path, state)) {
88 + return visitors[i](traverse, object, path, state);
89 + }
90 + }
91 +}
92 +
93 +function runPass(source, visitors, options) {
94 + var ast = esprima.parse(source, { comment: true, loc: true, range: true });
95 + var state = createState(source, options);
96 + state.g.originalProgramAST = ast;
97 + state.g.visitors = visitors;
98 +
99 + if (options.sourceMap) {
100 + var SourceMapGenerator = require('source-map').SourceMapGenerator;
101 + state.g.sourceMap = new SourceMapGenerator({ file: 'transformed.js' });
102 + }
103 + traverse(ast, [], state);
104 + catchup(source.length, state);
105 + return state;
106 +}
107 +
108 +/**
109 + * Applies all available transformations to the source
110 + * @param {array} visitors
111 + * @param {string} source
112 + * @param {?object} options
113 + * @return {object}
114 + */
115 +function transform(visitors, source, options) {
116 + options = options || {};
117 +
118 + var state = runPass(source, visitors, options);
119 + var sourceMap = state.g.sourceMap;
120 +
121 + if (sourceMap) {
122 + return {
123 + sourceMap: sourceMap,
124 + sourceMapFilename: options.filename || 'source.js',
125 + code: state.g.buffer
126 + };
127 + } else {
128 + return {
129 + code: state.g.buffer
130 + };
131 + }
132 +}
133 +
134 +
135 +exports.transform = transform;
vendor/fbtransform/lib/utils.js new
+332
@@ -0,0 +1,332 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +/*global exports:true*/
17 +
18 +/**
19 + * State represents the given parser state. It has a local and global parts.
20 + * Global contains parser position, source, etc. Local contains scope based
21 + * properties, like current class name. State should contain all the info
22 + * required for transformation. It's the only mandatory object that is being
23 + * passed to every function in transform chain.
24 + *
25 + * @param {String} source
26 + * @param {Object} transformOptions
27 + * @return {Object}
28 + */
29 +function createState(source, transformOptions) {
30 + return {
31 + /**
32 + * Name of the super class variable
33 + * @type {String}
34 + */
35 + superVar: '',
36 + /**
37 + * Name of the enclosing class scope
38 + * @type {String}
39 + */
40 + scopeName: '',
41 + /**
42 + * Global state (not affected by updateState)
43 + * @type {Object}
44 + */
45 + g: {
46 + /**
47 + * A set of general options that transformations can consider while doing
48 + * a transformation:
49 + *
50 + * - minify
51 + * Specifies that transformation steps should do their best to minify
52 + * the output source when possible. This is useful for places where
53 + * minification optimizations are possible with higher-level context
54 + * info than what jsxmin can provide.
55 + *
56 + * For example, the ES6 class transform will minify munged private
57 + * variables if this flag is set.
58 + */
59 + opts: transformOptions,
60 + /**
61 + * Current position in the source code
62 + * @type {Number}
63 + */
64 + position: 0,
65 + /**
66 + * Buffer containing the result
67 + * @type {String}
68 + */
69 + buffer: '',
70 + /**
71 + * Indentation offset (only negative offset is supported now)
72 + * @type {Number}
73 + */
74 + indentBy: 0,
75 + /**
76 + * Source that is being transformed
77 + * @type {String}
78 + */
79 + source: source,
80 +
81 + /**
82 + * Cached parsed docblock (see getDocblock)
83 + * @type {object}
84 + */
85 + docblock: null,
86 +
87 + /**
88 + * Whether the thing was used
89 + * @type {Boolean}
90 + */
91 + tagNamespaceUsed: false,
92 +
93 + /**
94 + * If using bolt xjs transformation
95 + * @type {Boolean}
96 + */
97 + isBolt: undefined,
98 +
99 + /**
100 + * Whether to record source map (expensive) or not
101 + * @type {SourceMapGenerator|null}
102 + */
103 + sourceMap: null,
104 +
105 + /**
106 + * Filename of the file being processed. Will be returned as a source
107 + * attribute in the source map
108 + */
109 + sourceMapFilename: 'source.js',
110 +
111 + /**
112 + * Only when source map is used: last line in the source for which
113 + * source map was generated
114 + * @type {Number}
115 + */
116 + sourceLine: 1,
117 +
118 + /**
119 + * Only when source map is used: last line in the buffer for which
120 + * source map was generated
121 + * @type {Number}
122 + */
123 + bufferLine: 1,
124 +
125 + /**
126 + * The top-level Program AST for the original file.
127 + */
128 + originalProgramAST: null,
129 +
130 + sourceColumn: 0,
131 + bufferColumn: 0
132 + }
133 + };
134 +}
135 +
136 +/**
137 + * Updates a copy of a given state with "update" and returns an updated state.
138 + *
139 + * @param {Object} state
140 + * @param {Object} update
141 + * @return {Object}
142 + */
143 +function updateState(state, update) {
144 + return {
145 + g: state.g,
146 + superVar: update.superVar || state.superVar,
147 + scopeName: update.scopeName || state.scopeName
148 + };
149 +}
150 +
151 +/**
152 + * Given a state fill the resulting buffer from the original source up to
153 + * the end
154 + * @param {Number} end
155 + * @param {Object} state
156 + * @param {Function?} contentTransformer Optional callback to transform newly
157 + * added content.
158 + */
159 +function catchup(end, state, contentTransformer) {
160 + if (end < state.g.position) {
161 + // cannot move backwards
162 + return;
163 + }
164 + var source = state.g.source.substring(state.g.position, end);
165 + var transformed = updateIndent(source, state);
166 + if (state.g.sourceMap && transformed) {
167 + // record where we are
168 + state.g.sourceMap.addMapping({
169 + generated: { line: state.g.bufferLine, column: state.g.bufferColumn },
170 + original: { line: state.g.sourceLine, column: state.g.sourceColumn },
171 + source: state.g.sourceMapFilename
172 + });
173 +
174 + // record line breaks in transformed source
175 + var sourceLines = source.split('\n');
176 + var transformedLines = transformed.split('\n');
177 + // Add line break mappings between last known mapping and the end of the
178 + // added piece. So for the code piece
179 + // (foo, bar);
180 + // > var x = 2;
181 + // > var b = 3;
182 + // var c =
183 + // only add lines marked with ">": 2, 3.
184 + for (var i = 1; i < sourceLines.length - 1; i++) {
185 + state.g.sourceMap.addMapping({
186 + generated: { line: state.g.bufferLine, column: 0 },
187 + original: { line: state.g.sourceLine, column: 0 },
188 + source: state.g.sourceMapFilename
189 + });
190 + state.g.sourceLine++;
191 + state.g.bufferLine++;
192 + }
193 + // offset for the last piece
194 + if (sourceLines.length > 1) {
195 + state.g.sourceLine++;
196 + state.g.bufferLine++;
197 + state.g.sourceColumn = 0;
198 + state.g.bufferColumn = 0;
199 + }
200 + state.g.sourceColumn += sourceLines[sourceLines.length - 1].length;
201 + state.g.bufferColumn +=
202 + transformedLines[transformedLines.length - 1].length;
203 + }
204 + state.g.buffer +=
205 + contentTransformer ? contentTransformer(transformed) : transformed;
206 + state.g.position = end;
207 +}
208 +
209 +/**
210 + * Applies `catchup` but passing in a function that removes any non-whitespace
211 + * characters.
212 + */
213 +var re = /(\S)/g;
214 +function stripNonWhite(value) {
215 + return value.replace(re, function() {
216 + return '';
217 + });
218 +}
219 +/**
220 + * Catches up as `catchup` but turns each non-white character into a space.
221 + */
222 +function catchupWhiteSpace(end, state) {
223 + catchup(end, state, stripNonWhite);
224 +}
225 +
226 +/**
227 + * Same as catchup but does not touch the buffer
228 + * @param {Number} end
229 + * @param {Object} state
230 + */
231 +function move(end, state) {
232 + // move the internal cursors
233 + if (state.g.sourceMap) {
234 + if (end < state.g.position) {
235 + state.g.position = 0;
236 + state.g.sourceLine = 1;
237 + state.g.sourceColumn = 0;
238 + }
239 +
240 + var source = state.g.source.substring(state.g.position, end);
241 + var sourceLines = source.split('\n');
242 + if (sourceLines.length > 1) {
243 + state.g.sourceLine += sourceLines.length - 1;
244 + state.g.sourceColumn = 0;
245 + }
246 + state.g.sourceColumn += sourceLines[sourceLines.length - 1].length;
247 + }
248 + state.g.position = end;
249 +}
250 +
251 +/**
252 + * Appends a string of text to the buffer
253 + * @param {String} string
254 + * @param {Object} state
255 + */
256 +function append(string, state) {
257 + if (state.g.sourceMap && string) {
258 + state.g.sourceMap.addMapping({
259 + generated: { line: state.g.bufferLine, column: state.g.bufferColumn },
260 + original: { line: state.g.sourceLine, column: state.g.sourceColumn },
261 + source: state.g.sourceMapFilename
262 + });
263 + var transformedLines = string.split('\n');
264 + if (transformedLines.length > 1) {
265 + state.g.bufferLine += transformedLines.length - 1;
266 + state.g.bufferColumn = 0;
267 + }
268 + state.g.bufferColumn +=
269 + transformedLines[transformedLines.length - 1].length;
270 + }
271 + state.g.buffer += string;
272 +}
273 +
274 +/**
275 + * Update indent using state.indentBy property. Indent is measured in
276 + * double spaces. Updates a single line only.
277 + *
278 + * @param {String} str
279 + * @param {Object} state
280 + * @return {String}
281 + */
282 +function updateIndent(str, state) {
283 + for (var i = 0; i < -state.g.indentBy; i++) {
284 + str = str.replace(/(^|\n)( {2}|\t)/g, '$1');
285 + }
286 + return str;
287 +}
288 +
289 +/**
290 + * Calculates indent from the beginning of the line until "start" or the first
291 + * character before start.
292 + * @example
293 + * " foo.bar()"
294 + * ^
295 + * start
296 + * indent will be 2
297 + *
298 + * @param {Number} start
299 + * @param {Object} state
300 + * @return {Number}
301 + */
302 +function indentBefore(start, state) {
303 + var end = start;
304 + start = start - 1;
305 +
306 + while (start > 0 && state.g.source[start] != '\n') {
307 + if (!state.g.source[start].match(/[ \t]/)) {
308 + end = start;
309 + }
310 + start--;
311 + }
312 + return state.g.source.substring(start + 1, end);
313 +}
314 +
315 +function getDocblock(state) {
316 + if (!state.g.docblock) {
317 + var docblock = require('./docblock');
318 + state.g.docblock =
319 + docblock.parseAsObject(docblock.extract(state.g.source));
320 + }
321 + return state.g.docblock;
322 +}
323 +
324 +exports.catchup = catchup;
325 +exports.catchupWhiteSpace = catchupWhiteSpace;
326 +exports.append = append;
327 +exports.move = move;
328 +exports.updateIndent = updateIndent;
329 +exports.indentBefore = indentBefore;
330 +exports.updateState = updateState;
331 +exports.createState = createState;
332 +exports.getDocblock = getDocblock;
vendor/fbtransform/syntax.js new
+82
@@ -0,0 +1,82 @@
1 +/*global process:false*/
2 +/*global module:true*/
3 +/*global exports:true*/
4 +"use strict";
5 +
6 +var transform = require('./lib/transform').transform;
7 +var visitors = require('./visitors');
8 +
9 +/**
10 + * @typechecks
11 + * @param {string} source
12 + * @param {object?} options
13 + * @param {array?} excludes
14 + * @return {string}
15 + */
16 +function transformAll(source, options, excludes) {
17 + excludes = excludes || [];
18 +
19 + // The typechecker transform must run in a second pass in order to operate on
20 + // the entire source code -- so exclude it from the first pass
21 + var visitorsList = visitors.getVisitorsList(excludes.concat('typechecker'));
22 + source = transform(visitorsList, source, options);
23 + if (excludes.indexOf('typechecks') == -1 && /@typechecks/.test(source.code)) {
24 + source = transform(
25 + visitors.transformVisitors.typechecker,
26 + source.code,
27 + options
28 + );
29 + }
30 + return source;
31 +}
32 +
33 +function runCli(argv) {
34 + var options = {};
35 + for (var optName in argv) {
36 + if (optName === '_' || optName === '$0') {
37 + continue;
38 + }
39 + options[optName] = optimist.argv[optName];
40 + }
41 +
42 + if (options.help) {
43 + optimist.showHelp();
44 + process.exit(0);
45 + }
46 +
47 + var excludes = options.excludes;
48 + delete options.excludes;
49 +
50 + var source = '';
51 + process.stdin.resume();
52 + process.stdin.setEncoding('utf8');
53 + process.stdin.on('data', function (chunk) {
54 + source += chunk;
55 + });
56 + process.stdin.on('end', function () {
57 + source = transformAll(source, options, excludes);
58 + process.stdout.write(source.code);
59 + });
60 +}
61 +
62 +if (require.main === module) {
63 + var optimist = require('optimist');
64 +
65 + optimist = optimist
66 + .usage('Usage: $0 [options]')
67 + .default('exclude', [])
68 + .boolean('help').alias('h', 'help')
69 + .boolean('minify')
70 + .describe(
71 + 'minify',
72 + 'Best-effort minification of the output source (when possible)'
73 + )
74 + .describe(
75 + 'exclude',
76 + 'A list of transformNames to exclude'
77 + );
78 +
79 + runCli(optimist.argv);
80 +} else {
81 + exports.transformAll = transformAll;
82 +}
vendor/fbtransform/transforms/classes.js new
+492
@@ -0,0 +1,492 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +/*global exports:true*/
17 +"use strict";
18 +
19 +/**
20 + * Desugarizer for ES6 minimal class proposal. See
21 + * http://wiki.ecmascript.org/doku.php?id=harmony:proposals
22 + *
23 + * Does not require any runtime. Preserves whitespace and comments.
24 + * Supports a class declaration with methods, super calls and inheritance.
25 + * Currently does not support for getters and setters, since there's a very
26 + * low probability we're going to use them anytime soon.
27 + *
28 + * Additional features:
29 + * - Any member with private name (the name with prefix _, such _name) inside
30 + * the class's scope will be munged. This would will to eliminate the case
31 + * of sub-class accidentally overriding the super-class's provate properties
32 + * also discouage people from accessing private members that they should not
33 + * access. However, quoted property names don't get munged.
34 + *
35 + * class SkinnedMesh extends require('THREE').Mesh {
36 + *
37 + * update(camera) {
38 + * camera.code = 'iphone'
39 + * super.update(camera);
40 + * }
41 + *
42 + * /
43 + * * @constructor
44 + * /
45 + * constructor(geometry, materials) {
46 + * super(geometry, materials);
47 + *
48 + * super.update(1);
49 + *
50 + * this.identityMatrix = new THREE.Matrix4();
51 + * this.bones = [];
52 + * this.boneMatrices = [];
53 + * this._name = 'foo';
54 + * }
55 + *
56 + * /
57 + * * some other code
58 + * /
59 + * readMore() {
60 + *
61 + * }
62 + *
63 + * _doSomething() {
64 + *
65 + * }
66 + * }
67 + *
68 + * should be converted to
69 + *
70 + * var SkinnedMesh = (function() {
71 + * var __super = require('parent').Mesh;
72 + *
73 + * /
74 + * * @constructor
75 + * /
76 + * function SkinnedMesh(geometry, materials) {
77 + * __super.call(this, geometry, materials);
78 + *
79 + * __super.prototype.update.call(this, 1);
80 + *
81 + * this.identityMatrix = new THREE.Matrix4();
82 + * this.bones = [];
83 + * this.boneMatrices = [];
84 + * this.$SkinnedMesh_name = 'foo';
85 + * }
86 + * SkinnedMesh.prototype = Object.create(__super.prototype);
87 + * SkinnedMesh.prototype.constructor = SkinnedMesh;
88 + *
89 + * /
90 + * * @param camera
91 + * /
92 + * SkinnedMesh.prototype.update = function(camera) {
93 + * camera.code = 'iphone'
94 + * __super.prototype.update.call(this, camera);
95 + * };
96 + *
97 + * SkinnedMesh.prototype.readMore = function() {
98 + *
99 + * };
100 + *
101 + * SkinnedMesh.prototype.$SkinnedMesh_doSomething = function() {
102 + *
103 + * };
104 + *
105 + * return SkinnedMesh;
106 + * })();
107 + *
108 + */
109 +var Syntax = require('esprima').Syntax;
110 +var base62 = require('base62');
111 +
112 +var catchup = require('../lib/utils').catchup;
113 +var append = require('../lib/utils').append;
114 +var move = require('../lib/utils').move;
115 +var indentBefore = require('../lib/utils').indentBefore;
116 +var updateIndent = require('../lib/utils').updateIndent;
117 +var updateState = require('../lib/utils').updateState;
118 +
119 +function findConstructorIndex(object) {
120 + var classElements = object.body && object.body.body || [];
121 + for (var i = 0; i < classElements.length; i++) {
122 + if (classElements[i].type === Syntax.MethodDefinition &&
123 + classElements[i].key.name === 'constructor') {
124 + return i;
125 + }
126 + }
127 + return -1;
128 +}
129 +
130 +var _mungedSymbolMaps = {};
131 +function getMungedName(scopeName, name, minify) {
132 + if (minify) {
133 + if (!_mungedSymbolMaps[scopeName]) {
134 + _mungedSymbolMaps[scopeName] = {
135 + symbolMap: {},
136 + identifierUUIDCounter: 0
137 + };
138 + }
139 +
140 + var symbolMap = _mungedSymbolMaps[scopeName].symbolMap;
141 + if (!symbolMap[name]) {
142 + symbolMap[name] =
143 + base62.encode(_mungedSymbolMaps[scopeName].identifierUUIDCounter);
144 + _mungedSymbolMaps[scopeName].identifierUUIDCounter++;
145 + }
146 + name = symbolMap[name];
147 + }
148 + return '$' + scopeName + name;
149 +}
150 +
151 +function shouldMungeName(scopeName, name, state) {
152 + // only run when @preventMunge is not present in the docblock
153 + if (state.g.preventMunge === undefined) {
154 + var docblock = require('../lib/docblock');
155 + state.g.preventMunge = docblock.parseAsObject(
156 + docblock.extract(state.g.source)).preventMunge !== undefined;
157 + }
158 + // Starts with only a single underscore (i.e. don't count double-underscores)
159 + return !state.g.preventMunge && scopeName ? /^_(?!_)/.test(name) : false;
160 +}
161 +
162 +
163 +function getProtoOfPrototypeVariableName(superVar) {
164 + return superVar + 'ProtoOfPrototype';
165 +}
166 +
167 +function getSuperKeyName(superVar) {
168 + return superVar + 'Key';
169 +}
170 +
171 +function getSuperProtoOfPrototypeVariable(superVariableName, indent) {
172 + var string = (indent +
173 + 'var $proto = $superName && $superName.prototype ? ' +
174 + '$superName.prototype : $superName;\n'
175 + ).replace(/\$proto/g, getProtoOfPrototypeVariableName(superVariableName))
176 + .replace(/\$superName/g, superVariableName);
177 + return string;
178 +}
179 +
180 +
181 +function getInheritanceSetup(superClassToken, className, indent, superName) {
182 + var string = '';
183 + if (superClassToken) {
184 + string += getStaticMethodsOnConstructorSetup(className, indent, superName);
185 + string += getPrototypeOnConstructorSetup(className, indent, superName);
186 + string += getConstructorPropertySetup(className, indent);
187 + }
188 + return string;
189 +}
190 +
191 +function getStaticMethodsOnConstructorSetup(className, indent, superName) {
192 + var string = ( indent +
193 + 'for (var $keyName in $superName) {\n' + indent +
194 + ' if ($superName.hasOwnProperty($keyName)) {\n' + indent +
195 + ' $className[$keyName] = $superName[$keyName];\n' + indent +
196 + ' }\n' + indent +
197 + '}\n')
198 + .replace(/\$className/g, className)
199 + .replace(/\$keyName/g, getSuperKeyName(superName))
200 + .replace(/\$superName/g, superName);
201 + return string;
202 +}
203 +
204 +function getPrototypeOnConstructorSetup(className, indent, superName) {
205 + var string = ( indent +
206 + '$className.prototype = Object.create($protoPrototype);\n')
207 + .replace(/\$protoPrototype/g, getProtoOfPrototypeVariableName(superName))
208 + .replace(/\$className/g, className);
209 + return string;
210 +}
211 +
212 +function getConstructorPropertySetup(className, indent) {
213 + var string = ( indent +
214 + '$className.prototype.constructor = $className;\n')
215 + .replace(/\$className/g, className);
216 +
217 + return string;
218 +}
219 +
220 +function getSuperConstructorSetup(superClassToken, indent, superName) {
221 + if (!superClassToken) return '';
222 + var string = ( '\n' + indent +
223 + ' if ($superName && $superName.prototype) {\n' + indent +
224 + ' $superName.apply(this, arguments);\n' + indent +
225 + ' }\n' + indent)
226 + .replace(/\$superName/g, superName);
227 + return string;
228 +}
229 +
230 +function getMemberFunctionCall(superVar, propertyName, superArgs) {
231 + var string = (
232 + '$superPrototype.$propertyName.call($superArguments)')
233 + .replace(/\$superPrototype/g, getProtoOfPrototypeVariableName(superVar))
234 + .replace(/\$propertyName/g, propertyName)
235 + .replace(/\$superArguments/g, superArgs);
236 + return string;
237 +}
238 +
239 +function getCallParams(classElement, state) {
240 + var params = classElement.value.params;
241 + if (!params.length) {
242 + return '';
243 + }
244 + return state.g.source.substring(
245 + params[0].range[0],
246 + params[params.length - 1].range[1]);
247 +}
248 +
249 +function getSuperArguments(callExpression, state) {
250 + var args = callExpression.arguments;
251 + if (!args.length) {
252 + return 'this';
253 + }
254 + return 'this, ' + state.g.source.substring(
255 + args[0].range[0],
256 + args[args.length - 1].range[1]);
257 +}
258 +
259 +// The seed is used to generate the name for an anonymous class,
260 +// and this seed should be unique per browser's session.
261 +// The value of the seed looks like this: 1229588505.2969012.
262 +var classIDSeed = Date.now() % (60 * 60 * 1000) + Math.random();
263 +
264 +/**
265 + * Generates a name for an anonymous class. The generated value looks like
266 + * this: "Classkc6pcn_mniza1yvi"
267 + * @param {String} scopeName
268 + * @return {string} the scope name for Anonymous Class
269 + */
270 +function generateAnonymousClassName(scopeName) {
271 + classIDSeed++;
272 + return 'Class' +
273 + (classIDSeed).toString(36).replace('.', '_') +
274 + (scopeName || '');
275 +}
276 +
277 +function renderMethods(traverse, object, name, path, state) {
278 + var classElements = object.body && object.body.body || [];
279 +
280 + move(object.body.range[0] + 1, state);
281 + for (var i = 0; i < classElements.length; i++) {
282 + if (classElements[i].key.name !== 'constructor') {
283 + catchup(classElements[i].range[0], state);
284 +
285 + var memberName = classElements[i].key.name;
286 + if (shouldMungeName(state.scopeName, memberName, state)) {
287 + memberName = getMungedName(
288 + state.scopeName,
289 + memberName,
290 + state.g.opts.minify
291 + );
292 + }
293 +
294 + var prototypeOrStatic;
295 + if (classElements[i]['static']) {
296 + prototypeOrStatic = '';
297 + } else {
298 + prototypeOrStatic = 'prototype.';
299 + }
300 +
301 + append(name + '.' + prototypeOrStatic + memberName + ' = ', state);
302 + renderMethod(traverse, classElements[i], null, path, state);
303 + append(';', state);
304 + }
305 + move(classElements[i].range[1], state);
306 + }
307 + if (classElements.length) {
308 + append('\n', state);
309 + }
310 + move(object.range[1], state);
311 +}
312 +
313 +function renderMethod(traverse, method, name, path, state) {
314 + append(name ? 'function ' + name + '(' : 'function(', state);
315 + append(getCallParams(method, state) + ') {', state);
316 + move(method.value.body.range[0] + 1, state);
317 + traverse(method.value.body, path, state);
318 + catchup(method.value.body.range[1] - 1, state);
319 + append('}', state);
320 +}
321 +
322 +function renderSuperClass(traverse, superClass, path, state) {
323 + append('var ' + state.superVar + ' = ', state);
324 + move(superClass.range[0], state);
325 + traverse(superClass, path, state);
326 + catchup(superClass.range[1], state);
327 + append(';\n', state);
328 +}
329 +
330 +function renderConstructor(traverse, object, name, indent, path, state) {
331 + var classElements = object.body && object.body.body || [];
332 + var constructorIndex = findConstructorIndex(object);
333 + var constructor = constructorIndex === -1 ?
334 + null :
335 + classElements[constructorIndex];
336 + if (constructor) {
337 + move(constructorIndex === 0 ?
338 + object.body.range[0] + 1 :
339 + classElements[constructorIndex - 1].range[1], state);
340 + catchup(constructor.range[0], state);
341 + renderMethod(traverse, constructor, name, path, state);
342 + append('\n', state);
343 + } else {
344 + if (object.superClass) {
345 + append('\n' + indent, state);
346 + }
347 + append('function ', state);
348 + if (object.id) {
349 + move(object.id.range[0], state);
350 + }
351 + append(name, state);
352 + if (object.id) {
353 + move(object.id.range[1], state);
354 + }
355 + append('(){ ', state);
356 + if (object.body) {
357 + move(object.body.range[0], state);
358 + }
359 + append(getSuperConstructorSetup(
360 + object.superClass,
361 + indent,
362 + state.superVar), state);
363 + append('}\n', state);
364 + }
365 +}
366 +
367 +var superId = 0;
368 +function renderClassBody(traverse, object, path, state) {
369 + var name = object.id ? object.id.name : 'constructor';
370 + var superClass = object.superClass;
371 + var indent = updateIndent(
372 + indentBefore(object.range[0], state) + ' ',
373 + state);
374 +
375 + state = updateState(
376 + state,
377 + {
378 + scopeName: object.id ? object.id.name :
379 + generateAnonymousClassName(state.scopeName),
380 + superVar: superClass ? '__super' + superId++ : ''
381 + });
382 +
383 + // super class
384 + if (superClass) {
385 + append(indent, state);
386 + renderSuperClass(traverse, superClass, path, state);
387 + append(getSuperProtoOfPrototypeVariable(state.superVar, indent), state);
388 + }
389 +
390 + renderConstructor(traverse, object, name, indent, path, state);
391 + append(getInheritanceSetup(superClass, name, indent, state.superVar), state);
392 + renderMethods(traverse, object, name, path, state);
393 +}
394 +
395 +
396 +/**
397 + * @public
398 + */
399 +function visitClassExpression(traverse, object, path, state) {
400 + var indent = updateIndent(
401 + indentBefore(object.range[0], state) + ' ',
402 + state);
403 + var name = object.id ? object.id.name : 'constructor';
404 +
405 + append('(function() {\n', state);
406 + renderClassBody(traverse, object, path, state);
407 + append(indent + 'return ' + name + ';\n', state);
408 + append(indent.substring(0, indent.length - 2) + '})()', state);
409 + return false
410 +}
411 +
412 +visitClassExpression.test = function(object, path, state) {
413 + return object.type === Syntax.ClassExpression;
414 +};
415 +
416 +/**
417 + * @public
418 + */
419 +function visitClassDeclaration(traverse, object, path, state) {
420 + state.g.indentBy--;
421 + renderClassBody(traverse, object, path, state);
422 + state.g.indentBy++;
423 + return false;
424 +}
425 +
426 +visitClassDeclaration.test = function(object, path, state) {
427 + return object.type === Syntax.ClassDeclaration;
428 +};
429 +
430 +
431 +/**
432 + * @public
433 + */
434 +function visitSuperCall(traverse, object, path, state) {
435 + if (path[0].type === Syntax.CallExpression) {
436 + append(state.superVar +
437 + '.call(' + getSuperArguments(path[0], state) + ')', state);
438 + move(path[0].range[1], state);
439 + } else if (path[0].type === Syntax.MemberExpression) {
440 + append(getMemberFunctionCall(
441 + state.superVar,
442 + path[0].property.name,
443 + getSuperArguments(path[1], state)), state);
444 + move(path[1].range[1], state);
445 + }
446 + return false;
447 +}
448 +
449 +visitSuperCall.test = function(object, path, state) {
450 + return state.superVar && object.type === Syntax.Identifier &&
451 + object.name === 'super';
452 +};
453 +
454 +/**
455 + * @public
456 + */
457 +function visitPrivateProperty(traverse, object, path, state) {
458 + var type = path[0] ? path[0].type : null;
459 + if (type !== Syntax.Property) {
460 + if (type === Syntax.MemberExpression) {
461 + type = path[0].object ? path[0].object.type : null;
462 + if (type === Syntax.Identifier &&
463 + path[0].object.range[0] === object.range[0]) {
464 + // Identifier is a variable that appears "private".
465 + return;
466 + }
467 + } else {
468 + // Other syntax that are neither Property nor MemberExpression.
469 + return;
470 + }
471 + }
472 +
473 + var oldName = object.name;
474 + var newName = getMungedName(
475 + state.scopeName,
476 + oldName,
477 + state.g.opts.minify
478 + );
479 + append(newName, state);
480 + move(object.range[1], state);
481 +}
482 +
483 +visitPrivateProperty.test = function(object, path, state) {
484 + return object.type === Syntax.Identifier &&
485 + shouldMungeName(state.scopeName, object.name, state);
486 +};
487 +
488 +
489 +exports.visitClassDeclaration = visitClassDeclaration;
490 +exports.visitClassExpression = visitClassExpression;
491 +exports.visitSuperCall = visitSuperCall;
492 +exports.visitPrivateProperty = visitPrivateProperty;
vendor/fbtransform/transforms/react.js new
+198
@@ -0,0 +1,198 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +/*global exports:true*/
17 +"use strict";
18 +
19 +var Syntax = require('esprima').Syntax;
20 +
21 +var catchup = require('../lib/utils').catchup;
22 +var append = require('../lib/utils').append;
23 +var move = require('../lib/utils').move;
24 +var getDocblock = require('../lib/utils').getDocblock;
25 +
26 +var FALLBACK_TAGS = require('./xjs').knownTags;
27 +var renderXJSExpression = require('./xjs').renderXJSExpression;
28 +var renderXJSLiteral = require('./xjs').renderXJSLiteral;
29 +var quoteAttrName = require('./xjs').quoteAttrName;
30 +
31 +/**
32 + * Customized desugar processor.
33 + *
34 + * Currently: (Somewhat tailored to React)
35 + * <X> </X> => X(null, null)
36 + * <X prop="1" /> => X({prop: '1'}, null)
37 + * <X prop="2"><Y /></X> => X({prop:'2'}, Y(null, null))
38 + * <X prop="2"><Y /><Z /></X> => X({prop:'2'}, [Y(null, null), Z(null, null)])
39 + *
40 + * Exceptions to the simple rules above:
41 + * if a property is named "class" it will be changed to "className" in the
42 + * javascript since "class" is not a valid object key in javascript.
43 + */
44 +
45 +var JSX_ATTRIBUTE_RENAMES = {
46 + 'class': 'className',
47 + cxName: 'className'
48 +};
49 +
50 +var JSX_ATTRIBUTE_TRANSFORMS = {
51 + cxName: function(attr) {
52 + if (attr.value.type !== Syntax.Literal) {
53 + throw new Error("cx only accepts a string literal");
54 + } else {
55 + var classNames = attr.value.value.split(/\s+/g);
56 + return 'cx(' + classNames.map(JSON.stringify).join(',') + ')';
57 + }
58 + }
59 +};
60 +
61 +function visitReactTag(traverse, object, path, state) {
62 + var jsxObjIdent = getDocblock(state).jsx;
63 +
64 + catchup(object.openingElement.range[0], state);
65 +
66 + if (object.name.namespace) {
67 + throw new Error(
68 + 'Namespace tags are not supported. ReactJSX is not XML.');
69 + }
70 +
71 + var isFallbackTag = FALLBACK_TAGS[object.name.name];
72 + append(
73 + (isFallbackTag ? jsxObjIdent + '.' : '') + (object.name.name) + '(',
74 + state
75 + );
76 +
77 + move(object.name.range[1], state);
78 +
79 + var childrenToRender = object.children.filter(function(child) {
80 + return !(child.type === Syntax.Literal && !child.value.match(/\S/));
81 + });
82 +
83 + // if we don't have any attributes, pass in null
84 + if (object.attributes.length === 0) {
85 + append('null', state);
86 + }
87 +
88 + // write attributes
89 + object.attributes.forEach(function(attr, index) {
90 + catchup(attr.range[0], state);
91 + if (attr.name.namespace) {
92 + throw new Error(
93 + 'Namespace attributes are not supported. ReactJSX is not XML.');
94 + }
95 + var name = JSX_ATTRIBUTE_RENAMES[attr.name.name] || attr.name.name;
96 + var isFirst = index === 0;
97 + var isLast = index === object.attributes.length - 1;
98 +
99 + if (isFirst) {
100 + append('{', state);
101 + }
102 +
103 + append(quoteAttrName(name), state);
104 + append(':', state);
105 +
106 + if (!attr.value) {
107 + state.g.buffer += 'true';
108 + state.g.position = attr.name.range[1];
109 + if (!isLast) {
110 + append(',', state);
111 + }
112 + } else if (JSX_ATTRIBUTE_TRANSFORMS[attr.name.name]) {
113 + move(attr.value.range[0], state);
114 + append(JSX_ATTRIBUTE_TRANSFORMS[attr.name.name](attr), state);
115 + move(attr.value.range[1], state);
116 + if (!isLast) {
117 + append(',', state);
118 + }
119 + } else if (attr.value.type === Syntax.Literal) {
120 + move(attr.value.range[0], state);
121 + renderXJSLiteral(attr.value, isLast, state);
122 + } else {
123 + move(attr.value.range[0], state);
124 + renderXJSExpression(traverse, attr.value, isLast, path, state);
125 + }
126 +
127 + if (isLast) {
128 + append('}', state);
129 + }
130 +
131 + catchup(attr.range[1], state);
132 + });
133 +
134 + if (!object.selfClosing) {
135 + catchup(object.openingElement.range[1] - 1, state);
136 + move(object.openingElement.range[1], state);
137 + }
138 +
139 + // separate props and children arguments
140 + append(', ', state);
141 +
142 + // filter out whitespace
143 + if (childrenToRender.length > 0) {
144 + if (childrenToRender.length > 1) {
145 + append('[', state);
146 + }
147 + object.children.forEach(function(child) {
148 + if (child.type === Syntax.Literal && !child.value.match(/\S/)) {
149 + return;
150 + }
151 + catchup(child.range[0], state);
152 +
153 + var isLast = child === childrenToRender[childrenToRender.length - 1];
154 +
155 + if (child.type === Syntax.Literal) {
156 + renderXJSLiteral(child, isLast, state);
157 + } else if (child.type === Syntax.XJSExpression) {
158 + renderXJSExpression(traverse, child, isLast, path, state);
159 + } else {
160 + traverse(child, path, state);
161 + if (!isLast) {
162 + append(',', state);
163 + state.g.buffer = state.g.buffer.replace(/(\s*),$/, ',$1');
164 + }
165 + }
166 +
167 + catchup(child.range[1], state);
168 + });
169 + } else {
170 + append('null', state);
171 + }
172 +
173 + if (object.selfClosing) {
174 + // everything up to />
175 + catchup(object.openingElement.range[1] - 2, state);
176 + move(object.openingElement.range[1], state);
177 + } else {
178 + // everything up to </ sdflksjfd>
179 + catchup(object.closingElement.range[0], state);
180 + move(object.closingElement.range[1], state);
181 + }
182 +
183 + if (childrenToRender.length > 0) {
184 + if (childrenToRender.length > 1) {
185 + append(']', state);
186 + }
187 + }
188 + append(')', state);
189 + return false;
190 +}
191 +
192 +visitReactTag.test = function(object, path, state) {
193 + // only run react when react @jsx namespace is specified in docblock
194 + var jsx = getDocblock(state).jsx;
195 + return object.type === Syntax.XJSElement && jsx && jsx.length;
196 +};
197 +
198 +exports.visitReactTag = visitReactTag;
vendor/fbtransform/transforms/reactDisplayName.js new
+67
@@ -0,0 +1,67 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +/*global exports:true*/
17 +"use strict";
18 +
19 +var Syntax = require('esprima').Syntax;
20 +var catchup = require('../lib/utils').catchup;
21 +var append = require('../lib/utils').append;
22 +var getDocblock = require('../lib/utils').getDocblock;
23 +
24 +/**
25 + * Transforms the following:
26 + *
27 + * var MyComponent = React.createClass({
28 + * render: ...
29 + * });
30 + *
31 + * into:
32 + *
33 + * var MyComponent = React.createClass({
34 + * displayName: 'MyComponent',
35 + * render: ...
36 + * });
37 + */
38 +function visitReactDisplayName(traverse, object, path, state) {
39 + object.declarations.forEach(function(dec) {
40 + if (dec.type === Syntax.VariableDeclarator &&
41 + dec.id.type === Syntax.Identifier &&
42 + dec.init &&
43 + dec.init.type === Syntax.CallExpression &&
44 + dec.init.callee.type === Syntax.MemberExpression &&
45 + dec.init.callee.object.type === Syntax.Identifier &&
46 + dec.init.callee.object.name === 'React' &&
47 + dec.init.callee.property.type === Syntax.Identifier &&
48 + dec.init.callee.property.name === 'createClass' &&
49 + dec.init['arguments'].length === 1 &&
50 + dec.init['arguments'][0].type === Syntax.ObjectExpression) {
51 +
52 + var displayName = dec.id.name;
53 + catchup(dec.init['arguments'][0].range[0] + 1, state);
54 + append("displayName: '" + displayName + "',", state);
55 + }
56 + });
57 +}
58 +
59 +
60 +/**
61 + * Will only run on @jsx files for now.
62 + */
63 +visitReactDisplayName.test = function(object, path, state) {
64 + return object.type === Syntax.VariableDeclaration && !!getDocblock(state).jsx;
65 +};
66 +
67 +exports.visitReactDisplayName = visitReactDisplayName;
vendor/fbtransform/transforms/xjs.js new
+284
@@ -0,0 +1,284 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +/*global exports:true*/
17 +"use strict";
18 +var catchup = require('../lib/utils').catchup;
19 +var append = require('../lib/utils').append;
20 +var move = require('../lib/utils').move;
21 +
22 +var knownTags = {
23 + a: true,
24 + abbr: true,
25 + address: true,
26 + applet: true,
27 + area: true,
28 + article: true,
29 + aside: true,
30 + audio: true,
31 + b: true,
32 + base: true,
33 + bdi: true,
34 + bdo: true,
35 + blockquote: true,
36 + body: true,
37 + br: true,
38 + button: true,
39 + canvas: true,
40 + circle: true,
41 + ellipse: true,
42 + caption: true,
43 + cite: true,
44 + code: true,
45 + col: true,
46 + colgroup: true,
47 + command: true,
48 + data: true,
49 + datalist: true,
50 + dd: true,
51 + del: true,
52 + details: true,
53 + dfn: true,
54 + dialog: true,
55 + div: true,
56 + dl: true,
57 + dt: true,
58 + em: true,
59 + embed: true,
60 + fieldset: true,
61 + figcaption: true,
62 + figure: true,
63 + footer: true,
64 + form: true,
65 + g: true,
66 + h1: true,
67 + h2: true,
68 + h3: true,
69 + h4: true,
70 + h5: true,
71 + h6: true,
72 + head: true,
73 + header: true,
74 + hgroup: true,
75 + hr: true,
76 + html: true,
77 + i: true,
78 + iframe: true,
79 + img: true,
80 + input: true,
81 + ins: true,
82 + kbd: true,
83 + keygen: true,
84 + label: true,
85 + legend: true,
86 + li: true,
87 + line: true,
88 + link: true,
89 + map: true,
90 + mark: true,
91 + marquee: true,
92 + menu: true,
93 + meta: true,
94 + meter: true,
95 + nav: true,
96 + noscript: true,
97 + object: true,
98 + ol: true,
99 + optgroup: true,
100 + option: true,
101 + output: true,
102 + p: true,
103 + path: true,
104 + param: true,
105 + pre: true,
106 + progress: true,
107 + q: true,
108 + rect: true,
109 + rp: true,
110 + rt: true,
111 + ruby: true,
112 + s: true,
113 + samp: true,
114 + script: true,
115 + section: true,
116 + select: true,
117 + small: true,
118 + source: true,
119 + span: true,
120 + strong: true,
121 + style: true,
122 + sub: true,
123 + summary: true,
124 + sup: true,
125 + svg: true,
126 + table: true,
127 + tbody: true,
128 + td: true,
129 + text: true,
130 + textarea: true,
131 + tfoot: true,
132 + th: true,
133 + thead: true,
134 + time: true,
135 + title: true,
136 + tr: true,
137 + track: true,
138 + u: true,
139 + ul: true,
140 + 'var': true,
141 + video: true,
142 + wbr: true
143 +};
144 +
145 +function safeTrim(string) {
146 + return string.replace(/^[ \t]+/, '').replace(/[ \t]+$/, '');
147 +}
148 +
149 +// Replace all trailing whitespace characters with a single space character
150 +function trimWithSingleSpace(string) {
151 + return string.replace(/^[ \t\xA0]{2,}/, ' ').
152 + replace(/[ \t\xA0]{2,}$/, ' ').replace(/^\s+$/, '');
153 +}
154 +
155 +/**
156 + * Special handling for multiline string literals
157 + * print lines:
158 + *
159 + * line
160 + * line
161 + *
162 + * as:
163 + *
164 + * "line "+
165 + * "line"
166 + */
167 +function renderXJSLiteral(object, isLast, state, start, end) {
168 + /** Added blank check filtering and triming*/
169 + var trimmedChildValue = safeTrim(object.value);
170 +
171 + if (trimmedChildValue) {
172 + // head whitespace
173 + append(object.value.match(/^[\t ]*/)[0], state);
174 + if (start) {
175 + append(start, state);
176 + }
177 +
178 + var trimmedChildValueWithSpace = trimWithSingleSpace(object.value);
179 +
180 + /**
181 + */
182 + var initialLines = trimmedChildValue.split(/\r\n|\n|\r/);
183 +
184 + var lines = initialLines.filter(function(line) {
185 + return safeTrim(line).length > 0;
186 + });
187 +
188 + var hasInitialNewLine = initialLines[0] !== lines[0];
189 + var hasFinalNewLine =
190 + initialLines[initialLines.length - 1] !== lines[lines.length - 1];
191 +
192 + var numLines = lines.length;
193 + lines.forEach(function (line, ii) {
194 + var lastLine = ii === numLines - 1;
195 + var trimmedLine = safeTrim(line);
196 + if (trimmedLine === '' && !lastLine) {
197 + append(line, state);
198 + } else {
199 + var preString = '';
200 + var postString = '';
201 + var leading = '';
202 +
203 + if (ii === 0) {
204 + if (hasInitialNewLine) {
205 + preString = ' ';
206 + leading = '\n';
207 + }
208 + if (trimmedChildValueWithSpace.substring(0, 1) === ' ') {
209 + // If this is the first line, and the original content starts with
210 + // whitespace, place a single space at the beginning.
211 + preString = ' ';
212 + }
213 + } else {
214 + leading = line.match(/^[ \t]*/)[0];
215 + }
216 + if (!lastLine || trimmedChildValueWithSpace.substr(
217 + trimmedChildValueWithSpace.length - 1, 1) === ' ' ||
218 + hasFinalNewLine
219 + ) {
220 + // If either not on the last line, or the original content ends with
221 + // whitespace, place a single character at the end.
222 + postString = ' ';
223 + }
224 +
225 + append(
226 + leading +
227 + JSON.stringify(
228 + preString + trimmedLine + postString
229 + ) +
230 + (lastLine ? '' : '+') +
231 + line.match(/[ \t]*$/)[0],
232 + state);
233 + }
234 + if (!lastLine) {
235 + append('\n', state);
236 + }
237 + });
238 + } else {
239 + if (start) {
240 + append(start, state);
241 + }
242 + append('""', state);
243 + }
244 + if (end) {
245 + append(end, state);
246 + }
247 +
248 + // add comma before trailing whitespace
249 + if (!isLast) {
250 + append(',', state);
251 + }
252 +
253 + // tail whitespace
254 + append(object.value.match(/[ \t]*$/)[0], state);
255 + move(object.range[1], state);
256 +}
257 +
258 +function renderXJSExpression(traverse, object, isLast, path, state) {
259 + // Plus 1 to skip `{`.
260 + move(object.range[0] + 1, state);
261 + traverse(object.value, path, state);
262 + if (!isLast) {
263 + // If we need to append a comma, make sure to do so after the expression.
264 + catchup(object.value.range[1], state);
265 + append(',', state);
266 + }
267 + // Minus 1 to skip `}`.
268 + catchup(object.range[1] - 1, state);
269 + move(object.range[1], state);
270 + return false;
271 +}
272 +
273 +function quoteAttrName(attr) {
274 + // Quote invalid JS identifiers.
275 + if (!/^[a-z_$][a-z\d_$]*$/i.test(attr)) {
276 + return "'" + attr + "'";
277 + }
278 + return attr;
279 +}
280 +
281 +exports.knownTags = knownTags;
282 +exports.renderXJSExpression = renderXJSExpression;
283 +exports.renderXJSLiteral = renderXJSLiteral;
284 +exports.quoteAttrName = quoteAttrName;
vendor/fbtransform/visitors.js new
+51
@@ -0,0 +1,51 @@
1 +/*global exports:true*/
2 +var classes = require('./transforms/classes');
3 +var react = require('./transforms/react');
4 +var reactDisplayName = require('./transforms/reactDisplayName');
5 +
6 +/**
7 + * Map from transformName => orderedListOfVisitors.
8 + */
9 +var transformVisitors = {
10 + 'es6-classes': [
11 + classes.visitClassExpression,
12 + classes.visitClassDeclaration,
13 + classes.visitSuperCall,
14 + classes.visitPrivateProperty
15 + ]
16 +};
17 +
18 +transformVisitors.react = transformVisitors[
19 + "es6-classes"
20 +].concat([
21 + react.visitReactTag,
22 + reactDisplayName.visitReactDisplayName
23 +]);
24 +
25 +/**
26 + * Specifies the order in which each transform should run.
27 + */
28 +var transformRunOrder = [
29 + 'es6-classes',
30 + 'react'
31 +];
32 +
33 +/**
34 + * Given a list of transform names, return the ordered list of visitors to be
35 + * passed to the transform() function.
36 + *
37 + * @param {array?} excludes
38 + * @return {array}
39 + */
40 +function getVisitorsList(excludes) {
41 + var ret = [];
42 + for (var i = 0, il = transformRunOrder.length; i < il; i++) {
43 + if (!excludes || excludes.indexOf(transformRunOrder[i]) === -1) {
44 + ret = ret.concat(transformVisitors[transformRunOrder[i]]);
45 + }
46 + }
47 + return ret;
48 +}
49 +
50 +exports.getVisitorsList = getVisitorsList;
51 +exports.transformVisitors = transformVisitors;
vendor/jasmine/jasmine-html.js new
+681
@@ -0,0 +1,681 @@
1 +jasmine.HtmlReporterHelpers = {};
2 +
3 +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
4 + var el = document.createElement(type);
5 +
6 + for (var i = 2; i < arguments.length; i++) {
7 + var child = arguments[i];
8 +
9 + if (typeof child === 'string') {
10 + el.appendChild(document.createTextNode(child));
11 + } else {
12 + if (child) {
13 + el.appendChild(child);
14 + }
15 + }
16 + }
17 +
18 + for (var attr in attrs) {
19 + if (attr == "className") {
20 + el[attr] = attrs[attr];
21 + } else {
22 + el.setAttribute(attr, attrs[attr]);
23 + }
24 + }
25 +
26 + return el;
27 +};
28 +
29 +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
30 + var results = child.results();
31 + var status = results.passed() ? 'passed' : 'failed';
32 + if (results.skipped) {
33 + status = 'skipped';
34 + }
35 +
36 + return status;
37 +};
38 +
39 +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
40 + var parentDiv = this.dom.summary;
41 + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
42 + var parent = child[parentSuite];
43 +
44 + if (parent) {
45 + if (typeof this.views.suites[parent.id] == 'undefined') {
46 + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
47 + }
48 + parentDiv = this.views.suites[parent.id].element;
49 + }
50 +
51 + parentDiv.appendChild(childElement);
52 +};
53 +
54 +
55 +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
56 + for(var fn in jasmine.HtmlReporterHelpers) {
57 + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
58 + }
59 +};
60 +
61 +jasmine.HtmlReporter = function(_doc) {
62 + var self = this;
63 + var doc = _doc || window.document;
64 +
65 + var reporterView;
66 +
67 + var dom = {};
68 +
69 + // Jasmine Reporter Public Interface
70 + self.logRunningSpecs = false;
71 +
72 + self.reportRunnerStarting = function(runner) {
73 + var specs = runner.specs() || [];
74 +
75 + if (specs.length == 0) {
76 + return;
77 + }
78 +
79 + createReporterDom(runner.env.versionString());
80 + doc.body.appendChild(dom.reporter);
81 + setExceptionHandling();
82 +
83 + reporterView = new jasmine.HtmlReporter.ReporterView(dom);
84 + reporterView.addSpecs(specs, self.specFilter);
85 + };
86 +
87 + self.reportRunnerResults = function(runner) {
88 + reporterView && reporterView.complete();
89 + };
90 +
91 + self.reportSuiteResults = function(suite) {
92 + reporterView.suiteComplete(suite);
93 + };
94 +
95 + self.reportSpecStarting = function(spec) {
96 + if (self.logRunningSpecs) {
97 + self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
98 + }
99 + };
100 +
101 + self.reportSpecResults = function(spec) {
102 + reporterView.specComplete(spec);
103 + };
104 +
105 + self.log = function() {
106 + var console = jasmine.getGlobal().console;
107 + if (console && console.log) {
108 + if (console.log.apply) {
109 + console.log.apply(console, arguments);
110 + } else {
111 + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
112 + }
113 + }
114 + };
115 +
116 + self.specFilter = function(spec) {
117 + if (!focusedSpecName()) {
118 + return true;
119 + }
120 +
121 + return spec.getFullName().indexOf(focusedSpecName()) === 0;
122 + };
123 +
124 + return self;
125 +
126 + function focusedSpecName() {
127 + var specName;
128 +
129 + (function memoizeFocusedSpec() {
130 + if (specName) {
131 + return;
132 + }
133 +
134 + var paramMap = [];
135 + var params = jasmine.HtmlReporter.parameters(doc);
136 +
137 + for (var i = 0; i < params.length; i++) {
138 + var p = params[i].split('=');
139 + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
140 + }
141 +
142 + specName = paramMap.spec;
143 + })();
144 +
145 + return specName;
146 + }
147 +
148 + function createReporterDom(version) {
149 + dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
150 + dom.banner = self.createDom('div', { className: 'banner' },
151 + self.createDom('span', { className: 'title' }, "Jasmine "),
152 + self.createDom('span', { className: 'version' }, version)),
153 +
154 + dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
155 + dom.alert = self.createDom('div', {className: 'alert'},
156 + self.createDom('span', { className: 'exceptions' },
157 + self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'),
158 + self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))),
159 + dom.results = self.createDom('div', {className: 'results'},
160 + dom.summary = self.createDom('div', { className: 'summary' }),
161 + dom.details = self.createDom('div', { id: 'details' }))
162 + );
163 + }
164 +
165 + function noTryCatch() {
166 + return window.location.search.match(/catch=false/);
167 + }
168 +
169 + function searchWithCatch() {
170 + var params = jasmine.HtmlReporter.parameters(window.document);
171 + var removed = false;
172 + var i = 0;
173 +
174 + while (!removed && i < params.length) {
175 + if (params[i].match(/catch=/)) {
176 + params.splice(i, 1);
177 + removed = true;
178 + }
179 + i++;
180 + }
181 + if (jasmine.CATCH_EXCEPTIONS) {
182 + params.push("catch=false");
183 + }
184 +
185 + return params.join("&");
186 + }
187 +
188 + function setExceptionHandling() {
189 + var chxCatch = document.getElementById('no_try_catch');
190 +
191 + if (noTryCatch()) {
192 + chxCatch.setAttribute('checked', true);
193 + jasmine.CATCH_EXCEPTIONS = false;
194 + }
195 + chxCatch.onclick = function() {
196 + window.location.search = searchWithCatch();
197 + };
198 + }
199 +};
200 +jasmine.HtmlReporter.parameters = function(doc) {
201 + var paramStr = doc.location.search.substring(1);
202 + var params = [];
203 +
204 + if (paramStr.length > 0) {
205 + params = paramStr.split('&');
206 + }
207 + return params;
208 +}
209 +jasmine.HtmlReporter.sectionLink = function(sectionName) {
210 + var link = '?';
211 + var params = [];
212 +
213 + if (sectionName) {
214 + params.push('spec=' + encodeURIComponent(sectionName));
215 + }
216 + if (!jasmine.CATCH_EXCEPTIONS) {
217 + params.push("catch=false");
218 + }
219 + if (params.length > 0) {
220 + link += params.join("&");
221 + }
222 +
223 + return link;
224 +};
225 +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);
226 +jasmine.HtmlReporter.ReporterView = function(dom) {
227 + this.startedAt = new Date();
228 + this.runningSpecCount = 0;
229 + this.completeSpecCount = 0;
230 + this.passedCount = 0;
231 + this.failedCount = 0;
232 + this.skippedCount = 0;
233 +
234 + this.createResultsMenu = function() {
235 + this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
236 + this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
237 + ' | ',
238 + this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
239 +
240 + this.summaryMenuItem.onclick = function() {
241 + dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
242 + };
243 +
244 + this.detailsMenuItem.onclick = function() {
245 + showDetails();
246 + };
247 + };
248 +
249 + this.addSpecs = function(specs, specFilter) {
250 + this.totalSpecCount = specs.length;
251 +
252 + this.views = {
253 + specs: {},
254 + suites: {}
255 + };
256 +
257 + for (var i = 0; i < specs.length; i++) {
258 + var spec = specs[i];
259 + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
260 + if (specFilter(spec)) {
261 + this.runningSpecCount++;
262 + }
263 + }
264 + };
265 +
266 + this.specComplete = function(spec) {
267 + this.completeSpecCount++;
268 +
269 + if (isUndefined(this.views.specs[spec.id])) {
270 + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
271 + }
272 +
273 + var specView = this.views.specs[spec.id];
274 +
275 + switch (specView.status()) {
276 + case 'passed':
277 + this.passedCount++;
278 + break;
279 +
280 + case 'failed':
281 + this.failedCount++;
282 + break;
283 +
284 + case 'skipped':
285 + this.skippedCount++;
286 + break;
287 + }
288 +
289 + specView.refresh();
290 + this.refresh();
291 + };
292 +
293 + this.suiteComplete = function(suite) {
294 + var suiteView = this.views.suites[suite.id];
295 + if (isUndefined(suiteView)) {
296 + return;
297 + }
298 + suiteView.refresh();
299 + };
300 +
301 + this.refresh = function() {
302 +
303 + if (isUndefined(this.resultsMenu)) {
304 + this.createResultsMenu();
305 + }
306 +
307 + // currently running UI
308 + if (isUndefined(this.runningAlert)) {
309 + this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" });
310 + dom.alert.appendChild(this.runningAlert);
311 + }
312 + this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
313 +
314 + // skipped specs UI
315 + if (isUndefined(this.skippedAlert)) {
316 + this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" });
317 + }
318 +
319 + this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
320 +
321 + if (this.skippedCount === 1 && isDefined(dom.alert)) {
322 + dom.alert.appendChild(this.skippedAlert);
323 + }
324 +
325 + // passing specs UI
326 + if (isUndefined(this.passedAlert)) {
327 + this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" });
328 + }
329 + this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
330 +
331 + // failing specs UI
332 + if (isUndefined(this.failedAlert)) {
333 + this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
334 + }
335 + this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
336 +
337 + if (this.failedCount === 1 && isDefined(dom.alert)) {
338 + dom.alert.appendChild(this.failedAlert);
339 + dom.alert.appendChild(this.resultsMenu);
340 + }
341 +
342 + // summary info
343 + this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
344 + this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
345 + };
346 +
347 + this.complete = function() {
348 + dom.alert.removeChild(this.runningAlert);
349 +
350 + this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
351 +
352 + if (this.failedCount === 0) {
353 + dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
354 + } else {
355 + showDetails();
356 + }
357 +
358 + dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
359 + };
360 +
361 + return this;
362 +
363 + function showDetails() {
364 + if (dom.reporter.className.search(/showDetails/) === -1) {
365 + dom.reporter.className += " showDetails";
366 + }
367 + }
368 +
369 + function isUndefined(obj) {
370 + return typeof obj === 'undefined';
371 + }
372 +
373 + function isDefined(obj) {
374 + return !isUndefined(obj);
375 + }
376 +
377 + function specPluralizedFor(count) {
378 + var str = count + " spec";
379 + if (count > 1) {
380 + str += "s"
381 + }
382 + return str;
383 + }
384 +
385 +};
386 +
387 +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
388 +
389 +
390 +jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
391 + this.spec = spec;
392 + this.dom = dom;
393 + this.views = views;
394 +
395 + this.symbol = this.createDom('li', { className: 'pending' });
396 + this.dom.symbolSummary.appendChild(this.symbol);
397 +
398 + this.summary = this.createDom('div', { className: 'specSummary' },
399 + this.createDom('a', {
400 + className: 'description',
401 + href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()),
402 + title: this.spec.getFullName()
403 + }, this.spec.description)
404 + );
405 +
406 + this.detail = this.createDom('div', { className: 'specDetail' },
407 + this.createDom('a', {
408 + className: 'description',
409 + href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
410 + title: this.spec.getFullName()
411 + }, this.spec.getFullName())
412 + );
413 +};
414 +
415 +jasmine.HtmlReporter.SpecView.prototype.status = function() {
416 + return this.getSpecStatus(this.spec);
417 +};
418 +
419 +jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
420 + this.symbol.className = this.status();
421 +
422 + switch (this.status()) {
423 + case 'skipped':
424 + break;
425 +
426 + case 'passed':
427 + this.appendSummaryToSuiteDiv();
428 + break;
429 +
430 + case 'failed':
431 + this.appendSummaryToSuiteDiv();
432 + this.appendFailureDetail();
433 + break;
434 + }
435 +};
436 +
437 +jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
438 + this.summary.className += ' ' + this.status();
439 + this.appendToSummary(this.spec, this.summary);
440 +};
441 +
442 +jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
443 + this.detail.className += ' ' + this.status();
444 +
445 + var resultItems = this.spec.results().getItems();
446 + var messagesDiv = this.createDom('div', { className: 'messages' });
447 +
448 + for (var i = 0; i < resultItems.length; i++) {
449 + var result = resultItems[i];
450 +
451 + if (result.type == 'log') {
452 + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
453 + } else if (result.type == 'expect' && result.passed && !result.passed()) {
454 + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
455 +
456 + if (result.trace.stack) {
457 + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
458 + }
459 + }
460 + }
461 +
462 + if (messagesDiv.childNodes.length > 0) {
463 + this.detail.appendChild(messagesDiv);
464 + this.dom.details.appendChild(this.detail);
465 + }
466 +};
467 +
468 +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
469 + this.suite = suite;
470 + this.dom = dom;
471 + this.views = views;
472 +
473 + this.element = this.createDom('div', { className: 'suite' },
474 + this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description)
475 + );
476 +
477 + this.appendToSummary(this.suite, this.element);
478 +};
479 +
480 +jasmine.HtmlReporter.SuiteView.prototype.status = function() {
481 + return this.getSpecStatus(this.suite);
482 +};
483 +
484 +jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
485 + this.element.className += " " + this.status();
486 +};
487 +
488 +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
489 +
490 +/* @deprecated Use jasmine.HtmlReporter instead
491 + */
492 +jasmine.TrivialReporter = function(doc) {
493 + this.document = doc || document;
494 + this.suiteDivs = {};
495 + this.logRunningSpecs = false;
496 +};
497 +
498 +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
499 + var el = document.createElement(type);
500 +
501 + for (var i = 2; i < arguments.length; i++) {
502 + var child = arguments[i];
503 +
504 + if (typeof child === 'string') {
505 + el.appendChild(document.createTextNode(child));
506 + } else {
507 + if (child) { el.appendChild(child); }
508 + }
509 + }
510 +
511 + for (var attr in attrs) {
512 + if (attr == "className") {
513 + el[attr] = attrs[attr];
514 + } else {
515 + el.setAttribute(attr, attrs[attr]);
516 + }
517 + }
518 +
519 + return el;
520 +};
521 +
522 +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
523 + var showPassed, showSkipped;
524 +
525 + this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
526 + this.createDom('div', { className: 'banner' },
527 + this.createDom('div', { className: 'logo' },
528 + this.createDom('span', { className: 'title' }, "Jasmine"),
529 + this.createDom('span', { className: 'version' }, runner.env.versionString())),
530 + this.createDom('div', { className: 'options' },
531 + "Show ",
532 + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
533 + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
534 + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
535 + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
536 + )
537 + ),
538 +
539 + this.runnerDiv = this.createDom('div', { className: 'runner running' },
540 + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
541 + this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
542 + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
543 + );
544 +
545 + this.document.body.appendChild(this.outerDiv);
546 +
547 + var suites = runner.suites();
548 + for (var i = 0; i < suites.length; i++) {
549 + var suite = suites[i];
550 + var suiteDiv = this.createDom('div', { className: 'suite' },
551 + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
552 + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
553 + this.suiteDivs[suite.id] = suiteDiv;
554 + var parentDiv = this.outerDiv;
555 + if (suite.parentSuite) {
556 + parentDiv = this.suiteDivs[suite.parentSuite.id];
557 + }
558 + parentDiv.appendChild(suiteDiv);
559 + }
560 +
561 + this.startedAt = new Date();
562 +
563 + var self = this;
564 + showPassed.onclick = function(evt) {
565 + if (showPassed.checked) {
566 + self.outerDiv.className += ' show-passed';
567 + } else {
568 + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
569 + }
570 + };
571 +
572 + showSkipped.onclick = function(evt) {
573 + if (showSkipped.checked) {
574 + self.outerDiv.className += ' show-skipped';
575 + } else {
576 + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
577 + }
578 + };
579 +};
580 +
581 +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
582 + var results = runner.results();
583 + var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
584 + this.runnerDiv.setAttribute("class", className);
585 + //do it twice for IE
586 + this.runnerDiv.setAttribute("className", className);
587 + var specs = runner.specs();
588 + var specCount = 0;
589 + for (var i = 0; i < specs.length; i++) {
590 + if (this.specFilter(specs[i])) {
591 + specCount++;
592 + }
593 + }
594 + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
595 + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
596 + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
597 +
598 + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
599 +};
600 +
601 +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
602 + var results = suite.results();
603 + var status = results.passed() ? 'passed' : 'failed';
604 + if (results.totalCount === 0) { // todo: change this to check results.skipped
605 + status = 'skipped';
606 + }
607 + this.suiteDivs[suite.id].className += " " + status;
608 +};
609 +
610 +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
611 + if (this.logRunningSpecs) {
612 + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
613 + }
614 +};
615 +
616 +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
617 + var results = spec.results();
618 + var status = results.passed() ? 'passed' : 'failed';
619 + if (results.skipped) {
620 + status = 'skipped';
621 + }
622 + var specDiv = this.createDom('div', { className: 'spec ' + status },
623 + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
624 + this.createDom('a', {
625 + className: 'description',
626 + href: '?spec=' + encodeURIComponent(spec.getFullName()),
627 + title: spec.getFullName()
628 + }, spec.description));
629 +
630 +
631 + var resultItems = results.getItems();
632 + var messagesDiv = this.createDom('div', { className: 'messages' });
633 + for (var i = 0; i < resultItems.length; i++) {
634 + var result = resultItems[i];
635 +
636 + if (result.type == 'log') {
637 + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
638 + } else if (result.type == 'expect' && result.passed && !result.passed()) {
639 + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
640 +
641 + if (result.trace.stack) {
642 + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
643 + }
644 + }
645 + }
646 +
647 + if (messagesDiv.childNodes.length > 0) {
648 + specDiv.appendChild(messagesDiv);
649 + }
650 +
651 + this.suiteDivs[spec.suite.id].appendChild(specDiv);
652 +};
653 +
654 +jasmine.TrivialReporter.prototype.log = function() {
655 + var console = jasmine.getGlobal().console;
656 + if (console && console.log) {
657 + if (console.log.apply) {
658 + console.log.apply(console, arguments);
659 + } else {
660 + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
661 + }
662 + }
663 +};
664 +
665 +jasmine.TrivialReporter.prototype.getLocation = function() {
666 + return this.document.location;
667 +};
668 +
669 +jasmine.TrivialReporter.prototype.specFilter = function(spec) {
670 + var paramMap = {};
671 + var params = this.getLocation().search.substring(1).split('&');
672 + for (var i = 0; i < params.length; i++) {
673 + var p = params[i].split('=');
674 + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
675 + }
676 +
677 + if (!paramMap.spec) {
678 + return true;
679 + }
680 + return spec.getFullName().indexOf(paramMap.spec) === 0;
681 +};
vendor/jasmine/jasmine.css new
+82
@@ -0,0 +1,82 @@
1 +body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
2 +
3 +#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
4 +#HTMLReporter a { text-decoration: none; }
5 +#HTMLReporter a:hover { text-decoration: underline; }
6 +#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
7 +#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
8 +#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
9 +#HTMLReporter .version { color: #aaaaaa; }
10 +#HTMLReporter .banner { margin-top: 14px; }
11 +#HTMLReporter .duration { color: #aaaaaa; float: right; }
12 +#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
13 +#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
14 +#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
15 +#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
16 +#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
17 +#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
18 +#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
19 +#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
20 +#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
21 +#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
22 +#HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
23 +#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
24 +#HTMLReporter .runningAlert { background-color: #666666; }
25 +#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
26 +#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
27 +#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
28 +#HTMLReporter .passingAlert { background-color: #a6b779; }
29 +#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
30 +#HTMLReporter .failingAlert { background-color: #cf867e; }
31 +#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
32 +#HTMLReporter .results { margin-top: 14px; }
33 +#HTMLReporter #details { display: none; }
34 +#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
35 +#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
36 +#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
37 +#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
38 +#HTMLReporter.showDetails .summary { display: none; }
39 +#HTMLReporter.showDetails #details { display: block; }
40 +#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
41 +#HTMLReporter .summary { margin-top: 14px; }
42 +#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
43 +#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
44 +#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
45 +#HTMLReporter .description + .suite { margin-top: 0; }
46 +#HTMLReporter .suite { margin-top: 14px; }
47 +#HTMLReporter .suite a { color: #333333; }
48 +#HTMLReporter #details .specDetail { margin-bottom: 28px; }
49 +#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
50 +#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
51 +#HTMLReporter .resultMessage span.result { display: block; }
52 +#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
53 +
54 +#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
55 +#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
56 +#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
57 +#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
58 +#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
59 +#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
60 +#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
61 +#TrivialReporter .runner.running { background-color: yellow; }
62 +#TrivialReporter .options { text-align: right; font-size: .8em; }
63 +#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
64 +#TrivialReporter .suite .suite { margin: 5px; }
65 +#TrivialReporter .suite.passed { background-color: #dfd; }
66 +#TrivialReporter .suite.failed { background-color: #fdd; }
67 +#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
68 +#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
69 +#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
70 +#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
71 +#TrivialReporter .spec.skipped { background-color: #bbb; }
72 +#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
73 +#TrivialReporter .passed { background-color: #cfc; display: none; }
74 +#TrivialReporter .failed { background-color: #fbb; }
75 +#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
76 +#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
77 +#TrivialReporter .resultMessage .mismatch { color: black; }
78 +#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
79 +#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
80 +#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
81 +#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
82 +#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
vendor/jasmine/jasmine.js new
+2599
@@ -0,0 +1,2599 @@
1 +var isCommonJS = typeof window == "undefined" && typeof exports == "object";
2 +
3 +/**
4 + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
5 + *
6 + * @namespace
7 + */
8 +var jasmine = {};
9 +if (isCommonJS) exports.jasmine = jasmine;
10 +/**
11 + * @private
12 + */
13 +jasmine.unimplementedMethod_ = function() {
14 + throw new Error("unimplemented method");
15 +};
16 +
17 +/**
18 + * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
19 + * a plain old variable and may be redefined by somebody else.
20 + *
21 + * @private
22 + */
23 +jasmine.undefined = jasmine.___undefined___;
24 +
25 +/**
26 + * Show diagnostic messages in the console if set to true
27 + *
28 + */
29 +jasmine.VERBOSE = false;
30 +
31 +/**
32 + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
33 + *
34 + */
35 +jasmine.DEFAULT_UPDATE_INTERVAL = 250;
36 +
37 +/**
38 + * Maximum levels of nesting that will be included when an object is pretty-printed
39 + */
40 +jasmine.MAX_PRETTY_PRINT_DEPTH = 40;
41 +
42 +/**
43 + * Default timeout interval in milliseconds for waitsFor() blocks.
44 + */
45 +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
46 +
47 +/**
48 + * By default exceptions thrown in the context of a test are caught by jasmine so that it can run the remaining tests in the suite.
49 + * Set to false to let the exception bubble up in the browser.
50 + *
51 + */
52 +jasmine.CATCH_EXCEPTIONS = true;
53 +
54 +jasmine.getGlobal = function() {
55 + function getGlobal() {
56 + return this;
57 + }
58 +
59 + return getGlobal();
60 +};
61 +
62 +/**
63 + * Allows for bound functions to be compared. Internal use only.
64 + *
65 + * @ignore
66 + * @private
67 + * @param base {Object} bound 'this' for the function
68 + * @param name {Function} function to find
69 + */
70 +jasmine.bindOriginal_ = function(base, name) {
71 + var original = base[name];
72 + if (original.apply) {
73 + return function() {
74 + return original.apply(base, arguments);
75 + };
76 + } else {
77 + // IE support
78 + return jasmine.getGlobal()[name];
79 + }
80 +};
81 +
82 +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
83 +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
84 +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
85 +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
86 +
87 +jasmine.MessageResult = function(values) {
88 + this.type = 'log';
89 + this.values = values;
90 + this.trace = new Error(); // todo: test better
91 +};
92 +
93 +jasmine.MessageResult.prototype.toString = function() {
94 + var text = "";
95 + for (var i = 0; i < this.values.length; i++) {
96 + if (i > 0) text += " ";
97 + if (jasmine.isString_(this.values[i])) {
98 + text += this.values[i];
99 + } else {
100 + text += jasmine.pp(this.values[i]);
101 + }
102 + }
103 + return text;
104 +};
105 +
106 +jasmine.ExpectationResult = function(params) {
107 + this.type = 'expect';
108 + this.matcherName = params.matcherName;
109 + this.passed_ = params.passed;
110 + this.expected = params.expected;
111 + this.actual = params.actual;
112 + this.message = this.passed_ ? 'Passed.' : params.message;
113 +
114 + var trace = (params.trace || new Error(this.message));
115 + this.trace = this.passed_ ? '' : trace;
116 +};
117 +
118 +jasmine.ExpectationResult.prototype.toString = function () {
119 + return this.message;
120 +};
121 +
122 +jasmine.ExpectationResult.prototype.passed = function () {
123 + return this.passed_;
124 +};
125 +
126 +/**
127 + * Getter for the Jasmine environment. Ensures one gets created
128 + */
129 +jasmine.getEnv = function() {
130 + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
131 + return env;
132 +};
133 +
134 +/**
135 + * @ignore
136 + * @private
137 + * @param value
138 + * @returns {Boolean}
139 + */
140 +jasmine.isArray_ = function(value) {
141 + return jasmine.isA_("Array", value);
142 +};
143 +
144 +/**
145 + * @ignore
146 + * @private
147 + * @param value
148 + * @returns {Boolean}
149 + */
150 +jasmine.isString_ = function(value) {
151 + return jasmine.isA_("String", value);
152 +};
153 +
154 +/**
155 + * @ignore
156 + * @private
157 + * @param value
158 + * @returns {Boolean}
159 + */
160 +jasmine.isNumber_ = function(value) {
161 + return jasmine.isA_("Number", value);
162 +};
163 +
164 +/**
165 + * @ignore
166 + * @private
167 + * @param {String} typeName
168 + * @param value
169 + * @returns {Boolean}
170 + */
171 +jasmine.isA_ = function(typeName, value) {
172 + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
173 +};
174 +
175 +/**
176 + * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
177 + *
178 + * @param value {Object} an object to be outputted
179 + * @returns {String}
180 + */
181 +jasmine.pp = function(value) {
182 + var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
183 + stringPrettyPrinter.format(value);
184 + return stringPrettyPrinter.string;
185 +};
186 +
187 +/**
188 + * Returns true if the object is a DOM Node.
189 + *
190 + * @param {Object} obj object to check
191 + * @returns {Boolean}
192 + */
193 +jasmine.isDomNode = function(obj) {
194 + return obj.nodeType > 0;
195 +};
196 +
197 +/**
198 + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
199 + *
200 + * @example
201 + * // don't care about which function is passed in, as long as it's a function
202 + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
203 + *
204 + * @param {Class} clazz
205 + * @returns matchable object of the type clazz
206 + */
207 +jasmine.any = function(clazz) {
208 + return new jasmine.Matchers.Any(clazz);
209 +};
210 +
211 +/**
212 + * Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
213 + * attributes on the object.
214 + *
215 + * @example
216 + * // don't care about any other attributes than foo.
217 + * expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
218 + *
219 + * @param sample {Object} sample
220 + * @returns matchable object for the sample
221 + */
222 +jasmine.objectContaining = function (sample) {
223 + return new jasmine.Matchers.ObjectContaining(sample);
224 +};
225 +
226 +/**
227 + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
228 + *
229 + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
230 + * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
231 + *
232 + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
233 + *
234 + * Spies are torn down at the end of every spec.
235 + *
236 + * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
237 + *
238 + * @example
239 + * // a stub
240 + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
241 + *
242 + * // spy example
243 + * var foo = {
244 + * not: function(bool) { return !bool; }
245 + * }
246 + *
247 + * // actual foo.not will not be called, execution stops
248 + * spyOn(foo, 'not');
249 +
250 + // foo.not spied upon, execution will continue to implementation
251 + * spyOn(foo, 'not').andCallThrough();
252 + *
253 + * // fake example
254 + * var foo = {
255 + * not: function(bool) { return !bool; }
256 + * }
257 + *
258 + * // foo.not(val) will return val
259 + * spyOn(foo, 'not').andCallFake(function(value) {return value;});
260 + *
261 + * // mock example
262 + * foo.not(7 == 7);
263 + * expect(foo.not).toHaveBeenCalled();
264 + * expect(foo.not).toHaveBeenCalledWith(true);
265 + *
266 + * @constructor
267 + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
268 + * @param {String} name
269 + */
270 +jasmine.Spy = function(name) {
271 + /**
272 + * The name of the spy, if provided.
273 + */
274 + this.identity = name || 'unknown';
275 + /**
276 + * Is this Object a spy?
277 + */
278 + this.isSpy = true;
279 + /**
280 + * The actual function this spy stubs.
281 + */
282 + this.plan = function() {
283 + };
284 + /**
285 + * Tracking of the most recent call to the spy.
286 + * @example
287 + * var mySpy = jasmine.createSpy('foo');
288 + * mySpy(1, 2);
289 + * mySpy.mostRecentCall.args = [1, 2];
290 + */
291 + this.mostRecentCall = {};
292 +
293 + /**
294 + * Holds arguments for each call to the spy, indexed by call count
295 + * @example
296 + * var mySpy = jasmine.createSpy('foo');
297 + * mySpy(1, 2);
298 + * mySpy(7, 8);
299 + * mySpy.mostRecentCall.args = [7, 8];
300 + * mySpy.argsForCall[0] = [1, 2];
301 + * mySpy.argsForCall[1] = [7, 8];
302 + */
303 + this.argsForCall = [];
304 + this.calls = [];
305 +};
306 +
307 +/**
308 + * Tells a spy to call through to the actual implemenatation.
309 + *
310 + * @example
311 + * var foo = {
312 + * bar: function() { // do some stuff }
313 + * }
314 + *
315 + * // defining a spy on an existing property: foo.bar
316 + * spyOn(foo, 'bar').andCallThrough();
317 + */
318 +jasmine.Spy.prototype.andCallThrough = function() {
319 + this.plan = this.originalValue;
320 + return this;
321 +};
322 +
323 +/**
324 + * For setting the return value of a spy.
325 + *
326 + * @example
327 + * // defining a spy from scratch: foo() returns 'baz'
328 + * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
329 + *
330 + * // defining a spy on an existing property: foo.bar() returns 'baz'
331 + * spyOn(foo, 'bar').andReturn('baz');
332 + *
333 + * @param {Object} value
334 + */
335 +jasmine.Spy.prototype.andReturn = function(value) {
336 + this.plan = function() {
337 + return value;
338 + };
339 + return this;
340 +};
341 +
342 +/**
343 + * For throwing an exception when a spy is called.
344 + *
345 + * @example
346 + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
347 + * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
348 + *
349 + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
350 + * spyOn(foo, 'bar').andThrow('baz');
351 + *
352 + * @param {String} exceptionMsg
353 + */
354 +jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
355 + this.plan = function() {
356 + throw exceptionMsg;
357 + };
358 + return this;
359 +};
360 +
361 +/**
362 + * Calls an alternate implementation when a spy is called.
363 + *
364 + * @example
365 + * var baz = function() {
366 + * // do some stuff, return something
367 + * }
368 + * // defining a spy from scratch: foo() calls the function baz
369 + * var foo = jasmine.createSpy('spy on foo').andCall(baz);
370 + *
371 + * // defining a spy on an existing property: foo.bar() calls an anonymnous function
372 + * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
373 + *
374 + * @param {Function} fakeFunc
375 + */
376 +jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
377 + this.plan = fakeFunc;
378 + return this;
379 +};
380 +
381 +/**
382 + * Resets all of a spy's the tracking variables so that it can be used again.
383 + *
384 + * @example
385 + * spyOn(foo, 'bar');
386 + *
387 + * foo.bar();
388 + *
389 + * expect(foo.bar.callCount).toEqual(1);
390 + *
391 + * foo.bar.reset();
392 + *
393 + * expect(foo.bar.callCount).toEqual(0);
394 + */
395 +jasmine.Spy.prototype.reset = function() {
396 + this.wasCalled = false;
397 + this.callCount = 0;
398 + this.argsForCall = [];
399 + this.calls = [];
400 + this.mostRecentCall = {};
401 +};
402 +
403 +jasmine.createSpy = function(name) {
404 +
405 + var spyObj = function() {
406 + spyObj.wasCalled = true;
407 + spyObj.callCount++;
408 + var args = jasmine.util.argsToArray(arguments);
409 + spyObj.mostRecentCall.object = this;
410 + spyObj.mostRecentCall.args = args;
411 + spyObj.argsForCall.push(args);
412 + spyObj.calls.push({object: this, args: args});
413 + return spyObj.plan.apply(this, arguments);
414 + };
415 +
416 + var spy = new jasmine.Spy(name);
417 +
418 + for (var prop in spy) {
419 + spyObj[prop] = spy[prop];
420 + }
421 +
422 + spyObj.reset();
423 +
424 + return spyObj;
425 +};
426 +
427 +/**
428 + * Determines whether an object is a spy.
429 + *
430 + * @param {jasmine.Spy|Object} putativeSpy
431 + * @returns {Boolean}
432 + */
433 +jasmine.isSpy = function(putativeSpy) {
434 + return putativeSpy && putativeSpy.isSpy;
435 +};
436 +
437 +/**
438 + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
439 + * large in one call.
440 + *
441 + * @param {String} baseName name of spy class
442 + * @param {Array} methodNames array of names of methods to make spies
443 + */
444 +jasmine.createSpyObj = function(baseName, methodNames) {
445 + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
446 + throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
447 + }
448 + var obj = {};
449 + for (var i = 0; i < methodNames.length; i++) {
450 + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
451 + }
452 + return obj;
453 +};
454 +
455 +/**
456 + * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
457 + *
458 + * Be careful not to leave calls to <code>jasmine.log</code> in production code.
459 + */
460 +jasmine.log = function() {
461 + var spec = jasmine.getEnv().currentSpec;
462 + spec.log.apply(spec, arguments);
463 +};
464 +
465 +/**
466 + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
467 + *
468 + * @example
469 + * // spy example
470 + * var foo = {
471 + * not: function(bool) { return !bool; }
472 + * }
473 + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
474 + *
475 + * @see jasmine.createSpy
476 + * @param obj
477 + * @param methodName
478 + * @return {jasmine.Spy} a Jasmine spy that can be chained with all spy methods
479 + */
480 +var spyOn = function(obj, methodName) {
481 + return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
482 +};
483 +if (isCommonJS) exports.spyOn = spyOn;
484 +
485 +/**
486 + * Creates a Jasmine spec that will be added to the current suite.
487 + *
488 + * // TODO: pending tests
489 + *
490 + * @example
491 + * it('should be true', function() {
492 + * expect(true).toEqual(true);
493 + * });
494 + *
495 + * @param {String} desc description of this specification
496 + * @param {Function} func defines the preconditions and expectations of the spec
497 + */
498 +var it = function(desc, func) {
499 + return jasmine.getEnv().it(desc, func);
500 +};
501 +if (isCommonJS) exports.it = it;
502 +
503 +/**
504 + * Creates a <em>disabled</em> Jasmine spec.
505 + *
506 + * A convenience method that allows existing specs to be disabled temporarily during development.
507 + *
508 + * @param {String} desc description of this specification
509 + * @param {Function} func defines the preconditions and expectations of the spec
510 + */
511 +var xit = function(desc, func) {
512 + return jasmine.getEnv().xit(desc, func);
513 +};
514 +if (isCommonJS) exports.xit = xit;
515 +
516 +/**
517 + * Starts a chain for a Jasmine expectation.
518 + *
519 + * It is passed an Object that is the actual value and should chain to one of the many
520 + * jasmine.Matchers functions.
521 + *
522 + * @param {Object} actual Actual value to test against and expected value
523 + * @return {jasmine.Matchers}
524 + */
525 +var expect = function(actual) {
526 + return jasmine.getEnv().currentSpec.expect(actual);
527 +};
528 +if (isCommonJS) exports.expect = expect;
529 +
530 +/**
531 + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
532 + *
533 + * @param {Function} func Function that defines part of a jasmine spec.
534 + */
535 +var runs = function(func) {
536 + jasmine.getEnv().currentSpec.runs(func);
537 +};
538 +if (isCommonJS) exports.runs = runs;
539 +
540 +/**
541 + * Waits a fixed time period before moving to the next block.
542 + *
543 + * @deprecated Use waitsFor() instead
544 + * @param {Number} timeout milliseconds to wait
545 + */
546 +var waits = function(timeout) {
547 + jasmine.getEnv().currentSpec.waits(timeout);
548 +};
549 +if (isCommonJS) exports.waits = waits;
550 +
551 +/**
552 + * Waits for the latchFunction to return true before proceeding to the next block.
553 + *
554 + * @param {Function} latchFunction
555 + * @param {String} optional_timeoutMessage
556 + * @param {Number} optional_timeout
557 + */
558 +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
559 + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
560 +};
561 +if (isCommonJS) exports.waitsFor = waitsFor;
562 +
563 +/**
564 + * A function that is called before each spec in a suite.
565 + *
566 + * Used for spec setup, including validating assumptions.
567 + *
568 + * @param {Function} beforeEachFunction
569 + */
570 +var beforeEach = function(beforeEachFunction) {
571 + jasmine.getEnv().beforeEach(beforeEachFunction);
572 +};
573 +if (isCommonJS) exports.beforeEach = beforeEach;
574 +
575 +/**
576 + * A function that is called after each spec in a suite.
577 + *
578 + * Used for restoring any state that is hijacked during spec execution.
579 + *
580 + * @param {Function} afterEachFunction
581 + */
582 +var afterEach = function(afterEachFunction) {
583 + jasmine.getEnv().afterEach(afterEachFunction);
584 +};
585 +if (isCommonJS) exports.afterEach = afterEach;
586 +
587 +/**
588 + * Defines a suite of specifications.
589 + *
590 + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
591 + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
592 + * of setup in some tests.
593 + *
594 + * @example
595 + * // TODO: a simple suite
596 + *
597 + * // TODO: a simple suite with a nested describe block
598 + *
599 + * @param {String} description A string, usually the class under test.
600 + * @param {Function} specDefinitions function that defines several specs.
601 + */
602 +var describe = function(description, specDefinitions) {
603 + return jasmine.getEnv().describe(description, specDefinitions);
604 +};
605 +if (isCommonJS) exports.describe = describe;
606 +
607 +/**
608 + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
609 + *
610 + * @param {String} description A string, usually the class under test.
611 + * @param {Function} specDefinitions function that defines several specs.
612 + */
613 +var xdescribe = function(description, specDefinitions) {
614 + return jasmine.getEnv().xdescribe(description, specDefinitions);
615 +};
616 +if (isCommonJS) exports.xdescribe = xdescribe;
617 +
618 +
619 +// Provide the XMLHttpRequest class for IE 5.x-6.x:
620 +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
621 + function tryIt(f) {
622 + try {
623 + return f();
624 + } catch(e) {
625 + }
626 + return null;
627 + }
628 +
629 + var xhr = tryIt(function() {
630 + return new ActiveXObject("Msxml2.XMLHTTP.6.0");
631 + }) ||
632 + tryIt(function() {
633 + return new ActiveXObject("Msxml2.XMLHTTP.3.0");
634 + }) ||
635 + tryIt(function() {
636 + return new ActiveXObject("Msxml2.XMLHTTP");
637 + }) ||
638 + tryIt(function() {
639 + return new ActiveXObject("Microsoft.XMLHTTP");
640 + });
641 +
642 + if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
643 +
644 + return xhr;
645 +} : XMLHttpRequest;
646 +/**
647 + * @namespace
648 + */
649 +jasmine.util = {};
650 +
651 +/**
652 + * Declare that a child class inherit it's prototype from the parent class.
653 + *
654 + * @private
655 + * @param {Function} childClass
656 + * @param {Function} parentClass
657 + */
658 +jasmine.util.inherit = function(childClass, parentClass) {
659 + /**
660 + * @private
661 + */
662 + var subclass = function() {
663 + };
664 + subclass.prototype = parentClass.prototype;
665 + childClass.prototype = new subclass();
666 +};
667 +
668 +jasmine.util.formatException = function(e) {
669 + var lineNumber;
670 + if (e.line) {
671 + lineNumber = e.line;
672 + }
673 + else if (e.lineNumber) {
674 + lineNumber = e.lineNumber;
675 + }
676 +
677 + var file;
678 +
679 + if (e.sourceURL) {
680 + file = e.sourceURL;
681 + }
682 + else if (e.fileName) {
683 + file = e.fileName;
684 + }
685 +
686 + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
687 +
688 + if (file && lineNumber) {
689 + message += ' in ' + file + ' (line ' + lineNumber + ')';
690 + }
691 +
692 + return message;
693 +};
694 +
695 +jasmine.util.htmlEscape = function(str) {
696 + if (!str) return str;
697 + return str.replace(/&/g, '&amp;')
698 + .replace(/</g, '&lt;')
699 + .replace(/>/g, '&gt;');
700 +};
701 +
702 +jasmine.util.argsToArray = function(args) {
703 + var arrayOfArgs = [];
704 + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
705 + return arrayOfArgs;
706 +};
707 +
708 +jasmine.util.extend = function(destination, source) {
709 + for (var property in source) destination[property] = source[property];
710 + return destination;
711 +};
712 +
713 +/**
714 + * Environment for Jasmine
715 + *
716 + * @constructor
717 + */
718 +jasmine.Env = function() {
719 + this.currentSpec = null;
720 + this.currentSuite = null;
721 + this.currentRunner_ = new jasmine.Runner(this);
722 +
723 + this.reporter = new jasmine.MultiReporter();
724 +
725 + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
726 + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
727 + this.lastUpdate = 0;
728 + this.specFilter = function() {
729 + return true;
730 + };
731 +
732 + this.nextSpecId_ = 0;
733 + this.nextSuiteId_ = 0;
734 + this.equalityTesters_ = [];
735 +
736 + // wrap matchers
737 + this.matchersClass = function() {
738 + jasmine.Matchers.apply(this, arguments);
739 + };
740 + jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
741 +
742 + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
743 +};
744 +
745 +
746 +jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
747 +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
748 +jasmine.Env.prototype.setInterval = jasmine.setInterval;
749 +jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
750 +
751 +/**
752 + * @returns an object containing jasmine version build info, if set.
753 + */
754 +jasmine.Env.prototype.version = function () {
755 + if (jasmine.version_) {
756 + return jasmine.version_;
757 + } else {
758 + throw new Error('Version not set');
759 + }
760 +};
761 +
762 +/**
763 + * @returns string containing jasmine version build info, if set.
764 + */
765 +jasmine.Env.prototype.versionString = function() {
766 + if (!jasmine.version_) {
767 + return "version unknown";
768 + }
769 +
770 + var version = this.version();
771 + var versionString = version.major + "." + version.minor + "." + version.build;
772 + if (version.release_candidate) {
773 + versionString += ".rc" + version.release_candidate;
774 + }
775 + versionString += " revision " + version.revision;
776 + return versionString;
777 +};
778 +
779 +/**
780 + * @returns a sequential integer starting at 0
781 + */
782 +jasmine.Env.prototype.nextSpecId = function () {
783 + return this.nextSpecId_++;
784 +};
785 +
786 +/**
787 + * @returns a sequential integer starting at 0
788 + */
789 +jasmine.Env.prototype.nextSuiteId = function () {
790 + return this.nextSuiteId_++;
791 +};
792 +
793 +/**
794 + * Register a reporter to receive status updates from Jasmine.
795 + * @param {jasmine.Reporter} reporter An object which will receive status updates.
796 + */
797 +jasmine.Env.prototype.addReporter = function(reporter) {
798 + this.reporter.addReporter(reporter);
799 +};
800 +
801 +jasmine.Env.prototype.execute = function() {
802 + this.currentRunner_.execute();
803 +};
804 +
805 +jasmine.Env.prototype.describe = function(description, specDefinitions) {
806 + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
807 +
808 + var parentSuite = this.currentSuite;
809 + if (parentSuite) {
810 + parentSuite.add(suite);
811 + } else {
812 + this.currentRunner_.add(suite);
813 + }
814 +
815 + this.currentSuite = suite;
816 +
817 + var declarationError = null;
818 + try {
819 + specDefinitions.call(suite);
820 + } catch(e) {
821 + declarationError = e;
822 + }
823 +
824 + if (declarationError) {
825 + this.it("encountered a declaration exception", function() {
826 + throw declarationError;
827 + });
828 + }
829 +
830 + this.currentSuite = parentSuite;
831 +
832 + return suite;
833 +};
834 +
835 +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
836 + if (this.currentSuite) {
837 + this.currentSuite.beforeEach(beforeEachFunction);
838 + } else {
839 + this.currentRunner_.beforeEach(beforeEachFunction);
840 + }
841 +};
842 +
843 +jasmine.Env.prototype.currentRunner = function () {
844 + return this.currentRunner_;
845 +};
846 +
847 +jasmine.Env.prototype.afterEach = function(afterEachFunction) {
848 + if (this.currentSuite) {
849 + this.currentSuite.afterEach(afterEachFunction);
850 + } else {
851 + this.currentRunner_.afterEach(afterEachFunction);
852 + }
853 +
854 +};
855 +
856 +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
857 + return {
858 + execute: function() {
859 + }
860 + };
861 +};
862 +
863 +jasmine.Env.prototype.it = function(description, func) {
864 + var spec = new jasmine.Spec(this, this.currentSuite, description);
865 + this.currentSuite.add(spec);
866 + this.currentSpec = spec;
867 +
868 + if (func) {
869 + spec.runs(func);
870 + }
871 +
872 + return spec;
873 +};
874 +
875 +jasmine.Env.prototype.xit = function(desc, func) {
876 + var spec = this.it(desc, func);
877 + spec.results_.skipped = true;
878 + spec.runs = function() {};
879 + return spec;
880 +};
881 +
882 +jasmine.Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) {
883 + if (a.source != b.source)
884 + mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/");
885 +
886 + if (a.ignoreCase != b.ignoreCase)
887 + mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier");
888 +
889 + if (a.global != b.global)
890 + mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier");
891 +
892 + if (a.multiline != b.multiline)
893 + mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier");
894 +
895 + if (a.sticky != b.sticky)
896 + mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier");
897 +
898 + return (mismatchValues.length === 0);
899 +};
900 +
901 +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
902 + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
903 + return true;
904 + }
905 +
906 + a.__Jasmine_been_here_before__ = b;
907 + b.__Jasmine_been_here_before__ = a;
908 +
909 + var hasKey = function(obj, keyName) {
910 + return obj !== null && obj[keyName] !== jasmine.undefined;
911 + };
912 +
913 + for (var property in b) {
914 + if (!hasKey(a, property) && hasKey(b, property)) {
915 + mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
916 + }
917 + }
918 + for (property in a) {
919 + if (!hasKey(b, property) && hasKey(a, property)) {
920 + mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
921 + }
922 + }
923 + for (property in b) {
924 + if (property == '__Jasmine_been_here_before__') continue;
925 + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
926 + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
927 + }
928 + }
929 +
930 + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
931 + mismatchValues.push("arrays were not the same length");
932 + }
933 +
934 + delete a.__Jasmine_been_here_before__;
935 + delete b.__Jasmine_been_here_before__;
936 + return (mismatchKeys.length === 0 && mismatchValues.length === 0);
937 +};
938 +
939 +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
940 + mismatchKeys = mismatchKeys || [];
941 + mismatchValues = mismatchValues || [];
942 +
943 + for (var i = 0; i < this.equalityTesters_.length; i++) {
944 + var equalityTester = this.equalityTesters_[i];
945 + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
946 + if (result !== jasmine.undefined) return result;
947 + }
948 +
949 + if (a === b) return true;
950 +
951 + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
952 + return (a == jasmine.undefined && b == jasmine.undefined);
953 + }
954 +
955 + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
956 + return a === b;
957 + }
958 +
959 + if (a instanceof Date && b instanceof Date) {
960 + return a.getTime() == b.getTime();
961 + }
962 +
963 + if (a.jasmineMatches) {
964 + return a.jasmineMatches(b);
965 + }
966 +
967 + if (b.jasmineMatches) {
968 + return b.jasmineMatches(a);
969 + }
970 +
971 + if (a instanceof jasmine.Matchers.ObjectContaining) {
972 + return a.matches(b);
973 + }
974 +
975 + if (b instanceof jasmine.Matchers.ObjectContaining) {
976 + return b.matches(a);
977 + }
978 +
979 + if (jasmine.isString_(a) && jasmine.isString_(b)) {
980 + return (a == b);
981 + }
982 +
983 + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
984 + return (a == b);
985 + }
986 +
987 + if (a instanceof RegExp && b instanceof RegExp) {
988 + return this.compareRegExps_(a, b, mismatchKeys, mismatchValues);
989 + }
990 +
991 + if (typeof a === "object" && typeof b === "object") {
992 + return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
993 + }
994 +
995 + //Straight check
996 + return (a === b);
997 +};
998 +
999 +jasmine.Env.prototype.contains_ = function(haystack, needle) {
1000 + if (jasmine.isArray_(haystack)) {
1001 + for (var i = 0; i < haystack.length; i++) {
1002 + if (this.equals_(haystack[i], needle)) return true;
1003 + }
1004 + return false;
1005 + }
1006 + return haystack.indexOf(needle) >= 0;
1007 +};
1008 +
1009 +jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
1010 + this.equalityTesters_.push(equalityTester);
1011 +};
1012 +/** No-op base class for Jasmine reporters.
1013 + *
1014 + * @constructor
1015 + */
1016 +jasmine.Reporter = function() {
1017 +};
1018 +
1019 +//noinspection JSUnusedLocalSymbols
1020 +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
1021 +};
1022 +
1023 +//noinspection JSUnusedLocalSymbols
1024 +jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
1025 +};
1026 +
1027 +//noinspection JSUnusedLocalSymbols
1028 +jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
1029 +};
1030 +
1031 +//noinspection JSUnusedLocalSymbols
1032 +jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
1033 +};
1034 +
1035 +//noinspection JSUnusedLocalSymbols
1036 +jasmine.Reporter.prototype.reportSpecResults = function(spec) {
1037 +};
1038 +
1039 +//noinspection JSUnusedLocalSymbols
1040 +jasmine.Reporter.prototype.log = function(str) {
1041 +};
1042 +
1043 +/**
1044 + * Blocks are functions with executable code that make up a spec.
1045 + *
1046 + * @constructor
1047 + * @param {jasmine.Env} env
1048 + * @param {Function} func
1049 + * @param {jasmine.Spec} spec
1050 + */
1051 +jasmine.Block = function(env, func, spec) {
1052 + this.env = env;
1053 + this.func = func;
1054 + this.spec = spec;
1055 +};
1056 +
1057 +jasmine.Block.prototype.execute = function(onComplete) {
1058 + if (!jasmine.CATCH_EXCEPTIONS) {
1059 + this.func.apply(this.spec);
1060 + }
1061 + else {
1062 + try {
1063 + this.func.apply(this.spec);
1064 + } catch (e) {
1065 + this.spec.fail(e);
1066 + }
1067 + }
1068 + onComplete();
1069 +};
1070 +/** JavaScript API reporter.
1071 + *
1072 + * @constructor
1073 + */
1074 +jasmine.JsApiReporter = function() {
1075 + this.started = false;
1076 + this.finished = false;
1077 + this.suites_ = [];
1078 + this.results_ = {};
1079 +};
1080 +
1081 +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
1082 + this.started = true;
1083 + var suites = runner.topLevelSuites();
1084 + for (var i = 0; i < suites.length; i++) {
1085 + var suite = suites[i];
1086 + this.suites_.push(this.summarize_(suite));
1087 + }
1088 +};
1089 +
1090 +jasmine.JsApiReporter.prototype.suites = function() {
1091 + return this.suites_;
1092 +};
1093 +
1094 +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
1095 + var isSuite = suiteOrSpec instanceof jasmine.Suite;
1096 + var summary = {
1097 + id: suiteOrSpec.id,
1098 + name: suiteOrSpec.description,
1099 + type: isSuite ? 'suite' : 'spec',
1100 + children: []
1101 + };
1102 +
1103 + if (isSuite) {
1104 + var children = suiteOrSpec.children();
1105 + for (var i = 0; i < children.length; i++) {
1106 + summary.children.push(this.summarize_(children[i]));
1107 + }
1108 + }
1109 + return summary;
1110 +};
1111 +
1112 +jasmine.JsApiReporter.prototype.results = function() {
1113 + return this.results_;
1114 +};
1115 +
1116 +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
1117 + return this.results_[specId];
1118 +};
1119 +
1120 +//noinspection JSUnusedLocalSymbols
1121 +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
1122 + this.finished = true;
1123 +};
1124 +
1125 +//noinspection JSUnusedLocalSymbols
1126 +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
1127 +};
1128 +
1129 +//noinspection JSUnusedLocalSymbols
1130 +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
1131 + this.results_[spec.id] = {
1132 + messages: spec.results().getItems(),
1133 + result: spec.results().failedCount > 0 ? "failed" : "passed"
1134 + };
1135 +};
1136 +
1137 +//noinspection JSUnusedLocalSymbols
1138 +jasmine.JsApiReporter.prototype.log = function(str) {
1139 +};
1140 +
1141 +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
1142 + var results = {};
1143 + for (var i = 0; i < specIds.length; i++) {
1144 + var specId = specIds[i];
1145 + results[specId] = this.summarizeResult_(this.results_[specId]);
1146 + }
1147 + return results;
1148 +};
1149 +
1150 +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
1151 + var summaryMessages = [];
1152 + var messagesLength = result.messages.length;
1153 + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
1154 + var resultMessage = result.messages[messageIndex];
1155 + summaryMessages.push({
1156 + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
1157 + passed: resultMessage.passed ? resultMessage.passed() : true,
1158 + type: resultMessage.type,
1159 + message: resultMessage.message,
1160 + trace: {
1161 + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
1162 + }
1163 + });
1164 + }
1165 +
1166 + return {
1167 + result : result.result,
1168 + messages : summaryMessages
1169 + };
1170 +};
1171 +
1172 +/**
1173 + * @constructor
1174 + * @param {jasmine.Env} env
1175 + * @param actual
1176 + * @param {jasmine.Spec} spec
1177 + */
1178 +jasmine.Matchers = function(env, actual, spec, opt_isNot) {
1179 + this.env = env;
1180 + this.actual = actual;
1181 + this.spec = spec;
1182 + this.isNot = opt_isNot || false;
1183 + this.reportWasCalled_ = false;
1184 +};
1185 +
1186 +// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
1187 +jasmine.Matchers.pp = function(str) {
1188 + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
1189 +};
1190 +
1191 +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
1192 +jasmine.Matchers.prototype.report = function(result, failing_message, details) {
1193 + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
1194 +};
1195 +
1196 +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
1197 + for (var methodName in prototype) {
1198 + if (methodName == 'report') continue;
1199 + var orig = prototype[methodName];
1200 + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
1201 + }
1202 +};
1203 +
1204 +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
1205 + return function() {
1206 + var matcherArgs = jasmine.util.argsToArray(arguments);
1207 + var result = matcherFunction.apply(this, arguments);
1208 +
1209 + if (this.isNot) {
1210 + result = !result;
1211 + }
1212 +
1213 + if (this.reportWasCalled_) return result;
1214 +
1215 + var message;
1216 + if (!result) {
1217 + if (this.message) {
1218 + message = this.message.apply(this, arguments);
1219 + if (jasmine.isArray_(message)) {
1220 + message = message[this.isNot ? 1 : 0];
1221 + }
1222 + } else {
1223 + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
1224 + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
1225 + if (matcherArgs.length > 0) {
1226 + for (var i = 0; i < matcherArgs.length; i++) {
1227 + if (i > 0) message += ",";
1228 + message += " " + jasmine.pp(matcherArgs[i]);
1229 + }
1230 + }
1231 + message += ".";
1232 + }
1233 + }
1234 + var expectationResult = new jasmine.ExpectationResult({
1235 + matcherName: matcherName,
1236 + passed: result,
1237 + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
1238 + actual: this.actual,
1239 + message: message
1240 + });
1241 + this.spec.addMatcherResult(expectationResult);
1242 + return jasmine.undefined;
1243 + };
1244 +};
1245 +
1246 +
1247 +
1248 +
1249 +/**
1250 + * toBe: compares the actual to the expected using ===
1251 + * @param expected
1252 + */
1253 +jasmine.Matchers.prototype.toBe = function(expected) {
1254 + return this.actual === expected;
1255 +};
1256 +
1257 +/**
1258 + * toNotBe: compares the actual to the expected using !==
1259 + * @param expected
1260 + * @deprecated as of 1.0. Use not.toBe() instead.
1261 + */
1262 +jasmine.Matchers.prototype.toNotBe = function(expected) {
1263 + return this.actual !== expected;
1264 +};
1265 +
1266 +/**
1267 + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
1268 + *
1269 + * @param expected
1270 + */
1271 +jasmine.Matchers.prototype.toEqual = function(expected) {
1272 + return this.env.equals_(this.actual, expected);
1273 +};
1274 +
1275 +/**
1276 + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
1277 + * @param expected
1278 + * @deprecated as of 1.0. Use not.toEqual() instead.
1279 + */
1280 +jasmine.Matchers.prototype.toNotEqual = function(expected) {
1281 + return !this.env.equals_(this.actual, expected);
1282 +};
1283 +
1284 +/**
1285 + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
1286 + * a pattern or a String.
1287 + *
1288 + * @param expected
1289 + */
1290 +jasmine.Matchers.prototype.toMatch = function(expected) {
1291 + return new RegExp(expected).test(this.actual);
1292 +};
1293 +
1294 +/**
1295 + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
1296 + * @param expected
1297 + * @deprecated as of 1.0. Use not.toMatch() instead.
1298 + */
1299 +jasmine.Matchers.prototype.toNotMatch = function(expected) {
1300 + return !(new RegExp(expected).test(this.actual));
1301 +};
1302 +
1303 +/**
1304 + * Matcher that compares the actual to jasmine.undefined.
1305 + */
1306 +jasmine.Matchers.prototype.toBeDefined = function() {
1307 + return (this.actual !== jasmine.undefined);
1308 +};
1309 +
1310 +/**
1311 + * Matcher that compares the actual to jasmine.undefined.
1312 + */
1313 +jasmine.Matchers.prototype.toBeUndefined = function() {
1314 + return (this.actual === jasmine.undefined);
1315 +};
1316 +
1317 +/**
1318 + * Matcher that compares the actual to null.
1319 + */
1320 +jasmine.Matchers.prototype.toBeNull = function() {
1321 + return (this.actual === null);
1322 +};
1323 +
1324 +/**
1325 + * Matcher that compares the actual to NaN.
1326 + */
1327 +jasmine.Matchers.prototype.toBeNaN = function() {
1328 + this.message = function() {
1329 + return [ "Expected " + jasmine.pp(this.actual) + " to be NaN." ];
1330 + };
1331 +
1332 + return (this.actual !== this.actual);
1333 +};
1334 +
1335 +/**
1336 + * Matcher that boolean not-nots the actual.
1337 + */
1338 +jasmine.Matchers.prototype.toBeTruthy = function() {
1339 + return !!this.actual;
1340 +};
1341 +
1342 +
1343 +/**
1344 + * Matcher that boolean nots the actual.
1345 + */
1346 +jasmine.Matchers.prototype.toBeFalsy = function() {
1347 + return !this.actual;
1348 +};
1349 +
1350 +
1351 +/**
1352 + * Matcher that checks to see if the actual, a Jasmine spy, was called.
1353 + */
1354 +jasmine.Matchers.prototype.toHaveBeenCalled = function() {
1355 + if (arguments.length > 0) {
1356 + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
1357 + }
1358 +
1359 + if (!jasmine.isSpy(this.actual)) {
1360 + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1361 + }
1362 +
1363 + this.message = function() {
1364 + return [
1365 + "Expected spy " + this.actual.identity + " to have been called.",
1366 + "Expected spy " + this.actual.identity + " not to have been called."
1367 + ];
1368 + };
1369 +
1370 + return this.actual.wasCalled;
1371 +};
1372 +
1373 +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
1374 +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
1375 +
1376 +/**
1377 + * Matcher that checks to see if the actual, a Jasmine spy, was not called.
1378 + *
1379 + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
1380 + */
1381 +jasmine.Matchers.prototype.wasNotCalled = function() {
1382 + if (arguments.length > 0) {
1383 + throw new Error('wasNotCalled does not take arguments');
1384 + }
1385 +
1386 + if (!jasmine.isSpy(this.actual)) {
1387 + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1388 + }
1389 +
1390 + this.message = function() {
1391 + return [
1392 + "Expected spy " + this.actual.identity + " to not have been called.",
1393 + "Expected spy " + this.actual.identity + " to have been called."
1394 + ];
1395 + };
1396 +
1397 + return !this.actual.wasCalled;
1398 +};
1399 +
1400 +/**
1401 + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
1402 + *
1403 + * @example
1404 + *
1405 + */
1406 +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
1407 + var expectedArgs = jasmine.util.argsToArray(arguments);
1408 + if (!jasmine.isSpy(this.actual)) {
1409 + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1410 + }
1411 + this.message = function() {
1412 + var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was.";
1413 + var positiveMessage = "";
1414 + if (this.actual.callCount === 0) {
1415 + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.";
1416 + } else {
1417 + positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but actual calls were " + jasmine.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '')
1418 + }
1419 + return [positiveMessage, invertedMessage];
1420 + };
1421 +
1422 + return this.env.contains_(this.actual.argsForCall, expectedArgs);
1423 +};
1424 +
1425 +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
1426 +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
1427 +
1428 +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
1429 +jasmine.Matchers.prototype.wasNotCalledWith = function() {
1430 + var expectedArgs = jasmine.util.argsToArray(arguments);
1431 + if (!jasmine.isSpy(this.actual)) {
1432 + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
1433 + }
1434 +
1435 + this.message = function() {
1436 + return [
1437 + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
1438 + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
1439 + ];
1440 + };
1441 +
1442 + return !this.env.contains_(this.actual.argsForCall, expectedArgs);
1443 +};
1444 +
1445 +/**
1446 + * Matcher that checks that the expected item is an element in the actual Array.
1447 + *
1448 + * @param {Object} expected
1449 + */
1450 +jasmine.Matchers.prototype.toContain = function(expected) {
1451 + return this.env.contains_(this.actual, expected);
1452 +};
1453 +
1454 +/**
1455 + * Matcher that checks that the expected item is NOT an element in the actual Array.
1456 + *
1457 + * @param {Object} expected
1458 + * @deprecated as of 1.0. Use not.toContain() instead.
1459 + */
1460 +jasmine.Matchers.prototype.toNotContain = function(expected) {
1461 + return !this.env.contains_(this.actual, expected);
1462 +};
1463 +
1464 +jasmine.Matchers.prototype.toBeLessThan = function(expected) {
1465 + return this.actual < expected;
1466 +};
1467 +
1468 +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
1469 + return this.actual > expected;
1470 +};
1471 +
1472 +/**
1473 + * Matcher that checks that the expected item is equal to the actual item
1474 + * up to a given level of decimal precision (default 2).
1475 + *
1476 + * @param {Number} expected
1477 + * @param {Number} precision, as number of decimal places
1478 + */
1479 +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
1480 + if (!(precision === 0)) {
1481 + precision = precision || 2;
1482 + }
1483 + return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2);
1484 +};
1485 +
1486 +/**
1487 + * Matcher that checks that the expected exception was thrown by the actual.
1488 + *
1489 + * @param {String} [expected]
1490 + */
1491 +jasmine.Matchers.prototype.toThrow = function(expected) {
1492 + var result = false;
1493 + var exception;
1494 + if (typeof this.actual != 'function') {
1495 + throw new Error('Actual is not a function');
1496 + }
1497 + try {
1498 + this.actual();
1499 + } catch (e) {
1500 + exception = e;
1501 + }
1502 + if (exception) {
1503 + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
1504 + }
1505 +
1506 + var not = this.isNot ? "not " : "";
1507 +
1508 + this.message = function() {
1509 + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
1510 + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
1511 + } else {
1512 + return "Expected function to throw an exception.";
1513 + }
1514 + };
1515 +
1516 + return result;
1517 +};
1518 +
1519 +jasmine.Matchers.Any = function(expectedClass) {
1520 + this.expectedClass = expectedClass;
1521 +};
1522 +
1523 +jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
1524 + if (this.expectedClass == String) {
1525 + return typeof other == 'string' || other instanceof String;
1526 + }
1527 +
1528 + if (this.expectedClass == Number) {
1529 + return typeof other == 'number' || other instanceof Number;
1530 + }
1531 +
1532 + if (this.expectedClass == Function) {
1533 + return typeof other == 'function' || other instanceof Function;
1534 + }
1535 +
1536 + if (this.expectedClass == Object) {
1537 + return typeof other == 'object';
1538 + }
1539 +
1540 + return other instanceof this.expectedClass;
1541 +};
1542 +
1543 +jasmine.Matchers.Any.prototype.jasmineToString = function() {
1544 + return '<jasmine.any(' + this.expectedClass + ')>';
1545 +};
1546 +
1547 +jasmine.Matchers.ObjectContaining = function (sample) {
1548 + this.sample = sample;
1549 +};
1550 +
1551 +jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
1552 + mismatchKeys = mismatchKeys || [];
1553 + mismatchValues = mismatchValues || [];
1554 +
1555 + var env = jasmine.getEnv();
1556 +
1557 + var hasKey = function(obj, keyName) {
1558 + return obj != null && obj[keyName] !== jasmine.undefined;
1559 + };
1560 +
1561 + for (var property in this.sample) {
1562 + if (!hasKey(other, property) && hasKey(this.sample, property)) {
1563 + mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
1564 + }
1565 + else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
1566 + mismatchValues.push("'" + property + "' was '" + (other[property] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
1567 + }
1568 + }
1569 +
1570 + return (mismatchKeys.length === 0 && mismatchValues.length === 0);
1571 +};
1572 +
1573 +jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
1574 + return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
1575 +};
1576 +// Mock setTimeout, clearTimeout
1577 +// Contributed by Pivotal Computer Systems, www.pivotalsf.com
1578 +
1579 +jasmine.FakeTimer = function() {
1580 + this.reset();
1581 +
1582 + var self = this;
1583 + self.setTimeout = function(funcToCall, millis) {
1584 + self.timeoutsMade++;
1585 + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
1586 + return self.timeoutsMade;
1587 + };
1588 +
1589 + self.setInterval = function(funcToCall, millis) {
1590 + self.timeoutsMade++;
1591 + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
1592 + return self.timeoutsMade;
1593 + };
1594 +
1595 + self.clearTimeout = function(timeoutKey) {
1596 + self.scheduledFunctions[timeoutKey] = jasmine.undefined;
1597 + };
1598 +
1599 + self.clearInterval = function(timeoutKey) {
1600 + self.scheduledFunctions[timeoutKey] = jasmine.undefined;
1601 + };
1602 +
1603 +};
1604 +
1605 +jasmine.FakeTimer.prototype.reset = function() {
1606 + this.timeoutsMade = 0;
1607 + this.scheduledFunctions = {};
1608 + this.nowMillis = 0;
1609 +};
1610 +
1611 +jasmine.FakeTimer.prototype.tick = function(millis) {
1612 + var oldMillis = this.nowMillis;
1613 + var newMillis = oldMillis + millis;
1614 + this.runFunctionsWithinRange(oldMillis, newMillis);
1615 + this.nowMillis = newMillis;
1616 +};
1617 +
1618 +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
1619 + var scheduledFunc;
1620 + var funcsToRun = [];
1621 + for (var timeoutKey in this.scheduledFunctions) {
1622 + scheduledFunc = this.scheduledFunctions[timeoutKey];
1623 + if (scheduledFunc != jasmine.undefined &&
1624 + scheduledFunc.runAtMillis >= oldMillis &&
1625 + scheduledFunc.runAtMillis <= nowMillis) {
1626 + funcsToRun.push(scheduledFunc);
1627 + this.scheduledFunctions[timeoutKey] = jasmine.undefined;
1628 + }
1629 + }
1630 +
1631 + if (funcsToRun.length > 0) {
1632 + funcsToRun.sort(function(a, b) {
1633 + return a.runAtMillis - b.runAtMillis;
1634 + });
1635 + for (var i = 0; i < funcsToRun.length; ++i) {
1636 + try {
1637 + var funcToRun = funcsToRun[i];
1638 + this.nowMillis = funcToRun.runAtMillis;
1639 + funcToRun.funcToCall();
1640 + if (funcToRun.recurring) {
1641 + this.scheduleFunction(funcToRun.timeoutKey,
1642 + funcToRun.funcToCall,
1643 + funcToRun.millis,
1644 + true);
1645 + }
1646 + } catch(e) {
1647 + }
1648 + }
1649 + this.runFunctionsWithinRange(oldMillis, nowMillis);
1650 + }
1651 +};
1652 +
1653 +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
1654 + this.scheduledFunctions[timeoutKey] = {
1655 + runAtMillis: this.nowMillis + millis,
1656 + funcToCall: funcToCall,
1657 + recurring: recurring,
1658 + timeoutKey: timeoutKey,
1659 + millis: millis
1660 + };
1661 +};
1662 +
1663 +/**
1664 + * @namespace
1665 + */
1666 +jasmine.Clock = {
1667 + defaultFakeTimer: new jasmine.FakeTimer(),
1668 +
1669 + reset: function() {
1670 + jasmine.Clock.assertInstalled();
1671 + jasmine.Clock.defaultFakeTimer.reset();
1672 + },
1673 +
1674 + tick: function(millis) {
1675 + jasmine.Clock.assertInstalled();
1676 + jasmine.Clock.defaultFakeTimer.tick(millis);
1677 + },
1678 +
1679 + runFunctionsWithinRange: function(oldMillis, nowMillis) {
1680 + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
1681 + },
1682 +
1683 + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
1684 + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
1685 + },
1686 +
1687 + useMock: function() {
1688 + if (!jasmine.Clock.isInstalled()) {
1689 + var spec = jasmine.getEnv().currentSpec;
1690 + spec.after(jasmine.Clock.uninstallMock);
1691 +
1692 + jasmine.Clock.installMock();
1693 + }
1694 + },
1695 +
1696 + installMock: function() {
1697 + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
1698 + },
1699 +
1700 + uninstallMock: function() {
1701 + jasmine.Clock.assertInstalled();
1702 + jasmine.Clock.installed = jasmine.Clock.real;
1703 + },
1704 +
1705 + real: {
1706 + setTimeout: jasmine.getGlobal().setTimeout,
1707 + clearTimeout: jasmine.getGlobal().clearTimeout,
1708 + setInterval: jasmine.getGlobal().setInterval,
1709 + clearInterval: jasmine.getGlobal().clearInterval
1710 + },
1711 +
1712 + assertInstalled: function() {
1713 + if (!jasmine.Clock.isInstalled()) {
1714 + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
1715 + }
1716 + },
1717 +
1718 + isInstalled: function() {
1719 + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
1720 + },
1721 +
1722 + installed: null
1723 +};
1724 +jasmine.Clock.installed = jasmine.Clock.real;
1725 +
1726 +//else for IE support
1727 +jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
1728 + if (jasmine.Clock.installed.setTimeout.apply) {
1729 + return jasmine.Clock.installed.setTimeout.apply(this, arguments);
1730 + } else {
1731 + return jasmine.Clock.installed.setTimeout(funcToCall, millis);
1732 + }
1733 +};
1734 +
1735 +jasmine.getGlobal().setInterval = function(funcToCall, millis) {
1736 + if (jasmine.Clock.installed.setInterval.apply) {
1737 + return jasmine.Clock.installed.setInterval.apply(this, arguments);
1738 + } else {
1739 + return jasmine.Clock.installed.setInterval(funcToCall, millis);
1740 + }
1741 +};
1742 +
1743 +jasmine.getGlobal().clearTimeout = function(timeoutKey) {
1744 + if (jasmine.Clock.installed.clearTimeout.apply) {
1745 + return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
1746 + } else {
1747 + return jasmine.Clock.installed.clearTimeout(timeoutKey);
1748 + }
1749 +};
1750 +
1751 +jasmine.getGlobal().clearInterval = function(timeoutKey) {
1752 + if (jasmine.Clock.installed.clearTimeout.apply) {
1753 + return jasmine.Clock.installed.clearInterval.apply(this, arguments);
1754 + } else {
1755 + return jasmine.Clock.installed.clearInterval(timeoutKey);
1756 + }
1757 +};
1758 +
1759 +/**
1760 + * @constructor
1761 + */
1762 +jasmine.MultiReporter = function() {
1763 + this.subReporters_ = [];
1764 +};
1765 +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
1766 +
1767 +jasmine.MultiReporter.prototype.addReporter = function(reporter) {
1768 + this.subReporters_.push(reporter);
1769 +};
1770 +
1771 +(function() {
1772 + var functionNames = [
1773 + "reportRunnerStarting",
1774 + "reportRunnerResults",
1775 + "reportSuiteResults",
1776 + "reportSpecStarting",
1777 + "reportSpecResults",
1778 + "log"
1779 + ];
1780 + for (var i = 0; i < functionNames.length; i++) {
1781 + var functionName = functionNames[i];
1782 + jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
1783 + return function() {
1784 + for (var j = 0; j < this.subReporters_.length; j++) {
1785 + var subReporter = this.subReporters_[j];
1786 + if (subReporter[functionName]) {
1787 + subReporter[functionName].apply(subReporter, arguments);
1788 + }
1789 + }
1790 + };
1791 + })(functionName);
1792 + }
1793 +})();
1794 +/**
1795 + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
1796 + *
1797 + * @constructor
1798 + */
1799 +jasmine.NestedResults = function() {
1800 + /**
1801 + * The total count of results
1802 + */
1803 + this.totalCount = 0;
1804 + /**
1805 + * Number of passed results
1806 + */
1807 + this.passedCount = 0;
1808 + /**
1809 + * Number of failed results
1810 + */
1811 + this.failedCount = 0;
1812 + /**
1813 + * Was this suite/spec skipped?
1814 + */
1815 + this.skipped = false;
1816 + /**
1817 + * @ignore
1818 + */
1819 + this.items_ = [];
1820 +};
1821 +
1822 +/**
1823 + * Roll up the result counts.
1824 + *
1825 + * @param result
1826 + */
1827 +jasmine.NestedResults.prototype.rollupCounts = function(result) {
1828 + this.totalCount += result.totalCount;
1829 + this.passedCount += result.passedCount;
1830 + this.failedCount += result.failedCount;
1831 +};
1832 +
1833 +/**
1834 + * Adds a log message.
1835 + * @param values Array of message parts which will be concatenated later.
1836 + */
1837 +jasmine.NestedResults.prototype.log = function(values) {
1838 + this.items_.push(new jasmine.MessageResult(values));
1839 +};
1840 +
1841 +/**
1842 + * Getter for the results: message & results.
1843 + */
1844 +jasmine.NestedResults.prototype.getItems = function() {
1845 + return this.items_;
1846 +};
1847 +
1848 +/**
1849 + * Adds a result, tracking counts (total, passed, & failed)
1850 + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
1851 + */
1852 +jasmine.NestedResults.prototype.addResult = function(result) {
1853 + if (result.type != 'log') {
1854 + if (result.items_) {
1855 + this.rollupCounts(result);
1856 + } else {
1857 + this.totalCount++;
1858 + if (result.passed()) {
1859 + this.passedCount++;
1860 + } else {
1861 + this.failedCount++;
1862 + }
1863 + }
1864 + }
1865 + this.items_.push(result);
1866 +};
1867 +
1868 +/**
1869 + * @returns {Boolean} True if <b>everything</b> below passed
1870 + */
1871 +jasmine.NestedResults.prototype.passed = function() {
1872 + return this.passedCount === this.totalCount;
1873 +};
1874 +/**
1875 + * Base class for pretty printing for expectation results.
1876 + */
1877 +jasmine.PrettyPrinter = function() {
1878 + this.ppNestLevel_ = 0;
1879 +};
1880 +
1881 +/**
1882 + * Formats a value in a nice, human-readable string.
1883 + *
1884 + * @param value
1885 + */
1886 +jasmine.PrettyPrinter.prototype.format = function(value) {
1887 + this.ppNestLevel_++;
1888 + try {
1889 + if (value === jasmine.undefined) {
1890 + this.emitScalar('undefined');
1891 + } else if (value === null) {
1892 + this.emitScalar('null');
1893 + } else if (value === jasmine.getGlobal()) {
1894 + this.emitScalar('<global>');
1895 + } else if (value.jasmineToString) {
1896 + this.emitScalar(value.jasmineToString());
1897 + } else if (typeof value === 'string') {
1898 + this.emitString(value);
1899 + } else if (jasmine.isSpy(value)) {
1900 + this.emitScalar("spy on " + value.identity);
1901 + } else if (value instanceof RegExp) {
1902 + this.emitScalar(value.toString());
1903 + } else if (typeof value === 'function') {
1904 + this.emitScalar('Function');
1905 + } else if (typeof value.nodeType === 'number') {
1906 + this.emitScalar('HTMLNode');
1907 + } else if (value instanceof Date) {
1908 + this.emitScalar('Date(' + value + ')');
1909 + } else if (value.__Jasmine_been_here_before__) {
1910 + this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
1911 + } else if (jasmine.isArray_(value) || typeof value == 'object') {
1912 + value.__Jasmine_been_here_before__ = true;
1913 + if (jasmine.isArray_(value)) {
1914 + this.emitArray(value);
1915 + } else {
1916 + this.emitObject(value);
1917 + }
1918 + delete value.__Jasmine_been_here_before__;
1919 + } else {
1920 + this.emitScalar(value.toString());
1921 + }
1922 + } finally {
1923 + this.ppNestLevel_--;
1924 + }
1925 +};
1926 +
1927 +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
1928 + for (var property in obj) {
1929 + if (!obj.hasOwnProperty(property)) continue;
1930 + if (property == '__Jasmine_been_here_before__') continue;
1931 + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
1932 + obj.__lookupGetter__(property) !== null) : false);
1933 + }
1934 +};
1935 +
1936 +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
1937 +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
1938 +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
1939 +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
1940 +
1941 +jasmine.StringPrettyPrinter = function() {
1942 + jasmine.PrettyPrinter.call(this);
1943 +
1944 + this.string = '';
1945 +};
1946 +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
1947 +
1948 +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
1949 + this.append(value);
1950 +};
1951 +
1952 +jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
1953 + this.append("'" + value + "'");
1954 +};
1955 +
1956 +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
1957 + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
1958 + this.append("Array");
1959 + return;
1960 + }
1961 +
1962 + this.append('[ ');
1963 + for (var i = 0; i < array.length; i++) {
1964 + if (i > 0) {
1965 + this.append(', ');
1966 + }
1967 + this.format(array[i]);
1968 + }
1969 + this.append(' ]');
1970 +};
1971 +
1972 +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
1973 + if (this.ppNestLevel_ > jasmine.MAX_PRETTY_PRINT_DEPTH) {
1974 + this.append("Object");
1975 + return;
1976 + }
1977 +
1978 + var self = this;
1979 + this.append('{ ');
1980 + var first = true;
1981 +
1982 + this.iterateObject(obj, function(property, isGetter) {
1983 + if (first) {
1984 + first = false;
1985 + } else {
1986 + self.append(', ');
1987 + }
1988 +
1989 + self.append(property);
1990 + self.append(' : ');
1991 + if (isGetter) {
1992 + self.append('<getter>');
1993 + } else {
1994 + self.format(obj[property]);
1995 + }
1996 + });
1997 +
1998 + this.append(' }');
1999 +};
2000 +
2001 +jasmine.StringPrettyPrinter.prototype.append = function(value) {
2002 + this.string += value;
2003 +};
2004 +jasmine.Queue = function(env) {
2005 + this.env = env;
2006 +
2007 + // parallel to blocks. each true value in this array means the block will
2008 + // get executed even if we abort
2009 + this.ensured = [];
2010 + this.blocks = [];
2011 + this.running = false;
2012 + this.index = 0;
2013 + this.offset = 0;
2014 + this.abort = false;
2015 +};
2016 +
2017 +jasmine.Queue.prototype.addBefore = function(block, ensure) {
2018 + if (ensure === jasmine.undefined) {
2019 + ensure = false;
2020 + }
2021 +
2022 + this.blocks.unshift(block);
2023 + this.ensured.unshift(ensure);
2024 +};
2025 +
2026 +jasmine.Queue.prototype.add = function(block, ensure) {
2027 + if (ensure === jasmine.undefined) {
2028 + ensure = false;
2029 + }
2030 +
2031 + this.blocks.push(block);
2032 + this.ensured.push(ensure);
2033 +};
2034 +
2035 +jasmine.Queue.prototype.insertNext = function(block, ensure) {
2036 + if (ensure === jasmine.undefined) {
2037 + ensure = false;
2038 + }
2039 +
2040 + this.ensured.splice((this.index + this.offset + 1), 0, ensure);
2041 + this.blocks.splice((this.index + this.offset + 1), 0, block);
2042 + this.offset++;
2043 +};
2044 +
2045 +jasmine.Queue.prototype.start = function(onComplete) {
2046 + this.running = true;
2047 + this.onComplete = onComplete;
2048 + this.next_();
2049 +};
2050 +
2051 +jasmine.Queue.prototype.isRunning = function() {
2052 + return this.running;
2053 +};
2054 +
2055 +jasmine.Queue.LOOP_DONT_RECURSE = true;
2056 +
2057 +jasmine.Queue.prototype.next_ = function() {
2058 + var self = this;
2059 + var goAgain = true;
2060 +
2061 + while (goAgain) {
2062 + goAgain = false;
2063 +
2064 + if (self.index < self.blocks.length && !(this.abort && !this.ensured[self.index])) {
2065 + var calledSynchronously = true;
2066 + var completedSynchronously = false;
2067 +
2068 + var onComplete = function () {
2069 + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
2070 + completedSynchronously = true;
2071 + return;
2072 + }
2073 +
2074 + if (self.blocks[self.index].abort) {
2075 + self.abort = true;
2076 + }
2077 +
2078 + self.offset = 0;
2079 + self.index++;
2080 +
2081 + var now = new Date().getTime();
2082 + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
2083 + self.env.lastUpdate = now;
2084 + self.env.setTimeout(function() {
2085 + self.next_();
2086 + }, 0);
2087 + } else {
2088 + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
2089 + goAgain = true;
2090 + } else {
2091 + self.next_();
2092 + }
2093 + }
2094 + };
2095 + self.blocks[self.index].execute(onComplete);
2096 +
2097 + calledSynchronously = false;
2098 + if (completedSynchronously) {
2099 + onComplete();
2100 + }
2101 +
2102 + } else {
2103 + self.running = false;
2104 + if (self.onComplete) {
2105 + self.onComplete();
2106 + }
2107 + }
2108 + }
2109 +};
2110 +
2111 +jasmine.Queue.prototype.results = function() {
2112 + var results = new jasmine.NestedResults();
2113 + for (var i = 0; i < this.blocks.length; i++) {
2114 + if (this.blocks[i].results) {
2115 + results.addResult(this.blocks[i].results());
2116 + }
2117 + }
2118 + return results;
2119 +};
2120 +
2121 +
2122 +/**
2123 + * Runner
2124 + *
2125 + * @constructor
2126 + * @param {jasmine.Env} env
2127 + */
2128 +jasmine.Runner = function(env) {
2129 + var self = this;
2130 + self.env = env;
2131 + self.queue = new jasmine.Queue(env);
2132 + self.before_ = [];
2133 + self.after_ = [];
2134 + self.suites_ = [];
2135 +};
2136 +
2137 +jasmine.Runner.prototype.execute = function() {
2138 + var self = this;
2139 + if (self.env.reporter.reportRunnerStarting) {
2140 + self.env.reporter.reportRunnerStarting(this);
2141 + }
2142 + self.queue.start(function () {
2143 + self.finishCallback();
2144 + });
2145 +};
2146 +
2147 +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
2148 + beforeEachFunction.typeName = 'beforeEach';
2149 + this.before_.splice(0,0,beforeEachFunction);
2150 +};
2151 +
2152 +jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
2153 + afterEachFunction.typeName = 'afterEach';
2154 + this.after_.splice(0,0,afterEachFunction);
2155 +};
2156 +
2157 +
2158 +jasmine.Runner.prototype.finishCallback = function() {
2159 + this.env.reporter.reportRunnerResults(this);
2160 +};
2161 +
2162 +jasmine.Runner.prototype.addSuite = function(suite) {
2163 + this.suites_.push(suite);
2164 +};
2165 +
2166 +jasmine.Runner.prototype.add = function(block) {
2167 + if (block instanceof jasmine.Suite) {
2168 + this.addSuite(block);
2169 + }
2170 + this.queue.add(block);
2171 +};
2172 +
2173 +jasmine.Runner.prototype.specs = function () {
2174 + var suites = this.suites();
2175 + var specs = [];
2176 + for (var i = 0; i < suites.length; i++) {
2177 + specs = specs.concat(suites[i].specs());
2178 + }
2179 + return specs;
2180 +};
2181 +
2182 +jasmine.Runner.prototype.suites = function() {
2183 + return this.suites_;
2184 +};
2185 +
2186 +jasmine.Runner.prototype.topLevelSuites = function() {
2187 + var topLevelSuites = [];
2188 + for (var i = 0; i < this.suites_.length; i++) {
2189 + if (!this.suites_[i].parentSuite) {
2190 + topLevelSuites.push(this.suites_[i]);
2191 + }
2192 + }
2193 + return topLevelSuites;
2194 +};
2195 +
2196 +jasmine.Runner.prototype.results = function() {
2197 + return this.queue.results();
2198 +};
2199 +/**
2200 + * Internal representation of a Jasmine specification, or test.
2201 + *
2202 + * @constructor
2203 + * @param {jasmine.Env} env
2204 + * @param {jasmine.Suite} suite
2205 + * @param {String} description
2206 + */
2207 +jasmine.Spec = function(env, suite, description) {
2208 + if (!env) {
2209 + throw new Error('jasmine.Env() required');
2210 + }
2211 + if (!suite) {
2212 + throw new Error('jasmine.Suite() required');
2213 + }
2214 + var spec = this;
2215 + spec.id = env.nextSpecId ? env.nextSpecId() : null;
2216 + spec.env = env;
2217 + spec.suite = suite;
2218 + spec.description = description;
2219 + spec.queue = new jasmine.Queue(env);
2220 +
2221 + spec.afterCallbacks = [];
2222 + spec.spies_ = [];
2223 +
2224 + spec.results_ = new jasmine.NestedResults();
2225 + spec.results_.description = description;
2226 + spec.matchersClass = null;
2227 +};
2228 +
2229 +jasmine.Spec.prototype.getFullName = function() {
2230 + return this.suite.getFullName() + ' ' + this.description + '.';
2231 +};
2232 +
2233 +
2234 +jasmine.Spec.prototype.results = function() {
2235 + return this.results_;
2236 +};
2237 +
2238 +/**
2239 + * All parameters are pretty-printed and concatenated together, then written to the spec's output.
2240 + *
2241 + * Be careful not to leave calls to <code>jasmine.log</code> in production code.
2242 + */
2243 +jasmine.Spec.prototype.log = function() {
2244 + return this.results_.log(arguments);
2245 +};
2246 +
2247 +jasmine.Spec.prototype.runs = function (func) {
2248 + var block = new jasmine.Block(this.env, func, this);
2249 + this.addToQueue(block);
2250 + return this;
2251 +};
2252 +
2253 +jasmine.Spec.prototype.addToQueue = function (block) {
2254 + if (this.queue.isRunning()) {
2255 + this.queue.insertNext(block);
2256 + } else {
2257 + this.queue.add(block);
2258 + }
2259 +};
2260 +
2261 +/**
2262 + * @param {jasmine.ExpectationResult} result
2263 + */
2264 +jasmine.Spec.prototype.addMatcherResult = function(result) {
2265 + this.results_.addResult(result);
2266 +};
2267 +
2268 +jasmine.Spec.prototype.expect = function(actual) {
2269 + var positive = new (this.getMatchersClass_())(this.env, actual, this);
2270 + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
2271 + return positive;
2272 +};
2273 +
2274 +/**
2275 + * Waits a fixed time period before moving to the next block.
2276 + *
2277 + * @deprecated Use waitsFor() instead
2278 + * @param {Number} timeout milliseconds to wait
2279 + */
2280 +jasmine.Spec.prototype.waits = function(timeout) {
2281 + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
2282 + this.addToQueue(waitsFunc);
2283 + return this;
2284 +};
2285 +
2286 +/**
2287 + * Waits for the latchFunction to return true before proceeding to the next block.
2288 + *
2289 + * @param {Function} latchFunction
2290 + * @param {String} optional_timeoutMessage
2291 + * @param {Number} optional_timeout
2292 + */
2293 +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
2294 + var latchFunction_ = null;
2295 + var optional_timeoutMessage_ = null;
2296 + var optional_timeout_ = null;
2297 +
2298 + for (var i = 0; i < arguments.length; i++) {
2299 + var arg = arguments[i];
2300 + switch (typeof arg) {
2301 + case 'function':
2302 + latchFunction_ = arg;
2303 + break;
2304 + case 'string':
2305 + optional_timeoutMessage_ = arg;
2306 + break;
2307 + case 'number':
2308 + optional_timeout_ = arg;
2309 + break;
2310 + }
2311 + }
2312 +
2313 + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
2314 + this.addToQueue(waitsForFunc);
2315 + return this;
2316 +};
2317 +
2318 +jasmine.Spec.prototype.fail = function (e) {
2319 + var expectationResult = new jasmine.ExpectationResult({
2320 + passed: false,
2321 + message: e ? jasmine.util.formatException(e) : 'Exception',
2322 + trace: { stack: e.stack }
2323 + });
2324 + this.results_.addResult(expectationResult);
2325 +};
2326 +
2327 +jasmine.Spec.prototype.getMatchersClass_ = function() {
2328 + return this.matchersClass || this.env.matchersClass;
2329 +};
2330 +
2331 +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
2332 + var parent = this.getMatchersClass_();
2333 + var newMatchersClass = function() {
2334 + parent.apply(this, arguments);
2335 + };
2336 + jasmine.util.inherit(newMatchersClass, parent);
2337 + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
2338 + this.matchersClass = newMatchersClass;
2339 +};
2340 +
2341 +jasmine.Spec.prototype.finishCallback = function() {
2342 + this.env.reporter.reportSpecResults(this);
2343 +};
2344 +
2345 +jasmine.Spec.prototype.finish = function(onComplete) {
2346 + this.removeAllSpies();
2347 + this.finishCallback();
2348 + if (onComplete) {
2349 + onComplete();
2350 + }
2351 +};
2352 +
2353 +jasmine.Spec.prototype.after = function(doAfter) {
2354 + if (this.queue.isRunning()) {
2355 + this.queue.add(new jasmine.Block(this.env, doAfter, this), true);
2356 + } else {
2357 + this.afterCallbacks.unshift(doAfter);
2358 + }
2359 +};
2360 +
2361 +jasmine.Spec.prototype.execute = function(onComplete) {
2362 + var spec = this;
2363 + if (!spec.env.specFilter(spec)) {
2364 + spec.results_.skipped = true;
2365 + spec.finish(onComplete);
2366 + return;
2367 + }
2368 +
2369 + this.env.reporter.reportSpecStarting(this);
2370 +
2371 + spec.env.currentSpec = spec;
2372 +
2373 + spec.addBeforesAndAftersToQueue();
2374 +
2375 + spec.queue.start(function () {
2376 + spec.finish(onComplete);
2377 + });
2378 +};
2379 +
2380 +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
2381 + var runner = this.env.currentRunner();
2382 + var i;
2383 +
2384 + for (var suite = this.suite; suite; suite = suite.parentSuite) {
2385 + for (i = 0; i < suite.before_.length; i++) {
2386 + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
2387 + }
2388 + }
2389 + for (i = 0; i < runner.before_.length; i++) {
2390 + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
2391 + }
2392 + for (i = 0; i < this.afterCallbacks.length; i++) {
2393 + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this), true);
2394 + }
2395 + for (suite = this.suite; suite; suite = suite.parentSuite) {
2396 + for (i = 0; i < suite.after_.length; i++) {
2397 + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this), true);
2398 + }
2399 + }
2400 + for (i = 0; i < runner.after_.length; i++) {
2401 + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this), true);
2402 + }
2403 +};
2404 +
2405 +jasmine.Spec.prototype.explodes = function() {
2406 + throw 'explodes function should not have been called';
2407 +};
2408 +
2409 +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
2410 + if (obj == jasmine.undefined) {
2411 + throw "spyOn could not find an object to spy upon for " + methodName + "()";
2412 + }
2413 +
2414 + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
2415 + throw methodName + '() method does not exist';
2416 + }
2417 +
2418 + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
2419 + throw new Error(methodName + ' has already been spied upon');
2420 + }
2421 +
2422 + var spyObj = jasmine.createSpy(methodName);
2423 +
2424 + this.spies_.push(spyObj);
2425 + spyObj.baseObj = obj;
2426 + spyObj.methodName = methodName;
2427 + spyObj.originalValue = obj[methodName];
2428 +
2429 + obj[methodName] = spyObj;
2430 +
2431 + return spyObj;
2432 +};
2433 +
2434 +jasmine.Spec.prototype.removeAllSpies = function() {
2435 + for (var i = 0; i < this.spies_.length; i++) {
2436 + var spy = this.spies_[i];
2437 + spy.baseObj[spy.methodName] = spy.originalValue;
2438 + }
2439 + this.spies_ = [];
2440 +};
2441 +
2442 +/**
2443 + * Internal representation of a Jasmine suite.
2444 + *
2445 + * @constructor
2446 + * @param {jasmine.Env} env
2447 + * @param {String} description
2448 + * @param {Function} specDefinitions
2449 + * @param {jasmine.Suite} parentSuite
2450 + */
2451 +jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
2452 + var self = this;
2453 + self.id = env.nextSuiteId ? env.nextSuiteId() : null;
2454 + self.description = description;
2455 + self.queue = new jasmine.Queue(env);
2456 + self.parentSuite = parentSuite;
2457 + self.env = env;
2458 + self.before_ = [];
2459 + self.after_ = [];
2460 + self.children_ = [];
2461 + self.suites_ = [];
2462 + self.specs_ = [];
2463 +};
2464 +
2465 +jasmine.Suite.prototype.getFullName = function() {
2466 + var fullName = this.description;
2467 + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
2468 + fullName = parentSuite.description + ' ' + fullName;
2469 + }
2470 + return fullName;
2471 +};
2472 +
2473 +jasmine.Suite.prototype.finish = function(onComplete) {
2474 + this.env.reporter.reportSuiteResults(this);
2475 + this.finished = true;
2476 + if (typeof(onComplete) == 'function') {
2477 + onComplete();
2478 + }
2479 +};
2480 +
2481 +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
2482 + beforeEachFunction.typeName = 'beforeEach';
2483 + this.before_.unshift(beforeEachFunction);
2484 +};
2485 +
2486 +jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
2487 + afterEachFunction.typeName = 'afterEach';
2488 + this.after_.unshift(afterEachFunction);
2489 +};
2490 +
2491 +jasmine.Suite.prototype.results = function() {
2492 + return this.queue.results();
2493 +};
2494 +
2495 +jasmine.Suite.prototype.add = function(suiteOrSpec) {
2496 + this.children_.push(suiteOrSpec);
2497 + if (suiteOrSpec instanceof jasmine.Suite) {
2498 + this.suites_.push(suiteOrSpec);
2499 + this.env.currentRunner().addSuite(suiteOrSpec);
2500 + } else {
2501 + this.specs_.push(suiteOrSpec);
2502 + }
2503 + this.queue.add(suiteOrSpec);
2504 +};
2505 +
2506 +jasmine.Suite.prototype.specs = function() {
2507 + return this.specs_;
2508 +};
2509 +
2510 +jasmine.Suite.prototype.suites = function() {
2511 + return this.suites_;
2512 +};
2513 +
2514 +jasmine.Suite.prototype.children = function() {
2515 + return this.children_;
2516 +};
2517 +
2518 +jasmine.Suite.prototype.execute = function(onComplete) {
2519 + var self = this;
2520 + this.queue.start(function () {
2521 + self.finish(onComplete);
2522 + });
2523 +};
2524 +jasmine.WaitsBlock = function(env, timeout, spec) {
2525 + this.timeout = timeout;
2526 + jasmine.Block.call(this, env, null, spec);
2527 +};
2528 +
2529 +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
2530 +
2531 +jasmine.WaitsBlock.prototype.execute = function (onComplete) {
2532 + if (jasmine.VERBOSE) {
2533 + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
2534 + }
2535 + this.env.setTimeout(function () {
2536 + onComplete();
2537 + }, this.timeout);
2538 +};
2539 +/**
2540 + * A block which waits for some condition to become true, with timeout.
2541 + *
2542 + * @constructor
2543 + * @extends jasmine.Block
2544 + * @param {jasmine.Env} env The Jasmine environment.
2545 + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
2546 + * @param {Function} latchFunction A function which returns true when the desired condition has been met.
2547 + * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
2548 + * @param {jasmine.Spec} spec The Jasmine spec.
2549 + */
2550 +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
2551 + this.timeout = timeout || env.defaultTimeoutInterval;
2552 + this.latchFunction = latchFunction;
2553 + this.message = message;
2554 + this.totalTimeSpentWaitingForLatch = 0;
2555 + jasmine.Block.call(this, env, null, spec);
2556 +};
2557 +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
2558 +
2559 +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
2560 +
2561 +jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
2562 + if (jasmine.VERBOSE) {
2563 + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
2564 + }
2565 + var latchFunctionResult;
2566 + try {
2567 + latchFunctionResult = this.latchFunction.apply(this.spec);
2568 + } catch (e) {
2569 + this.spec.fail(e);
2570 + onComplete();
2571 + return;
2572 + }
2573 +
2574 + if (latchFunctionResult) {
2575 + onComplete();
2576 + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
2577 + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
2578 + this.spec.fail({
2579 + name: 'timeout',
2580 + message: message
2581 + });
2582 +
2583 + this.abort = true;
2584 + onComplete();
2585 + } else {
2586 + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
2587 + var self = this;
2588 + this.env.setTimeout(function() {
2589 + self.execute(onComplete);
2590 + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
2591 + }
2592 +};
2593 +
2594 +jasmine.version_= {
2595 + "major": 1,
2596 + "minor": 3,
2597 + "build": 1,
2598 + "revision": 1354556913
2599 +};
vendor/woodchipper.js new
+62
@@ -0,0 +1,62 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +'use strict';
17 +
18 +var recast = require('recast');
19 +
20 +exports.debranch = function(constants, source, writeback) {
21 + recast.runString(
22 + source,
23 + function(ast, callback) {
24 + callback(new Woodchipper(constants).visit(ast));
25 + },
26 + { writeback: writeback }
27 + );
28 +};
29 +
30 +var Woodchipper = recast.Visitor.extend({
31 + init: function(constants) {
32 + this.constants = constants || {};
33 + },
34 +
35 + visitIdentifier: function(ident) {
36 + if (this.constants.hasOwnProperty(ident.name)) {
37 + return recast.builder.literal(this.constants[ident.name]);
38 + }
39 + },
40 +
41 + visitIfStatement: function(stmt) {
42 + // Replaces all identifiers in this.constants with literal values.
43 + this.genericVisit(stmt);
44 +
45 + if (stmt.test.type === recast.Syntax.Literal) {
46 + if (stmt.test.value) {
47 + stmt.alternate = null;
48 + } else if (stmt.alternate) {
49 + return stmt.alternate;
50 + } else {
51 + this.remove();
52 + }
53 + }
54 + }
55 +});
56 +
57 +if (!module.parent) {
58 + var constants = JSON.parse(process.argv[3]);
59 + recast.run(function(ast, callback) {
60 + callback(new Woodchipper(constants).visit(ast));
61 + });
62 +}