summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Branca <chewbranca@gmail.com>2013-03-19 16:23:22 -0700
committerRussell Branca <chewbranca@gmail.com>2013-03-19 16:23:22 -0700
commit4e3d67456c60e456bcb267e765e7df7d3429913c (patch)
treef9b3a9851e3cb2b369e9d900df6d311b15ce2006
parent15c1a97e42c5bafad6f5fa83d753d3a7cf64d6d7 (diff)
parent28c8973bf19cd141ab19803f79e8df4821a0aaf7 (diff)
downloadcouchdb-4e3d67456c60e456bcb267e765e7df7d3429913c.tar.gz
Merge branch 'fauxton'
Conflicts: .gitignore NOTICE configure.ac license.skip
-rw-r--r--.gitignore13
-rw-r--r--LICENSE285
-rw-r--r--NOTICE52
-rw-r--r--license.skip13
-rw-r--r--src/fauxton/Gruntfile.js351
-rw-r--r--src/fauxton/TODO.md26
-rw-r--r--src/fauxton/app/addons/config/base.js28
-rw-r--r--src/fauxton/app/addons/config/resources.js175
-rw-r--r--src/fauxton/app/addons/config/routes.js44
-rw-r--r--src/fauxton/app/addons/config/templates/dashboard.html52
-rw-r--r--src/fauxton/app/addons/config/templates/item.html31
-rw-r--r--src/fauxton/app/addons/contribute/base.js33
-rw-r--r--src/fauxton/app/addons/logs/base.js28
-rw-r--r--src/fauxton/app/addons/logs/resources.js225
-rw-r--r--src/fauxton/app/addons/logs/routes.js47
-rw-r--r--src/fauxton/app/addons/logs/templates/dashboard.html46
-rw-r--r--src/fauxton/app/addons/logs/templates/filterItem.html16
-rw-r--r--src/fauxton/app/addons/logs/templates/sidebar.html27
-rw-r--r--src/fauxton/app/addons/stats/assets/less/stats.less20
-rw-r--r--src/fauxton/app/addons/stats/base.js28
-rw-r--r--src/fauxton/app/addons/stats/resources.js37
-rw-r--r--src/fauxton/app/addons/stats/routes.js59
-rw-r--r--src/fauxton/app/addons/stats/templates/by_method.html16
-rw-r--r--src/fauxton/app/addons/stats/templates/pie_table.html56
-rw-r--r--src/fauxton/app/addons/stats/templates/stats.html16
-rw-r--r--src/fauxton/app/addons/stats/templates/statselect.html22
-rw-r--r--src/fauxton/app/addons/stats/views.js172
-rw-r--r--src/fauxton/app/api.js101
-rw-r--r--src/fauxton/app/app.js74
-rw-r--r--src/fauxton/app/config.js53
-rw-r--r--src/fauxton/app/helpers.js35
-rw-r--r--src/fauxton/app/initialize.js62
-rw-r--r--src/fauxton/app/load_addons.js.underscore27
-rw-r--r--src/fauxton/app/main.js38
-rw-r--r--src/fauxton/app/modules/databases/base.js36
-rw-r--r--src/fauxton/app/modules/databases/resources.js146
-rw-r--r--src/fauxton/app/modules/databases/routes.js70
-rw-r--r--src/fauxton/app/modules/databases/views.js168
-rw-r--r--src/fauxton/app/modules/documents/base.js24
-rw-r--r--src/fauxton/app/modules/documents/resources.js255
-rw-r--r--src/fauxton/app/modules/documents/routes.js426
-rw-r--r--src/fauxton/app/modules/documents/views.js950
-rw-r--r--src/fauxton/app/modules/fauxton/base.js124
-rw-r--r--src/fauxton/app/modules/fauxton/layout.js96
-rw-r--r--src/fauxton/app/modules/pouchdb/base.js57
-rw-r--r--src/fauxton/app/modules/pouchdb/pouch.collate.js115
-rw-r--r--src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js286
-rw-r--r--src/fauxton/app/router.js160
-rw-r--r--src/fauxton/app/templates/databases/item.html25
-rw-r--r--src/fauxton/app/templates/databases/list.html35
-rw-r--r--src/fauxton/app/templates/databases/sidebar.html27
-rw-r--r--src/fauxton/app/templates/documents/all_docs_item.html26
-rw-r--r--src/fauxton/app/templates/documents/all_docs_list.html141
-rw-r--r--src/fauxton/app/templates/documents/changes.html36
-rw-r--r--src/fauxton/app/templates/documents/doc.html28
-rw-r--r--src/fauxton/app/templates/documents/doc_field_editor.html60
-rw-r--r--src/fauxton/app/templates/documents/doc_field_editor_tabs.html26
-rw-r--r--src/fauxton/app/templates/documents/index_menu_item.html17
-rw-r--r--src/fauxton/app/templates/documents/index_row_docular.html26
-rw-r--r--src/fauxton/app/templates/documents/index_row_tabular.html25
-rw-r--r--src/fauxton/app/templates/documents/search.html15
-rw-r--r--src/fauxton/app/templates/documents/sidebar.html29
-rw-r--r--src/fauxton/app/templates/documents/tabs.html39
-rw-r--r--src/fauxton/app/templates/documents/view_editor.html92
-rw-r--r--src/fauxton/app/templates/fauxton/api_bar.html30
-rw-r--r--src/fauxton/app/templates/fauxton/breadcrumbs.html24
-rw-r--r--src/fauxton/app/templates/fauxton/nav_bar.html49
-rw-r--r--src/fauxton/app/templates/fauxton/notification.html18
-rw-r--r--src/fauxton/app/templates/layouts/one_pane.html28
-rw-r--r--src/fauxton/app/templates/layouts/two_pane.html29
-rw-r--r--src/fauxton/app/templates/layouts/with_right_sidebar.html27
-rw-r--r--src/fauxton/app/templates/layouts/with_sidebar.html27
-rw-r--r--src/fauxton/app/templates/layouts/with_tabs.html27
-rw-r--r--src/fauxton/app/templates/layouts/with_tabs_sidebar.html28
-rw-r--r--src/fauxton/assets/css/codemirror.css169
-rw-r--r--src/fauxton/assets/css/nv.d3.css656
-rw-r--r--src/fauxton/assets/img/couchdblogo.pngbin0 -> 2738 bytes
-rw-r--r--src/fauxton/assets/img/glyphicons-halflings-white.pngbin0 -> 8777 bytes
-rw-r--r--src/fauxton/assets/img/glyphicons-halflings.pngbin0 -> 13826 bytes
-rw-r--r--src/fauxton/assets/index.underscore55
-rw-r--r--src/fauxton/assets/js/libs/almond.js314
-rw-r--r--src/fauxton/assets/js/libs/backbone.js1533
-rw-r--r--src/fauxton/assets/js/libs/bootstrap.js2025
-rw-r--r--src/fauxton/assets/js/libs/codemirror.js3231
-rw-r--r--src/fauxton/assets/js/libs/d3.js7026
-rw-r--r--src/fauxton/assets/js/libs/jquery.js9404
-rw-r--r--src/fauxton/assets/js/libs/jshint.js4529
-rw-r--r--src/fauxton/assets/js/libs/lodash.js3554
-rwxr-xr-xsrc/fauxton/assets/js/libs/nv.d3.js11286
-rw-r--r--src/fauxton/assets/js/libs/require.js2037
-rw-r--r--src/fauxton/assets/js/plugins/backbone.layoutmanager.js781
-rw-r--r--src/fauxton/assets/js/plugins/codemirror-javascript.js361
-rw-r--r--src/fauxton/assets/js/plugins/prettify.js28
-rw-r--r--src/fauxton/assets/less/bootstrap/accordion.less34
-rw-r--r--src/fauxton/assets/less/bootstrap/alerts.less65
-rw-r--r--src/fauxton/assets/less/bootstrap/bootstrap.less63
-rw-r--r--src/fauxton/assets/less/bootstrap/breadcrumbs.less24
-rw-r--r--src/fauxton/assets/less/bootstrap/button-groups.less242
-rw-r--r--src/fauxton/assets/less/bootstrap/buttons.less232
-rw-r--r--src/fauxton/assets/less/bootstrap/carousel.less131
-rw-r--r--src/fauxton/assets/less/bootstrap/close.less31
-rw-r--r--src/fauxton/assets/less/bootstrap/code.less58
-rw-r--r--src/fauxton/assets/less/bootstrap/component-animations.less22
-rw-r--r--src/fauxton/assets/less/bootstrap/dropdowns.less237
-rw-r--r--src/fauxton/assets/less/bootstrap/forms.less683
-rw-r--r--src/fauxton/assets/less/bootstrap/grid.less21
-rw-r--r--src/fauxton/assets/less/bootstrap/hero-unit.less25
-rw-r--r--src/fauxton/assets/less/bootstrap/labels-badges.less74
-rw-r--r--src/fauxton/assets/less/bootstrap/layouts.less16
-rw-r--r--src/fauxton/assets/less/bootstrap/media.less55
-rw-r--r--src/fauxton/assets/less/bootstrap/mixins.less686
-rw-r--r--src/fauxton/assets/less/bootstrap/modals.less94
-rw-r--r--src/fauxton/assets/less/bootstrap/navbar.less475
-rw-r--r--src/fauxton/assets/less/bootstrap/navs.less385
-rw-r--r--src/fauxton/assets/less/bootstrap/pager.less41
-rw-r--r--src/fauxton/assets/less/bootstrap/pagination.less121
-rw-r--r--src/fauxton/assets/less/bootstrap/popovers.less117
-rw-r--r--src/fauxton/assets/less/bootstrap/progress-bars.less122
-rw-r--r--src/fauxton/assets/less/bootstrap/reset.less138
-rw-r--r--src/fauxton/assets/less/bootstrap/responsive-1200px-min.less28
-rw-r--r--src/fauxton/assets/less/bootstrap/responsive-767px-max.less193
-rw-r--r--src/fauxton/assets/less/bootstrap/responsive-768px-979px.less19
-rw-r--r--src/fauxton/assets/less/bootstrap/responsive-navbar.less185
-rw-r--r--src/fauxton/assets/less/bootstrap/responsive-utilities.less43
-rw-r--r--src/fauxton/assets/less/bootstrap/responsive.less48
-rw-r--r--src/fauxton/assets/less/bootstrap/scaffolding.less52
-rw-r--r--src/fauxton/assets/less/bootstrap/sprites.less193
-rw-r--r--src/fauxton/assets/less/bootstrap/tables.less236
-rw-r--r--src/fauxton/assets/less/bootstrap/tests/buttons.html139
-rw-r--r--src/fauxton/assets/less/bootstrap/tests/css-tests.css139
-rw-r--r--src/fauxton/assets/less/bootstrap/tests/css-tests.html1296
-rw-r--r--src/fauxton/assets/less/bootstrap/tests/forms-responsive.html71
-rw-r--r--src/fauxton/assets/less/bootstrap/tests/forms.html179
-rw-r--r--src/fauxton/assets/less/bootstrap/tests/navbar-fixed-top.html104
-rw-r--r--src/fauxton/assets/less/bootstrap/tests/navbar-static-top.html107
-rw-r--r--src/fauxton/assets/less/bootstrap/tests/navbar.html107
-rw-r--r--src/fauxton/assets/less/bootstrap/thumbnails.less52
-rw-r--r--src/fauxton/assets/less/bootstrap/tooltip.less70
-rw-r--r--src/fauxton/assets/less/bootstrap/type.less227
-rw-r--r--src/fauxton/assets/less/bootstrap/utilities.less30
-rw-r--r--src/fauxton/assets/less/bootstrap/variables.less301
-rw-r--r--src/fauxton/assets/less/bootstrap/wells.less29
-rw-r--r--src/fauxton/assets/less/config.less46
-rw-r--r--src/fauxton/assets/less/couchdb.less72
-rw-r--r--src/fauxton/assets/less/database.less163
-rw-r--r--src/fauxton/assets/less/fauxton.less95
-rw-r--r--src/fauxton/assets/less/logs.less24
-rw-r--r--src/fauxton/assets/less/prettyprint.less38
-rwxr-xr-xsrc/fauxton/bin/grunt18
-rw-r--r--src/fauxton/couchapp.js27
-rw-r--r--src/fauxton/favicon.icobin0 -> 1150 bytes
-rw-r--r--src/fauxton/index.html53
-rw-r--r--src/fauxton/package.json42
-rw-r--r--src/fauxton/readme.md66
-rw-r--r--src/fauxton/settings.json.default27
-rw-r--r--src/fauxton/settings.json.sample_external10
-rw-r--r--src/fauxton/tasks/couchserver.js72
-rw-r--r--src/fauxton/tasks/fauxton.js170
-rw-r--r--src/fauxton/tasks/helper.js32
-rw-r--r--src/fauxton/tasks/templates/base.js.underscore21
-rw-r--r--src/fauxton/tasks/templates/resources.js.underscore21
-rw-r--r--src/fauxton/tasks/templates/route.js.underscore21
-rw-r--r--src/fauxton/test/jasmine/index.html44
-rw-r--r--src/fauxton/test/jasmine/spec/example.js73
-rw-r--r--src/fauxton/test/jasmine/vendor/MIT.LICENSE20
-rw-r--r--src/fauxton/test/jasmine/vendor/jasmine-html.js190
-rw-r--r--src/fauxton/test/jasmine/vendor/jasmine.css166
-rw-r--r--src/fauxton/test/jasmine/vendor/jasmine.js2476
-rw-r--r--src/fauxton/test/jasmine/vendor/jasmine_favicon.pngbin0 -> 905 bytes
-rw-r--r--src/fauxton/test/qunit/index.html47
-rw-r--r--src/fauxton/test/qunit/tests/example.js54
-rw-r--r--src/fauxton/test/qunit/vendor/qunit.css228
-rw-r--r--src/fauxton/test/qunit/vendor/qunit.js1589
-rw-r--r--src/fauxton/writing_addons.md163
174 files changed, 67252 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index d3217db2f..e3f57eb11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -81,6 +81,19 @@ src/ejson/ejson.app
src/ejson/priv
src/erlang-oauth/oauth.app
src/erlang-oauth/oauth.app
+src/fauxton/node_modules
+src/fauxton/dist
+src/fauxton/build
+src/fauxton/app/load_addons.js
+!src/fauxton/app/addons/
+src/fauxton/app/addons/*
+!src/fauxton/app/addons/config
+!src/fauxton/app/addons/logs
+!src/fauxton/app/addons/stats
+!src/fauxton/app/addons/contribute
+src/fauxton/settings.json*
+!src/fauxton/settings.json.default
+share/www/fauxton
src/ibrowse/ibrowse.app
src/ibrowse/ibrowse.app
src/mochiweb/mochiweb.app
diff --git a/LICENSE b/LICENSE
index 7d9d0cefa..a9a1ce4a5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -603,3 +603,288 @@ For the share/server/coffee-script.js file
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
+
+For src/fauxton/apps/modules/pouchdb
+
+ Copyright (c) 2012 Dale Harvey et al
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+
+For src/fauxton/assets/js/libs/almond.js
+
+ Copyright (c) 2010-2011, The Dojo Foundation
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the Dojo Foundation nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+For src/fauxton/assets/js/libs/backbone.js
+
+ Copyright (c) 2010-2013 Jeremy Ashkenas, DocumentCloud
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
+For src/fauxton/assets/js/libs/bootstrap.js
+And for src/fauxton/assets/less/bootstrap
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+For src/fauxton/assets/less/bootstrap
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+For src/fauxton/assets/js/libs/d3.js
+
+ Copyright (c) 2012, Michael Bostock
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * The name Michael Bostock may not be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+For src/fauxton/assets/js/libs/jshint.js
+
+ Copyright 2012 Anton Kovalyov (http://jshint.com)
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+For src/fauxton/assets/js/libs/lodash.js
+
+ Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
+ Based on Underscore.js 1.4.3, copyright 2009-2013 Jeremy Ashkenas,
+ DocumentCloud Inc. <http://underscorejs.org/>
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+For src/fauxton/assets/js/libs/nv.d3.js
+
+ Copyright (c) 2011, 2012 [Novus Partners, Inc.][novus]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ [novus]: https://www.novus.com/
+
+For src/fauxton/assets/js/libs/require.js
+
+ Copyright (c) 2010-2011, The Dojo Foundation
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the Dojo Foundation nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+For src/fauxton/assets/js/plugins/backbone.layoutmanager.js
+
+ Copyright (c) 2013 Tim Branyen
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
+ this software and associated documentation files (the "Software"), to deal in
+ the Software without restriction, including without limitation the rights to
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ of the Software, and to permit persons to whom the Software is furnished to do
+ so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+For src/fauxton/assets/js/plugins/prettify.js
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+For src/fauxton/assets/js/libs/codemirror.js
+
+ Copyright (C) 2013 by Marijn Haverbeke <marijnh@gmail.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+ Please note that some subdirectories of the CodeMirror distribution
+ include their own LICENSE files, and are released under different
+ licences.
+
+For src/fauxton/assets/js/plugins/codemirror-javascript.js
+
+ Copyright (C) 2013 by Marijn Haverbeke <marijnh@gmail.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+ Please note that some subdirectories of the CodeMirror distribution
+ include their own LICENSE files, and are released under different
+ licences.
diff --git a/NOTICE b/NOTICE
index 5ff6eee8c..49875531a 100644
--- a/NOTICE
+++ b/NOTICE
@@ -85,3 +85,55 @@ This product also includes the following third-party components:
* Underscore.js 1.4.2 (http://underscorejs.org)
Copyright 2012, Jeremy Ashkenas
+
+ * almond.js (http://github.com/jrburke/almond)
+
+ Copyright 2011, The Dojo Foundation
+
+ * backbone.js (http://backbonejs.org/)
+
+ Copyright 2012, Jeremy Ashkenas, DocumentCloud Inc.
+
+ * Bootstrap (http://twitter.github.com/bootstrap/)
+
+ Copyright 2012, Twitter, Inc.
+
+ * d3.js (http://d3js.org)
+
+ Copyright 2012, Michael Bostock
+
+ * JSHint (http://jshint.com/)
+
+ Copyright 2002, Douglas Crockford, modifications by JSHint Community
+
+ * Lodash (http://lodash.com/)
+
+ Copyright 2012, John-David Dalton <http://allyoucanleet.com/>
+
+ * nvd3.js (http://nvd3.org/)
+
+ Copyright 2012, Novus Partners, Inc.
+
+ * backbone.layoutmanager.js (https://github.com/tbranyen/backbone.layoutmanager)
+
+ Copyright 2012, Tim Branyen (@tbranyen)
+
+ * prettify.js (http://code.google.com/p/google-code-prettify/)
+
+ Copyright 2011, Mike Samuel et al
+
+ * PouchDB (https://github.com/daleharvey/pouchdb)
+
+ Copyright 2012, Dale Harvey et al
+
+ * require.js (https://github.com/jrburke/requirejs)
+
+ Copyright (c) 2010-2011, The Dojo Foundation
+
+ * codemirror.js (https://github.com/marijnh/CodeMirror)
+
+ Copyright (C) 2013 by Marijn Haverbeke <marijnh@gmail.com>
+
+ * codemirror-javascript.js (https://github.com/marijnh/CodeMirror)
+
+ Copyright (C) 2013 by Marijn Haverbeke <marijnh@gmail.com>
diff --git a/license.skip b/license.skip
index 89a6b0546..7c1f783f9 100644
--- a/license.skip
+++ b/license.skip
@@ -100,6 +100,19 @@
^src/erlang-oauth/.*
^src/ejson/.*
^src/etap/.*
+^src/fauxton/app/app.js
+^src/fauxton/app/config.js
+^src/fauxton/app/main.js
+^src/fauxton/app/modules/pouchdb/*
+^src/fauxton/assets/.*
+^src/fauxton/couchapp.js
+^src/fauxton/favicon.ico
+^src/fauxton/package.json
+^src/fauxton/readme.md
+^src/fauxton/writing_addons.md
+^src/fauxton/TODO.md
+^src/fauxton/settings.json.*
+^src/fauxton/test/.*
^src/ibrowse/.*
^src/mochiweb/.*
^src/snappy/.*
diff --git a/src/fauxton/Gruntfile.js b/src/fauxton/Gruntfile.js
new file mode 100644
index 000000000..814279ae5
--- /dev/null
+++ b/src/fauxton/Gruntfile.js
@@ -0,0 +1,351 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+
+// This is the main application configuration file. It is a Grunt
+// configuration file, which you can learn more about here:
+// https://github.com/cowboy/grunt/blob/master/docs/configuring.md
+
+module.exports = function(grunt) {
+ var helper = require('./tasks/helper').init(grunt),
+ path = require('path');
+
+ var couch_config = function () {
+
+ var default_couch_config = {
+ fauxton: {
+ db: 'http://localhost:5984/fauxton',
+ app: './couchapp.js',
+ options: {
+ okay_if_missing: true
+ }
+ }
+ };
+
+ var settings_couch_config = helper.readSettingsFile().couch_config;
+ return settings_couch_config || default_couch_config;
+ }();
+
+ var cleanable = function(){
+ // Whitelist files and directories to be cleaned
+
+ // You'll always want to clean these two directories
+ var theListToClean = ["dist/", "app/load_addons.js"];
+ // Now find the external addons you have and add them for cleaning up
+ helper.processAddons(function(addon){
+ // Only clean addons that are included from a local dir
+ if (addon.path){
+ theListToClean.push("app/addons/" + addon.name);
+ }
+ });
+ return theListToClean;
+ }();
+
+ var assets = function(){
+ // Base assets
+ var theAssets = {
+ less:{
+ paths: ["assets/less"],
+ files: {
+ "dist/debug/css/fauxton.css": "assets/less/fauxton.less"
+ }
+ },
+ img: ["assets/img/**"]
+ };
+ helper.processAddons(function(addon){
+ // Less files from addons
+ var root = addon.path || "app/addons/" + addon.name;
+ var lessPath = root + "/assets/less";
+ if(path.existsSync(lessPath)){
+ // .less files exist for this addon
+ theAssets.less.paths.push(lessPath);
+ theAssets.less.files["dist/debug/css/" + addon.name + ".css"] =
+ lessPath + "/" + addon.name + ".less";
+ }
+ // Images
+ root = addon.path || "app/addons/" + addon.name;
+ var imgPath = root + "/assets/img";
+ if(path.existsSync(imgPath)){
+ theAssets.img.push(imgPath + "/**");
+ }
+ });
+ grunt.log.write(theAssets.img[0]);
+ return theAssets;
+ }();
+
+ var templateSettings = function(){
+ var defaultSettings = {
+ "src": "assets/index.underscore",
+ "dest": "dist/debug/index.html",
+ "variables": {
+ "assets_root": "./",
+ "requirejs": "require.js",
+ "base": null
+ }
+ };
+ var settings = helper.readSettingsFile();
+ return {template: settings.template || defaultSettings};
+ }();
+
+ grunt.initConfig({
+
+ // The clean task ensures all files are removed from the dist/ directory so
+ // that no files linger from previous builds.
+ clean: cleanable,
+
+ // The lint task will run the build configuration and the application
+ // JavaScript through JSHint and report any errors. You can change the
+ // options for this task, by reading this:
+ // https://github.com/cowboy/grunt/blob/master/docs/task_lint.md
+ lint: {
+ files: [
+ "build/config.js", "app/**/*.js"
+ ]
+ },
+
+ less: {
+ compile: {
+ options: {
+ paths: assets.less.paths
+ },
+ files: assets.less.files
+ }
+ },
+
+ // The jshint option for scripturl is set to lax, because the anchor
+ // override inside main.js needs to test for them so as to not accidentally
+ // route.
+ jshint: {
+ all: ['app/**/*.js', 'Gruntfile.js'],
+ options: {
+ scripturl: true,
+ evil: true
+ }
+ },
+
+ // The jst task compiles all application templates into JavaScript
+ // functions with the underscore.js template function from 1.2.4. You can
+ // change the namespace and the template options, by reading this:
+ // https://github.com/gruntjs/grunt-contrib/blob/master/docs/jst.md
+ //
+ // The concat task depends on this file to exist, so if you decide to
+ // remove this, ensure concat is updated accordingly.
+ jst: {
+ "dist/debug/templates.js": [
+ "app/templates/**/*.html",
+ "app/addons/**/templates/**/*.html"
+ ]
+ },
+
+ template: templateSettings,
+
+ // The concatenate task is used here to merge the almond require/define
+ // shim and the templates into the application code. It's named
+ // dist/debug/require.js, because we want to only load one script file in
+ // index.html.
+ concat: {
+ requirejs: {
+ src: ["assets/js/libs/almond.js", "dist/debug/templates.js", "dist/debug/require.js"],
+ dest: "dist/debug/js/require.js"
+ },
+
+ index_css: {
+ src: ["dist/debug/css/*.css", 'assets/css/*.css'],
+ dest: 'dist/debug/css/index.css'
+ }
+
+ },
+
+ cssmin: {
+ compress: {
+ files: {
+ "dist/release/css/index.css": [
+ "dist/debug/css/index.css", 'assets/css/*.css',
+ "app/addons/**/assets/css/*.css"
+ ]
+ },
+ options: {
+ report: 'min'
+ }
+ }
+ },
+
+ uglify: {
+ release: {
+ files: {
+ "dist/release/js/require.js": [
+ "dist/debug/js/require.js"
+ ]
+ }
+ }
+ },
+
+ // Runs a proxy server for easier development, no need to keep deploying to couchdb
+ couchserver: {
+ dist: './dist/debug/',
+ port: 8000,
+ proxy: {
+ host: 'localhost',
+ port: 5984,
+ https: false
+ }
+ },
+
+ watch: {
+ files: './app/**/*',
+ tasks: ['debug', 'template']
+ },
+
+ requirejs: {
+ compile: {
+ options: {
+ baseUrl: 'app',
+ // Include the main configuration file.
+ mainConfigFile: "app/config.js",
+
+ // Output file.
+ out: "dist/debug/require.js",
+
+ // Root application module.
+ name: "config",
+
+ // Do not wrap everything in an IIFE.
+ wrap: false,
+ optimize: "none"
+ }
+ }
+ },
+
+ // The headless QUnit testing environment is provided for "free" by Grunt.
+ // Simply point the configuration to your test directory.
+ qunit: {
+ all: ["test/qunit/*.html"]
+ },
+
+ // The headless Jasmine testing is provided by grunt-jasmine-task. Simply
+ // point the configuration to your test directory.
+ jasmine: {
+ all: ["test/jasmine/*.html"]
+ },
+
+ // Copy build artifacts and library code into the distribution
+ // see - http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
+ copy: {
+ couchdb: {
+ files: [
+ // this gets built in the template task
+ {src: "dist/release/index.html", dest: "../../share/www/fauxton/index.html"},
+ {src: ["**"], dest: "../../share/www/fauxton/js/", cwd:'dist/release/js/', expand: true},
+ {src: ["**"], dest: "../../share/www/fauxton/img/", cwd:'dist/release/img/', expand: true},
+ {src: ["**"], dest: "../../share/www/fauxton/css/", cwd:"dist/release/css/", expand: true}
+ ]
+ },
+ couchdebug: {
+ files: [
+ // this gets built in the template task
+ {src: "dist/debug/index.html", dest: "../../share/www/fauxton/index.html"},
+ {src: ["**"], dest: "../../share/www/fauxton/js/", cwd:'dist/debug/js/', expand: true},
+ {src: ["**"], dest: "../../share/www/fauxton/img/", cwd:'dist/debug/img/', expand: true},
+ {src: ["**"], dest: "../../share/www/fauxton/css/", cwd:"dist/debug/css/", expand: true}
+ ]
+ },
+ dist:{
+ files:[
+ {src: "dist/debug/index.html", dest: "dist/release/index.html"},
+ {src: assets.img, dest: "dist/release/img/", flatten: true, expand: true}
+ ]
+ },
+ debug:{
+ files:[
+ {src: assets.img, dest: "dist/debug/img/", flatten: true, expand: true}
+ ]
+ }
+ },
+
+ get_deps: {
+ "default": {
+ src: "settings.json"
+ }
+ },
+
+ gen_load_addons: {
+ "default": {
+ src: "settings.json"
+ }
+ },
+
+ mkcouchdb: couch_config,
+ rmcouchdb: couch_config,
+ couchapp: couch_config
+
+ });
+
+ /*
+ * Load Grunt plugins
+ */
+ // Load fauxton specific tasks
+ grunt.loadTasks('tasks');
+ // Load the couchapp task
+ grunt.loadNpmTasks('grunt-couchapp');
+ // Load the copy task
+ grunt.loadNpmTasks('grunt-contrib');
+ // Load the exec task
+ grunt.loadNpmTasks('grunt-exec');
+ // Load Require.js task
+ grunt.loadNpmTasks('grunt-requirejs');
+ // Load UglifyJS task
+ grunt.loadNpmTasks('grunt-contrib-uglify');
+ // Load CSSMin task
+ grunt.loadNpmTasks('grunt-contrib-cssmin');
+
+ /*
+ * Default task
+ */
+ // defult task - install minified app to local CouchDB
+ grunt.registerTask('default', 'couchdb');
+
+ /*
+ * Transformation tasks
+ */
+ // clean out previous build artefacts, lint and unit test
+ grunt.registerTask('test', ['clean', 'jshint']); //qunit
+ // Fetch dependencies (from git or local dir), lint them and make load_addons
+ grunt.registerTask('dependencies', ['get_deps', 'jshint', 'gen_load_addons:default']);
+ // build templates, js and css
+ grunt.registerTask('build', ['jst', 'requirejs', 'concat:requirejs', 'less', 'concat:index_css', 'template']);
+ // minify code and css, ready for release.
+ grunt.registerTask('minify', ['uglify', 'cssmin:compress']);
+
+ /*
+ * Build the app in either dev, debug, or release mode
+ */
+ // dev server
+ grunt.registerTask('dev', ['debug', 'couchserver']);
+ // build a debug release
+ grunt.registerTask('debug', ['test', 'dependencies', 'build', 'copy:debug']);
+ // build a release
+ grunt.registerTask('release', ['test' ,'dependencies', 'build', 'minify', 'copy:dist']);
+
+ /*
+ * Install into CouchDB in either debug, release, or couchapp mode
+ */
+ // make a development install that is server by mochiweb under _utils
+ grunt.registerTask('couchdebug', ['debug', 'copy:couchdebug']);
+ // make a minimized install that is server by mochiweb under _utils
+ grunt.registerTask('couchdb', ['release', 'copy:couchdb']);
+ // make an install that can be deployed as a couchapp
+ grunt.registerTask('couchapp_setup', ['build', 'minify', 'copy:dist']);
+ // install fauxton as couchapp
+ grunt.registerTask('couchapp_install', ['rmcouchdb:fauxton', 'mkcouchdb:fauxton', 'couchapp:fauxton']);
+ // setup and install fauxton as couchapp
+ grunt.registerTask('couchapp_deploy', ['couchapp_setup', 'couchapp_install']);
+};
diff --git a/src/fauxton/TODO.md b/src/fauxton/TODO.md
new file mode 100644
index 000000000..b929e0546
--- /dev/null
+++ b/src/fauxton/TODO.md
@@ -0,0 +1,26 @@
+# Fauxton todo
+In no particular order
+
+- [ ] docco docs
+- [ ] user management
+- [ ] view options
+- [ ] view editor
+- [ ] visual view builder
+- [ ] new db as modal
+- [ ] new view button
+- [ ] show design docs only
+- [ ] fix delete doc button UI bug
+- [ ] delete multiple docs via _bulk_docs
+- [x] show change events in database view
+- [ ] pouchdb addon
+- [ ] bespoke bootstrap style
+- [ ] responsive interface
+- [ ] sticky subnav for some UI components on _all_docs
+- [ ] "show me" button in API bar doesn't
+- [ ] edit index button doesn't
+- [ ] replicate UI
+- [x] delete database
+- [x] format dates better (e.g. in logs plugin)
+- [ ] format log entry better
+- [ ] filter logs by method
+- [ ] restore unfiltered data in logs UI
diff --git a/src/fauxton/app/addons/config/base.js b/src/fauxton/app/addons/config/base.js
new file mode 100644
index 000000000..f589b16e2
--- /dev/null
+++ b/src/fauxton/app/addons/config/base.js
@@ -0,0 +1,28 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "addons/config/routes"
+],
+
+function(app, FauxtonAPI, Config) {
+ Config.initialize = function() {
+ FauxtonAPI.addHeaderLink({title: "Config", href: "#_config"});
+ };
+
+ return Config;
+});
diff --git a/src/fauxton/app/addons/config/resources.js b/src/fauxton/app/addons/config/resources.js
new file mode 100644
index 000000000..db26bb7da
--- /dev/null
+++ b/src/fauxton/app/addons/config/resources.js
@@ -0,0 +1,175 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+ "api"
+],
+
+function (app, FauxtonAPI) {
+
+ var Config = FauxtonAPI.addon();
+
+ Config.Model = Backbone.Model.extend({});
+ Config.OptionModel = Backbone.Model.extend({
+
+ url: function () {
+ return app.host + '/_config/' + this.get("section") + '/' + this.get("name");
+ },
+
+ isNew: function () { return false; },
+
+ sync: function (method, model, options) {
+
+ var params = {
+ url: model.url(),
+ contentType: 'application/json',
+ dataType: 'json',
+ data: JSON.stringify(model.get('value'))
+ };
+
+ if (method === 'delete') {
+ params.type = 'DELETE';
+ } else {
+ params.type = 'PUT';
+ }
+
+ return $.ajax(params);
+ }
+ });
+
+ Config.Collection = Backbone.Collection.extend({
+ model: Config.Model,
+
+ url: function () {
+ return app.host + '/_config';
+ },
+
+ parse: function (resp) {
+ return _.map(resp, function (section, section_name) {
+ return {
+ section: section_name,
+ options: _.map(section, function (option, option_name) {
+ return {
+ name: option_name,
+ value: option
+ };
+ })
+ };
+ });
+ }
+ });
+
+ Config.ViewItem = FauxtonAPI.View.extend({
+ tagName: "tr",
+ className: "config-item",
+ template: "addons/config/templates/item",
+
+ events: {
+ "click .edit-button": "editValue",
+ "click #delete-value": "deleteValue",
+ "click #cancel-value": "cancelEdit",
+ "click #save-value": "saveValue"
+ },
+
+ deleteValue: function (event) {
+ var result = confirm("Are you sure you want to delete this configuration value?");
+
+ if (!result) { return; }
+
+ this.model.destroy();
+ this.remove();
+ },
+
+ editValue: function (event) {
+ this.$("#show-value").hide();
+ this.$("#edit-value-form").show();
+ },
+
+ saveValue: function (event) {
+ this.model.save({value: this.$(".value-input").val()});
+ this.render();
+ },
+
+ cancelEdit: function (event) {
+ this.$("#edit-value-form").hide();
+ this.$("#show-value").show();
+ },
+
+ serialize: function () {
+ return {option: this.model.toJSON()};
+ }
+
+ });
+
+ Config.View = FauxtonAPI.View.extend({
+ template: "addons/config/templates/dashboard",
+
+ events: {
+ "click #add-section": "addSection",
+ "submit #add-section-form": "submitForm"
+ },
+
+ submitForm: function (event) {
+ event.preventDefault();
+ var option = new Config.OptionModel({
+ section: this.$('input[name="section"]').val(),
+ name: this.$('input[name="name"]').val(),
+ value: this.$('input[name="value"]').val()
+ });
+
+ option.save();
+
+ var section = this.collection.find(function (section) {
+ return section.get("section") === option.get("section");
+ });
+
+ if (section) {
+ section.get("options").push(option.attributes);
+ } else {
+ this.collection.add({
+ section: option.get("section"),
+ options: [option.attributes]
+ });
+ }
+
+ this.$("#add-section-modal").modal('hide');
+ this.render();
+ },
+
+ addSection: function (event) {
+ event.preventDefault();
+ this.$("#add-section-modal").modal({show:true});
+ },
+
+ beforeRender: function() {
+ this.collection.each(function(config) {
+ _.each(config.get("options"), function (option, index) {
+ this.insertView("table.config tbody", new Config.ViewItem({
+ model: new Config.OptionModel({
+ section: config.get("section"),
+ name: option.name,
+ value: option.value,
+ index: index
+ })
+ }));
+ }, this);
+ }, this);
+ },
+
+ establish: function() {
+ return [this.collection.fetch()];
+ }
+ });
+
+ return Config;
+});
diff --git a/src/fauxton/app/addons/config/routes.js b/src/fauxton/app/addons/config/routes.js
new file mode 100644
index 000000000..7ed64980b
--- /dev/null
+++ b/src/fauxton/app/addons/config/routes.js
@@ -0,0 +1,44 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "addons/config/resources"
+],
+
+function(app, FauxtonAPI, Config) {
+ var configRoute = function () {
+ var configs = new Config.Collection();
+
+ return {
+ layout: "one_pane",
+ crumbs: [
+ {"name": "Config","link": "_config"}
+ ],
+ views: {
+ "#dashboard-content": new Config.View({collection: configs})
+ },
+ apiUrl: configs.url()
+ };
+ };
+
+ Config.Routes = {
+ "_config": configRoute
+ };
+
+ return Config;
+
+});
diff --git a/src/fauxton/app/addons/config/templates/dashboard.html b/src/fauxton/app/addons/config/templates/dashboard.html
new file mode 100644
index 000000000..7cff90a15
--- /dev/null
+++ b/src/fauxton/app/addons/config/templates/dashboard.html
@@ -0,0 +1,52 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div class="row">
+ <div class="span2 offset10">
+ <button id="add-section" href="#" class="btn btn-primary button-margin">
+ <i class="icon-plus icon-white"> </i>
+ Add Section
+ </button>
+ </div>
+</div>
+<table class="config table table-striped table-bordered">
+ <thead>
+ <th> Section </th>
+ <th> Option </th>
+ <th> Value </th>
+ <th></th>
+ </thead>
+ <tbody>
+ </tbody>
+</table>
+<div id="add-section-modal" class="modal hide fade">
+ <div class="modal-header">
+ <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+ <h3>Create Config Option</h3>
+ </div>
+ <div class="modal-body">
+ <form id="add-section-form" class="form well">
+ <label>Section</label>
+ <input type="text" name="section" placeholder="Section">
+ <span class="help-block">Enter an existing section name to add to it.</span>
+ <input type="text" name="name" placeholder="Name">
+ <br/>
+ <input type="text" name="value" placeholder="Value">
+ <div class="modal-footer">
+ <button type="button" class="btn" data-dismiss="modal">Cancel</button>
+ <button type="submit" class="btn btn-primary"> Save </button>
+ </div>
+ </form>
+ </div>
+</div>
diff --git a/src/fauxton/app/addons/config/templates/item.html b/src/fauxton/app/addons/config/templates/item.html
new file mode 100644
index 000000000..3e6e4eeb8
--- /dev/null
+++ b/src/fauxton/app/addons/config/templates/item.html
@@ -0,0 +1,31 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<% if (option.index === 0) {%>
+<th> <%= option.section %> </th>
+<% } else { %>
+<td></td>
+<% } %>
+<td> <%= option.name %> </td>
+<td>
+ <div id="show-value">
+ <%= option.value %> <button class="edit-button"> Edit </button>
+ </div>
+ <div id="edit-value-form" style="display:none">
+ <input class="value-input" type="text" value="<%= option.value %>" />
+ <button id="save-value" class="btn btn-success btn-small"> Save </button>
+ <button id="cancel-value" class="btn btn-danger btn-small"> Cancel </button>
+ </div>
+</td>
+<td id="delete-value"> <i class="icon-trash"> </i> </td>
diff --git a/src/fauxton/app/addons/contribute/base.js b/src/fauxton/app/addons/contribute/base.js
new file mode 100644
index 000000000..8f622feda
--- /dev/null
+++ b/src/fauxton/app/addons/contribute/base.js
@@ -0,0 +1,33 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ // Libraries.
+ "jquery",
+ "lodash"
+],
+function($, _){
+ $.contribute = function(message, file){
+ /*
+ var JST = window.JST = window.JST || {};
+ var template = JST['app/addons/contribute/templates/modal.html'];
+ console.log(template);
+ var compiled = template({message: message, file: file});
+ */
+ console.log('contribute!contribute!monorail!contribute!');
+ /*
+ console.log(compiled);
+ var elem = $(compiled);
+ elem.modal('show');
+ */
+ };
+});
diff --git a/src/fauxton/app/addons/logs/base.js b/src/fauxton/app/addons/logs/base.js
new file mode 100644
index 000000000..c17e159cf
--- /dev/null
+++ b/src/fauxton/app/addons/logs/base.js
@@ -0,0 +1,28 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "addons/logs/routes"
+],
+
+function(app, FauxtonAPI, Log) {
+ Log.initialize = function() {
+ FauxtonAPI.addHeaderLink({title: "Log", href: "#_log"});
+ };
+
+ return Log;
+});
diff --git a/src/fauxton/app/addons/logs/resources.js b/src/fauxton/app/addons/logs/resources.js
new file mode 100644
index 000000000..382fafe42
--- /dev/null
+++ b/src/fauxton/app/addons/logs/resources.js
@@ -0,0 +1,225 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+ "api"
+],
+
+function (app, FauxtonAPI) {
+
+ var Log = FauxtonAPI.addon();
+
+ Log.Model = Backbone.Model.extend({
+
+ date: function () {
+ var date = new Date(this.get('date'));
+
+ var formatted_time = date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
+ var formatted_date = date.toDateString().slice(4, 10);
+
+ return formatted_date + ' ' + formatted_time;
+ },
+
+ logLevel: function () {
+ return this.get('log_level').replace(/ /g,'');
+ },
+
+ pid: function () {
+ return _.escape(this.get('pid'));
+ },
+
+ args: function () {
+ return _.escape(this.get('args'));
+ }
+
+ });
+
+ Log.Collection = Backbone.Collection.extend({
+ model: Log.Model,
+
+ initialize: function (options) {
+ this.params = {bytes: 5000};
+ },
+
+ url: function () {
+ query = "?" + $.param(this.params);
+ return app.host + '/_log' + query;
+ },
+
+ // override fetch because backbone expects json and couchdb sends text/html for logs,
+ // I think its more elegant to set the dataType here than where ever fetch is called
+ fetch: function (options) {
+ options = options ? options : {};
+
+ return Backbone.Collection.prototype.fetch.call(this, _.extend(options, {dataType: "html"}));
+ },
+
+ parse: function (resp) {
+ var lines = resp.split(/\n/);
+
+ return _.foldr(lines, function (acc, logLine) {
+ var match = logLine.match(/^\[(.*?)\]\s\[(.*?)\]\s\[(.*?)\]\s(.*)/);
+
+ if (!match) { return acc;}
+
+ acc.push({
+ date: match[1],
+ log_level: match[2],
+ pid: match[3],
+ args: match[4]
+ });
+
+ return acc;
+ }, []);
+ }
+ });
+
+ Log.events = {};
+ _.extend(Log.events, Backbone.Events);
+
+ Log.Views.View = FauxtonAPI.View.extend({
+ template: "addons/logs/templates/dashboard",
+
+ initialize: function (options) {
+ this.refreshTime = options.refreshTime || 5000;
+
+ Log.events.on("log:filter", this.filterLogs, this);
+ Log.events.on("log:remove", this.removeFilterLogs, this);
+
+ this.filters = [];
+ this.filteredCollection = new Log.Collection(this.collection.toJSON());
+ this.collection.on("reset", function () {
+ this.createFilteredCollection();
+ }, this);
+ },
+
+ serialize: function () {
+ return { logs: this.filteredCollection};
+ },
+
+ afterRender: function () {
+ this.startRefreshInterval();
+ },
+
+ cleanup: function () {
+ this.stopRefreshInterval();
+ },
+
+ filterLogs: function (filter) {
+ this.filters.push(filter);
+ this.createFilteredCollection();
+ },
+
+ resetFilterCollectionAndRender: function (logs) {
+ this.filteredCollection.reset(logs);
+ this.render();
+ },
+
+ createFilteredCollection: function () {
+ var self = this;
+
+ var filtered = _.reduce(this.filters, function (logs, filter) {
+
+ return _.filter(logs, function (log) {
+ var match = false;
+
+ _.each(log, function (value) {
+ if (value.toString().match(new RegExp(filter))) {
+ match = true;
+ }
+ });
+ return match;
+ });
+
+
+ }, this.collection.toJSON(), this);
+
+ this.resetFilterCollectionAndRender(filtered);
+ },
+
+ removeFilterLogs: function (filter) {
+ this.filters.splice(this.filters.indexOf(filter), 1);
+ this.createFilteredCollection();
+ },
+
+ startRefreshInterval: function () {
+ var self = this;
+
+ // Interval already set
+ if (self.intervalId) { return ; }
+
+ self.intervalId = setInterval(function () {
+ self.collection.fetch();
+ }, self.refreshTime);
+
+ },
+
+ stopRefreshInterval: function () {
+ clearInterval(this.intervalId);
+ }
+ });
+
+ Log.Views.FilterView = FauxtonAPI.View.extend({
+ template: "addons/logs/templates/sidebar",
+
+ events: {
+ "submit #log-filter-form": "filterLogs"
+ },
+
+ filterLogs: function (event) {
+ event.preventDefault();
+ var $filter = this.$('input[name="filter"]'),
+ filter = $filter.val();
+
+ Log.events.trigger("log:filter", filter);
+
+ this.insertView("#filter-list", new Log.Views.FilterItemView({
+ filter: filter
+ })).render();
+
+ $filter.val('');
+ }
+
+ });
+
+ Log.Views.FilterItemView = FauxtonAPI.View.extend({
+ template: "addons/logs/templates/filterItem",
+ tagName: "li",
+
+ initialize: function (options) {
+ this.filter = options.filter;
+ },
+
+ events: {
+ "click .remove-filter": "removeFilter"
+ },
+
+ serialize: function () {
+ return {
+ filter: this.filter
+ };
+ },
+
+ removeFilter: function (event) {
+ event.preventDefault();
+
+ Log.events.trigger("log:remove", this.filter);
+ this.remove();
+ }
+
+ });
+
+
+ return Log;
+
+});
diff --git a/src/fauxton/app/addons/logs/routes.js b/src/fauxton/app/addons/logs/routes.js
new file mode 100644
index 000000000..4e04d0843
--- /dev/null
+++ b/src/fauxton/app/addons/logs/routes.js
@@ -0,0 +1,47 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "addons/logs/resources"
+],
+
+function(app, FauxtonAPI, Log) {
+
+ Log.Routes = {
+ "_log": function() {
+ var logs = new Log.Collection();
+
+ return {
+ layout: "with_sidebar",
+ crumbs: [
+ {"name": "Logs", "link": "_log"}
+ ],
+ views: {
+ "#dashboard-content": new Log.Views.View({collection: logs}),
+ "#sidebar-content": new Log.Views.FilterView({})
+ },
+ apiUrl: logs.url(),
+ establish: function() {
+ return [logs.fetch()];
+ }
+ };
+ }
+ };
+
+ return Log;
+
+});
diff --git a/src/fauxton/app/addons/logs/templates/dashboard.html b/src/fauxton/app/addons/logs/templates/dashboard.html
new file mode 100644
index 000000000..14969c8c1
--- /dev/null
+++ b/src/fauxton/app/addons/logs/templates/dashboard.html
@@ -0,0 +1,46 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+ <h2> Couchdb Logs </h2>
+ <table class="table table-bordered" >
+ <thead>
+ <tr>
+ <th class="Date">Date</th>
+ <th class="Log Level">Log Value</th>
+ <th class="Pid">Pid</th>
+ <th class="Args">Url</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <% logs.each(function (log) { %>
+ <tr class="<%= log.logLevel() %>">
+ <td>
+ <!-- TODO: better format the date -->
+ <%= log.date() %>
+ </td>
+ <td>
+ <%= log.logLevel() %>
+ </td>
+ <td>
+ <%= log.pid() %>
+ </td>
+ <td>
+ <!-- TODO: split the line, maybe put method in it's own column -->
+ <%= log.args() %>
+ </td>
+ </tr>
+ <% }); %>
+ </tbody>
+</table>
diff --git a/src/fauxton/app/addons/logs/templates/filterItem.html b/src/fauxton/app/addons/logs/templates/filterItem.html
new file mode 100644
index 000000000..c4e885af2
--- /dev/null
+++ b/src/fauxton/app/addons/logs/templates/filterItem.html
@@ -0,0 +1,16 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<span class="label label-info"> <%= filter %> </span>
+<a class="label label-info remove-filter" href="#">&times;</a>
diff --git a/src/fauxton/app/addons/logs/templates/sidebar.html b/src/fauxton/app/addons/logs/templates/sidebar.html
new file mode 100644
index 000000000..91822e013
--- /dev/null
+++ b/src/fauxton/app/addons/logs/templates/sidebar.html
@@ -0,0 +1,27 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div id="log-sidebar">
+ <form class="form-inline" id="log-filter-form">
+ <fieldset>
+ <legend>Log Filter</legend>
+ <input type="text" name="filter" placeholder="Type a filter to sort the logs by">
+ <!-- TODO: filter by method -->
+ <!-- TODO: correct removed filter behaviour -->
+ <button type="submit" class="btn">Filter</button>
+ <span class="help-block"> <h6> Eg. debug or <1.4.1> or any regex </h6> </span>
+ </fieldset>
+ </form>
+ <ul id="filter-list"></ul>
+</div>
diff --git a/src/fauxton/app/addons/stats/assets/less/stats.less b/src/fauxton/app/addons/stats/assets/less/stats.less
new file mode 100644
index 000000000..8c81f86f2
--- /dev/null
+++ b/src/fauxton/app/addons/stats/assets/less/stats.less
@@ -0,0 +1,20 @@
+/* Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+.datatypes {
+ border: #d3d3d3 1px solid;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+ padding: 15px;
+}
diff --git a/src/fauxton/app/addons/stats/base.js b/src/fauxton/app/addons/stats/base.js
new file mode 100644
index 000000000..33316c404
--- /dev/null
+++ b/src/fauxton/app/addons/stats/base.js
@@ -0,0 +1,28 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+ "api",
+ "addons/stats/routes"
+],
+
+function(app, FauxtonAPI, AddonRoutes) {
+ var Stats = new FauxtonAPI.addon();
+
+ Stats.initialize = function() {
+ FauxtonAPI.addHeaderLink({title: "Statistics", href: "#stats"});
+ };
+
+ Stats.Routes = AddonRoutes;
+ return Stats;
+});
diff --git a/src/fauxton/app/addons/stats/resources.js b/src/fauxton/app/addons/stats/resources.js
new file mode 100644
index 000000000..238a03281
--- /dev/null
+++ b/src/fauxton/app/addons/stats/resources.js
@@ -0,0 +1,37 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+ "api",
+ "backbone",
+ "lodash",
+ "modules/fauxton/base"
+],
+
+function (app, FauxtonAPI, backbone, _, Fauxton) {
+ var Stats = {};
+
+ Stats.Collection = Backbone.Collection.extend({
+ model: Backbone.Model,
+ url: "/_stats",
+ parse: function(resp) {
+ return _.flatten(_.map(resp, function(doc, key) {
+ return _.map(doc, function(v, k){
+ return _.extend({id: k, type: key}, v);
+ });
+ }), true);
+ }
+ });
+
+ return Stats;
+});
diff --git a/src/fauxton/app/addons/stats/routes.js b/src/fauxton/app/addons/stats/routes.js
new file mode 100644
index 000000000..84947fad7
--- /dev/null
+++ b/src/fauxton/app/addons/stats/routes.js
@@ -0,0 +1,59 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+ "api",
+ "addons/stats/resources",
+ "addons/stats/views"
+],
+
+function(app, FauxtonAPI, Stats, Views) {
+ var data = {
+ stats: new Stats.Collection()
+ };
+
+ var deferred = FauxtonAPI.Deferred();
+
+ var routeCallback = function() {
+ return {
+ layout: "with_sidebar",
+
+ data: data,
+
+ crumbs: [],
+
+ views: {
+ "#sidebar-content": new Views.StatSelect({
+ collection: data.stats
+ }),
+
+ "#dashboard-content": new Views.Statistics({
+ collection: data.stats
+ })
+ },
+
+ establish: function() {
+ return [data.stats.fetch()];
+ },
+
+ apiUrl: "_stats"
+ };
+ };
+
+ Routes = {
+ "stats": routeCallback,
+ "_stats": routeCallback
+ };
+
+ return Routes;
+});
diff --git a/src/fauxton/app/addons/stats/templates/by_method.html b/src/fauxton/app/addons/stats/templates/by_method.html
new file mode 100644
index 000000000..099d7371a
--- /dev/null
+++ b/src/fauxton/app/addons/stats/templates/by_method.html
@@ -0,0 +1,16 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<h2>By Method <small>GET, POST, PUT, DELETE</small></h2>
+<div id="httpd_request_methods"></div>
diff --git a/src/fauxton/app/addons/stats/templates/pie_table.html b/src/fauxton/app/addons/stats/templates/pie_table.html
new file mode 100644
index 000000000..fba4717cf
--- /dev/null
+++ b/src/fauxton/app/addons/stats/templates/pie_table.html
@@ -0,0 +1,56 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div class="row-fluid">
+ <div class="span8">
+ <h2> <%= datatype %> </h2>
+ </div>
+</div>
+
+<div class="row-fluid">
+ <div class="span8" style="margin-top:70px">
+ <table class="table table-condensed table-striped">
+ <thead>
+ <tr>
+ <th> Description </th>
+ <th> current </th>
+ <th> sum </th>
+ <th> mean </th>
+ <th> stddev </th>
+ <th> min </th>
+ <th> max </th>
+ </tr>
+ </thead>
+ <% _.each (statistics, function (stat_line) {
+ if (stat_line.get("sum")){
+ %>
+ <tr>
+ <td><%= stat_line.get("description") %></td>
+ <td><%= stat_line.get("current") %></td>
+ <td><%= stat_line.get("sum") %></td>
+ <td><%= stat_line.get("mean") %></td>
+ <td><%= stat_line.get("stddev") %></td>
+ <td><%= stat_line.get("min") %></td>
+ <td><%= stat_line.get("max") %></td>
+ </tr>
+ <% }}) %>
+ </table>
+ </div>
+
+ <div class="span4" style="height:430px;min-width: 430px">
+ <center>
+ <svg id="<%= datatype %>_graph"></svg>
+ </center>
+ </div>
+</div>
diff --git a/src/fauxton/app/addons/stats/templates/stats.html b/src/fauxton/app/addons/stats/templates/stats.html
new file mode 100644
index 000000000..ae7ce1448
--- /dev/null
+++ b/src/fauxton/app/addons/stats/templates/stats.html
@@ -0,0 +1,16 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div class="datatypes">
+</div>
diff --git a/src/fauxton/app/addons/stats/templates/statselect.html b/src/fauxton/app/addons/stats/templates/statselect.html
new file mode 100644
index 000000000..ef1133c48
--- /dev/null
+++ b/src/fauxton/app/addons/stats/templates/statselect.html
@@ -0,0 +1,22 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<% _.each(datatypes, function (datatype) { %>
+<li>
+<a href="#stats" class="datatype-select" data-type-select="<%= datatype %>">
+ <%= datatype %>
+ <i class="icon-chevron-right" style="float:right"></i>
+</a>
+</li>
+<% }); %>
diff --git a/src/fauxton/app/addons/stats/views.js b/src/fauxton/app/addons/stats/views.js
new file mode 100644
index 000000000..21454f93d
--- /dev/null
+++ b/src/fauxton/app/addons/stats/views.js
@@ -0,0 +1,172 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+ 'addons/stats/resources',
+ "d3",
+ "nv.d3"
+
+],
+
+function(app, FauxtonAPI,Stats) {
+ console.log(arguments);
+ Views = {};
+
+ datatypeEventer = {};
+ _.extend(datatypeEventer, Backbone.Events);
+
+ Views.Legend = FauxtonAPI.View.extend({
+ tagName: 'ul',
+ template: "addons/stats/templates/legend",
+
+ serialize: function () {
+ return {
+ legend_items: this.collection.toJSON()
+ };
+ }
+ });
+
+ Views.Pie = FauxtonAPI.View.extend({
+ className: "datatype-section",
+ template: 'addons/stats/templates/pie_table',
+
+ initialize: function(args){
+ this.datatype = args.datatype;
+ },
+
+ serialize: function() {
+ return {
+ statistics: this.collection.where({type: this.datatype}),
+ datatype: this.datatype
+ };
+ },
+
+ afterRender: function(){
+ var collection = this.collection,
+ chartelem = "#" + this.datatype + '_graph',
+ series = _.map(this.collection.where({type: this.datatype}),
+ function(d, counter){
+ // TODO: x should be a counter
+ var point = {
+ y: d.get("sum") || 0,
+ key: d.id
+ };
+ return point;
+ }
+ );
+
+ series = _.filter(series, function(d){return d.y > 0;});
+ series = _.sortBy(series, function(d){return -d.y;});
+ console.log('series');
+ console.log(series);
+
+ nv.addGraph(function() {
+ var width = 550,
+ height = 400;
+
+ var chart = nv.models.pieChart()
+ .x(function(d) { return d.key; })
+ .y(function(d) { return d.y; })
+ .showLabels(true)
+ .showLegend(false)
+ .values(function(d) { return d; })
+ .color(d3.scale.category10().range())
+ .width(width)
+ .height(height);
+
+ d3.select(chartelem)
+ .datum([series])
+ .transition().duration(300)
+ .attr('width', width)
+ .attr('height', height)
+ .call(chart);
+
+ return chart;
+ });
+
+ this.$el.addClass(this.datatype + '_section');
+ }
+ });
+
+ Views.StatSelect = FauxtonAPI.View.extend({
+ className: 'nav nav-tabs nav-stacked',
+ tagName: 'ul',
+
+ template: "addons/stats/templates/statselect",
+
+ initialize: function (options) {
+ this.rows = [];
+ },
+
+ events: {
+ 'click .datatype-select': "datatype_selected"
+ },
+
+ serialize: function () {
+ return {
+ datatypes: _.uniq(this.collection.pluck("type"))
+ };
+ },
+
+ afterRender: function () {
+ this.$('.datatype-select').first().addClass('active');
+ },
+
+ datatype_selected: function (event) {
+ var $target = $(event.currentTarget);
+
+ event.preventDefault();
+ event.stopPropagation();
+ this.$('.datatype-select').removeClass('active');
+ $target.addClass('active');
+ datatypeEventer.trigger('datatype-select', $target.attr('data-type-select'));
+ }
+ });
+
+ Views.Statistics = FauxtonAPI.View.extend({
+ template: "addons/stats/templates/stats",
+
+ initialize: function (options) {
+ this.rows = [];
+ datatypeEventer.on('datatype-select', this.display_datatype, this);
+ },
+
+ serialize: function () {
+ return {
+ datatypes: _.uniq(this.collection.pluck("type"))
+ };
+ },
+
+ beforeRender: function () {
+ _.each(_.uniq(this.collection.pluck("type")), function(datatype) {
+ this.rows[datatype] = this.insertView(".datatypes", new Views.Pie({
+ collection: this.collection,
+ datatype: datatype
+ }));
+ }, this);
+ },
+
+ afterRender: function () {
+ this.$('.datatype-section').hide().first().toggle();
+ },
+
+ display_datatype: function (datatype) {
+ this.$('.datatype-section').hide();
+ this.$('.' + datatype + '_section').show();
+ }
+ });
+
+ return Views;
+});
diff --git a/src/fauxton/app/api.js b/src/fauxton/app/api.js
new file mode 100644
index 000000000..52e861183
--- /dev/null
+++ b/src/fauxton/app/api.js
@@ -0,0 +1,101 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ // Modules
+ "modules/fauxton/base"
+],
+
+function(app, Fauxton) {
+ var FauxtonAPI = app.module();
+
+ FauxtonAPI.moduleExtensions = {
+ Routes: {
+ }
+ };
+
+ FauxtonAPI.addonExtensions = {
+ initialize: function() {}
+ };
+
+ // List of JSHINT errors to ignore
+ // Gets around problem of anonymous functions not being a valid statement
+ FauxtonAPI.excludedViewErrors = [
+ "Missing name in function declaration."
+ ];
+
+ FauxtonAPI.isIgnorableError = function(msg) {
+ return _.contains(FauxtonAPI.excludedViewErrors, msg);
+ };
+
+ FauxtonAPI.View = Backbone.View.extend({
+ // This should return an array of promises, an empty array, or null
+ establish: function() {
+ return null;
+ }
+ });
+
+ FauxtonAPI.navigate = function(url) {
+ Backbone.history.navigate(url, true);
+ };
+
+ FauxtonAPI.addHeaderLink = function(link) {
+ app.masterLayout.navBar.addLink(link);
+ };
+
+ FauxtonAPI.Deferred = function() {
+ return $.Deferred();
+ };
+
+ FauxtonAPI.addRoute = function(route) {
+ app.router.route(route.route, route.name, route.callback);
+ };
+
+ FauxtonAPI.module = function(extra) {
+ return app.module(_.extend(FauxtonAPI.moduleExtensions, extra));
+ };
+
+ FauxtonAPI.addon = function(extra) {
+ return FauxtonAPI.module(FauxtonAPI.addonExtensions, extra);
+ };
+
+ FauxtonAPI.addNotification = function(options) {
+ options = _.extend({
+ msg: "Notification Event Triggered!",
+ type: "info",
+ selector: "#global-notifications"
+ }, options);
+ var view = new Fauxton.Notification(options);
+
+ return view.renderNotification();
+ };
+
+ FauxtonAPI.UUID = Backbone.Model.extend({
+ initialize: function(options) {
+ options = _.extend({count: 1}, options);
+ this.count = options.count;
+ },
+
+ url: function() {
+ return app.host + "/_uuids?count=" + this.count;
+ },
+
+ next: function() {
+ return this.get("uuids").pop();
+ }
+ });
+
+ app.fauxtonAPI = FauxtonAPI;
+ return app.fauxtonAPI;
+});
diff --git a/src/fauxton/app/app.js b/src/fauxton/app/app.js
new file mode 100644
index 000000000..ffd2227aa
--- /dev/null
+++ b/src/fauxton/app/app.js
@@ -0,0 +1,74 @@
+define([
+ // Libraries.
+ "jquery",
+ "lodash",
+ "backbone",
+
+ "helpers",
+
+ // Plugins.
+ "plugins/backbone.layoutmanager"
+],
+
+function($, _, Backbone, Helpers) {
+
+ // Make sure we have a console.log
+ if (typeof console == "undefined") {
+ console = {
+ log: function(){}
+ };
+ }
+
+ // Provide a global location to place configuration settings and module
+ // creation.
+ var app = {
+ // The root path to run the application.
+ root: "/"
+ };
+
+ // Localize or create a new JavaScript Template object.
+ var JST = window.JST = window.JST || {};
+
+ // Configure LayoutManager with Backbone Boilerplate defaults.
+ Backbone.LayoutManager.configure({
+ // Allow LayoutManager to augment Backbone.View.prototype.
+ manage: true,
+
+ prefix: "app/",
+
+ // Inject app/helper.js for shared functionality across all html templates
+ render: function(template, context) {
+ return template(_.extend(Helpers, context));
+ },
+
+ fetch: function(path) {
+ // Initialize done for use in async-mode
+ var done;
+
+ // Concatenate the file extension.
+ path = path + ".html";
+
+ // If cached, use the compiled template.
+ if (JST[path]) {
+ return JST[path];
+ } else {
+ // Put fetch into `async-mode`.
+ done = this.async();
+
+ // Seek out the template asynchronously.
+ return $.ajax({ url: app.root + path }).then(function(contents) {
+ done(JST[path] = _.template(contents));
+ });
+ }
+ }
+ });
+
+ // Mix Backbone.Events, and modules into the app object.
+ return _.extend(app, {
+ // Create a custom object with a nested Views object.
+ module: function(additionalProps) {
+ return _.extend({ Views: {} }, additionalProps);
+ }
+ }, Backbone.Events);
+
+});
diff --git a/src/fauxton/app/config.js b/src/fauxton/app/config.js
new file mode 100644
index 000000000..5bf1f8761
--- /dev/null
+++ b/src/fauxton/app/config.js
@@ -0,0 +1,53 @@
+// Set the require.js configuration for your application.
+require.config({
+
+ // Initialize the application with the main application file.
+ deps: ["main"],
+
+ paths: {
+ // JavaScript folders.
+ libs: "../assets/js/libs",
+ plugins: "../assets/js/plugins",
+
+ // Libraries.
+ jquery: "../assets/js/libs/jquery",
+ lodash: "../assets/js/libs/lodash",
+ backbone: "../assets/js/libs/backbone",
+ bootstrap: "../assets/js/libs/bootstrap",
+ codemirror: "../assets/js/libs/codemirror",
+ jshint: "../assets/js/libs/jshint",
+ d3: "../assets/js/libs/d3",
+ "nv.d3": "../assets/js/libs/nv.d3"
+ },
+
+ shim: {
+ // Backbone library depends on lodash and jQuery.
+ backbone: {
+ deps: ["lodash", "jquery"],
+ exports: "Backbone"
+ },
+
+ bootstrap: {
+ deps: ["jquery"],
+ exports: "Bootstrap"
+ },
+
+ codemirror: {
+ deps: ["jquery"],
+ exports: "CodeMirror"
+ },
+
+ jshint: {
+ deps: ["jquery"],
+ exports: "JSHINT"
+ },
+
+ // Backbone.LayoutManager depends on Backbone.
+ "plugins/backbone.layoutmanager": ["backbone"],
+
+ "plugins/codemirror-javascript": ["codemirror"],
+
+ "plugins/prettify": []
+ }
+
+});
diff --git a/src/fauxton/app/helpers.js b/src/fauxton/app/helpers.js
new file mode 100644
index 000000000..6b3a7cd5e
--- /dev/null
+++ b/src/fauxton/app/helpers.js
@@ -0,0 +1,35 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+
+// This file creates a set of helper functions that will be loaded for all html
+// templates. These functions should be self contained and not rely on any
+// external dependencies as they are loaded prior to the application. We may
+// want to change this later, but for now this should be thought of as a
+// "purely functional" helper system.
+
+
+define([
+],
+
+function() {
+
+ var Helpers = {};
+
+ Helpers.imageUrl = function(path) {
+ // TODO: add dynamic path for different deploy targets
+ return path;
+ };
+
+ return Helpers;
+});
+
diff --git a/src/fauxton/app/initialize.js b/src/fauxton/app/initialize.js
new file mode 100644
index 000000000..569967828
--- /dev/null
+++ b/src/fauxton/app/initialize.js
@@ -0,0 +1,62 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ // Application.
+ "app",
+
+ // Libraries
+ "lodash",
+ "bootstrap"
+],
+
+function(app, _, Bootstrap) {
+
+ // Provide a global location to place configuration settings and module
+ // creation.
+ _.extend(app, {
+ // The root path to run the application through.
+ // TODO: pick this up wither at build time or from the browser
+ root: "/_utils/fauxton/",
+
+ host: window.location.protocol + "//" + window.location.host,
+
+ renderView: function(baseView, selector, view, options, callback) {
+ baseView.setView(selector, new view(options)).render().then(callback);
+ },
+
+ // Thanks to: http://stackoverflow.com/a/2880929
+ getParams: function(queryString) {
+ if (typeof queryString !== "undefined") {
+ if (queryString.substring(0,1) === "?")
+ queryString = queryString.substring(1);
+ }
+ var hash = window.location.hash.split('?')[1];
+ queryString = queryString || hash || window.location.search.substring(1);
+ var match,
+ urlParams = {},
+ pl = /\+/g, // Regex for replacing addition symbol with a space
+ search = /([^&=]+)=?([^&]*)/g,
+ decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
+ query = queryString;
+
+ if (queryString) {
+ while ((match = search.exec(query))) {
+ urlParams[decode(match[1])] = decode(match[2]);
+ }
+ }
+
+ return urlParams;
+ }
+ });
+
+});
diff --git a/src/fauxton/app/load_addons.js.underscore b/src/fauxton/app/load_addons.js.underscore
new file mode 100644
index 000000000..9686ad76b
--- /dev/null
+++ b/src/fauxton/app/load_addons.js.underscore
@@ -0,0 +1,27 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+
+/*
+ * ::WARNING::
+ * THIS IS A GENERATED FILE. DO NOT EDIT.
+ */
+define([
+ <%= '"' + deps.join('","') + '"' %>
+],
+function() {
+ var LoadAddons = {
+ addons: arguments
+ };
+
+ return LoadAddons;
+});
diff --git a/src/fauxton/app/main.js b/src/fauxton/app/main.js
new file mode 100644
index 000000000..d3f8ac21e
--- /dev/null
+++ b/src/fauxton/app/main.js
@@ -0,0 +1,38 @@
+require([
+ // Application.
+ "app",
+
+ // Main Router.
+ "router"
+],
+
+function(app, Router) {
+
+ // Define your master router on the application namespace and trigger all
+ // navigation from this instance.
+ app.router = new Router();
+ // Trigger the initial route and enable HTML5 History API support, set the
+ // root folder to '/' by default. Change in app.js.
+ Backbone.history.start({ pushState: false, root: app.root });
+ // All navigation that is relative should be passed through the navigate
+ // method, to be processed by the router. If the link has a `data-bypass`
+ // attribute, bypass the delegation completely.
+ $(document).on("click", "a:not([data-bypass])", function(evt) {
+ // Get the absolute anchor href.
+ var href = { prop: $(this).prop("href"), attr: $(this).attr("href") };
+ // Get the absolute root.
+ var root = location.protocol + "//" + location.host + app.root;
+
+ // Ensure the root is part of the anchor href, meaning it's relative.
+ if (href.prop && href.prop.slice(0, root.length) === root) {
+ // Stop the default event to ensure the link will not cause a page
+ // refresh.
+ evt.preventDefault();
+
+ // `Backbone.history.navigate` is sufficient for all Routers and will
+ // trigger the correct events. The Router's internal `navigate` method
+ // calls this anyways. The fragment is sliced from the root.
+ Backbone.history.navigate(href.attr, true);
+ }
+ });
+});
diff --git a/src/fauxton/app/modules/databases/base.js b/src/fauxton/app/modules/databases/base.js
new file mode 100644
index 000000000..a5b4542b3
--- /dev/null
+++ b/src/fauxton/app/modules/databases/base.js
@@ -0,0 +1,36 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "modules/databases/routes",
+ // Views
+ "modules/databases/views"
+
+],
+
+function(app, FauxtonAPI, Databases, Views) {
+ Databases.Views = Views;
+
+ // Utility functions
+ Databases.databaseUrl = function(database) {
+ var name = _.isObject(database) ? database.id : database;
+
+ return ["/database/", name, "/_all_docs?limit=10"].join('');
+ };
+
+ return Databases;
+});
diff --git a/src/fauxton/app/modules/databases/resources.js b/src/fauxton/app/modules/databases/resources.js
new file mode 100644
index 000000000..e5d3faefb
--- /dev/null
+++ b/src/fauxton/app/modules/databases/resources.js
@@ -0,0 +1,146 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "modules/documents/resources"
+],
+
+function(app, FauxtonAPI, Documents) {
+ var Databases = FauxtonAPI.module();
+
+ Databases.Model = Backbone.Model.extend({
+ initialize: function(options) {
+ this.status = new Databases.Status({
+ database: this
+ });
+ },
+
+ buildAllDocs: function(params) {
+ this.allDocs = new Documents.AllDocs(null, {
+ database: this,
+ params: params
+ });
+
+ return this.allDocs;
+ },
+
+ isNew: function(){
+ // Databases are never new, to make Backbone do a PUT
+ return false;
+ },
+
+ url: function(context) {
+ if (context === "index") {
+ return "/database/" + this.id + "/_all_docs";
+ } else if (context === "changes") {
+ return "/database/" + this.id + "/_changes?descending=true&limit=100";
+ } else if (context === "app") {
+ return "/database/" + this.id;
+ } else {
+ return app.host + "/" + this.id;
+ }
+ },
+
+ buildChanges: function (params) {
+ this.changes = new Databases.Changes({
+ database: this,
+ params: params
+ });
+
+ return this.changes;
+ }
+ });
+
+ Databases.Changes = Backbone.Collection.extend({
+
+ initialize: function(options) {
+ this.database = options.database;
+ this.params = options.params;
+ },
+
+ url: function () {
+ var query = "";
+ if (this.params) {
+ query = "?" + $.param(this.params);
+ }
+
+ return app.host + '/' + this.database.id + '/_changes' + query;
+ },
+
+ parse: function (resp) {
+ this.last_seq = resp.last_seq;
+ return resp.results;
+ }
+ });
+
+ Databases.Status = Backbone.Model.extend({
+ url: function() {
+ return app.host + "/" + this.database.id;
+ },
+
+ initialize: function(options) {
+ this.database = options.database;
+ },
+
+ numDocs: function() {
+ return this.get("doc_count");
+ },
+
+ updateSeq: function(full) {
+ var updateSeq = this.get("update_seq");
+ if (full || (typeof(updateSeq) === 'number')) {
+ return updateSeq;
+ } else {
+ return updateSeq.split('-')[0];
+ }
+ },
+
+ humanSize: function() {
+ // cribbed from http://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable
+ var i = -1;
+ var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
+ var fileSizeInBytes = this.get("disk_size");
+ do {
+ fileSizeInBytes = fileSizeInBytes / 1024;
+ i++;
+ } while (fileSizeInBytes > 1024);
+
+ return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
+ }
+ });
+
+ // TODO: shared databases - read from the user doc
+ Databases.List = Backbone.Collection.extend({
+ model: Databases.Model,
+
+ url: function() {
+ return app.host + "/_all_dbs";
+ },
+
+ parse: function(resp) {
+ // TODO: pagination!
+ return _.map(_.first(resp, 10), function(database) {
+ return {
+ id: encodeURIComponent(database),
+ name: database
+ };
+ });
+ }
+ });
+
+ return Databases;
+});
diff --git a/src/fauxton/app/modules/databases/routes.js b/src/fauxton/app/modules/databases/routes.js
new file mode 100644
index 000000000..12edd0ea2
--- /dev/null
+++ b/src/fauxton/app/modules/databases/routes.js
@@ -0,0 +1,70 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "modules/databases/resources"
+],
+
+function(app, FauxtonAPI, Databases) {
+ var allDbsCallback = function() {
+ var data = {
+ databases: new Databases.List()
+ };
+ var deferred = FauxtonAPI.Deferred();
+
+ return {
+ layout: "with_sidebar",
+
+ data: data,
+
+ crumbs: [
+ {"name": "Databases", "link": "/_all_dbs"}
+ ],
+
+ views: {
+ "#dashboard-content": new Databases.Views.List({
+ collection: data.databases
+ }),
+
+ "#sidebar-content": new Databases.Views.Sidebar({
+ collection: data.databases
+ })
+ },
+
+ apiUrl: data.databases.url(),
+
+ establish: function() {
+ data.databases.fetch().done(function(resp) {
+ $.when.apply(null, data.databases.map(function(database) {
+ return database.status.fetch();
+ })).done(function(resp) {
+ deferred.resolve();
+ });
+ });
+ return [deferred];
+ }
+ };
+ };
+
+ Databases.Routes = {
+ "": allDbsCallback,
+ "index.html": allDbsCallback,
+ "_all_dbs": allDbsCallback
+ };
+
+ return Databases;
+});
diff --git a/src/fauxton/app/modules/databases/views.js b/src/fauxton/app/modules/databases/views.js
new file mode 100644
index 000000000..322cd5ad9
--- /dev/null
+++ b/src/fauxton/app/modules/databases/views.js
@@ -0,0 +1,168 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api"
+],
+
+function(app, FauxtonAPI) {
+ var Views = {};
+
+ Views.Item = FauxtonAPI.View.extend({
+ template: "templates/databases/item",
+ tagName: "tr",
+
+ serialize: function() {
+ return {
+ database: this.model
+ };
+ }
+ });
+
+ Views.List = FauxtonAPI.View.extend({
+ dbLimit: 10,
+ template: "templates/databases/list",
+ events: {
+ "click button.all": "selectAll",
+ "submit form.database-search": "switchDatabase"
+ },
+
+ initialize: function(options) {
+ this.collection.on("add", this.render, this);
+ },
+
+ serialize: function() {
+ return {
+ databases: this.collection
+ };
+ },
+
+ switchDatabase: function(event) {
+ event.preventDefault();
+ var dbname = this.$el.find("input.search-query").val();
+
+ if (dbname) {
+ // TODO: switch to using a model, or Databases.databaseUrl()
+ // Neither of which are in scope right now
+ // var db = new Database.Model({id: dbname});
+ var url = ["/database/", dbname, "/_all_docs?limit=10"].join('');
+ FauxtonAPI.navigate(url);
+ }
+ },
+
+ beforeRender: function() {
+ this.collection.each(function(database) {
+ this.insertView("table.databases tbody", new Views.Item({
+ model: database
+ }));
+ }, this);
+ },
+
+ afterRender: function() {
+ var dbLimit = this.dbLimit;
+ var ajaxReq;
+
+ this.$el.find("input.search-query").typeahead({
+ source: function(query, process) {
+ var url = [
+ app.host,
+ "/_all_dbs?startkey=%22",
+ query,
+ "%22&endkey=%22",
+ query,
+ "\u9999%22&limit=",
+ dbLimit
+ ].join('');
+ if (ajaxReq) ajaxReq.abort();
+ ajaxReq = $.ajax({
+ url: url,
+ dataType: 'json',
+ success: function(data) {
+ process(data);
+ }
+ });
+ }
+ });
+ },
+
+ selectAll: function(evt){
+ $("input:checkbox").attr('checked', !$(evt.target).hasClass('active'));
+ }
+ });
+
+ Views.Sidebar = FauxtonAPI.View.extend({
+ template: "templates/databases/sidebar",
+ events: {
+ "click a#new": "newDatabase",
+ "click a#owned": "showMine",
+ "click a#shared": "showShared"
+ },
+
+ newDatabase: function() {
+ var notification;
+ var db;
+ // TODO: use a modal here instead of the prompt
+ var name = prompt('Name of database', 'newdatabase');
+ if (name === null) {
+ return;
+ } else if (name.length === 0) {
+ notification = FauxtonAPI.addNotification({
+ msg: "Please enter a valid database name",
+ type: "error",
+ clear: true
+ });
+ return;
+ }
+ db = new this.collection.model({
+ id: encodeURIComponent(name),
+ name: name
+ });
+ notification = FauxtonAPI.addNotification({msg: "Creating database."});
+ db.save().done(function() {
+ notification = FauxtonAPI.addNotification({
+ msg: "Database created successfully",
+ type: "success",
+ clear: true
+ });
+ var route = "#/database/" + name + "/_all_docs?limit=100";
+ app.router.navigate(route, { trigger: true });
+ }
+ ).error(function(xhr) {
+ var responseText = JSON.parse(xhr.responseText).reason;
+ notification = FauxtonAPI.addNotification({
+ msg: "Create database failed: " + responseText,
+ type: "error",
+ clear: true
+ });
+ }
+ );
+ },
+
+ showMine: function(){
+ $.contribute(
+ 'Show unshared databases',
+ 'app/addons/databases/views.js'
+ );
+ },
+
+ showShared: function(){
+ $.contribute(
+ 'Show shared databases (e.g. continuous replications to/from the database)',
+ 'app/addons/databases/views.js'
+ );
+ }
+ });
+
+ return Views;
+});
diff --git a/src/fauxton/app/modules/documents/base.js b/src/fauxton/app/modules/documents/base.js
new file mode 100644
index 000000000..96e4ada62
--- /dev/null
+++ b/src/fauxton/app/modules/documents/base.js
@@ -0,0 +1,24 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "modules/documents/routes"
+],
+
+function(app, FauxtonAPI, Documents) {
+ return Documents;
+});
diff --git a/src/fauxton/app/modules/documents/resources.js b/src/fauxton/app/modules/documents/resources.js
new file mode 100644
index 000000000..5a08fb4cd
--- /dev/null
+++ b/src/fauxton/app/modules/documents/resources.js
@@ -0,0 +1,255 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Views
+ "modules/documents/views"
+
+ // Plugins
+],
+
+function(app, FauxtonAPI, Views) {
+ var Documents = app.module();
+
+ Documents.Doc = Backbone.Model.extend({
+ idAttribute: "_id",
+
+ url: function(context) {
+ if (context === "app") {
+ return this.getDatabase().url("app") + "/" + this.safeID();
+ } else {
+ return app.host + "/" + this.getDatabase().id + "/" + this.id;
+ }
+ },
+
+ initialize: function() {
+ if (this.collection && this.collection.database) {
+ this.database = this.collection.database;
+ }
+ },
+
+ // HACK: the doc needs to know about the database, but it may be
+ // set directly or indirectly in all docs
+ getDatabase: function() {
+ return this.database ? this.database : this.collection.database;
+ },
+
+ docType: function() {
+ return this.id.match(/^_design/) ? "design doc" : "doc";
+ },
+
+ isEditable: function() {
+ return this.docType() != "reduction";
+ },
+
+ isDdoc: function() {
+ return this.docType() === "design doc";
+ },
+
+ hasViews: function() {
+ if (!this.isDdoc()) return false;
+ var doc = this.get('doc');
+ return doc && doc.views && _.keys(doc.views).length > 0;
+ },
+
+ getDdocView: function(view) {
+ if (!this.isDdoc() || !this.hasViews()) return false;
+
+ var doc = this.get('doc');
+ return doc.views[view];
+ },
+
+ viewHasReduce: function(viewName) {
+ var view = this.getDdocView(viewName);
+
+ return view && view.reduce;
+ },
+
+ // Need this to work around backbone router thinking _design/foo
+ // is a separate route. Alternatively, maybe these should be
+ // treated separately. For instance, we could default into the
+ // json editor for docs, or into a ddoc specific page.
+ safeID: function() {
+ return this.id.replace('/', '%2F');
+ },
+
+ destroy: function() {
+ var url = this.url() + "?rev=" + this.get('_rev');
+ return $.ajax({
+ url: url,
+ dataType: 'json',
+ type: 'DELETE'
+ });
+ },
+
+ parse: function(resp) {
+ if (resp.rev) {
+ resp._rev = resp.rev;
+ delete resp.rev;
+ }
+ if (resp.id) {
+ if (typeof(this.id) === "undefined") {
+ resp._id = resp.id;
+ }
+ delete resp.id;
+ }
+ if (resp.ok) {
+ delete resp.ok;
+ }
+
+ return resp;
+ },
+
+ prettyJSON: function() {
+ var data = this.get("doc") ? this.get("doc") : this;
+
+ return JSON.stringify(data, null, " ");
+ }
+ });
+
+ Documents.ViewRow = Backbone.Model.extend({
+ docType: function() {
+ if (!this.id) return "reduction";
+
+ return this.id.match(/^_design/) ? "design doc" : "doc";
+ },
+
+ isEditable: function() {
+ return this.docType() != "reduction";
+ },
+
+ prettyJSON: function() {
+ //var data = this.get("doc") ? this.get("doc") : this;
+ return JSON.stringify(this, null, " ");
+ }
+ });
+
+ Documents.NewDoc = Documents.Doc.extend({
+ fetch: function() {
+ var uuid = new FauxtonAPI.UUID();
+ var deferred = this.deferred = $.Deferred();
+ var that = this;
+
+ uuid.fetch().done(function() {
+ that.set("_id", uuid.next());
+ deferred.resolve();
+ });
+
+ return deferred.promise();
+ }
+ });
+
+ Documents.AllDocs = Backbone.Collection.extend({
+ model: Documents.Doc,
+
+ initialize: function(_models, options) {
+ this.database = options.database;
+ this.params = options.params;
+ },
+
+ url: function() {
+ var query = "";
+ if (this.params) {
+ query = "?" + $.param(this.params);
+ }
+ return app.host + "/" + this.database.id + "/_all_docs" + query;
+ },
+
+ totalRows: function() {
+ return this.viewMeta.total_rows || "unknown";
+ },
+
+ updateSeq: function() {
+ return this.viewMeta.update_seq || false;
+ },
+
+ parse: function(resp) {
+ that = this;
+ this.viewMeta = {
+ total_rows: resp.total_rows,
+ offest: resp.offest,
+ update_seq: resp.update_seq
+ };
+ return _.map(resp.rows, function(row) {
+ return {
+ _id: row.id,
+ _rev: row.value.rev,
+ value: row.value,
+ key: row.key,
+ doc: row.doc || undefined
+ };
+ });
+ }
+ });
+
+ Documents.IndexCollection = Backbone.Collection.extend({
+ model: Documents.ViewRow,
+
+ initialize: function(_models, options) {
+ this.database = options.database;
+ this.view = options.view;
+ this.design = options.design;
+ this.params = _.extend({limit: 10, reduce: false}, options.params);
+ this.idxType = "_view";
+ },
+
+ url: function() {
+ var query = "";
+ if (this.params) {
+ query = "?" + $.param(this.params);
+ }
+ var url = [app.host, this.database.id, "_design", this.design, this.idxType, this.view];
+ return url.join("/") + query;
+ },
+
+ totalRows: function() {
+ return this.viewMeta.total_rows || "unknown";
+ },
+
+ updateSeq: function() {
+ return this.viewMeta.update_seq || false;
+ },
+
+ parse: function(resp) {
+ that = this;
+ this.viewMeta = {
+ total_rows: resp.total_rows,
+ offest: resp.offest,
+ update_seq: resp.update_seq
+ };
+ return _.map(resp.rows, function(row) {
+ return {
+ value: row.value,
+ key: row.key,
+ doc: row.doc,
+ id: row.id
+ };
+ });
+ },
+
+ buildAllDocs: function(){
+ this.fetch();
+ },
+
+ allDocs: function(){
+ return this.models;
+ }
+ });
+
+ Documents.Views = Views;
+
+ return Documents;
+});
diff --git a/src/fauxton/app/modules/documents/routes.js b/src/fauxton/app/modules/documents/routes.js
new file mode 100644
index 000000000..34b00d846
--- /dev/null
+++ b/src/fauxton/app/modules/documents/routes.js
@@ -0,0 +1,426 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "modules/documents/resources",
+ "modules/databases/base"
+],
+
+function(app, FauxtonAPI, Documents, Databases) {
+ // TODO: look at using:
+ // var Documents = require("modules/documents/models_collections");
+ // var Databases = require("modules/databases/module");
+
+ var codeEditorCallback = function(databaseName, docID) {
+ var data = {
+ database: new Databases.Model({id:databaseName}),
+ doc: new Documents.Doc({
+ "_id": docID
+ }),
+ selected: "code_editor"
+ };
+ data.doc.database = data.database;
+ data.designDocs = new Documents.AllDocs(null, {
+ database: data.database,
+ params: {startkey: '"_design"',
+ endkey: '"_design1"',
+ include_docs: true}
+ });
+
+ var options = app.getParams();
+ options.include_docs = true;
+ data.database.buildAllDocs(options);
+
+ return {
+ layout: "one_pane",
+
+ data: data,
+
+ crumbs: [
+ {"name": "Databases", "link": "/_all_dbs"},
+ {"name": data.database.id, "link": Databases.databaseUrl(data.database)},
+ {"name": docID, "link": "#"}
+ ],
+
+ views: {
+ "#dashboard-content": new Documents.Views.Doc({
+ model: data.doc
+ }),
+
+ "#tabs": new Documents.Views.FieldEditorTabs({
+ selected: data.selected,
+ model: data.doc
+ })
+ },
+
+ apiUrl: data.doc.url()
+ };
+ };
+
+ var newViewEditorCallback = function(databaseName) {
+ var data = {
+ database: new Databases.Model({id:databaseName})
+ };
+ data.designDocs = new Documents.AllDocs(null, {
+ database: data.database,
+ params: {startkey: '"_design"',
+ endkey: '"_design1"',
+ include_docs: true}
+ });
+
+ return {
+ layout: "with_tabs_sidebar",
+
+ data: data,
+
+ crumbs: [
+ {"name": "Databases", "link": "/_all_dbs"},
+ {"name": data.database.id, "link": data.database.url('app')}
+ ],
+
+ views: {
+ "#sidebar-content": new Documents.Views.Sidebar({
+ collection: data.designDocs
+ }),
+
+ "#tabs": new Documents.Views.Tabs({
+ collection: data.designDocs,
+ database: data.database
+ }),
+
+ "#dashboard-content": new Documents.Views.ViewEditor({
+ model: data.database,
+ ddocs: data.designDocs
+ })
+ },
+
+ apiUrl: data.database.url()
+ };
+ };
+
+ // HACK: this kind of works
+ // Basically need a way to share state between different routes, for
+ // instance making a new doc won't work for switching back and forth
+ // between code and field editors
+ var newDocCodeEditorCallback = function(databaseName) {
+ var data = {
+ database: new Databases.Model({id:databaseName}),
+ doc: new Documents.NewDoc(),
+ selected: "code_editor"
+ };
+ data.doc.database = data.database;
+ data.designDocs = new Documents.AllDocs(null, {
+ database: data.database,
+ params: {startkey: '"_design"',
+ endkey: '"_design1"',
+ include_docs: true}
+ });
+
+ var options = app.getParams();
+ options.include_docs = true;
+ data.database.buildAllDocs(options);
+
+ return {
+ layout: "one_pane",
+
+ data: data,
+
+ crumbs: [
+ {"name": "Databases", "link": "/_all_dbs"},
+ {"name": data.database.id, "link": Databases.databaseUrl(data.database)},
+ {"name": "new", "link": "#"}
+ ],
+
+ views: {
+ "#dashboard-content": new Documents.Views.Doc({
+ model: data.doc
+ }),
+
+ "#tabs": new Documents.Views.FieldEditorTabs({
+ selected: data.selected,
+ model: data.doc
+ })
+ },
+
+ apiUrl: data.doc.url()
+ };
+ };
+
+ Documents.Routes = {
+ "database/:database/:doc/field_editor": function(databaseName, docID) {
+ var data = {
+ database: new Databases.Model({id:databaseName}),
+ doc: new Documents.Doc({
+ "_id": docID
+ }),
+ selected: "field_editor"
+ };
+ data.doc.database = data.database;
+ data.designDocs = new Documents.AllDocs(null, {
+ database: data.database,
+ params: {startkey: '"_design"',
+ endkey: '"_design1"',
+ include_docs: true}
+ });
+
+ var options = app.getParams();
+ options.include_docs = true;
+ data.database.buildAllDocs(options);
+
+ return {
+ layout: "one_pane",
+
+ data: data,
+
+ crumbs: [
+ {"name": "Databases", "link": "/_all_dbs"},
+ {"name": data.database.id, "link": Databases.databaseUrl(data.database)},
+ {"name": docID, "link": "#"}
+ ],
+
+ views: {
+ "#dashboard-content": new Documents.Views.DocFieldEditor({
+ model: data.doc
+ }),
+
+ "#tabs": new Documents.Views.FieldEditorTabs({
+ selected: data.selected,
+ model: data.doc
+ })
+ },
+
+ apiUrl: data.doc.url()
+ };
+ },
+
+ "database/:database/:doc/code_editor": codeEditorCallback,
+ "database/:database/:doc": codeEditorCallback,
+ "database/:database/_design%2F:doc": function(database, doc) {
+ var docID = "_design/"+doc;
+ return codeEditorCallback(database, docID);
+ },
+
+ // HACK
+ // The ordering of routes is different in this object that the
+ // routes object in the Backbone.Router. As a result, the
+ // declaration order of show doc and _handler methods has been
+ // switched. This is a brittle solution that needs to be fixed.
+ // Conflicts with route: "database/:database/_:handler"
+ //
+ // TODO: add support for regex based rotues
+ // Javascript does not handle a regex as an object key very well,
+ // and it turns it into its string representation when you use in
+ // non object literal form, which does get recast back as a regex
+ // when we need it.
+ // The inability to use regex based routes here is a design flaw
+ // and should be rectified.
+ "old_database/:database/:doc": function(databaseName, docID) {
+ var data = {
+ database: new Databases.Model({id:databaseName}),
+ doc: new Documents.Doc({
+ "_id": docID
+ })
+ };
+ data.doc.database = data.database;
+ data.designDocs = new Documents.AllDocs(null, {
+ database: data.database,
+ params: {startkey: '"_design"',
+ endkey: '"_design1"',
+ include_docs: true}
+ });
+
+ var options = app.getParams();
+ options.include_docs = true;
+ data.database.buildAllDocs(options);
+
+ return {
+ layout: "with_sidebar",
+
+ data: data,
+
+ crumbs: [
+ {"name": "Databases", "link": "/_all_dbs"},
+ {"name": data.database.id, "link": Databases.databaseUrl(data.database)},
+ {"name": docID, "link": "#"}
+ ],
+
+ views: {
+ "#dashboard-content": new Documents.Views.Doc({
+ model: data.doc
+ }),
+
+ "#sidebar-content": new Documents.Views.Sidebar({
+ collection: data.designDocs
+ })
+ },
+
+ apiUrl: data.doc.url()
+ };
+ },
+
+ "database/:database/_all_docs(:extra)": function(databaseName, page) {
+ var data = {
+ database: new Databases.Model({id:databaseName})
+ };
+ data.designDocs = new Documents.AllDocs(null, {
+ database: data.database,
+ params: {startkey: '"_design"',
+ endkey: '"_design1"',
+ include_docs: true}
+ });
+
+ var options = app.getParams();
+ options.include_docs = true;
+ data.database.buildAllDocs(options);
+
+ return {
+ layout: "with_tabs_sidebar",
+
+ data: data,
+
+ crumbs: [
+ {"name": "Databases", "link": "/_all_dbs"},
+ {"name": data.database.id, "link": Databases.databaseUrl(data.database)}
+ ],
+
+ views: {
+ "#dashboard-content": new Documents.Views.AllDocsList({
+ collection: data.database.allDocs
+ }),
+
+ "#sidebar-content": new Documents.Views.Sidebar({
+ collection: data.designDocs
+ }),
+
+ "#tabs": new Documents.Views.Tabs({
+ collection: data.designDocs,
+ database: data.database
+ })
+ },
+
+ apiUrl: data.database.allDocs.url()
+ };
+ },
+
+ "database/:database/_changes(:params)": function(databaseName, params) {
+ var data = {
+ database: new Databases.Model({id:databaseName})
+ };
+
+ var options = app.getParams();
+ data.database.buildChanges(options);
+
+ return {
+ layout: "with_tabs",
+
+ data: data,
+
+ crumbs: [
+ {"name": "Databases", "link": "/_all_dbs"},
+ {"name": data.database.id, "link": Databases.databaseUrl(data.database)},
+ {"name": "_changes", "link": "/_changes"}
+ ],
+
+ views: {
+ "#dashboard-content": new Documents.Views.Changes({
+ model: data.database
+ }),
+
+ "#tabs": new Documents.Views.Tabs({
+ collection: data.designDocs,
+ database: data.database,
+ active_id: 'changes'
+ })
+ },
+
+ apiUrl: data.database.changes.url()
+ };
+ },
+
+ "database/:database/new": newDocCodeEditorCallback,
+ "database/:database/new_view": newViewEditorCallback,
+
+ // TODO: fix optional search params
+ // Can't get ":view(?*search)" to work
+ // However ":view?*search" does work
+ //"database/:database/_design/:ddoc/_view/:view(\?*options)": function(databaseName, ddoc, view, options) {
+ "database/:database/_design/:ddoc/_view/:view": function(databaseName, ddoc, view, options) {
+ // hack around backbone router limitations
+ view = view.replace(/\?.*$/,'');
+ var params = app.getParams();
+ var data = {
+ database: new Databases.Model({id:databaseName})
+ };
+
+ data.indexedDocs = new Documents.IndexCollection(null, {
+ database: data.database,
+ design: ddoc,
+ view: view,
+ params: params
+ });
+
+ data.designDocs = new Documents.AllDocs(null, {
+ database: data.database,
+ params: {startkey: '"_design"',
+ endkey: '"_design1"',
+ include_docs: true}
+ });
+
+ var ddocInfo = {
+ id: "_design/" + ddoc,
+ currView: view,
+ designDocs: data.designDocs
+ };
+
+ return {
+ layout: "with_tabs_sidebar",
+
+ data: data,
+ // TODO: change dashboard-content
+ views: {
+ "#dashboard-content": new Documents.Views.AllDocsList({
+ collection: data.indexedDocs,
+ nestedView: Documents.Views.Row,
+ viewList: true,
+ ddocInfo: ddocInfo,
+ params: params
+ }),
+
+ "#sidebar-content": new Documents.Views.Sidebar({
+ collection: data.designDocs,
+ ddocInfo: ddocInfo
+ }),
+
+ "#tabs": new Documents.Views.Tabs({
+ collection: data.designDocs,
+ database: data.database
+ })
+ },
+
+ crumbs: [
+ {"name": "Databases", "link": "/_all_dbs"},
+ {"name": data.database.id, "link": Databases.databaseUrl(data.database)},
+ {"name": ddoc + "/" + view, "link": data.indexedDocs.url()}
+ ],
+ // TODO: change to view URL
+ apiUrl: data.indexedDocs.url()
+ };
+ }
+ };
+
+ return Documents;
+});
diff --git a/src/fauxton/app/modules/documents/views.js b/src/fauxton/app/modules/documents/views.js
new file mode 100644
index 000000000..6f370cc73
--- /dev/null
+++ b/src/fauxton/app/modules/documents/views.js
@@ -0,0 +1,950 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ "api",
+
+ // Libs
+ "codemirror",
+ "jshint",
+
+ // Plugins
+ "plugins/codemirror-javascript",
+ "plugins/prettify"
+],
+
+function(app, FauxtonAPI, Codemirror, JSHint) {
+ var Views = {};
+
+ Views.Tabs = FauxtonAPI.View.extend({
+ template: "templates/documents/tabs",
+ initialize: function(options){
+ this.collection = options.collection;
+ this.database = options.database;
+ this.active_id = options.active_id;
+ },
+
+ events: {
+ "click #delete-database": "delete_database"
+ },
+
+ serialize: function () {
+ return {
+ // TODO make this not hard coded here
+ changes_url: '#' + this.database.url('changes'),
+ db_url: '#' + this.database.url('index') + '?limit=100'
+ };
+ },
+
+ beforeRender: function(manage) {
+ this.insertView("#search", new Views.SearchBox({
+ collection: this.collection,
+ database: this.database.id
+ }));
+ },
+
+ afterRender: function () {
+ if (this.active_id) {
+ this.$('.active').removeClass('active');
+ this.$('#'+this.active_id).addClass('active');
+ }
+ },
+
+ delete_database: function (event) {
+ event.preventDefault();
+
+ var result = confirm("Are you sure you want to delete this database?");
+
+ if (!result) { return; }
+
+ return this.database.destroy().done(function () {
+ app.router.navigate('/', {trigger: true});
+ });
+ }
+ });
+
+ Views.SearchBox = FauxtonAPI.View.extend({
+ template: "templates/documents/search",
+ tagName: "form",
+ initialize: function(options){
+ this.collection = options.collection;
+ this.database = options.database;
+ },
+ afterRender: function(){
+ var collection = this.collection;
+ var form = this.$el;
+ var searchbox = form.find("input#searchbox");
+ var database = this.database;
+
+ form.submit(function(evt){
+ evt.preventDefault();
+ var viewname = form.find("input#view").val().split('/');
+ var url = "#database/" + database + "/_design/";
+ url += viewname[0] + "/_view/" + viewname[1];
+ if (searchbox.val() !== ""){
+ // TODO: this'll need to work when val() is a number etc.
+ url += '?start_key="' + searchbox.val() + '"';
+ }
+ FauxtonAPI.navigate(url);
+ });
+
+ searchbox.typeahead({
+ source: function(query, process) {
+ // TODO: include _all_docs and view keys somehow
+ var views = _.map(collection.pluck('doc'), function(d){
+ return _.map(_.keys(d.views), function(view){
+ return d._id.split('/')[1] + "/" + view;
+ });
+ });
+ return _.flatten(views);
+ },
+ minLength: 3,
+ updater: function(item){
+ // TODO: some way to return the original search box
+ this.$element.removeClass('span12');
+ this.$element.addClass('span6');
+ this.$element.attr('placeholder', 'Search by view key');
+ $('<span class="add-on span6">' + item +'</span>').insertBefore(this.$element);
+ $('<input type="hidden" id="view" value="' + item +'"/>').insertBefore(this.$element);
+ // Remove the type ahead for now
+ $('.typehead').unbind();
+ }
+ });
+ }
+ });
+
+ Views.FieldEditorTabs = FauxtonAPI.View.extend({
+ template: "templates/documents/doc_field_editor_tabs",
+
+ initialize: function(options) {
+ this.selected = options.selected;
+ },
+
+ events: {
+ "click button.delete": "destroy",
+ "click button.duplicate": "duplicate"
+ },
+
+ destroy: function(event) {
+ if (!window.confirm("Are you sure you want to delete this doc?")) {
+ return false;
+ }
+
+ var database = this.model.database;
+
+ this.model.destroy().then(function(resp) {
+ FauxtonAPI.addNotification({
+ msg: "Succesfully destroyed your doc"
+ });
+ FauxtonAPI.navigate(database.url("index"));
+ }, function(resp) {
+ FauxtonAPI.addNotification({
+ msg: "Failed to destroy your doc!",
+ type: "error"
+ });
+ });
+ },
+
+ duplicate: function(event) {
+ FauxtonAPI.addNotification({
+ type: "warning",
+ msg: "Duplicate functionality coming soon."
+ });
+ },
+
+ serialize: function() {
+ var selected = this.selected;
+ return {
+ doc: this.model,
+ isSelectedClass: function(item) {
+ return item && item === selected ? "active" : "";
+ }
+ };
+ },
+
+ establish: function() {
+ return [this.model.fetch()];
+ }
+ });
+
+ Views.Document = FauxtonAPI.View.extend({
+ template: "templates/documents/all_docs_item",
+ tagName: "tr",
+ className: "all-docs-item",
+
+ events: {
+ "click button.delete": "destroy"
+ },
+
+ attributes: function() {
+ return {
+ "data-id": this.model.id
+ };
+ },
+
+ serialize: function() {
+ return {
+ doc: this.model
+ };
+ },
+
+ establish: function() {
+ return [this.model.fetch()];
+ },
+
+ destroy: function(event) {
+ event.preventDefault();
+ var that = this;
+
+ if (!window.confirm("Are you sure you want to delete this doc?")) {
+ return false;
+ }
+
+ this.model.destroy().then(function(resp) {
+ FauxtonAPI.addNotification({
+ msg: "Succesfully destroyed your doc"
+ });
+ that.$el.fadeOut();
+ that.model.collection.remove(that.id);
+ }, function(resp) {
+ FauxtonAPI.addNotification({
+ msg: "Failed to destroy your doc!",
+ type: "error"
+ });
+ });
+ }
+ });
+
+ Views.Row = FauxtonAPI.View.extend({
+ template: "templates/documents/index_row_docular",
+ tagName: "tr",
+
+ serialize: function() {
+ return {
+ doc: this.model
+ };
+ }
+ });
+
+ Views.IndexItem = FauxtonAPI.View.extend({
+ template: "templates/documents/index_menu_item",
+ tagName: "li",
+
+ initialize: function(options){
+ this.index = options.index;
+ this.ddoc = options.ddoc;
+ this.database = options.database;
+ this.selected = !! options.selected;
+ },
+
+ serialize: function() {
+ return {
+ index: this.index,
+ ddoc: this.ddoc,
+ database: this.database,
+ selected: this.selected
+ };
+ },
+
+ afterRender: function() {
+ if (this.selected) {
+ $("#sidenav ul.nav-list li").removeClass("active");
+ this.$el.addClass("active");
+ }
+ }
+ });
+
+ // TODO: Rename to reflect that this is a list of rows or documents
+ Views.AllDocsList = FauxtonAPI.View.extend({
+ template: "templates/documents/all_docs_list",
+ events: {
+ "click button.all": "selectAll",
+ "click button.bulk-delete": "bulkDelete",
+ "change form.view-query-update input": "updateFilters",
+ "change form.view-query-update select": "updateFilters",
+ "submit form.view-query-update": "updateView"
+ },
+
+ initialize: function(options){
+ this.nestedView = options.nestedView || Views.Document;
+ this.rows = {};
+ this.viewList = !! options.viewList;
+ this.params = options.params;
+ if (options.ddocInfo) {
+ this.designDocs = options.ddocInfo.designDocs;
+ this.ddocID = options.ddocInfo.id;
+ }
+ },
+
+ establish: function() {
+ var deferreds = [
+ this.collection.fetch().error(function() {
+ // TODO: handle error requests that slip through
+ // This should just throw a notification, not break the page
+ console.log("ERROR: ", arguments);
+ })
+ ];
+ if (this.designDocs) {
+ deferreds.push(this.designDocs.fetch());
+ }
+ return deferreds;
+ },
+
+ selectAll: function(evt){
+ $("input:checkbox").attr('checked', !$(evt.target).hasClass('active'));
+ },
+
+ // TODO:: HACK::
+ // Hack to grab info about the ddoc and current view to determine whether
+ // or not the view has a reduce function so we can display the advanced
+ // options appropriately.
+ //
+ // NOTE: we have this here temporarily because we have to wait for the
+ // design docs to be present.
+ //
+ // NOTE: We should probably refactor this View out into a separate View
+ // dedicated to displaying view query results.
+ // If nothing else, we should at least switch to something along the lines
+ // of fetchOnce to ensure we're not reloading the ddocs here in addition to
+ // the sidebar.
+ setDdocInfo: function() {
+ if (!this.ddoc && this.designDocs) {
+ this.ddoc = this.designDocs.get(this.ddocID);
+ }
+ },
+
+ serialize: function() {
+ this.setDdocInfo();
+ var data = {
+ database: this.collection,
+ viewList: this.viewList,
+ hasReduce: false,
+ params: this.params,
+ ddocs: this.designDocs
+ };
+ if (this.ddoc) {
+ data.ddoc = this.ddoc;
+ data.hasReduce = this.ddoc.viewHasReduce(this.collection.view);
+ }
+ return data;
+ },
+
+ updateView: function(event) {
+ event.preventDefault();
+ var $form = $(event.currentTarget);
+
+ // Ignore params without a value
+ var params = _.filter($form.serializeArray(), function(param) {
+ return param.value;
+ });
+
+ // Validate *key* params to ensure they're valid JSON
+ var keyParams = ["key","keys","startkey","endkey"];
+ var errorParams = _.filter(params, function(param) {
+ if (_.contains(keyParams, param.name)) {
+ try {
+ JSON.parse(param.value);
+ return false;
+ } catch(e) {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ });
+
+ if (_.any(errorParams)) {
+ _.map(errorParams, function(param) {
+
+ // TODO: Where to add this error?
+ // bootstrap wants the error on a control-group div, but we're not using that
+ //$('form.view-query-update input[name='+param+'], form.view-query-update select[name='+param+']').addClass('error');
+
+ return FauxtonAPI.addNotification({
+ msg: "JSON Parse Error on field: "+param.name,
+ type: "error",
+ selector: ".view.show .all-docs-list.errors-container"
+ });
+ });
+
+ FauxtonAPI.addNotification({
+ msg: "Make sure that strings are properly quoted and any other values are valid JSON structures",
+ type: "warning",
+ selector: ".view.show .all-docs-list.errors-container"
+ });
+
+ return false;
+ }
+
+ var fragment = window.location.hash.replace(/\?.*$/, '');
+ fragment = fragment + '?' + $.param(params);
+ FauxtonAPI.navigate(fragment);
+ },
+
+ updateFilters: function(event) {
+ event.preventDefault();
+ var $ele = $(event.currentTarget);
+ var name = $ele.attr('name');
+ this.updateFiltersFor(name, $ele);
+ },
+
+ updateFiltersFor: function(name, $ele) {
+ var $form = $ele.parents("form.view-query-update:first");
+ switch (name) {
+ // Reduce constraints
+ // - Can't include_docs for reduce=true
+ // - can't include group_level for reduce=false
+ case "reduce":
+ if ($ele.prop('checked') === true) {
+ if ($form.find("input[name=include_docs]").prop("checked") === true) {
+ $form.find("input[name=include_docs]").prop("checked", false);
+ var notification = FauxtonAPI.addNotification({
+ msg: "include_docs has been disabled as you cannot include docs on a reduced view",
+ type: "warn",
+ selector: ".view.show .all-docs-list.errors-container"
+ });
+ }
+ $form.find("input[name=include_docs]").prop("disabled", true);
+ $form.find("select[name=group_level]").prop("disabled", false);
+ } else {
+ $form.find("select[name=group_level]").prop("disabled", true);
+ $form.find("input[name=include_docs]").prop("disabled", false);
+ }
+ break;
+ case "include_docs":
+ break;
+ }
+ },
+
+ /*
+ * TODO: this should be reconsidered
+ * This currently performs delete operations on the model level,
+ * when we could be using bulk docs with _deleted = true. Using
+ * individual models is cleaner from a backbone standpoint, but
+ * not from the couchdb api.
+ * Also, the delete method is naive and leaves the body intact,
+ * when we should switch the doc to only having id/rev/deleted.
+ */
+ bulkDelete: function() {
+ var that = this;
+ // yuck, data binding ftw?
+ var eles = this.$el.find("input.row-select:checked").parents("tr.all-docs-item").map(function(e) { return $(this).attr("data-id"); }).get();
+
+ if (!window.confirm("Are you sure you want to delete these " + eles.length + " docs?")) {
+ return false;
+ }
+
+ _.each(eles, function(ele) {
+ var model = this.collection.get(ele);
+
+ model.destroy().then(function(resp) {
+ that.rows[ele].$el.fadeOut();
+
+ model.collection.remove(model.id);
+ }, function(resp) {
+ FauxtonAPI.addNotification({
+ msg: "Failed to destroy your doc!",
+ type: "error"
+ });
+ });
+ }, this);
+ },
+
+ beforeRender: function() {
+ this.setDdocInfo();
+ if (this.viewList) {
+ this.viewEditorView = this.insertView("#edit-index-container", new Views.ViewEditor({
+ model: this.ddoc,
+ ddocs: this.designDocs,
+ viewCollection: this.collection
+ }));
+ }
+ this.collection.each(function(doc) {
+ this.rows[doc.id] = this.insertView("table.all-docs tbody", new this.nestedView({
+ model: doc
+ }));
+ }, this);
+ },
+
+ afterRender: function(){
+ prettyPrint();
+ if (this.params) {
+ var $form = this.$el.find("form.view-query-update");
+ _.each(this.params, function(val, key) {
+ var $ele;
+ switch (key) {
+ case "limit":
+ case "group_level":
+ $form.find("select[name='"+key+"']").val(val);
+ break;
+ case "include_docs":
+ case "stale":
+ case "descending":
+ case "inclusive_end":
+ $form.find("input[name='"+key+"']").prop('checked', true);
+ break;
+ case "reduce":
+ $ele = $form.find("input[name='"+key+"']");
+ if (val == "true") {
+ $ele.prop('checked', true);
+ }
+ this.updateFiltersFor(key, $ele);
+ break;
+ default:
+ $form.find("input[name='"+key+"']").val(val);
+ break;
+ }
+ }, this);
+ }
+ }
+ });
+
+ Views.Doc = FauxtonAPI.View.extend({
+ template: "templates/documents/doc",
+
+ events: {
+ "click button.save-doc": "saveDoc"
+ },
+
+ updateValues: function() {
+ var notification;
+ if (this.model.changedAttributes()) {
+ notification = FauxtonAPI.addNotification({
+ msg: "Document saved successfully.",
+ type: "success",
+ clear: true
+ });
+ this.editor.setValue(this.model.prettyJSON());
+ }
+ },
+
+ establish: function() {
+ return [this.model.fetch()];
+ },
+
+ saveDoc: function(event) {
+ var json, notification;
+ if (this.hasValidCode()) {
+ json = JSON.parse(this.editor.getValue());
+ this.model.clear({silent:true});
+ this.model.set(json);
+ notification = FauxtonAPI.addNotification({msg: "Saving document."});
+ this.model.save().error(function(xhr) {
+ var responseText = JSON.parse(xhr.responseText).reason;
+ notification = FauxtonAPI.addNotification({
+ msg: "Save failed: " + responseText,
+ type: "error",
+ clear: true
+ });
+ });
+ } else {
+ notification = FauxtonAPI.addNotification({
+ msg: "Please fix the JSON errors and try again.",
+ type: "error",
+ selector: "#doc .errors-container"
+ });
+ }
+ },
+
+ hasValidCode: function() {
+ return JSHINT(this.editor.getValue()) !== false;
+ },
+
+ runJSHint: function() {
+ var json = this.editor.getValue();
+ var output = JSHint(json);
+
+ // Clear existing markers
+ for (var i = 0, l = this.editor.lineCount(); i < l; i++) {
+ this.editor.clearMarker(i);
+ }
+
+ if (output === false) {
+ _.map(JSHint.errors, function(error) {
+ var line = error.line - 1;
+ var className = "view-code-error-line-" + line;
+ this.editor.setMarker(line, "●", "view-code-error "+className);
+
+ setTimeout(function() {
+ $(".CodeMirror ."+className).tooltip({
+ title: "ERROR: " + error.reason
+ });
+ }, 0);
+ }, this);
+ }
+ },
+
+ serialize: function() {
+ return {
+ doc: this.model
+ };
+ },
+
+ afterRender: function() {
+ this.model.on("sync", this.updateValues, this);
+ var that = this;
+ this.editor = Codemirror.fromTextArea(this.$el.find("textarea.doc-code").get()[0], {
+ mode: "application/json",
+ json: false,
+ lineNumbers: true,
+ matchBrackets: true,
+ lineWrapping: true,
+ onChange: function() {
+ that.runJSHint();
+ },
+ extraKeys: {
+ "Ctrl-S": function(instance) { that.saveDoc(); },
+ "Ctrl-/": "undo"
+ }
+ });
+ }
+ });
+
+ Views.DocFieldEditor = FauxtonAPI.View.extend({
+ template: "templates/documents/doc_field_editor",
+
+ events: {
+ "click button.save": "saveDoc"
+ },
+
+ saveDoc: function(event) {
+ FauxtonAPI.addNotification({
+ type: "warning",
+ msg: "Save functionality coming soon."
+ });
+ },
+
+ serialize: function() {
+ return {
+ doc: this.getModel()
+ };
+ },
+
+ getModel: function() {
+ return this.model;
+ },
+
+ establish: function() {
+ return [this.model.fetch()];
+ }
+ });
+
+ Views.ViewEditor = FauxtonAPI.View.extend({
+ template: "templates/documents/view_editor",
+ builtinReduces: ['_sum', '_count', '_stats'],
+
+ events: {
+ "click button.save": "saveView",
+ "click button.preview": "previewView",
+ "change select#reduce-function-selector": "updateReduce"
+ },
+
+ langTemplates: {
+ "javascript": {
+ map: "function(doc) {\n emit(null, doc);\n}",
+ reduce: "function(keys, values, rereduce){\n if (rereduce){\n return sum(values);\n } else {\n return values.length;\n }\n}"
+ }
+ },
+
+ defaultLang: "javascript",
+
+ initialize: function(options) {
+ this.ddocs = options.ddocs;
+ this.viewCollection = options.viewCollection;
+ this.reduceFunStr = this.model.viewHasReduce(this.viewCollection.view);
+ this.newView = false;
+ },
+
+ updateValues: function() {
+ var notification;
+ if (this.model.changedAttributes()) {
+ notification = FauxtonAPI.addNotification({
+ msg: "Document saved successfully.",
+ type: "success",
+ clear: true
+ });
+ this.editor.setValue(this.model.prettyJSON());
+ }
+ },
+
+ updateReduce: function(event) {
+ var $ele = $("#reduce-function-selector");
+ var $reduceContainer = $(".control-group.reduce-function");
+ if ($ele.val() == "CUSTOM") {
+ $reduceContainer.show();
+ } else {
+ $reduceContainer.hide();
+ }
+ },
+
+ establish: function() {
+ //return [this.ddocs.fetch(), this.model.fetch()];
+ return [];
+ },
+
+ previewView: function(event) {
+ FauxtonAPI.addNotification({
+ msg: "<strong>Warning!</strong> Preview executes the Map/Reduce functions in your browser, and may behave differently from CouchDB.",
+ type: "warning",
+ selector: "#define-view .errors-container",
+ fade: false
+ });
+ FauxtonAPI.addNotification({
+ msg: "Preview Functionality Coming Soon",
+ type: "warning",
+ selector: "#define-view .errors-container"
+ });
+ },
+
+ saveView: function(event) {
+ var json, notification;
+ if (this.hasValidCode()) {
+ var mapVal = this.mapEditor.getValue();
+ var reduceVal = this.reduceEditor.getValue();
+ /*
+ notification = FauxtonAPI.addNotification({
+ msg: "Saving document.",
+ selector: "#define-view .errors-container"
+ });
+ */
+ FauxtonAPI.addNotification({
+ msg: "Save Functionality Coming Soon",
+ type: "warning",
+ selector: "#define-view .errors-container"
+ });
+ /*
+ this.model.save().error(function(xhr) {
+ var responseText = JSON.parse(xhr.responseText).reason;
+ notification = FauxtonAPI.addNotification({
+ msg: "Save failed: " + responseText,
+ type: "error",
+ clear: true
+ });
+ });
+ */
+ } else {
+ notification = FauxtonAPI.addNotification({
+ msg: "Please fix the JSON errors and try again.",
+ type: "error",
+ selector: "#define-view .errors-container"
+ });
+ }
+ },
+
+ isCustomReduceEnabled: function() {
+ return $("#reduce-function-selector").val() == "CUSTOM";
+ },
+
+ reduceVal: function() {
+ },
+
+ hasValidCode: function() {
+ return _.every(["mapEditor", "reduceEditor"], function(editorName) {
+ var editor = this[editorName];
+ if (editorName == "reduceEditor" && ! this.isCustomReduceEnabled()) {
+ return true;
+ } else if (JSHINT(editor.getValue()) !== false) {
+ return true;
+ } else {
+ // By default CouchDB view functions don't pass lint
+ return _.every(JSHINT.errors, function(error) {
+ return FauxtonAPI.isIgnorableError(error.reason);
+ });
+ }
+ }, this);
+ },
+
+ runJSHint: function(editorName) {
+ var editor = this[editorName];
+ var json = editor.getValue();
+ var output = JSHint(json);
+
+ // Clear existing markers
+ for (var i = 0, l = editor.lineCount(); i < l; i++) {
+ editor.clearMarker(i);
+ }
+
+ if (output === false) {
+ _.map(JSHint.errors, function(error) {
+ // By default CouchDB view functions don't pass lint
+ if (FauxtonAPI.isIgnorableError(error.reason)) return true;
+
+ var line = error.line - 1;
+ var className = "view-code-error-line-" + line;
+ editor.setMarker(line, "●", "view-code-error "+className);
+
+ setTimeout(function() {
+ $(".CodeMirror ."+className).tooltip({
+ title: "ERROR: " + error.reason
+ });
+ }, 0);
+ }, this);
+ }
+ },
+
+ serialize: function() {
+ return {
+ //database: this.model,
+ ddocs: this.ddocs,
+ ddoc: this.model,
+ viewCollection: this.viewCollection,
+ reduceFunStr: this.reduceFunStr,
+ isCustomReduce: this.hasCustomReduce(),
+ newView: this.newView
+ };
+ },
+
+ hasCustomReduce: function() {
+ return this.reduceFunStr && ! _.contains(this.builtinReduces, this.reduceFunStr);
+ },
+
+ afterRender: function() {
+ var that = this;
+ var mapFun = $("#map-function");
+ var reduceFun = $("#reduce-function");
+ if (this.newView) {
+ mapFun.val(this.langTemplates[this.defaultLang].map);
+ reduceFun.val(this.langTemplates[this.defaultLang].reduce);
+ }
+ this.mapEditor = Codemirror.fromTextArea(mapFun.get()[0], {
+ mode: "javascript",
+ lineNumbers: true,
+ matchBrackets: true,
+ lineWrapping: true,
+ onChange: function() {
+ that.runJSHint("mapEditor");
+ },
+ extraKeys: {
+ "Ctrl-S": function(instance) { that.saveView(); },
+ "Ctrl-/": "undo"
+ }
+ });
+ this.reduceEditor = Codemirror.fromTextArea(reduceFun.get()[0], {
+ mode: "javascript",
+ lineNumbers: true,
+ matchBrackets: true,
+ lineWrapping: true,
+ onChange: function() {
+ that.runJSHint("reduceEditor");
+ },
+ extraKeys: {
+ "Ctrl-S": function(instance) { that.saveView(); },
+ "Ctrl-/": "undo"
+ }
+ });
+ // HACK: this should be in the html
+ // but CodeMirror's head explodes and it won't set the hight properly.
+ // So render it first, set the editor, then hide.
+ if ( ! this.hasCustomReduce()) {
+ $(".control-group.reduce-function").hide();
+ }
+ }
+ });
+
+ Views.Sidebar = FauxtonAPI.View.extend({
+ template: "templates/documents/sidebar",
+ events: {
+ "click a.new#index": "newIndex"
+ },
+
+ initialize: function(options) {
+ if (options.ddocInfo) {
+ this.ddocID = options.ddocInfo.id;
+ this.currView = options.ddocInfo.currView;
+ }
+ },
+
+ establish: function() {
+ if (this.collection) {
+ return [this.collection.fetch()];
+ } else {
+ return null;
+ }
+ },
+
+ serialize: function() {
+ return {
+ index: [1,2,3],
+ view: [1,2],
+ database: this.collection.database
+ };
+ },
+
+ newIndex: function(event){
+ event.preventDefault();
+ $.contribute(
+ 'Create a new view.',
+ 'app/addons/documents/views.js'
+ );
+ },
+
+ toggleView: function(event){
+ event.preventDefault();
+ $.contribute(
+ 'Filter data by type or view',
+ 'app/addons/databases/views.js'
+ );
+ url = event.currentTarget.href.split('#')[1];
+ app.router.navigate(url);
+ },
+
+ buildIndexList: function(collection, selector, design){
+
+ _.each(_.keys(collection), function(key){
+ var selected = this.ddocID == "_design/"+design;
+ this.insertView("ul.nav." + selector, new Views.IndexItem({
+ ddoc: design,
+ index: key,
+ database: this.collection.database.id,
+ selected: selected && key == this.currView
+ }));
+ }, this);
+ },
+
+ beforeRender: function(manage) {
+ this.collection.each(function(design) {
+ if (design.has('doc')){
+ var ddoc = design.id.split('/')[1];
+ if (design.get('doc').views){
+ this.buildIndexList(design.get('doc').views, "views", ddoc);
+ }
+ }
+ }, this);
+ }
+
+ });
+
+ Views.Indexed = FauxtonAPI.View.extend({});
+
+ Views.Changes = FauxtonAPI.View.extend({
+ template: "templates/documents/changes",
+
+ establish: function() {
+ return [
+ this.model.changes.fetch()
+ ];
+ },
+
+ serialize: function () {
+ return {
+ changes: this.model.changes.toJSON(),
+ database: this.model
+ };
+ }
+
+ });
+
+
+ return Views;
+});
diff --git a/src/fauxton/app/modules/fauxton/base.js b/src/fauxton/app/modules/fauxton/base.js
new file mode 100644
index 000000000..a8bf43051
--- /dev/null
+++ b/src/fauxton/app/modules/fauxton/base.js
@@ -0,0 +1,124 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+
+ // Libs
+ "backbone"
+
+],
+
+function(app, Backbone) {
+ var Fauxton = app.module();
+
+ Fauxton.Breadcrumbs = Backbone.View.extend({
+ template: "templates/fauxton/breadcrumbs",
+
+ serialize: function() {
+ var crumbs = _.clone(this.crumbs);
+ return {
+ crumbs: crumbs
+ };
+ },
+
+ initialize: function(options) {
+ this.crumbs = options.crumbs;
+ }
+ });
+
+ Fauxton.NavBar = Backbone.View.extend({
+ template: "templates/fauxton/nav_bar",
+ // TODO: can we generate this list from the router?
+ navLinks: [
+ {href:"#/_all_dbs", title:"Databases"}
+ ],
+
+ initialize: function() {
+ this.on("link:add", this.render, this);
+ },
+
+ serialize: function() {
+ return {navLinks: this.navLinks};
+ },
+
+ addLink: function(link) {
+ if (link.top){
+ this.navLinks.unshift(link);
+ } else {
+ this.navLinks.push(link);
+ }
+ this.trigger("link:add");
+ this.render();
+ }
+
+ // TODO: ADD ACTIVE CLASS
+ });
+
+ Fauxton.ApiBar = Backbone.View.extend({
+ template: "templates/fauxton/api_bar",
+ endpoint: '_all_docs',
+
+ serialize: function() {
+ return {endpoint: this.endpoint};
+ },
+
+ update: function(endpoint) {
+ // Take endpoint and write it into the api bar.
+ console.log('ApiBar endpoint: ' + endpoint);
+ this.endpoint = endpoint;
+ this.render();
+ }
+
+ });
+
+ Fauxton.Notification = Backbone.View.extend({
+ template: "templates/fauxton/notification",
+ fadeTimer: 5000,
+
+ initialize: function(options) {
+ this.msg = options.msg;
+ this.type = options.type || "info";
+ this.selector = options.selector;
+ this.fade = options.fade === undefined ? true : options.fade;
+ this.clear = options.clear;
+ },
+
+ serialize: function() {
+ return {
+ msg: this.msg,
+ type: this.type
+ };
+ },
+
+ delayedFade: function() {
+ var that = this;
+ if (this.fade) {
+ setTimeout(function() {
+ that.$el.fadeOut();
+ }, this.fadeTimer);
+ }
+ },
+
+ renderNotification: function(selector) {
+ selector = selector || this.selector;
+ if (this.clear) {
+ $(selector).html('');
+ }
+ this.render().view.$el.appendTo(selector);
+ this.delayedFade();
+ return this;
+ }
+ });
+
+ return Fauxton;
+});
diff --git a/src/fauxton/app/modules/fauxton/layout.js b/src/fauxton/app/modules/fauxton/layout.js
new file mode 100644
index 000000000..d964b7b75
--- /dev/null
+++ b/src/fauxton/app/modules/fauxton/layout.js
@@ -0,0 +1,96 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define(["backbone"],
+
+function(Backbone) {
+
+ // A wrapper of the main Backbone.layoutmanager
+ // Allows the main layout of the page to be changed by any plugin.
+ // Exposes the different views:
+ // navBar -> the top navigation bar
+ // dashboardContent -> Main display view
+ // breadcrumbs -> Breadcrumbs navigation section
+ var Layout = function (navBar, apiBar) {
+ this.navBar = navBar;
+ this.apiBar = apiBar;
+
+ this.layout = new Backbone.Layout({
+ template: "templates/layouts/with_sidebar",
+
+ views: {
+ "#primary-navbar": this.navBar,
+ "#api-navbar": this.apiBar
+ }
+ });
+
+ this.layoutViews = {};
+ this.hooks = {};
+
+ this.el = this.layout.el;
+ };
+
+ // creatings the dashboard object same way backbone does
+ _.extend(Layout.prototype, {
+
+ render: function () {
+ return this.layout.render();
+ },
+
+ setTemplate: function(template) {
+ if (template.prefix){
+ this.layout.template = template.prefix + template.name;
+ } else{
+ this.layout.template = "templates/layouts/" + template;
+ }
+ // If we're changing layouts all bets are off, so kill off all the
+ // existing views in the layout.
+ _.each(this.layoutViews, function(view){view.remove();});
+ this.layoutViews = {};
+ this.render();
+ },
+
+ setTabs: function(view){
+ // TODO: Not sure I like this - seems fragile/repetitive
+ this.tabs = this.layout.setView("#tabs", view);
+ this.tabs.render();
+ },
+
+ setBreadcrumbs: function(view) {
+ this.breadcrumbs = this.layout.setView("#breadcrumbs", view);
+ this.breadcrumbs.render();
+ },
+
+ clearBreadcrumbs: function () {
+ if (!this.breadcrumbs) {return ;}
+
+ this.breadcrumbs.remove();
+ },
+
+ setView: function(selector, view) {
+ this.layoutViews[selector] = this.layout.setView(selector, view, false);
+ },
+
+ renderView: function(selector) {
+ var view = this.layoutViews[selector];
+ if (!view) {
+ return false;
+ } else {
+ return view.render();
+ }
+ }
+
+ });
+
+ return Layout;
+
+});
diff --git a/src/fauxton/app/modules/pouchdb/base.js b/src/fauxton/app/modules/pouchdb/base.js
new file mode 100644
index 000000000..2b7cfc927
--- /dev/null
+++ b/src/fauxton/app/modules/pouchdb/base.js
@@ -0,0 +1,57 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+
+/*
+ * NOTE:
+ * This temporarily uses the PouchDB map reduce implementation
+ * These files are modified locally until we make a more general version and
+ * push it back upstream.
+ */
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "modules/pouchdb/pouchdb.mapreduce.js"
+],
+
+function(app, FauxtonAPI, MapReduce) {
+ var Pouch = {};
+ Pouch.MapReduce = MapReduce;
+
+ Pouch.runViewQuery = function(fun, docs) {
+ docs = [
+ {_id: 'test_doc_1', foo: 'bar-1'},
+ {_id: 'test_doc_2', foo: 'bar-2'},
+ {_id: 'test_doc_3', foo: 'bar-3'},
+ {_id: 'test_doc_4', foo: 'bar-4'},
+ {_id: 'test_doc_5', foo: 'bar-5'},
+ {_id: 'test_doc_6', foo: 'bar-6'},
+ {_id: 'test_doc_7', foo: 'bar-7'},
+ {_id: 'test_doc_8', foo: 'bar-8'},
+ {_id: 'test_doc_9', foo: 'bar-9'},
+ {_id: 'test_doc_10', foo: 'bar-10'}
+ ];
+
+ var deferred = FauxtonAPI.Deferred();
+ var complete = function(resp) {
+ console.log("COMPLETE TRIGGERED", arguments);
+ };
+
+ return Pouch.MapReduce.query(fun, {docs: docs, complete:complete});
+ };
+
+ return Pouch;
+});
diff --git a/src/fauxton/app/modules/pouchdb/pouch.collate.js b/src/fauxton/app/modules/pouchdb/pouch.collate.js
new file mode 100644
index 000000000..7cc5f9cfe
--- /dev/null
+++ b/src/fauxton/app/modules/pouchdb/pouch.collate.js
@@ -0,0 +1,115 @@
+/*
+ * NOTE:
+ * This temporarily uses the PouchDB map reduce implementation
+ * These files are modified locally until we make a more general version and
+ * push it back upstream.
+ * Original file:
+ * https://github.com/daleharvey/pouchdb/blob/master/src/pouch.collate.js
+ */
+
+/*
+(function() {
+ // a few hacks to get things in the right place for node.js
+ if (typeof module !== 'undefined' && module.exports) {
+ module.exports = Pouch;
+ }
+*/
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "modules/pouchdb/pouch.collate.js"
+],
+
+function(app, FauxtonAPI, Collate) {
+ var Pouch = {};
+
+ Pouch.collate = function(a, b) {
+ var ai = collationIndex(a);
+ var bi = collationIndex(b);
+ if ((ai - bi) !== 0) {
+ return ai - bi;
+ }
+ if (a === null) {
+ return 0;
+ }
+ if (typeof a === 'number') {
+ return a - b;
+ }
+ if (typeof a === 'boolean') {
+ return a < b ? -1 : 1;
+ }
+ if (typeof a === 'string') {
+ return stringCollate(a, b);
+ }
+ if (Array.isArray(a)) {
+ return arrayCollate(a, b);
+ }
+ if (typeof a === 'object') {
+ return objectCollate(a, b);
+ }
+ };
+
+ var stringCollate = function(a, b) {
+ // See: https://github.com/daleharvey/pouchdb/issues/40
+ // This is incompatible with the CouchDB implementation, but its the
+ // best we can do for now
+ return (a === b) ? 0 : ((a > b) ? 1 : -1);
+ };
+
+ var objectCollate = function(a, b) {
+ var ak = Object.keys(a), bk = Object.keys(b);
+ var len = Math.min(ak.length, bk.length);
+ for (var i = 0; i < len; i++) {
+ // First sort the keys
+ var sort = Pouch.collate(ak[i], bk[i]);
+ if (sort !== 0) {
+ return sort;
+ }
+ // if the keys are equal sort the values
+ sort = Pouch.collate(a[ak[i]], b[bk[i]]);
+ if (sort !== 0) {
+ return sort;
+ }
+
+ }
+ return (ak.length === bk.length) ? 0 :
+ (ak.length > bk.length) ? 1 : -1;
+ };
+
+ var arrayCollate = function(a, b) {
+ var len = Math.min(a.length, b.length);
+ for (var i = 0; i < len; i++) {
+ var sort = Pouch.collate(a[i], b[i]);
+ if (sort !== 0) {
+ return sort;
+ }
+ }
+ return (a.length === b.length) ? 0 :
+ (a.length > b.length) ? 1 : -1;
+ };
+
+ // The collation is defined by erlangs ordered terms
+ // the atoms null, true, false come first, then numbers, strings,
+ // arrays, then objects
+ var collationIndex = function(x) {
+ var id = ['boolean', 'number', 'string', 'object'];
+ if (id.indexOf(typeof x) !== -1) {
+ if (x === null) {
+ return 1;
+ }
+ return id.indexOf(typeof x) + 2;
+ }
+ if (Array.isArray(x)) {
+ return 4.5;
+ }
+ };
+
+ return Pouch;
+
+//}).call(this);
+
+});
diff --git a/src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js b/src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js
new file mode 100644
index 000000000..b97eb7fc3
--- /dev/null
+++ b/src/fauxton/app/modules/pouchdb/pouchdb.mapreduce.js
@@ -0,0 +1,286 @@
+/*
+ * NOTE:
+ * This temporarily uses the PouchDB map reduce implementation
+ * These files are modified locally until we make a more general version and
+ * push it back upstream.
+ * Original file:
+ * https://github.com/daleharvey/pouchdb/blob/master/src/plugins/pouchdb.mapreduce.js
+ */
+
+/*global Pouch: true */
+
+//"use strict";
+
+// This is the first implementation of a basic plugin, we register the
+// plugin object with pouch and it is mixin'd to each database created
+// (regardless of adapter), adapters can override plugins by providing
+// their own implementation. functions on the plugin object that start
+// with _ are reserved function that are called by pouchdb for special
+// notifications.
+
+// If we wanted to store incremental views we can do it here by listening
+// to the changes feed (keeping track of our last update_seq between page loads)
+// and storing the result of the map function (possibly using the upcoming
+// extracted adapter functions)
+
+define([
+ "app",
+
+ "api",
+
+ // Modules
+ "modules/pouchdb/pouch.collate.js"
+],
+
+function(app, FauxtonAPI, Collate) {
+ var Pouch = {};
+ Pouch.collate = Collate.collate;
+
+ //var MapReduce = function(db) {
+ var MapReduce = function() {
+
+ function viewQuery(fun, options) {
+ console.log("IN VIEW QUERY");
+ if (!options.complete) {
+ return;
+ }
+
+ function sum(values) {
+ return values.reduce(function(a, b) { return a + b; }, 0);
+ }
+
+ var results = [];
+ var current = null;
+ var num_started= 0;
+ var completed= false;
+
+ var emit = function(key, val) {
+ console.log("IN EMIT: ", key, val, current);
+ var viewRow = {
+ id: current.doc._id,
+ key: key,
+ value: val
+ };
+ console.log("VIEW ROW: ", viewRow);
+
+ if (options.startkey && Pouch.collate(key, options.startkey) < 0) return;
+ if (options.endkey && Pouch.collate(key, options.endkey) > 0) return;
+ if (options.key && Pouch.collate(key, options.key) !== 0) return;
+ num_started++;
+ if (options.include_docs) {
+ // TODO:: FIX
+ throw({error: "Include Docs not supported"});
+ /*
+
+ //in this special case, join on _id (issue #106)
+ if (val && typeof val === 'object' && val._id){
+ db.get(val._id,
+ function(_, joined_doc){
+ if (joined_doc) {
+ viewRow.doc = joined_doc;
+ }
+ results.push(viewRow);
+ checkComplete();
+ });
+ return;
+ } else {
+ viewRow.doc = current.doc;
+ }
+ */
+ }
+ console.log("EMITTING: ", viewRow);
+ results.push(viewRow);
+ };
+
+ // ugly way to make sure references to 'emit' in map/reduce bind to the
+ // above emit
+ eval('fun.map = ' + fun.map.toString() + ';');
+ if (fun.reduce) {
+ eval('fun.reduce = ' + fun.reduce.toString() + ';');
+ }
+
+ // exclude _conflicts key by default
+ // or to use options.conflicts if it's set when called by db.query
+ var conflicts = ('conflicts' in options ? options.conflicts : false);
+
+ //only proceed once all documents are mapped and joined
+ var checkComplete= function(){
+ if (completed && results.length == num_started){
+ results.sort(function(a, b) {
+ return Pouch.collate(a.key, b.key);
+ });
+ if (options.descending) {
+ results.reverse();
+ }
+ if (options.reduce === false) {
+ return options.complete(null, {rows: results});
+ }
+
+ var groups = [];
+ results.forEach(function(e) {
+ var last = groups[groups.length-1] || null;
+ if (last && Pouch.collate(last.key[0][0], e.key) === 0) {
+ last.key.push([e.key, e.id]);
+ last.value.push(e.value);
+ return;
+ }
+ groups.push({key: [[e.key, e.id]], value: [e.value]});
+ });
+ groups.forEach(function(e) {
+ e.value = fun.reduce(e.key, e.value) || null;
+ e.key = e.key[0][0];
+ });
+ options.complete(null, {rows: groups});
+ }
+ };
+
+ if (options.docs) {
+ console.log("RUNNING MR ON DOCS: ", options.docs);
+ _.each(options.docs, function(doc) {
+ current = {doc: doc};
+ fun.map.call(this, doc);
+ }, this);
+ return options.complete(null, {rows: results});
+ } else {
+ console.log("COULD NOT FIND DOCS");
+ return false;
+ }
+
+ /*
+ db.changes({
+ conflicts: conflicts,
+ include_docs: true,
+ onChange: function(doc) {
+ if (!('deleted' in doc)) {
+ current = {doc: doc.doc};
+ fun.map.call(this, doc.doc);
+ }
+ },
+ complete: function() {
+ completed= true;
+ checkComplete();
+ }
+ });
+ */
+ }
+
+ /*
+ function httpQuery(fun, opts, callback) {
+
+ // List of parameters to add to the PUT request
+ var params = [];
+ var body = undefined;
+ var method = 'GET';
+
+ // If opts.reduce exists and is defined, then add it to the list
+ // of parameters.
+ // If reduce=false then the results are that of only the map function
+ // not the final result of map and reduce.
+ if (typeof opts.reduce !== 'undefined') {
+ params.push('reduce=' + opts.reduce);
+ }
+ if (typeof opts.include_docs !== 'undefined') {
+ params.push('include_docs=' + opts.include_docs);
+ }
+ if (typeof opts.limit !== 'undefined') {
+ params.push('limit=' + opts.limit);
+ }
+ if (typeof opts.descending !== 'undefined') {
+ params.push('descending=' + opts.descending);
+ }
+ if (typeof opts.startkey !== 'undefined') {
+ params.push('startkey=' + encodeURIComponent(JSON.stringify(opts.startkey)));
+ }
+ if (typeof opts.endkey !== 'undefined') {
+ params.push('endkey=' + encodeURIComponent(JSON.stringify(opts.endkey)));
+ }
+ if (typeof opts.key !== 'undefined') {
+ params.push('key=' + encodeURIComponent(JSON.stringify(opts.key)));
+ }
+
+ // If keys are supplied, issue a POST request to circumvent GET query string limits
+ // see http://wiki.apache.org/couchdb/HTTP_view_API#Querying_Options
+ if (typeof opts.keys !== 'undefined') {
+ method = 'POST';
+ body = JSON.stringify({keys:opts.keys});
+ }
+
+ // Format the list of parameters into a valid URI query string
+ params = params.join('&');
+ params = params === '' ? '' : '?' + params;
+
+ // We are referencing a query defined in the design doc
+ if (typeof fun === 'string') {
+ var parts = fun.split('/');
+ db.request({
+ method: method,
+ url: '_design/' + parts[0] + '/_view/' + parts[1] + params,
+ body: body
+ }, callback);
+ return;
+ }
+
+ // We are using a temporary view, terrible for performance but good for testing
+ var queryObject = JSON.parse(JSON.stringify(fun, function(key, val) {
+ if (typeof val === 'function') {
+ return val + ''; // implicitly `toString` it
+ }
+ return val;
+ }));
+
+ db.request({
+ method:'POST',
+ url: '_temp_view' + params,
+ body: queryObject
+ }, callback);
+ }
+ */
+
+ function query(fun, opts, callback) {
+ if (typeof opts === 'function') {
+ callback = opts;
+ opts = {};
+ }
+
+ if (callback) {
+ opts.complete = callback;
+ }
+
+ /*
+ if (db.type() === 'http') {
+ return httpQuery(fun, opts, callback);
+ }
+ */
+
+ if (typeof fun === 'object') {
+ console.log("RUNNING VIEW QUERY", fun, opts, arguments);
+ return viewQuery(fun, opts);
+ }
+
+ throw({error: "Shouldn't have gotten here"});
+
+ /*
+ var parts = fun.split('/');
+ db.get('_design/' + parts[0], function(err, doc) {
+ if (err) {
+ if (callback) callback(err);
+ return;
+ }
+ viewQuery({
+ map: doc.views[parts[1]].map,
+ reduce: doc.views[parts[1]].reduce
+ }, opts);
+ });
+ */
+ }
+
+ return {'query': query};
+ };
+
+ // Deletion is a noop since we dont store the results of the view
+ MapReduce._delete = function() { };
+
+ //Pouch.plugin('mapreduce', MapReduce);
+
+ return MapReduce();
+});
diff --git a/src/fauxton/app/router.js b/src/fauxton/app/router.js
new file mode 100644
index 000000000..2e994e547
--- /dev/null
+++ b/src/fauxton/app/router.js
@@ -0,0 +1,160 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ // Load require for use in nested requiring
+ // as per the note in: http://requirejs.org/docs/api.html#multiversion
+ "require",
+
+ // Application.
+ "app",
+
+ // Initialize application
+ "initialize",
+
+ // Load Fauxton API
+ "api",
+
+ // Modules
+ "modules/fauxton/base",
+ // Layout
+ "modules/fauxton/layout",
+
+ // Routes return the module that they define routes for
+ "modules/databases/base",
+ "modules/documents/base",
+ "modules/pouchdb/base",
+
+
+ // this needs to be added as a plugin later
+ // "modules/logs/base",
+ // "modules/config/base",
+
+ "load_addons"
+],
+
+function(req, app, Initialize, FauxtonAPI, Fauxton, Layout, Databases, Documents, Pouch, LoadAddons) {
+
+ var defaultLayout = 'with_sidebar';
+ // TODO: auto generate this list if possible
+ var modules = [Databases, Documents];
+
+ var generateRoute = function(settingsGenerator, route) {
+ return function() {
+ var boundRoute = route;
+ var settings = settingsGenerator.apply(null, arguments);
+ var layout = settings.layout || defaultLayout;
+ var establish = settings.establish || function() { return null; };
+ var masterLayout = this.masterLayout;
+
+ console.log("Settings generator for: ", layout, settings);
+
+ masterLayout.setTemplate(layout);
+ masterLayout.clearBreadcrumbs();
+
+ if (settings.crumbs) {
+ masterLayout.setBreadcrumbs(new Fauxton.Breadcrumbs({
+ crumbs: settings.crumbs
+ }));
+ }
+
+ $.when.apply(null, establish()).done(function(resp) {
+ _.each(settings.views, function(view, selector) {
+ masterLayout.setView(selector, view);
+
+ $.when.apply(null, view.establish()).then(function(resp) {
+ masterLayout.renderView(selector);
+ }, function(resp) {
+ view.establishError = {
+ error: true,
+ reason: resp
+ };
+ masterLayout.renderView(selector);
+ });
+
+ var hooks = masterLayout.hooks[selector];
+
+ if(hooks){
+ _.each(hooks, function(hook){
+ if (_.any(hook.routes, function(route){return route == boundRoute;})){
+ hook.callback(view);
+ }
+ });
+ }
+ });
+ });
+
+ if (settings.apiUrl) this.apiBar.update(settings.apiUrl);
+ };
+ };
+
+ var Router = app.router = Backbone.Router.extend({
+ routes: {},
+
+ // These moduleRoutes functions are aguably better outside but
+ // need access to the Router instance which is not created in this
+ // module
+ addModuleRoute: function(generator, route) {
+ this.route(route, route.toString(), generateRoute(generator, route));
+ },
+
+ setModuleRoutes: function() {
+ _.each(modules, function(module) {
+ if (module){
+ _.each(module.Routes, this.addModuleRoute, this);
+ }
+ }, this);
+ _.each(LoadAddons.addons, function(module) {
+ if (module){
+ module.initialize();
+ // This is pure routes the addon provides
+ if (module.Routes) {
+ _.each(module.Routes, this.addModuleRoute, this);
+ }
+ }
+ }, this);
+ },
+
+ setAddonHooks: function() {
+ _.each(LoadAddons.addons, function(module) {
+ // This is updates to views by the addon
+ if (module && module.hooks){
+ _.each(module.hooks, function(callback, route){
+ if (this.masterLayout.hooks[route]) {
+ this.masterLayout.hooks[route].push(callback);
+ } else {
+ this.masterLayout.hooks[route] = [callback];
+ }
+ }, this);
+ }
+ }, this);
+ },
+
+ initialize: function() {
+ this.navBar = app.navBar = new Fauxton.NavBar();
+ this.apiBar = app.apiBar = new Fauxton.ApiBar();
+
+ app.masterLayout = this.masterLayout = new Layout(this.navBar, this.apiBar);
+
+ // NOTE: This must be below creation of the layout
+ // FauxtonAPI header links and others depend on existence of the layout
+ this.setAddonHooks();
+ this.setModuleRoutes();
+
+ $("#app-container").html(this.masterLayout.el);
+ this.masterLayout.render();
+ }
+ });
+
+ return Router;
+
+});
diff --git a/src/fauxton/app/templates/databases/item.html b/src/fauxton/app/templates/databases/item.html
new file mode 100644
index 000000000..5ffcefdcc
--- /dev/null
+++ b/src/fauxton/app/templates/databases/item.html
@@ -0,0 +1,25 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<td width="10">
+ <label class="checkbox">
+ <input type="checkbox" class="row-select">
+ </label>
+</td>
+<td>
+ <a href="#/database/<%= database.get("name") %>/_all_docs?limit=100"><%= database.get("name") %></a>
+</td>
+<td><%= database.status.humanSize() %></td>
+<td><%= database.status.numDocs() %></td>
+<td><%= database.status.updateSeq() %></td>
diff --git a/src/fauxton/app/templates/databases/list.html b/src/fauxton/app/templates/databases/list.html
new file mode 100644
index 000000000..e07dcfd15
--- /dev/null
+++ b/src/fauxton/app/templates/databases/list.html
@@ -0,0 +1,35 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div class="result-tools" style="">
+ <div class="btn-toolbar pull-left">
+ <button type="button" class="btn all" data-toggle="button">✓ All</button>
+ <button class="btn btn-small disabled bulk-delete"><i class="icon-trash"></i></button>
+ </div>
+
+ <form class="navbar-form pull-right database-search">
+ <input type="text" class="search-query" placeholder="Search by database name">
+ </form>
+</div>
+<table class="databases table table-striped">
+ <thead>
+ <th></th>
+ <th>Name</th>
+ <th>Size</th>
+ <th>Number of Documents</th>
+ <th>Update Seq</th>
+ </thead>
+ <tbody>
+ </tbody>
+</table>
diff --git a/src/fauxton/app/templates/databases/sidebar.html b/src/fauxton/app/templates/databases/sidebar.html
new file mode 100644
index 000000000..1b78aba24
--- /dev/null
+++ b/src/fauxton/app/templates/databases/sidebar.html
@@ -0,0 +1,27 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div class="row-fluid">
+<a class="btn new" id="new"><i class="icon-plus"></i> New database</a>
+</div>
+<hr>
+<ul class="nav nav-list">
+ <!-- <li class="nav-header">Database types</li> -->
+ <li class="active"><a class="toggle-view" id="owned">Your databases</a></li>
+</ul>
+<hr>
+<div class="well">
+ <p>Here be news.</p>
+</div>
+<a href="http://couchdb.org" target="_blank"><img src="img/couchdblogo.png"/></a>
diff --git a/src/fauxton/app/templates/documents/all_docs_item.html b/src/fauxton/app/templates/documents/all_docs_item.html
new file mode 100644
index 000000000..c4c075409
--- /dev/null
+++ b/src/fauxton/app/templates/documents/all_docs_item.html
@@ -0,0 +1,26 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<td class="select"><input type="checkbox" class="row-select"></td>
+<td>
+ <div>
+ <pre class="prettyprint"><%= doc.prettyJSON() %></pre>
+ <% if (doc.isEditable()) { %>
+ <div class="btn-group">
+ <a href="#<%= doc.url('app') %>" class="btn btn-small edits">Edit <%= doc.docType() %></a>
+ <button href="#" class="btn btn-small btn-danger delete" title="Delete this document."><i class="icon icon-trash"></i></button>
+ </div>
+ <% } %>
+ </div>
+</td>
diff --git a/src/fauxton/app/templates/documents/all_docs_list.html b/src/fauxton/app/templates/documents/all_docs_list.html
new file mode 100644
index 000000000..2f63af038
--- /dev/null
+++ b/src/fauxton/app/templates/documents/all_docs_list.html
@@ -0,0 +1,141 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div class="view show">
+ <% if (!viewList) { %>
+ <div class="row">
+ <div class="btn-toolbar span6">
+ <button type="button" class="btn all" data-toggle="button">✓ All</button>
+ <button class="btn btn-small disabled bulk-delete"><i class="icon-trash"></i></button>
+ </div>
+ <!-- TODO::REENABLE
+ <div class="btn-toolbar pull-right">
+ <a href="#new-view-index" class="btn btn-small toggle-edit disabled"><i class="icon-wrench"></i> Edit index</a>
+ <a href="#params" class="btn btn-small toggle-params"><i class="icon-plus"></i> API preview</a>
+ </div>
+ -->
+ </div>
+ <% } %>
+
+ <div class="row">
+ <div class="all-docs-list errors-container"></div>
+ <div id="edit-index-container"></div>
+ <% if (viewList) { %>
+ <div class="accordion" id="advanced-options-accordion">
+ <div class="accordion-group">
+ <div class="accordion-heading">
+ <a class="accordion-toggle" data-bypass="true" data-toggle="collapse" data-parent="#advanced-options-accordion" href="#collapse-advanced-options">
+ <i class="icon-plus"></i> Advanced Options
+ </a>
+ </div>
+ <div id="collapse-advanced-options" class="accordion-body collapse">
+ <div class="accordion-inner">
+ <form class="view-query-update">
+ <div class="controls controls-row">
+ <label class="span3 inline">
+ Limit:
+ <select name="limit" class="input-small">
+ <option>5</option>
+ <option selected="selected">10</option>
+ <option>25</option>
+ <option>50</option>
+ <option>100</option>
+ </select>
+ </label>
+ <label class="span3 checkbox inline">
+ <input name="include_docs" type="checkbox" value="true"> Include Docs
+ </label>
+ <% if (hasReduce) { %>
+ <label class="span2 checkbox inline">
+ <input name="reduce" type="checkbox" value="true"> Reduce
+ </label>
+ <label class="span4 inline">
+ Group Level:
+ <select disabled name="group_level" class="input-small">
+ <option value="0">None</option>
+ <option value="1">1</option>
+ <option value="2">2</option>
+ <option value="3">3</option>
+ <option value="4">4</option>
+ <option value="5">5</option>
+ <option value="6">6</option>
+ <option value="7">7</option>
+ <option value="8">8</option>
+ <option value="9">9</option>
+ <option value="999" selected="selected">exact</option>
+ </select>
+ </label>
+ <% } %>
+ </div>
+
+ <div class="controls controls-row">
+ <input name="key" class="span4" type="text" placeholder="Key">
+ <input name="keys" class="span8" type="text" placeholder="Keys">
+ </div>
+ <div class="controls controls-row">
+ <input name="startkey" class="span6" type="text" placeholder="Start Key">
+ <input name="endkey" class="span6" type="text" placeholder="End Key">
+ </div>
+ <div class="controls controls-row">
+ <label class="span2 checkbox inline">
+ <input name="stale" type="checkbox" value="ok"> Stale
+ </label>
+ <label class="span2 checkbox inline">
+ <input name="descending" type="checkbox" value="true"> Descending
+ </label>
+ <label class="span4 checkbox inline">
+ <input name="inclusive_end" type="checkbox" value="false"> Disable Inclusive End
+ </label>
+ <label class="span4 checkbox inline">
+ <input name="update_seq" type="checkbox" value="true"> Include Update Sequence
+ </label>
+ </div>
+ <div class="controls controls-row">
+ <button type="submit" class="btn btn-primary">Query</button>
+ </div>
+ </form>
+
+ </div>
+ </div>
+
+ </div>
+ </div>
+ <% } %>
+ </div>
+
+
+ <p>
+ Showing 1-<%= database.models.length %> of <%= database.totalRows() %> rows
+ <% if (database.updateSeq()) { %>
+ -- Update Sequence: <%= database.updateSeq() %>
+ <% } %>
+ </p>
+ <table class="all-docs table table-striped table-condensed">
+ <tbody></tbody>
+ </table>
+ <!--
+ <div class="pagination pagination-centered">
+ <ul>
+ <li class="disabled"><a href="#">&laquo;</a></li>
+ <li class="active"><a href="#">1</a></li>
+ <li><a href="#">2</a></li>
+ <li><a href="#">3</a></li>
+ <li><a href="#">4</a></li>
+ <li><a href="#">5</a></li>
+ <li><a href="#">&raquo;</a></li>
+ </ul>
+ </div>
+ -->
+
+</div>
diff --git a/src/fauxton/app/templates/documents/changes.html b/src/fauxton/app/templates/documents/changes.html
new file mode 100644
index 000000000..3e5009c0f
--- /dev/null
+++ b/src/fauxton/app/templates/documents/changes.html
@@ -0,0 +1,36 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<table class="table">
+ <thead>
+ <th> seq </th>
+ <th> id </th>
+ <th> changes </th>
+ <th> deleted? </th>
+ </thead>
+ <tbody>
+ <% _.each(changes, function (change) { %>
+ <tr>
+ <td> <%= change.seq %> </td>
+ <% if (change.deleted) { %>
+ <td> <%= change.id %> </td>
+ <% } else { %>
+ <td> <a href="#<%= database.url('app') %>/<%= change.id %>"><%= change.id %></a> </td>
+ <% } %>
+ <td> <%= JSON.stringify(change.changes) %> </td>
+ <td><%= change.deleted ? "true" : "false" %></td>
+ </tr>
+ <% }); %>
+ </tbody>
+</table>
diff --git a/src/fauxton/app/templates/documents/doc.html b/src/fauxton/app/templates/documents/doc.html
new file mode 100644
index 000000000..d66229420
--- /dev/null
+++ b/src/fauxton/app/templates/documents/doc.html
@@ -0,0 +1,28 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div id="doc">
+ <div class="page-header">
+ <h2>Doc: <%= doc.id %></h2>
+ </div>
+
+ <div class="errors-container"></div>
+
+ <textarea class="doc-code"><%= JSON.stringify(doc.attributes, null, " ") %></textarea>
+ <br />
+ <p>
+ <button class="save-doc btn btn-large btn-primary" type="button">Save</button>
+ </p>
+
+</div>
diff --git a/src/fauxton/app/templates/documents/doc_field_editor.html b/src/fauxton/app/templates/documents/doc_field_editor.html
new file mode 100644
index 000000000..494e72121
--- /dev/null
+++ b/src/fauxton/app/templates/documents/doc_field_editor.html
@@ -0,0 +1,60 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div id="doc-field-editor">
+ <div class="tools">
+
+ <div class="btn-toolbar pull-left">
+ <button class="btn btn-small all">&#x2713; All</button>
+ <button class="btn btn-small disabled delete"><i class="icon-trash"></i> Delete field</button>
+ <button class="btn btn-small new" style="margin-left: 64px"><i class="icon-plus"></i> New field</button>
+ </div>
+ <div class="btn-toolbar pull-right">
+ <button class="btn btn-small cancel">Cancel</button>
+ <button class="btn btn-small save">Save</button>
+ </div>
+ </div>
+
+ <div class="clearfix"></div>
+ <!-- <hr style="margin-top: 0"/> -->
+
+ <table class="table table-striped table-condensed">
+ <thead>
+ <tr>
+ <th class="select">
+ </th>
+ <th>Key</th>
+ <th>Value</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr style="display:none">
+ <td class="select"><input type="checkbox" /></td>
+ <td class="key"><input type="text" class="input-large" value='' /></td>
+ <td class="value"><input type="text" class="input-xxlarge" value='' /></td>
+ </tr>
+ <% _.each(doc.attributes, function(value, key) { %>
+ <tr>
+ <td class="select"><input type="checkbox" /></td>
+ <td class="key">
+ <input type="text" class="input-large" name="doc[<%= key %>]" value="<%= key %>" />
+ </td>
+ <td class="value"><input type="text" class="input-xxlarge" value='<%= JSON.stringify(value) %>' /></td>
+ </tr>
+ <% }); %>
+ </tbody>
+ </table>
+ <a class="btn btn-small new" style="margin-left: 64px"><i class="icon-plus"></i> New field</a>
+
+</div>
diff --git a/src/fauxton/app/templates/documents/doc_field_editor_tabs.html b/src/fauxton/app/templates/documents/doc_field_editor_tabs.html
new file mode 100644
index 000000000..bb16d7384
--- /dev/null
+++ b/src/fauxton/app/templates/documents/doc_field_editor_tabs.html
@@ -0,0 +1,26 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<ul class="nav nav-tabs">
+ <li class="<%= isSelectedClass('field_editor') %>"><a href="#<%= doc.url('app') %>/field_editor">Doc fields</a></li>
+ <li class="<%= isSelectedClass('code_editor') %>"><a href="#<%= doc.url('app') %>/code_editor"><i class="icon-pencil"></i> Code editor</a></li>
+ <ul class="nav pull-right" style="margin:5px 10px 0px 10px;">
+ <li>
+ <div class="btn-group">
+ <button class="btn btn-small duplicate"><i class="icon-repeat"></i> Duplicate document</button>
+ <button class="btn btn-small delete"><i class="icon-trash"></i> Delete document</button>
+ </div>
+ </li>
+ </ul>
+</ul>
diff --git a/src/fauxton/app/templates/documents/index_menu_item.html b/src/fauxton/app/templates/documents/index_menu_item.html
new file mode 100644
index 000000000..bb16b4fc3
--- /dev/null
+++ b/src/fauxton/app/templates/documents/index_menu_item.html
@@ -0,0 +1,17 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<a href="#database/<%= database %>/_design/<%= ddoc %>/_view/<%= index %>" class="toggle-view">
+ <i class="icon-list"></i> <%= ddoc %><span class="divider">/</span><%= index %>
+</a> \ No newline at end of file
diff --git a/src/fauxton/app/templates/documents/index_row_docular.html b/src/fauxton/app/templates/documents/index_row_docular.html
new file mode 100644
index 000000000..26c0280d9
--- /dev/null
+++ b/src/fauxton/app/templates/documents/index_row_docular.html
@@ -0,0 +1,26 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<td class="select"><input type="checkbox"></td>
+<td>
+ <div>
+ <pre class="prettyprint"><%= doc.prettyJSON() %></pre>
+ <% if (doc.isEditable()) { %>
+ <div class="btn-group">
+ <a href="#<%= doc.url('app') %>" class="btn btn-small edits">Edit <%= doc.docType() %></a>
+ <button href="#" class="btn btn-small btn-danger delete" title="Delete this document."><i class="icon icon-trash"></i></button>
+ </div>
+ <% } %>
+ </div>
+</td> \ No newline at end of file
diff --git a/src/fauxton/app/templates/documents/index_row_tabular.html b/src/fauxton/app/templates/documents/index_row_tabular.html
new file mode 100644
index 000000000..f52c48cb6
--- /dev/null
+++ b/src/fauxton/app/templates/documents/index_row_tabular.html
@@ -0,0 +1,25 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<td class="select"><input type="checkbox"></td>
+<td>
+ <div>
+ <pre class="prettyprint"><%= JSON.stringify(doc.get("key")) %></pre>
+ </div>
+</td>
+<td>
+ <div>
+ <pre class="prettyprint"><%= JSON.stringify(doc.get("value")) %></pre>
+ </div>
+</td> \ No newline at end of file
diff --git a/src/fauxton/app/templates/documents/search.html b/src/fauxton/app/templates/documents/search.html
new file mode 100644
index 000000000..bb8489110
--- /dev/null
+++ b/src/fauxton/app/templates/documents/search.html
@@ -0,0 +1,15 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<input id="searchbox" type="text" class="span12" placeholder="Search by doc id, view key or search index"> \ No newline at end of file
diff --git a/src/fauxton/app/templates/documents/sidebar.html b/src/fauxton/app/templates/documents/sidebar.html
new file mode 100644
index 000000000..7a0ecdf95
--- /dev/null
+++ b/src/fauxton/app/templates/documents/sidebar.html
@@ -0,0 +1,29 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div id="sidenav">
+ <a class="btn btn-small new" id="doc" href="#<%= database.url('app') %>/new"><i class="icon-file"></i> New doc</a>
+ <div class="btn-group" id="new-index">
+ <a class="btn btn-small" href="#<%= database.url('app') %>/new_view">New view</a>
+ </div>
+ <hr>
+ <ul class="nav nav-list">
+ <li class="active"><a id="all-docs" href="#<%= database.url('index') %>?limit=100" class="toggle-view"><i class="icon-list"></i> All documents</a></li>
+ <li><a id="design-docs" href='#<%= database.url("index") %>?limit=100&start_key="_design"&end_key="_e"' class="toggle-view"><i class="icon-list"></i> All design docs</a></li>
+ </ul>
+ <ul class="nav nav-list views">
+ <li class="nav-header">Secondary Indexes</li>
+ <li><a href="#<%= database.url('app') %>/new_view" class="new"><i class="icon-plus"></i> New</a></li>
+ </ul>
+</div>
diff --git a/src/fauxton/app/templates/documents/tabs.html b/src/fauxton/app/templates/documents/tabs.html
new file mode 100644
index 000000000..57e6cb180
--- /dev/null
+++ b/src/fauxton/app/templates/documents/tabs.html
@@ -0,0 +1,39 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<ul class="nav nav-tabs">
+ <li class="active"><a href="<%= db_url %>">Docs</a></li>
+ <!-- TODO::REENABLE
+ <li><a href="#">Permissions</a></li>
+ <li><a href="#">Stats</a></li>
+ -->
+ <li id="changes"><a href="<%= changes_url %>">Changes</a></li>
+ <!-- TODO::REENABLE
+ <div id="search" class="navbar-search span4 nav pull-right input-prepend" style="height:20px;"></div>
+ <!-- TODO: put this styling into less --//>
+ <ul class="nav pull-right" style="margin:5px 10px 0px 10px;">
+ <li>
+ <div class="btn-group">
+ <a class="btn btn-small dropdown-toggle" data-toggle="dropdown" href="#">
+ <i class="icon icon-cog"></i> Database actions <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a class=""><i class="icon-repeat"></i> Replicate database</a></li>
+ <li><a id="delete-database" class=""><i class="icon-trash"></i> Delete database</a></li>
+ </ul>
+ </div>
+ </li>
+ </ul>
+ -->
+</ul>
diff --git a/src/fauxton/app/templates/documents/view_editor.html b/src/fauxton/app/templates/documents/view_editor.html
new file mode 100644
index 000000000..4a8668e29
--- /dev/null
+++ b/src/fauxton/app/templates/documents/view_editor.html
@@ -0,0 +1,92 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div class="accordion" id="edit-index-accordion">
+ <div class="accordion-group">
+ <div class="accordion-heading">
+ <a class="accordion-toggle" data-bypass="true" data-toggle="collapse" data-parent="#edit-index-accordion" href="#collapse-edit-index">
+ <i class="icon-wrench"></i> Edit Index
+ </a>
+ </div>
+ <div id="collapse-edit-index" class="accordion-body collapse">
+ <div class="accordion-inner">
+ <div id="define-view" class="ddoc-alert well">
+ <div class="errors-container"></div>
+ <form class="form-horizontal">
+ <h3>Define your index</h3>
+ <div class="control-group">
+ <label class="control-label" for="ddoc">Design document <a target="_couch_docs" href="http://docs.couchdb.org/en/latest/ddocs/#design-docs"><i class="icon-question-sign"></i></a></label>
+ <div class="controls">
+ <select id="ddoc">
+ <optgroup label="Select a document">
+ <option>New document</option>
+ <% ddocs.each(function(ddoc) { %>
+ <% if (ddoc.id == "_design/"+viewCollection.design) { %>
+ <option selected="selected"><%= ddoc.id %></option>
+ <% } else { %>
+ <option><%= ddoc.id %></option>
+ <% } %>
+ <% }); %>
+ <option selected="selected">_design/views101</option>
+ </optgroup>
+ </select>
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label" for="index-name">Index name <a target="_couch_docs" href="http://docs.couchdb.org/en/latest/ddocs/#view-functions"><i class="icon-question-sign"></i></a></label>
+ <div class="controls">
+ <input type="text" id="index-name" value="<%= viewCollection.view %>" placeholder="Index name" />
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label" for="map-function">Map function <a target="_couch_docs" href="http://docs.couchdb.org/en/latest/ddocs/#map-functions"><i class="icon-question-sign"></i></a></label>
+ <div class="controls">
+ <textarea class="js-editor" id="map-function"><%= ddoc.get('doc').views[viewCollection.view].map %></textarea>
+ </div>
+ </div>
+ <div class="control-group">
+ <label class="control-label" for="reduce-function-selector">Reduce function <a target="_couch_docs" href="http://docs.couchdb.org/en/latest/ddocs/#reduce-and-rereduce-functions"><i class="icon-question-sign"></i></a></label>
+ <div class="controls">
+ <select id="reduce-function-selector">
+ <option value="" <%= !reduceFunStr ? 'selected="selected"' : '' %>>None</option>
+ <% _.each(["_sum", "_count", "_stats"], function(reduce) { %>
+ <option value="<%= reduce %>" <% if (reduce == reduceFunStr) { %>selected<% } %>><%= reduce %></option>
+ <% }) %>
+ <option value="CUSTOM" <% if (isCustomReduce) { %>selected<% } %>>Custom reduce</option>
+ </select>
+ <span class="help-block">Reduce functions are optional.</span>
+ </div>
+ </div>
+ <div class="control-group reduce-function">
+ <label class="control-label" for="reduce-function">Custom Reduce</label>
+ <div class="controls">
+ <textarea class="js-editor" id="reduce-function"><%= ddoc.get('doc').views[viewCollection.view].reduce %></textarea>
+ </div>
+ </div>
+ <div class="control-group">
+ <hr />
+ <div class="controls">
+ <button class="btn btn-small btn-inverse cancel">Cancel</button>
+ <button class="btn btn-small btn-info preview">Preview</button>
+ <button class="btn btn-primary save">Save</button>
+ </div>
+ </div>
+ <div class="clearfix"></div>
+ </form>
+ </div>
+ </div>
+ </div>
+
+ </div>
+</div>
diff --git a/src/fauxton/app/templates/fauxton/api_bar.html b/src/fauxton/app/templates/fauxton/api_bar.html
new file mode 100644
index 000000000..0ca6c69ca
--- /dev/null
+++ b/src/fauxton/app/templates/fauxton/api_bar.html
@@ -0,0 +1,30 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div class="navbar navbar-fixed-bottom">
+ <div class="navbar-inner">
+ <div class="container">
+ <div class="input-prepend input-append">
+ <span class="add-on">
+ API reference
+ <a href="http://docs.couchdb.org/en/latest/" target="_blank">
+ <i class="icon-question-sign"></i>
+ </a>
+ </span>
+ <input type="text" class="input-xxlarge" value="<%= endpoint %>">
+ <a href="<%= endpoint %>" target="_blank" class="btn">Show me</a>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/src/fauxton/app/templates/fauxton/breadcrumbs.html b/src/fauxton/app/templates/fauxton/breadcrumbs.html
new file mode 100644
index 000000000..489fef3e9
--- /dev/null
+++ b/src/fauxton/app/templates/fauxton/breadcrumbs.html
@@ -0,0 +1,24 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<ul class="breadcrumb">
+ <% _.each(_.initial(crumbs), function(crumb) { %>
+ <li>
+ <a href="#<%= crumb.link %>"><%= crumb.name %></a>
+ <span class="divider"> / </span>
+ </li>
+ <% }); %>
+ <% var last = _.last(crumbs) || {name: ''} %>
+ <li class="active"><%= last.name %></li>
+</ul>
diff --git a/src/fauxton/app/templates/fauxton/nav_bar.html b/src/fauxton/app/templates/fauxton/nav_bar.html
new file mode 100644
index 000000000..c9800bf86
--- /dev/null
+++ b/src/fauxton/app/templates/fauxton/nav_bar.html
@@ -0,0 +1,49 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div class="navbar navbar-inverse navbar-fixed-top">
+ <div class="navbar-inner">
+ <div class="container">
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <a class="brand" href="#">Project Fauxton</a>
+ <div class="nav-collapse">
+ <ul class="nav pull-right">
+ <% _.each(navLinks, function(link) { %>
+ <li><a href="<%= link.href %>"><%= link.title %></a></li>
+ <% }); %>
+ <!-- TODO: pick this up from code - nested dicts in the above -->
+
+ <!-- <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">drsm79 <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="account.html#account">Account Info</a></li>
+ <li><a href="account.html#password">Password</a></li>
+ <li><a href="account.html#indexing-rate">Indexing rate</a></li>
+ <li><a href="account.html#support-level">Support level</a></li>
+ <li><a href="account.html#location">Location for your data</a></li>
+ <li><a href="account.html#email-notifications">Email notifications</a></li>
+ <li><a href="account.html#design">Design</a></li>
+ <li class="divider"></li>
+ <li><a href="/sign-out.html">Sign Out</a></li>
+ </ul>
+ </li> -->
+ </ul>
+ </div><!--/.nav-collapse -->
+ </div>
+ </div>
+</div>
diff --git a/src/fauxton/app/templates/fauxton/notification.html b/src/fauxton/app/templates/fauxton/notification.html
new file mode 100644
index 000000000..ca8a9033a
--- /dev/null
+++ b/src/fauxton/app/templates/fauxton/notification.html
@@ -0,0 +1,18 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div class="alert alert-<%= type %>">
+ <button type="button" class="close" data-dismiss="alert">×</button>
+ <%= msg %>
+</div>
diff --git a/src/fauxton/app/templates/layouts/one_pane.html b/src/fauxton/app/templates/layouts/one_pane.html
new file mode 100644
index 000000000..71c38fb07
--- /dev/null
+++ b/src/fauxton/app/templates/layouts/one_pane.html
@@ -0,0 +1,28 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div id="primary-navbar"></div>
+
+<div id="dashboard" class="container">
+ <div class="row">
+ <div id="breadcrumbs" class="span12"></div>
+ </div>
+ <div id="tabs" class="row"></div>
+
+ <div class="row">
+ <div id="dashboard-content" class="list span12"></div>
+ </div>
+</div>
+
+<div id="api-navbar"></div>
diff --git a/src/fauxton/app/templates/layouts/two_pane.html b/src/fauxton/app/templates/layouts/two_pane.html
new file mode 100644
index 000000000..e8b8411c5
--- /dev/null
+++ b/src/fauxton/app/templates/layouts/two_pane.html
@@ -0,0 +1,29 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div id="primary-navbar"></div>
+
+<div id="dashboard" class="container">
+ <div class="row">
+ <div id="breadcrumbs" class="span12"></div>
+ </div>
+ <div id="tabs" class="row"></div>
+
+ <div class="row">
+ <div id="left-content" class="span6"></div>
+ <div id="right-content" class="span6"></div>
+ </div>
+</div>
+
+<div id="api-navbar"></div>
diff --git a/src/fauxton/app/templates/layouts/with_right_sidebar.html b/src/fauxton/app/templates/layouts/with_right_sidebar.html
new file mode 100644
index 000000000..06623c4b6
--- /dev/null
+++ b/src/fauxton/app/templates/layouts/with_right_sidebar.html
@@ -0,0 +1,27 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div id="primary-navbar"></div>
+
+<div id="dashboard" class="container">
+ <div class="row">
+ <div id="breadcrumbs" class="span12"></div>
+ </div>
+ <div class="row">
+ <div id="dashboard-content" class="list span8"></div>
+ <div id="sidebar-content" class="sidebar span4 pull-right"></div>
+ </div>
+</div>
+
+<div id="api-navbar"></div>
diff --git a/src/fauxton/app/templates/layouts/with_sidebar.html b/src/fauxton/app/templates/layouts/with_sidebar.html
new file mode 100644
index 000000000..5deb4d13f
--- /dev/null
+++ b/src/fauxton/app/templates/layouts/with_sidebar.html
@@ -0,0 +1,27 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div id="primary-navbar"></div>
+
+<div id="dashboard" class="container-fluid">
+ <div class="row-fluid">
+ <div id="breadcrumbs" class="span12"></div>
+ </div>
+ <div class="row-fluid">
+ <div id="sidebar-content" class="sidebar span3"></div>
+ <div id="dashboard-content" class="list span9"></div>
+ </div>
+</div>
+
+<div id="api-navbar"></div>
diff --git a/src/fauxton/app/templates/layouts/with_tabs.html b/src/fauxton/app/templates/layouts/with_tabs.html
new file mode 100644
index 000000000..432a68d1d
--- /dev/null
+++ b/src/fauxton/app/templates/layouts/with_tabs.html
@@ -0,0 +1,27 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div id="primary-navbar"></div>
+<div id="dashboard" class="container">
+
+<div class="row-fluid">
+ <div id="breadcrumbs" class="row-fluid"></div>
+ <div id="tabs" class="row-fluid"></div>
+
+ <div class="row-fluid">
+ <div id="dashboard-content" class="list span12"></div>
+ </div>
+</div>
+
+<div id="api-navbar"></div>
diff --git a/src/fauxton/app/templates/layouts/with_tabs_sidebar.html b/src/fauxton/app/templates/layouts/with_tabs_sidebar.html
new file mode 100644
index 000000000..f78832fe4
--- /dev/null
+++ b/src/fauxton/app/templates/layouts/with_tabs_sidebar.html
@@ -0,0 +1,28 @@
+<!--
+Licensed under the Apache License, Version 2.0 (the "License"); you may not
+use this file except in compliance with the License. You may obtain a copy of
+the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+License for the specific language governing permissions and limitations under
+the License.
+-->
+
+<div id="primary-navbar"></div>
+<div id="dashboard" class="container-fluid">
+
+<div class="row-fluid">
+ <div id="breadcrumbs" class="row-fluid"></div>
+ <div id="tabs" class="row-fluid"></div>
+
+ <div class="row-fluid">
+ <div id="sidebar-content" class="sidebar span4"></div>
+ <div id="dashboard-content" class="list span8 pull-right"></div>
+ </div>
+</div>
+
+<div id="api-navbar"></div>
diff --git a/src/fauxton/assets/css/codemirror.css b/src/fauxton/assets/css/codemirror.css
new file mode 100644
index 000000000..fb5b6d544
--- /dev/null
+++ b/src/fauxton/assets/css/codemirror.css
@@ -0,0 +1,169 @@
+.CodeMirror {
+ line-height: 1em;
+ font-family: monospace;
+
+ /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */
+ position: relative;
+ /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */
+ overflow: hidden;
+}
+
+.CodeMirror-scroll {
+ overflow-x: auto;
+ overflow-y: hidden;
+ height: 300px;
+ /* This is needed to prevent an IE[67] bug where the scrolled content
+ is visible outside of the scrolling box. */
+ position: relative;
+ outline: none;
+}
+
+/* Vertical scrollbar */
+.CodeMirror-scrollbar {
+ float: right;
+ overflow-x: hidden;
+ overflow-y: scroll;
+
+ /* This corrects for the 1px gap introduced to the left of the scrollbar
+ by the rule for .CodeMirror-scrollbar-inner. */
+ margin-left: -1px;
+}
+.CodeMirror-scrollbar-inner {
+ /* This needs to have a nonzero width in order for the scrollbar to appear
+ in Firefox and IE9. */
+ width: 1px;
+}
+.CodeMirror-scrollbar.cm-sb-overlap {
+ /* Ensure that the scrollbar appears in Lion, and that it overlaps the content
+ rather than sitting to the right of it. */
+ position: absolute;
+ z-index: 1;
+ float: none;
+ right: 0;
+ min-width: 12px;
+}
+.CodeMirror-scrollbar.cm-sb-nonoverlap {
+ min-width: 12px;
+}
+.CodeMirror-scrollbar.cm-sb-ie7 {
+ min-width: 18px;
+}
+
+.CodeMirror-gutter {
+ position: absolute; left: 0; top: 0;
+ z-index: 10;
+ background-color: #f7f7f7;
+ border-right: 1px solid #eee;
+ min-width: 2em;
+ height: 100%;
+}
+.CodeMirror-gutter-text {
+ color: #aaa;
+ text-align: right;
+ padding: .4em .2em .4em .4em;
+ white-space: pre !important;
+ cursor: default;
+}
+.CodeMirror-lines {
+ padding: .4em;
+ white-space: pre;
+ cursor: text;
+}
+.CodeMirror-lines * {
+ /* Necessary for throw-scrolling to decelerate properly on Safari. */
+ pointer-events: none;
+}
+
+.CodeMirror pre {
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+ -o-border-radius: 0;
+ border-radius: 0;
+ border-width: 0; margin: 0; padding: 0; background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ padding: 0; margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+}
+
+.CodeMirror-wrap pre {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+.CodeMirror-wrap .CodeMirror-scroll {
+ overflow-x: hidden;
+}
+
+.CodeMirror textarea {
+ outline: none !important;
+}
+
+.CodeMirror pre.CodeMirror-cursor {
+ z-index: 10;
+ position: absolute;
+ visibility: hidden;
+ border-left: 1px solid black;
+ border-right: none;
+ width: 0;
+}
+.cm-keymap-fat-cursor pre.CodeMirror-cursor {
+ width: auto;
+ border: 0;
+ background: transparent;
+ background: rgba(0, 200, 0, .4);
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800);
+}
+/* Kludge to turn off filter in ie9+, which also accepts rgba */
+.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) {
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
+.CodeMirror-focused pre.CodeMirror-cursor {
+ visibility: visible;
+}
+
+div.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
+
+.CodeMirror-searching {
+ background: #ffa;
+ background: rgba(255, 255, 0, .4);
+}
+
+/* Default theme */
+
+.cm-s-default span.cm-keyword {color: #708;}
+.cm-s-default span.cm-atom {color: #219;}
+.cm-s-default span.cm-number {color: #164;}
+.cm-s-default span.cm-def {color: #00f;}
+.cm-s-default span.cm-variable {color: black;}
+.cm-s-default span.cm-variable-2 {color: #05a;}
+.cm-s-default span.cm-variable-3 {color: #085;}
+.cm-s-default span.cm-property {color: black;}
+.cm-s-default span.cm-operator {color: black;}
+.cm-s-default span.cm-comment {color: #a50;}
+.cm-s-default span.cm-string {color: #a11;}
+.cm-s-default span.cm-string-2 {color: #f50;}
+.cm-s-default span.cm-meta {color: #555;}
+.cm-s-default span.cm-error {color: #f00;}
+.cm-s-default span.cm-qualifier {color: #555;}
+.cm-s-default span.cm-builtin {color: #30a;}
+.cm-s-default span.cm-bracket {color: #cc7;}
+.cm-s-default span.cm-tag {color: #170;}
+.cm-s-default span.cm-attribute {color: #00c;}
+.cm-s-default span.cm-header {color: blue;}
+.cm-s-default span.cm-quote {color: #090;}
+.cm-s-default span.cm-hr {color: #999;}
+.cm-s-default span.cm-link {color: #00c;}
+
+span.cm-header, span.cm-strong {font-weight: bold;}
+span.cm-em {font-style: italic;}
+span.cm-emstrong {font-style: italic; font-weight: bold;}
+span.cm-link {text-decoration: underline;}
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
diff --git a/src/fauxton/assets/css/nv.d3.css b/src/fauxton/assets/css/nv.d3.css
new file mode 100644
index 000000000..28ccd053e
--- /dev/null
+++ b/src/fauxton/assets/css/nv.d3.css
@@ -0,0 +1,656 @@
+
+/********************
+ * HTML CSS
+ */
+
+
+.chartWrap {
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+
+/********************
+ * TOOLTIP CSS
+ */
+
+.nvtooltip {
+ position: absolute;
+ background-color: rgba(255,255,255,1);
+ padding: 10px;
+ border: 1px solid #ddd;
+ z-index: 10000;
+
+ font-family: Arial;
+ font-size: 13px;
+
+ transition: opacity 500ms linear;
+ -moz-transition: opacity 500ms linear;
+ -webkit-transition: opacity 500ms linear;
+
+ transition-delay: 500ms;
+ -moz-transition-delay: 500ms;
+ -webkit-transition-delay: 500ms;
+
+ -moz-box-shadow: 4px 4px 8px rgba(0,0,0,.5);
+ -webkit-box-shadow: 4px 4px 8px rgba(0,0,0,.5);
+ box-shadow: 4px 4px 8px rgba(0,0,0,.5);
+
+ -moz-border-radius: 10px;
+ border-radius: 10px;
+
+ pointer-events: none;
+
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.nvtooltip h3 {
+ margin: 0;
+ padding: 0;
+ text-align: center;
+}
+
+.nvtooltip p {
+ margin: 0;
+ padding: 0;
+ text-align: center;
+}
+
+.nvtooltip span {
+ display: inline-block;
+ margin: 2px 0;
+}
+
+.nvtooltip-pending-removal {
+ position: absolute;
+ pointer-events: none;
+}
+
+
+/********************
+ * SVG CSS
+ */
+
+
+svg {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ /* Trying to get SVG to act like a greedy block in all browsers */
+ display: block;
+ width:100%;
+ height:100%;
+}
+
+
+svg text {
+ font: normal 12px Arial;
+}
+
+svg .title {
+ font: bold 14px Arial;
+}
+
+.nvd3 .nv-background {
+ fill: white;
+ fill-opacity: 0;
+ /*
+ pointer-events: none;
+ */
+}
+
+.nvd3.nv-noData {
+ font-size: 18px;
+ font-weight: bolf;
+}
+
+
+/**********
+* Brush
+*/
+
+.nv-brush .extent {
+ fill-opacity: .125;
+ shape-rendering: crispEdges;
+}
+
+
+
+/**********
+* Legend
+*/
+
+.nvd3 .nv-legend .nv-series {
+ cursor: pointer;
+}
+
+.nvd3 .nv-legend .disabled circle {
+ fill-opacity: 0;
+}
+
+
+
+/**********
+* Axes
+*/
+
+.nvd3 .nv-axis path {
+ fill: none;
+ stroke: #000;
+ stroke-opacity: .75;
+ shape-rendering: crispEdges;
+}
+
+.nvd3 .nv-axis path.domain {
+ stroke-opacity: .75;
+}
+
+.nvd3 .nv-axis.nv-x path.domain {
+ stroke-opacity: 0;
+}
+
+.nvd3 .nv-axis line {
+ fill: none;
+ stroke: #000;
+ stroke-opacity: .25;
+ shape-rendering: crispEdges;
+}
+
+.nvd3 .nv-axis line.zero {
+ stroke-opacity: .75;
+}
+
+.nvd3 .nv-axis .nv-axisMaxMin text {
+ font-weight: bold;
+}
+
+.nvd3 .x .nv-axis .nv-axisMaxMin text,
+.nvd3 .x2 .nv-axis .nv-axisMaxMin text,
+.nvd3 .x3 .nv-axis .nv-axisMaxMin text {
+ text-anchor: middle
+}
+
+
+
+/**********
+* Brush
+*/
+
+.nv-brush .resize path {
+ fill: #eee;
+ stroke: #666;
+}
+
+
+
+/**********
+* Bars
+*/
+
+.nvd3 .nv-bars .negative rect {
+ zfill: brown;
+}
+
+.nvd3 .nv-bars rect {
+ zfill: steelblue;
+ fill-opacity: .75;
+
+ transition: fill-opacity 250ms linear;
+ -moz-transition: fill-opacity 250ms linear;
+ -webkit-transition: fill-opacity 250ms linear;
+}
+
+.nvd3 .nv-bars rect:hover {
+ fill-opacity: 1;
+}
+
+.nvd3 .nv-bars .hover rect {
+ fill: lightblue;
+}
+
+.nvd3 .nv-bars text {
+ fill: rgba(0,0,0,0);
+}
+
+.nvd3 .nv-bars .hover text {
+ fill: rgba(0,0,0,1);
+}
+
+
+/**********
+* Bars
+*/
+
+.nvd3 .nv-multibar .nv-groups rect,
+.nvd3 .nv-multibarHorizontal .nv-groups rect,
+.nvd3 .nv-discretebar .nv-groups rect {
+ stroke-opacity: 0;
+
+ transition: fill-opacity 250ms linear;
+ -moz-transition: fill-opacity 250ms linear;
+ -webkit-transition: fill-opacity 250ms linear;
+}
+
+.nvd3 .nv-multibar .nv-groups rect:hover,
+.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,
+.nvd3 .nv-discretebar .nv-groups rect:hover {
+ fill-opacity: 1;
+}
+
+.nvd3 .nv-discretebar .nv-groups text,
+.nvd3 .nv-multibarHorizontal .nv-groups text {
+ font-weight: bold;
+ fill: rgba(0,0,0,1);
+ stroke: rgba(0,0,0,0);
+}
+
+/***********
+* Pie Chart
+*/
+
+.nvd3.nv-pie path {
+ stroke-opacity: 0;
+
+ transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
+ -moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
+ -webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
+
+}
+
+.nvd3.nv-pie .nv-slice text {
+ stroke: #000;
+ stroke-width: 0;
+}
+
+.nvd3.nv-pie path {
+ stroke: #fff;
+ stroke-width: 1px;
+ stroke-opacity: 1;
+}
+
+.nvd3.nv-pie .hover path {
+ fill-opacity: .7;
+/*
+ stroke-width: 6px;
+ stroke-opacity: 1;
+*/
+}
+
+.nvd3.nv-pie .nv-label rect {
+ fill-opacity: 0;
+ stroke-opacity: 0;
+}
+
+/**********
+* Lines
+*/
+
+.nvd3 .nv-groups path.nv-line {
+ fill: none;
+ stroke-width: 2.5px;
+ /*
+ stroke-linecap: round;
+ shape-rendering: geometricPrecision;
+
+ transition: stroke-width 250ms linear;
+ -moz-transition: stroke-width 250ms linear;
+ -webkit-transition: stroke-width 250ms linear;
+
+ transition-delay: 250ms
+ -moz-transition-delay: 250ms;
+ -webkit-transition-delay: 250ms;
+ */
+}
+
+.nvd3 .nv-groups path.nv-area {
+ stroke: none;
+ /*
+ stroke-linecap: round;
+ shape-rendering: geometricPrecision;
+
+ stroke-width: 2.5px;
+ transition: stroke-width 250ms linear;
+ -moz-transition: stroke-width 250ms linear;
+ -webkit-transition: stroke-width 250ms linear;
+
+ transition-delay: 250ms
+ -moz-transition-delay: 250ms;
+ -webkit-transition-delay: 250ms;
+ */
+}
+
+.nvd3 .nv-line.hover path {
+ stroke-width: 6px;
+}
+
+/*
+.nvd3.scatter .groups .point {
+ fill-opacity: 0.1;
+ stroke-opacity: 0.1;
+}
+ */
+
+.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point {
+ fill-opacity: 0;
+ stroke-opacity: 0;
+}
+
+.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point {
+ fill-opacity: .5 !important;
+ stroke-opacity: .5 !important;
+}
+
+
+.nvd3 .nv-groups .nv-point {
+ transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+ -moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+ -webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
+}
+
+.nvd3.nv-scatter .nv-groups .nv-point.hover,
+.nvd3 .nv-groups .nv-point.hover {
+ stroke-width: 20px;
+ fill-opacity: .5 !important;
+ stroke-opacity: .5 !important;
+}
+
+
+.nvd3 .nv-point-paths path {
+ stroke: #aaa;
+ stroke-opacity: 0;
+ fill: #eee;
+ fill-opacity: 0;
+}
+
+
+
+.nvd3 .nv-indexLine {
+ cursor: ew-resize;
+}
+
+
+/**********
+* Distribution
+*/
+
+.nvd3 .nv-distribution {
+ pointer-events: none;
+}
+
+
+
+/**********
+* Scatter
+*/
+
+/* **Attempting to remove this for useVoronoi(false), need to see if it's required anywhere
+.nvd3 .nv-groups .nv-point {
+ pointer-events: none;
+}
+*/
+
+.nvd3 .nv-groups .nv-point.hover {
+ stroke-width: 20px;
+ stroke-opacity: .5;
+}
+
+.nvd3 .nv-scatter .nv-point.hover {
+ fill-opacity: 1;
+}
+
+/*
+.nv-group.hover .nv-point {
+ fill-opacity: 1;
+}
+*/
+
+
+/**********
+* Stacked Area
+*/
+
+.nvd3.nv-stackedarea path.nv-area {
+ fill-opacity: .7;
+ /*
+ stroke-opacity: .65;
+ fill-opacity: 1;
+ */
+ stroke-opacity: 0;
+
+ transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
+ -moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
+ -webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
+
+ /*
+ transition-delay: 500ms;
+ -moz-transition-delay: 500ms;
+ -webkit-transition-delay: 500ms;
+ */
+
+}
+
+.nvd3.nv-stackedarea path.nv-area.hover {
+ fill-opacity: .9;
+ /*
+ stroke-opacity: .85;
+ */
+}
+/*
+.d3stackedarea .groups path {
+ stroke-opacity: 0;
+}
+ */
+
+
+
+.nvd3.nv-stackedarea .nv-groups .nv-point {
+ stroke-opacity: 0;
+ fill-opacity: 0;
+}
+
+.nvd3.nv-stackedarea .nv-groups .nv-point.hover {
+ stroke-width: 20px;
+ stroke-opacity: .75;
+ fill-opacity: 1;
+}
+
+
+
+/**********
+* Line Plus Bar
+*/
+
+.nvd3.nv-linePlusBar .nv-bar rect {
+ fill-opacity: .75;
+}
+
+.nvd3.nv-linePlusBar .nv-bar rect:hover {
+ fill-opacity: 1;
+}
+
+
+/**********
+* Bullet
+*/
+
+.nvd3.nv-bullet { font: 10px sans-serif; }
+.nvd3.nv-bullet .nv-measure { fill-opacity: .8; }
+.nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; }
+.nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; }
+.nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; }
+.nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; }
+.nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; }
+.nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; }
+.nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; }
+.nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; }
+.nvd3.nv-bullet .nv-subtitle { fill: #999; }
+
+
+.nvd3.nv-bullet .nv-range {
+ fill: #999;
+ fill-opacity: .4;
+}
+.nvd3.nv-bullet .nv-range:hover {
+ fill-opacity: .7;
+}
+
+
+
+/**********
+* Sparkline
+*/
+
+.nvd3.nv-sparkline path {
+ fill: none;
+}
+
+.nvd3.nv-sparklineplus g.nv-hoverValue {
+ pointer-events: none;
+}
+
+.nvd3.nv-sparklineplus .nv-hoverValue line {
+ stroke: #333;
+ stroke-width: 1.5px;
+ }
+
+.nvd3.nv-sparklineplus,
+.nvd3.nv-sparklineplus g {
+ pointer-events: all;
+}
+
+.nvd3 .nv-hoverArea {
+ fill-opacity: 0;
+ stroke-opacity: 0;
+}
+
+.nvd3.nv-sparklineplus .nv-xValue,
+.nvd3.nv-sparklineplus .nv-yValue {
+ /*
+ stroke: #666;
+ */
+ stroke-width: 0;
+ font-size: .9em;
+ font-weight: normal;
+}
+
+.nvd3.nv-sparklineplus .nv-yValue {
+ stroke: #f66;
+}
+
+.nvd3.nv-sparklineplus .nv-maxValue {
+ stroke: #2ca02c;
+ fill: #2ca02c;
+}
+
+.nvd3.nv-sparklineplus .nv-minValue {
+ stroke: #d62728;
+ fill: #d62728;
+}
+
+.nvd3.nv-sparklineplus .nv-currentValue {
+ /*
+ stroke: #444;
+ fill: #000;
+ */
+ font-weight: bold;
+ font-size: 1.1em;
+}
+
+/**********
+* historical stock
+*/
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick {
+ stroke-width: 2px;
+}
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover {
+ stroke-width: 4px;
+}
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive {
+ stroke: #2ca02c;
+}
+
+.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative {
+ stroke: #d62728;
+}
+
+.nvd3.nv-historicalStockChart .nv-axis .nv-axislabel {
+ font-weight: bold;
+}
+
+.nvd3.nv-historicalStockChart .nv-dragTarget {
+ fill-opacity: 0;
+ stroke: none;
+ cursor: move;
+}
+
+.nvd3 .nv-brush .extent {
+ /*
+ cursor: ew-resize !important;
+ */
+ fill-opacity: 0 !important;
+}
+
+.nvd3 .nv-brushBackground rect {
+ stroke: #000;
+ stroke-width: .4;
+ fill: #fff;
+ fill-opacity: .7;
+}
+
+
+
+/**********
+* Indented Tree
+*/
+
+
+/**
+ * TODO: the following 3 selectors are based on classes used in the example. I should either make them standard and leave them here, or move to a CSS file not included in the library
+ */
+.nvd3.nv-indentedtree .name {
+ margin-left: 5px;
+}
+
+.nvd3.nv-indentedtree .clickable {
+ color: #08C;
+ cursor: pointer;
+}
+
+.nvd3.nv-indentedtree span.clickable:hover {
+ color: #005580;
+ text-decoration: underline;
+}
+
+
+.nvd3.nv-indentedtree .nv-childrenCount {
+ display: inline-block;
+ margin-left: 5px;
+}
+
+.nvd3.nv-indentedtree .nv-treeicon {
+ cursor: pointer;
+ /*
+ cursor: n-resize;
+ */
+}
+
+.nvd3.nv-indentedtree .nv-treeicon.nv-folded {
+ cursor: pointer;
+ /*
+ cursor: s-resize;
+ */
+}
+
+
diff --git a/src/fauxton/assets/img/couchdblogo.png b/src/fauxton/assets/img/couchdblogo.png
new file mode 100644
index 000000000..cbe991cda
--- /dev/null
+++ b/src/fauxton/assets/img/couchdblogo.png
Binary files differ
diff --git a/src/fauxton/assets/img/glyphicons-halflings-white.png b/src/fauxton/assets/img/glyphicons-halflings-white.png
new file mode 100644
index 000000000..3bf6484a2
--- /dev/null
+++ b/src/fauxton/assets/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/src/fauxton/assets/img/glyphicons-halflings.png b/src/fauxton/assets/img/glyphicons-halflings.png
new file mode 100644
index 000000000..79bc568c2
--- /dev/null
+++ b/src/fauxton/assets/img/glyphicons-halflings.png
Binary files differ
diff --git a/src/fauxton/assets/index.underscore b/src/fauxton/assets/index.underscore
new file mode 100644
index 000000000..8a620062b
--- /dev/null
+++ b/src/fauxton/assets/index.underscore
@@ -0,0 +1,55 @@
+<!doctype html>
+
+<!--
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+-->
+
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+
+ <title>Project Fauxton</title>
+
+ <!-- Application styles. -->
+ <link rel="stylesheet" href="<%= assets_root %>css/index.css">
+ <style type="text/css">
+ body {
+ padding-top: 60px;
+ padding-bottom: 40px;
+ }
+ </style>
+ <% if (base) { %>
+ <base href="<%= base %>"></base>
+ <% } %>
+</head>
+
+<body id="home">
+ <!-- Main container. -->
+ <div role="main" id="main">
+ <div id="global-notifications" class="container errors-container"></div>
+ <div id="app-container"></div>
+ <hr>
+
+ <footer>
+ <div id="footer-content" class="container">
+ <p>&copy; Project Fauxton 2012</p>
+ </div>
+ </footer>
+ </div>
+
+ <!-- Application source. -->
+ <script data-main="/app/config" src="<%= assets_root %>js/<%= requirejs %>"></script>
+</body>
+</html>
diff --git a/src/fauxton/assets/js/libs/almond.js b/src/fauxton/assets/js/libs/almond.js
new file mode 100644
index 000000000..f530767f7
--- /dev/null
+++ b/src/fauxton/assets/js/libs/almond.js
@@ -0,0 +1,314 @@
+/**
+ * almond 0.1.1 Copyright (c) 2011, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/jrburke/almond for details
+ */
+//Going sloppy to avoid 'use strict' string cost, but strict practices should
+//be followed.
+/*jslint sloppy: true */
+/*global setTimeout: false */
+
+var requirejs, require, define;
+(function (undef) {
+ var defined = {},
+ waiting = {},
+ config = {},
+ defining = {},
+ aps = [].slice,
+ main, req;
+
+ /**
+ * Given a relative module name, like ./something, normalize it to
+ * a real name that can be mapped to a path.
+ * @param {String} name the relative name
+ * @param {String} baseName a real name that the name arg is relative
+ * to.
+ * @returns {String} normalized name
+ */
+ function normalize(name, baseName) {
+ var baseParts = baseName && baseName.split("/"),
+ map = config.map,
+ starMap = (map && map['*']) || {},
+ nameParts, nameSegment, mapValue, foundMap, i, j, part;
+
+ //Adjust any relative paths.
+ if (name && name.charAt(0) === ".") {
+ //If have a base name, try to normalize against it,
+ //otherwise, assume it is a top-level require that will
+ //be relative to baseUrl in the end.
+ if (baseName) {
+ //Convert baseName to array, and lop off the last part,
+ //so that . matches that "directory" and not name of the baseName's
+ //module. For instance, baseName of "one/two/three", maps to
+ //"one/two/three.js", but we want the directory, "one/two" for
+ //this normalization.
+ baseParts = baseParts.slice(0, baseParts.length - 1);
+
+ name = baseParts.concat(name.split("/"));
+
+ //start trimDots
+ for (i = 0; (part = name[i]); i++) {
+ if (part === ".") {
+ name.splice(i, 1);
+ i -= 1;
+ } else if (part === "..") {
+ if (i === 1 && (name[2] === '..' || name[0] === '..')) {
+ //End of the line. Keep at least one non-dot
+ //path segment at the front so it can be mapped
+ //correctly to disk. Otherwise, there is likely
+ //no path mapping for a path starting with '..'.
+ //This can still fail, but catches the most reasonable
+ //uses of ..
+ return true;
+ } else if (i > 0) {
+ name.splice(i - 1, 2);
+ i -= 2;
+ }
+ }
+ }
+ //end trimDots
+
+ name = name.join("/");
+ }
+ }
+
+ //Apply map config if available.
+ if ((baseParts || starMap) && map) {
+ nameParts = name.split('/');
+
+ for (i = nameParts.length; i > 0; i -= 1) {
+ nameSegment = nameParts.slice(0, i).join("/");
+
+ if (baseParts) {
+ //Find the longest baseName segment match in the config.
+ //So, do joins on the biggest to smallest lengths of baseParts.
+ for (j = baseParts.length; j > 0; j -= 1) {
+ mapValue = map[baseParts.slice(0, j).join('/')];
+
+ //baseName segment has config, find if it has one for
+ //this name.
+ if (mapValue) {
+ mapValue = mapValue[nameSegment];
+ if (mapValue) {
+ //Match, update name to the new value.
+ foundMap = mapValue;
+ break;
+ }
+ }
+ }
+ }
+
+ foundMap = foundMap || starMap[nameSegment];
+
+ if (foundMap) {
+ nameParts.splice(0, i, foundMap);
+ name = nameParts.join('/');
+ break;
+ }
+ }
+ }
+
+ return name;
+ }
+
+ function makeRequire(relName, forceSync) {
+ return function () {
+ //A version of a require function that passes a moduleName
+ //value for items that may need to
+ //look up paths relative to the moduleName
+ return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
+ };
+ }
+
+ function makeNormalize(relName) {
+ return function (name) {
+ return normalize(name, relName);
+ };
+ }
+
+ function makeLoad(depName) {
+ return function (value) {
+ defined[depName] = value;
+ };
+ }
+
+ function callDep(name) {
+ if (waiting.hasOwnProperty(name)) {
+ var args = waiting[name];
+ delete waiting[name];
+ defining[name] = true;
+ main.apply(undef, args);
+ }
+
+ if (!defined.hasOwnProperty(name)) {
+ throw new Error('No ' + name);
+ }
+ return defined[name];
+ }
+
+ /**
+ * Makes a name map, normalizing the name, and using a plugin
+ * for normalization if necessary. Grabs a ref to plugin
+ * too, as an optimization.
+ */
+ function makeMap(name, relName) {
+ var prefix, plugin,
+ index = name.indexOf('!');
+
+ if (index !== -1) {
+ prefix = normalize(name.slice(0, index), relName);
+ name = name.slice(index + 1);
+ plugin = callDep(prefix);
+
+ //Normalize according
+ if (plugin && plugin.normalize) {
+ name = plugin.normalize(name, makeNormalize(relName));
+ } else {
+ name = normalize(name, relName);
+ }
+ } else {
+ name = normalize(name, relName);
+ }
+
+ //Using ridiculous property names for space reasons
+ return {
+ f: prefix ? prefix + '!' + name : name, //fullName
+ n: name,
+ p: plugin
+ };
+ }
+
+ function makeConfig(name) {
+ return function () {
+ return (config && config.config && config.config[name]) || {};
+ };
+ }
+
+ main = function (name, deps, callback, relName) {
+ var args = [],
+ usingExports,
+ cjsModule, depName, ret, map, i;
+
+ //Use name if no relName
+ relName = relName || name;
+
+ //Call the callback to define the module, if necessary.
+ if (typeof callback === 'function') {
+
+ //Pull out the defined dependencies and pass the ordered
+ //values to the callback.
+ //Default to [require, exports, module] if no deps
+ deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
+ for (i = 0; i < deps.length; i++) {
+ map = makeMap(deps[i], relName);
+ depName = map.f;
+
+ //Fast path CommonJS standard dependencies.
+ if (depName === "require") {
+ args[i] = makeRequire(name);
+ } else if (depName === "exports") {
+ //CommonJS module spec 1.1
+ args[i] = defined[name] = {};
+ usingExports = true;
+ } else if (depName === "module") {
+ //CommonJS module spec 1.1
+ cjsModule = args[i] = {
+ id: name,
+ uri: '',
+ exports: defined[name],
+ config: makeConfig(name)
+ };
+ } else if (defined.hasOwnProperty(depName) || waiting.hasOwnProperty(depName)) {
+ args[i] = callDep(depName);
+ } else if (map.p) {
+ map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
+ args[i] = defined[depName];
+ } else if (!defining[depName]) {
+ throw new Error(name + ' missing ' + depName);
+ }
+ }
+
+ ret = callback.apply(defined[name], args);
+
+ if (name) {
+ //If setting exports via "module" is in play,
+ //favor that over return value and exports. After that,
+ //favor a non-undefined return value over exports use.
+ if (cjsModule && cjsModule.exports !== undef &&
+ cjsModule.exports !== defined[name]) {
+ defined[name] = cjsModule.exports;
+ } else if (ret !== undef || !usingExports) {
+ //Use the return value from the function.
+ defined[name] = ret;
+ }
+ }
+ } else if (name) {
+ //May just be an object definition for the module. Only
+ //worry about defining if have a module name.
+ defined[name] = callback;
+ }
+ };
+
+ requirejs = require = req = function (deps, callback, relName, forceSync) {
+ if (typeof deps === "string") {
+ //Just return the module wanted. In this scenario, the
+ //deps arg is the module name, and second arg (if passed)
+ //is just the relName.
+ //Normalize module name, if it contains . or ..
+ return callDep(makeMap(deps, callback).f);
+ } else if (!deps.splice) {
+ //deps is a config object, not an array.
+ config = deps;
+ if (callback.splice) {
+ //callback is an array, which means it is a dependency list.
+ //Adjust args if there are dependencies
+ deps = callback;
+ callback = relName;
+ relName = null;
+ } else {
+ deps = undef;
+ }
+ }
+
+ //Support require(['a'])
+ callback = callback || function () {};
+
+ //Simulate async callback;
+ if (forceSync) {
+ main(undef, deps, callback, relName);
+ } else {
+ setTimeout(function () {
+ main(undef, deps, callback, relName);
+ }, 15);
+ }
+
+ return req;
+ };
+
+ /**
+ * Just drops the config on the floor, but returns req in case
+ * the config return value is used.
+ */
+ req.config = function (cfg) {
+ config = cfg;
+ return req;
+ };
+
+ define = function (name, deps, callback) {
+
+ //This module may not have dependencies
+ if (!deps.splice) {
+ //deps is not an array, so probably means
+ //an object literal or factory function for
+ //the value. Adjust args.
+ callback = deps;
+ deps = [];
+ }
+
+ waiting[name] = [name, deps, callback];
+ };
+
+ define.amd = {
+ jQuery: true
+ };
+}());
diff --git a/src/fauxton/assets/js/libs/backbone.js b/src/fauxton/assets/js/libs/backbone.js
new file mode 100644
index 000000000..90e18d4ac
--- /dev/null
+++ b/src/fauxton/assets/js/libs/backbone.js
@@ -0,0 +1,1533 @@
+// Backbone.js 0.9.9
+
+// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://backbonejs.org
+
+(function(){
+
+ // Initial Setup
+ // -------------
+
+ // Save a reference to the global object (`window` in the browser, `exports`
+ // on the server).
+ var root = this;
+
+ // Save the previous value of the `Backbone` variable, so that it can be
+ // restored later on, if `noConflict` is used.
+ var previousBackbone = root.Backbone;
+
+ // Create a local reference to array methods.
+ var array = [];
+ var push = array.push;
+ var slice = array.slice;
+ var splice = array.splice;
+
+ // The top-level namespace. All public Backbone classes and modules will
+ // be attached to this. Exported for both CommonJS and the browser.
+ var Backbone;
+ if (typeof exports !== 'undefined') {
+ Backbone = exports;
+ } else {
+ Backbone = root.Backbone = {};
+ }
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '0.9.9';
+
+ // Require Underscore, if we're on the server, and it's not already present.
+ var _ = root._;
+ if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
+
+ // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
+ Backbone.$ = root.jQuery || root.Zepto || root.ender;
+
+ // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+ // to its previous owner. Returns a reference to this Backbone object.
+ Backbone.noConflict = function() {
+ root.Backbone = previousBackbone;
+ return this;
+ };
+
+ // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
+ // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
+ // set a `X-Http-Method-Override` header.
+ Backbone.emulateHTTP = false;
+
+ // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+ // `application/json` requests ... will encode the body as
+ // `application/x-www-form-urlencoded` instead and will send the model in a
+ // form param named `model`.
+ Backbone.emulateJSON = false;
+
+ // Backbone.Events
+ // ---------------
+
+ // Regular expression used to split event strings.
+ var eventSplitter = /\s+/;
+
+ // Implement fancy features of the Events API such as multiple event
+ // names `"change blur"` and jQuery-style event maps `{change: action}`
+ // in terms of the existing API.
+ var eventsApi = function(obj, action, name, rest) {
+ if (!name) return true;
+ if (typeof name === 'object') {
+ for (var key in name) {
+ obj[action].apply(obj, [key, name[key]].concat(rest));
+ }
+ } else if (eventSplitter.test(name)) {
+ var names = name.split(eventSplitter);
+ for (var i = 0, l = names.length; i < l; i++) {
+ obj[action].apply(obj, [names[i]].concat(rest));
+ }
+ } else {
+ return true;
+ }
+ };
+
+ // Optimized internal dispatch function for triggering events. Tries to
+ // keep the usual cases speedy (most Backbone events have 3 arguments).
+ var triggerEvents = function(obj, events, args) {
+ var ev, i = -1, l = events.length;
+ switch (args.length) {
+ case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx);
+ return;
+ case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0]);
+ return;
+ case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1]);
+ return;
+ case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, args[0], args[1], args[2]);
+ return;
+ default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
+ }
+ };
+
+ // A module that can be mixed in to *any object* in order to provide it with
+ // custom events. You may bind with `on` or remove with `off` callback
+ // functions to an event; `trigger`-ing an event fires all callbacks in
+ // succession.
+ //
+ // var object = {};
+ // _.extend(object, Backbone.Events);
+ // object.on('expand', function(){ alert('expanded'); });
+ // object.trigger('expand');
+ //
+ var Events = Backbone.Events = {
+
+ // Bind one or more space separated events, or an events map,
+ // to a `callback` function. Passing `"all"` will bind the callback to
+ // all events fired.
+ on: function(name, callback, context) {
+ if (!(eventsApi(this, 'on', name, [callback, context]) && callback)) return this;
+ this._events || (this._events = {});
+ var list = this._events[name] || (this._events[name] = []);
+ list.push({callback: callback, context: context, ctx: context || this});
+ return this;
+ },
+
+ // Bind events to only be triggered a single time. After the first time
+ // the callback is invoked, it will be removed.
+ once: function(name, callback, context) {
+ if (!(eventsApi(this, 'once', name, [callback, context]) && callback)) return this;
+ var self = this;
+ var once = _.once(function() {
+ self.off(name, once);
+ callback.apply(this, arguments);
+ });
+ once._callback = callback;
+ this.on(name, once, context);
+ return this;
+ },
+
+ // Remove one or many callbacks. If `context` is null, removes all
+ // callbacks with that function. If `callback` is null, removes all
+ // callbacks for the event. If `events` is null, removes all bound
+ // callbacks for all events.
+ off: function(name, callback, context) {
+ var list, ev, events, names, i, l, j, k;
+ if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
+ if (!name && !callback && !context) {
+ this._events = {};
+ return this;
+ }
+
+ names = name ? [name] : _.keys(this._events);
+ for (i = 0, l = names.length; i < l; i++) {
+ name = names[i];
+ if (list = this._events[name]) {
+ events = [];
+ if (callback || context) {
+ for (j = 0, k = list.length; j < k; j++) {
+ ev = list[j];
+ if ((callback && callback !== (ev.callback._callback || ev.callback)) ||
+ (context && context !== ev.context)) {
+ events.push(ev);
+ }
+ }
+ }
+ this._events[name] = events;
+ }
+ }
+
+ return this;
+ },
+
+ // Trigger one or many events, firing all bound callbacks. Callbacks are
+ // passed the same arguments as `trigger` is, apart from the event name
+ // (unless you're listening on `"all"`, which will cause your callback to
+ // receive the true name of the event as the first argument).
+ trigger: function(name) {
+ if (!this._events) return this;
+ var args = slice.call(arguments, 1);
+ if (!eventsApi(this, 'trigger', name, args)) return this;
+ var events = this._events[name];
+ var allEvents = this._events.all;
+ if (events) triggerEvents(this, events, args);
+ if (allEvents) triggerEvents(this, allEvents, arguments);
+ return this;
+ },
+
+ // An inversion-of-control version of `on`. Tell *this* object to listen to
+ // an event in another object ... keeping track of what it's listening to.
+ listenTo: function(object, events, callback) {
+ var listeners = this._listeners || (this._listeners = {});
+ var id = object._listenerId || (object._listenerId = _.uniqueId('l'));
+ listeners[id] = object;
+ object.on(events, callback || this, this);
+ return this;
+ },
+
+ // Tell this object to stop listening to either specific events ... or
+ // to every object it's currently listening to.
+ stopListening: function(object, events, callback) {
+ var listeners = this._listeners;
+ if (!listeners) return;
+ if (object) {
+ object.off(events, callback, this);
+ if (!events && !callback) delete listeners[object._listenerId];
+ } else {
+ for (var id in listeners) {
+ listeners[id].off(null, null, this);
+ }
+ this._listeners = {};
+ }
+ return this;
+ }
+ };
+
+ // Aliases for backwards compatibility.
+ Events.bind = Events.on;
+ Events.unbind = Events.off;
+
+ // Allow the `Backbone` object to serve as a global event bus, for folks who
+ // want global "pubsub" in a convenient place.
+ _.extend(Backbone, Events);
+
+ // Backbone.Model
+ // --------------
+
+ // Create a new model, with defined attributes. A client id (`cid`)
+ // is automatically generated and assigned for you.
+ var Model = Backbone.Model = function(attributes, options) {
+ var defaults;
+ var attrs = attributes || {};
+ this.cid = _.uniqueId('c');
+ this.changed = {};
+ this.attributes = {};
+ this._changes = [];
+ if (options && options.collection) this.collection = options.collection;
+ if (options && options.parse) attrs = this.parse(attrs);
+ if (defaults = _.result(this, 'defaults')) _.defaults(attrs, defaults);
+ this.set(attrs, {silent: true});
+ this._currentAttributes = _.clone(this.attributes);
+ this._previousAttributes = _.clone(this.attributes);
+ this.initialize.apply(this, arguments);
+ };
+
+ // Attach all inheritable methods to the Model prototype.
+ _.extend(Model.prototype, Events, {
+
+ // A hash of attributes whose current and previous value differ.
+ changed: null,
+
+ // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+ // CouchDB users may want to set this to `"_id"`.
+ idAttribute: 'id',
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Return a copy of the model's `attributes` object.
+ toJSON: function(options) {
+ return _.clone(this.attributes);
+ },
+
+ // Proxy `Backbone.sync` by default.
+ sync: function() {
+ return Backbone.sync.apply(this, arguments);
+ },
+
+ // Get the value of an attribute.
+ get: function(attr) {
+ return this.attributes[attr];
+ },
+
+ // Get the HTML-escaped value of an attribute.
+ escape: function(attr) {
+ return _.escape(this.get(attr));
+ },
+
+ // Returns `true` if the attribute contains a value that is not null
+ // or undefined.
+ has: function(attr) {
+ return this.get(attr) != null;
+ },
+
+ // Set a hash of model attributes on the object, firing `"change"` unless
+ // you choose to silence it.
+ set: function(key, val, options) {
+ var attr, attrs;
+ if (key == null) return this;
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (_.isObject(key)) {
+ attrs = key;
+ options = val;
+ } else {
+ (attrs = {})[key] = val;
+ }
+
+ // Extract attributes and options.
+ var silent = options && options.silent;
+ var unset = options && options.unset;
+
+ // Run validation.
+ if (!this._validate(attrs, options)) return false;
+
+ // Check for changes of `id`.
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+ var now = this.attributes;
+
+ // For each `set` attribute...
+ for (attr in attrs) {
+ val = attrs[attr];
+
+ // Update or delete the current value, and track the change.
+ unset ? delete now[attr] : now[attr] = val;
+ this._changes.push(attr, val);
+ }
+
+ // Signal that the model's state has potentially changed, and we need
+ // to recompute the actual changes.
+ this._hasComputed = false;
+
+ // Fire the `"change"` events.
+ if (!silent) this.change(options);
+ return this;
+ },
+
+ // Remove an attribute from the model, firing `"change"` unless you choose
+ // to silence it. `unset` is a noop if the attribute doesn't exist.
+ unset: function(attr, options) {
+ return this.set(attr, void 0, _.extend({}, options, {unset: true}));
+ },
+
+ // Clear all attributes on the model, firing `"change"` unless you choose
+ // to silence it.
+ clear: function(options) {
+ var attrs = {};
+ for (var key in this.attributes) attrs[key] = void 0;
+ return this.set(attrs, _.extend({}, options, {unset: true}));
+ },
+
+ // Fetch the model from the server. If the server's representation of the
+ // model differs from its current attributes, they will be overriden,
+ // triggering a `"change"` event.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ if (options.parse === void 0) options.parse = true;
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ if (!model.set(model.parse(resp), options)) return false;
+ if (success) success(model, resp, options);
+ };
+ return this.sync('read', this, options);
+ },
+
+ // Set a hash of model attributes, and sync the model to the server.
+ // If the server returns an attributes hash that differs, the model's
+ // state will be `set` again.
+ save: function(key, val, options) {
+ var attrs, current, done;
+
+ // Handle both `"key", value` and `{key: value}` -style arguments.
+ if (key == null || _.isObject(key)) {
+ attrs = key;
+ options = val;
+ } else if (key != null) {
+ (attrs = {})[key] = val;
+ }
+ options = options ? _.clone(options) : {};
+
+ // If we're "wait"-ing to set changed attributes, validate early.
+ if (options.wait) {
+ if (attrs && !this._validate(attrs, options)) return false;
+ current = _.clone(this.attributes);
+ }
+
+ // Regular saves `set` attributes before persisting to the server.
+ var silentOptions = _.extend({}, options, {silent: true});
+ if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
+ return false;
+ }
+
+ // Do not persist invalid models.
+ if (!attrs && !this._validate(null, options)) return false;
+
+ // After a successful server-side save, the client is (optionally)
+ // updated with the server-side state.
+ var model = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ done = true;
+ var serverAttrs = model.parse(resp);
+ if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
+ if (!model.set(serverAttrs, options)) return false;
+ if (success) success(model, resp, options);
+ };
+
+ // Finish configuring and sending the Ajax request.
+ var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
+ if (method == 'patch') options.attrs = attrs;
+ var xhr = this.sync(method, this, options);
+
+ // When using `wait`, reset attributes to original values unless
+ // `success` has been called already.
+ if (!done && options.wait) {
+ this.clear(silentOptions);
+ this.set(current, silentOptions);
+ }
+
+ return xhr;
+ },
+
+ // Destroy this model on the server if it was already persisted.
+ // Optimistically removes the model from its collection, if it has one.
+ // If `wait: true` is passed, waits for the server to respond before removal.
+ destroy: function(options) {
+ options = options ? _.clone(options) : {};
+ var model = this;
+ var success = options.success;
+
+ var destroy = function() {
+ model.trigger('destroy', model, model.collection, options);
+ };
+
+ options.success = function(resp) {
+ if (options.wait || model.isNew()) destroy();
+ if (success) success(model, resp, options);
+ };
+
+ if (this.isNew()) {
+ options.success();
+ return false;
+ }
+
+ var xhr = this.sync('delete', this, options);
+ if (!options.wait) destroy();
+ return xhr;
+ },
+
+ // Default URL for the model's representation on the server -- if you're
+ // using Backbone's restful methods, override this to change the endpoint
+ // that will be called.
+ url: function() {
+ var base = _.result(this, 'urlRoot') || _.result(this.collection, 'url') || urlError();
+ if (this.isNew()) return base;
+ return base + (base.charAt(base.length - 1) === '/' ? '' : '/') + encodeURIComponent(this.id);
+ },
+
+ // **parse** converts a response into the hash of attributes to be `set` on
+ // the model. The default implementation is just to pass the response along.
+ parse: function(resp) {
+ return resp;
+ },
+
+ // Create a new model with identical attributes to this one.
+ clone: function() {
+ return new this.constructor(this.attributes);
+ },
+
+ // A model is new if it has never been saved to the server, and lacks an id.
+ isNew: function() {
+ return this.id == null;
+ },
+
+ // Call this method to manually fire a `"change"` event for this model and
+ // a `"change:attribute"` event for each changed attribute.
+ // Calling this will cause all objects observing the model to update.
+ change: function(options) {
+ var changing = this._changing;
+ this._changing = true;
+
+ // Generate the changes to be triggered on the model.
+ var triggers = this._computeChanges(true);
+
+ this._pending = !!triggers.length;
+
+ for (var i = triggers.length - 2; i >= 0; i -= 2) {
+ this.trigger('change:' + triggers[i], this, triggers[i + 1], options);
+ }
+
+ if (changing) return this;
+
+ // Trigger a `change` while there have been changes.
+ while (this._pending) {
+ this._pending = false;
+ this.trigger('change', this, options);
+ this._previousAttributes = _.clone(this.attributes);
+ }
+
+ this._changing = false;
+ return this;
+ },
+
+ // Determine if the model has changed since the last `"change"` event.
+ // If you specify an attribute name, determine if that attribute has changed.
+ hasChanged: function(attr) {
+ if (!this._hasComputed) this._computeChanges();
+ if (attr == null) return !_.isEmpty(this.changed);
+ return _.has(this.changed, attr);
+ },
+
+ // Return an object containing all the attributes that have changed, or
+ // false if there are no changed attributes. Useful for determining what
+ // parts of a view need to be updated and/or what attributes need to be
+ // persisted to the server. Unset attributes will be set to undefined.
+ // You can also pass an attributes object to diff against the model,
+ // determining if there *would be* a change.
+ changedAttributes: function(diff) {
+ if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
+ var val, changed = false, old = this._previousAttributes;
+ for (var attr in diff) {
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+ (changed || (changed = {}))[attr] = val;
+ }
+ return changed;
+ },
+
+ // Looking at the built up list of `set` attribute changes, compute how
+ // many of the attributes have actually changed. If `loud`, return a
+ // boiled-down list of only the real changes.
+ _computeChanges: function(loud) {
+ this.changed = {};
+ var already = {};
+ var triggers = [];
+ var current = this._currentAttributes;
+ var changes = this._changes;
+
+ // Loop through the current queue of potential model changes.
+ for (var i = changes.length - 2; i >= 0; i -= 2) {
+ var key = changes[i], val = changes[i + 1];
+ if (already[key]) continue;
+ already[key] = true;
+
+ // Check if the attribute has been modified since the last change,
+ // and update `this.changed` accordingly. If we're inside of a `change`
+ // call, also add a trigger to the list.
+ if (current[key] !== val) {
+ this.changed[key] = val;
+ if (!loud) continue;
+ triggers.push(key, val);
+ current[key] = val;
+ }
+ }
+ if (loud) this._changes = [];
+
+ // Signals `this.changed` is current to prevent duplicate calls from `this.hasChanged`.
+ this._hasComputed = true;
+ return triggers;
+ },
+
+ // Get the previous value of an attribute, recorded at the time the last
+ // `"change"` event was fired.
+ previous: function(attr) {
+ if (attr == null || !this._previousAttributes) return null;
+ return this._previousAttributes[attr];
+ },
+
+ // Get all of the attributes of the model at the time of the previous
+ // `"change"` event.
+ previousAttributes: function() {
+ return _.clone(this._previousAttributes);
+ },
+
+ // Run validation against the next complete set of model attributes,
+ // returning `true` if all is well. If a specific `error` callback has
+ // been passed, call that instead of firing the general `"error"` event.
+ _validate: function(attrs, options) {
+ if (!this.validate) return true;
+ attrs = _.extend({}, this.attributes, attrs);
+ var error = this.validate(attrs, options);
+ if (!error) return true;
+ if (options && options.error) options.error(this, error, options);
+ this.trigger('error', this, error, options);
+ return false;
+ }
+
+ });
+
+ // Backbone.Collection
+ // -------------------
+
+ // Provides a standard collection class for our sets of models, ordered
+ // or unordered. If a `comparator` is specified, the Collection will maintain
+ // its models in sort order, as they're added and removed.
+ var Collection = Backbone.Collection = function(models, options) {
+ options || (options = {});
+ if (options.model) this.model = options.model;
+ if (options.comparator !== void 0) this.comparator = options.comparator;
+ this._reset();
+ this.initialize.apply(this, arguments);
+ if (models) this.reset(models, _.extend({silent: true}, options));
+ };
+
+ // Define the Collection's inheritable methods.
+ _.extend(Collection.prototype, Events, {
+
+ // The default model for a collection is just a **Backbone.Model**.
+ // This should be overridden in most cases.
+ model: Model,
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // The JSON representation of a Collection is an array of the
+ // models' attributes.
+ toJSON: function(options) {
+ return this.map(function(model){ return model.toJSON(options); });
+ },
+
+ // Proxy `Backbone.sync` by default.
+ sync: function() {
+ return Backbone.sync.apply(this, arguments);
+ },
+
+ // Add a model, or list of models to the set. Pass **silent** to avoid
+ // firing the `add` event for every new model.
+ add: function(models, options) {
+ var i, args, length, model, existing, needsSort;
+ var at = options && options.at;
+ var sort = ((options && options.sort) == null ? true : options.sort);
+ models = _.isArray(models) ? models.slice() : [models];
+
+ // Turn bare objects into model references, and prevent invalid models
+ // from being added.
+ for (i = models.length - 1; i >= 0; i--) {
+ if(!(model = this._prepareModel(models[i], options))) {
+ this.trigger("error", this, models[i], options);
+ models.splice(i, 1);
+ continue;
+ }
+ models[i] = model;
+
+ existing = model.id != null && this._byId[model.id];
+ // If a duplicate is found, prevent it from being added and
+ // optionally merge it into the existing model.
+ if (existing || this._byCid[model.cid]) {
+ if (options && options.merge && existing) {
+ existing.set(model.attributes, options);
+ needsSort = sort;
+ }
+ models.splice(i, 1);
+ continue;
+ }
+
+ // Listen to added models' events, and index models for lookup by
+ // `id` and by `cid`.
+ model.on('all', this._onModelEvent, this);
+ this._byCid[model.cid] = model;
+ if (model.id != null) this._byId[model.id] = model;
+ }
+
+ // See if sorting is needed, update `length` and splice in new models.
+ if (models.length) needsSort = sort;
+ this.length += models.length;
+ args = [at != null ? at : this.models.length, 0];
+ push.apply(args, models);
+ splice.apply(this.models, args);
+
+ // Sort the collection if appropriate.
+ if (needsSort && this.comparator && at == null) this.sort({silent: true});
+
+ if (options && options.silent) return this;
+
+ // Trigger `add` events.
+ while (model = models.shift()) {
+ model.trigger('add', model, this, options);
+ }
+
+ return this;
+ },
+
+ // Remove a model, or a list of models from the set. Pass silent to avoid
+ // firing the `remove` event for every model removed.
+ remove: function(models, options) {
+ var i, l, index, model;
+ options || (options = {});
+ models = _.isArray(models) ? models.slice() : [models];
+ for (i = 0, l = models.length; i < l; i++) {
+ model = this.get(models[i]);
+ if (!model) continue;
+ delete this._byId[model.id];
+ delete this._byCid[model.cid];
+ index = this.indexOf(model);
+ this.models.splice(index, 1);
+ this.length--;
+ if (!options.silent) {
+ options.index = index;
+ model.trigger('remove', model, this, options);
+ }
+ this._removeReference(model);
+ }
+ return this;
+ },
+
+ // Add a model to the end of the collection.
+ push: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, _.extend({at: this.length}, options));
+ return model;
+ },
+
+ // Remove a model from the end of the collection.
+ pop: function(options) {
+ var model = this.at(this.length - 1);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Add a model to the beginning of the collection.
+ unshift: function(model, options) {
+ model = this._prepareModel(model, options);
+ this.add(model, _.extend({at: 0}, options));
+ return model;
+ },
+
+ // Remove a model from the beginning of the collection.
+ shift: function(options) {
+ var model = this.at(0);
+ this.remove(model, options);
+ return model;
+ },
+
+ // Slice out a sub-array of models from the collection.
+ slice: function(begin, end) {
+ return this.models.slice(begin, end);
+ },
+
+ // Get a model from the set by id.
+ get: function(obj) {
+ if (obj == null) return void 0;
+ return this._byId[obj.id != null ? obj.id : obj] || this._byCid[obj.cid || obj];
+ },
+
+ // Get the model at the given index.
+ at: function(index) {
+ return this.models[index];
+ },
+
+ // Return models with matching attributes. Useful for simple cases of `filter`.
+ where: function(attrs) {
+ if (_.isEmpty(attrs)) return [];
+ return this.filter(function(model) {
+ for (var key in attrs) {
+ if (attrs[key] !== model.get(key)) return false;
+ }
+ return true;
+ });
+ },
+
+ // Force the collection to re-sort itself. You don't need to call this under
+ // normal circumstances, as the set will maintain sort order as each item
+ // is added.
+ sort: function(options) {
+ if (!this.comparator) {
+ throw new Error('Cannot sort a set without a comparator');
+ }
+
+ if (_.isString(this.comparator) || this.comparator.length === 1) {
+ this.models = this.sortBy(this.comparator, this);
+ } else {
+ this.models.sort(_.bind(this.comparator, this));
+ }
+
+ if (!options || !options.silent) this.trigger('sort', this, options);
+ return this;
+ },
+
+ // Pluck an attribute from each model in the collection.
+ pluck: function(attr) {
+ return _.invoke(this.models, 'get', attr);
+ },
+
+ // Smartly update a collection with a change set of models, adding,
+ // removing, and merging as necessary.
+ update: function(models, options) {
+ var model, i, l, existing;
+ var add = [], remove = [], modelMap = {};
+ var idAttr = this.model.prototype.idAttribute;
+ options = _.extend({add: true, merge: true, remove: true}, options);
+ if (options.parse) models = this.parse(models);
+
+ // Allow a single model (or no argument) to be passed.
+ if (!_.isArray(models)) models = models ? [models] : [];
+
+ // Proxy to `add` for this case, no need to iterate...
+ if (options.add && !options.remove) return this.add(models, options);
+
+ // Determine which models to add and merge, and which to remove.
+ for (i = 0, l = models.length; i < l; i++) {
+ model = models[i];
+ existing = this.get(model.id || model.cid || model[idAttr]);
+ if (options.remove && existing) modelMap[existing.cid] = true;
+ if ((options.add && !existing) || (options.merge && existing)) {
+ add.push(model);
+ }
+ }
+ if (options.remove) {
+ for (i = 0, l = this.models.length; i < l; i++) {
+ model = this.models[i];
+ if (!modelMap[model.cid]) remove.push(model);
+ }
+ }
+
+ // Remove models (if applicable) before we add and merge the rest.
+ if (remove.length) this.remove(remove, options);
+ if (add.length) this.add(add, options);
+ return this;
+ },
+
+ // When you have more items than you want to add or remove individually,
+ // you can reset the entire set with a new list of models, without firing
+ // any `add` or `remove` events. Fires `reset` when finished.
+ reset: function(models, options) {
+ options || (options = {});
+ if (options.parse) models = this.parse(models);
+ for (var i = 0, l = this.models.length; i < l; i++) {
+ this._removeReference(this.models[i]);
+ }
+ options.previousModels = this.models;
+ this._reset();
+ if (models) this.add(models, _.extend({silent: true}, options));
+ if (!options.silent) this.trigger('reset', this, options);
+ return this;
+ },
+
+ // Fetch the default set of models for this collection, resetting the
+ // collection when they arrive. If `add: true` is passed, appends the
+ // models to the collection instead of resetting.
+ fetch: function(options) {
+ options = options ? _.clone(options) : {};
+ if (options.parse === void 0) options.parse = true;
+ var collection = this;
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ var method = options.update ? 'update' : 'reset';
+ collection[method](resp, options);
+ if (success) success(collection, resp, options);
+ };
+ return this.sync('read', this, options);
+ },
+
+ // Create a new instance of a model in this collection. Add the model to the
+ // collection immediately, unless `wait: true` is passed, in which case we
+ // wait for the server to agree.
+ create: function(model, options) {
+ var collection = this;
+ options = options ? _.clone(options) : {};
+ model = this._prepareModel(model, options);
+ if (!model) return false;
+ if (!options.wait) collection.add(model, options);
+ var success = options.success;
+ options.success = function(model, resp, options) {
+ if (options.wait) collection.add(model, options);
+ if (success) success(model, resp, options);
+ };
+ model.save(null, options);
+ return model;
+ },
+
+ // **parse** converts a response into a list of models to be added to the
+ // collection. The default implementation is just to pass it through.
+ parse: function(resp) {
+ return resp;
+ },
+
+ // Create a new collection with an identical list of models as this one.
+ clone: function() {
+ return new this.constructor(this.models);
+ },
+
+ // Proxy to _'s chain. Can't be proxied the same way the rest of the
+ // underscore methods are proxied because it relies on the underscore
+ // constructor.
+ chain: function() {
+ return _(this.models).chain();
+ },
+
+ // Reset all internal state. Called when the collection is reset.
+ _reset: function() {
+ this.length = 0;
+ this.models = [];
+ this._byId = {};
+ this._byCid = {};
+ },
+
+ // Prepare a model or hash of attributes to be added to this collection.
+ _prepareModel: function(attrs, options) {
+ if (attrs instanceof Model) {
+ if (!attrs.collection) attrs.collection = this;
+ return attrs;
+ }
+ options || (options = {});
+ options.collection = this;
+ var model = new this.model(attrs, options);
+ if (!model._validate(attrs, options)) return false;
+ return model;
+ },
+
+ // Internal method to remove a model's ties to a collection.
+ _removeReference: function(model) {
+ if (this === model.collection) delete model.collection;
+ model.off('all', this._onModelEvent, this);
+ },
+
+ // Internal method called every time a model in the set fires an event.
+ // Sets need to update their indexes when models change ids. All other
+ // events simply proxy through. "add" and "remove" events that originate
+ // in other collections are ignored.
+ _onModelEvent: function(event, model, collection, options) {
+ if ((event === 'add' || event === 'remove') && collection !== this) return;
+ if (event === 'destroy') this.remove(model, options);
+ if (model && event === 'change:' + model.idAttribute) {
+ delete this._byId[model.previous(model.idAttribute)];
+ if (model.id != null) this._byId[model.id] = model;
+ }
+ this.trigger.apply(this, arguments);
+ }
+
+ });
+
+ // Underscore methods that we want to implement on the Collection.
+ var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
+ 'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
+ 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
+ 'max', 'min', 'sortedIndex', 'toArray', 'size', 'first', 'head', 'take',
+ 'initial', 'rest', 'tail', 'last', 'without', 'indexOf', 'shuffle',
+ 'lastIndexOf', 'isEmpty'];
+
+ // Mix in each Underscore method as a proxy to `Collection#models`.
+ _.each(methods, function(method) {
+ Collection.prototype[method] = function() {
+ var args = slice.call(arguments);
+ args.unshift(this.models);
+ return _[method].apply(_, args);
+ };
+ });
+
+ // Underscore methods that take a property name as an argument.
+ var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
+
+ // Use attributes instead of properties.
+ _.each(attributeMethods, function(method) {
+ Collection.prototype[method] = function(value, context) {
+ var iterator = _.isFunction(value) ? value : function(model) {
+ return model.get(value);
+ };
+ return _[method](this.models, iterator, context);
+ };
+ });
+
+ // Backbone.Router
+ // ---------------
+
+ // Routers map faux-URLs to actions, and fire events when routes are
+ // matched. Creating a new one sets its `routes` hash, if not set statically.
+ var Router = Backbone.Router = function(options) {
+ options || (options = {});
+ if (options.routes) this.routes = options.routes;
+ this._bindRoutes();
+ this.initialize.apply(this, arguments);
+ };
+
+ // Cached regular expressions for matching named param parts and splatted
+ // parts of route strings.
+ var optionalParam = /\((.*?)\)/g;
+ var namedParam = /:\w+/g;
+ var splatParam = /\*\w+/g;
+ var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
+
+ // Set up all inheritable **Backbone.Router** properties and methods.
+ _.extend(Router.prototype, Events, {
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // Manually bind a single named route to a callback. For example:
+ //
+ // this.route('search/:query/p:num', 'search', function(query, num) {
+ // ...
+ // });
+ //
+ route: function(route, name, callback) {
+ if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+ if (!callback) callback = this[name];
+ Backbone.history.route(route, _.bind(function(fragment) {
+ var args = this._extractParameters(route, fragment);
+ callback && callback.apply(this, args);
+ this.trigger.apply(this, ['route:' + name].concat(args));
+ Backbone.history.trigger('route', this, name, args);
+ }, this));
+ return this;
+ },
+
+ // Simple proxy to `Backbone.history` to save a fragment into the history.
+ navigate: function(fragment, options) {
+ Backbone.history.navigate(fragment, options);
+ return this;
+ },
+
+ // Bind all defined routes to `Backbone.history`. We have to reverse the
+ // order of the routes here to support behavior where the most general
+ // routes can be defined at the bottom of the route map.
+ _bindRoutes: function() {
+ if (!this.routes) return;
+ var route, routes = _.keys(this.routes);
+ while ((route = routes.pop()) != null) {
+ this.route(route, this.routes[route]);
+ }
+ },
+
+ // Convert a route string into a regular expression, suitable for matching
+ // against the current location hash.
+ _routeToRegExp: function(route) {
+ route = route.replace(escapeRegExp, '\\$&')
+ .replace(optionalParam, '(?:$1)?')
+ .replace(namedParam, '([^\/]+)')
+ .replace(splatParam, '(.*?)');
+ return new RegExp('^' + route + '$');
+ },
+
+ // Given a route, and a URL fragment that it matches, return the array of
+ // extracted parameters.
+ _extractParameters: function(route, fragment) {
+ return route.exec(fragment).slice(1);
+ }
+
+ });
+
+ // Backbone.History
+ // ----------------
+
+ // Handles cross-browser history management, based on URL fragments. If the
+ // browser does not support `onhashchange`, falls back to polling.
+ var History = Backbone.History = function() {
+ this.handlers = [];
+ _.bindAll(this, 'checkUrl');
+
+ // Ensure that `History` can be used outside of the browser.
+ if (typeof window !== 'undefined') {
+ this.location = window.location;
+ this.history = window.history;
+ }
+ };
+
+ // Cached regex for stripping a leading hash/slash and trailing space.
+ var routeStripper = /^[#\/]|\s+$/g;
+
+ // Cached regex for stripping leading and trailing slashes.
+ var rootStripper = /^\/+|\/+$/g;
+
+ // Cached regex for detecting MSIE.
+ var isExplorer = /msie [\w.]+/;
+
+ // Cached regex for removing a trailing slash.
+ var trailingSlash = /\/$/;
+
+ // Has the history handling already been started?
+ History.started = false;
+
+ // Set up all inheritable **Backbone.History** properties and methods.
+ _.extend(History.prototype, Events, {
+
+ // The default interval to poll for hash changes, if necessary, is
+ // twenty times a second.
+ interval: 50,
+
+ // Gets the true hash value. Cannot use location.hash directly due to bug
+ // in Firefox where location.hash will always be decoded.
+ getHash: function(window) {
+ var match = (window || this).location.href.match(/#(.*)$/);
+ return match ? match[1] : '';
+ },
+
+ // Get the cross-browser normalized URL fragment, either from the URL,
+ // the hash, or the override.
+ getFragment: function(fragment, forcePushState) {
+ if (fragment == null) {
+ if (this._hasPushState || !this._wantsHashChange || forcePushState) {
+ fragment = this.location.pathname;
+ var root = this.root.replace(trailingSlash, '');
+ if (!fragment.indexOf(root)) fragment = fragment.substr(root.length);
+ } else {
+ fragment = this.getHash();
+ }
+ }
+ return fragment.replace(routeStripper, '');
+ },
+
+ // Start the hash change handling, returning `true` if the current URL matches
+ // an existing route, and `false` otherwise.
+ start: function(options) {
+ if (History.started) throw new Error("Backbone.history has already been started");
+ History.started = true;
+
+ // Figure out the initial configuration. Do we need an iframe?
+ // Is pushState desired ... is it available?
+ this.options = _.extend({}, {root: '/'}, this.options, options);
+ this.root = this.options.root;
+ this._wantsHashChange = this.options.hashChange !== false;
+ this._wantsPushState = !!this.options.pushState;
+ this._hasPushState = !!(this.options.pushState && this.history && this.history.pushState);
+ var fragment = this.getFragment();
+ var docMode = document.documentMode;
+ var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
+
+ // Normalize root to always include a leading and trailing slash.
+ this.root = ('/' + this.root + '/').replace(rootStripper, '/');
+
+ if (oldIE && this._wantsHashChange) {
+ this.iframe = Backbone.$('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
+ this.navigate(fragment);
+ }
+
+ // Depending on whether we're using pushState or hashes, and whether
+ // 'onhashchange' is supported, determine how we check the URL state.
+ if (this._hasPushState) {
+ Backbone.$(window).bind('popstate', this.checkUrl);
+ } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
+ Backbone.$(window).bind('hashchange', this.checkUrl);
+ } else if (this._wantsHashChange) {
+ this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+ }
+
+ // Determine if we need to change the base url, for a pushState link
+ // opened by a non-pushState browser.
+ this.fragment = fragment;
+ var loc = this.location;
+ var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === this.root;
+
+ // If we've started off with a route from a `pushState`-enabled browser,
+ // but we're currently in a browser that doesn't support it...
+ if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
+ this.fragment = this.getFragment(null, true);
+ this.location.replace(this.root + this.location.search + '#' + this.fragment);
+ // Return immediately as browser will do redirect to new url
+ return true;
+
+ // Or if we've started out with a hash-based route, but we're currently
+ // in a browser where it could be `pushState`-based instead...
+ } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
+ this.fragment = this.getHash().replace(routeStripper, '');
+ this.history.replaceState({}, document.title, this.root + this.fragment + loc.search);
+ }
+
+ if (!this.options.silent) return this.loadUrl();
+ },
+
+ // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
+ // but possibly useful for unit testing Routers.
+ stop: function() {
+ Backbone.$(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
+ clearInterval(this._checkUrlInterval);
+ History.started = false;
+ },
+
+ // Add a route to be tested when the fragment changes. Routes added later
+ // may override previous routes.
+ route: function(route, callback) {
+ this.handlers.unshift({route: route, callback: callback});
+ },
+
+ // Checks the current URL to see if it has changed, and if it has,
+ // calls `loadUrl`, normalizing across the hidden iframe.
+ checkUrl: function(e) {
+ var current = this.getFragment();
+ if (current === this.fragment && this.iframe) {
+ current = this.getFragment(this.getHash(this.iframe));
+ }
+ if (current === this.fragment) return false;
+ if (this.iframe) this.navigate(current);
+ this.loadUrl() || this.loadUrl(this.getHash());
+ },
+
+ // Attempt to load the current URL fragment. If a route succeeds with a
+ // match, returns `true`. If no defined routes matches the fragment,
+ // returns `false`.
+ loadUrl: function(fragmentOverride) {
+ var fragment = this.fragment = this.getFragment(fragmentOverride);
+ var matched = _.any(this.handlers, function(handler) {
+ if (handler.route.test(fragment)) {
+ handler.callback(fragment);
+ return true;
+ }
+ });
+ return matched;
+ },
+
+ // Save a fragment into the hash history, or replace the URL state if the
+ // 'replace' option is passed. You are responsible for properly URL-encoding
+ // the fragment in advance.
+ //
+ // The options object can contain `trigger: true` if you wish to have the
+ // route callback be fired (not usually desirable), or `replace: true`, if
+ // you wish to modify the current URL without adding an entry to the history.
+ navigate: function(fragment, options) {
+ if (!History.started) return false;
+ if (!options || options === true) options = {trigger: options};
+ fragment = this.getFragment(fragment || '');
+ if (this.fragment === fragment) return;
+ this.fragment = fragment;
+ var url = this.root + fragment;
+
+ // If pushState is available, we use it to set the fragment as a real URL.
+ if (this._hasPushState) {
+ this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
+
+ // If hash changes haven't been explicitly disabled, update the hash
+ // fragment to store history.
+ } else if (this._wantsHashChange) {
+ this._updateHash(this.location, fragment, options.replace);
+ if (this.iframe && (fragment !== this.getFragment(this.getHash(this.iframe)))) {
+ // Opening and closing the iframe tricks IE7 and earlier to push a
+ // history entry on hash-tag change. When replace is true, we don't
+ // want this.
+ if(!options.replace) this.iframe.document.open().close();
+ this._updateHash(this.iframe.location, fragment, options.replace);
+ }
+
+ // If you've told us that you explicitly don't want fallback hashchange-
+ // based history, then `navigate` becomes a page refresh.
+ } else {
+ return this.location.assign(url);
+ }
+ if (options.trigger) this.loadUrl(fragment);
+ },
+
+ // Update the hash location, either replacing the current entry, or adding
+ // a new one to the browser history.
+ _updateHash: function(location, fragment, replace) {
+ if (replace) {
+ var href = location.href.replace(/(javascript:|#).*$/, '');
+ location.replace(href + '#' + fragment);
+ } else {
+ // Some browsers require that `hash` contains a leading #.
+ location.hash = '#' + fragment;
+ }
+ }
+
+ });
+
+ // Create the default Backbone.history.
+ Backbone.history = new History;
+
+ // Backbone.View
+ // -------------
+
+ // Creating a Backbone.View creates its initial element outside of the DOM,
+ // if an existing element is not provided...
+ var View = Backbone.View = function(options) {
+ this.cid = _.uniqueId('view');
+ this._configure(options || {});
+ this._ensureElement();
+ this.initialize.apply(this, arguments);
+ this.delegateEvents();
+ };
+
+ // Cached regex to split keys for `delegate`.
+ var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+ // List of view options to be merged as properties.
+ var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
+
+ // Set up all inheritable **Backbone.View** properties and methods.
+ _.extend(View.prototype, Events, {
+
+ // The default `tagName` of a View's element is `"div"`.
+ tagName: 'div',
+
+ // jQuery delegate for element lookup, scoped to DOM elements within the
+ // current view. This should be prefered to global lookups where possible.
+ $: function(selector) {
+ return this.$el.find(selector);
+ },
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize: function(){},
+
+ // **render** is the core function that your view should override, in order
+ // to populate its element (`this.el`), with the appropriate HTML. The
+ // convention is for **render** to always return `this`.
+ render: function() {
+ return this;
+ },
+
+ // Remove this view by taking the element out of the DOM, and removing any
+ // applicable Backbone.Events listeners.
+ remove: function() {
+ this.$el.remove();
+ this.stopListening();
+ return this;
+ },
+
+ // For small amounts of DOM Elements, where a full-blown template isn't
+ // needed, use **make** to manufacture elements, one at a time.
+ //
+ // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
+ //
+ make: function(tagName, attributes, content) {
+ var el = document.createElement(tagName);
+ if (attributes) Backbone.$(el).attr(attributes);
+ if (content != null) Backbone.$(el).html(content);
+ return el;
+ },
+
+ // Change the view's element (`this.el` property), including event
+ // re-delegation.
+ setElement: function(element, delegate) {
+ if (this.$el) this.undelegateEvents();
+ this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
+ this.el = this.$el[0];
+ if (delegate !== false) this.delegateEvents();
+ return this;
+ },
+
+ // Set callbacks, where `this.events` is a hash of
+ //
+ // *{"event selector": "callback"}*
+ //
+ // {
+ // 'mousedown .title': 'edit',
+ // 'click .button': 'save'
+ // 'click .open': function(e) { ... }
+ // }
+ //
+ // pairs. Callbacks will be bound to the view, with `this` set properly.
+ // Uses event delegation for efficiency.
+ // Omitting the selector binds the event to `this.el`.
+ // This only works for delegate-able events: not `focus`, `blur`, and
+ // not `change`, `submit`, and `reset` in Internet Explorer.
+ delegateEvents: function(events) {
+ if (!(events || (events = _.result(this, 'events')))) return;
+ this.undelegateEvents();
+ for (var key in events) {
+ var method = events[key];
+ if (!_.isFunction(method)) method = this[events[key]];
+ if (!method) throw new Error('Method "' + events[key] + '" does not exist');
+ var match = key.match(delegateEventSplitter);
+ var eventName = match[1], selector = match[2];
+ method = _.bind(method, this);
+ eventName += '.delegateEvents' + this.cid;
+ if (selector === '') {
+ this.$el.bind(eventName, method);
+ } else {
+ this.$el.delegate(selector, eventName, method);
+ }
+ }
+ },
+
+ // Clears all callbacks previously bound to the view with `delegateEvents`.
+ // You usually don't need to use this, but may wish to if you have multiple
+ // Backbone views attached to the same DOM element.
+ undelegateEvents: function() {
+ this.$el.unbind('.delegateEvents' + this.cid);
+ },
+
+ // Performs the initial configuration of a View with a set of options.
+ // Keys with special meaning *(model, collection, id, className)*, are
+ // attached directly to the view.
+ _configure: function(options) {
+ if (this.options) options = _.extend({}, _.result(this, 'options'), options);
+ _.extend(this, _.pick(options, viewOptions));
+ this.options = options;
+ },
+
+ // Ensure that the View has a DOM element to render into.
+ // If `this.el` is a string, pass it through `$()`, take the first
+ // matching element, and re-assign it to `el`. Otherwise, create
+ // an element from the `id`, `className` and `tagName` properties.
+ _ensureElement: function() {
+ if (!this.el) {
+ var attrs = _.extend({}, _.result(this, 'attributes'));
+ if (this.id) attrs.id = _.result(this, 'id');
+ if (this.className) attrs['class'] = _.result(this, 'className');
+ this.setElement(this.make(_.result(this, 'tagName'), attrs), false);
+ } else {
+ this.setElement(_.result(this, 'el'), false);
+ }
+ }
+
+ });
+
+ // Backbone.sync
+ // -------------
+
+ // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+ var methodMap = {
+ 'create': 'POST',
+ 'update': 'PUT',
+ 'patch': 'PATCH',
+ 'delete': 'DELETE',
+ 'read': 'GET'
+ };
+
+ // Override this function to change the manner in which Backbone persists
+ // models to the server. You will be passed the type of request, and the
+ // model in question. By default, makes a RESTful Ajax request
+ // to the model's `url()`. Some possible customizations could be:
+ //
+ // * Use `setTimeout` to batch rapid-fire updates into a single request.
+ // * Send up the models as XML instead of JSON.
+ // * Persist models via WebSockets instead of Ajax.
+ //
+ // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
+ // as `POST`, with a `_method` parameter containing the true HTTP method,
+ // as well as all requests with the body as `application/x-www-form-urlencoded`
+ // instead of `application/json` with the model in a param named `model`.
+ // Useful when interfacing with server-side languages like **PHP** that make
+ // it difficult to read the body of `PUT` requests.
+ Backbone.sync = function(method, model, options) {
+ var type = methodMap[method];
+
+ // Default options, unless specified.
+ _.defaults(options || (options = {}), {
+ emulateHTTP: Backbone.emulateHTTP,
+ emulateJSON: Backbone.emulateJSON
+ });
+
+ // Default JSON-request options.
+ var params = {type: type, dataType: 'json'};
+
+ // Ensure that we have a URL.
+ if (!options.url) {
+ params.url = _.result(model, 'url') || urlError();
+ }
+
+ // Ensure that we have the appropriate request data.
+ if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
+ params.contentType = 'application/json';
+ params.data = JSON.stringify(options.attrs || model.toJSON(options));
+ }
+
+ // For older servers, emulate JSON by encoding the request into an HTML-form.
+ if (options.emulateJSON) {
+ params.contentType = 'application/x-www-form-urlencoded';
+ params.data = params.data ? {model: params.data} : {};
+ }
+
+ // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
+ // And an `X-HTTP-Method-Override` header.
+ if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
+ params.type = 'POST';
+ if (options.emulateJSON) params.data._method = type;
+ var beforeSend = options.beforeSend;
+ options.beforeSend = function(xhr) {
+ xhr.setRequestHeader('X-HTTP-Method-Override', type);
+ if (beforeSend) return beforeSend.apply(this, arguments);
+ };
+ }
+
+ // Don't process data on a non-GET request.
+ if (params.type !== 'GET' && !options.emulateJSON) {
+ params.processData = false;
+ }
+
+ var success = options.success;
+ options.success = function(resp, status, xhr) {
+ if (success) success(resp, status, xhr);
+ model.trigger('sync', model, resp, options);
+ };
+
+ var error = options.error;
+ options.error = function(xhr, status, thrown) {
+ if (error) error(model, xhr, options);
+ model.trigger('error', model, xhr, options);
+ };
+
+ // Make the request, allowing the user to override any Ajax options.
+ var xhr = Backbone.ajax(_.extend(params, options));
+ model.trigger('request', model, xhr, options);
+ return xhr;
+ };
+
+ // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
+ Backbone.ajax = function() {
+ return Backbone.$.ajax.apply(Backbone.$, arguments);
+ };
+
+ // Helpers
+ // -------
+
+ // Helper function to correctly set up the prototype chain, for subclasses.
+ // Similar to `goog.inherits`, but uses a hash of prototype properties and
+ // class properties to be extended.
+ var extend = function(protoProps, staticProps) {
+ var parent = this;
+ var child;
+
+ // The constructor function for the new subclass is either defined by you
+ // (the "constructor" property in your `extend` definition), or defaulted
+ // by us to simply call the parent's constructor.
+ if (protoProps && _.has(protoProps, 'constructor')) {
+ child = protoProps.constructor;
+ } else {
+ child = function(){ parent.apply(this, arguments); };
+ }
+
+ // Add static properties to the constructor function, if supplied.
+ _.extend(child, parent, staticProps);
+
+ // Set the prototype chain to inherit from `parent`, without calling
+ // `parent`'s constructor function.
+ var Surrogate = function(){ this.constructor = child; };
+ Surrogate.prototype = parent.prototype;
+ child.prototype = new Surrogate;
+
+ // Add prototype properties (instance properties) to the subclass,
+ // if supplied.
+ if (protoProps) _.extend(child.prototype, protoProps);
+
+ // Set a convenience property in case the parent's prototype is needed
+ // later.
+ child.__super__ = parent.prototype;
+
+ return child;
+ };
+
+ // Set up inheritance for the model, collection, router, view and history.
+ Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
+
+ // Throw an error when a URL is needed, and none is supplied.
+ var urlError = function() {
+ throw new Error('A "url" property or function must be specified');
+ };
+
+}).call(this);
diff --git a/src/fauxton/assets/js/libs/bootstrap.js b/src/fauxton/assets/js/libs/bootstrap.js
new file mode 100644
index 000000000..c753bd6f8
--- /dev/null
+++ b/src/fauxton/assets/js/libs/bootstrap.js
@@ -0,0 +1,2025 @@
+/* ===================================================
+ * bootstrap-transition.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
+ * ===================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
+ * ======================================================= */
+
+ $(function () {
+
+ $.support.transition = (function () {
+
+ var transitionEnd = (function () {
+
+ var el = document.createElement('bootstrap')
+ , transEndEventNames = {
+ 'WebkitTransition' : 'webkitTransitionEnd'
+ , 'MozTransition' : 'transitionend'
+ , 'OTransition' : 'oTransitionEnd otransitionend'
+ , 'transition' : 'transitionend'
+ }
+ , name
+
+ for (name in transEndEventNames){
+ if (el.style[name] !== undefined) {
+ return transEndEventNames[name]
+ }
+ }
+
+ }())
+
+ return transitionEnd && {
+ end: transitionEnd
+ }
+
+ })()
+
+ })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-alert.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* ALERT CLASS DEFINITION
+ * ====================== */
+
+ var dismiss = '[data-dismiss="alert"]'
+ , Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ , selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+
+ e && e.preventDefault()
+
+ $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+ $parent.trigger(e = $.Event('close'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ $parent
+ .trigger('closed')
+ .remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent.on($.support.transition.end, removeElement) :
+ removeElement()
+ }
+
+
+ /* ALERT PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.alert = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('alert')
+ if (!data) $this.data('alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.alert.Constructor = Alert
+
+
+ /* ALERT DATA-API
+ * ============== */
+
+ $(document).on('click.alert.data-api', dismiss, Alert.prototype.close)
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-button.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#buttons
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* BUTTON PUBLIC CLASS DEFINITION
+ * ============================== */
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.button.defaults, options)
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ , $el = this.$element
+ , data = $el.data()
+ , val = $el.is('input') ? 'val' : 'html'
+
+ state = state + 'Text'
+ data.resetText || $el.data('resetText', $el[val]())
+
+ $el[val](data[state] || this.options[state])
+
+ // push to event loop to allow forms to submit
+ setTimeout(function () {
+ state == 'loadingText' ?
+ $el.addClass(d).attr(d, d) :
+ $el.removeClass(d).removeAttr(d)
+ }, 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var $parent = this.$element.closest('[data-toggle="buttons-radio"]')
+
+ $parent && $parent
+ .find('.active')
+ .removeClass('active')
+
+ this.$element.toggleClass('active')
+ }
+
+
+ /* BUTTON PLUGIN DEFINITION
+ * ======================== */
+
+ $.fn.button = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('button')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('button', (data = new Button(this, options)))
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ $.fn.button.defaults = {
+ loadingText: 'loading...'
+ }
+
+ $.fn.button.Constructor = Button
+
+
+ /* BUTTON DATA-API
+ * =============== */
+
+ $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ $btn.button('toggle')
+ })
+
+}(window.jQuery);/* ==========================================================
+ * bootstrap-carousel.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#carousel
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CAROUSEL CLASS DEFINITION
+ * ========================= */
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.options = options
+ this.options.slide && this.slide(this.options.slide)
+ this.options.pause == 'hover' && this.$element
+ .on('mouseenter', $.proxy(this.pause, this))
+ .on('mouseleave', $.proxy(this.cycle, this))
+ }
+
+ Carousel.prototype = {
+
+ cycle: function (e) {
+ if (!e) this.paused = false
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+ return this
+ }
+
+ , to: function (pos) {
+ var $active = this.$element.find('.item.active')
+ , children = $active.parent().children()
+ , activePos = children.index($active)
+ , that = this
+
+ if (pos > (children.length - 1) || pos < 0) return
+
+ if (this.sliding) {
+ return this.$element.one('slid', function () {
+ that.to(pos)
+ })
+ }
+
+ if (activePos == pos) {
+ return this.pause().cycle()
+ }
+
+ return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
+ }
+
+ , pause: function (e) {
+ if (!e) this.paused = true
+ if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle()
+ }
+ clearInterval(this.interval)
+ this.interval = null
+ return this
+ }
+
+ , next: function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ , prev: function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ , slide: function (type, next) {
+ var $active = this.$element.find('.item.active')
+ , $next = next || $active[type]()
+ , isCycling = this.interval
+ , direction = type == 'next' ? 'left' : 'right'
+ , fallback = type == 'next' ? 'first' : 'last'
+ , that = this
+ , e
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+ e = $.Event('slide', {
+ relatedTarget: $next[0]
+ })
+
+ if ($next.hasClass('active')) return
+
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ this.$element.one($.support.transition.end, function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () { that.$element.trigger('slid') }, 0)
+ })
+ } else {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger('slid')
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+ }
+
+
+ /* CAROUSEL PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.carousel = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('carousel')
+ , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
+ , action = typeof option == 'string' ? option : options.slide
+ if (!data) $this.data('carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.cycle()
+ })
+ }
+
+ $.fn.carousel.defaults = {
+ interval: 5000
+ , pause: 'hover'
+ }
+
+ $.fn.carousel.Constructor = Carousel
+
+
+ /* CAROUSEL DATA-API
+ * ================= */
+
+ $(document).on('click.carousel.data-api', '[data-slide]', function (e) {
+ var $this = $(this), href
+ , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ , options = $.extend({}, $target.data(), $this.data())
+ $target.carousel(options)
+ e.preventDefault()
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-collapse.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#collapse
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* COLLAPSE PUBLIC CLASS DEFINITION
+ * ================================ */
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.collapse.defaults, options)
+
+ if (this.options.parent) {
+ this.$parent = $(this.options.parent)
+ }
+
+ this.options.toggle && this.toggle()
+ }
+
+ Collapse.prototype = {
+
+ constructor: Collapse
+
+ , dimension: function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ , show: function () {
+ var dimension
+ , scroll
+ , actives
+ , hasData
+
+ if (this.transitioning) return
+
+ dimension = this.dimension()
+ scroll = $.camelCase(['scroll', dimension].join('-'))
+ actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+ if (actives && actives.length) {
+ hasData = actives.data('collapse')
+ if (hasData && hasData.transitioning) return
+ actives.collapse('hide')
+ hasData || actives.data('collapse', null)
+ }
+
+ this.$element[dimension](0)
+ this.transition('addClass', $.Event('show'), 'shown')
+ $.support.transition && this.$element[dimension](this.$element[0][scroll])
+ }
+
+ , hide: function () {
+ var dimension
+ if (this.transitioning) return
+ dimension = this.dimension()
+ this.reset(this.$element[dimension]())
+ this.transition('removeClass', $.Event('hide'), 'hidden')
+ this.$element[dimension](0)
+ }
+
+ , reset: function (size) {
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ [dimension](size || 'auto')
+ [0].offsetWidth
+
+ this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
+
+ return this
+ }
+
+ , transition: function (method, startEvent, completeEvent) {
+ var that = this
+ , complete = function () {
+ if (startEvent.type == 'show') that.reset()
+ that.transitioning = 0
+ that.$element.trigger(completeEvent)
+ }
+
+ this.$element.trigger(startEvent)
+
+ if (startEvent.isDefaultPrevented()) return
+
+ this.transitioning = 1
+
+ this.$element[method]('in')
+
+ $.support.transition && this.$element.hasClass('collapse') ?
+ this.$element.one($.support.transition.end, complete) :
+ complete()
+ }
+
+ , toggle: function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+ }
+
+
+ /* COLLAPSIBLE PLUGIN DEFINITION
+ * ============================== */
+
+ $.fn.collapse = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('collapse')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.collapse.defaults = {
+ toggle: true
+ }
+
+ $.fn.collapse.Constructor = Collapse
+
+
+ /* COLLAPSIBLE DATA-API
+ * ==================== */
+
+ $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
+ var $this = $(this), href
+ , target = $this.attr('data-target')
+ || e.preventDefault()
+ || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+ , option = $(target).data('collapse') ? 'toggle' : $this.data()
+ $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+ $(target).collapse(option)
+ })
+
+}(window.jQuery);/* ============================================================
+ * bootstrap-dropdown.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* DROPDOWN CLASS DEFINITION
+ * ========================= */
+
+ var toggle = '[data-toggle=dropdown]'
+ , Dropdown = function (element) {
+ var $el = $(element).on('click.dropdown.data-api', this.toggle)
+ $('html').on('click.dropdown.data-api', function () {
+ $el.parent().removeClass('open')
+ })
+ }
+
+ Dropdown.prototype = {
+
+ constructor: Dropdown
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+ , isActive
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ $parent.toggleClass('open')
+ $this.focus()
+ }
+
+ return false
+ }
+
+ , keydown: function (e) {
+ var $this
+ , $items
+ , $active
+ , $parent
+ , isActive
+ , index
+
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ if (!isActive || (isActive && e.keyCode == 27)) return $this.click()
+
+ $items = $('[role=menu] li:not(.divider) a', $parent)
+
+ if (!$items.length) return
+
+ index = $items.index($items.filter(':focus'))
+
+ if (e.keyCode == 38 && index > 0) index-- // up
+ if (e.keyCode == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items
+ .eq(index)
+ .focus()
+ }
+
+ }
+
+ function clearMenus() {
+ $(toggle).each(function () {
+ getParent($(this)).removeClass('open')
+ })
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+ $parent.length || ($parent = $this.parent())
+
+ return $parent
+ }
+
+
+ /* DROPDOWN PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.dropdown = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('dropdown')
+ if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ /* APPLY TO STANDARD DROPDOWN ELEMENTS
+ * =================================== */
+
+ $(document)
+ .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus)
+ .on('click.dropdown touchstart.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
+ .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+
+}(window.jQuery);/* =========================================================
+ * bootstrap-modal.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#modals
+ * =========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* MODAL CLASS DEFINITION
+ * ====================== */
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$element = $(element)
+ .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
+ this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
+ }
+
+ Modal.prototype = {
+
+ constructor: Modal
+
+ , toggle: function () {
+ return this[!this.isShown ? 'show' : 'hide']()
+ }
+
+ , show: function () {
+ var that = this
+ , e = $.Event('show')
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = true
+
+ this.escape()
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(document.body) //don't move modals dom position
+ }
+
+ that.$element
+ .show()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+
+ that.enforceFocus()
+
+ transition ?
+ that.$element.one($.support.transition.end, function () { that.$element.focus().trigger('shown') }) :
+ that.$element.focus().trigger('shown')
+
+ })
+ }
+
+ , hide: function (e) {
+ e && e.preventDefault()
+
+ var that = this
+
+ e = $.Event('hide')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ this.escape()
+
+ $(document).off('focusin.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.hideWithTransition() :
+ this.hideModal()
+ }
+
+ , enforceFocus: function () {
+ var that = this
+ $(document).on('focusin.modal', function (e) {
+ if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
+ that.$element.focus()
+ }
+ })
+ }
+
+ , escape: function () {
+ var that = this
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keyup.dismiss.modal', function ( e ) {
+ e.which == 27 && that.hide()
+ })
+ } else if (!this.isShown) {
+ this.$element.off('keyup.dismiss.modal')
+ }
+ }
+
+ , hideWithTransition: function () {
+ var that = this
+ , timeout = setTimeout(function () {
+ that.$element.off($.support.transition.end)
+ that.hideModal()
+ }, 500)
+
+ this.$element.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ that.hideModal()
+ })
+ }
+
+ , hideModal: function (that) {
+ this.$element
+ .hide()
+ .trigger('hidden')
+
+ this.backdrop()
+ }
+
+ , removeBackdrop: function () {
+ this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ , backdrop: function (callback) {
+ var that = this
+ , animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+ .appendTo(document.body)
+
+ this.$backdrop.click(
+ this.options.backdrop == 'static' ?
+ $.proxy(this.$element[0].focus, this.$element[0])
+ : $.proxy(this.hide, this)
+ )
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ doAnimate ?
+ this.$backdrop.one($.support.transition.end, callback) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ $.support.transition && this.$element.hasClass('fade')?
+ this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) :
+ this.removeBackdrop()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+ }
+
+
+ /* MODAL PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.modal = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('modal')
+ , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option]()
+ else if (options.show) data.show()
+ })
+ }
+
+ $.fn.modal.defaults = {
+ backdrop: true
+ , keyboard: true
+ , show: true
+ }
+
+ $.fn.modal.Constructor = Modal
+
+
+ /* MODAL DATA-API
+ * ============== */
+
+ $(document).on('click.modal.data-api', '[data-toggle="modal"]', function (e) {
+ var $this = $(this)
+ , href = $this.attr('href')
+ , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+ , option = $target.data('modal') ? 'toggle' : $.extend({ remote:!/#/.test(href) && href }, $target.data(), $this.data())
+
+ e.preventDefault()
+
+ $target
+ .modal(option)
+ .one('hide', function () {
+ $this.focus()
+ })
+ })
+
+}(window.jQuery);
+/* ===========================================================
+ * bootstrap-tooltip.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Tooltip = function (element, options) {
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.prototype = {
+
+ constructor: Tooltip
+
+ , init: function (type, element, options) {
+ var eventIn
+ , eventOut
+
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.enabled = true
+
+ if (this.options.trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (this.options.trigger != 'manual') {
+ eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
+ eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ , getOptions: function (options) {
+ options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay
+ , hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ , enter: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ clearTimeout(this.timeout)
+ self.hoverState = 'in'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ , leave: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (this.timeout) clearTimeout(this.timeout)
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.hoverState = 'out'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ , show: function () {
+ var $tip
+ , inside
+ , pos
+ , actualWidth
+ , actualHeight
+ , placement
+ , tp
+
+ if (this.hasContent() && this.enabled) {
+ $tip = this.tip()
+ this.setContent()
+
+ if (this.options.animation) {
+ $tip.addClass('fade')
+ }
+
+ placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ inside = /in/.test(placement)
+
+ $tip
+ .detach()
+ .css({ top: 0, left: 0, display: 'block' })
+ .insertAfter(this.$element)
+
+ pos = this.getPosition(inside)
+
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+
+ switch (inside ? placement.split(' ')[1] : placement) {
+ case 'bottom':
+ tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'top':
+ tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'left':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+ break
+ case 'right':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+ break
+ }
+
+ $tip
+ .offset(tp)
+ .addClass(placement)
+ .addClass('in')
+ }
+ }
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ , hide: function () {
+ var that = this
+ , $tip = this.tip()
+
+ $tip.removeClass('in')
+
+ function removeWithAnimation() {
+ var timeout = setTimeout(function () {
+ $tip.off($.support.transition.end).detach()
+ }, 500)
+
+ $tip.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ $tip.detach()
+ })
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ removeWithAnimation() :
+ $tip.detach()
+
+ return this
+ }
+
+ , fixTitle: function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
+ }
+ }
+
+ , hasContent: function () {
+ return this.getTitle()
+ }
+
+ , getPosition: function (inside) {
+ return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
+ width: this.$element[0].offsetWidth
+ , height: this.$element[0].offsetHeight
+ })
+ }
+
+ , getTitle: function () {
+ var title
+ , $e = this.$element
+ , o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ , tip: function () {
+ return this.$tip = this.$tip || $(this.options.template)
+ }
+
+ , validate: function () {
+ if (!this.$element[0].parentNode) {
+ this.hide()
+ this.$element = null
+ this.options = null
+ }
+ }
+
+ , enable: function () {
+ this.enabled = true
+ }
+
+ , disable: function () {
+ this.enabled = false
+ }
+
+ , toggleEnabled: function () {
+ this.enabled = !this.enabled
+ }
+
+ , toggle: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+ self[self.tip().hasClass('in') ? 'hide' : 'show']()
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+ * ========================= */
+
+ $.fn.tooltip = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tooltip')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tooltip.Constructor = Tooltip
+
+ $.fn.tooltip.defaults = {
+ animation: true
+ , placement: 'top'
+ , selector: false
+ , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+ , trigger: 'hover'
+ , title: ''
+ , delay: 0
+ , html: false
+ }
+
+}(window.jQuery);/* ===========================================================
+ * bootstrap-popover.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* POPOVER PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+
+ /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+ ========================================== */
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+ constructor: Popover
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+ , content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content > *')[this.options.html ? 'html' : 'text'](content)
+
+ $tip.removeClass('fade top bottom left right in')
+ }
+
+ , hasContent: function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ , getContent: function () {
+ var content
+ , $e = this.$element
+ , o = this.options
+
+ content = $e.attr('data-content')
+ || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
+
+ return content
+ }
+
+ , tip: function () {
+ if (!this.$tip) {
+ this.$tip = $(this.options.template)
+ }
+ return this.$tip
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ })
+
+
+ /* POPOVER PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.popover = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('popover')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+ $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+ placement: 'right'
+ , trigger: 'click'
+ , content: ''
+ , template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-scrollspy.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#scrollspy
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* SCROLLSPY CLASS DEFINITION
+ * ========================== */
+
+ function ScrollSpy(element, options) {
+ var process = $.proxy(this.process, this)
+ , $element = $(element).is('body') ? $(window) : $(element)
+ , href
+ this.options = $.extend({}, $.fn.scrollspy.defaults, options)
+ this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
+ this.selector = (this.options.target
+ || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ || '') + ' .nav li > a'
+ this.$body = $('body')
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.prototype = {
+
+ constructor: ScrollSpy
+
+ , refresh: function () {
+ var self = this
+ , $targets
+
+ this.offsets = $([])
+ this.targets = $([])
+
+ $targets = this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ , href = $el.data('target') || $el.attr('href')
+ , $href = /^#\w/.test(href) && $(href)
+ return ( $href
+ && $href.length
+ && [[ $href.position().top, href ]] ) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ , process: function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+ , maxScroll = scrollHeight - this.$scrollElement.height()
+ , offsets = this.offsets
+ , targets = this.targets
+ , activeTarget = this.activeTarget
+ , i
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets.last()[0])
+ && this.activate ( i )
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate( targets[i] )
+ }
+ }
+
+ , activate: function (target) {
+ var active
+ , selector
+
+ this.activeTarget = target
+
+ $(this.selector)
+ .parent('.active')
+ .removeClass('active')
+
+ selector = this.selector
+ + '[data-target="' + target + '"],'
+ + this.selector + '[href="' + target + '"]'
+
+ active = $(selector)
+ .parent('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active.closest('li.dropdown').addClass('active')
+ }
+
+ active.trigger('activate')
+ }
+
+ }
+
+
+ /* SCROLLSPY PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.scrollspy = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('scrollspy')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+ $.fn.scrollspy.defaults = {
+ offset: 10
+ }
+
+
+ /* SCROLLSPY DATA-API
+ * ================== */
+
+ $(window).on('load', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ $spy.scrollspy($spy.data())
+ })
+ })
+
+}(window.jQuery);/* ========================================================
+ * bootstrap-tab.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#tabs
+ * ========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TAB CLASS DEFINITION
+ * ==================== */
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.prototype = {
+
+ constructor: Tab
+
+ , show: function () {
+ var $this = this.element
+ , $ul = $this.closest('ul:not(.dropdown-menu)')
+ , selector = $this.attr('data-target')
+ , previous
+ , $target
+ , e
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ if ( $this.parent('li').hasClass('active') ) return
+
+ previous = $ul.find('.active:last a')[0]
+
+ e = $.Event('show', {
+ relatedTarget: previous
+ })
+
+ $this.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $target = $(selector)
+
+ this.activate($this.parent('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $this.trigger({
+ type: 'shown'
+ , relatedTarget: previous
+ })
+ })
+ }
+
+ , activate: function ( element, container, callback) {
+ var $active = container.find('> .active')
+ , transition = callback
+ && $.support.transition
+ && $active.hasClass('fade')
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+
+ element.addClass('active')
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if ( element.parent('.dropdown-menu') ) {
+ element.closest('li.dropdown').addClass('active')
+ }
+
+ callback && callback()
+ }
+
+ transition ?
+ $active.one($.support.transition.end, next) :
+ next()
+
+ $active.removeClass('in')
+ }
+ }
+
+
+ /* TAB PLUGIN DEFINITION
+ * ===================== */
+
+ $.fn.tab = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tab')
+ if (!data) $this.data('tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tab.Constructor = Tab
+
+
+ /* TAB DATA-API
+ * ============ */
+
+ $(document).on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+ e.preventDefault()
+ $(this).tab('show')
+ })
+
+}(window.jQuery);/* =============================================================
+ * bootstrap-typeahead.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+ "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+ * ================================= */
+
+ var Typeahead = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.typeahead.defaults, options)
+ 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.$menu = $(this.options.menu).appendTo('body')
+ this.source = this.options.source
+ this.shown = false
+ this.listen()
+ }
+
+ Typeahead.prototype = {
+
+ constructor: Typeahead
+
+ , select: function () {
+ var val = this.$menu.find('.active').attr('data-value')
+ this.$element
+ .val(this.updater(val))
+ .change()
+ return this.hide()
+ }
+
+ , updater: function (item) {
+ return item
+ }
+
+ , show: function () {
+ var pos = $.extend({}, this.$element.offset(), {
+ height: this.$element[0].offsetHeight
+ })
+
+ this.$menu.css({
+ top: pos.top + pos.height
+ , left: pos.left
+ })
+
+ this.$menu.show()
+ this.shown = true
+ return this
+ }
+
+ , hide: function () {
+ this.$menu.hide()
+ this.shown = false
+ return this
+ }
+
+ , lookup: function (event) {
+ var items
+
+ this.query = this.$element.val()
+
+ if (!this.query || this.query.length < this.options.minLength) {
+ return this.shown ? this.hide() : this
+ }
+
+ items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+ return items ? this.process(items) : this
+ }
+
+ , process: function (items) {
+ var that = this
+
+ items = $.grep(items, function (item) {
+ return that.matcher(item)
+ })
+
+ items = this.sorter(items)
+
+ if (!items.length) {
+ return this.shown ? this.hide() : this
+ }
+
+ return this.render(items.slice(0, this.options.items)).show()
+ }
+
+ , matcher: function (item) {
+ return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+ }
+
+ , sorter: function (items) {
+ var beginswith = []
+ , caseSensitive = []
+ , caseInsensitive = []
+ , item
+
+ while (item = items.shift()) {
+ if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+ else if (~item.indexOf(this.query)) caseSensitive.push(item)
+ else caseInsensitive.push(item)
+ }
+
+ return beginswith.concat(caseSensitive, caseInsensitive)
+ }
+
+ , highlighter: function (item) {
+ var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+ return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+ return '<strong>' + match + '</strong>'
+ })
+ }
+
+ , render: function (items) {
+ var that = this
+
+ items = $(items).map(function (i, item) {
+ i = $(that.options.item).attr('data-value', item)
+ i.find('a').html(that.highlighter(item))
+ return i[0]
+ })
+
+ items.first().addClass('active')
+ this.$menu.html(items)
+ return this
+ }
+
+ , next: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , next = active.next()
+
+ if (!next.length) {
+ next = $(this.$menu.find('li')[0])
+ }
+
+ next.addClass('active')
+ }
+
+ , prev: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , prev = active.prev()
+
+ if (!prev.length) {
+ prev = this.$menu.find('li').last()
+ }
+
+ prev.addClass('active')
+ }
+
+ , listen: function () {
+ this.$element
+ .on('blur', $.proxy(this.blur, this))
+ .on('keypress', $.proxy(this.keypress, this))
+ .on('keyup', $.proxy(this.keyup, this))
+
+ if (this.eventSupported('keydown')) {
+ this.$element.on('keydown', $.proxy(this.keydown, this))
+ }
+
+ this.$menu
+ .on('click', $.proxy(this.click, this))
+ .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+ }
+
+ , eventSupported: function(eventName) {
+ var isSupported = eventName in this.$element
+ if (!isSupported) {
+ this.$element.setAttribute(eventName, 'return;')
+ isSupported = typeof this.$element[eventName] === 'function'
+ }
+ return isSupported
+ }
+
+ , move: function (e) {
+ if (!this.shown) return
+
+ switch(e.keyCode) {
+ case 9: // tab
+ case 13: // enter
+ case 27: // escape
+ e.preventDefault()
+ break
+
+ case 38: // up arrow
+ e.preventDefault()
+ this.prev()
+ break
+
+ case 40: // down arrow
+ e.preventDefault()
+ this.next()
+ break
+ }
+
+ e.stopPropagation()
+ }
+
+ , keydown: function (e) {
+ this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
+ this.move(e)
+ }
+
+ , keypress: function (e) {
+ if (this.suppressKeyPressRepeat) return
+ this.move(e)
+ }
+
+ , keyup: function (e) {
+ switch(e.keyCode) {
+ case 40: // down arrow
+ case 38: // up arrow
+ case 16: // shift
+ case 17: // ctrl
+ case 18: // alt
+ break
+
+ case 9: // tab
+ case 13: // enter
+ if (!this.shown) return
+ this.select()
+ break
+
+ case 27: // escape
+ if (!this.shown) return
+ this.hide()
+ break
+
+ default:
+ this.lookup()
+ }
+
+ e.stopPropagation()
+ e.preventDefault()
+ }
+
+ , blur: function (e) {
+ var that = this
+ setTimeout(function () { that.hide() }, 150)
+ }
+
+ , click: function (e) {
+ e.stopPropagation()
+ e.preventDefault()
+ this.select()
+ }
+
+ , mouseenter: function (e) {
+ this.$menu.find('.active').removeClass('active')
+ $(e.currentTarget).addClass('active')
+ }
+
+ }
+
+
+ /* TYPEAHEAD PLUGIN DEFINITION
+ * =========================== */
+
+ $.fn.typeahead = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('typeahead')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.typeahead.defaults = {
+ source: []
+ , items: 8
+ , menu: '<ul class="typeahead dropdown-menu"></ul>'
+ , item: '<li><a href="#"></a></li>'
+ , minLength: 1
+ }
+
+ $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD DATA-API
+ * ================== */
+
+ $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+ var $this = $(this)
+ if ($this.data('typeahead')) return
+ e.preventDefault()
+ $this.typeahead($this.data())
+ })
+
+}(window.jQuery);
+/* ==========================================================
+ * bootstrap-affix.js v2.2.1
+ * http://twitter.github.com/bootstrap/javascript.html#affix
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* AFFIX CLASS DEFINITION
+ * ====================== */
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, $.fn.affix.defaults, options)
+ this.$window = $(window)
+ .on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
+ .on('click.affix.data-api', $.proxy(function () { setTimeout($.proxy(this.checkPosition, this), 1) }, this))
+ this.$element = $(element)
+ this.checkPosition()
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var scrollHeight = $(document).height()
+ , scrollTop = this.$window.scrollTop()
+ , position = this.$element.offset()
+ , offset = this.options.offset
+ , offsetBottom = offset.bottom
+ , offsetTop = offset.top
+ , reset = 'affix affix-top affix-bottom'
+ , affix
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top()
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+ affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
+ false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
+ 'bottom' : offsetTop != null && scrollTop <= offsetTop ?
+ 'top' : false
+
+ if (this.affixed === affix) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+ this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
+ }
+
+
+ /* AFFIX PLUGIN DEFINITION
+ * ======================= */
+
+ $.fn.affix = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('affix')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.affix.Constructor = Affix
+
+ $.fn.affix.defaults = {
+ offset: 0
+ }
+
+
+ /* AFFIX DATA-API
+ * ============== */
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ , data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ data.offsetBottom && (data.offset.bottom = data.offsetBottom)
+ data.offsetTop && (data.offset.top = data.offsetTop)
+
+ $spy.affix(data)
+ })
+ })
+
+
+}(window.jQuery); \ No newline at end of file
diff --git a/src/fauxton/assets/js/libs/codemirror.js b/src/fauxton/assets/js/libs/codemirror.js
new file mode 100644
index 000000000..532dd8974
--- /dev/null
+++ b/src/fauxton/assets/js/libs/codemirror.js
@@ -0,0 +1,3231 @@
+// CodeMirror version 2.32
+//
+// All functions that need access to the editor's state live inside
+// the CodeMirror function. Below that, at the bottom of the file,
+// some utilities are defined.
+
+// CodeMirror is the only global var we claim
+var CodeMirror = (function() {
+ // This is the function that produces an editor instance. Its
+ // closure is used to store the editor state.
+ function CodeMirror(place, givenOptions) {
+ // Determine effective options based on given values and defaults.
+ var options = {}, defaults = CodeMirror.defaults;
+ for (var opt in defaults)
+ if (defaults.hasOwnProperty(opt))
+ options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];
+
+ // The element in which the editor lives.
+ var wrapper = document.createElement("div");
+ wrapper.className = "CodeMirror" + (options.lineWrapping ? " CodeMirror-wrap" : "");
+ // This mess creates the base DOM structure for the editor.
+ wrapper.innerHTML =
+ '<div style="overflow: hidden; position: relative; width: 3px; height: 0px;">' + // Wraps and hides input textarea
+ '<textarea style="position: absolute; padding: 0; width: 1px; height: 1em" wrap="off" ' +
+ 'autocorrect="off" autocapitalize="off"></textarea></div>' +
+ '<div class="CodeMirror-scrollbar">' + // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself.
+ '<div class="CodeMirror-scrollbar-inner">' + // The empty scrollbar content, used solely for managing the scrollbar thumb.
+ '</div></div>' + // This must be before the scroll area because it's float-right.
+ '<div class="CodeMirror-scroll" tabindex="-1">' +
+ '<div style="position: relative">' + // Set to the height of the text, causes scrolling
+ '<div style="position: relative">' + // Moved around its parent to cover visible view
+ '<div class="CodeMirror-gutter"><div class="CodeMirror-gutter-text"></div></div>' +
+ // Provides positioning relative to (visible) text origin
+ '<div class="CodeMirror-lines"><div style="position: relative; z-index: 0">' +
+ // Used to measure text size
+ '<div style="position: absolute; width: 100%; height: 0px; overflow: hidden; visibility: hidden;"></div>' +
+ '<pre class="CodeMirror-cursor">&#160;</pre>' + // Absolutely positioned blinky cursor
+ '<pre class="CodeMirror-cursor" style="visibility: hidden">&#160;</pre>' + // Used to force a width
+ '<div style="position: relative; z-index: -1"></div><div></div>' + // DIVs containing the selection and the actual code
+ '</div></div></div></div></div>';
+ if (place.appendChild) place.appendChild(wrapper); else place(wrapper);
+ // I've never seen more elegant code in my life.
+ var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,
+ scroller = wrapper.lastChild, code = scroller.firstChild,
+ mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,
+ lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,
+ cursor = measure.nextSibling, widthForcer = cursor.nextSibling,
+ selectionDiv = widthForcer.nextSibling, lineDiv = selectionDiv.nextSibling,
+ scrollbar = inputDiv.nextSibling, scrollbarInner = scrollbar.firstChild;
+ themeChanged(); keyMapChanged();
+ // Needed to hide big blue blinking cursor on Mobile Safari
+ if (ios) input.style.width = "0px";
+ if (!webkit) scroller.draggable = true;
+ lineSpace.style.outline = "none";
+ if (options.tabindex != null) input.tabIndex = options.tabindex;
+ if (options.autofocus) focusInput();
+ if (!options.gutter && !options.lineNumbers) gutter.style.display = "none";
+ // Needed to handle Tab key in KHTML
+ if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute";
+
+ // Check for OS X >= 10.7. If so, we need to force a width on the scrollbar, and
+ // make it overlap the content. (But we only do this if the scrollbar doesn't already
+ // have a natural width. If the mouse is plugged in or the user sets the system pref
+ // to always show scrollbars, the scrollbar shouldn't overlap.)
+ if (mac_geLion) {
+ scrollbar.className += (overlapScrollbars() ? " cm-sb-overlap" : " cm-sb-nonoverlap");
+ } else if (ie_lt8) {
+ // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8).
+ scrollbar.className += " cm-sb-ie7";
+ }
+
+ // Check for problem with IE innerHTML not working when we have a
+ // P (or similar) parent node.
+ try { stringWidth("x"); }
+ catch (e) {
+ if (e.message.match(/runtime/i))
+ e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)");
+ throw e;
+ }
+
+ // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.
+ var poll = new Delayed(), highlight = new Delayed(), blinker;
+
+ // mode holds a mode API object. doc is the tree of Line objects,
+ // work an array of lines that should be parsed, and history the
+ // undo history (instance of History constructor).
+ var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused;
+ loadMode();
+ // The selection. These are always maintained to point at valid
+ // positions. Inverted is used to remember that the user is
+ // selecting bottom-to-top.
+ var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};
+ // Selection-related flags. shiftSelecting obviously tracks
+ // whether the user is holding shift.
+ var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, lastScrollLeft = 0, draggingText,
+ overwrite = false, suppressEdits = false;
+ // Variables used by startOperation/endOperation to track what
+ // happened during the operation.
+ var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,
+ gutterDirty, callbacks;
+ // Current visible range (may be bigger than the view window).
+ var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;
+ // bracketHighlighted is used to remember that a bracket has been
+ // marked.
+ var bracketHighlighted;
+ // Tracks the maximum line length so that the horizontal scrollbar
+ // can be kept static when scrolling.
+ var maxLine = "", updateMaxLine = false, maxLineChanged = true;
+ var tabCache = {};
+
+ // Initialize the content.
+ operation(function(){setValue(options.value || ""); updateInput = false;})();
+ var history = new History();
+
+ // Register our event handlers.
+ connect(scroller, "mousedown", operation(onMouseDown));
+ connect(scroller, "dblclick", operation(onDoubleClick));
+ connect(lineSpace, "selectstart", e_preventDefault);
+ // Gecko browsers fire contextmenu *after* opening the menu, at
+ // which point we can't mess with it anymore. Context menu is
+ // handled in onMouseDown for Gecko.
+ if (!gecko) connect(scroller, "contextmenu", onContextMenu);
+ connect(scroller, "scroll", onScroll);
+ connect(scrollbar, "scroll", onScroll);
+ connect(scrollbar, "mousedown", function() {if (focused) setTimeout(focusInput, 0);});
+ connect(scroller, "mousewheel", onMouseWheel);
+ connect(scroller, "DOMMouseScroll", onMouseWheel);
+ connect(window, "resize", function() {updateDisplay(true);});
+ connect(input, "keyup", operation(onKeyUp));
+ connect(input, "input", fastPoll);
+ connect(input, "keydown", operation(onKeyDown));
+ connect(input, "keypress", operation(onKeyPress));
+ connect(input, "focus", onFocus);
+ connect(input, "blur", onBlur);
+
+ if (options.dragDrop) {
+ connect(scroller, "dragstart", onDragStart);
+ function drag_(e) {
+ if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
+ e_stop(e);
+ }
+ connect(scroller, "dragenter", drag_);
+ connect(scroller, "dragover", drag_);
+ connect(scroller, "drop", operation(onDrop));
+ }
+ connect(scroller, "paste", function(){focusInput(); fastPoll();});
+ connect(input, "paste", fastPoll);
+ connect(input, "cut", operation(function(){
+ if (!options.readOnly) replaceSelection("");
+ }));
+
+ // Needed to handle Tab key in KHTML
+ if (khtml) connect(code, "mouseup", function() {
+ if (document.activeElement == input) input.blur();
+ focusInput();
+ });
+
+ // IE throws unspecified error in certain cases, when
+ // trying to access activeElement before onload
+ var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { }
+ if (hasFocus || options.autofocus) setTimeout(onFocus, 20);
+ else onBlur();
+
+ function isLine(l) {return l >= 0 && l < doc.size;}
+ // The instance object that we'll return. Mostly calls out to
+ // local functions in the CodeMirror function. Some do some extra
+ // range checking and/or clipping. operation is used to wrap the
+ // call so that changes it makes are tracked, and the display is
+ // updated afterwards.
+ var instance = wrapper.CodeMirror = {
+ getValue: getValue,
+ setValue: operation(setValue),
+ getSelection: getSelection,
+ replaceSelection: operation(replaceSelection),
+ focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();},
+ setOption: function(option, value) {
+ var oldVal = options[option];
+ options[option] = value;
+ if (option == "mode" || option == "indentUnit") loadMode();
+ else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();}
+ else if (option == "readOnly" && !value) {resetInput(true);}
+ else if (option == "theme") themeChanged();
+ else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)();
+ else if (option == "tabSize") updateDisplay(true);
+ else if (option == "keyMap") keyMapChanged();
+ if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") {
+ gutterChanged();
+ updateDisplay(true);
+ }
+ },
+ getOption: function(option) {return options[option];},
+ undo: operation(undo),
+ redo: operation(redo),
+ indentLine: operation(function(n, dir) {
+ if (typeof dir != "string") {
+ if (dir == null) dir = options.smartIndent ? "smart" : "prev";
+ else dir = dir ? "add" : "subtract";
+ }
+ if (isLine(n)) indentLine(n, dir);
+ }),
+ indentSelection: operation(indentSelected),
+ historySize: function() {return {undo: history.done.length, redo: history.undone.length};},
+ clearHistory: function() {history = new History();},
+ setHistory: function(histData) {
+ history = new History();
+ history.done = histData.done;
+ history.undone = histData.undone;
+ },
+ getHistory: function() {
+ history.time = 0;
+ return {done: history.done.concat([]), undone: history.undone.concat([])};
+ },
+ matchBrackets: operation(function(){matchBrackets(true);}),
+ getTokenAt: operation(function(pos) {
+ pos = clipPos(pos);
+ return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);
+ }),
+ getStateAfter: function(line) {
+ line = clipLine(line == null ? doc.size - 1: line);
+ return getStateBefore(line + 1);
+ },
+ cursorCoords: function(start, mode) {
+ if (start == null) start = sel.inverted;
+ return this.charCoords(start ? sel.from : sel.to, mode);
+ },
+ charCoords: function(pos, mode) {
+ pos = clipPos(pos);
+ if (mode == "local") return localCoords(pos, false);
+ if (mode == "div") return localCoords(pos, true);
+ return pageCoords(pos);
+ },
+ coordsChar: function(coords) {
+ var off = eltOffset(lineSpace);
+ return coordsChar(coords.x - off.left, coords.y - off.top);
+ },
+ markText: operation(markText),
+ setBookmark: setBookmark,
+ findMarksAt: findMarksAt,
+ setMarker: operation(addGutterMarker),
+ clearMarker: operation(removeGutterMarker),
+ setLineClass: operation(setLineClass),
+ hideLine: operation(function(h) {return setLineHidden(h, true);}),
+ showLine: operation(function(h) {return setLineHidden(h, false);}),
+ onDeleteLine: function(line, f) {
+ if (typeof line == "number") {
+ if (!isLine(line)) return null;
+ line = getLine(line);
+ }
+ (line.handlers || (line.handlers = [])).push(f);
+ return line;
+ },
+ lineInfo: lineInfo,
+ addWidget: function(pos, node, scroll, vert, horiz) {
+ pos = localCoords(clipPos(pos));
+ var top = pos.yBot, left = pos.x;
+ node.style.position = "absolute";
+ code.appendChild(node);
+ if (vert == "over") top = pos.y;
+ else if (vert == "near") {
+ var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),
+ hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();
+ if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)
+ top = pos.y - node.offsetHeight;
+ if (left + node.offsetWidth > hspace)
+ left = hspace - node.offsetWidth;
+ }
+ node.style.top = (top + paddingTop()) + "px";
+ node.style.left = node.style.right = "";
+ if (horiz == "right") {
+ left = code.clientWidth - node.offsetWidth;
+ node.style.right = "0px";
+ } else {
+ if (horiz == "left") left = 0;
+ else if (horiz == "middle") left = (code.clientWidth - node.offsetWidth) / 2;
+ node.style.left = (left + paddingLeft()) + "px";
+ }
+ if (scroll)
+ scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);
+ },
+
+ lineCount: function() {return doc.size;},
+ clipPos: clipPos,
+ getCursor: function(start) {
+ if (start == null) start = sel.inverted;
+ return copyPos(start ? sel.from : sel.to);
+ },
+ somethingSelected: function() {return !posEq(sel.from, sel.to);},
+ setCursor: operation(function(line, ch, user) {
+ if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user);
+ else setCursor(line, ch, user);
+ }),
+ setSelection: operation(function(from, to, user) {
+ (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));
+ }),
+ getLine: function(line) {if (isLine(line)) return getLine(line).text;},
+ getLineHandle: function(line) {if (isLine(line)) return getLine(line);},
+ setLine: operation(function(line, text) {
+ if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});
+ }),
+ removeLine: operation(function(line) {
+ if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));
+ }),
+ replaceRange: operation(replaceRange),
+ getRange: function(from, to, lineSep) {return getRange(clipPos(from), clipPos(to), lineSep);},
+
+ triggerOnKeyDown: operation(onKeyDown),
+ execCommand: function(cmd) {return commands[cmd](instance);},
+ // Stuff used by commands, probably not much use to outside code.
+ moveH: operation(moveH),
+ deleteH: operation(deleteH),
+ moveV: operation(moveV),
+ toggleOverwrite: function() {
+ if(overwrite){
+ overwrite = false;
+ cursor.className = cursor.className.replace(" CodeMirror-overwrite", "");
+ } else {
+ overwrite = true;
+ cursor.className += " CodeMirror-overwrite";
+ }
+ },
+
+ posFromIndex: function(off) {
+ var lineNo = 0, ch;
+ doc.iter(0, doc.size, function(line) {
+ var sz = line.text.length + 1;
+ if (sz > off) { ch = off; return true; }
+ off -= sz;
+ ++lineNo;
+ });
+ return clipPos({line: lineNo, ch: ch});
+ },
+ indexFromPos: function (coords) {
+ if (coords.line < 0 || coords.ch < 0) return 0;
+ var index = coords.ch;
+ doc.iter(0, coords.line, function (line) {
+ index += line.text.length + 1;
+ });
+ return index;
+ },
+ scrollTo: function(x, y) {
+ if (x != null) scroller.scrollLeft = x;
+ if (y != null) scrollbar.scrollTop = y;
+ updateDisplay([]);
+ },
+ getScrollInfo: function() {
+ return {x: scroller.scrollLeft, y: scrollbar.scrollTop,
+ height: scrollbar.scrollHeight, width: scroller.scrollWidth};
+ },
+ setSize: function(width, height) {
+ function interpret(val) {
+ val = String(val);
+ return /^\d+$/.test(val) ? val + "px" : val;
+ }
+ if (width != null) wrapper.style.width = interpret(width);
+ if (height != null) scroller.style.height = interpret(height);
+ },
+
+ operation: function(f){return operation(f)();},
+ compoundChange: function(f){return compoundChange(f);},
+ refresh: function(){
+ updateDisplay(true, null, lastScrollTop);
+ if (scrollbar.scrollHeight > lastScrollTop)
+ scrollbar.scrollTop = lastScrollTop;
+ },
+ getInputField: function(){return input;},
+ getWrapperElement: function(){return wrapper;},
+ getScrollerElement: function(){return scroller;},
+ getGutterElement: function(){return gutter;}
+ };
+
+ function getLine(n) { return getLineAt(doc, n); }
+ function updateLineHeight(line, height) {
+ gutterDirty = true;
+ var diff = height - line.height;
+ for (var n = line; n; n = n.parent) n.height += diff;
+ }
+
+ function setValue(code) {
+ var top = {line: 0, ch: 0};
+ updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},
+ splitLines(code), top, top);
+ updateInput = true;
+ }
+ function getValue(lineSep) {
+ var text = [];
+ doc.iter(0, doc.size, function(line) { text.push(line.text); });
+ return text.join(lineSep || "\n");
+ }
+
+ function onScroll(e) {
+ if (scroller.scrollTop) {
+ scrollbar.scrollTop += scroller.scrollTop;
+ scroller.scrollTop = 0;
+ }
+ if (lastScrollTop != scrollbar.scrollTop || lastScrollLeft != scroller.scrollLeft) {
+ lastScrollTop = scrollbar.scrollTop;
+ lastScrollLeft = scroller.scrollLeft;
+ updateDisplay([]);
+ if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px";
+ if (options.onScroll) options.onScroll(instance);
+ }
+ }
+
+ function onMouseDown(e) {
+ setShift(e_prop(e, "shiftKey"));
+ // Check whether this is a click in a widget
+ for (var n = e_target(e); n != wrapper; n = n.parentNode)
+ if (n.parentNode == code && n != mover) return;
+
+ // See if this is a click in the gutter
+ for (var n = e_target(e); n != wrapper; n = n.parentNode)
+ if (n.parentNode == gutterText) {
+ if (options.onGutterClick)
+ options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);
+ return e_preventDefault(e);
+ }
+
+ var start = posFromMouse(e);
+
+ switch (e_button(e)) {
+ case 3:
+ if (gecko) onContextMenu(e);
+ return;
+ case 2:
+ if (start) setCursor(start.line, start.ch, true);
+ setTimeout(focusInput, 20);
+ e_preventDefault(e);
+ return;
+ }
+ // For button 1, if it was clicked inside the editor
+ // (posFromMouse returning non-null), we have to adjust the
+ // selection.
+ if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}
+
+ if (!focused) onFocus();
+
+ var now = +new Date, type = "single";
+ if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {
+ type = "triple";
+ e_preventDefault(e);
+ setTimeout(focusInput, 20);
+ selectLine(start.line);
+ } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {
+ type = "double";
+ lastDoubleClick = {time: now, pos: start};
+ e_preventDefault(e);
+ var word = findWordAt(start);
+ setSelectionUser(word.from, word.to);
+ } else { lastClick = {time: now, pos: start}; }
+
+ var last = start, going;
+ if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) &&
+ !posLess(start, sel.from) && !posLess(sel.to, start) && type == "single") {
+ // Let the drag handler handle this.
+ if (webkit) scroller.draggable = true;
+ function dragEnd(e2) {
+ if (webkit) scroller.draggable = false;
+ draggingText = false;
+ up(); drop();
+ if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {
+ e_preventDefault(e2);
+ setCursor(start.line, start.ch, true);
+ focusInput();
+ }
+ }
+ var up = connect(document, "mouseup", operation(dragEnd), true);
+ var drop = connect(scroller, "drop", operation(dragEnd), true);
+ draggingText = true;
+ // IE's approach to draggable
+ if (scroller.dragDrop) scroller.dragDrop();
+ return;
+ }
+ e_preventDefault(e);
+ if (type == "single") setCursor(start.line, start.ch, true);
+
+ var startstart = sel.from, startend = sel.to;
+
+ function doSelect(cur) {
+ if (type == "single") {
+ setSelectionUser(start, cur);
+ } else if (type == "double") {
+ var word = findWordAt(cur);
+ if (posLess(cur, startstart)) setSelectionUser(word.from, startend);
+ else setSelectionUser(startstart, word.to);
+ } else if (type == "triple") {
+ if (posLess(cur, startstart)) setSelectionUser(startend, clipPos({line: cur.line, ch: 0}));
+ else setSelectionUser(startstart, clipPos({line: cur.line + 1, ch: 0}));
+ }
+ }
+
+ function extend(e) {
+ var cur = posFromMouse(e, true);
+ if (cur && !posEq(cur, last)) {
+ if (!focused) onFocus();
+ last = cur;
+ doSelect(cur);
+ updateInput = false;
+ var visible = visibleLines();
+ if (cur.line >= visible.to || cur.line < visible.from)
+ going = setTimeout(operation(function(){extend(e);}), 150);
+ }
+ }
+
+ function done(e) {
+ clearTimeout(going);
+ var cur = posFromMouse(e);
+ if (cur) doSelect(cur);
+ e_preventDefault(e);
+ focusInput();
+ updateInput = true;
+ move(); up();
+ }
+ var move = connect(document, "mousemove", operation(function(e) {
+ clearTimeout(going);
+ e_preventDefault(e);
+ if (!ie && !e_button(e)) done(e);
+ else extend(e);
+ }), true);
+ var up = connect(document, "mouseup", operation(done), true);
+ }
+ function onDoubleClick(e) {
+ for (var n = e_target(e); n != wrapper; n = n.parentNode)
+ if (n.parentNode == gutterText) return e_preventDefault(e);
+ e_preventDefault(e);
+ }
+ function onDrop(e) {
+ if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return;
+ e.preventDefault();
+ var pos = posFromMouse(e, true), files = e.dataTransfer.files;
+ if (!pos || options.readOnly) return;
+ if (files && files.length && window.FileReader && window.File) {
+ function loadFile(file, i) {
+ var reader = new FileReader;
+ reader.onload = function() {
+ text[i] = reader.result;
+ if (++read == n) {
+ pos = clipPos(pos);
+ operation(function() {
+ var end = replaceRange(text.join(""), pos, pos);
+ setSelectionUser(pos, end);
+ })();
+ }
+ };
+ reader.readAsText(file);
+ }
+ var n = files.length, text = Array(n), read = 0;
+ for (var i = 0; i < n; ++i) loadFile(files[i], i);
+ } else {
+ // Don't do a replace if the drop happened inside of the selected text.
+ if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return;
+ try {
+ var text = e.dataTransfer.getData("Text");
+ if (text) {
+ compoundChange(function() {
+ var curFrom = sel.from, curTo = sel.to;
+ setSelectionUser(pos, pos);
+ if (draggingText) replaceRange("", curFrom, curTo);
+ replaceSelection(text);
+ focusInput();
+ });
+ }
+ }
+ catch(e){}
+ }
+ }
+ function onDragStart(e) {
+ var txt = getSelection();
+ e.dataTransfer.setData("Text", txt);
+
+ // Use dummy image instead of default browsers image.
+ if (gecko || chrome || opera) {
+ var img = document.createElement('img');
+ img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image
+ e.dataTransfer.setDragImage(img, 0, 0);
+ }
+ }
+
+ function doHandleBinding(bound, dropShift) {
+ if (typeof bound == "string") {
+ bound = commands[bound];
+ if (!bound) return false;
+ }
+ var prevShift = shiftSelecting;
+ try {
+ if (options.readOnly) suppressEdits = true;
+ if (dropShift) shiftSelecting = null;
+ bound(instance);
+ } catch(e) {
+ if (e != Pass) throw e;
+ return false;
+ } finally {
+ shiftSelecting = prevShift;
+ suppressEdits = false;
+ }
+ return true;
+ }
+ function handleKeyBinding(e) {
+ // Handle auto keymap transitions
+ var startMap = getKeyMap(options.keyMap), next = startMap.auto;
+ clearTimeout(maybeTransition);
+ if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() {
+ if (getKeyMap(options.keyMap) == startMap) {
+ options.keyMap = (next.call ? next.call(null, instance) : next);
+ }
+ }, 50);
+
+ var name = keyNames[e_prop(e, "keyCode")], handled = false;
+ if (name == null || e.altGraphKey) return false;
+ if (e_prop(e, "altKey")) name = "Alt-" + name;
+ if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name;
+ if (e_prop(e, "metaKey")) name = "Cmd-" + name;
+
+ var stopped = false;
+ function stop() { stopped = true; }
+
+ if (e_prop(e, "shiftKey")) {
+ handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap,
+ function(b) {return doHandleBinding(b, true);}, stop)
+ || lookupKey(name, options.extraKeys, options.keyMap, function(b) {
+ if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b);
+ }, stop);
+ } else {
+ handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop);
+ }
+ if (stopped) handled = false;
+ if (handled) {
+ e_preventDefault(e);
+ restartBlink();
+ if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; }
+ }
+ return handled;
+ }
+ function handleCharBinding(e, ch) {
+ var handled = lookupKey("'" + ch + "'", options.extraKeys,
+ options.keyMap, function(b) { return doHandleBinding(b, true); });
+ if (handled) {
+ e_preventDefault(e);
+ restartBlink();
+ }
+ return handled;
+ }
+
+ var lastStoppedKey = null, maybeTransition;
+ function onKeyDown(e) {
+ if (!focused) onFocus();
+ if (ie && e.keyCode == 27) { e.returnValue = false; }
+ if (pollingFast) { if (readInput()) pollingFast = false; }
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
+ var code = e_prop(e, "keyCode");
+ // IE does strange things with escape.
+ setShift(code == 16 || e_prop(e, "shiftKey"));
+ // First give onKeyEvent option a chance to handle this.
+ var handled = handleKeyBinding(e);
+ if (opera) {
+ lastStoppedKey = handled ? code : null;
+ // Opera has no cut event... we try to at least catch the key combo
+ if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey"))
+ replaceSelection("");
+ }
+ }
+ function onKeyPress(e) {
+ if (pollingFast) readInput();
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
+ var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode");
+ if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}
+ if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return;
+ var ch = String.fromCharCode(charCode == null ? keyCode : charCode);
+ if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) {
+ if (mode.electricChars.indexOf(ch) > -1)
+ setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75);
+ }
+ if (handleCharBinding(e, ch)) return;
+ fastPoll();
+ }
+ function onKeyUp(e) {
+ if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;
+ if (e_prop(e, "keyCode") == 16) shiftSelecting = null;
+ }
+
+ function onFocus() {
+ if (options.readOnly == "nocursor") return;
+ if (!focused) {
+ if (options.onFocus) options.onFocus(instance);
+ focused = true;
+ if (scroller.className.search(/\bCodeMirror-focused\b/) == -1)
+ scroller.className += " CodeMirror-focused";
+ if (!leaveInputAlone) resetInput(true);
+ }
+ slowPoll();
+ restartBlink();
+ }
+ function onBlur() {
+ if (focused) {
+ if (options.onBlur) options.onBlur(instance);
+ focused = false;
+ if (bracketHighlighted)
+ operation(function(){
+ if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; }
+ })();
+ scroller.className = scroller.className.replace(" CodeMirror-focused", "");
+ }
+ clearInterval(blinker);
+ setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);
+ }
+
+ function chopDelta(delta) {
+ // Make sure we always scroll a little bit for any nonzero delta.
+ if (delta > 0.0 && delta < 1.0) return 1;
+ else if (delta > -1.0 && delta < 0.0) return -1;
+ else return Math.round(delta);
+ }
+
+ function onMouseWheel(e) {
+ var deltaX = 0, deltaY = 0;
+ if (e.type == "DOMMouseScroll") { // Firefox
+ var delta = -e.detail * 8.0;
+ if (e.axis == e.HORIZONTAL_AXIS) deltaX = delta;
+ else if (e.axis == e.VERTICAL_AXIS) deltaY = delta;
+ } else if (e.wheelDeltaX !== undefined && e.wheelDeltaY !== undefined) { // WebKit
+ deltaX = e.wheelDeltaX / 3.0;
+ deltaY = e.wheelDeltaY / 3.0;
+ } else if (e.wheelDelta !== undefined) { // IE or Opera
+ deltaY = e.wheelDelta / 3.0;
+ }
+
+ var scrolled = false;
+ deltaX = chopDelta(deltaX);
+ deltaY = chopDelta(deltaY);
+ if ((deltaX > 0 && scroller.scrollLeft > 0) ||
+ (deltaX < 0 && scroller.scrollLeft + scroller.clientWidth < scroller.scrollWidth)) {
+ scroller.scrollLeft -= deltaX;
+ scrolled = true;
+ }
+ if ((deltaY > 0 && scrollbar.scrollTop > 0) ||
+ (deltaY < 0 && scrollbar.scrollTop + scrollbar.clientHeight < scrollbar.scrollHeight)) {
+ scrollbar.scrollTop -= deltaY;
+ scrolled = true;
+ }
+ if (scrolled) e_stop(e);
+ }
+
+ // Replace the range from from to to by the strings in newText.
+ // Afterwards, set the selection to selFrom, selTo.
+ function updateLines(from, to, newText, selFrom, selTo) {
+ if (suppressEdits) return;
+ if (history) {
+ var old = [];
+ doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });
+ history.addChange(from.line, newText.length, old);
+ while (history.done.length > options.undoDepth) history.done.shift();
+ }
+ updateLinesNoUndo(from, to, newText, selFrom, selTo);
+ }
+ function unredoHelper(from, to) {
+ if (!from.length) return;
+ var set = from.pop(), out = [];
+ for (var i = set.length - 1; i >= 0; i -= 1) {
+ var change = set[i];
+ var replaced = [], end = change.start + change.added;
+ doc.iter(change.start, end, function(line) { replaced.push(line.text); });
+ out.push({start: change.start, added: change.old.length, old: replaced});
+ var pos = {line: change.start + change.old.length - 1,
+ ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])};
+ updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);
+ }
+ updateInput = true;
+ to.push(out);
+ }
+ function undo() {unredoHelper(history.done, history.undone);}
+ function redo() {unredoHelper(history.undone, history.done);}
+
+ function updateLinesNoUndo(from, to, newText, selFrom, selTo) {
+ if (suppressEdits) return;
+ var recomputeMaxLength = false, maxLineLength = maxLine.length;
+ if (!options.lineWrapping)
+ doc.iter(from.line, to.line + 1, function(line) {
+ if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}
+ });
+ if (from.line != to.line || newText.length > 1) gutterDirty = true;
+
+ var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);
+ // First adjust the line structure, taking some care to leave highlighting intact.
+ if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") {
+ // This is a whole-line replace. Treated specially to make
+ // sure line objects move the way they are supposed to.
+ var added = [], prevLine = null;
+ if (from.line) {
+ prevLine = getLine(from.line - 1);
+ prevLine.fixMarkEnds(lastLine);
+ } else lastLine.fixMarkStarts();
+ for (var i = 0, e = newText.length - 1; i < e; ++i)
+ added.push(Line.inheritMarks(newText[i], prevLine));
+ if (nlines) doc.remove(from.line, nlines, callbacks);
+ if (added.length) doc.insert(from.line, added);
+ } else if (firstLine == lastLine) {
+ if (newText.length == 1)
+ firstLine.replace(from.ch, to.ch, newText[0]);
+ else {
+ lastLine = firstLine.split(to.ch, newText[newText.length-1]);
+ firstLine.replace(from.ch, null, newText[0]);
+ firstLine.fixMarkEnds(lastLine);
+ var added = [];
+ for (var i = 1, e = newText.length - 1; i < e; ++i)
+ added.push(Line.inheritMarks(newText[i], firstLine));
+ added.push(lastLine);
+ doc.insert(from.line + 1, added);
+ }
+ } else if (newText.length == 1) {
+ firstLine.replace(from.ch, null, newText[0]);
+ lastLine.replace(null, to.ch, "");
+ firstLine.append(lastLine);
+ doc.remove(from.line + 1, nlines, callbacks);
+ } else {
+ var added = [];
+ firstLine.replace(from.ch, null, newText[0]);
+ lastLine.replace(null, to.ch, newText[newText.length-1]);
+ firstLine.fixMarkEnds(lastLine);
+ for (var i = 1, e = newText.length - 1; i < e; ++i)
+ added.push(Line.inheritMarks(newText[i], firstLine));
+ if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);
+ doc.insert(from.line + 1, added);
+ }
+ if (options.lineWrapping) {
+ var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3);
+ doc.iter(from.line, from.line + newText.length, function(line) {
+ if (line.hidden) return;
+ var guess = Math.ceil(line.text.length / perLine) || 1;
+ if (guess != line.height) updateLineHeight(line, guess);
+ });
+ } else {
+ doc.iter(from.line, from.line + newText.length, function(line) {
+ var l = line.text;
+ if (!line.hidden && l.length > maxLineLength) {
+ maxLine = l; maxLineLength = l.length; maxLineChanged = true;
+ recomputeMaxLength = false;
+ }
+ });
+ if (recomputeMaxLength) updateMaxLine = true;
+ }
+
+ // Add these lines to the work array, so that they will be
+ // highlighted. Adjust work lines if lines were added/removed.
+ var newWork = [], lendiff = newText.length - nlines - 1;
+ for (var i = 0, l = work.length; i < l; ++i) {
+ var task = work[i];
+ if (task < from.line) newWork.push(task);
+ else if (task > to.line) newWork.push(task + lendiff);
+ }
+ var hlEnd = from.line + Math.min(newText.length, 500);
+ highlightLines(from.line, hlEnd);
+ newWork.push(hlEnd);
+ work = newWork;
+ startWorker(100);
+ // Remember that these lines changed, for updating the display
+ changes.push({from: from.line, to: to.line + 1, diff: lendiff});
+ var changeObj = {from: from, to: to, text: newText};
+ if (textChanged) {
+ for (var cur = textChanged; cur.next; cur = cur.next) {}
+ cur.next = changeObj;
+ } else textChanged = changeObj;
+
+ // Update the selection
+ function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}
+ setSelection(clipPos(selFrom), clipPos(selTo),
+ updateLine(sel.from.line), updateLine(sel.to.line));
+ }
+
+ function needsScrollbar() {
+ var realHeight = doc.height * textHeight() + 2 * paddingTop();
+ return realHeight - 1 > scroller.offsetHeight ? realHeight : false;
+ }
+
+ function updateVerticalScroll(scrollTop) {
+ var scrollHeight = needsScrollbar();
+ scrollbar.style.display = scrollHeight ? "block" : "none";
+ if (scrollHeight) {
+ scrollbarInner.style.height = scrollHeight + "px";
+ scrollbar.style.height = scroller.offsetHeight + "px";
+ if (scrollTop != null) scrollbar.scrollTop = scrollTop;
+ }
+ // Position the mover div to align with the current virtual scroll position
+ mover.style.top = (displayOffset * textHeight() - scrollbar.scrollTop) + "px";
+ }
+
+ // On Mac OS X Lion and up, detect whether the mouse is plugged in by measuring
+ // the width of a div with a scrollbar in it. If the width is <= 1, then
+ // the mouse isn't plugged in and scrollbars should overlap the content.
+ function overlapScrollbars() {
+ var tmpSb = document.createElement('div'),
+ tmpSbInner = document.createElement('div');
+ tmpSb.className = "CodeMirror-scrollbar";
+ tmpSb.style.cssText = "position: absolute; left: -9999px; height: 100px;";
+ tmpSbInner.className = "CodeMirror-scrollbar-inner";
+ tmpSbInner.style.height = "200px";
+ tmpSb.appendChild(tmpSbInner);
+
+ document.body.appendChild(tmpSb);
+ var result = (tmpSb.offsetWidth <= 1);
+ document.body.removeChild(tmpSb);
+ return result;
+ }
+
+ function computeMaxLength() {
+ var maxLineLength = 0;
+ maxLine = ""; maxLineChanged = true;
+ doc.iter(0, doc.size, function(line) {
+ var l = line.text;
+ if (!line.hidden && l.length > maxLineLength) {
+ maxLineLength = l.length; maxLine = l;
+ }
+ });
+ updateMaxLine = false;
+ }
+
+ function replaceRange(code, from, to) {
+ from = clipPos(from);
+ if (!to) to = from; else to = clipPos(to);
+ code = splitLines(code);
+ function adjustPos(pos) {
+ if (posLess(pos, from)) return pos;
+ if (!posLess(to, pos)) return end;
+ var line = pos.line + code.length - (to.line - from.line) - 1;
+ var ch = pos.ch;
+ if (pos.line == to.line)
+ ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0));
+ return {line: line, ch: ch};
+ }
+ var end;
+ replaceRange1(code, from, to, function(end1) {
+ end = end1;
+ return {from: adjustPos(sel.from), to: adjustPos(sel.to)};
+ });
+ return end;
+ }
+ function replaceSelection(code, collapse) {
+ replaceRange1(splitLines(code), sel.from, sel.to, function(end) {
+ if (collapse == "end") return {from: end, to: end};
+ else if (collapse == "start") return {from: sel.from, to: sel.from};
+ else return {from: sel.from, to: end};
+ });
+ }
+ function replaceRange1(code, from, to, computeSel) {
+ var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length;
+ var newSel = computeSel({line: from.line + code.length - 1, ch: endch});
+ updateLines(from, to, code, newSel.from, newSel.to);
+ }
+
+ function getRange(from, to, lineSep) {
+ var l1 = from.line, l2 = to.line;
+ if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);
+ var code = [getLine(l1).text.slice(from.ch)];
+ doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });
+ code.push(getLine(l2).text.slice(0, to.ch));
+ return code.join(lineSep || "\n");
+ }
+ function getSelection(lineSep) {
+ return getRange(sel.from, sel.to, lineSep);
+ }
+
+ var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll
+ function slowPoll() {
+ if (pollingFast) return;
+ poll.set(options.pollInterval, function() {
+ startOperation();
+ readInput();
+ if (focused) slowPoll();
+ endOperation();
+ });
+ }
+ function fastPoll() {
+ var missed = false;
+ pollingFast = true;
+ function p() {
+ startOperation();
+ var changed = readInput();
+ if (!changed && !missed) {missed = true; poll.set(60, p);}
+ else {pollingFast = false; slowPoll();}
+ endOperation();
+ }
+ poll.set(20, p);
+ }
+
+ // Previnput is a hack to work with IME. If we reset the textarea
+ // on every change, that breaks IME. So we look for changes
+ // compared to the previous content instead. (Modern browsers have
+ // events that indicate IME taking place, but these are not widely
+ // supported or compatible enough yet to rely on.)
+ var prevInput = "";
+ function readInput() {
+ if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false;
+ var text = input.value;
+ if (text == prevInput) return false;
+ shiftSelecting = null;
+ var same = 0, l = Math.min(prevInput.length, text.length);
+ while (same < l && prevInput[same] == text[same]) ++same;
+ if (same < prevInput.length)
+ sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};
+ else if (overwrite && posEq(sel.from, sel.to))
+ sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};
+ replaceSelection(text.slice(same), "end");
+ if (text.length > 1000) { input.value = prevInput = ""; }
+ else prevInput = text;
+ return true;
+ }
+ function resetInput(user) {
+ if (!posEq(sel.from, sel.to)) {
+ prevInput = "";
+ input.value = getSelection();
+ selectInput(input);
+ } else if (user) prevInput = input.value = "";
+ }
+
+ function focusInput() {
+ if (options.readOnly != "nocursor") input.focus();
+ }
+
+ function scrollEditorIntoView() {
+ var rect = cursor.getBoundingClientRect();
+ // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden
+ if (ie && rect.top == rect.bottom) return;
+ var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
+ if (rect.top < 0 || rect.bottom > winH) scrollCursorIntoView();
+ }
+ function scrollCursorIntoView() {
+ var coords = calculateCursorCoords();
+ return scrollIntoView(coords.x, coords.y, coords.x, coords.yBot);
+ }
+ function calculateCursorCoords() {
+ var cursor = localCoords(sel.inverted ? sel.from : sel.to);
+ var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;
+ return {x: x, y: cursor.y, yBot: cursor.yBot};
+ }
+ function scrollIntoView(x1, y1, x2, y2) {
+ var scrollPos = calculateScrollPos(x1, y1, x2, y2), scrolled = false;
+ if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft; scrolled = true;}
+ if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scrollPos.scrollTop; scrolled = true;}
+ if (scrolled && options.onScroll) options.onScroll(instance);
+ }
+ function calculateScrollPos(x1, y1, x2, y2) {
+ var pl = paddingLeft(), pt = paddingTop();
+ y1 += pt; y2 += pt; x1 += pl; x2 += pl;
+ var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {};
+ var docBottom = scroller.scrollHeight;
+ var atTop = y1 < pt + 10, atBottom = y2 + pt > docBottom - 10;;
+ if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1);
+ else if (y2 > screentop + screen) result.scrollTop = (atBottom ? docBottom : y2) - screen;
+
+ var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;
+ var gutterw = options.fixedGutter ? gutter.clientWidth : 0;
+ var atLeft = x1 < gutterw + pl + 10;
+ if (x1 < screenleft + gutterw || atLeft) {
+ if (atLeft) x1 = 0;
+ result.scrollLeft = Math.max(0, x1 - 10 - gutterw);
+ } else if (x2 > screenw + screenleft - 3) {
+ result.scrollLeft = x2 + 10 - screenw;
+ }
+ return result;
+ }
+
+ function visibleLines(scrollTop) {
+ var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop();
+ var fromHeight = Math.max(0, Math.floor(top / lh));
+ var toHeight = Math.ceil((top + scroller.clientHeight) / lh);
+ return {from: lineAtHeight(doc, fromHeight),
+ to: lineAtHeight(doc, toHeight)};
+ }
+ // Uses a set of changes plus the current scroll position to
+ // determine which DOM updates have to be made, and makes the
+ // updates.
+ function updateDisplay(changes, suppressCallback, scrollTop) {
+ if (!scroller.clientWidth) {
+ showingFrom = showingTo = displayOffset = 0;
+ return;
+ }
+ // Compute the new visible window
+ // If scrollTop is specified, use that to determine which lines
+ // to render instead of the current scrollbar position.
+ var visible = visibleLines(scrollTop);
+ // Bail out if the visible area is already rendered and nothing changed.
+ if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) {
+ updateVerticalScroll(scrollTop);
+ return;
+ }
+ var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);
+ if (showingFrom < from && from - showingFrom < 20) from = showingFrom;
+ if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);
+
+ // Create a range of theoretically intact lines, and punch holes
+ // in that using the change info.
+ var intact = changes === true ? [] :
+ computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);
+ // Clip off the parts that won't be visible
+ var intactLines = 0;
+ for (var i = 0; i < intact.length; ++i) {
+ var range = intact[i];
+ if (range.from < from) {range.domStart += (from - range.from); range.from = from;}
+ if (range.to > to) range.to = to;
+ if (range.from >= range.to) intact.splice(i--, 1);
+ else intactLines += range.to - range.from;
+ }
+ if (intactLines == to - from && from == showingFrom && to == showingTo) {
+ updateVerticalScroll(scrollTop);
+ return;
+ }
+ intact.sort(function(a, b) {return a.domStart - b.domStart;});
+
+ var th = textHeight(), gutterDisplay = gutter.style.display;
+ lineDiv.style.display = "none";
+ patchDisplay(from, to, intact);
+ lineDiv.style.display = gutter.style.display = "";
+
+ var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;
+ // This is just a bogus formula that detects when the editor is
+ // resized or the font size changes.
+ if (different) lastSizeC = scroller.clientHeight + th;
+ showingFrom = from; showingTo = to;
+ displayOffset = heightAtLine(doc, from);
+
+ // Since this is all rather error prone, it is honoured with the
+ // only assertion in the whole file.
+ if (lineDiv.childNodes.length != showingTo - showingFrom)
+ throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) +
+ " nodes=" + lineDiv.childNodes.length);
+
+ function checkHeights() {
+ var curNode = lineDiv.firstChild, heightChanged = false;
+ doc.iter(showingFrom, showingTo, function(line) {
+ if (!line.hidden) {
+ var height = Math.round(curNode.offsetHeight / th) || 1;
+ if (line.height != height) {
+ updateLineHeight(line, height);
+ gutterDirty = heightChanged = true;
+ }
+ }
+ curNode = curNode.nextSibling;
+ });
+ return heightChanged;
+ }
+
+ if (options.lineWrapping) {
+ checkHeights();
+ var scrollHeight = needsScrollbar();
+ var shouldHaveScrollbar = scrollHeight ? "block" : "none";
+ if (scrollbar.style.display != shouldHaveScrollbar) {
+ scrollbar.style.display = shouldHaveScrollbar;
+ if (scrollHeight) scrollbarInner.style.height = scrollHeight + "px";
+ checkHeights();
+ }
+ }
+
+ gutter.style.display = gutterDisplay;
+ if (different || gutterDirty) {
+ // If the gutter grew in size, re-check heights. If those changed, re-draw gutter.
+ updateGutter() && options.lineWrapping && checkHeights() && updateGutter();
+ }
+ updateVerticalScroll(scrollTop);
+ updateSelection();
+ if (!suppressCallback && options.onUpdate) options.onUpdate(instance);
+ return true;
+ }
+
+ function computeIntact(intact, changes) {
+ for (var i = 0, l = changes.length || 0; i < l; ++i) {
+ var change = changes[i], intact2 = [], diff = change.diff || 0;
+ for (var j = 0, l2 = intact.length; j < l2; ++j) {
+ var range = intact[j];
+ if (change.to <= range.from && change.diff)
+ intact2.push({from: range.from + diff, to: range.to + diff,
+ domStart: range.domStart});
+ else if (change.to <= range.from || change.from >= range.to)
+ intact2.push(range);
+ else {
+ if (change.from > range.from)
+ intact2.push({from: range.from, to: change.from, domStart: range.domStart});
+ if (change.to < range.to)
+ intact2.push({from: change.to + diff, to: range.to + diff,
+ domStart: range.domStart + (change.to - range.from)});
+ }
+ }
+ intact = intact2;
+ }
+ return intact;
+ }
+
+ function patchDisplay(from, to, intact) {
+ // The first pass removes the DOM nodes that aren't intact.
+ if (!intact.length) lineDiv.innerHTML = "";
+ else {
+ function killNode(node) {
+ var tmp = node.nextSibling;
+ node.parentNode.removeChild(node);
+ return tmp;
+ }
+ var domPos = 0, curNode = lineDiv.firstChild, n;
+ for (var i = 0; i < intact.length; ++i) {
+ var cur = intact[i];
+ while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}
+ for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}
+ }
+ while (curNode) curNode = killNode(curNode);
+ }
+ // This pass fills in the lines that actually changed.
+ var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;
+ var scratch = document.createElement("div");
+ doc.iter(from, to, function(line) {
+ if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();
+ if (!nextIntact || nextIntact.from > j) {
+ if (line.hidden) var html = scratch.innerHTML = "<pre></pre>";
+ else {
+ var html = '<pre' + (line.className ? ' class="' + line.className + '"' : '') + '>'
+ + line.getHTML(makeTab) + '</pre>';
+ // Kludge to make sure the styled element lies behind the selection (by z-index)
+ if (line.bgClassName)
+ html = '<div style="position: relative"><pre class="' + line.bgClassName +
+ '" style="position: absolute; left: 0; right: 0; top: 0; bottom: 0; z-index: -2">&#160;</pre>' + html + "</div>";
+ }
+ scratch.innerHTML = html;
+ lineDiv.insertBefore(scratch.firstChild, curNode);
+ } else {
+ curNode = curNode.nextSibling;
+ }
+ ++j;
+ });
+ }
+
+ function updateGutter() {
+ if (!options.gutter && !options.lineNumbers) return;
+ var hText = mover.offsetHeight, hEditor = scroller.clientHeight;
+ gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px";
+ var html = [], i = showingFrom, normalNode;
+ doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {
+ if (line.hidden) {
+ html.push("<pre></pre>");
+ } else {
+ var marker = line.gutterMarker;
+ var text = options.lineNumbers ? options.lineNumberFormatter(i + options.firstLineNumber) : null;
+ if (marker && marker.text)
+ text = marker.text.replace("%N%", text != null ? text : "");
+ else if (text == null)
+ text = "\u00a0";
+ html.push((marker && marker.style ? '<pre class="' + marker.style + '">' : "<pre>"), text);
+ for (var j = 1; j < line.height; ++j) html.push("<br/>&#160;");
+ html.push("</pre>");
+ if (!marker) normalNode = i;
+ }
+ ++i;
+ });
+ gutter.style.display = "none";
+ gutterText.innerHTML = html.join("");
+ // Make sure scrolling doesn't cause number gutter size to pop
+ if (normalNode != null && options.lineNumbers) {
+ var node = gutterText.childNodes[normalNode - showingFrom];
+ var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = "";
+ while (val.length + pad.length < minwidth) pad += "\u00a0";
+ if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild);
+ }
+ gutter.style.display = "";
+ var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2;
+ lineSpace.style.marginLeft = gutter.offsetWidth + "px";
+ gutterDirty = false;
+ return resized;
+ }
+ function updateSelection() {
+ var collapsed = posEq(sel.from, sel.to);
+ var fromPos = localCoords(sel.from, true);
+ var toPos = collapsed ? fromPos : localCoords(sel.to, true);
+ var headPos = sel.inverted ? fromPos : toPos, th = textHeight();
+ var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);
+ inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px";
+ inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px";
+ if (collapsed) {
+ cursor.style.top = headPos.y + "px";
+ cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px";
+ cursor.style.display = "";
+ selectionDiv.style.display = "none";
+ } else {
+ var sameLine = fromPos.y == toPos.y, html = "";
+ var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth;
+ var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight;
+ function add(left, top, right, height) {
+ var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px"
+ : "right: " + right + "px";
+ html += '<div class="CodeMirror-selected" style="position: absolute; left: ' + left +
+ 'px; top: ' + top + 'px; ' + rstyle + '; height: ' + height + 'px"></div>';
+ }
+ if (sel.from.ch && fromPos.y >= 0) {
+ var right = sameLine ? clientWidth - toPos.x : 0;
+ add(fromPos.x, fromPos.y, right, th);
+ }
+ var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0));
+ var middleHeight = Math.min(toPos.y, clientHeight) - middleStart;
+ if (middleHeight > 0.2 * th)
+ add(0, middleStart, 0, middleHeight);
+ if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th)
+ add(0, toPos.y, clientWidth - toPos.x, th);
+ selectionDiv.innerHTML = html;
+ cursor.style.display = "none";
+ selectionDiv.style.display = "";
+ }
+ }
+
+ function setShift(val) {
+ if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);
+ else shiftSelecting = null;
+ }
+ function setSelectionUser(from, to) {
+ var sh = shiftSelecting && clipPos(shiftSelecting);
+ if (sh) {
+ if (posLess(sh, from)) from = sh;
+ else if (posLess(to, sh)) to = sh;
+ }
+ setSelection(from, to);
+ userSelChange = true;
+ }
+ // Update the selection. Last two args are only used by
+ // updateLines, since they have to be expressed in the line
+ // numbers before the update.
+ function setSelection(from, to, oldFrom, oldTo) {
+ goalColumn = null;
+ if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}
+ if (posEq(sel.from, from) && posEq(sel.to, to)) return;
+ if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}
+
+ // Skip over hidden lines.
+ if (from.line != oldFrom) {
+ var from1 = skipHidden(from, oldFrom, sel.from.ch);
+ // If there is no non-hidden line left, force visibility on current line
+ if (!from1) setLineHidden(from.line, false);
+ else from = from1;
+ }
+ if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);
+
+ if (posEq(from, to)) sel.inverted = false;
+ else if (posEq(from, sel.to)) sel.inverted = false;
+ else if (posEq(to, sel.from)) sel.inverted = true;
+
+ if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) {
+ var head = sel.inverted ? from : to;
+ if (head.line != sel.from.line && sel.from.line < doc.size) {
+ var oldLine = getLine(sel.from.line);
+ if (/^\s+$/.test(oldLine.text))
+ setTimeout(operation(function() {
+ if (oldLine.parent && /^\s+$/.test(oldLine.text)) {
+ var no = lineNo(oldLine);
+ replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length});
+ }
+ }, 10));
+ }
+ }
+
+ sel.from = from; sel.to = to;
+ selectionChanged = true;
+ }
+ function skipHidden(pos, oldLine, oldCh) {
+ function getNonHidden(dir) {
+ var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;
+ while (lNo != end) {
+ var line = getLine(lNo);
+ if (!line.hidden) {
+ var ch = pos.ch;
+ if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length;
+ return {line: lNo, ch: ch};
+ }
+ lNo += dir;
+ }
+ }
+ var line = getLine(pos.line);
+ var toEnd = pos.ch == line.text.length && pos.ch != oldCh;
+ if (!line.hidden) return pos;
+ if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);
+ else return getNonHidden(-1) || getNonHidden(1);
+ }
+ function setCursor(line, ch, user) {
+ var pos = clipPos({line: line, ch: ch || 0});
+ (user ? setSelectionUser : setSelection)(pos, pos);
+ }
+
+ function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}
+ function clipPos(pos) {
+ if (pos.line < 0) return {line: 0, ch: 0};
+ if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};
+ var ch = pos.ch, linelen = getLine(pos.line).text.length;
+ if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};
+ else if (ch < 0) return {line: pos.line, ch: 0};
+ else return pos;
+ }
+
+ function findPosH(dir, unit) {
+ var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;
+ var lineObj = getLine(line);
+ function findNextLine() {
+ for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {
+ var lo = getLine(l);
+ if (!lo.hidden) { line = l; lineObj = lo; return true; }
+ }
+ }
+ function moveOnce(boundToLine) {
+ if (ch == (dir < 0 ? 0 : lineObj.text.length)) {
+ if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;
+ else return false;
+ } else ch += dir;
+ return true;
+ }
+ if (unit == "char") moveOnce();
+ else if (unit == "column") moveOnce(true);
+ else if (unit == "word") {
+ var sawWord = false;
+ for (;;) {
+ if (dir < 0) if (!moveOnce()) break;
+ if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;
+ else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}
+ if (dir > 0) if (!moveOnce()) break;
+ }
+ }
+ return {line: line, ch: ch};
+ }
+ function moveH(dir, unit) {
+ var pos = dir < 0 ? sel.from : sel.to;
+ if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);
+ setCursor(pos.line, pos.ch, true);
+ }
+ function deleteH(dir, unit) {
+ if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to);
+ else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to);
+ else replaceRange("", sel.from, findPosH(dir, unit));
+ userSelChange = true;
+ }
+ var goalColumn = null;
+ function moveV(dir, unit) {
+ var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);
+ if (goalColumn != null) pos.x = goalColumn;
+ if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight);
+ else if (unit == "line") dist = textHeight();
+ var target = coordsChar(pos.x, pos.y + dist * dir + 2);
+ if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y;
+ setCursor(target.line, target.ch, true);
+ goalColumn = pos.x;
+ }
+
+ function findWordAt(pos) {
+ var line = getLine(pos.line).text;
+ var start = pos.ch, end = pos.ch;
+ var check = isWordChar(line.charAt(start < line.length ? start : start - 1)) ?
+ isWordChar : function(ch) {return !isWordChar(ch);};
+ while (start > 0 && check(line.charAt(start - 1))) --start;
+ while (end < line.length && check(line.charAt(end))) ++end;
+ return {from: {line: pos.line, ch: start}, to: {line: pos.line, ch: end}};
+ }
+ function selectLine(line) {
+ setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0}));
+ }
+ function indentSelected(mode) {
+ if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);
+ var e = sel.to.line - (sel.to.ch ? 0 : 1);
+ for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);
+ }
+
+ function indentLine(n, how) {
+ if (!how) how = "add";
+ if (how == "smart") {
+ if (!mode.indent) how = "prev";
+ else var state = getStateBefore(n);
+ }
+
+ var line = getLine(n), curSpace = line.indentation(options.tabSize),
+ curSpaceString = line.text.match(/^\s*/)[0], indentation;
+ if (how == "smart") {
+ indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);
+ if (indentation == Pass) how = "prev";
+ }
+ if (how == "prev") {
+ if (n) indentation = getLine(n-1).indentation(options.tabSize);
+ else indentation = 0;
+ }
+ else if (how == "add") indentation = curSpace + options.indentUnit;
+ else if (how == "subtract") indentation = curSpace - options.indentUnit;
+ indentation = Math.max(0, indentation);
+ var diff = indentation - curSpace;
+
+ var indentString = "", pos = 0;
+ if (options.indentWithTabs)
+ for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";}
+ while (pos < indentation) {++pos; indentString += " ";}
+
+ replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});
+ }
+
+ function loadMode() {
+ mode = CodeMirror.getMode(options, options.mode);
+ doc.iter(0, doc.size, function(line) { line.stateAfter = null; });
+ work = [0];
+ startWorker();
+ }
+ function gutterChanged() {
+ var visible = options.gutter || options.lineNumbers;
+ gutter.style.display = visible ? "" : "none";
+ if (visible) gutterDirty = true;
+ else lineDiv.parentNode.style.marginLeft = 0;
+ }
+ function wrappingChanged(from, to) {
+ if (options.lineWrapping) {
+ wrapper.className += " CodeMirror-wrap";
+ var perLine = scroller.clientWidth / charWidth() - 3;
+ doc.iter(0, doc.size, function(line) {
+ if (line.hidden) return;
+ var guess = Math.ceil(line.text.length / perLine) || 1;
+ if (guess != 1) updateLineHeight(line, guess);
+ });
+ lineSpace.style.width = code.style.width = "";
+ widthForcer.style.left = "";
+ } else {
+ wrapper.className = wrapper.className.replace(" CodeMirror-wrap", "");
+ maxLine = ""; maxLineChanged = true;
+ doc.iter(0, doc.size, function(line) {
+ if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);
+ if (line.text.length > maxLine.length) maxLine = line.text;
+ });
+ }
+ changes.push({from: 0, to: doc.size});
+ }
+ function makeTab(col) {
+ var w = options.tabSize - col % options.tabSize, cached = tabCache[w];
+ if (cached) return cached;
+ for (var str = '<span class="cm-tab">', i = 0; i < w; ++i) str += " ";
+ return (tabCache[w] = {html: str + "</span>", width: w});
+ }
+ function themeChanged() {
+ scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") +
+ options.theme.replace(/(^|\s)\s*/g, " cm-s-");
+ }
+ function keyMapChanged() {
+ var style = keyMap[options.keyMap].style;
+ wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") +
+ (style ? " cm-keymap-" + style : "");
+ }
+
+ function TextMarker() { this.set = []; }
+ TextMarker.prototype.clear = operation(function() {
+ var min = Infinity, max = -Infinity;
+ for (var i = 0, e = this.set.length; i < e; ++i) {
+ var line = this.set[i], mk = line.marked;
+ if (!mk || !line.parent) continue;
+ var lineN = lineNo(line);
+ min = Math.min(min, lineN); max = Math.max(max, lineN);
+ for (var j = 0; j < mk.length; ++j)
+ if (mk[j].marker == this) mk.splice(j--, 1);
+ }
+ if (min != Infinity)
+ changes.push({from: min, to: max + 1});
+ });
+ TextMarker.prototype.find = function() {
+ var from, to;
+ for (var i = 0, e = this.set.length; i < e; ++i) {
+ var line = this.set[i], mk = line.marked;
+ for (var j = 0; j < mk.length; ++j) {
+ var mark = mk[j];
+ if (mark.marker == this) {
+ if (mark.from != null || mark.to != null) {
+ var found = lineNo(line);
+ if (found != null) {
+ if (mark.from != null) from = {line: found, ch: mark.from};
+ if (mark.to != null) to = {line: found, ch: mark.to};
+ }
+ }
+ }
+ }
+ }
+ return {from: from, to: to};
+ };
+
+ function markText(from, to, className) {
+ from = clipPos(from); to = clipPos(to);
+ var tm = new TextMarker();
+ if (!posLess(from, to)) return tm;
+ function add(line, from, to, className) {
+ getLine(line).addMark(new MarkedText(from, to, className, tm));
+ }
+ if (from.line == to.line) add(from.line, from.ch, to.ch, className);
+ else {
+ add(from.line, from.ch, null, className);
+ for (var i = from.line + 1, e = to.line; i < e; ++i)
+ add(i, null, null, className);
+ add(to.line, null, to.ch, className);
+ }
+ changes.push({from: from.line, to: to.line + 1});
+ return tm;
+ }
+
+ function setBookmark(pos) {
+ pos = clipPos(pos);
+ var bm = new Bookmark(pos.ch);
+ getLine(pos.line).addMark(bm);
+ return bm;
+ }
+
+ function findMarksAt(pos) {
+ pos = clipPos(pos);
+ var markers = [], marked = getLine(pos.line).marked;
+ if (!marked) return markers;
+ for (var i = 0, e = marked.length; i < e; ++i) {
+ var m = marked[i];
+ if ((m.from == null || m.from <= pos.ch) &&
+ (m.to == null || m.to >= pos.ch))
+ markers.push(m.marker || m);
+ }
+ return markers;
+ }
+
+ function addGutterMarker(line, text, className) {
+ if (typeof line == "number") line = getLine(clipLine(line));
+ line.gutterMarker = {text: text, style: className};
+ gutterDirty = true;
+ return line;
+ }
+ function removeGutterMarker(line) {
+ if (typeof line == "number") line = getLine(clipLine(line));
+ line.gutterMarker = null;
+ gutterDirty = true;
+ }
+
+ function changeLine(handle, op) {
+ var no = handle, line = handle;
+ if (typeof handle == "number") line = getLine(clipLine(handle));
+ else no = lineNo(handle);
+ if (no == null) return null;
+ if (op(line, no)) changes.push({from: no, to: no + 1});
+ else return null;
+ return line;
+ }
+ function setLineClass(handle, className, bgClassName) {
+ return changeLine(handle, function(line) {
+ if (line.className != className || line.bgClassName != bgClassName) {
+ line.className = className;
+ line.bgClassName = bgClassName;
+ return true;
+ }
+ });
+ }
+ function setLineHidden(handle, hidden) {
+ return changeLine(handle, function(line, no) {
+ if (line.hidden != hidden) {
+ line.hidden = hidden;
+ if (!options.lineWrapping) {
+ var l = line.text;
+ if (hidden && l.length == maxLine.length) {
+ updateMaxLine = true;
+ } else if (!hidden && l.length > maxLine.length) {
+ maxLine = l; updateMaxLine = false;
+ }
+ }
+ updateLineHeight(line, hidden ? 0 : 1);
+ var fline = sel.from.line, tline = sel.to.line;
+ if (hidden && (fline == no || tline == no)) {
+ var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from;
+ var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to;
+ // Can't hide the last visible line, we'd have no place to put the cursor
+ if (!to) return;
+ setSelection(from, to);
+ }
+ return (gutterDirty = true);
+ }
+ });
+ }
+
+ function lineInfo(line) {
+ if (typeof line == "number") {
+ if (!isLine(line)) return null;
+ var n = line;
+ line = getLine(line);
+ if (!line) return null;
+ } else {
+ var n = lineNo(line);
+ if (n == null) return null;
+ }
+ var marker = line.gutterMarker;
+ return {line: n, handle: line, text: line.text, markerText: marker && marker.text,
+ markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName};
+ }
+
+ function stringWidth(str) {
+ measure.innerHTML = "<pre><span>x</span></pre>";
+ measure.firstChild.firstChild.firstChild.nodeValue = str;
+ return measure.firstChild.firstChild.offsetWidth || 10;
+ }
+ // These are used to go from pixel positions to character
+ // positions, taking varying character widths into account.
+ function charFromX(line, x) {
+ if (x <= 0) return 0;
+ var lineObj = getLine(line), text = lineObj.text;
+ function getX(len) {
+ return measureLine(lineObj, len).left;
+ }
+ var from = 0, fromX = 0, to = text.length, toX;
+ // Guess a suitable upper bound for our search.
+ var estimated = Math.min(to, Math.ceil(x / charWidth()));
+ for (;;) {
+ var estX = getX(estimated);
+ if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
+ else {toX = estX; to = estimated; break;}
+ }
+ if (x > toX) return to;
+ // Try to guess a suitable lower bound as well.
+ estimated = Math.floor(to * 0.8); estX = getX(estimated);
+ if (estX < x) {from = estimated; fromX = estX;}
+ // Do a binary search between these bounds.
+ for (;;) {
+ if (to - from <= 1) return (toX - x > x - fromX) ? from : to;
+ var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
+ if (middleX > x) {to = middle; toX = middleX;}
+ else {from = middle; fromX = middleX;}
+ }
+ }
+
+ var tempId = "CodeMirror-temp-" + Math.floor(Math.random() * 0xffffff).toString(16);
+ function measureLine(line, ch) {
+ if (ch == 0) return {top: 0, left: 0};
+ var wbr = options.lineWrapping && ch < line.text.length &&
+ spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1));
+ measure.innerHTML = "<pre>" + line.getHTML(makeTab, ch, tempId, wbr) + "</pre>";
+ var elt = document.getElementById(tempId);
+ var top = elt.offsetTop, left = elt.offsetLeft;
+ // Older IEs report zero offsets for spans directly after a wrap
+ if (ie && top == 0 && left == 0) {
+ var backup = document.createElement("span");
+ backup.innerHTML = "x";
+ elt.parentNode.insertBefore(backup, elt.nextSibling);
+ top = backup.offsetTop;
+ }
+ return {top: top, left: left};
+ }
+ function localCoords(pos, inLineWrap) {
+ var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));
+ if (pos.ch == 0) x = 0;
+ else {
+ var sp = measureLine(getLine(pos.line), pos.ch);
+ x = sp.left;
+ if (options.lineWrapping) y += Math.max(0, sp.top);
+ }
+ return {x: x, y: y, yBot: y + lh};
+ }
+ // Coords must be lineSpace-local
+ function coordsChar(x, y) {
+ if (y < 0) y = 0;
+ var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);
+ var lineNo = lineAtHeight(doc, heightPos);
+ if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};
+ var lineObj = getLine(lineNo), text = lineObj.text;
+ var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;
+ if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};
+ function getX(len) {
+ var sp = measureLine(lineObj, len);
+ if (tw) {
+ var off = Math.round(sp.top / th);
+ return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);
+ }
+ return sp.left;
+ }
+ var from = 0, fromX = 0, to = text.length, toX;
+ // Guess a suitable upper bound for our search.
+ var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));
+ for (;;) {
+ var estX = getX(estimated);
+ if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));
+ else {toX = estX; to = estimated; break;}
+ }
+ if (x > toX) return {line: lineNo, ch: to};
+ // Try to guess a suitable lower bound as well.
+ estimated = Math.floor(to * 0.8); estX = getX(estimated);
+ if (estX < x) {from = estimated; fromX = estX;}
+ // Do a binary search between these bounds.
+ for (;;) {
+ if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};
+ var middle = Math.ceil((from + to) / 2), middleX = getX(middle);
+ if (middleX > x) {to = middle; toX = middleX;}
+ else {from = middle; fromX = middleX;}
+ }
+ }
+ function pageCoords(pos) {
+ var local = localCoords(pos, true), off = eltOffset(lineSpace);
+ return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};
+ }
+
+ var cachedHeight, cachedHeightFor, measureText;
+ function textHeight() {
+ if (measureText == null) {
+ measureText = "<pre>";
+ for (var i = 0; i < 49; ++i) measureText += "x<br/>";
+ measureText += "x</pre>";
+ }
+ var offsetHeight = lineDiv.clientHeight;
+ if (offsetHeight == cachedHeightFor) return cachedHeight;
+ cachedHeightFor = offsetHeight;
+ measure.innerHTML = measureText;
+ cachedHeight = measure.firstChild.offsetHeight / 50 || 1;
+ measure.innerHTML = "";
+ return cachedHeight;
+ }
+ var cachedWidth, cachedWidthFor = 0;
+ function charWidth() {
+ if (scroller.clientWidth == cachedWidthFor) return cachedWidth;
+ cachedWidthFor = scroller.clientWidth;
+ return (cachedWidth = stringWidth("x"));
+ }
+ function paddingTop() {return lineSpace.offsetTop;}
+ function paddingLeft() {return lineSpace.offsetLeft;}
+
+ function posFromMouse(e, liberal) {
+ var offW = eltOffset(scroller, true), x, y;
+ // Fails unpredictably on IE[67] when mouse is dragged around quickly.
+ try { x = e.clientX; y = e.clientY; } catch (e) { return null; }
+ // This is a mess of a heuristic to try and determine whether a
+ // scroll-bar was clicked or not, and to return null if one was
+ // (and !liberal).
+ if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))
+ return null;
+ var offL = eltOffset(lineSpace, true);
+ return coordsChar(x - offL.left, y - offL.top);
+ }
+ function onContextMenu(e) {
+ var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop;
+ if (!pos || opera) return; // Opera is difficult.
+ if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))
+ operation(setCursor)(pos.line, pos.ch);
+
+ var oldCSS = input.style.cssText;
+ inputDiv.style.position = "absolute";
+ input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) +
+ "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; " +
+ "border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
+ leaveInputAlone = true;
+ var val = input.value = getSelection();
+ focusInput();
+ selectInput(input);
+ function rehide() {
+ var newVal = splitLines(input.value).join("\n");
+ if (newVal != val && !options.readOnly) operation(replaceSelection)(newVal, "end");
+ inputDiv.style.position = "relative";
+ input.style.cssText = oldCSS;
+ if (ie_lt9) scrollbar.scrollTop = scrollPos;
+ leaveInputAlone = false;
+ resetInput(true);
+ slowPoll();
+ }
+
+ if (gecko) {
+ e_stop(e);
+ var mouseup = connect(window, "mouseup", function() {
+ mouseup();
+ setTimeout(rehide, 20);
+ }, true);
+ } else {
+ setTimeout(rehide, 50);
+ }
+ }
+
+ // Cursor-blinking
+ function restartBlink() {
+ clearInterval(blinker);
+ var on = true;
+ cursor.style.visibility = "";
+ blinker = setInterval(function() {
+ cursor.style.visibility = (on = !on) ? "" : "hidden";
+ }, 650);
+ }
+
+ var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
+ function matchBrackets(autoclear) {
+ var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;
+ var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
+ if (!match) return;
+ var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles;
+ for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)
+ if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}
+
+ var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
+ function scan(line, from, to) {
+ if (!line.text) return;
+ var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;
+ for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {
+ var text = st[i];
+ if (st[i+1] != style) {pos += d * text.length; continue;}
+ for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {
+ if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {
+ var match = matching[cur];
+ if (match.charAt(1) == ">" == forward) stack.push(cur);
+ else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
+ else if (!stack.length) return {pos: pos, match: true};
+ }
+ }
+ }
+ }
+ for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {
+ var line = getLine(i), first = i == head.line;
+ var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);
+ if (found) break;
+ }
+ if (!found) found = {pos: null, match: false};
+ var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
+ var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),
+ two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);
+ var clear = operation(function(){one.clear(); two && two.clear();});
+ if (autoclear) setTimeout(clear, 800);
+ else bracketHighlighted = clear;
+ }
+
+ // Finds the line to start with when starting a parse. Tries to
+ // find a line with a stateAfter, so that it can start with a
+ // valid state. If that fails, it returns the line with the
+ // smallest indentation, which tends to need the least context to
+ // parse correctly.
+ function findStartLine(n) {
+ var minindent, minline;
+ for (var search = n, lim = n - 40; search > lim; --search) {
+ if (search == 0) return 0;
+ var line = getLine(search-1);
+ if (line.stateAfter) return search;
+ var indented = line.indentation(options.tabSize);
+ if (minline == null || minindent > indented) {
+ minline = search - 1;
+ minindent = indented;
+ }
+ }
+ return minline;
+ }
+ function getStateBefore(n) {
+ var start = findStartLine(n), state = start && getLine(start-1).stateAfter;
+ if (!state) state = startState(mode);
+ else state = copyState(mode, state);
+ doc.iter(start, n, function(line) {
+ line.highlight(mode, state, options.tabSize);
+ line.stateAfter = copyState(mode, state);
+ });
+ if (start < n) changes.push({from: start, to: n});
+ if (n < doc.size && !getLine(n).stateAfter) work.push(n);
+ return state;
+ }
+ function highlightLines(start, end) {
+ var state = getStateBefore(start);
+ doc.iter(start, end, function(line) {
+ line.highlight(mode, state, options.tabSize);
+ line.stateAfter = copyState(mode, state);
+ });
+ }
+ function highlightWorker() {
+ var end = +new Date + options.workTime;
+ var foundWork = work.length;
+ while (work.length) {
+ if (!getLine(showingFrom).stateAfter) var task = showingFrom;
+ else var task = work.pop();
+ if (task >= doc.size) continue;
+ var start = findStartLine(task), state = start && getLine(start-1).stateAfter;
+ if (state) state = copyState(mode, state);
+ else state = startState(mode);
+
+ var unchanged = 0, compare = mode.compareStates, realChange = false,
+ i = start, bail = false;
+ doc.iter(i, doc.size, function(line) {
+ var hadState = line.stateAfter;
+ if (+new Date > end) {
+ work.push(i);
+ startWorker(options.workDelay);
+ if (realChange) changes.push({from: task, to: i + 1});
+ return (bail = true);
+ }
+ var changed = line.highlight(mode, state, options.tabSize);
+ if (changed) realChange = true;
+ line.stateAfter = copyState(mode, state);
+ var done = null;
+ if (compare) {
+ var same = hadState && compare(hadState, state);
+ if (same != Pass) done = !!same;
+ }
+ if (done == null) {
+ if (changed !== false || !hadState) unchanged = 0;
+ else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, "")))
+ done = true;
+ }
+ if (done) return true;
+ ++i;
+ });
+ if (bail) return;
+ if (realChange) changes.push({from: task, to: i + 1});
+ }
+ if (foundWork && options.onHighlightComplete)
+ options.onHighlightComplete(instance);
+ }
+ function startWorker(time) {
+ if (!work.length) return;
+ highlight.set(time, operation(highlightWorker));
+ }
+
+ // Operations are used to wrap changes in such a way that each
+ // change won't have to update the cursor and display (which would
+ // be awkward, slow, and error-prone), but instead updates are
+ // batched and then all combined and executed at once.
+ function startOperation() {
+ updateInput = userSelChange = textChanged = null;
+ changes = []; selectionChanged = false; callbacks = [];
+ }
+ function endOperation() {
+ if (updateMaxLine) computeMaxLength();
+ if (maxLineChanged && !options.lineWrapping) {
+ var cursorWidth = widthForcer.offsetWidth, left = stringWidth(maxLine);
+ widthForcer.style.left = left + "px";
+ lineSpace.style.minWidth = (left + cursorWidth) + "px";
+ maxLineChanged = false;
+ }
+ var newScrollPos, updated;
+ if (selectionChanged) {
+ var coords = calculateCursorCoords();
+ newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot);
+ }
+ if (changes.length) updated = updateDisplay(changes, true, (newScrollPos ? newScrollPos.scrollTop : null));
+ else {
+ if (selectionChanged) updateSelection();
+ if (gutterDirty) updateGutter();
+ }
+ if (newScrollPos) scrollCursorIntoView();
+ if (selectionChanged) {scrollEditorIntoView(); restartBlink();}
+
+ if (focused && !leaveInputAlone &&
+ (updateInput === true || (updateInput !== false && selectionChanged)))
+ resetInput(userSelChange);
+
+ if (selectionChanged && options.matchBrackets)
+ setTimeout(operation(function() {
+ if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}
+ if (posEq(sel.from, sel.to)) matchBrackets(false);
+ }), 20);
+ var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks
+ if (textChanged && options.onChange && instance)
+ options.onChange(instance, textChanged);
+ if (sc && options.onCursorActivity)
+ options.onCursorActivity(instance);
+ for (var i = 0; i < cbs.length; ++i) cbs[i](instance);
+ if (updated && options.onUpdate) options.onUpdate(instance);
+ }
+ var nestedOperation = 0;
+ function operation(f) {
+ return function() {
+ if (!nestedOperation++) startOperation();
+ try {var result = f.apply(this, arguments);}
+ finally {if (!--nestedOperation) endOperation();}
+ return result;
+ };
+ }
+
+ function compoundChange(f) {
+ history.startCompound();
+ try { return f(); } finally { history.endCompound(); }
+ }
+
+ for (var ext in extensions)
+ if (extensions.propertyIsEnumerable(ext) &&
+ !instance.propertyIsEnumerable(ext))
+ instance[ext] = extensions[ext];
+ return instance;
+ } // (end of function CodeMirror)
+
+ // The default configuration options.
+ CodeMirror.defaults = {
+ value: "",
+ mode: null,
+ theme: "default",
+ indentUnit: 2,
+ indentWithTabs: false,
+ smartIndent: true,
+ tabSize: 4,
+ keyMap: "default",
+ extraKeys: null,
+ electricChars: true,
+ autoClearEmptyLines: false,
+ onKeyEvent: null,
+ onDragEvent: null,
+ lineWrapping: false,
+ lineNumbers: false,
+ gutter: false,
+ fixedGutter: false,
+ firstLineNumber: 1,
+ readOnly: false,
+ dragDrop: true,
+ onChange: null,
+ onCursorActivity: null,
+ onGutterClick: null,
+ onHighlightComplete: null,
+ onUpdate: null,
+ onFocus: null, onBlur: null, onScroll: null,
+ matchBrackets: false,
+ workTime: 100,
+ workDelay: 200,
+ pollInterval: 100,
+ undoDepth: 40,
+ tabindex: null,
+ autofocus: null,
+ lineNumberFormatter: function(integer) { return integer; }
+ };
+
+ var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent);
+ var mac = ios || /Mac/.test(navigator.platform);
+ var win = /Win/.test(navigator.platform);
+
+ // Known modes, by name and by MIME
+ var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {};
+ CodeMirror.defineMode = function(name, mode) {
+ if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name;
+ if (arguments.length > 2) {
+ mode.dependencies = [];
+ for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]);
+ }
+ modes[name] = mode;
+ };
+ CodeMirror.defineMIME = function(mime, spec) {
+ mimeModes[mime] = spec;
+ };
+ CodeMirror.resolveMode = function(spec) {
+ if (typeof spec == "string" && mimeModes.hasOwnProperty(spec))
+ spec = mimeModes[spec];
+ else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec))
+ return CodeMirror.resolveMode("application/xml");
+ if (typeof spec == "string") return {name: spec};
+ else return spec || {name: "null"};
+ };
+ CodeMirror.getMode = function(options, spec) {
+ var spec = CodeMirror.resolveMode(spec);
+ var mfactory = modes[spec.name];
+ if (!mfactory) return CodeMirror.getMode(options, "text/plain");
+ return mfactory(options, spec);
+ };
+ CodeMirror.listModes = function() {
+ var list = [];
+ for (var m in modes)
+ if (modes.propertyIsEnumerable(m)) list.push(m);
+ return list;
+ };
+ CodeMirror.listMIMEs = function() {
+ var list = [];
+ for (var m in mimeModes)
+ if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});
+ return list;
+ };
+
+ var extensions = CodeMirror.extensions = {};
+ CodeMirror.defineExtension = function(name, func) {
+ extensions[name] = func;
+ };
+
+ var commands = CodeMirror.commands = {
+ selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},
+ killLine: function(cm) {
+ var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
+ if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0});
+ else cm.replaceRange("", from, sel ? to : {line: from.line});
+ },
+ deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});},
+ undo: function(cm) {cm.undo();},
+ redo: function(cm) {cm.redo();},
+ goDocStart: function(cm) {cm.setCursor(0, 0, true);},
+ goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},
+ goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},
+ goLineStartSmart: function(cm) {
+ var cur = cm.getCursor();
+ var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/));
+ cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);
+ },
+ goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},
+ goLineUp: function(cm) {cm.moveV(-1, "line");},
+ goLineDown: function(cm) {cm.moveV(1, "line");},
+ goPageUp: function(cm) {cm.moveV(-1, "page");},
+ goPageDown: function(cm) {cm.moveV(1, "page");},
+ goCharLeft: function(cm) {cm.moveH(-1, "char");},
+ goCharRight: function(cm) {cm.moveH(1, "char");},
+ goColumnLeft: function(cm) {cm.moveH(-1, "column");},
+ goColumnRight: function(cm) {cm.moveH(1, "column");},
+ goWordLeft: function(cm) {cm.moveH(-1, "word");},
+ goWordRight: function(cm) {cm.moveH(1, "word");},
+ delCharLeft: function(cm) {cm.deleteH(-1, "char");},
+ delCharRight: function(cm) {cm.deleteH(1, "char");},
+ delWordLeft: function(cm) {cm.deleteH(-1, "word");},
+ delWordRight: function(cm) {cm.deleteH(1, "word");},
+ indentAuto: function(cm) {cm.indentSelection("smart");},
+ indentMore: function(cm) {cm.indentSelection("add");},
+ indentLess: function(cm) {cm.indentSelection("subtract");},
+ insertTab: function(cm) {cm.replaceSelection("\t", "end");},
+ defaultTab: function(cm) {
+ if (cm.somethingSelected()) cm.indentSelection("add");
+ else cm.replaceSelection("\t", "end");
+ },
+ transposeChars: function(cm) {
+ var cur = cm.getCursor(), line = cm.getLine(cur.line);
+ if (cur.ch > 0 && cur.ch < line.length - 1)
+ cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),
+ {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});
+ },
+ newlineAndIndent: function(cm) {
+ cm.replaceSelection("\n", "end");
+ cm.indentLine(cm.getCursor().line);
+ },
+ toggleOverwrite: function(cm) {cm.toggleOverwrite();}
+ };
+
+ var keyMap = CodeMirror.keyMap = {};
+ keyMap.basic = {
+ "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown",
+ "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown",
+ "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto",
+ "Enter": "newlineAndIndent", "Insert": "toggleOverwrite"
+ };
+ // Note that the save and find-related commands aren't defined by
+ // default. Unknown commands are simply ignored.
+ keyMap.pcDefault = {
+ "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo",
+ "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd",
+ "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd",
+ "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find",
+ "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll",
+ "Ctrl-[": "indentLess", "Ctrl-]": "indentMore",
+ fallthrough: "basic"
+ };
+ keyMap.macDefault = {
+ "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
+ "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft",
+ "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft",
+ "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find",
+ "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll",
+ "Cmd-[": "indentLess", "Cmd-]": "indentMore",
+ fallthrough: ["basic", "emacsy"]
+ };
+ keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault;
+ keyMap.emacsy = {
+ "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
+ "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
+ "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft",
+ "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars"
+ };
+
+ function getKeyMap(val) {
+ if (typeof val == "string") return keyMap[val];
+ else return val;
+ }
+ function lookupKey(name, extraMap, map, handle, stop) {
+ function lookup(map) {
+ map = getKeyMap(map);
+ var found = map[name];
+ if (found != null && handle(found)) return true;
+ if (map.nofallthrough) {
+ if (stop) stop();
+ return true;
+ }
+ var fallthrough = map.fallthrough;
+ if (fallthrough == null) return false;
+ if (Object.prototype.toString.call(fallthrough) != "[object Array]")
+ return lookup(fallthrough);
+ for (var i = 0, e = fallthrough.length; i < e; ++i) {
+ if (lookup(fallthrough[i])) return true;
+ }
+ return false;
+ }
+ if (extraMap && lookup(extraMap)) return true;
+ return lookup(map);
+ }
+ function isModifierKey(event) {
+ var name = keyNames[e_prop(event, "keyCode")];
+ return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod";
+ }
+
+ CodeMirror.fromTextArea = function(textarea, options) {
+ if (!options) options = {};
+ options.value = textarea.value;
+ if (!options.tabindex && textarea.tabindex)
+ options.tabindex = textarea.tabindex;
+ if (options.autofocus == null && textarea.getAttribute("autofocus") != null)
+ options.autofocus = true;
+
+ function save() {textarea.value = instance.getValue();}
+ if (textarea.form) {
+ // Deplorable hack to make the submit method do the right thing.
+ var rmSubmit = connect(textarea.form, "submit", save, true);
+ if (typeof textarea.form.submit == "function") {
+ var realSubmit = textarea.form.submit;
+ function wrappedSubmit() {
+ save();
+ textarea.form.submit = realSubmit;
+ textarea.form.submit();
+ textarea.form.submit = wrappedSubmit;
+ }
+ textarea.form.submit = wrappedSubmit;
+ }
+ }
+
+ textarea.style.display = "none";
+ var instance = CodeMirror(function(node) {
+ textarea.parentNode.insertBefore(node, textarea.nextSibling);
+ }, options);
+ instance.save = save;
+ instance.getTextArea = function() { return textarea; };
+ instance.toTextArea = function() {
+ save();
+ textarea.parentNode.removeChild(instance.getWrapperElement());
+ textarea.style.display = "";
+ if (textarea.form) {
+ rmSubmit();
+ if (typeof textarea.form.submit == "function")
+ textarea.form.submit = realSubmit;
+ }
+ };
+ return instance;
+ };
+
+ // Utility functions for working with state. Exported because modes
+ // sometimes need to do this.
+ function copyState(mode, state) {
+ if (state === true) return state;
+ if (mode.copyState) return mode.copyState(state);
+ var nstate = {};
+ for (var n in state) {
+ var val = state[n];
+ if (val instanceof Array) val = val.concat([]);
+ nstate[n] = val;
+ }
+ return nstate;
+ }
+ CodeMirror.copyState = copyState;
+ function startState(mode, a1, a2) {
+ return mode.startState ? mode.startState(a1, a2) : true;
+ }
+ CodeMirror.startState = startState;
+
+ // The character stream used by a mode's parser.
+ function StringStream(string, tabSize) {
+ this.pos = this.start = 0;
+ this.string = string;
+ this.tabSize = tabSize || 8;
+ }
+ StringStream.prototype = {
+ eol: function() {return this.pos >= this.string.length;},
+ sol: function() {return this.pos == 0;},
+ peek: function() {return this.string.charAt(this.pos);},
+ next: function() {
+ if (this.pos < this.string.length)
+ return this.string.charAt(this.pos++);
+ },
+ eat: function(match) {
+ var ch = this.string.charAt(this.pos);
+ if (typeof match == "string") var ok = ch == match;
+ else var ok = ch && (match.test ? match.test(ch) : match(ch));
+ if (ok) {++this.pos; return ch;}
+ },
+ eatWhile: function(match) {
+ var start = this.pos;
+ while (this.eat(match)){}
+ return this.pos > start;
+ },
+ eatSpace: function() {
+ var start = this.pos;
+ while (/[\s\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;
+ return this.pos > start;
+ },
+ skipToEnd: function() {this.pos = this.string.length;},
+ skipTo: function(ch) {
+ var found = this.string.indexOf(ch, this.pos);
+ if (found > -1) {this.pos = found; return true;}
+ },
+ backUp: function(n) {this.pos -= n;},
+ column: function() {return countColumn(this.string, this.start, this.tabSize);},
+ indentation: function() {return countColumn(this.string, null, this.tabSize);},
+ match: function(pattern, consume, caseInsensitive) {
+ if (typeof pattern == "string") {
+ function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
+ if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {
+ if (consume !== false) this.pos += pattern.length;
+ return true;
+ }
+ } else {
+ var match = this.string.slice(this.pos).match(pattern);
+ if (match && consume !== false) this.pos += match[0].length;
+ return match;
+ }
+ },
+ current: function(){return this.string.slice(this.start, this.pos);}
+ };
+ CodeMirror.StringStream = StringStream;
+
+ function MarkedText(from, to, className, marker) {
+ this.from = from; this.to = to; this.style = className; this.marker = marker;
+ }
+ MarkedText.prototype = {
+ attach: function(line) { this.marker.set.push(line); },
+ detach: function(line) {
+ var ix = indexOf(this.marker.set, line);
+ if (ix > -1) this.marker.set.splice(ix, 1);
+ },
+ split: function(pos, lenBefore) {
+ if (this.to <= pos && this.to != null) return null;
+ var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;
+ var to = this.to == null ? null : this.to - pos + lenBefore;
+ return new MarkedText(from, to, this.style, this.marker);
+ },
+ dup: function() { return new MarkedText(null, null, this.style, this.marker); },
+ clipTo: function(fromOpen, from, toOpen, to, diff) {
+ if (fromOpen && to > this.from && (to < this.to || this.to == null))
+ this.from = null;
+ else if (this.from != null && this.from >= from)
+ this.from = Math.max(to, this.from) + diff;
+ if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))
+ this.to = null;
+ else if (this.to != null && this.to > from)
+ this.to = to < this.to ? this.to + diff : from;
+ },
+ isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },
+ sameSet: function(x) { return this.marker == x.marker; }
+ };
+
+ function Bookmark(pos) {
+ this.from = pos; this.to = pos; this.line = null;
+ }
+ Bookmark.prototype = {
+ attach: function(line) { this.line = line; },
+ detach: function(line) { if (this.line == line) this.line = null; },
+ split: function(pos, lenBefore) {
+ if (pos < this.from) {
+ this.from = this.to = (this.from - pos) + lenBefore;
+ return this;
+ }
+ },
+ isDead: function() { return this.from > this.to; },
+ clipTo: function(fromOpen, from, toOpen, to, diff) {
+ if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {
+ this.from = 0; this.to = -1;
+ } else if (this.from > from) {
+ this.from = this.to = Math.max(to, this.from) + diff;
+ }
+ },
+ sameSet: function(x) { return false; },
+ find: function() {
+ if (!this.line || !this.line.parent) return null;
+ return {line: lineNo(this.line), ch: this.from};
+ },
+ clear: function() {
+ if (this.line) {
+ var found = indexOf(this.line.marked, this);
+ if (found != -1) this.line.marked.splice(found, 1);
+ this.line = null;
+ }
+ }
+ };
+
+ // Line objects. These hold state related to a line, including
+ // highlighting info (the styles array).
+ function Line(text, styles) {
+ this.styles = styles || [text, null];
+ this.text = text;
+ this.height = 1;
+ this.marked = this.gutterMarker = this.className = this.bgClassName = this.handlers = null;
+ this.stateAfter = this.parent = this.hidden = null;
+ }
+ Line.inheritMarks = function(text, orig) {
+ var ln = new Line(text), mk = orig && orig.marked;
+ if (mk) {
+ for (var i = 0; i < mk.length; ++i) {
+ if (mk[i].to == null && mk[i].style) {
+ var newmk = ln.marked || (ln.marked = []), mark = mk[i];
+ var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);
+ }
+ }
+ }
+ return ln;
+ }
+ Line.prototype = {
+ // Replace a piece of a line, keeping the styles around it intact.
+ replace: function(from, to_, text) {
+ var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;
+ copyStyles(0, from, this.styles, st);
+ if (text) st.push(text, null);
+ copyStyles(to, this.text.length, this.styles, st);
+ this.styles = st;
+ this.text = this.text.slice(0, from) + text + this.text.slice(to);
+ this.stateAfter = null;
+ if (mk) {
+ var diff = text.length - (to - from);
+ for (var i = 0; i < mk.length; ++i) {
+ var mark = mk[i];
+ mark.clipTo(from == null, from || 0, to_ == null, to, diff);
+ if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}
+ }
+ }
+ },
+ // Split a part off a line, keeping styles and markers intact.
+ split: function(pos, textBefore) {
+ var st = [textBefore, null], mk = this.marked;
+ copyStyles(pos, this.text.length, this.styles, st);
+ var taken = new Line(textBefore + this.text.slice(pos), st);
+ if (mk) {
+ for (var i = 0; i < mk.length; ++i) {
+ var mark = mk[i];
+ var newmark = mark.split(pos, textBefore.length);
+ if (newmark) {
+ if (!taken.marked) taken.marked = [];
+ taken.marked.push(newmark); newmark.attach(taken);
+ if (newmark == mark) mk.splice(i--, 1);
+ }
+ }
+ }
+ return taken;
+ },
+ append: function(line) {
+ var mylen = this.text.length, mk = line.marked, mymk = this.marked;
+ this.text += line.text;
+ copyStyles(0, line.text.length, line.styles, this.styles);
+ if (mymk) {
+ for (var i = 0; i < mymk.length; ++i)
+ if (mymk[i].to == null) mymk[i].to = mylen;
+ }
+ if (mk && mk.length) {
+ if (!mymk) this.marked = mymk = [];
+ outer: for (var i = 0; i < mk.length; ++i) {
+ var mark = mk[i];
+ if (!mark.from) {
+ for (var j = 0; j < mymk.length; ++j) {
+ var mymark = mymk[j];
+ if (mymark.to == mylen && mymark.sameSet(mark)) {
+ mymark.to = mark.to == null ? null : mark.to + mylen;
+ if (mymark.isDead()) {
+ mymark.detach(this);
+ mk.splice(i--, 1);
+ }
+ continue outer;
+ }
+ }
+ }
+ mymk.push(mark);
+ mark.attach(this);
+ mark.from += mylen;
+ if (mark.to != null) mark.to += mylen;
+ }
+ }
+ },
+ fixMarkEnds: function(other) {
+ var mk = this.marked, omk = other.marked;
+ if (!mk) return;
+ outer: for (var i = 0; i < mk.length; ++i) {
+ var mark = mk[i], close = mark.to == null;
+ if (close && omk) {
+ for (var j = 0; j < omk.length; ++j) {
+ var om = omk[j];
+ if (!om.sameSet(mark) || om.from != null) continue
+ if (mark.from == this.text.length && om.to == 0) {
+ omk.splice(j, 1);
+ mk.splice(i--, 1);
+ continue outer;
+ } else {
+ close = false; break;
+ }
+ }
+ }
+ if (close) mark.to = this.text.length;
+ }
+ },
+ fixMarkStarts: function() {
+ var mk = this.marked;
+ if (!mk) return;
+ for (var i = 0; i < mk.length; ++i)
+ if (mk[i].from == null) mk[i].from = 0;
+ },
+ addMark: function(mark) {
+ mark.attach(this);
+ if (this.marked == null) this.marked = [];
+ this.marked.push(mark);
+ this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});
+ },
+ // Run the given mode's parser over a line, update the styles
+ // array, which contains alternating fragments of text and CSS
+ // classes.
+ highlight: function(mode, state, tabSize) {
+ var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;
+ var changed = false, curWord = st[0], prevWord;
+ if (this.text == "" && mode.blankLine) mode.blankLine(state);
+ while (!stream.eol()) {
+ var style = mode.token(stream, state);
+ var substr = this.text.slice(stream.start, stream.pos);
+ stream.start = stream.pos;
+ if (pos && st[pos-1] == style)
+ st[pos-2] += substr;
+ else if (substr) {
+ if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;
+ st[pos++] = substr; st[pos++] = style;
+ prevWord = curWord; curWord = st[pos];
+ }
+ // Give up when line is ridiculously long
+ if (stream.pos > 5000) {
+ st[pos++] = this.text.slice(stream.pos); st[pos++] = null;
+ break;
+ }
+ }
+ if (st.length != pos) {st.length = pos; changed = true;}
+ if (pos && st[pos-2] != prevWord) changed = true;
+ // Short lines with simple highlights return null, and are
+ // counted as changed by the driver because they are likely to
+ // highlight the same way in various contexts.
+ return changed || (st.length < 5 && this.text.length < 10 ? null : false);
+ },
+ // Fetch the parser token for a given character. Useful for hacks
+ // that want to inspect the mode state (say, for completion).
+ getTokenAt: function(mode, state, ch) {
+ var txt = this.text, stream = new StringStream(txt);
+ while (stream.pos < ch && !stream.eol()) {
+ stream.start = stream.pos;
+ var style = mode.token(stream, state);
+ }
+ return {start: stream.start,
+ end: stream.pos,
+ string: stream.current(),
+ className: style || null,
+ state: state};
+ },
+ indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},
+ // Produces an HTML fragment for the line, taking selection,
+ // marking, and highlighting into account.
+ getHTML: function(makeTab, wrapAt, wrapId, wrapWBR) {
+ var html = [], first = true, col = 0;
+ function span_(text, style) {
+ if (!text) return;
+ // Work around a bug where, in some compat modes, IE ignores leading spaces
+ if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
+ first = false;
+ if (text.indexOf("\t") == -1) {
+ col += text.length;
+ var escaped = htmlEscape(text);
+ } else {
+ var escaped = "";
+ for (var pos = 0;;) {
+ var idx = text.indexOf("\t", pos);
+ if (idx == -1) {
+ escaped += htmlEscape(text.slice(pos));
+ col += text.length - pos;
+ break;
+ } else {
+ col += idx - pos;
+ var tab = makeTab(col);
+ escaped += htmlEscape(text.slice(pos, idx)) + tab.html;
+ col += tab.width;
+ pos = idx + 1;
+ }
+ }
+ }
+ if (style) html.push('<span class="', style, '">', escaped, "</span>");
+ else html.push(escaped);
+ }
+ var span = span_;
+ if (wrapAt != null) {
+ var outPos = 0, open = "<span id=\"" + wrapId + "\">";
+ span = function(text, style) {
+ var l = text.length;
+ if (wrapAt >= outPos && wrapAt < outPos + l) {
+ if (wrapAt > outPos) {
+ span_(text.slice(0, wrapAt - outPos), style);
+ // See comment at the definition of spanAffectsWrapping
+ if (wrapWBR) html.push("<wbr>");
+ }
+ html.push(open);
+ var cut = wrapAt - outPos;
+ span_(opera ? text.slice(cut, cut + 1) : text.slice(cut), style);
+ html.push("</span>");
+ if (opera) span_(text.slice(cut + 1), style);
+ wrapAt--;
+ outPos += l;
+ } else {
+ outPos += l;
+ span_(text, style);
+ // Output empty wrapper when at end of line
+ // (Gecko and IE8+ do strange wrapping when adding a space
+ // to the end of the line. Other browsers don't react well
+ // to zero-width spaces. So we do hideous browser sniffing
+ // to determine which to use.)
+ if (outPos == wrapAt && outPos == len)
+ html.push(open + (gecko || (ie && !ie_lt8) ? "&#x200b;" : " ") + "</span>");
+ // Stop outputting HTML when gone sufficiently far beyond measure
+ else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){};
+ }
+ }
+ }
+
+ var st = this.styles, allText = this.text, marked = this.marked;
+ var len = allText.length;
+ function styleToClass(style) {
+ if (!style) return null;
+ return "cm-" + style.replace(/ +/g, " cm-");
+ }
+
+ if (!allText && wrapAt == null) {
+ span(" ");
+ } else if (!marked || !marked.length) {
+ for (var i = 0, ch = 0; ch < len; i+=2) {
+ var str = st[i], style = st[i+1], l = str.length;
+ if (ch + l > len) str = str.slice(0, len - ch);
+ ch += l;
+ span(str, styleToClass(style));
+ }
+ } else {
+ var pos = 0, i = 0, text = "", style, sg = 0;
+ var nextChange = marked[0].from || 0, marks = [], markpos = 0;
+ function advanceMarks() {
+ var m;
+ while (markpos < marked.length &&
+ ((m = marked[markpos]).from == pos || m.from == null)) {
+ if (m.style != null) marks.push(m);
+ ++markpos;
+ }
+ nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
+ for (var i = 0; i < marks.length; ++i) {
+ var to = marks[i].to;
+ if (to == null) to = Infinity;
+ if (to == pos) marks.splice(i--, 1);
+ else nextChange = Math.min(to, nextChange);
+ }
+ }
+ var m = 0;
+ while (pos < len) {
+ if (nextChange == pos) advanceMarks();
+ var upto = Math.min(len, nextChange);
+ while (true) {
+ if (text) {
+ var end = pos + text.length;
+ var appliedStyle = style;
+ for (var j = 0; j < marks.length; ++j)
+ appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style;
+ span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
+ if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
+ pos = end;
+ }
+ text = st[i++]; style = styleToClass(st[i++]);
+ }
+ }
+ }
+ return html.join("");
+ },
+ cleanUp: function() {
+ this.parent = null;
+ if (this.marked)
+ for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);
+ }
+ };
+ // Utility used by replace and split above
+ function copyStyles(from, to, source, dest) {
+ for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {
+ var part = source[i], end = pos + part.length;
+ if (state == 0) {
+ if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);
+ if (end >= from) state = 1;
+ } else if (state == 1) {
+ if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);
+ else dest.push(part, source[i+1]);
+ }
+ pos = end;
+ }
+ }
+
+ // Data structure that holds the sequence of lines.
+ function LeafChunk(lines) {
+ this.lines = lines;
+ this.parent = null;
+ for (var i = 0, e = lines.length, height = 0; i < e; ++i) {
+ lines[i].parent = this;
+ height += lines[i].height;
+ }
+ this.height = height;
+ }
+ LeafChunk.prototype = {
+ chunkSize: function() { return this.lines.length; },
+ remove: function(at, n, callbacks) {
+ for (var i = at, e = at + n; i < e; ++i) {
+ var line = this.lines[i];
+ this.height -= line.height;
+ line.cleanUp();
+ if (line.handlers)
+ for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);
+ }
+ this.lines.splice(at, n);
+ },
+ collapse: function(lines) {
+ lines.splice.apply(lines, [lines.length, 0].concat(this.lines));
+ },
+ insertHeight: function(at, lines, height) {
+ this.height += height;
+ this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at));
+ for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;
+ },
+ iterN: function(at, n, op) {
+ for (var e = at + n; at < e; ++at)
+ if (op(this.lines[at])) return true;
+ }
+ };
+ function BranchChunk(children) {
+ this.children = children;
+ var size = 0, height = 0;
+ for (var i = 0, e = children.length; i < e; ++i) {
+ var ch = children[i];
+ size += ch.chunkSize(); height += ch.height;
+ ch.parent = this;
+ }
+ this.size = size;
+ this.height = height;
+ this.parent = null;
+ }
+ BranchChunk.prototype = {
+ chunkSize: function() { return this.size; },
+ remove: function(at, n, callbacks) {
+ this.size -= n;
+ for (var i = 0; i < this.children.length; ++i) {
+ var child = this.children[i], sz = child.chunkSize();
+ if (at < sz) {
+ var rm = Math.min(n, sz - at), oldHeight = child.height;
+ child.remove(at, rm, callbacks);
+ this.height -= oldHeight - child.height;
+ if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }
+ if ((n -= rm) == 0) break;
+ at = 0;
+ } else at -= sz;
+ }
+ if (this.size - n < 25) {
+ var lines = [];
+ this.collapse(lines);
+ this.children = [new LeafChunk(lines)];
+ this.children[0].parent = this;
+ }
+ },
+ collapse: function(lines) {
+ for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);
+ },
+ insert: function(at, lines) {
+ var height = 0;
+ for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;
+ this.insertHeight(at, lines, height);
+ },
+ insertHeight: function(at, lines, height) {
+ this.size += lines.length;
+ this.height += height;
+ for (var i = 0, e = this.children.length; i < e; ++i) {
+ var child = this.children[i], sz = child.chunkSize();
+ if (at <= sz) {
+ child.insertHeight(at, lines, height);
+ if (child.lines && child.lines.length > 50) {
+ while (child.lines.length > 50) {
+ var spilled = child.lines.splice(child.lines.length - 25, 25);
+ var newleaf = new LeafChunk(spilled);
+ child.height -= newleaf.height;
+ this.children.splice(i + 1, 0, newleaf);
+ newleaf.parent = this;
+ }
+ this.maybeSpill();
+ }
+ break;
+ }
+ at -= sz;
+ }
+ },
+ maybeSpill: function() {
+ if (this.children.length <= 10) return;
+ var me = this;
+ do {
+ var spilled = me.children.splice(me.children.length - 5, 5);
+ var sibling = new BranchChunk(spilled);
+ if (!me.parent) { // Become the parent node
+ var copy = new BranchChunk(me.children);
+ copy.parent = me;
+ me.children = [copy, sibling];
+ me = copy;
+ } else {
+ me.size -= sibling.size;
+ me.height -= sibling.height;
+ var myIndex = indexOf(me.parent.children, me);
+ me.parent.children.splice(myIndex + 1, 0, sibling);
+ }
+ sibling.parent = me.parent;
+ } while (me.children.length > 10);
+ me.parent.maybeSpill();
+ },
+ iter: function(from, to, op) { this.iterN(from, to - from, op); },
+ iterN: function(at, n, op) {
+ for (var i = 0, e = this.children.length; i < e; ++i) {
+ var child = this.children[i], sz = child.chunkSize();
+ if (at < sz) {
+ var used = Math.min(n, sz - at);
+ if (child.iterN(at, used, op)) return true;
+ if ((n -= used) == 0) break;
+ at = 0;
+ } else at -= sz;
+ }
+ }
+ };
+
+ function getLineAt(chunk, n) {
+ while (!chunk.lines) {
+ for (var i = 0;; ++i) {
+ var child = chunk.children[i], sz = child.chunkSize();
+ if (n < sz) { chunk = child; break; }
+ n -= sz;
+ }
+ }
+ return chunk.lines[n];
+ }
+ function lineNo(line) {
+ if (line.parent == null) return null;
+ var cur = line.parent, no = indexOf(cur.lines, line);
+ for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {
+ for (var i = 0, e = chunk.children.length; ; ++i) {
+ if (chunk.children[i] == cur) break;
+ no += chunk.children[i].chunkSize();
+ }
+ }
+ return no;
+ }
+ function lineAtHeight(chunk, h) {
+ var n = 0;
+ outer: do {
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
+ var child = chunk.children[i], ch = child.height;
+ if (h < ch) { chunk = child; continue outer; }
+ h -= ch;
+ n += child.chunkSize();
+ }
+ return n;
+ } while (!chunk.lines);
+ for (var i = 0, e = chunk.lines.length; i < e; ++i) {
+ var line = chunk.lines[i], lh = line.height;
+ if (h < lh) break;
+ h -= lh;
+ }
+ return n + i;
+ }
+ function heightAtLine(chunk, n) {
+ var h = 0;
+ outer: do {
+ for (var i = 0, e = chunk.children.length; i < e; ++i) {
+ var child = chunk.children[i], sz = child.chunkSize();
+ if (n < sz) { chunk = child; continue outer; }
+ n -= sz;
+ h += child.height;
+ }
+ return h;
+ } while (!chunk.lines);
+ for (var i = 0; i < n; ++i) h += chunk.lines[i].height;
+ return h;
+ }
+
+ // The history object 'chunks' changes that are made close together
+ // and at almost the same time into bigger undoable units.
+ function History() {
+ this.time = 0;
+ this.done = []; this.undone = [];
+ this.compound = 0;
+ this.closed = false;
+ }
+ History.prototype = {
+ addChange: function(start, added, old) {
+ this.undone.length = 0;
+ var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1];
+ var dtime = time - this.time;
+
+ if (this.compound && cur && !this.closed) {
+ cur.push({start: start, added: added, old: old});
+ } else if (dtime > 400 || !last || this.closed ||
+ last.start > start + old.length || last.start + last.added < start) {
+ this.done.push([{start: start, added: added, old: old}]);
+ this.closed = false;
+ } else {
+ var startBefore = Math.max(0, last.start - start),
+ endAfter = Math.max(0, (start + old.length) - (last.start + last.added));
+ for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]);
+ for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]);
+ if (startBefore) last.start = start;
+ last.added += added - (old.length - startBefore - endAfter);
+ }
+ this.time = time;
+ },
+ startCompound: function() {
+ if (!this.compound++) this.closed = true;
+ },
+ endCompound: function() {
+ if (!--this.compound) this.closed = true;
+ }
+ };
+
+ function stopMethod() {e_stop(this);}
+ // Ensure an event has a stop method.
+ function addStop(event) {
+ if (!event.stop) event.stop = stopMethod;
+ return event;
+ }
+
+ function e_preventDefault(e) {
+ if (e.preventDefault) e.preventDefault();
+ else e.returnValue = false;
+ }
+ function e_stopPropagation(e) {
+ if (e.stopPropagation) e.stopPropagation();
+ else e.cancelBubble = true;
+ }
+ function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}
+ CodeMirror.e_stop = e_stop;
+ CodeMirror.e_preventDefault = e_preventDefault;
+ CodeMirror.e_stopPropagation = e_stopPropagation;
+
+ function e_target(e) {return e.target || e.srcElement;}
+ function e_button(e) {
+ var b = e.which;
+ if (b == null) {
+ if (e.button & 1) b = 1;
+ else if (e.button & 2) b = 3;
+ else if (e.button & 4) b = 2;
+ }
+ if (mac && e.ctrlKey && b == 1) b = 3;
+ return b;
+ }
+
+ // Allow 3rd-party code to override event properties by adding an override
+ // object to an event object.
+ function e_prop(e, prop) {
+ var overridden = e.override && e.override.hasOwnProperty(prop);
+ return overridden ? e.override[prop] : e[prop];
+ }
+
+ // Event handler registration. If disconnect is true, it'll return a
+ // function that unregisters the handler.
+ function connect(node, type, handler, disconnect) {
+ if (typeof node.addEventListener == "function") {
+ node.addEventListener(type, handler, false);
+ if (disconnect) return function() {node.removeEventListener(type, handler, false);};
+ } else {
+ var wrapHandler = function(event) {handler(event || window.event);};
+ node.attachEvent("on" + type, wrapHandler);
+ if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);};
+ }
+ }
+ CodeMirror.connect = connect;
+
+ function Delayed() {this.id = null;}
+ Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};
+
+ var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}};
+
+ var gecko = /gecko\/\d{7}/i.test(navigator.userAgent);
+ var ie = /MSIE \d/.test(navigator.userAgent);
+ var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent);
+ var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent);
+ var quirksMode = ie && document.documentMode == 5;
+ var webkit = /WebKit\//.test(navigator.userAgent);
+ var chrome = /Chrome\//.test(navigator.userAgent);
+ var opera = /Opera\//.test(navigator.userAgent);
+ var safari = /Apple Computer/.test(navigator.vendor);
+ var khtml = /KHTML\//.test(navigator.userAgent);
+ var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent);
+
+ // Detect drag-and-drop
+ var dragAndDrop = function() {
+ // There is *some* kind of drag-and-drop support in IE6-8, but I
+ // couldn't get it to work yet.
+ if (ie_lt9) return false;
+ var div = document.createElement('div');
+ return "draggable" in div || "dragDrop" in div;
+ }();
+
+ // Feature-detect whether newlines in textareas are converted to \r\n
+ var lineSep = function () {
+ var te = document.createElement("textarea");
+ te.value = "foo\nbar";
+ if (te.value.indexOf("\r") > -1) return "\r\n";
+ return "\n";
+ }();
+
+ // For a reason I have yet to figure out, some browsers disallow
+ // word wrapping between certain characters *only* if a new inline
+ // element is started between them. This makes it hard to reliably
+ // measure the position of things, since that requires inserting an
+ // extra span. This terribly fragile set of regexps matches the
+ // character combinations that suffer from this phenomenon on the
+ // various browsers.
+ var spanAffectsWrapping = /^$/; // Won't match any two-character string
+ if (gecko) spanAffectsWrapping = /$'/;
+ else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/;
+ else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/;
+
+ // Counts the column offset in a string, taking tabs into account.
+ // Used mostly to find indentation.
+ function countColumn(string, end, tabSize) {
+ if (end == null) {
+ end = string.search(/[^\s\u00a0]/);
+ if (end == -1) end = string.length;
+ }
+ for (var i = 0, n = 0; i < end; ++i) {
+ if (string.charAt(i) == "\t") n += tabSize - (n % tabSize);
+ else ++n;
+ }
+ return n;
+ }
+
+ function computedStyle(elt) {
+ if (elt.currentStyle) return elt.currentStyle;
+ return window.getComputedStyle(elt, null);
+ }
+
+ function eltOffset(node, screen) {
+ // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,
+ // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)
+ try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }
+ catch(e) { box = {top: 0, left: 0}; }
+ if (!screen) {
+ // Get the toplevel scroll, working around browser differences.
+ if (window.pageYOffset == null) {
+ var t = document.documentElement || document.body.parentNode;
+ if (t.scrollTop == null) t = document.body;
+ box.top += t.scrollTop; box.left += t.scrollLeft;
+ } else {
+ box.top += window.pageYOffset; box.left += window.pageXOffset;
+ }
+ }
+ return box;
+ }
+
+ // Get a node's text content.
+ function eltText(node) {
+ return node.textContent || node.innerText || node.nodeValue || "";
+ }
+ function selectInput(node) {
+ if (ios) { // Mobile Safari apparently has a bug where select() is broken.
+ node.selectionStart = 0;
+ node.selectionEnd = node.value.length;
+ } else node.select();
+ }
+
+ // Operations on {line, ch} objects.
+ function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}
+ function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}
+ function copyPos(x) {return {line: x.line, ch: x.ch};}
+
+ var escapeElement = document.createElement("pre");
+ function htmlEscape(str) {
+ escapeElement.textContent = str;
+ return escapeElement.innerHTML;
+ }
+ // Recent (late 2011) Opera betas insert bogus newlines at the start
+ // of the textContent, so we strip those.
+ if (htmlEscape("a") == "\na") {
+ htmlEscape = function(str) {
+ escapeElement.textContent = str;
+ return escapeElement.innerHTML.slice(1);
+ };
+ // Some IEs don't preserve tabs through innerHTML
+ } else if (htmlEscape("\t") != "\t") {
+ htmlEscape = function(str) {
+ escapeElement.innerHTML = "";
+ escapeElement.appendChild(document.createTextNode(str));
+ return escapeElement.innerHTML;
+ };
+ }
+ CodeMirror.htmlEscape = htmlEscape;
+
+ // Used to position the cursor after an undo/redo by finding the
+ // last edited character.
+ function editEnd(from, to) {
+ if (!to) return 0;
+ if (!from) return to.length;
+ for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)
+ if (from.charAt(i) != to.charAt(j)) break;
+ return j + 1;
+ }
+
+ function indexOf(collection, elt) {
+ if (collection.indexOf) return collection.indexOf(elt);
+ for (var i = 0, e = collection.length; i < e; ++i)
+ if (collection[i] == elt) return i;
+ return -1;
+ }
+ function isWordChar(ch) {
+ return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();
+ }
+
+ // See if "".split is the broken IE version, if so, provide an
+ // alternative way to split lines.
+ var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) {
+ var pos = 0, result = [], l = string.length;
+ while (pos <= l) {
+ var nl = string.indexOf("\n", pos);
+ if (nl == -1) nl = string.length;
+ var line = string.slice(pos, string.charAt(nl - 1) == "\r" ? nl - 1 : nl);
+ var rt = line.indexOf("\r");
+ if (rt != -1) {
+ result.push(line.slice(0, rt));
+ pos += rt + 1;
+ } else {
+ result.push(line);
+ pos = nl + 1;
+ }
+ }
+ return result;
+ } : function(string){return string.split(/\r\n?|\n/);};
+ CodeMirror.splitLines = splitLines;
+
+ var hasSelection = window.getSelection ? function(te) {
+ try { return te.selectionStart != te.selectionEnd; }
+ catch(e) { return false; }
+ } : function(te) {
+ try {var range = te.ownerDocument.selection.createRange();}
+ catch(e) {}
+ if (!range || range.parentElement() != te) return false;
+ return range.compareEndPoints("StartToEnd", range) != 0;
+ };
+
+ CodeMirror.defineMode("null", function() {
+ return {token: function(stream) {stream.skipToEnd();}};
+ });
+ CodeMirror.defineMIME("text/plain", "null");
+
+ var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt",
+ 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End",
+ 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert",
+ 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete",
+ 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\",
+ 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home",
+ 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"};
+ CodeMirror.keyNames = keyNames;
+ (function() {
+ // Number keys
+ for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);
+ // Alphabetic keys
+ for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);
+ // Function keys
+ for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i;
+ })();
+
+ return CodeMirror;
+})();
diff --git a/src/fauxton/assets/js/libs/d3.js b/src/fauxton/assets/js/libs/d3.js
new file mode 100644
index 000000000..44fc0b072
--- /dev/null
+++ b/src/fauxton/assets/js/libs/d3.js
@@ -0,0 +1,7026 @@
+(function() {
+ function d3_class(ctor, properties) {
+ try {
+ for (var key in properties) {
+ Object.defineProperty(ctor.prototype, key, {
+ value: properties[key],
+ enumerable: false
+ });
+ }
+ } catch (e) {
+ ctor.prototype = properties;
+ }
+ }
+ function d3_arrayCopy(pseudoarray) {
+ var i = -1, n = pseudoarray.length, array = [];
+ while (++i < n) array.push(pseudoarray[i]);
+ return array;
+ }
+ function d3_arraySlice(pseudoarray) {
+ return Array.prototype.slice.call(pseudoarray);
+ }
+ function d3_Map() {}
+ function d3_identity(d) {
+ return d;
+ }
+ function d3_this() {
+ return this;
+ }
+ function d3_true() {
+ return true;
+ }
+ function d3_functor(v) {
+ return typeof v === "function" ? v : function() {
+ return v;
+ };
+ }
+ function d3_rebind(target, source, method) {
+ return function() {
+ var value = method.apply(source, arguments);
+ return arguments.length ? target : value;
+ };
+ }
+ function d3_number(x) {
+ return x != null && !isNaN(x);
+ }
+ function d3_zipLength(d) {
+ return d.length;
+ }
+ function d3_splitter(d) {
+ return d == null;
+ }
+ function d3_collapse(s) {
+ return s.trim().replace(/\s+/g, " ");
+ }
+ function d3_range_integerScale(x) {
+ var k = 1;
+ while (x * k % 1) k *= 10;
+ return k;
+ }
+ function d3_dispatch() {}
+ function d3_dispatch_event(dispatch) {
+ function event() {
+ var z = listeners, i = -1, n = z.length, l;
+ while (++i < n) if (l = z[i].on) l.apply(this, arguments);
+ return dispatch;
+ }
+ var listeners = [], listenerByName = new d3_Map;
+ event.on = function(name, listener) {
+ var l = listenerByName.get(name), i;
+ if (arguments.length < 2) return l && l.on;
+ if (l) {
+ l.on = null;
+ listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
+ listenerByName.remove(name);
+ }
+ if (listener) listeners.push(listenerByName.set(name, {
+ on: listener
+ }));
+ return dispatch;
+ };
+ return event;
+ }
+ function d3_format_precision(x, p) {
+ return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1);
+ }
+ function d3_format_typeDefault(x) {
+ return x + "";
+ }
+ function d3_format_group(value) {
+ var i = value.lastIndexOf("."), f = i >= 0 ? value.substring(i) : (i = value.length, ""), t = [];
+ while (i > 0) t.push(value.substring(i -= 3, i + 3));
+ return t.reverse().join(",") + f;
+ }
+ function d3_formatPrefix(d, i) {
+ var k = Math.pow(10, Math.abs(8 - i) * 3);
+ return {
+ scale: i > 8 ? function(d) {
+ return d / k;
+ } : function(d) {
+ return d * k;
+ },
+ symbol: d
+ };
+ }
+ function d3_ease_clamp(f) {
+ return function(t) {
+ return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
+ };
+ }
+ function d3_ease_reverse(f) {
+ return function(t) {
+ return 1 - f(1 - t);
+ };
+ }
+ function d3_ease_reflect(f) {
+ return function(t) {
+ return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t));
+ };
+ }
+ function d3_ease_identity(t) {
+ return t;
+ }
+ function d3_ease_poly(e) {
+ return function(t) {
+ return Math.pow(t, e);
+ };
+ }
+ function d3_ease_sin(t) {
+ return 1 - Math.cos(t * Math.PI / 2);
+ }
+ function d3_ease_exp(t) {
+ return Math.pow(2, 10 * (t - 1));
+ }
+ function d3_ease_circle(t) {
+ return 1 - Math.sqrt(1 - t * t);
+ }
+ function d3_ease_elastic(a, p) {
+ var s;
+ if (arguments.length < 2) p = .45;
+ if (arguments.length < 1) {
+ a = 1;
+ s = p / 4;
+ } else s = p / (2 * Math.PI) * Math.asin(1 / a);
+ return function(t) {
+ return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * Math.PI / p);
+ };
+ }
+ function d3_ease_back(s) {
+ if (!s) s = 1.70158;
+ return function(t) {
+ return t * t * ((s + 1) * t - s);
+ };
+ }
+ function d3_ease_bounce(t) {
+ return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
+ }
+ function d3_eventCancel() {
+ d3.event.stopPropagation();
+ d3.event.preventDefault();
+ }
+ function d3_eventSource() {
+ var e = d3.event, s;
+ while (s = e.sourceEvent) e = s;
+ return e;
+ }
+ function d3_eventDispatch(target) {
+ var dispatch = new d3_dispatch, i = 0, n = arguments.length;
+ while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
+ dispatch.of = function(thiz, argumentz) {
+ return function(e1) {
+ try {
+ var e0 = e1.sourceEvent = d3.event;
+ e1.target = target;
+ d3.event = e1;
+ dispatch[e1.type].apply(thiz, argumentz);
+ } finally {
+ d3.event = e0;
+ }
+ };
+ };
+ return dispatch;
+ }
+ function d3_transform(m) {
+ var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
+ if (r0[0] * r1[1] < r1[0] * r0[1]) {
+ r0[0] *= -1;
+ r0[1] *= -1;
+ kx *= -1;
+ kz *= -1;
+ }
+ this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_transformDegrees;
+ this.translate = [ m.e, m.f ];
+ this.scale = [ kx, ky ];
+ this.skew = ky ? Math.atan2(kz, ky) * d3_transformDegrees : 0;
+ }
+ function d3_transformDot(a, b) {
+ return a[0] * b[0] + a[1] * b[1];
+ }
+ function d3_transformNormalize(a) {
+ var k = Math.sqrt(d3_transformDot(a, a));
+ if (k) {
+ a[0] /= k;
+ a[1] /= k;
+ }
+ return k;
+ }
+ function d3_transformCombine(a, b, k) {
+ a[0] += k * b[0];
+ a[1] += k * b[1];
+ return a;
+ }
+ function d3_interpolateByName(name) {
+ return name == "transform" ? d3.interpolateTransform : d3.interpolate;
+ }
+ function d3_uninterpolateNumber(a, b) {
+ b = b - (a = +a) ? 1 / (b - a) : 0;
+ return function(x) {
+ return (x - a) * b;
+ };
+ }
+ function d3_uninterpolateClamp(a, b) {
+ b = b - (a = +a) ? 1 / (b - a) : 0;
+ return function(x) {
+ return Math.max(0, Math.min(1, (x - a) * b));
+ };
+ }
+ function d3_Color() {}
+ function d3_rgb(r, g, b) {
+ return new d3_Rgb(r, g, b);
+ }
+ function d3_Rgb(r, g, b) {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ }
+ function d3_rgb_hex(v) {
+ return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
+ }
+ function d3_rgb_parse(format, rgb, hsl) {
+ var r = 0, g = 0, b = 0, m1, m2, name;
+ m1 = /([a-z]+)\((.*)\)/i.exec(format);
+ if (m1) {
+ m2 = m1[2].split(",");
+ switch (m1[1]) {
+ case "hsl":
+ {
+ return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100);
+ }
+ case "rgb":
+ {
+ return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2]));
+ }
+ }
+ }
+ if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b);
+ if (format != null && format.charAt(0) === "#") {
+ if (format.length === 4) {
+ r = format.charAt(1);
+ r += r;
+ g = format.charAt(2);
+ g += g;
+ b = format.charAt(3);
+ b += b;
+ } else if (format.length === 7) {
+ r = format.substring(1, 3);
+ g = format.substring(3, 5);
+ b = format.substring(5, 7);
+ }
+ r = parseInt(r, 16);
+ g = parseInt(g, 16);
+ b = parseInt(b, 16);
+ }
+ return rgb(r, g, b);
+ }
+ function d3_rgb_hsl(r, g, b) {
+ var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2;
+ if (d) {
+ s = l < .5 ? d / (max + min) : d / (2 - max - min);
+ if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4;
+ h *= 60;
+ } else {
+ s = h = 0;
+ }
+ return d3_hsl(h, s, l);
+ }
+ function d3_rgb_lab(r, g, b) {
+ r = d3_rgb_xyz(r);
+ g = d3_rgb_xyz(g);
+ b = d3_rgb_xyz(b);
+ var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z);
+ return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
+ }
+ function d3_rgb_xyz(r) {
+ return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4);
+ }
+ function d3_rgb_parseNumber(c) {
+ var f = parseFloat(c);
+ return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
+ }
+ function d3_hsl(h, s, l) {
+ return new d3_Hsl(h, s, l);
+ }
+ function d3_Hsl(h, s, l) {
+ this.h = h;
+ this.s = s;
+ this.l = l;
+ }
+ function d3_hsl_rgb(h, s, l) {
+ function v(h) {
+ if (h > 360) h -= 360; else if (h < 0) h += 360;
+ if (h < 60) return m1 + (m2 - m1) * h / 60;
+ if (h < 180) return m2;
+ if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
+ return m1;
+ }
+ function vv(h) {
+ return Math.round(v(h) * 255);
+ }
+ var m1, m2;
+ h = h % 360;
+ if (h < 0) h += 360;
+ s = s < 0 ? 0 : s > 1 ? 1 : s;
+ l = l < 0 ? 0 : l > 1 ? 1 : l;
+ m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
+ m1 = 2 * l - m2;
+ return d3_rgb(vv(h + 120), vv(h), vv(h - 120));
+ }
+ function d3_hcl(h, c, l) {
+ return new d3_Hcl(h, c, l);
+ }
+ function d3_Hcl(h, c, l) {
+ this.h = h;
+ this.c = c;
+ this.l = l;
+ }
+ function d3_hcl_lab(h, c, l) {
+ return d3_lab(l, Math.cos(h *= Math.PI / 180) * c, Math.sin(h) * c);
+ }
+ function d3_lab(l, a, b) {
+ return new d3_Lab(l, a, b);
+ }
+ function d3_Lab(l, a, b) {
+ this.l = l;
+ this.a = a;
+ this.b = b;
+ }
+ function d3_lab_rgb(l, a, b) {
+ var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200;
+ x = d3_lab_xyz(x) * d3_lab_X;
+ y = d3_lab_xyz(y) * d3_lab_Y;
+ z = d3_lab_xyz(z) * d3_lab_Z;
+ return d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z));
+ }
+ function d3_lab_hcl(l, a, b) {
+ return d3_hcl(Math.atan2(b, a) / Math.PI * 180, Math.sqrt(a * a + b * b), l);
+ }
+ function d3_lab_xyz(x) {
+ return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
+ }
+ function d3_xyz_lab(x) {
+ return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
+ }
+ function d3_xyz_rgb(r) {
+ return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055));
+ }
+ function d3_selection(groups) {
+ d3_arraySubclass(groups, d3_selectionPrototype);
+ return groups;
+ }
+ function d3_selection_selector(selector) {
+ return function() {
+ return d3_select(selector, this);
+ };
+ }
+ function d3_selection_selectorAll(selector) {
+ return function() {
+ return d3_selectAll(selector, this);
+ };
+ }
+ function d3_selection_attr(name, value) {
+ function attrNull() {
+ this.removeAttribute(name);
+ }
+ function attrNullNS() {
+ this.removeAttributeNS(name.space, name.local);
+ }
+ function attrConstant() {
+ this.setAttribute(name, value);
+ }
+ function attrConstantNS() {
+ this.setAttributeNS(name.space, name.local, value);
+ }
+ function attrFunction() {
+ var x = value.apply(this, arguments);
+ if (x == null) this.removeAttribute(name); else this.setAttribute(name, x);
+ }
+ function attrFunctionNS() {
+ var x = value.apply(this, arguments);
+ if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x);
+ }
+ name = d3.ns.qualify(name);
+ return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
+ }
+ function d3_selection_classedRe(name) {
+ return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
+ }
+ function d3_selection_classed(name, value) {
+ function classedConstant() {
+ var i = -1;
+ while (++i < n) name[i](this, value);
+ }
+ function classedFunction() {
+ var i = -1, x = value.apply(this, arguments);
+ while (++i < n) name[i](this, x);
+ }
+ name = name.trim().split(/\s+/).map(d3_selection_classedName);
+ var n = name.length;
+ return typeof value === "function" ? classedFunction : classedConstant;
+ }
+ function d3_selection_classedName(name) {
+ var re = d3_selection_classedRe(name);
+ return function(node, value) {
+ if (c = node.classList) return value ? c.add(name) : c.remove(name);
+ var c = node.className, cb = c.baseVal != null, cv = cb ? c.baseVal : c;
+ if (value) {
+ re.lastIndex = 0;
+ if (!re.test(cv)) {
+ cv = d3_collapse(cv + " " + name);
+ if (cb) c.baseVal = cv; else node.className = cv;
+ }
+ } else if (cv) {
+ cv = d3_collapse(cv.replace(re, " "));
+ if (cb) c.baseVal = cv; else node.className = cv;
+ }
+ };
+ }
+ function d3_selection_style(name, value, priority) {
+ function styleNull() {
+ this.style.removeProperty(name);
+ }
+ function styleConstant() {
+ this.style.setProperty(name, value, priority);
+ }
+ function styleFunction() {
+ var x = value.apply(this, arguments);
+ if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority);
+ }
+ return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant;
+ }
+ function d3_selection_property(name, value) {
+ function propertyNull() {
+ delete this[name];
+ }
+ function propertyConstant() {
+ this[name] = value;
+ }
+ function propertyFunction() {
+ var x = value.apply(this, arguments);
+ if (x == null) delete this[name]; else this[name] = x;
+ }
+ return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant;
+ }
+ function d3_selection_dataNode(data) {
+ return {
+ __data__: data
+ };
+ }
+ function d3_selection_filter(selector) {
+ return function() {
+ return d3_selectMatches(this, selector);
+ };
+ }
+ function d3_selection_sortComparator(comparator) {
+ if (!arguments.length) comparator = d3.ascending;
+ return function(a, b) {
+ return comparator(a && a.__data__, b && b.__data__);
+ };
+ }
+ function d3_selection_on(type, listener, capture) {
+ function onRemove() {
+ var wrapper = this[name];
+ if (wrapper) {
+ this.removeEventListener(type, wrapper, wrapper.$);
+ delete this[name];
+ }
+ }
+ function onAdd() {
+ function wrapper(e) {
+ var o = d3.event;
+ d3.event = e;
+ args[0] = node.__data__;
+ try {
+ listener.apply(node, args);
+ } finally {
+ d3.event = o;
+ }
+ }
+ var node = this, args = arguments;
+ onRemove.call(this);
+ this.addEventListener(type, this[name] = wrapper, wrapper.$ = capture);
+ wrapper._ = listener;
+ }
+ var name = "__on" + type, i = type.indexOf(".");
+ if (i > 0) type = type.substring(0, i);
+ return listener ? onAdd : onRemove;
+ }
+ function d3_selection_each(groups, callback) {
+ for (var j = 0, m = groups.length; j < m; j++) {
+ for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
+ if (node = group[i]) callback(node, i, j);
+ }
+ }
+ return groups;
+ }
+ function d3_selection_enter(selection) {
+ d3_arraySubclass(selection, d3_selection_enterPrototype);
+ return selection;
+ }
+ function d3_transition(groups, id, time) {
+ d3_arraySubclass(groups, d3_transitionPrototype);
+ var tweens = new d3_Map, event = d3.dispatch("start", "end"), ease = d3_transitionEase;
+ groups.id = id;
+ groups.time = time;
+ groups.tween = function(name, tween) {
+ if (arguments.length < 2) return tweens.get(name);
+ if (tween == null) tweens.remove(name); else tweens.set(name, tween);
+ return groups;
+ };
+ groups.ease = function(value) {
+ if (!arguments.length) return ease;
+ ease = typeof value === "function" ? value : d3.ease.apply(d3, arguments);
+ return groups;
+ };
+ groups.each = function(type, listener) {
+ if (arguments.length < 2) return d3_transition_each.call(groups, type);
+ event.on(type, listener);
+ return groups;
+ };
+ d3.timer(function(elapsed) {
+ return d3_selection_each(groups, function(node, i, j) {
+ function start(elapsed) {
+ if (lock.active > id) return stop();
+ lock.active = id;
+ tweens.forEach(function(key, value) {
+ if (value = value.call(node, d, i)) {
+ tweened.push(value);
+ }
+ });
+ event.start.call(node, d, i);
+ if (!tick(elapsed)) d3.timer(tick, 0, time);
+ return 1;
+ }
+ function tick(elapsed) {
+ if (lock.active !== id) return stop();
+ var t = (elapsed - delay) / duration, e = ease(t), n = tweened.length;
+ while (n > 0) {
+ tweened[--n].call(node, e);
+ }
+ if (t >= 1) {
+ stop();
+ d3_transitionId = id;
+ event.end.call(node, d, i);
+ d3_transitionId = 0;
+ return 1;
+ }
+ }
+ function stop() {
+ if (!--lock.count) delete node.__transition__;
+ return 1;
+ }
+ var tweened = [], delay = node.delay, duration = node.duration, lock = (node = node.node).__transition__ || (node.__transition__ = {
+ active: 0,
+ count: 0
+ }), d = node.__data__;
+ ++lock.count;
+ delay <= elapsed ? start(elapsed) : d3.timer(start, delay, time);
+ });
+ }, 0, time);
+ return groups;
+ }
+ function d3_transition_each(callback) {
+ var id = d3_transitionId, ease = d3_transitionEase, delay = d3_transitionDelay, duration = d3_transitionDuration;
+ d3_transitionId = this.id;
+ d3_transitionEase = this.ease();
+ d3_selection_each(this, function(node, i, j) {
+ d3_transitionDelay = node.delay;
+ d3_transitionDuration = node.duration;
+ callback.call(node = node.node, node.__data__, i, j);
+ });
+ d3_transitionId = id;
+ d3_transitionEase = ease;
+ d3_transitionDelay = delay;
+ d3_transitionDuration = duration;
+ return this;
+ }
+ function d3_tweenNull(d, i, a) {
+ return a != "" && d3_tweenRemove;
+ }
+ function d3_tweenByName(b, name) {
+ return d3.tween(b, d3_interpolateByName(name));
+ }
+ function d3_timer_step() {
+ var elapsed, now = Date.now(), t1 = d3_timer_queue;
+ while (t1) {
+ elapsed = now - t1.then;
+ if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed);
+ t1 = t1.next;
+ }
+ var delay = d3_timer_flush() - now;
+ if (delay > 24) {
+ if (isFinite(delay)) {
+ clearTimeout(d3_timer_timeout);
+ d3_timer_timeout = setTimeout(d3_timer_step, delay);
+ }
+ d3_timer_interval = 0;
+ } else {
+ d3_timer_interval = 1;
+ d3_timer_frame(d3_timer_step);
+ }
+ }
+ function d3_timer_flush() {
+ var t0 = null, t1 = d3_timer_queue, then = Infinity;
+ while (t1) {
+ if (t1.flush) {
+ delete d3_timer_byId[t1.callback.id];
+ t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next;
+ } else {
+ then = Math.min(then, t1.then + t1.delay);
+ t1 = (t0 = t1).next;
+ }
+ }
+ return then;
+ }
+ function d3_mousePoint(container, e) {
+ var svg = container.ownerSVGElement || container;
+ if (svg.createSVGPoint) {
+ var point = svg.createSVGPoint();
+ if (d3_mouse_bug44083 < 0 && (window.scrollX || window.scrollY)) {
+ svg = d3.select(document.body).append("svg").style("position", "absolute").style("top", 0).style("left", 0);
+ var ctm = svg[0][0].getScreenCTM();
+ d3_mouse_bug44083 = !(ctm.f || ctm.e);
+ svg.remove();
+ }
+ if (d3_mouse_bug44083) {
+ point.x = e.pageX;
+ point.y = e.pageY;
+ } else {
+ point.x = e.clientX;
+ point.y = e.clientY;
+ }
+ point = point.matrixTransform(container.getScreenCTM().inverse());
+ return [ point.x, point.y ];
+ }
+ var rect = container.getBoundingClientRect();
+ return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
+ }
+ function d3_noop() {}
+ function d3_scaleExtent(domain) {
+ var start = domain[0], stop = domain[domain.length - 1];
+ return start < stop ? [ start, stop ] : [ stop, start ];
+ }
+ function d3_scaleRange(scale) {
+ return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
+ }
+ function d3_scale_nice(domain, nice) {
+ var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx;
+ if (x1 < x0) {
+ dx = i0, i0 = i1, i1 = dx;
+ dx = x0, x0 = x1, x1 = dx;
+ }
+ if (nice = nice(x1 - x0)) {
+ domain[i0] = nice.floor(x0);
+ domain[i1] = nice.ceil(x1);
+ }
+ return domain;
+ }
+ function d3_scale_niceDefault() {
+ return Math;
+ }
+ function d3_scale_linear(domain, range, interpolate, clamp) {
+ function rescale() {
+ var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber;
+ output = linear(domain, range, uninterpolate, interpolate);
+ input = linear(range, domain, uninterpolate, d3.interpolate);
+ return scale;
+ }
+ function scale(x) {
+ return output(x);
+ }
+ var output, input;
+ scale.invert = function(y) {
+ return input(y);
+ };
+ scale.domain = function(x) {
+ if (!arguments.length) return domain;
+ domain = x.map(Number);
+ return rescale();
+ };
+ scale.range = function(x) {
+ if (!arguments.length) return range;
+ range = x;
+ return rescale();
+ };
+ scale.rangeRound = function(x) {
+ return scale.range(x).interpolate(d3.interpolateRound);
+ };
+ scale.clamp = function(x) {
+ if (!arguments.length) return clamp;
+ clamp = x;
+ return rescale();
+ };
+ scale.interpolate = function(x) {
+ if (!arguments.length) return interpolate;
+ interpolate = x;
+ return rescale();
+ };
+ scale.ticks = function(m) {
+ return d3_scale_linearTicks(domain, m);
+ };
+ scale.tickFormat = function(m) {
+ return d3_scale_linearTickFormat(domain, m);
+ };
+ scale.nice = function() {
+ d3_scale_nice(domain, d3_scale_linearNice);
+ return rescale();
+ };
+ scale.copy = function() {
+ return d3_scale_linear(domain, range, interpolate, clamp);
+ };
+ return rescale();
+ }
+ function d3_scale_linearRebind(scale, linear) {
+ return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
+ }
+ function d3_scale_linearNice(dx) {
+ dx = Math.pow(10, Math.round(Math.log(dx) / Math.LN10) - 1);
+ return dx && {
+ floor: function(x) {
+ return Math.floor(x / dx) * dx;
+ },
+ ceil: function(x) {
+ return Math.ceil(x / dx) * dx;
+ }
+ };
+ }
+ function d3_scale_linearTickRange(domain, m) {
+ var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step;
+ if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2;
+ extent[0] = Math.ceil(extent[0] / step) * step;
+ extent[1] = Math.floor(extent[1] / step) * step + step * .5;
+ extent[2] = step;
+ return extent;
+ }
+ function d3_scale_linearTicks(domain, m) {
+ return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
+ }
+ function d3_scale_linearTickFormat(domain, m) {
+ return d3.format(",." + Math.max(0, -Math.floor(Math.log(d3_scale_linearTickRange(domain, m)[2]) / Math.LN10 + .01)) + "f");
+ }
+ function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
+ var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]);
+ return function(x) {
+ return i(u(x));
+ };
+ }
+ function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
+ var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1;
+ if (domain[k] < domain[0]) {
+ domain = domain.slice().reverse();
+ range = range.slice().reverse();
+ }
+ while (++j <= k) {
+ u.push(uninterpolate(domain[j - 1], domain[j]));
+ i.push(interpolate(range[j - 1], range[j]));
+ }
+ return function(x) {
+ var j = d3.bisect(domain, x, 1, k) - 1;
+ return i[j](u[j](x));
+ };
+ }
+ function d3_scale_log(linear, log) {
+ function scale(x) {
+ return linear(log(x));
+ }
+ var pow = log.pow;
+ scale.invert = function(x) {
+ return pow(linear.invert(x));
+ };
+ scale.domain = function(x) {
+ if (!arguments.length) return linear.domain().map(pow);
+ log = x[0] < 0 ? d3_scale_logn : d3_scale_logp;
+ pow = log.pow;
+ linear.domain(x.map(log));
+ return scale;
+ };
+ scale.nice = function() {
+ linear.domain(d3_scale_nice(linear.domain(), d3_scale_niceDefault));
+ return scale;
+ };
+ scale.ticks = function() {
+ var extent = d3_scaleExtent(linear.domain()), ticks = [];
+ if (extent.every(isFinite)) {
+ var i = Math.floor(extent[0]), j = Math.ceil(extent[1]), u = pow(extent[0]), v = pow(extent[1]);
+ if (log === d3_scale_logn) {
+ ticks.push(pow(i));
+ for (; i++ < j; ) for (var k = 9; k > 0; k--) ticks.push(pow(i) * k);
+ } else {
+ for (; i < j; i++) for (var k = 1; k < 10; k++) ticks.push(pow(i) * k);
+ ticks.push(pow(i));
+ }
+ for (i = 0; ticks[i] < u; i++) {}
+ for (j = ticks.length; ticks[j - 1] > v; j--) {}
+ ticks = ticks.slice(i, j);
+ }
+ return ticks;
+ };
+ scale.tickFormat = function(n, format) {
+ if (arguments.length < 2) format = d3_scale_logFormat;
+ if (arguments.length < 1) return format;
+ var k = Math.max(.1, n / scale.ticks().length), f = log === d3_scale_logn ? (e = -1e-12, Math.floor) : (e = 1e-12, Math.ceil), e;
+ return function(d) {
+ return d / pow(f(log(d) + e)) <= k ? format(d) : "";
+ };
+ };
+ scale.copy = function() {
+ return d3_scale_log(linear.copy(), log);
+ };
+ return d3_scale_linearRebind(scale, linear);
+ }
+ function d3_scale_logp(x) {
+ return Math.log(x < 0 ? 0 : x) / Math.LN10;
+ }
+ function d3_scale_logn(x) {
+ return -Math.log(x > 0 ? 0 : -x) / Math.LN10;
+ }
+ function d3_scale_pow(linear, exponent) {
+ function scale(x) {
+ return linear(powp(x));
+ }
+ var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent);
+ scale.invert = function(x) {
+ return powb(linear.invert(x));
+ };
+ scale.domain = function(x) {
+ if (!arguments.length) return linear.domain().map(powb);
+ linear.domain(x.map(powp));
+ return scale;
+ };
+ scale.ticks = function(m) {
+ return d3_scale_linearTicks(scale.domain(), m);
+ };
+ scale.tickFormat = function(m) {
+ return d3_scale_linearTickFormat(scale.domain(), m);
+ };
+ scale.nice = function() {
+ return scale.domain(d3_scale_nice(scale.domain(), d3_scale_linearNice));
+ };
+ scale.exponent = function(x) {
+ if (!arguments.length) return exponent;
+ var domain = scale.domain();
+ powp = d3_scale_powPow(exponent = x);
+ powb = d3_scale_powPow(1 / exponent);
+ return scale.domain(domain);
+ };
+ scale.copy = function() {
+ return d3_scale_pow(linear.copy(), exponent);
+ };
+ return d3_scale_linearRebind(scale, linear);
+ }
+ function d3_scale_powPow(e) {
+ return function(x) {
+ return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e);
+ };
+ }
+ function d3_scale_ordinal(domain, ranger) {
+ function scale(x) {
+ return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length];
+ }
+ function steps(start, step) {
+ return d3.range(domain.length).map(function(i) {
+ return start + step * i;
+ });
+ }
+ var index, range, rangeBand;
+ scale.domain = function(x) {
+ if (!arguments.length) return domain;
+ domain = [];
+ index = new d3_Map;
+ var i = -1, n = x.length, xi;
+ while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi));
+ return scale[ranger.t].apply(scale, ranger.a);
+ };
+ scale.range = function(x) {
+ if (!arguments.length) return range;
+ range = x;
+ rangeBand = 0;
+ ranger = {
+ t: "range",
+ a: arguments
+ };
+ return scale;
+ };
+ scale.rangePoints = function(x, padding) {
+ if (arguments.length < 2) padding = 0;
+ var start = x[0], stop = x[1], step = (stop - start) / (Math.max(1, domain.length - 1) + padding);
+ range = steps(domain.length < 2 ? (start + stop) / 2 : start + step * padding / 2, step);
+ rangeBand = 0;
+ ranger = {
+ t: "rangePoints",
+ a: arguments
+ };
+ return scale;
+ };
+ scale.rangeBands = function(x, padding, outerPadding) {
+ if (arguments.length < 2) padding = 0;
+ if (arguments.length < 3) outerPadding = padding;
+ var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding);
+ range = steps(start + step * outerPadding, step);
+ if (reverse) range.reverse();
+ rangeBand = step * (1 - padding);
+ ranger = {
+ t: "rangeBands",
+ a: arguments
+ };
+ return scale;
+ };
+ scale.rangeRoundBands = function(x, padding, outerPadding) {
+ if (arguments.length < 2) padding = 0;
+ if (arguments.length < 3) outerPadding = padding;
+ var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding)), error = stop - start - (domain.length - padding) * step;
+ range = steps(start + Math.round(error / 2), step);
+ if (reverse) range.reverse();
+ rangeBand = Math.round(step * (1 - padding));
+ ranger = {
+ t: "rangeRoundBands",
+ a: arguments
+ };
+ return scale;
+ };
+ scale.rangeBand = function() {
+ return rangeBand;
+ };
+ scale.rangeExtent = function() {
+ return d3_scaleExtent(ranger.a[0]);
+ };
+ scale.copy = function() {
+ return d3_scale_ordinal(domain, ranger);
+ };
+ return scale.domain(domain);
+ }
+ function d3_scale_quantile(domain, range) {
+ function rescale() {
+ var k = 0, n = domain.length, q = range.length;
+ thresholds = [];
+ while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q);
+ return scale;
+ }
+ function scale(x) {
+ if (isNaN(x = +x)) return NaN;
+ return range[d3.bisect(thresholds, x)];
+ }
+ var thresholds;
+ scale.domain = function(x) {
+ if (!arguments.length) return domain;
+ domain = x.filter(function(d) {
+ return !isNaN(d);
+ }).sort(d3.ascending);
+ return rescale();
+ };
+ scale.range = function(x) {
+ if (!arguments.length) return range;
+ range = x;
+ return rescale();
+ };
+ scale.quantiles = function() {
+ return thresholds;
+ };
+ scale.copy = function() {
+ return d3_scale_quantile(domain, range);
+ };
+ return rescale();
+ }
+ function d3_scale_quantize(x0, x1, range) {
+ function scale(x) {
+ return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))];
+ }
+ function rescale() {
+ kx = range.length / (x1 - x0);
+ i = range.length - 1;
+ return scale;
+ }
+ var kx, i;
+ scale.domain = function(x) {
+ if (!arguments.length) return [ x0, x1 ];
+ x0 = +x[0];
+ x1 = +x[x.length - 1];
+ return rescale();
+ };
+ scale.range = function(x) {
+ if (!arguments.length) return range;
+ range = x;
+ return rescale();
+ };
+ scale.copy = function() {
+ return d3_scale_quantize(x0, x1, range);
+ };
+ return rescale();
+ }
+ function d3_scale_threshold(domain, range) {
+ function scale(x) {
+ return range[d3.bisect(domain, x)];
+ }
+ scale.domain = function(_) {
+ if (!arguments.length) return domain;
+ domain = _;
+ return scale;
+ };
+ scale.range = function(_) {
+ if (!arguments.length) return range;
+ range = _;
+ return scale;
+ };
+ scale.copy = function() {
+ return d3_scale_threshold(domain, range);
+ };
+ return scale;
+ }
+ function d3_scale_identity(domain) {
+ function identity(x) {
+ return +x;
+ }
+ identity.invert = identity;
+ identity.domain = identity.range = function(x) {
+ if (!arguments.length) return domain;
+ domain = x.map(identity);
+ return identity;
+ };
+ identity.ticks = function(m) {
+ return d3_scale_linearTicks(domain, m);
+ };
+ identity.tickFormat = function(m) {
+ return d3_scale_linearTickFormat(domain, m);
+ };
+ identity.copy = function() {
+ return d3_scale_identity(domain);
+ };
+ return identity;
+ }
+ function d3_svg_arcInnerRadius(d) {
+ return d.innerRadius;
+ }
+ function d3_svg_arcOuterRadius(d) {
+ return d.outerRadius;
+ }
+ function d3_svg_arcStartAngle(d) {
+ return d.startAngle;
+ }
+ function d3_svg_arcEndAngle(d) {
+ return d.endAngle;
+ }
+ function d3_svg_line(projection) {
+ function line(data) {
+ function segment() {
+ segments.push("M", interpolate(projection(points), tension));
+ }
+ var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y);
+ while (++i < n) {
+ if (defined.call(this, d = data[i], i)) {
+ points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]);
+ } else if (points.length) {
+ segment();
+ points = [];
+ }
+ }
+ if (points.length) segment();
+ return segments.length ? segments.join("") : null;
+ }
+ var x = d3_svg_lineX, y = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7;
+ line.x = function(_) {
+ if (!arguments.length) return x;
+ x = _;
+ return line;
+ };
+ line.y = function(_) {
+ if (!arguments.length) return y;
+ y = _;
+ return line;
+ };
+ line.defined = function(_) {
+ if (!arguments.length) return defined;
+ defined = _;
+ return line;
+ };
+ line.interpolate = function(_) {
+ if (!arguments.length) return interpolateKey;
+ if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
+ return line;
+ };
+ line.tension = function(_) {
+ if (!arguments.length) return tension;
+ tension = _;
+ return line;
+ };
+ return line;
+ }
+ function d3_svg_lineX(d) {
+ return d[0];
+ }
+ function d3_svg_lineY(d) {
+ return d[1];
+ }
+ function d3_svg_lineLinear(points) {
+ return points.join("L");
+ }
+ function d3_svg_lineLinearClosed(points) {
+ return d3_svg_lineLinear(points) + "Z";
+ }
+ function d3_svg_lineStepBefore(points) {
+ var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
+ while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]);
+ return path.join("");
+ }
+ function d3_svg_lineStepAfter(points) {
+ var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
+ while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]);
+ return path.join("");
+ }
+ function d3_svg_lineCardinalOpen(points, tension) {
+ return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), d3_svg_lineCardinalTangents(points, tension));
+ }
+ function d3_svg_lineCardinalClosed(points, tension) {
+ return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension));
+ }
+ function d3_svg_lineCardinal(points, tension, closed) {
+ return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension));
+ }
+ function d3_svg_lineHermite(points, tangents) {
+ if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) {
+ return d3_svg_lineLinear(points);
+ }
+ var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1;
+ if (quad) {
+ path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1];
+ p0 = points[1];
+ pi = 2;
+ }
+ if (tangents.length > 1) {
+ t = tangents[1];
+ p = points[pi];
+ pi++;
+ path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
+ for (var i = 2; i < tangents.length; i++, pi++) {
+ p = points[pi];
+ t = tangents[i];
+ path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
+ }
+ }
+ if (quad) {
+ var lp = points[pi];
+ path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1];
+ }
+ return path;
+ }
+ function d3_svg_lineCardinalTangents(points, tension) {
+ var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length;
+ while (++i < n) {
+ p0 = p1;
+ p1 = p2;
+ p2 = points[i];
+ tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]);
+ }
+ return tangents;
+ }
+ function d3_svg_lineBasis(points) {
+ if (points.length < 3) return d3_svg_lineLinear(points);
+ var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0 ];
+ d3_svg_lineBasisBezier(path, px, py);
+ while (++i < n) {
+ pi = points[i];
+ px.shift();
+ px.push(pi[0]);
+ py.shift();
+ py.push(pi[1]);
+ d3_svg_lineBasisBezier(path, px, py);
+ }
+ i = -1;
+ while (++i < 2) {
+ px.shift();
+ px.push(pi[0]);
+ py.shift();
+ py.push(pi[1]);
+ d3_svg_lineBasisBezier(path, px, py);
+ }
+ return path.join("");
+ }
+ function d3_svg_lineBasisOpen(points) {
+ if (points.length < 4) return d3_svg_lineLinear(points);
+ var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ];
+ while (++i < 3) {
+ pi = points[i];
+ px.push(pi[0]);
+ py.push(pi[1]);
+ }
+ path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py));
+ --i;
+ while (++i < n) {
+ pi = points[i];
+ px.shift();
+ px.push(pi[0]);
+ py.shift();
+ py.push(pi[1]);
+ d3_svg_lineBasisBezier(path, px, py);
+ }
+ return path.join("");
+ }
+ function d3_svg_lineBasisClosed(points) {
+ var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = [];
+ while (++i < 4) {
+ pi = points[i % n];
+ px.push(pi[0]);
+ py.push(pi[1]);
+ }
+ path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
+ --i;
+ while (++i < m) {
+ pi = points[i % n];
+ px.shift();
+ px.push(pi[0]);
+ py.shift();
+ py.push(pi[1]);
+ d3_svg_lineBasisBezier(path, px, py);
+ }
+ return path.join("");
+ }
+ function d3_svg_lineBundle(points, tension) {
+ var n = points.length - 1;
+ if (n) {
+ var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t;
+ while (++i <= n) {
+ p = points[i];
+ t = i / n;
+ p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx);
+ p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy);
+ }
+ }
+ return d3_svg_lineBasis(points);
+ }
+ function d3_svg_lineDot4(a, b) {
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
+ }
+ function d3_svg_lineBasisBezier(path, x, y) {
+ path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y));
+ }
+ function d3_svg_lineSlope(p0, p1) {
+ return (p1[1] - p0[1]) / (p1[0] - p0[0]);
+ }
+ function d3_svg_lineFiniteDifferences(points) {
+ var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1);
+ while (++i < j) {
+ m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
+ }
+ m[i] = d;
+ return m;
+ }
+ function d3_svg_lineMonotoneTangents(points) {
+ var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
+ while (++i < j) {
+ d = d3_svg_lineSlope(points[i], points[i + 1]);
+ if (Math.abs(d) < 1e-6) {
+ m[i] = m[i + 1] = 0;
+ } else {
+ a = m[i] / d;
+ b = m[i + 1] / d;
+ s = a * a + b * b;
+ if (s > 9) {
+ s = d * 3 / Math.sqrt(s);
+ m[i] = s * a;
+ m[i + 1] = s * b;
+ }
+ }
+ }
+ i = -1;
+ while (++i <= j) {
+ s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
+ tangents.push([ s || 0, m[i] * s || 0 ]);
+ }
+ return tangents;
+ }
+ function d3_svg_lineMonotone(points) {
+ return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points));
+ }
+ function d3_svg_lineRadial(points) {
+ var point, i = -1, n = points.length, r, a;
+ while (++i < n) {
+ point = points[i];
+ r = point[0];
+ a = point[1] + d3_svg_arcOffset;
+ point[0] = r * Math.cos(a);
+ point[1] = r * Math.sin(a);
+ }
+ return points;
+ }
+ function d3_svg_area(projection) {
+ function area(data) {
+ function segment() {
+ segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z");
+ }
+ var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() {
+ return x;
+ } : d3_functor(x1), fy1 = y0 === y1 ? function() {
+ return y;
+ } : d3_functor(y1), x, y;
+ while (++i < n) {
+ if (defined.call(this, d = data[i], i)) {
+ points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]);
+ points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]);
+ } else if (points0.length) {
+ segment();
+ points0 = [];
+ points1 = [];
+ }
+ }
+ if (points0.length) segment();
+ return segments.length ? segments.join("") : null;
+ }
+ var x0 = d3_svg_lineX, x1 = d3_svg_lineX, y0 = 0, y1 = d3_svg_lineY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7;
+ area.x = function(_) {
+ if (!arguments.length) return x1;
+ x0 = x1 = _;
+ return area;
+ };
+ area.x0 = function(_) {
+ if (!arguments.length) return x0;
+ x0 = _;
+ return area;
+ };
+ area.x1 = function(_) {
+ if (!arguments.length) return x1;
+ x1 = _;
+ return area;
+ };
+ area.y = function(_) {
+ if (!arguments.length) return y1;
+ y0 = y1 = _;
+ return area;
+ };
+ area.y0 = function(_) {
+ if (!arguments.length) return y0;
+ y0 = _;
+ return area;
+ };
+ area.y1 = function(_) {
+ if (!arguments.length) return y1;
+ y1 = _;
+ return area;
+ };
+ area.defined = function(_) {
+ if (!arguments.length) return defined;
+ defined = _;
+ return area;
+ };
+ area.interpolate = function(_) {
+ if (!arguments.length) return interpolateKey;
+ if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
+ interpolateReverse = interpolate.reverse || interpolate;
+ L = interpolate.closed ? "M" : "L";
+ return area;
+ };
+ area.tension = function(_) {
+ if (!arguments.length) return tension;
+ tension = _;
+ return area;
+ };
+ return area;
+ }
+ function d3_svg_chordSource(d) {
+ return d.source;
+ }
+ function d3_svg_chordTarget(d) {
+ return d.target;
+ }
+ function d3_svg_chordRadius(d) {
+ return d.radius;
+ }
+ function d3_svg_chordStartAngle(d) {
+ return d.startAngle;
+ }
+ function d3_svg_chordEndAngle(d) {
+ return d.endAngle;
+ }
+ function d3_svg_diagonalProjection(d) {
+ return [ d.x, d.y ];
+ }
+ function d3_svg_diagonalRadialProjection(projection) {
+ return function() {
+ var d = projection.apply(this, arguments), r = d[0], a = d[1] + d3_svg_arcOffset;
+ return [ r * Math.cos(a), r * Math.sin(a) ];
+ };
+ }
+ function d3_svg_symbolSize() {
+ return 64;
+ }
+ function d3_svg_symbolType() {
+ return "circle";
+ }
+ function d3_svg_symbolCircle(size) {
+ var r = Math.sqrt(size / Math.PI);
+ return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z";
+ }
+ function d3_svg_axisX(selection, x) {
+ selection.attr("transform", function(d) {
+ return "translate(" + x(d) + ",0)";
+ });
+ }
+ function d3_svg_axisY(selection, y) {
+ selection.attr("transform", function(d) {
+ return "translate(0," + y(d) + ")";
+ });
+ }
+ function d3_svg_axisSubdivide(scale, ticks, m) {
+ subticks = [];
+ if (m && ticks.length > 1) {
+ var extent = d3_scaleExtent(scale.domain()), subticks, i = -1, n = ticks.length, d = (ticks[1] - ticks[0]) / ++m, j, v;
+ while (++i < n) {
+ for (j = m; --j > 0; ) {
+ if ((v = +ticks[i] - j * d) >= extent[0]) {
+ subticks.push(v);
+ }
+ }
+ }
+ for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1]; ) {
+ subticks.push(v);
+ }
+ }
+ return subticks;
+ }
+ function d3_behavior_zoomDelta() {
+ if (!d3_behavior_zoomDiv) {
+ d3_behavior_zoomDiv = d3.select("body").append("div").style("visibility", "hidden").style("top", 0).style("height", 0).style("width", 0).style("overflow-y", "scroll").append("div").style("height", "2000px").node().parentNode;
+ }
+ var e = d3.event, delta;
+ try {
+ d3_behavior_zoomDiv.scrollTop = 1e3;
+ d3_behavior_zoomDiv.dispatchEvent(e);
+ delta = 1e3 - d3_behavior_zoomDiv.scrollTop;
+ } catch (error) {
+ delta = e.wheelDelta || -e.detail * 5;
+ }
+ return delta;
+ }
+ function d3_layout_bundlePath(link) {
+ var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ];
+ while (start !== lca) {
+ start = start.parent;
+ points.push(start);
+ }
+ var k = points.length;
+ while (end !== lca) {
+ points.splice(k, 0, end);
+ end = end.parent;
+ }
+ return points;
+ }
+ function d3_layout_bundleAncestors(node) {
+ var ancestors = [], parent = node.parent;
+ while (parent != null) {
+ ancestors.push(node);
+ node = parent;
+ parent = parent.parent;
+ }
+ ancestors.push(node);
+ return ancestors;
+ }
+ function d3_layout_bundleLeastCommonAncestor(a, b) {
+ if (a === b) return a;
+ var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null;
+ while (aNode === bNode) {
+ sharedNode = aNode;
+ aNode = aNodes.pop();
+ bNode = bNodes.pop();
+ }
+ return sharedNode;
+ }
+ function d3_layout_forceDragstart(d) {
+ d.fixed |= 2;
+ }
+ function d3_layout_forceDragend(d) {
+ d.fixed &= 1;
+ }
+ function d3_layout_forceMouseover(d) {
+ d.fixed |= 4;
+ }
+ function d3_layout_forceMouseout(d) {
+ d.fixed &= 3;
+ }
+ function d3_layout_forceAccumulate(quad, alpha, charges) {
+ var cx = 0, cy = 0;
+ quad.charge = 0;
+ if (!quad.leaf) {
+ var nodes = quad.nodes, n = nodes.length, i = -1, c;
+ while (++i < n) {
+ c = nodes[i];
+ if (c == null) continue;
+ d3_layout_forceAccumulate(c, alpha, charges);
+ quad.charge += c.charge;
+ cx += c.charge * c.cx;
+ cy += c.charge * c.cy;
+ }
+ }
+ if (quad.point) {
+ if (!quad.leaf) {
+ quad.point.x += Math.random() - .5;
+ quad.point.y += Math.random() - .5;
+ }
+ var k = alpha * charges[quad.point.index];
+ quad.charge += quad.pointCharge = k;
+ cx += k * quad.point.x;
+ cy += k * quad.point.y;
+ }
+ quad.cx = cx / quad.charge;
+ quad.cy = cy / quad.charge;
+ }
+ function d3_layout_forceLinkDistance(link) {
+ return 20;
+ }
+ function d3_layout_forceLinkStrength(link) {
+ return 1;
+ }
+ function d3_layout_stackX(d) {
+ return d.x;
+ }
+ function d3_layout_stackY(d) {
+ return d.y;
+ }
+ function d3_layout_stackOut(d, y0, y) {
+ d.y0 = y0;
+ d.y = y;
+ }
+ function d3_layout_stackOrderDefault(data) {
+ return d3.range(data.length);
+ }
+ function d3_layout_stackOffsetZero(data) {
+ var j = -1, m = data[0].length, y0 = [];
+ while (++j < m) y0[j] = 0;
+ return y0;
+ }
+ function d3_layout_stackMaxIndex(array) {
+ var i = 1, j = 0, v = array[0][1], k, n = array.length;
+ for (; i < n; ++i) {
+ if ((k = array[i][1]) > v) {
+ j = i;
+ v = k;
+ }
+ }
+ return j;
+ }
+ function d3_layout_stackReduceSum(d) {
+ return d.reduce(d3_layout_stackSum, 0);
+ }
+ function d3_layout_stackSum(p, d) {
+ return p + d[1];
+ }
+ function d3_layout_histogramBinSturges(range, values) {
+ return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
+ }
+ function d3_layout_histogramBinFixed(range, n) {
+ var x = -1, b = +range[0], m = (range[1] - b) / n, f = [];
+ while (++x <= n) f[x] = m * x + b;
+ return f;
+ }
+ function d3_layout_histogramRange(values) {
+ return [ d3.min(values), d3.max(values) ];
+ }
+ function d3_layout_hierarchyRebind(object, hierarchy) {
+ d3.rebind(object, hierarchy, "sort", "children", "value");
+ object.links = d3_layout_hierarchyLinks;
+ object.nodes = function(d) {
+ d3_layout_hierarchyInline = true;
+ return (object.nodes = object)(d);
+ };
+ return object;
+ }
+ function d3_layout_hierarchyChildren(d) {
+ return d.children;
+ }
+ function d3_layout_hierarchyValue(d) {
+ return d.value;
+ }
+ function d3_layout_hierarchySort(a, b) {
+ return b.value - a.value;
+ }
+ function d3_layout_hierarchyLinks(nodes) {
+ return d3.merge(nodes.map(function(parent) {
+ return (parent.children || []).map(function(child) {
+ return {
+ source: parent,
+ target: child
+ };
+ });
+ }));
+ }
+ function d3_layout_packSort(a, b) {
+ return a.value - b.value;
+ }
+ function d3_layout_packInsert(a, b) {
+ var c = a._pack_next;
+ a._pack_next = b;
+ b._pack_prev = a;
+ b._pack_next = c;
+ c._pack_prev = b;
+ }
+ function d3_layout_packSplice(a, b) {
+ a._pack_next = b;
+ b._pack_prev = a;
+ }
+ function d3_layout_packIntersects(a, b) {
+ var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r;
+ return dr * dr - dx * dx - dy * dy > .001;
+ }
+ function d3_layout_packSiblings(node) {
+ function bound(node) {
+ xMin = Math.min(node.x - node.r, xMin);
+ xMax = Math.max(node.x + node.r, xMax);
+ yMin = Math.min(node.y - node.r, yMin);
+ yMax = Math.max(node.y + node.r, yMax);
+ }
+ if (!(nodes = node.children) || !(n = nodes.length)) return;
+ var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n;
+ nodes.forEach(d3_layout_packLink);
+ a = nodes[0];
+ a.x = -a.r;
+ a.y = 0;
+ bound(a);
+ if (n > 1) {
+ b = nodes[1];
+ b.x = b.r;
+ b.y = 0;
+ bound(b);
+ if (n > 2) {
+ c = nodes[2];
+ d3_layout_packPlace(a, b, c);
+ bound(c);
+ d3_layout_packInsert(a, c);
+ a._pack_prev = c;
+ d3_layout_packInsert(c, b);
+ b = a._pack_next;
+ for (i = 3; i < n; i++) {
+ d3_layout_packPlace(a, b, c = nodes[i]);
+ var isect = 0, s1 = 1, s2 = 1;
+ for (j = b._pack_next; j !== b; j = j._pack_next, s1++) {
+ if (d3_layout_packIntersects(j, c)) {
+ isect = 1;
+ break;
+ }
+ }
+ if (isect == 1) {
+ for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) {
+ if (d3_layout_packIntersects(k, c)) {
+ break;
+ }
+ }
+ }
+ if (isect) {
+ if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b);
+ i--;
+ } else {
+ d3_layout_packInsert(a, c);
+ b = c;
+ bound(c);
+ }
+ }
+ }
+ }
+ var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0;
+ for (i = 0; i < n; i++) {
+ c = nodes[i];
+ c.x -= cx;
+ c.y -= cy;
+ cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y));
+ }
+ node.r = cr;
+ nodes.forEach(d3_layout_packUnlink);
+ }
+ function d3_layout_packLink(node) {
+ node._pack_next = node._pack_prev = node;
+ }
+ function d3_layout_packUnlink(node) {
+ delete node._pack_next;
+ delete node._pack_prev;
+ }
+ function d3_layout_packTransform(node, x, y, k) {
+ var children = node.children;
+ node.x = x += k * node.x;
+ node.y = y += k * node.y;
+ node.r *= k;
+ if (children) {
+ var i = -1, n = children.length;
+ while (++i < n) d3_layout_packTransform(children[i], x, y, k);
+ }
+ }
+ function d3_layout_packPlace(a, b, c) {
+ var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y;
+ if (db && (dx || dy)) {
+ var da = b.r + c.r, dc = dx * dx + dy * dy;
+ da *= da;
+ db *= db;
+ var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
+ c.x = a.x + x * dx + y * dy;
+ c.y = a.y + x * dy - y * dx;
+ } else {
+ c.x = a.x + db;
+ c.y = a.y;
+ }
+ }
+ function d3_layout_clusterY(children) {
+ return 1 + d3.max(children, function(child) {
+ return child.y;
+ });
+ }
+ function d3_layout_clusterX(children) {
+ return children.reduce(function(x, child) {
+ return x + child.x;
+ }, 0) / children.length;
+ }
+ function d3_layout_clusterLeft(node) {
+ var children = node.children;
+ return children && children.length ? d3_layout_clusterLeft(children[0]) : node;
+ }
+ function d3_layout_clusterRight(node) {
+ var children = node.children, n;
+ return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node;
+ }
+ function d3_layout_treeSeparation(a, b) {
+ return a.parent == b.parent ? 1 : 2;
+ }
+ function d3_layout_treeLeft(node) {
+ var children = node.children;
+ return children && children.length ? children[0] : node._tree.thread;
+ }
+ function d3_layout_treeRight(node) {
+ var children = node.children, n;
+ return children && (n = children.length) ? children[n - 1] : node._tree.thread;
+ }
+ function d3_layout_treeSearch(node, compare) {
+ var children = node.children;
+ if (children && (n = children.length)) {
+ var child, n, i = -1;
+ while (++i < n) {
+ if (compare(child = d3_layout_treeSearch(children[i], compare), node) > 0) {
+ node = child;
+ }
+ }
+ }
+ return node;
+ }
+ function d3_layout_treeRightmost(a, b) {
+ return a.x - b.x;
+ }
+ function d3_layout_treeLeftmost(a, b) {
+ return b.x - a.x;
+ }
+ function d3_layout_treeDeepest(a, b) {
+ return a.depth - b.depth;
+ }
+ function d3_layout_treeVisitAfter(node, callback) {
+ function visit(node, previousSibling) {
+ var children = node.children;
+ if (children && (n = children.length)) {
+ var child, previousChild = null, i = -1, n;
+ while (++i < n) {
+ child = children[i];
+ visit(child, previousChild);
+ previousChild = child;
+ }
+ }
+ callback(node, previousSibling);
+ }
+ visit(node, null);
+ }
+ function d3_layout_treeShift(node) {
+ var shift = 0, change = 0, children = node.children, i = children.length, child;
+ while (--i >= 0) {
+ child = children[i]._tree;
+ child.prelim += shift;
+ child.mod += shift;
+ shift += child.shift + (change += child.change);
+ }
+ }
+ function d3_layout_treeMove(ancestor, node, shift) {
+ ancestor = ancestor._tree;
+ node = node._tree;
+ var change = shift / (node.number - ancestor.number);
+ ancestor.change += change;
+ node.change -= change;
+ node.shift += shift;
+ node.prelim += shift;
+ node.mod += shift;
+ }
+ function d3_layout_treeAncestor(vim, node, ancestor) {
+ return vim._tree.ancestor.parent == node.parent ? vim._tree.ancestor : ancestor;
+ }
+ function d3_layout_treemapPadNull(node) {
+ return {
+ x: node.x,
+ y: node.y,
+ dx: node.dx,
+ dy: node.dy
+ };
+ }
+ function d3_layout_treemapPad(node, padding) {
+ var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2];
+ if (dx < 0) {
+ x += dx / 2;
+ dx = 0;
+ }
+ if (dy < 0) {
+ y += dy / 2;
+ dy = 0;
+ }
+ return {
+ x: x,
+ y: y,
+ dx: dx,
+ dy: dy
+ };
+ }
+ function d3_dsv(delimiter, mimeType) {
+ function dsv(url, callback) {
+ d3.text(url, mimeType, function(text) {
+ callback(text && dsv.parse(text));
+ });
+ }
+ function formatRow(row) {
+ return row.map(formatValue).join(delimiter);
+ }
+ function formatValue(text) {
+ return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text;
+ }
+ var reParse = new RegExp("\r\n|[" + delimiter + "\r\n]", "g"), reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0);
+ dsv.parse = function(text) {
+ var header;
+ return dsv.parseRows(text, function(row, i) {
+ if (i) {
+ var o = {}, j = -1, m = header.length;
+ while (++j < m) o[header[j]] = row[j];
+ return o;
+ } else {
+ header = row;
+ return null;
+ }
+ });
+ };
+ dsv.parseRows = function(text, f) {
+ function token() {
+ if (reParse.lastIndex >= text.length) return EOF;
+ if (eol) {
+ eol = false;
+ return EOL;
+ }
+ var j = reParse.lastIndex;
+ if (text.charCodeAt(j) === 34) {
+ var i = j;
+ while (i++ < text.length) {
+ if (text.charCodeAt(i) === 34) {
+ if (text.charCodeAt(i + 1) !== 34) break;
+ i++;
+ }
+ }
+ reParse.lastIndex = i + 2;
+ var c = text.charCodeAt(i + 1);
+ if (c === 13) {
+ eol = true;
+ if (text.charCodeAt(i + 2) === 10) reParse.lastIndex++;
+ } else if (c === 10) {
+ eol = true;
+ }
+ return text.substring(j + 1, i).replace(/""/g, '"');
+ }
+ var m = reParse.exec(text);
+ if (m) {
+ eol = m[0].charCodeAt(0) !== delimiterCode;
+ return text.substring(j, m.index);
+ }
+ reParse.lastIndex = text.length;
+ return text.substring(j);
+ }
+ var EOL = {}, EOF = {}, rows = [], n = 0, t, eol;
+ reParse.lastIndex = 0;
+ while ((t = token()) !== EOF) {
+ var a = [];
+ while (t !== EOL && t !== EOF) {
+ a.push(t);
+ t = token();
+ }
+ if (f && !(a = f(a, n++))) continue;
+ rows.push(a);
+ }
+ return rows;
+ };
+ dsv.format = function(rows) {
+ return rows.map(formatRow).join("\n");
+ };
+ return dsv;
+ }
+ function d3_geo_type(types, defaultValue) {
+ return function(object) {
+ return object && types.hasOwnProperty(object.type) ? types[object.type](object) : defaultValue;
+ };
+ }
+ function d3_path_circle(radius) {
+ return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + +2 * radius + "z";
+ }
+ function d3_geo_bounds(o, f) {
+ if (d3_geo_boundsTypes.hasOwnProperty(o.type)) d3_geo_boundsTypes[o.type](o, f);
+ }
+ function d3_geo_boundsFeature(o, f) {
+ d3_geo_bounds(o.geometry, f);
+ }
+ function d3_geo_boundsFeatureCollection(o, f) {
+ for (var a = o.features, i = 0, n = a.length; i < n; i++) {
+ d3_geo_bounds(a[i].geometry, f);
+ }
+ }
+ function d3_geo_boundsGeometryCollection(o, f) {
+ for (var a = o.geometries, i = 0, n = a.length; i < n; i++) {
+ d3_geo_bounds(a[i], f);
+ }
+ }
+ function d3_geo_boundsLineString(o, f) {
+ for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
+ f.apply(null, a[i]);
+ }
+ }
+ function d3_geo_boundsMultiLineString(o, f) {
+ for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
+ for (var b = a[i], j = 0, m = b.length; j < m; j++) {
+ f.apply(null, b[j]);
+ }
+ }
+ }
+ function d3_geo_boundsMultiPolygon(o, f) {
+ for (var a = o.coordinates, i = 0, n = a.length; i < n; i++) {
+ for (var b = a[i][0], j = 0, m = b.length; j < m; j++) {
+ f.apply(null, b[j]);
+ }
+ }
+ }
+ function d3_geo_boundsPoint(o, f) {
+ f.apply(null, o.coordinates);
+ }
+ function d3_geo_boundsPolygon(o, f) {
+ for (var a = o.coordinates[0], i = 0, n = a.length; i < n; i++) {
+ f.apply(null, a[i]);
+ }
+ }
+ function d3_geo_greatArcSource(d) {
+ return d.source;
+ }
+ function d3_geo_greatArcTarget(d) {
+ return d.target;
+ }
+ function d3_geo_greatArcInterpolator() {
+ function interpolate(t) {
+ var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1;
+ return [ Math.atan2(y, x) / d3_geo_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_geo_radians ];
+ }
+ var x0, y0, cy0, sy0, kx0, ky0, x1, y1, cy1, sy1, kx1, ky1, d, k;
+ interpolate.distance = function() {
+ if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))));
+ return d;
+ };
+ interpolate.source = function(_) {
+ var cx0 = Math.cos(x0 = _[0] * d3_geo_radians), sx0 = Math.sin(x0);
+ cy0 = Math.cos(y0 = _[1] * d3_geo_radians);
+ sy0 = Math.sin(y0);
+ kx0 = cy0 * cx0;
+ ky0 = cy0 * sx0;
+ d = null;
+ return interpolate;
+ };
+ interpolate.target = function(_) {
+ var cx1 = Math.cos(x1 = _[0] * d3_geo_radians), sx1 = Math.sin(x1);
+ cy1 = Math.cos(y1 = _[1] * d3_geo_radians);
+ sy1 = Math.sin(y1);
+ kx1 = cy1 * cx1;
+ ky1 = cy1 * sx1;
+ d = null;
+ return interpolate;
+ };
+ return interpolate;
+ }
+ function d3_geo_greatArcInterpolate(a, b) {
+ var i = d3_geo_greatArcInterpolator().source(a).target(b);
+ i.distance();
+ return i;
+ }
+ function d3_geom_contourStart(grid) {
+ var x = 0, y = 0;
+ while (true) {
+ if (grid(x, y)) {
+ return [ x, y ];
+ }
+ if (x === 0) {
+ x = y + 1;
+ y = 0;
+ } else {
+ x = x - 1;
+ y = y + 1;
+ }
+ }
+ }
+ function d3_geom_hullCCW(i1, i2, i3, v) {
+ var t, a, b, c, d, e, f;
+ t = v[i1];
+ a = t[0];
+ b = t[1];
+ t = v[i2];
+ c = t[0];
+ d = t[1];
+ t = v[i3];
+ e = t[0];
+ f = t[1];
+ return (f - b) * (c - a) - (d - b) * (e - a) > 0;
+ }
+ function d3_geom_polygonInside(p, a, b) {
+ return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
+ }
+ function d3_geom_polygonIntersect(c, d, a, b) {
+ var x1 = c[0], x2 = d[0], x3 = a[0], x4 = b[0], y1 = c[1], y2 = d[1], y3 = a[1], y4 = b[1], x13 = x1 - x3, x21 = x2 - x1, x43 = x4 - x3, y13 = y1 - y3, y21 = y2 - y1, y43 = y4 - y3, ua = (x43 * y13 - y43 * x13) / (y43 * x21 - x43 * y21);
+ return [ x1 + ua * x21, y1 + ua * y21 ];
+ }
+ function d3_voronoi_tessellate(vertices, callback) {
+ var Sites = {
+ list: vertices.map(function(v, i) {
+ return {
+ index: i,
+ x: v[0],
+ y: v[1]
+ };
+ }).sort(function(a, b) {
+ return a.y < b.y ? -1 : a.y > b.y ? 1 : a.x < b.x ? -1 : a.x > b.x ? 1 : 0;
+ }),
+ bottomSite: null
+ };
+ var EdgeList = {
+ list: [],
+ leftEnd: null,
+ rightEnd: null,
+ init: function() {
+ EdgeList.leftEnd = EdgeList.createHalfEdge(null, "l");
+ EdgeList.rightEnd = EdgeList.createHalfEdge(null, "l");
+ EdgeList.leftEnd.r = EdgeList.rightEnd;
+ EdgeList.rightEnd.l = EdgeList.leftEnd;
+ EdgeList.list.unshift(EdgeList.leftEnd, EdgeList.rightEnd);
+ },
+ createHalfEdge: function(edge, side) {
+ return {
+ edge: edge,
+ side: side,
+ vertex: null,
+ l: null,
+ r: null
+ };
+ },
+ insert: function(lb, he) {
+ he.l = lb;
+ he.r = lb.r;
+ lb.r.l = he;
+ lb.r = he;
+ },
+ leftBound: function(p) {
+ var he = EdgeList.leftEnd;
+ do {
+ he = he.r;
+ } while (he != EdgeList.rightEnd && Geom.rightOf(he, p));
+ he = he.l;
+ return he;
+ },
+ del: function(he) {
+ he.l.r = he.r;
+ he.r.l = he.l;
+ he.edge = null;
+ },
+ right: function(he) {
+ return he.r;
+ },
+ left: function(he) {
+ return he.l;
+ },
+ leftRegion: function(he) {
+ return he.edge == null ? Sites.bottomSite : he.edge.region[he.side];
+ },
+ rightRegion: function(he) {
+ return he.edge == null ? Sites.bottomSite : he.edge.region[d3_voronoi_opposite[he.side]];
+ }
+ };
+ var Geom = {
+ bisect: function(s1, s2) {
+ var newEdge = {
+ region: {
+ l: s1,
+ r: s2
+ },
+ ep: {
+ l: null,
+ r: null
+ }
+ };
+ var dx = s2.x - s1.x, dy = s2.y - s1.y, adx = dx > 0 ? dx : -dx, ady = dy > 0 ? dy : -dy;
+ newEdge.c = s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * .5;
+ if (adx > ady) {
+ newEdge.a = 1;
+ newEdge.b = dy / dx;
+ newEdge.c /= dx;
+ } else {
+ newEdge.b = 1;
+ newEdge.a = dx / dy;
+ newEdge.c /= dy;
+ }
+ return newEdge;
+ },
+ intersect: function(el1, el2) {
+ var e1 = el1.edge, e2 = el2.edge;
+ if (!e1 || !e2 || e1.region.r == e2.region.r) {
+ return null;
+ }
+ var d = e1.a * e2.b - e1.b * e2.a;
+ if (Math.abs(d) < 1e-10) {
+ return null;
+ }
+ var xint = (e1.c * e2.b - e2.c * e1.b) / d, yint = (e2.c * e1.a - e1.c * e2.a) / d, e1r = e1.region.r, e2r = e2.region.r, el, e;
+ if (e1r.y < e2r.y || e1r.y == e2r.y && e1r.x < e2r.x) {
+ el = el1;
+ e = e1;
+ } else {
+ el = el2;
+ e = e2;
+ }
+ var rightOfSite = xint >= e.region.r.x;
+ if (rightOfSite && el.side === "l" || !rightOfSite && el.side === "r") {
+ return null;
+ }
+ return {
+ x: xint,
+ y: yint
+ };
+ },
+ rightOf: function(he, p) {
+ var e = he.edge, topsite = e.region.r, rightOfSite = p.x > topsite.x;
+ if (rightOfSite && he.side === "l") {
+ return 1;
+ }
+ if (!rightOfSite && he.side === "r") {
+ return 0;
+ }
+ if (e.a === 1) {
+ var dyp = p.y - topsite.y, dxp = p.x - topsite.x, fast = 0, above = 0;
+ if (!rightOfSite && e.b < 0 || rightOfSite && e.b >= 0) {
+ above = fast = dyp >= e.b * dxp;
+ } else {
+ above = p.x + p.y * e.b > e.c;
+ if (e.b < 0) {
+ above = !above;
+ }
+ if (!above) {
+ fast = 1;
+ }
+ }
+ if (!fast) {
+ var dxs = topsite.x - e.region.l.x;
+ above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1 + 2 * dxp / dxs + e.b * e.b);
+ if (e.b < 0) {
+ above = !above;
+ }
+ }
+ } else {
+ var yl = e.c - e.a * p.x, t1 = p.y - yl, t2 = p.x - topsite.x, t3 = yl - topsite.y;
+ above = t1 * t1 > t2 * t2 + t3 * t3;
+ }
+ return he.side === "l" ? above : !above;
+ },
+ endPoint: function(edge, side, site) {
+ edge.ep[side] = site;
+ if (!edge.ep[d3_voronoi_opposite[side]]) return;
+ callback(edge);
+ },
+ distance: function(s, t) {
+ var dx = s.x - t.x, dy = s.y - t.y;
+ return Math.sqrt(dx * dx + dy * dy);
+ }
+ };
+ var EventQueue = {
+ list: [],
+ insert: function(he, site, offset) {
+ he.vertex = site;
+ he.ystar = site.y + offset;
+ for (var i = 0, list = EventQueue.list, l = list.length; i < l; i++) {
+ var next = list[i];
+ if (he.ystar > next.ystar || he.ystar == next.ystar && site.x > next.vertex.x) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ list.splice(i, 0, he);
+ },
+ del: function(he) {
+ for (var i = 0, ls = EventQueue.list, l = ls.length; i < l && ls[i] != he; ++i) {}
+ ls.splice(i, 1);
+ },
+ empty: function() {
+ return EventQueue.list.length === 0;
+ },
+ nextEvent: function(he) {
+ for (var i = 0, ls = EventQueue.list, l = ls.length; i < l; ++i) {
+ if (ls[i] == he) return ls[i + 1];
+ }
+ return null;
+ },
+ min: function() {
+ var elem = EventQueue.list[0];
+ return {
+ x: elem.vertex.x,
+ y: elem.ystar
+ };
+ },
+ extractMin: function() {
+ return EventQueue.list.shift();
+ }
+ };
+ EdgeList.init();
+ Sites.bottomSite = Sites.list.shift();
+ var newSite = Sites.list.shift(), newIntStar;
+ var lbnd, rbnd, llbnd, rrbnd, bisector;
+ var bot, top, temp, p, v;
+ var e, pm;
+ while (true) {
+ if (!EventQueue.empty()) {
+ newIntStar = EventQueue.min();
+ }
+ if (newSite && (EventQueue.empty() || newSite.y < newIntStar.y || newSite.y == newIntStar.y && newSite.x < newIntStar.x)) {
+ lbnd = EdgeList.leftBound(newSite);
+ rbnd = EdgeList.right(lbnd);
+ bot = EdgeList.rightRegion(lbnd);
+ e = Geom.bisect(bot, newSite);
+ bisector = EdgeList.createHalfEdge(e, "l");
+ EdgeList.insert(lbnd, bisector);
+ p = Geom.intersect(lbnd, bisector);
+ if (p) {
+ EventQueue.del(lbnd);
+ EventQueue.insert(lbnd, p, Geom.distance(p, newSite));
+ }
+ lbnd = bisector;
+ bisector = EdgeList.createHalfEdge(e, "r");
+ EdgeList.insert(lbnd, bisector);
+ p = Geom.intersect(bisector, rbnd);
+ if (p) {
+ EventQueue.insert(bisector, p, Geom.distance(p, newSite));
+ }
+ newSite = Sites.list.shift();
+ } else if (!EventQueue.empty()) {
+ lbnd = EventQueue.extractMin();
+ llbnd = EdgeList.left(lbnd);
+ rbnd = EdgeList.right(lbnd);
+ rrbnd = EdgeList.right(rbnd);
+ bot = EdgeList.leftRegion(lbnd);
+ top = EdgeList.rightRegion(rbnd);
+ v = lbnd.vertex;
+ Geom.endPoint(lbnd.edge, lbnd.side, v);
+ Geom.endPoint(rbnd.edge, rbnd.side, v);
+ EdgeList.del(lbnd);
+ EventQueue.del(rbnd);
+ EdgeList.del(rbnd);
+ pm = "l";
+ if (bot.y > top.y) {
+ temp = bot;
+ bot = top;
+ top = temp;
+ pm = "r";
+ }
+ e = Geom.bisect(bot, top);
+ bisector = EdgeList.createHalfEdge(e, pm);
+ EdgeList.insert(llbnd, bisector);
+ Geom.endPoint(e, d3_voronoi_opposite[pm], v);
+ p = Geom.intersect(llbnd, bisector);
+ if (p) {
+ EventQueue.del(llbnd);
+ EventQueue.insert(llbnd, p, Geom.distance(p, bot));
+ }
+ p = Geom.intersect(bisector, rrbnd);
+ if (p) {
+ EventQueue.insert(bisector, p, Geom.distance(p, bot));
+ }
+ } else {
+ break;
+ }
+ }
+ for (lbnd = EdgeList.right(EdgeList.leftEnd); lbnd != EdgeList.rightEnd; lbnd = EdgeList.right(lbnd)) {
+ callback(lbnd.edge);
+ }
+ }
+ function d3_geom_quadtreeNode() {
+ return {
+ leaf: true,
+ nodes: [],
+ point: null
+ };
+ }
+ function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
+ if (!f(node, x1, y1, x2, y2)) {
+ var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes;
+ if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
+ if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
+ if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
+ if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
+ }
+ }
+ function d3_geom_quadtreePoint(p) {
+ return {
+ x: p[0],
+ y: p[1]
+ };
+ }
+ function d3_time_utc() {
+ this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
+ }
+ function d3_time_formatAbbreviate(name) {
+ return name.substring(0, 3);
+ }
+ function d3_time_parse(date, template, string, j) {
+ var c, p, i = 0, n = template.length, m = string.length;
+ while (i < n) {
+ if (j >= m) return -1;
+ c = template.charCodeAt(i++);
+ if (c == 37) {
+ p = d3_time_parsers[template.charAt(i++)];
+ if (!p || (j = p(date, string, j)) < 0) return -1;
+ } else if (c != string.charCodeAt(j++)) {
+ return -1;
+ }
+ }
+ return j;
+ }
+ function d3_time_formatRe(names) {
+ return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i");
+ }
+ function d3_time_formatLookup(names) {
+ var map = new d3_Map, i = -1, n = names.length;
+ while (++i < n) map.set(names[i].toLowerCase(), i);
+ return map;
+ }
+ function d3_time_parseWeekdayAbbrev(date, string, i) {
+ d3_time_dayAbbrevRe.lastIndex = 0;
+ var n = d3_time_dayAbbrevRe.exec(string.substring(i));
+ return n ? i += n[0].length : -1;
+ }
+ function d3_time_parseWeekday(date, string, i) {
+ d3_time_dayRe.lastIndex = 0;
+ var n = d3_time_dayRe.exec(string.substring(i));
+ return n ? i += n[0].length : -1;
+ }
+ function d3_time_parseMonthAbbrev(date, string, i) {
+ d3_time_monthAbbrevRe.lastIndex = 0;
+ var n = d3_time_monthAbbrevRe.exec(string.substring(i));
+ return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i += n[0].length) : -1;
+ }
+ function d3_time_parseMonth(date, string, i) {
+ d3_time_monthRe.lastIndex = 0;
+ var n = d3_time_monthRe.exec(string.substring(i));
+ return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i += n[0].length) : -1;
+ }
+ function d3_time_parseLocaleFull(date, string, i) {
+ return d3_time_parse(date, d3_time_formats.c.toString(), string, i);
+ }
+ function d3_time_parseLocaleDate(date, string, i) {
+ return d3_time_parse(date, d3_time_formats.x.toString(), string, i);
+ }
+ function d3_time_parseLocaleTime(date, string, i) {
+ return d3_time_parse(date, d3_time_formats.X.toString(), string, i);
+ }
+ function d3_time_parseFullYear(date, string, i) {
+ d3_time_numberRe.lastIndex = 0;
+ var n = d3_time_numberRe.exec(string.substring(i, i + 4));
+ return n ? (date.y = +n[0], i += n[0].length) : -1;
+ }
+ function d3_time_parseYear(date, string, i) {
+ d3_time_numberRe.lastIndex = 0;
+ var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+ return n ? (date.y = d3_time_expandYear(+n[0]), i += n[0].length) : -1;
+ }
+ function d3_time_expandYear(d) {
+ return d + (d > 68 ? 1900 : 2e3);
+ }
+ function d3_time_parseMonthNumber(date, string, i) {
+ d3_time_numberRe.lastIndex = 0;
+ var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+ return n ? (date.m = n[0] - 1, i += n[0].length) : -1;
+ }
+ function d3_time_parseDay(date, string, i) {
+ d3_time_numberRe.lastIndex = 0;
+ var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+ return n ? (date.d = +n[0], i += n[0].length) : -1;
+ }
+ function d3_time_parseHour24(date, string, i) {
+ d3_time_numberRe.lastIndex = 0;
+ var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+ return n ? (date.H = +n[0], i += n[0].length) : -1;
+ }
+ function d3_time_parseMinutes(date, string, i) {
+ d3_time_numberRe.lastIndex = 0;
+ var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+ return n ? (date.M = +n[0], i += n[0].length) : -1;
+ }
+ function d3_time_parseSeconds(date, string, i) {
+ d3_time_numberRe.lastIndex = 0;
+ var n = d3_time_numberRe.exec(string.substring(i, i + 2));
+ return n ? (date.S = +n[0], i += n[0].length) : -1;
+ }
+ function d3_time_parseMilliseconds(date, string, i) {
+ d3_time_numberRe.lastIndex = 0;
+ var n = d3_time_numberRe.exec(string.substring(i, i + 3));
+ return n ? (date.L = +n[0], i += n[0].length) : -1;
+ }
+ function d3_time_parseAmPm(date, string, i) {
+ var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase());
+ return n == null ? -1 : (date.p = n, i);
+ }
+ function d3_time_zone(d) {
+ var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = ~~(Math.abs(z) / 60), zm = Math.abs(z) % 60;
+ return zs + d3_time_zfill2(zh) + d3_time_zfill2(zm);
+ }
+ function d3_time_formatIsoNative(date) {
+ return date.toISOString();
+ }
+ function d3_time_interval(local, step, number) {
+ function round(date) {
+ var d0 = local(date), d1 = offset(d0, 1);
+ return date - d0 < d1 - date ? d0 : d1;
+ }
+ function ceil(date) {
+ step(date = local(new d3_time(date - 1)), 1);
+ return date;
+ }
+ function offset(date, k) {
+ step(date = new d3_time(+date), k);
+ return date;
+ }
+ function range(t0, t1, dt) {
+ var time = ceil(t0), times = [];
+ if (dt > 1) {
+ while (time < t1) {
+ if (!(number(time) % dt)) times.push(new Date(+time));
+ step(time, 1);
+ }
+ } else {
+ while (time < t1) times.push(new Date(+time)), step(time, 1);
+ }
+ return times;
+ }
+ function range_utc(t0, t1, dt) {
+ try {
+ d3_time = d3_time_utc;
+ var utc = new d3_time_utc;
+ utc._ = t0;
+ return range(utc, t1, dt);
+ } finally {
+ d3_time = Date;
+ }
+ }
+ local.floor = local;
+ local.round = round;
+ local.ceil = ceil;
+ local.offset = offset;
+ local.range = range;
+ var utc = local.utc = d3_time_interval_utc(local);
+ utc.floor = utc;
+ utc.round = d3_time_interval_utc(round);
+ utc.ceil = d3_time_interval_utc(ceil);
+ utc.offset = d3_time_interval_utc(offset);
+ utc.range = range_utc;
+ return local;
+ }
+ function d3_time_interval_utc(method) {
+ return function(date, k) {
+ try {
+ d3_time = d3_time_utc;
+ var utc = new d3_time_utc;
+ utc._ = date;
+ return method(utc, k)._;
+ } finally {
+ d3_time = Date;
+ }
+ };
+ }
+ function d3_time_scale(linear, methods, format) {
+ function scale(x) {
+ return linear(x);
+ }
+ scale.invert = function(x) {
+ return d3_time_scaleDate(linear.invert(x));
+ };
+ scale.domain = function(x) {
+ if (!arguments.length) return linear.domain().map(d3_time_scaleDate);
+ linear.domain(x);
+ return scale;
+ };
+ scale.nice = function(m) {
+ return scale.domain(d3_scale_nice(scale.domain(), function() {
+ return m;
+ }));
+ };
+ scale.ticks = function(m, k) {
+ var extent = d3_time_scaleExtent(scale.domain());
+ if (typeof m !== "function") {
+ var span = extent[1] - extent[0], target = span / m, i = d3.bisect(d3_time_scaleSteps, target);
+ if (i == d3_time_scaleSteps.length) return methods.year(extent, m);
+ if (!i) return linear.ticks(m).map(d3_time_scaleDate);
+ if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i;
+ m = methods[i];
+ k = m[1];
+ m = m[0].range;
+ }
+ return m(extent[0], new Date(+extent[1] + 1), k);
+ };
+ scale.tickFormat = function() {
+ return format;
+ };
+ scale.copy = function() {
+ return d3_time_scale(linear.copy(), methods, format);
+ };
+ return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
+ }
+ function d3_time_scaleExtent(domain) {
+ var start = domain[0], stop = domain[domain.length - 1];
+ return start < stop ? [ start, stop ] : [ stop, start ];
+ }
+ function d3_time_scaleDate(t) {
+ return new Date(t);
+ }
+ function d3_time_scaleFormat(formats) {
+ return function(date) {
+ var i = formats.length - 1, f = formats[i];
+ while (!f[1](date)) f = formats[--i];
+ return f[0](date);
+ };
+ }
+ function d3_time_scaleSetYear(y) {
+ var d = new Date(y, 0, 1);
+ d.setFullYear(y);
+ return d;
+ }
+ function d3_time_scaleGetYear(d) {
+ var y = d.getFullYear(), d0 = d3_time_scaleSetYear(y), d1 = d3_time_scaleSetYear(y + 1);
+ return y + (d - d0) / (d1 - d0);
+ }
+ function d3_time_scaleUTCSetYear(y) {
+ var d = new Date(Date.UTC(y, 0, 1));
+ d.setUTCFullYear(y);
+ return d;
+ }
+ function d3_time_scaleUTCGetYear(d) {
+ var y = d.getUTCFullYear(), d0 = d3_time_scaleUTCSetYear(y), d1 = d3_time_scaleUTCSetYear(y + 1);
+ return y + (d - d0) / (d1 - d0);
+ }
+ if (!Date.now) Date.now = function() {
+ return +(new Date);
+ };
+ try {
+ document.createElement("div").style.setProperty("opacity", 0, "");
+ } catch (error) {
+ var d3_style_prototype = CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
+ d3_style_prototype.setProperty = function(name, value, priority) {
+ d3_style_setProperty.call(this, name, value + "", priority);
+ };
+ }
+ d3 = {
+ version: "2.10.3"
+ };
+ var d3_array = d3_arraySlice;
+ try {
+ d3_array(document.documentElement.childNodes)[0].nodeType;
+ } catch (e) {
+ d3_array = d3_arrayCopy;
+ }
+ var d3_arraySubclass = [].__proto__ ? function(array, prototype) {
+ array.__proto__ = prototype;
+ } : function(array, prototype) {
+ for (var property in prototype) array[property] = prototype[property];
+ };
+ d3.map = function(object) {
+ var map = new d3_Map;
+ for (var key in object) map.set(key, object[key]);
+ return map;
+ };
+ d3_class(d3_Map, {
+ has: function(key) {
+ return d3_map_prefix + key in this;
+ },
+ get: function(key) {
+ return this[d3_map_prefix + key];
+ },
+ set: function(key, value) {
+ return this[d3_map_prefix + key] = value;
+ },
+ remove: function(key) {
+ key = d3_map_prefix + key;
+ return key in this && delete this[key];
+ },
+ keys: function() {
+ var keys = [];
+ this.forEach(function(key) {
+ keys.push(key);
+ });
+ return keys;
+ },
+ values: function() {
+ var values = [];
+ this.forEach(function(key, value) {
+ values.push(value);
+ });
+ return values;
+ },
+ entries: function() {
+ var entries = [];
+ this.forEach(function(key, value) {
+ entries.push({
+ key: key,
+ value: value
+ });
+ });
+ return entries;
+ },
+ forEach: function(f) {
+ for (var key in this) {
+ if (key.charCodeAt(0) === d3_map_prefixCode) {
+ f.call(this, key.substring(1), this[key]);
+ }
+ }
+ }
+ });
+ var d3_map_prefix = "\0", d3_map_prefixCode = d3_map_prefix.charCodeAt(0);
+ d3.functor = d3_functor;
+ d3.rebind = function(target, source) {
+ var i = 1, n = arguments.length, method;
+ while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
+ return target;
+ };
+ d3.ascending = function(a, b) {
+ return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
+ };
+ d3.descending = function(a, b) {
+ return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
+ };
+ d3.mean = function(array, f) {
+ var n = array.length, a, m = 0, i = -1, j = 0;
+ if (arguments.length === 1) {
+ while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j;
+ } else {
+ while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j;
+ }
+ return j ? m : undefined;
+ };
+ d3.median = function(array, f) {
+ if (arguments.length > 1) array = array.map(f);
+ array = array.filter(d3_number);
+ return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined;
+ };
+ d3.min = function(array, f) {
+ var i = -1, n = array.length, a, b;
+ if (arguments.length === 1) {
+ while (++i < n && ((a = array[i]) == null || a != a)) a = undefined;
+ while (++i < n) if ((b = array[i]) != null && a > b) a = b;
+ } else {
+ while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined;
+ while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
+ }
+ return a;
+ };
+ d3.max = function(array, f) {
+ var i = -1, n = array.length, a, b;
+ if (arguments.length === 1) {
+ while (++i < n && ((a = array[i]) == null || a != a)) a = undefined;
+ while (++i < n) if ((b = array[i]) != null && b > a) a = b;
+ } else {
+ while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined;
+ while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
+ }
+ return a;
+ };
+ d3.extent = function(array, f) {
+ var i = -1, n = array.length, a, b, c;
+ if (arguments.length === 1) {
+ while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined;
+ while (++i < n) if ((b = array[i]) != null) {
+ if (a > b) a = b;
+ if (c < b) c = b;
+ }
+ } else {
+ while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined;
+ while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
+ if (a > b) a = b;
+ if (c < b) c = b;
+ }
+ }
+ return [ a, c ];
+ };
+ d3.random = {
+ normal: function(µ, σ) {
+ var n = arguments.length;
+ if (n < 2) σ = 1;
+ if (n < 1) µ = 0;
+ return function() {
+ var x, y, r;
+ do {
+ x = Math.random() * 2 - 1;
+ y = Math.random() * 2 - 1;
+ r = x * x + y * y;
+ } while (!r || r > 1);
+ return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r);
+ };
+ },
+ logNormal: function(µ, σ) {
+ var n = arguments.length;
+ if (n < 2) σ = 1;
+ if (n < 1) µ = 0;
+ var random = d3.random.normal();
+ return function() {
+ return Math.exp(µ + σ * random());
+ };
+ },
+ irwinHall: function(m) {
+ return function() {
+ for (var s = 0, j = 0; j < m; j++) s += Math.random();
+ return s / m;
+ };
+ }
+ };
+ d3.sum = function(array, f) {
+ var s = 0, n = array.length, a, i = -1;
+ if (arguments.length === 1) {
+ while (++i < n) if (!isNaN(a = +array[i])) s += a;
+ } else {
+ while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a;
+ }
+ return s;
+ };
+ d3.quantile = function(values, p) {
+ var H = (values.length - 1) * p + 1, h = Math.floor(H), v = values[h - 1], e = H - h;
+ return e ? v + e * (values[h] - v) : v;
+ };
+ d3.transpose = function(matrix) {
+ return d3.zip.apply(d3, matrix);
+ };
+ d3.zip = function() {
+ if (!(n = arguments.length)) return [];
+ for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m; ) {
+ for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) {
+ zip[j] = arguments[j][i];
+ }
+ }
+ return zips;
+ };
+ d3.bisector = function(f) {
+ return {
+ left: function(a, x, lo, hi) {
+ if (arguments.length < 3) lo = 0;
+ if (arguments.length < 4) hi = a.length;
+ while (lo < hi) {
+ var mid = lo + hi >>> 1;
+ if (f.call(a, a[mid], mid) < x) lo = mid + 1; else hi = mid;
+ }
+ return lo;
+ },
+ right: function(a, x, lo, hi) {
+ if (arguments.length < 3) lo = 0;
+ if (arguments.length < 4) hi = a.length;
+ while (lo < hi) {
+ var mid = lo + hi >>> 1;
+ if (x < f.call(a, a[mid], mid)) hi = mid; else lo = mid + 1;
+ }
+ return lo;
+ }
+ };
+ };
+ var d3_bisector = d3.bisector(function(d) {
+ return d;
+ });
+ d3.bisectLeft = d3_bisector.left;
+ d3.bisect = d3.bisectRight = d3_bisector.right;
+ d3.first = function(array, f) {
+ var i = 0, n = array.length, a = array[0], b;
+ if (arguments.length === 1) f = d3.ascending;
+ while (++i < n) {
+ if (f.call(array, a, b = array[i]) > 0) {
+ a = b;
+ }
+ }
+ return a;
+ };
+ d3.last = function(array, f) {
+ var i = 0, n = array.length, a = array[0], b;
+ if (arguments.length === 1) f = d3.ascending;
+ while (++i < n) {
+ if (f.call(array, a, b = array[i]) <= 0) {
+ a = b;
+ }
+ }
+ return a;
+ };
+ d3.nest = function() {
+ function map(array, depth) {
+ if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array;
+ var i = -1, n = array.length, key = keys[depth++], keyValue, object, valuesByKey = new d3_Map, values, o = {};
+ while (++i < n) {
+ if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
+ values.push(object);
+ } else {
+ valuesByKey.set(keyValue, [ object ]);
+ }
+ }
+ valuesByKey.forEach(function(keyValue, values) {
+ o[keyValue] = map(values, depth);
+ });
+ return o;
+ }
+ function entries(map, depth) {
+ if (depth >= keys.length) return map;
+ var a = [], sortKey = sortKeys[depth++], key;
+ for (key in map) {
+ a.push({
+ key: key,
+ values: entries(map[key], depth)
+ });
+ }
+ if (sortKey) a.sort(function(a, b) {
+ return sortKey(a.key, b.key);
+ });
+ return a;
+ }
+ var nest = {}, keys = [], sortKeys = [], sortValues, rollup;
+ nest.map = function(array) {
+ return map(array, 0);
+ };
+ nest.entries = function(array) {
+ return entries(map(array, 0), 0);
+ };
+ nest.key = function(d) {
+ keys.push(d);
+ return nest;
+ };
+ nest.sortKeys = function(order) {
+ sortKeys[keys.length - 1] = order;
+ return nest;
+ };
+ nest.sortValues = function(order) {
+ sortValues = order;
+ return nest;
+ };
+ nest.rollup = function(f) {
+ rollup = f;
+ return nest;
+ };
+ return nest;
+ };
+ d3.keys = function(map) {
+ var keys = [];
+ for (var key in map) keys.push(key);
+ return keys;
+ };
+ d3.values = function(map) {
+ var values = [];
+ for (var key in map) values.push(map[key]);
+ return values;
+ };
+ d3.entries = function(map) {
+ var entries = [];
+ for (var key in map) entries.push({
+ key: key,
+ value: map[key]
+ });
+ return entries;
+ };
+ d3.permute = function(array, indexes) {
+ var permutes = [], i = -1, n = indexes.length;
+ while (++i < n) permutes[i] = array[indexes[i]];
+ return permutes;
+ };
+ d3.merge = function(arrays) {
+ return Array.prototype.concat.apply([], arrays);
+ };
+ d3.split = function(array, f) {
+ var arrays = [], values = [], value, i = -1, n = array.length;
+ if (arguments.length < 2) f = d3_splitter;
+ while (++i < n) {
+ if (f.call(values, value = array[i], i)) {
+ values = [];
+ } else {
+ if (!values.length) arrays.push(values);
+ values.push(value);
+ }
+ }
+ return arrays;
+ };
+ d3.range = function(start, stop, step) {
+ if (arguments.length < 3) {
+ step = 1;
+ if (arguments.length < 2) {
+ stop = start;
+ start = 0;
+ }
+ }
+ if ((stop - start) / step === Infinity) throw new Error("infinite range");
+ var range = [], k = d3_range_integerScale(Math.abs(step)), i = -1, j;
+ start *= k, stop *= k, step *= k;
+ if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
+ return range;
+ };
+ d3.requote = function(s) {
+ return s.replace(d3_requote_re, "\\$&");
+ };
+ var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
+ d3.round = function(x, n) {
+ return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
+ };
+ d3.xhr = function(url, mime, callback) {
+ var req = new XMLHttpRequest;
+ if (arguments.length < 3) callback = mime, mime = null; else if (mime && req.overrideMimeType) req.overrideMimeType(mime);
+ req.open("GET", url, true);
+ if (mime) req.setRequestHeader("Accept", mime);
+ req.onreadystatechange = function() {
+ if (req.readyState === 4) {
+ var s = req.status;
+ callback(!s && req.response || s >= 200 && s < 300 || s === 304 ? req : null);
+ }
+ };
+ req.send(null);
+ };
+ d3.text = function(url, mime, callback) {
+ function ready(req) {
+ callback(req && req.responseText);
+ }
+ if (arguments.length < 3) {
+ callback = mime;
+ mime = null;
+ }
+ d3.xhr(url, mime, ready);
+ };
+ d3.json = function(url, callback) {
+ d3.text(url, "application/json", function(text) {
+ callback(text ? JSON.parse(text) : null);
+ });
+ };
+ d3.html = function(url, callback) {
+ d3.text(url, "text/html", function(text) {
+ if (text != null) {
+ var range = document.createRange();
+ range.selectNode(document.body);
+ text = range.createContextualFragment(text);
+ }
+ callback(text);
+ });
+ };
+ d3.xml = function(url, mime, callback) {
+ function ready(req) {
+ callback(req && req.responseXML);
+ }
+ if (arguments.length < 3) {
+ callback = mime;
+ mime = null;
+ }
+ d3.xhr(url, mime, ready);
+ };
+ var d3_nsPrefix = {
+ svg: "http://www.w3.org/2000/svg",
+ xhtml: "http://www.w3.org/1999/xhtml",
+ xlink: "http://www.w3.org/1999/xlink",
+ xml: "http://www.w3.org/XML/1998/namespace",
+ xmlns: "http://www.w3.org/2000/xmlns/"
+ };
+ d3.ns = {
+ prefix: d3_nsPrefix,
+ qualify: function(name) {
+ var i = name.indexOf(":"), prefix = name;
+ if (i >= 0) {
+ prefix = name.substring(0, i);
+ name = name.substring(i + 1);
+ }
+ return d3_nsPrefix.hasOwnProperty(prefix) ? {
+ space: d3_nsPrefix[prefix],
+ local: name
+ } : name;
+ }
+ };
+ d3.dispatch = function() {
+ var dispatch = new d3_dispatch, i = -1, n = arguments.length;
+ while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
+ return dispatch;
+ };
+ d3_dispatch.prototype.on = function(type, listener) {
+ var i = type.indexOf("."), name = "";
+ if (i > 0) {
+ name = type.substring(i + 1);
+ type = type.substring(0, i);
+ }
+ return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener);
+ };
+ d3.format = function(specifier) {
+ var match = d3_format_re.exec(specifier), fill = match[1] || " ", sign = match[3] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, suffix = "", integer = false;
+ if (precision) precision = +precision.substring(1);
+ if (zfill) {
+ fill = "0";
+ if (comma) width -= Math.floor((width - 1) / 4);
+ }
+ switch (type) {
+ case "n":
+ comma = true;
+ type = "g";
+ break;
+ case "%":
+ scale = 100;
+ suffix = "%";
+ type = "f";
+ break;
+ case "p":
+ scale = 100;
+ suffix = "%";
+ type = "r";
+ break;
+ case "d":
+ integer = true;
+ precision = 0;
+ break;
+ case "s":
+ scale = -1;
+ type = "r";
+ break;
+ }
+ if (type == "r" && !precision) type = "g";
+ type = d3_format_types.get(type) || d3_format_typeDefault;
+ return function(value) {
+ if (integer && value % 1) return "";
+ var negative = value < 0 && (value = -value) ? "-" : sign;
+ if (scale < 0) {
+ var prefix = d3.formatPrefix(value, precision);
+ value = prefix.scale(value);
+ suffix = prefix.symbol;
+ } else {
+ value *= scale;
+ }
+ value = type(value, precision);
+ if (zfill) {
+ var length = value.length + negative.length;
+ if (length < width) value = (new Array(width - length + 1)).join(fill) + value;
+ if (comma) value = d3_format_group(value);
+ value = negative + value;
+ } else {
+ if (comma) value = d3_format_group(value);
+ value = negative + value;
+ var length = value.length;
+ if (length < width) value = (new Array(width - length + 1)).join(fill) + value;
+ }
+ return value + suffix;
+ };
+ };
+ var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/;
+ var d3_format_types = d3.map({
+ g: function(x, p) {
+ return x.toPrecision(p);
+ },
+ e: function(x, p) {
+ return x.toExponential(p);
+ },
+ f: function(x, p) {
+ return x.toFixed(p);
+ },
+ r: function(x, p) {
+ return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p)));
+ }
+ });
+ var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "μ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
+ d3.formatPrefix = function(value, precision) {
+ var i = 0;
+ if (value) {
+ if (value < 0) value *= -1;
+ if (precision) value = d3.round(value, d3_format_precision(value, precision));
+ i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
+ i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3));
+ }
+ return d3_formatPrefixes[8 + i / 3];
+ };
+ var d3_ease_quad = d3_ease_poly(2), d3_ease_cubic = d3_ease_poly(3), d3_ease_default = function() {
+ return d3_ease_identity;
+ };
+ var d3_ease = d3.map({
+ linear: d3_ease_default,
+ poly: d3_ease_poly,
+ quad: function() {
+ return d3_ease_quad;
+ },
+ cubic: function() {
+ return d3_ease_cubic;
+ },
+ sin: function() {
+ return d3_ease_sin;
+ },
+ exp: function() {
+ return d3_ease_exp;
+ },
+ circle: function() {
+ return d3_ease_circle;
+ },
+ elastic: d3_ease_elastic,
+ back: d3_ease_back,
+ bounce: function() {
+ return d3_ease_bounce;
+ }
+ });
+ var d3_ease_mode = d3.map({
+ "in": d3_ease_identity,
+ out: d3_ease_reverse,
+ "in-out": d3_ease_reflect,
+ "out-in": function(f) {
+ return d3_ease_reflect(d3_ease_reverse(f));
+ }
+ });
+ d3.ease = function(name) {
+ var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in";
+ t = d3_ease.get(t) || d3_ease_default;
+ m = d3_ease_mode.get(m) || d3_ease_identity;
+ return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1))));
+ };
+ d3.event = null;
+ d3.transform = function(string) {
+ var g = document.createElementNS(d3.ns.prefix.svg, "g");
+ return (d3.transform = function(string) {
+ g.setAttribute("transform", string);
+ var t = g.transform.baseVal.consolidate();
+ return new d3_transform(t ? t.matrix : d3_transformIdentity);
+ })(string);
+ };
+ d3_transform.prototype.toString = function() {
+ return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")";
+ };
+ var d3_transformDegrees = 180 / Math.PI, d3_transformIdentity = {
+ a: 1,
+ b: 0,
+ c: 0,
+ d: 1,
+ e: 0,
+ f: 0
+ };
+ d3.interpolate = function(a, b) {
+ var i = d3.interpolators.length, f;
+ while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ;
+ return f;
+ };
+ d3.interpolateNumber = function(a, b) {
+ b -= a;
+ return function(t) {
+ return a + b * t;
+ };
+ };
+ d3.interpolateRound = function(a, b) {
+ b -= a;
+ return function(t) {
+ return Math.round(a + b * t);
+ };
+ };
+ d3.interpolateString = function(a, b) {
+ var m, i, j, s0 = 0, s1 = 0, s = [], q = [], n, o;
+ d3_interpolate_number.lastIndex = 0;
+ for (i = 0; m = d3_interpolate_number.exec(b); ++i) {
+ if (m.index) s.push(b.substring(s0, s1 = m.index));
+ q.push({
+ i: s.length,
+ x: m[0]
+ });
+ s.push(null);
+ s0 = d3_interpolate_number.lastIndex;
+ }
+ if (s0 < b.length) s.push(b.substring(s0));
+ for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) {
+ o = q[i];
+ if (o.x == m[0]) {
+ if (o.i) {
+ if (s[o.i + 1] == null) {
+ s[o.i - 1] += o.x;
+ s.splice(o.i, 1);
+ for (j = i + 1; j < n; ++j) q[j].i--;
+ } else {
+ s[o.i - 1] += o.x + s[o.i + 1];
+ s.splice(o.i, 2);
+ for (j = i + 1; j < n; ++j) q[j].i -= 2;
+ }
+ } else {
+ if (s[o.i + 1] == null) {
+ s[o.i] = o.x;
+ } else {
+ s[o.i] = o.x + s[o.i + 1];
+ s.splice(o.i + 1, 1);
+ for (j = i + 1; j < n; ++j) q[j].i--;
+ }
+ }
+ q.splice(i, 1);
+ n--;
+ i--;
+ } else {
+ o.x = d3.interpolateNumber(parseFloat(m[0]), parseFloat(o.x));
+ }
+ }
+ while (i < n) {
+ o = q.pop();
+ if (s[o.i + 1] == null) {
+ s[o.i] = o.x;
+ } else {
+ s[o.i] = o.x + s[o.i + 1];
+ s.splice(o.i + 1, 1);
+ }
+ n--;
+ }
+ if (s.length === 1) {
+ return s[0] == null ? q[0].x : function() {
+ return b;
+ };
+ }
+ return function(t) {
+ for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t);
+ return s.join("");
+ };
+ };
+ d3.interpolateTransform = function(a, b) {
+ var s = [], q = [], n, A = d3.transform(a), B = d3.transform(b), ta = A.translate, tb = B.translate, ra = A.rotate, rb = B.rotate, wa = A.skew, wb = B.skew, ka = A.scale, kb = B.scale;
+ if (ta[0] != tb[0] || ta[1] != tb[1]) {
+ s.push("translate(", null, ",", null, ")");
+ q.push({
+ i: 1,
+ x: d3.interpolateNumber(ta[0], tb[0])
+ }, {
+ i: 3,
+ x: d3.interpolateNumber(ta[1], tb[1])
+ });
+ } else if (tb[0] || tb[1]) {
+ s.push("translate(" + tb + ")");
+ } else {
+ s.push("");
+ }
+ if (ra != rb) {
+ if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360;
+ q.push({
+ i: s.push(s.pop() + "rotate(", null, ")") - 2,
+ x: d3.interpolateNumber(ra, rb)
+ });
+ } else if (rb) {
+ s.push(s.pop() + "rotate(" + rb + ")");
+ }
+ if (wa != wb) {
+ q.push({
+ i: s.push(s.pop() + "skewX(", null, ")") - 2,
+ x: d3.interpolateNumber(wa, wb)
+ });
+ } else if (wb) {
+ s.push(s.pop() + "skewX(" + wb + ")");
+ }
+ if (ka[0] != kb[0] || ka[1] != kb[1]) {
+ n = s.push(s.pop() + "scale(", null, ",", null, ")");
+ q.push({
+ i: n - 4,
+ x: d3.interpolateNumber(ka[0], kb[0])
+ }, {
+ i: n - 2,
+ x: d3.interpolateNumber(ka[1], kb[1])
+ });
+ } else if (kb[0] != 1 || kb[1] != 1) {
+ s.push(s.pop() + "scale(" + kb + ")");
+ }
+ n = q.length;
+ return function(t) {
+ var i = -1, o;
+ while (++i < n) s[(o = q[i]).i] = o.x(t);
+ return s.join("");
+ };
+ };
+ d3.interpolateRgb = function(a, b) {
+ a = d3.rgb(a);
+ b = d3.rgb(b);
+ var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
+ return function(t) {
+ return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
+ };
+ };
+ d3.interpolateHsl = function(a, b) {
+ a = d3.hsl(a);
+ b = d3.hsl(b);
+ var h0 = a.h, s0 = a.s, l0 = a.l, h1 = b.h - h0, s1 = b.s - s0, l1 = b.l - l0;
+ if (h1 > 180) h1 -= 360; else if (h1 < -180) h1 += 360;
+ return function(t) {
+ return d3_hsl_rgb(h0 + h1 * t, s0 + s1 * t, l0 + l1 * t) + "";
+ };
+ };
+ d3.interpolateLab = function(a, b) {
+ a = d3.lab(a);
+ b = d3.lab(b);
+ var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab;
+ return function(t) {
+ return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
+ };
+ };
+ d3.interpolateHcl = function(a, b) {
+ a = d3.hcl(a);
+ b = d3.hcl(b);
+ var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al;
+ if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
+ return function(t) {
+ return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + "";
+ };
+ };
+ d3.interpolateArray = function(a, b) {
+ var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i;
+ for (i = 0; i < n0; ++i) x.push(d3.interpolate(a[i], b[i]));
+ for (; i < na; ++i) c[i] = a[i];
+ for (; i < nb; ++i) c[i] = b[i];
+ return function(t) {
+ for (i = 0; i < n0; ++i) c[i] = x[i](t);
+ return c;
+ };
+ };
+ d3.interpolateObject = function(a, b) {
+ var i = {}, c = {}, k;
+ for (k in a) {
+ if (k in b) {
+ i[k] = d3_interpolateByName(k)(a[k], b[k]);
+ } else {
+ c[k] = a[k];
+ }
+ }
+ for (k in b) {
+ if (!(k in a)) {
+ c[k] = b[k];
+ }
+ }
+ return function(t) {
+ for (k in i) c[k] = i[k](t);
+ return c;
+ };
+ };
+ var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g;
+ d3.interpolators = [ d3.interpolateObject, function(a, b) {
+ return b instanceof Array && d3.interpolateArray(a, b);
+ }, function(a, b) {
+ return (typeof a === "string" || typeof b === "string") && d3.interpolateString(a + "", b + "");
+ }, function(a, b) {
+ return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Color) && d3.interpolateRgb(a, b);
+ }, function(a, b) {
+ return !isNaN(a = +a) && !isNaN(b = +b) && d3.interpolateNumber(a, b);
+ } ];
+ d3_Color.prototype.toString = function() {
+ return this.rgb() + "";
+ };
+ d3.rgb = function(r, g, b) {
+ return arguments.length === 1 ? r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : d3_rgb(~~r, ~~g, ~~b);
+ };
+ var d3_rgbPrototype = d3_Rgb.prototype = new d3_Color;
+ d3_rgbPrototype.brighter = function(k) {
+ k = Math.pow(.7, arguments.length ? k : 1);
+ var r = this.r, g = this.g, b = this.b, i = 30;
+ if (!r && !g && !b) return d3_rgb(i, i, i);
+ if (r && r < i) r = i;
+ if (g && g < i) g = i;
+ if (b && b < i) b = i;
+ return d3_rgb(Math.min(255, Math.floor(r / k)), Math.min(255, Math.floor(g / k)), Math.min(255, Math.floor(b / k)));
+ };
+ d3_rgbPrototype.darker = function(k) {
+ k = Math.pow(.7, arguments.length ? k : 1);
+ return d3_rgb(Math.floor(k * this.r), Math.floor(k * this.g), Math.floor(k * this.b));
+ };
+ d3_rgbPrototype.hsl = function() {
+ return d3_rgb_hsl(this.r, this.g, this.b);
+ };
+ d3_rgbPrototype.toString = function() {
+ return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
+ };
+ var d3_rgb_names = d3.map({
+ aliceblue: "#f0f8ff",
+ antiquewhite: "#faebd7",
+ aqua: "#00ffff",
+ aquamarine: "#7fffd4",
+ azure: "#f0ffff",
+ beige: "#f5f5dc",
+ bisque: "#ffe4c4",
+ black: "#000000",
+ blanchedalmond: "#ffebcd",
+ blue: "#0000ff",
+ blueviolet: "#8a2be2",
+ brown: "#a52a2a",
+ burlywood: "#deb887",
+ cadetblue: "#5f9ea0",
+ chartreuse: "#7fff00",
+ chocolate: "#d2691e",
+ coral: "#ff7f50",
+ cornflowerblue: "#6495ed",
+ cornsilk: "#fff8dc",
+ crimson: "#dc143c",
+ cyan: "#00ffff",
+ darkblue: "#00008b",
+ darkcyan: "#008b8b",
+ darkgoldenrod: "#b8860b",
+ darkgray: "#a9a9a9",
+ darkgreen: "#006400",
+ darkgrey: "#a9a9a9",
+ darkkhaki: "#bdb76b",
+ darkmagenta: "#8b008b",
+ darkolivegreen: "#556b2f",
+ darkorange: "#ff8c00",
+ darkorchid: "#9932cc",
+ darkred: "#8b0000",
+ darksalmon: "#e9967a",
+ darkseagreen: "#8fbc8f",
+ darkslateblue: "#483d8b",
+ darkslategray: "#2f4f4f",
+ darkslategrey: "#2f4f4f",
+ darkturquoise: "#00ced1",
+ darkviolet: "#9400d3",
+ deeppink: "#ff1493",
+ deepskyblue: "#00bfff",
+ dimgray: "#696969",
+ dimgrey: "#696969",
+ dodgerblue: "#1e90ff",
+ firebrick: "#b22222",
+ floralwhite: "#fffaf0",
+ forestgreen: "#228b22",
+ fuchsia: "#ff00ff",
+ gainsboro: "#dcdcdc",
+ ghostwhite: "#f8f8ff",
+ gold: "#ffd700",
+ goldenrod: "#daa520",
+ gray: "#808080",
+ green: "#008000",
+ greenyellow: "#adff2f",
+ grey: "#808080",
+ honeydew: "#f0fff0",
+ hotpink: "#ff69b4",
+ indianred: "#cd5c5c",
+ indigo: "#4b0082",
+ ivory: "#fffff0",
+ khaki: "#f0e68c",
+ lavender: "#e6e6fa",
+ lavenderblush: "#fff0f5",
+ lawngreen: "#7cfc00",
+ lemonchiffon: "#fffacd",
+ lightblue: "#add8e6",
+ lightcoral: "#f08080",
+ lightcyan: "#e0ffff",
+ lightgoldenrodyellow: "#fafad2",
+ lightgray: "#d3d3d3",
+ lightgreen: "#90ee90",
+ lightgrey: "#d3d3d3",
+ lightpink: "#ffb6c1",
+ lightsalmon: "#ffa07a",
+ lightseagreen: "#20b2aa",
+ lightskyblue: "#87cefa",
+ lightslategray: "#778899",
+ lightslategrey: "#778899",
+ lightsteelblue: "#b0c4de",
+ lightyellow: "#ffffe0",
+ lime: "#00ff00",
+ limegreen: "#32cd32",
+ linen: "#faf0e6",
+ magenta: "#ff00ff",
+ maroon: "#800000",
+ mediumaquamarine: "#66cdaa",
+ mediumblue: "#0000cd",
+ mediumorchid: "#ba55d3",
+ mediumpurple: "#9370db",
+ mediumseagreen: "#3cb371",
+ mediumslateblue: "#7b68ee",
+ mediumspringgreen: "#00fa9a",
+ mediumturquoise: "#48d1cc",
+ mediumvioletred: "#c71585",
+ midnightblue: "#191970",
+ mintcream: "#f5fffa",
+ mistyrose: "#ffe4e1",
+ moccasin: "#ffe4b5",
+ navajowhite: "#ffdead",
+ navy: "#000080",
+ oldlace: "#fdf5e6",
+ olive: "#808000",
+ olivedrab: "#6b8e23",
+ orange: "#ffa500",
+ orangered: "#ff4500",
+ orchid: "#da70d6",
+ palegoldenrod: "#eee8aa",
+ palegreen: "#98fb98",
+ paleturquoise: "#afeeee",
+ palevioletred: "#db7093",
+ papayawhip: "#ffefd5",
+ peachpuff: "#ffdab9",
+ peru: "#cd853f",
+ pink: "#ffc0cb",
+ plum: "#dda0dd",
+ powderblue: "#b0e0e6",
+ purple: "#800080",
+ red: "#ff0000",
+ rosybrown: "#bc8f8f",
+ royalblue: "#4169e1",
+ saddlebrown: "#8b4513",
+ salmon: "#fa8072",
+ sandybrown: "#f4a460",
+ seagreen: "#2e8b57",
+ seashell: "#fff5ee",
+ sienna: "#a0522d",
+ silver: "#c0c0c0",
+ skyblue: "#87ceeb",
+ slateblue: "#6a5acd",
+ slategray: "#708090",
+ slategrey: "#708090",
+ snow: "#fffafa",
+ springgreen: "#00ff7f",
+ steelblue: "#4682b4",
+ tan: "#d2b48c",
+ teal: "#008080",
+ thistle: "#d8bfd8",
+ tomato: "#ff6347",
+ turquoise: "#40e0d0",
+ violet: "#ee82ee",
+ wheat: "#f5deb3",
+ white: "#ffffff",
+ whitesmoke: "#f5f5f5",
+ yellow: "#ffff00",
+ yellowgreen: "#9acd32"
+ });
+ d3_rgb_names.forEach(function(key, value) {
+ d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb));
+ });
+ d3.hsl = function(h, s, l) {
+ return arguments.length === 1 ? h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : d3_hsl(+h, +s, +l);
+ };
+ var d3_hslPrototype = d3_Hsl.prototype = new d3_Color;
+ d3_hslPrototype.brighter = function(k) {
+ k = Math.pow(.7, arguments.length ? k : 1);
+ return d3_hsl(this.h, this.s, this.l / k);
+ };
+ d3_hslPrototype.darker = function(k) {
+ k = Math.pow(.7, arguments.length ? k : 1);
+ return d3_hsl(this.h, this.s, k * this.l);
+ };
+ d3_hslPrototype.rgb = function() {
+ return d3_hsl_rgb(this.h, this.s, this.l);
+ };
+ d3.hcl = function(h, c, l) {
+ return arguments.length === 1 ? h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l) : h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : d3_hcl(+h, +c, +l);
+ };
+ var d3_hclPrototype = d3_Hcl.prototype = new d3_Color;
+ d3_hclPrototype.brighter = function(k) {
+ return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
+ };
+ d3_hclPrototype.darker = function(k) {
+ return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
+ };
+ d3_hclPrototype.rgb = function() {
+ return d3_hcl_lab(this.h, this.c, this.l).rgb();
+ };
+ d3.lab = function(l, a, b) {
+ return arguments.length === 1 ? l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b) : l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h) : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b) : d3_lab(+l, +a, +b);
+ };
+ var d3_lab_K = 18;
+ var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883;
+ var d3_labPrototype = d3_Lab.prototype = new d3_Color;
+ d3_labPrototype.brighter = function(k) {
+ return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
+ };
+ d3_labPrototype.darker = function(k) {
+ return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
+ };
+ d3_labPrototype.rgb = function() {
+ return d3_lab_rgb(this.l, this.a, this.b);
+ };
+ var d3_select = function(s, n) {
+ return n.querySelector(s);
+ }, d3_selectAll = function(s, n) {
+ return n.querySelectorAll(s);
+ }, d3_selectRoot = document.documentElement, d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, d3_selectMatches = function(n, s) {
+ return d3_selectMatcher.call(n, s);
+ };
+ if (typeof Sizzle === "function") {
+ d3_select = function(s, n) {
+ return Sizzle(s, n)[0] || null;
+ };
+ d3_selectAll = function(s, n) {
+ return Sizzle.uniqueSort(Sizzle(s, n));
+ };
+ d3_selectMatches = Sizzle.matchesSelector;
+ }
+ var d3_selectionPrototype = [];
+ d3.selection = function() {
+ return d3_selectionRoot;
+ };
+ d3.selection.prototype = d3_selectionPrototype;
+ d3_selectionPrototype.select = function(selector) {
+ var subgroups = [], subgroup, subnode, group, node;
+ if (typeof selector !== "function") selector = d3_selection_selector(selector);
+ for (var j = -1, m = this.length; ++j < m; ) {
+ subgroups.push(subgroup = []);
+ subgroup.parentNode = (group = this[j]).parentNode;
+ for (var i = -1, n = group.length; ++i < n; ) {
+ if (node = group[i]) {
+ subgroup.push(subnode = selector.call(node, node.__data__, i));
+ if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
+ } else {
+ subgroup.push(null);
+ }
+ }
+ }
+ return d3_selection(subgroups);
+ };
+ d3_selectionPrototype.selectAll = function(selector) {
+ var subgroups = [], subgroup, node;
+ if (typeof selector !== "function") selector = d3_selection_selectorAll(selector);
+ for (var j = -1, m = this.length; ++j < m; ) {
+ for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+ if (node = group[i]) {
+ subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i)));
+ subgroup.parentNode = node;
+ }
+ }
+ }
+ return d3_selection(subgroups);
+ };
+ d3_selectionPrototype.attr = function(name, value) {
+ if (arguments.length < 2) {
+ if (typeof name === "string") {
+ var node = this.node();
+ name = d3.ns.qualify(name);
+ return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
+ }
+ for (value in name) this.each(d3_selection_attr(value, name[value]));
+ return this;
+ }
+ return this.each(d3_selection_attr(name, value));
+ };
+ d3_selectionPrototype.classed = function(name, value) {
+ if (arguments.length < 2) {
+ if (typeof name === "string") {
+ var node = this.node(), n = (name = name.trim().split(/^|\s+/g)).length, i = -1;
+ if (value = node.classList) {
+ while (++i < n) if (!value.contains(name[i])) return false;
+ } else {
+ value = node.className;
+ if (value.baseVal != null) value = value.baseVal;
+ while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
+ }
+ return true;
+ }
+ for (value in name) this.each(d3_selection_classed(value, name[value]));
+ return this;
+ }
+ return this.each(d3_selection_classed(name, value));
+ };
+ d3_selectionPrototype.style = function(name, value, priority) {
+ var n = arguments.length;
+ if (n < 3) {
+ if (typeof name !== "string") {
+ if (n < 2) value = "";
+ for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
+ return this;
+ }
+ if (n < 2) return window.getComputedStyle(this.node(), null).getPropertyValue(name);
+ priority = "";
+ }
+ return this.each(d3_selection_style(name, value, priority));
+ };
+ d3_selectionPrototype.property = function(name, value) {
+ if (arguments.length < 2) {
+ if (typeof name === "string") return this.node()[name];
+ for (value in name) this.each(d3_selection_property(value, name[value]));
+ return this;
+ }
+ return this.each(d3_selection_property(name, value));
+ };
+ d3_selectionPrototype.text = function(value) {
+ return arguments.length < 1 ? this.node().textContent : this.each(typeof value === "function" ? function() {
+ var v = value.apply(this, arguments);
+ this.textContent = v == null ? "" : v;
+ } : value == null ? function() {
+ this.textContent = "";
+ } : function() {
+ this.textContent = value;
+ });
+ };
+ d3_selectionPrototype.html = function(value) {
+ return arguments.length < 1 ? this.node().innerHTML : this.each(typeof value === "function" ? function() {
+ var v = value.apply(this, arguments);
+ this.innerHTML = v == null ? "" : v;
+ } : value == null ? function() {
+ this.innerHTML = "";
+ } : function() {
+ this.innerHTML = value;
+ });
+ };
+ d3_selectionPrototype.append = function(name) {
+ function append() {
+ return this.appendChild(document.createElementNS(this.namespaceURI, name));
+ }
+ function appendNS() {
+ return this.appendChild(document.createElementNS(name.space, name.local));
+ }
+ name = d3.ns.qualify(name);
+ return this.select(name.local ? appendNS : append);
+ };
+ d3_selectionPrototype.insert = function(name, before) {
+ function insert() {
+ return this.insertBefore(document.createElementNS(this.namespaceURI, name), d3_select(before, this));
+ }
+ function insertNS() {
+ return this.insertBefore(document.createElementNS(name.space, name.local), d3_select(before, this));
+ }
+ name = d3.ns.qualify(name);
+ return this.select(name.local ? insertNS : insert);
+ };
+ d3_selectionPrototype.remove = function() {
+ return this.each(function() {
+ var parent = this.parentNode;
+ if (parent) parent.removeChild(this);
+ });
+ };
+ d3_selectionPrototype.data = function(value, key) {
+ function bind(group, groupData) {
+ var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), n1 = Math.max(n, m), updateNodes = [], enterNodes = [], exitNodes = [], node, nodeData;
+ if (key) {
+ var nodeByKeyValue = new d3_Map, keyValues = [], keyValue, j = groupData.length;
+ for (i = -1; ++i < n; ) {
+ keyValue = key.call(node = group[i], node.__data__, i);
+ if (nodeByKeyValue.has(keyValue)) {
+ exitNodes[j++] = node;
+ } else {
+ nodeByKeyValue.set(keyValue, node);
+ }
+ keyValues.push(keyValue);
+ }
+ for (i = -1; ++i < m; ) {
+ keyValue = key.call(groupData, nodeData = groupData[i], i);
+ if (nodeByKeyValue.has(keyValue)) {
+ updateNodes[i] = node = nodeByKeyValue.get(keyValue);
+ node.__data__ = nodeData;
+ enterNodes[i] = exitNodes[i] = null;
+ } else {
+ enterNodes[i] = d3_selection_dataNode(nodeData);
+ updateNodes[i] = exitNodes[i] = null;
+ }
+ nodeByKeyValue.remove(keyValue);
+ }
+ for (i = -1; ++i < n; ) {
+ if (nodeByKeyValue.has(keyValues[i])) {
+ exitNodes[i] = group[i];
+ }
+ }
+ } else {
+ for (i = -1; ++i < n0; ) {
+ node = group[i];
+ nodeData = groupData[i];
+ if (node) {
+ node.__data__ = nodeData;
+ updateNodes[i] = node;
+ enterNodes[i] = exitNodes[i] = null;
+ } else {
+ enterNodes[i] = d3_selection_dataNode(nodeData);
+ updateNodes[i] = exitNodes[i] = null;
+ }
+ }
+ for (; i < m; ++i) {
+ enterNodes[i] = d3_selection_dataNode(groupData[i]);
+ updateNodes[i] = exitNodes[i] = null;
+ }
+ for (; i < n1; ++i) {
+ exitNodes[i] = group[i];
+ enterNodes[i] = updateNodes[i] = null;
+ }
+ }
+ enterNodes.update = updateNodes;
+ enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
+ enter.push(enterNodes);
+ update.push(updateNodes);
+ exit.push(exitNodes);
+ }
+ var i = -1, n = this.length, group, node;
+ if (!arguments.length) {
+ value = new Array(n = (group = this[0]).length);
+ while (++i < n) {
+ if (node = group[i]) {
+ value[i] = node.__data__;
+ }
+ }
+ return value;
+ }
+ var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]);
+ if (typeof value === "function") {
+ while (++i < n) {
+ bind(group = this[i], value.call(group, group.parentNode.__data__, i));
+ }
+ } else {
+ while (++i < n) {
+ bind(group = this[i], value);
+ }
+ }
+ update.enter = function() {
+ return enter;
+ };
+ update.exit = function() {
+ return exit;
+ };
+ return update;
+ };
+ d3_selectionPrototype.datum = d3_selectionPrototype.map = function(value) {
+ return arguments.length < 1 ? this.property("__data__") : this.property("__data__", value);
+ };
+ d3_selectionPrototype.filter = function(filter) {
+ var subgroups = [], subgroup, group, node;
+ if (typeof filter !== "function") filter = d3_selection_filter(filter);
+ for (var j = 0, m = this.length; j < m; j++) {
+ subgroups.push(subgroup = []);
+ subgroup.parentNode = (group = this[j]).parentNode;
+ for (var i = 0, n = group.length; i < n; i++) {
+ if ((node = group[i]) && filter.call(node, node.__data__, i)) {
+ subgroup.push(node);
+ }
+ }
+ }
+ return d3_selection(subgroups);
+ };
+ d3_selectionPrototype.order = function() {
+ for (var j = -1, m = this.length; ++j < m; ) {
+ for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) {
+ if (node = group[i]) {
+ if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
+ next = node;
+ }
+ }
+ }
+ return this;
+ };
+ d3_selectionPrototype.sort = function(comparator) {
+ comparator = d3_selection_sortComparator.apply(this, arguments);
+ for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator);
+ return this.order();
+ };
+ d3_selectionPrototype.on = function(type, listener, capture) {
+ var n = arguments.length;
+ if (n < 3) {
+ if (typeof type !== "string") {
+ if (n < 2) listener = false;
+ for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
+ return this;
+ }
+ if (n < 2) return (n = this.node()["__on" + type]) && n._;
+ capture = false;
+ }
+ return this.each(d3_selection_on(type, listener, capture));
+ };
+ d3_selectionPrototype.each = function(callback) {
+ return d3_selection_each(this, function(node, i, j) {
+ callback.call(node, node.__data__, i, j);
+ });
+ };
+ d3_selectionPrototype.call = function(callback) {
+ callback.apply(this, (arguments[0] = this, arguments));
+ return this;
+ };
+ d3_selectionPrototype.empty = function() {
+ return !this.node();
+ };
+ d3_selectionPrototype.node = function(callback) {
+ for (var j = 0, m = this.length; j < m; j++) {
+ for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+ var node = group[i];
+ if (node) return node;
+ }
+ }
+ return null;
+ };
+ d3_selectionPrototype.transition = function() {
+ var subgroups = [], subgroup, node;
+ for (var j = -1, m = this.length; ++j < m; ) {
+ subgroups.push(subgroup = []);
+ for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+ subgroup.push((node = group[i]) ? {
+ node: node,
+ delay: d3_transitionDelay,
+ duration: d3_transitionDuration
+ } : null);
+ }
+ }
+ return d3_transition(subgroups, d3_transitionId || ++d3_transitionNextId, Date.now());
+ };
+ var d3_selectionRoot = d3_selection([ [ document ] ]);
+ d3_selectionRoot[0].parentNode = d3_selectRoot;
+ d3.select = function(selector) {
+ return typeof selector === "string" ? d3_selectionRoot.select(selector) : d3_selection([ [ selector ] ]);
+ };
+ d3.selectAll = function(selector) {
+ return typeof selector === "string" ? d3_selectionRoot.selectAll(selector) : d3_selection([ d3_array(selector) ]);
+ };
+ var d3_selection_enterPrototype = [];
+ d3.selection.enter = d3_selection_enter;
+ d3.selection.enter.prototype = d3_selection_enterPrototype;
+ d3_selection_enterPrototype.append = d3_selectionPrototype.append;
+ d3_selection_enterPrototype.insert = d3_selectionPrototype.insert;
+ d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
+ d3_selection_enterPrototype.node = d3_selectionPrototype.node;
+ d3_selection_enterPrototype.select = function(selector) {
+ var subgroups = [], subgroup, subnode, upgroup, group, node;
+ for (var j = -1, m = this.length; ++j < m; ) {
+ upgroup = (group = this[j]).update;
+ subgroups.push(subgroup = []);
+ subgroup.parentNode = group.parentNode;
+ for (var i = -1, n = group.length; ++i < n; ) {
+ if (node = group[i]) {
+ subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i));
+ subnode.__data__ = node.__data__;
+ } else {
+ subgroup.push(null);
+ }
+ }
+ }
+ return d3_selection(subgroups);
+ };
+ var d3_transitionPrototype = [], d3_transitionNextId = 0, d3_transitionId = 0, d3_transitionDefaultDelay = 0, d3_transitionDefaultDuration = 250, d3_transitionDefaultEase = d3.ease("cubic-in-out"), d3_transitionDelay = d3_transitionDefaultDelay, d3_transitionDuration = d3_transitionDefaultDuration, d3_transitionEase = d3_transitionDefaultEase;
+ d3_transitionPrototype.call = d3_selectionPrototype.call;
+ d3.transition = function(selection) {
+ return arguments.length ? d3_transitionId ? selection.transition() : selection : d3_selectionRoot.transition();
+ };
+ d3.transition.prototype = d3_transitionPrototype;
+ d3_transitionPrototype.select = function(selector) {
+ var subgroups = [], subgroup, subnode, node;
+ if (typeof selector !== "function") selector = d3_selection_selector(selector);
+ for (var j = -1, m = this.length; ++j < m; ) {
+ subgroups.push(subgroup = []);
+ for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+ if ((node = group[i]) && (subnode = selector.call(node.node, node.node.__data__, i))) {
+ if ("__data__" in node.node) subnode.__data__ = node.node.__data__;
+ subgroup.push({
+ node: subnode,
+ delay: node.delay,
+ duration: node.duration
+ });
+ } else {
+ subgroup.push(null);
+ }
+ }
+ }
+ return d3_transition(subgroups, this.id, this.time).ease(this.ease());
+ };
+ d3_transitionPrototype.selectAll = function(selector) {
+ var subgroups = [], subgroup, subnodes, node;
+ if (typeof selector !== "function") selector = d3_selection_selectorAll(selector);
+ for (var j = -1, m = this.length; ++j < m; ) {
+ for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+ if (node = group[i]) {
+ subnodes = selector.call(node.node, node.node.__data__, i);
+ subgroups.push(subgroup = []);
+ for (var k = -1, o = subnodes.length; ++k < o; ) {
+ subgroup.push({
+ node: subnodes[k],
+ delay: node.delay,
+ duration: node.duration
+ });
+ }
+ }
+ }
+ }
+ return d3_transition(subgroups, this.id, this.time).ease(this.ease());
+ };
+ d3_transitionPrototype.filter = function(filter) {
+ var subgroups = [], subgroup, group, node;
+ if (typeof filter !== "function") filter = d3_selection_filter(filter);
+ for (var j = 0, m = this.length; j < m; j++) {
+ subgroups.push(subgroup = []);
+ for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+ if ((node = group[i]) && filter.call(node.node, node.node.__data__, i)) {
+ subgroup.push(node);
+ }
+ }
+ }
+ return d3_transition(subgroups, this.id, this.time).ease(this.ease());
+ };
+ d3_transitionPrototype.attr = function(name, value) {
+ if (arguments.length < 2) {
+ for (value in name) this.attrTween(value, d3_tweenByName(name[value], value));
+ return this;
+ }
+ return this.attrTween(name, d3_tweenByName(value, name));
+ };
+ d3_transitionPrototype.attrTween = function(nameNS, tween) {
+ function attrTween(d, i) {
+ var f = tween.call(this, d, i, this.getAttribute(name));
+ return f === d3_tweenRemove ? (this.removeAttribute(name), null) : f && function(t) {
+ this.setAttribute(name, f(t));
+ };
+ }
+ function attrTweenNS(d, i) {
+ var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
+ return f === d3_tweenRemove ? (this.removeAttributeNS(name.space, name.local), null) : f && function(t) {
+ this.setAttributeNS(name.space, name.local, f(t));
+ };
+ }
+ var name = d3.ns.qualify(nameNS);
+ return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
+ };
+ d3_transitionPrototype.style = function(name, value, priority) {
+ var n = arguments.length;
+ if (n < 3) {
+ if (typeof name !== "string") {
+ if (n < 2) value = "";
+ for (priority in name) this.styleTween(priority, d3_tweenByName(name[priority], priority), value);
+ return this;
+ }
+ priority = "";
+ }
+ return this.styleTween(name, d3_tweenByName(value, name), priority);
+ };
+ d3_transitionPrototype.styleTween = function(name, tween, priority) {
+ if (arguments.length < 3) priority = "";
+ return this.tween("style." + name, function(d, i) {
+ var f = tween.call(this, d, i, window.getComputedStyle(this, null).getPropertyValue(name));
+ return f === d3_tweenRemove ? (this.style.removeProperty(name), null) : f && function(t) {
+ this.style.setProperty(name, f(t), priority);
+ };
+ });
+ };
+ d3_transitionPrototype.text = function(value) {
+ return this.tween("text", function(d, i) {
+ this.textContent = typeof value === "function" ? value.call(this, d, i) : value;
+ });
+ };
+ d3_transitionPrototype.remove = function() {
+ return this.each("end.transition", function() {
+ var p;
+ if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this);
+ });
+ };
+ d3_transitionPrototype.delay = function(value) {
+ return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
+ node.delay = value.call(node = node.node, node.__data__, i, j) | 0;
+ } : (value = value | 0, function(node) {
+ node.delay = value;
+ }));
+ };
+ d3_transitionPrototype.duration = function(value) {
+ return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
+ node.duration = Math.max(1, value.call(node = node.node, node.__data__, i, j) | 0);
+ } : (value = Math.max(1, value | 0), function(node) {
+ node.duration = value;
+ }));
+ };
+ d3_transitionPrototype.transition = function() {
+ return this.select(d3_this);
+ };
+ d3.tween = function(b, interpolate) {
+ function tweenFunction(d, i, a) {
+ var v = b.call(this, d, i);
+ return v == null ? a != "" && d3_tweenRemove : a != v && interpolate(a, v + "");
+ }
+ function tweenString(d, i, a) {
+ return a != b && interpolate(a, b);
+ }
+ return typeof b === "function" ? tweenFunction : b == null ? d3_tweenNull : (b += "", tweenString);
+ };
+ var d3_tweenRemove = {};
+ var d3_timer_id = 0, d3_timer_byId = {}, d3_timer_queue = null, d3_timer_interval, d3_timer_timeout;
+ d3.timer = function(callback, delay, then) {
+ if (arguments.length < 3) {
+ if (arguments.length < 2) delay = 0; else if (!isFinite(delay)) return;
+ then = Date.now();
+ }
+ var timer = d3_timer_byId[callback.id];
+ if (timer && timer.callback === callback) {
+ timer.then = then;
+ timer.delay = delay;
+ } else d3_timer_byId[callback.id = ++d3_timer_id] = d3_timer_queue = {
+ callback: callback,
+ then: then,
+ delay: delay,
+ next: d3_timer_queue
+ };
+ if (!d3_timer_interval) {
+ d3_timer_timeout = clearTimeout(d3_timer_timeout);
+ d3_timer_interval = 1;
+ d3_timer_frame(d3_timer_step);
+ }
+ };
+ d3.timer.flush = function() {
+ var elapsed, now = Date.now(), t1 = d3_timer_queue;
+ while (t1) {
+ elapsed = now - t1.then;
+ if (!t1.delay) t1.flush = t1.callback(elapsed);
+ t1 = t1.next;
+ }
+ d3_timer_flush();
+ };
+ var d3_timer_frame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
+ setTimeout(callback, 17);
+ };
+ d3.mouse = function(container) {
+ return d3_mousePoint(container, d3_eventSource());
+ };
+ var d3_mouse_bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0;
+ d3.touches = function(container, touches) {
+ if (arguments.length < 2) touches = d3_eventSource().touches;
+ return touches ? d3_array(touches).map(function(touch) {
+ var point = d3_mousePoint(container, touch);
+ point.identifier = touch.identifier;
+ return point;
+ }) : [];
+ };
+ d3.scale = {};
+ d3.scale.linear = function() {
+ return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3.interpolate, false);
+ };
+ d3.scale.log = function() {
+ return d3_scale_log(d3.scale.linear(), d3_scale_logp);
+ };
+ var d3_scale_logFormat = d3.format(".0e");
+ d3_scale_logp.pow = function(x) {
+ return Math.pow(10, x);
+ };
+ d3_scale_logn.pow = function(x) {
+ return -Math.pow(10, -x);
+ };
+ d3.scale.pow = function() {
+ return d3_scale_pow(d3.scale.linear(), 1);
+ };
+ d3.scale.sqrt = function() {
+ return d3.scale.pow().exponent(.5);
+ };
+ d3.scale.ordinal = function() {
+ return d3_scale_ordinal([], {
+ t: "range",
+ a: [ [] ]
+ });
+ };
+ d3.scale.category10 = function() {
+ return d3.scale.ordinal().range(d3_category10);
+ };
+ d3.scale.category20 = function() {
+ return d3.scale.ordinal().range(d3_category20);
+ };
+ d3.scale.category20b = function() {
+ return d3.scale.ordinal().range(d3_category20b);
+ };
+ d3.scale.category20c = function() {
+ return d3.scale.ordinal().range(d3_category20c);
+ };
+ var d3_category10 = [ "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf" ];
+ var d3_category20 = [ "#1f77b4", "#aec7e8", "#ff7f0e", "#ffbb78", "#2ca02c", "#98df8a", "#d62728", "#ff9896", "#9467bd", "#c5b0d5", "#8c564b", "#c49c94", "#e377c2", "#f7b6d2", "#7f7f7f", "#c7c7c7", "#bcbd22", "#dbdb8d", "#17becf", "#9edae5" ];
+ var d3_category20b = [ "#393b79", "#5254a3", "#6b6ecf", "#9c9ede", "#637939", "#8ca252", "#b5cf6b", "#cedb9c", "#8c6d31", "#bd9e39", "#e7ba52", "#e7cb94", "#843c39", "#ad494a", "#d6616b", "#e7969c", "#7b4173", "#a55194", "#ce6dbd", "#de9ed6" ];
+ var d3_category20c = [ "#3182bd", "#6baed6", "#9ecae1", "#c6dbef", "#e6550d", "#fd8d3c", "#fdae6b", "#fdd0a2", "#31a354", "#74c476", "#a1d99b", "#c7e9c0", "#756bb1", "#9e9ac8", "#bcbddc", "#dadaeb", "#636363", "#969696", "#bdbdbd", "#d9d9d9" ];
+ d3.scale.quantile = function() {
+ return d3_scale_quantile([], []);
+ };
+ d3.scale.quantize = function() {
+ return d3_scale_quantize(0, 1, [ 0, 1 ]);
+ };
+ d3.scale.threshold = function() {
+ return d3_scale_threshold([ .5 ], [ 0, 1 ]);
+ };
+ d3.scale.identity = function() {
+ return d3_scale_identity([ 0, 1 ]);
+ };
+ d3.svg = {};
+ d3.svg.arc = function() {
+ function arc() {
+ var r0 = innerRadius.apply(this, arguments), r1 = outerRadius.apply(this, arguments), a0 = startAngle.apply(this, arguments) + d3_svg_arcOffset, a1 = endAngle.apply(this, arguments) + d3_svg_arcOffset, da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0), df = da < Math.PI ? "0" : "1", c0 = Math.cos(a0), s0 = Math.sin(a0), c1 = Math.cos(a1), s1 = Math.sin(a1);
+ return da >= d3_svg_arcMax ? r0 ? "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "M0," + r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + -r0 + "A" + r0 + "," + r0 + " 0 1,0 0," + r0 + "Z" : "M0," + r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + -r1 + "A" + r1 + "," + r1 + " 0 1,1 0," + r1 + "Z" : r0 ? "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L" + r0 * c1 + "," + r0 * s1 + "A" + r0 + "," + r0 + " 0 " + df + ",0 " + r0 * c0 + "," + r0 * s0 + "Z" : "M" + r1 * c0 + "," + r1 * s0 + "A" + r1 + "," + r1 + " 0 " + df + ",1 " + r1 * c1 + "," + r1 * s1 + "L0,0" + "Z";
+ }
+ var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
+ arc.innerRadius = function(v) {
+ if (!arguments.length) return innerRadius;
+ innerRadius = d3_functor(v);
+ return arc;
+ };
+ arc.outerRadius = function(v) {
+ if (!arguments.length) return outerRadius;
+ outerRadius = d3_functor(v);
+ return arc;
+ };
+ arc.startAngle = function(v) {
+ if (!arguments.length) return startAngle;
+ startAngle = d3_functor(v);
+ return arc;
+ };
+ arc.endAngle = function(v) {
+ if (!arguments.length) return endAngle;
+ endAngle = d3_functor(v);
+ return arc;
+ };
+ arc.centroid = function() {
+ var r = (innerRadius.apply(this, arguments) + outerRadius.apply(this, arguments)) / 2, a = (startAngle.apply(this, arguments) + endAngle.apply(this, arguments)) / 2 + d3_svg_arcOffset;
+ return [ Math.cos(a) * r, Math.sin(a) * r ];
+ };
+ return arc;
+ };
+ var d3_svg_arcOffset = -Math.PI / 2, d3_svg_arcMax = 2 * Math.PI - 1e-6;
+ d3.svg.line = function() {
+ return d3_svg_line(d3_identity);
+ };
+ var d3_svg_lineInterpolators = d3.map({
+ linear: d3_svg_lineLinear,
+ "linear-closed": d3_svg_lineLinearClosed,
+ "step-before": d3_svg_lineStepBefore,
+ "step-after": d3_svg_lineStepAfter,
+ basis: d3_svg_lineBasis,
+ "basis-open": d3_svg_lineBasisOpen,
+ "basis-closed": d3_svg_lineBasisClosed,
+ bundle: d3_svg_lineBundle,
+ cardinal: d3_svg_lineCardinal,
+ "cardinal-open": d3_svg_lineCardinalOpen,
+ "cardinal-closed": d3_svg_lineCardinalClosed,
+ monotone: d3_svg_lineMonotone
+ });
+ d3_svg_lineInterpolators.forEach(function(key, value) {
+ value.key = key;
+ value.closed = /-closed$/.test(key);
+ });
+ var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ];
+ d3.svg.line.radial = function() {
+ var line = d3_svg_line(d3_svg_lineRadial);
+ line.radius = line.x, delete line.x;
+ line.angle = line.y, delete line.y;
+ return line;
+ };
+ d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter;
+ d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore;
+ d3.svg.area = function() {
+ return d3_svg_area(d3_identity);
+ };
+ d3.svg.area.radial = function() {
+ var area = d3_svg_area(d3_svg_lineRadial);
+ area.radius = area.x, delete area.x;
+ area.innerRadius = area.x0, delete area.x0;
+ area.outerRadius = area.x1, delete area.x1;
+ area.angle = area.y, delete area.y;
+ area.startAngle = area.y0, delete area.y0;
+ area.endAngle = area.y1, delete area.y1;
+ return area;
+ };
+ d3.svg.chord = function() {
+ function chord(d, i) {
+ var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i);
+ return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
+ }
+ function subgroup(self, f, d, i) {
+ var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) + d3_svg_arcOffset, a1 = endAngle.call(self, subgroup, i) + d3_svg_arcOffset;
+ return {
+ r: r,
+ a0: a0,
+ a1: a1,
+ p0: [ r * Math.cos(a0), r * Math.sin(a0) ],
+ p1: [ r * Math.cos(a1), r * Math.sin(a1) ]
+ };
+ }
+ function equals(a, b) {
+ return a.a0 == b.a0 && a.a1 == b.a1;
+ }
+ function arc(r, p, a) {
+ return "A" + r + "," + r + " 0 " + +(a > Math.PI) + ",1 " + p;
+ }
+ function curve(r0, p0, r1, p1) {
+ return "Q 0,0 " + p1;
+ }
+ var source = d3_svg_chordSource, target = d3_svg_chordTarget, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
+ chord.radius = function(v) {
+ if (!arguments.length) return radius;
+ radius = d3_functor(v);
+ return chord;
+ };
+ chord.source = function(v) {
+ if (!arguments.length) return source;
+ source = d3_functor(v);
+ return chord;
+ };
+ chord.target = function(v) {
+ if (!arguments.length) return target;
+ target = d3_functor(v);
+ return chord;
+ };
+ chord.startAngle = function(v) {
+ if (!arguments.length) return startAngle;
+ startAngle = d3_functor(v);
+ return chord;
+ };
+ chord.endAngle = function(v) {
+ if (!arguments.length) return endAngle;
+ endAngle = d3_functor(v);
+ return chord;
+ };
+ return chord;
+ };
+ d3.svg.diagonal = function() {
+ function diagonal(d, i) {
+ var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, {
+ x: p0.x,
+ y: m
+ }, {
+ x: p3.x,
+ y: m
+ }, p3 ];
+ p = p.map(projection);
+ return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
+ }
+ var source = d3_svg_chordSource, target = d3_svg_chordTarget, projection = d3_svg_diagonalProjection;
+ diagonal.source = function(x) {
+ if (!arguments.length) return source;
+ source = d3_functor(x);
+ return diagonal;
+ };
+ diagonal.target = function(x) {
+ if (!arguments.length) return target;
+ target = d3_functor(x);
+ return diagonal;
+ };
+ diagonal.projection = function(x) {
+ if (!arguments.length) return projection;
+ projection = x;
+ return diagonal;
+ };
+ return diagonal;
+ };
+ d3.svg.diagonal.radial = function() {
+ var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection;
+ diagonal.projection = function(x) {
+ return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection;
+ };
+ return diagonal;
+ };
+ d3.svg.mouse = d3.mouse;
+ d3.svg.touches = d3.touches;
+ d3.svg.symbol = function() {
+ function symbol(d, i) {
+ return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i));
+ }
+ var type = d3_svg_symbolType, size = d3_svg_symbolSize;
+ symbol.type = function(x) {
+ if (!arguments.length) return type;
+ type = d3_functor(x);
+ return symbol;
+ };
+ symbol.size = function(x) {
+ if (!arguments.length) return size;
+ size = d3_functor(x);
+ return symbol;
+ };
+ return symbol;
+ };
+ var d3_svg_symbols = d3.map({
+ circle: d3_svg_symbolCircle,
+ cross: function(size) {
+ var r = Math.sqrt(size / 5) / 2;
+ return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z";
+ },
+ diamond: function(size) {
+ var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30;
+ return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z";
+ },
+ square: function(size) {
+ var r = Math.sqrt(size) / 2;
+ return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z";
+ },
+ "triangle-down": function(size) {
+ var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
+ return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z";
+ },
+ "triangle-up": function(size) {
+ var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
+ return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z";
+ }
+ });
+ d3.svg.symbolTypes = d3_svg_symbols.keys();
+ var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180);
+ d3.svg.axis = function() {
+ function axis(g) {
+ g.each(function() {
+ var g = d3.select(this);
+ var ticks = tickValues == null ? scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain() : tickValues, tickFormat = tickFormat_ == null ? scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String : tickFormat_;
+ var subticks = d3_svg_axisSubdivide(scale, ticks, tickSubdivide), subtick = g.selectAll(".minor").data(subticks, String), subtickEnter = subtick.enter().insert("line", "g").attr("class", "tick minor").style("opacity", 1e-6), subtickExit = d3.transition(subtick.exit()).style("opacity", 1e-6).remove(), subtickUpdate = d3.transition(subtick).style("opacity", 1);
+ var tick = g.selectAll("g").data(ticks, String), tickEnter = tick.enter().insert("g", "path").style("opacity", 1e-6), tickExit = d3.transition(tick.exit()).style("opacity", 1e-6).remove(), tickUpdate = d3.transition(tick).style("opacity", 1), tickTransform;
+ var range = d3_scaleRange(scale), path = g.selectAll(".domain").data([ 0 ]), pathEnter = path.enter().append("path").attr("class", "domain"), pathUpdate = d3.transition(path);
+ var scale1 = scale.copy(), scale0 = this.__chart__ || scale1;
+ this.__chart__ = scale1;
+ tickEnter.append("line").attr("class", "tick");
+ tickEnter.append("text");
+ var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text");
+ switch (orient) {
+ case "bottom":
+ {
+ tickTransform = d3_svg_axisX;
+ subtickEnter.attr("y2", tickMinorSize);
+ subtickUpdate.attr("x2", 0).attr("y2", tickMinorSize);
+ lineEnter.attr("y2", tickMajorSize);
+ textEnter.attr("y", Math.max(tickMajorSize, 0) + tickPadding);
+ lineUpdate.attr("x2", 0).attr("y2", tickMajorSize);
+ textUpdate.attr("x", 0).attr("y", Math.max(tickMajorSize, 0) + tickPadding);
+ text.attr("dy", ".71em").attr("text-anchor", "middle");
+ pathUpdate.attr("d", "M" + range[0] + "," + tickEndSize + "V0H" + range[1] + "V" + tickEndSize);
+ break;
+ }
+ case "top":
+ {
+ tickTransform = d3_svg_axisX;
+ subtickEnter.attr("y2", -tickMinorSize);
+ subtickUpdate.attr("x2", 0).attr("y2", -tickMinorSize);
+ lineEnter.attr("y2", -tickMajorSize);
+ textEnter.attr("y", -(Math.max(tickMajorSize, 0) + tickPadding));
+ lineUpdate.attr("x2", 0).attr("y2", -tickMajorSize);
+ textUpdate.attr("x", 0).attr("y", -(Math.max(tickMajorSize, 0) + tickPadding));
+ text.attr("dy", "0em").attr("text-anchor", "middle");
+ pathUpdate.attr("d", "M" + range[0] + "," + -tickEndSize + "V0H" + range[1] + "V" + -tickEndSize);
+ break;
+ }
+ case "left":
+ {
+ tickTransform = d3_svg_axisY;
+ subtickEnter.attr("x2", -tickMinorSize);
+ subtickUpdate.attr("x2", -tickMinorSize).attr("y2", 0);
+ lineEnter.attr("x2", -tickMajorSize);
+ textEnter.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding));
+ lineUpdate.attr("x2", -tickMajorSize).attr("y2", 0);
+ textUpdate.attr("x", -(Math.max(tickMajorSize, 0) + tickPadding)).attr("y", 0);
+ text.attr("dy", ".32em").attr("text-anchor", "end");
+ pathUpdate.attr("d", "M" + -tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + -tickEndSize);
+ break;
+ }
+ case "right":
+ {
+ tickTransform = d3_svg_axisY;
+ subtickEnter.attr("x2", tickMinorSize);
+ subtickUpdate.attr("x2", tickMinorSize).attr("y2", 0);
+ lineEnter.attr("x2", tickMajorSize);
+ textEnter.attr("x", Math.max(tickMajorSize, 0) + tickPadding);
+ lineUpdate.attr("x2", tickMajorSize).attr("y2", 0);
+ textUpdate.attr("x", Math.max(tickMajorSize, 0) + tickPadding).attr("y", 0);
+ text.attr("dy", ".32em").attr("text-anchor", "start");
+ pathUpdate.attr("d", "M" + tickEndSize + "," + range[0] + "H0V" + range[1] + "H" + tickEndSize);
+ break;
+ }
+ }
+ if (scale.ticks) {
+ tickEnter.call(tickTransform, scale0);
+ tickUpdate.call(tickTransform, scale1);
+ tickExit.call(tickTransform, scale1);
+ subtickEnter.call(tickTransform, scale0);
+ subtickUpdate.call(tickTransform, scale1);
+ subtickExit.call(tickTransform, scale1);
+ } else {
+ var dx = scale1.rangeBand() / 2, x = function(d) {
+ return scale1(d) + dx;
+ };
+ tickEnter.call(tickTransform, x);
+ tickUpdate.call(tickTransform, x);
+ }
+ });
+ }
+ var scale = d3.scale.linear(), orient = "bottom", tickMajorSize = 6, tickMinorSize = 6, tickEndSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_, tickSubdivide = 0;
+ axis.scale = function(x) {
+ if (!arguments.length) return scale;
+ scale = x;
+ return axis;
+ };
+ axis.orient = function(x) {
+ if (!arguments.length) return orient;
+ orient = x;
+ return axis;
+ };
+ axis.ticks = function() {
+ if (!arguments.length) return tickArguments_;
+ tickArguments_ = arguments;
+ return axis;
+ };
+ axis.tickValues = function(x) {
+ if (!arguments.length) return tickValues;
+ tickValues = x;
+ return axis;
+ };
+ axis.tickFormat = function(x) {
+ if (!arguments.length) return tickFormat_;
+ tickFormat_ = x;
+ return axis;
+ };
+ axis.tickSize = function(x, y, z) {
+ if (!arguments.length) return tickMajorSize;
+ var n = arguments.length - 1;
+ tickMajorSize = +x;
+ tickMinorSize = n > 1 ? +y : tickMajorSize;
+ tickEndSize = n > 0 ? +arguments[n] : tickMajorSize;
+ return axis;
+ };
+ axis.tickPadding = function(x) {
+ if (!arguments.length) return tickPadding;
+ tickPadding = +x;
+ return axis;
+ };
+ axis.tickSubdivide = function(x) {
+ if (!arguments.length) return tickSubdivide;
+ tickSubdivide = +x;
+ return axis;
+ };
+ return axis;
+ };
+ d3.svg.brush = function() {
+ function brush(g) {
+ g.each(function() {
+ var g = d3.select(this), bg = g.selectAll(".background").data([ 0 ]), fg = g.selectAll(".extent").data([ 0 ]), tz = g.selectAll(".resize").data(resizes, String), e;
+ g.style("pointer-events", "all").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart);
+ bg.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair");
+ fg.enter().append("rect").attr("class", "extent").style("cursor", "move");
+ tz.enter().append("g").attr("class", function(d) {
+ return "resize " + d;
+ }).style("cursor", function(d) {
+ return d3_svg_brushCursor[d];
+ }).append("rect").attr("x", function(d) {
+ return /[ew]$/.test(d) ? -3 : null;
+ }).attr("y", function(d) {
+ return /^[ns]/.test(d) ? -3 : null;
+ }).attr("width", 6).attr("height", 6).style("visibility", "hidden");
+ tz.style("display", brush.empty() ? "none" : null);
+ tz.exit().remove();
+ if (x) {
+ e = d3_scaleRange(x);
+ bg.attr("x", e[0]).attr("width", e[1] - e[0]);
+ redrawX(g);
+ }
+ if (y) {
+ e = d3_scaleRange(y);
+ bg.attr("y", e[0]).attr("height", e[1] - e[0]);
+ redrawY(g);
+ }
+ redraw(g);
+ });
+ }
+ function redraw(g) {
+ g.selectAll(".resize").attr("transform", function(d) {
+ return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")";
+ });
+ }
+ function redrawX(g) {
+ g.select(".extent").attr("x", extent[0][0]);
+ g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]);
+ }
+ function redrawY(g) {
+ g.select(".extent").attr("y", extent[0][1]);
+ g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]);
+ }
+ function brushstart() {
+ function mouse() {
+ var touches = d3.event.changedTouches;
+ return touches ? d3.touches(target, touches)[0] : d3.mouse(target);
+ }
+ function keydown() {
+ if (d3.event.keyCode == 32) {
+ if (!dragging) {
+ center = null;
+ origin[0] -= extent[1][0];
+ origin[1] -= extent[1][1];
+ dragging = 2;
+ }
+ d3_eventCancel();
+ }
+ }
+ function keyup() {
+ if (d3.event.keyCode == 32 && dragging == 2) {
+ origin[0] += extent[1][0];
+ origin[1] += extent[1][1];
+ dragging = 0;
+ d3_eventCancel();
+ }
+ }
+ function brushmove() {
+ var point = mouse(), moved = false;
+ if (offset) {
+ point[0] += offset[0];
+ point[1] += offset[1];
+ }
+ if (!dragging) {
+ if (d3.event.altKey) {
+ if (!center) center = [ (extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2 ];
+ origin[0] = extent[+(point[0] < center[0])][0];
+ origin[1] = extent[+(point[1] < center[1])][1];
+ } else center = null;
+ }
+ if (resizingX && move1(point, x, 0)) {
+ redrawX(g);
+ moved = true;
+ }
+ if (resizingY && move1(point, y, 1)) {
+ redrawY(g);
+ moved = true;
+ }
+ if (moved) {
+ redraw(g);
+ event_({
+ type: "brush",
+ mode: dragging ? "move" : "resize"
+ });
+ }
+ }
+ function move1(point, scale, i) {
+ var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], size = extent[1][i] - extent[0][i], min, max;
+ if (dragging) {
+ r0 -= position;
+ r1 -= size + position;
+ }
+ min = Math.max(r0, Math.min(r1, point[i]));
+ if (dragging) {
+ max = (min += position) + size;
+ } else {
+ if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
+ if (position < min) {
+ max = min;
+ min = position;
+ } else {
+ max = position;
+ }
+ }
+ if (extent[0][i] !== min || extent[1][i] !== max) {
+ extentDomain = null;
+ extent[0][i] = min;
+ extent[1][i] = max;
+ return true;
+ }
+ }
+ function brushend() {
+ brushmove();
+ g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
+ d3.select("body").style("cursor", null);
+ w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null);
+ event_({
+ type: "brushend"
+ });
+ d3_eventCancel();
+ }
+ var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), center, origin = mouse(), offset;
+ var w = d3.select(window).on("mousemove.brush", brushmove).on("mouseup.brush", brushend).on("touchmove.brush", brushmove).on("touchend.brush", brushend).on("keydown.brush", keydown).on("keyup.brush", keyup);
+ if (dragging) {
+ origin[0] = extent[0][0] - origin[0];
+ origin[1] = extent[0][1] - origin[1];
+ } else if (resizing) {
+ var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing);
+ offset = [ extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1] ];
+ origin[0] = extent[ex][0];
+ origin[1] = extent[ey][1];
+ } else if (d3.event.altKey) center = origin.slice();
+ g.style("pointer-events", "none").selectAll(".resize").style("display", null);
+ d3.select("body").style("cursor", eventTarget.style("cursor"));
+ event_({
+ type: "brushstart"
+ });
+ brushmove();
+ d3_eventCancel();
+ }
+ var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, resizes = d3_svg_brushResizes[0], extent = [ [ 0, 0 ], [ 0, 0 ] ], extentDomain;
+ brush.x = function(z) {
+ if (!arguments.length) return x;
+ x = z;
+ resizes = d3_svg_brushResizes[!x << 1 | !y];
+ return brush;
+ };
+ brush.y = function(z) {
+ if (!arguments.length) return y;
+ y = z;
+ resizes = d3_svg_brushResizes[!x << 1 | !y];
+ return brush;
+ };
+ brush.extent = function(z) {
+ var x0, x1, y0, y1, t;
+ if (!arguments.length) {
+ z = extentDomain || extent;
+ if (x) {
+ x0 = z[0][0], x1 = z[1][0];
+ if (!extentDomain) {
+ x0 = extent[0][0], x1 = extent[1][0];
+ if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
+ if (x1 < x0) t = x0, x0 = x1, x1 = t;
+ }
+ }
+ if (y) {
+ y0 = z[0][1], y1 = z[1][1];
+ if (!extentDomain) {
+ y0 = extent[0][1], y1 = extent[1][1];
+ if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
+ if (y1 < y0) t = y0, y0 = y1, y1 = t;
+ }
+ }
+ return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ];
+ }
+ extentDomain = [ [ 0, 0 ], [ 0, 0 ] ];
+ if (x) {
+ x0 = z[0], x1 = z[1];
+ if (y) x0 = x0[0], x1 = x1[0];
+ extentDomain[0][0] = x0, extentDomain[1][0] = x1;
+ if (x.invert) x0 = x(x0), x1 = x(x1);
+ if (x1 < x0) t = x0, x0 = x1, x1 = t;
+ extent[0][0] = x0 | 0, extent[1][0] = x1 | 0;
+ }
+ if (y) {
+ y0 = z[0], y1 = z[1];
+ if (x) y0 = y0[1], y1 = y1[1];
+ extentDomain[0][1] = y0, extentDomain[1][1] = y1;
+ if (y.invert) y0 = y(y0), y1 = y(y1);
+ if (y1 < y0) t = y0, y0 = y1, y1 = t;
+ extent[0][1] = y0 | 0, extent[1][1] = y1 | 0;
+ }
+ return brush;
+ };
+ brush.clear = function() {
+ extentDomain = null;
+ extent[0][0] = extent[0][1] = extent[1][0] = extent[1][1] = 0;
+ return brush;
+ };
+ brush.empty = function() {
+ return x && extent[0][0] === extent[1][0] || y && extent[0][1] === extent[1][1];
+ };
+ return d3.rebind(brush, event, "on");
+ };
+ var d3_svg_brushCursor = {
+ n: "ns-resize",
+ e: "ew-resize",
+ s: "ns-resize",
+ w: "ew-resize",
+ nw: "nwse-resize",
+ ne: "nesw-resize",
+ se: "nwse-resize",
+ sw: "nesw-resize"
+ };
+ var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ];
+ d3.behavior = {};
+ d3.behavior.drag = function() {
+ function drag() {
+ this.on("mousedown.drag", mousedown).on("touchstart.drag", mousedown);
+ }
+ function mousedown() {
+ function point() {
+ var p = target.parentNode;
+ return touchId ? d3.touches(p).filter(function(p) {
+ return p.identifier === touchId;
+ })[0] : d3.mouse(p);
+ }
+ function dragmove() {
+ if (!target.parentNode) return dragend();
+ var p = point(), dx = p[0] - origin_[0], dy = p[1] - origin_[1];
+ moved |= dx | dy;
+ origin_ = p;
+ d3_eventCancel();
+ event_({
+ type: "drag",
+ x: p[0] + offset[0],
+ y: p[1] + offset[1],
+ dx: dx,
+ dy: dy
+ });
+ }
+ function dragend() {
+ event_({
+ type: "dragend"
+ });
+ if (moved) {
+ d3_eventCancel();
+ if (d3.event.target === eventTarget) w.on("click.drag", click, true);
+ }
+ w.on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", null).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", null);
+ }
+ function click() {
+ d3_eventCancel();
+ w.on("click.drag", null);
+ }
+ var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, touchId = d3.event.touches && d3.event.changedTouches[0].identifier, offset, origin_ = point(), moved = 0;
+ var w = d3.select(window).on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true);
+ if (origin) {
+ offset = origin.apply(target, arguments);
+ offset = [ offset.x - origin_[0], offset.y - origin_[1] ];
+ } else {
+ offset = [ 0, 0 ];
+ }
+ if (!touchId) d3_eventCancel();
+ event_({
+ type: "dragstart"
+ });
+ }
+ var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null;
+ drag.origin = function(x) {
+ if (!arguments.length) return origin;
+ origin = x;
+ return drag;
+ };
+ return d3.rebind(drag, event, "on");
+ };
+ d3.behavior.zoom = function() {
+ function zoom() {
+ this.on("mousedown.zoom", mousedown).on("mousewheel.zoom", mousewheel).on("mousemove.zoom", mousemove).on("DOMMouseScroll.zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart);
+ }
+ function location(p) {
+ return [ (p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale ];
+ }
+ function point(l) {
+ return [ l[0] * scale + translate[0], l[1] * scale + translate[1] ];
+ }
+ function scaleTo(s) {
+ scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
+ }
+ function translateTo(p, l) {
+ l = point(l);
+ translate[0] += p[0] - l[0];
+ translate[1] += p[1] - l[1];
+ }
+ function dispatch(event) {
+ if (x1) x1.domain(x0.range().map(function(x) {
+ return (x - translate[0]) / scale;
+ }).map(x0.invert));
+ if (y1) y1.domain(y0.range().map(function(y) {
+ return (y - translate[1]) / scale;
+ }).map(y0.invert));
+ d3.event.preventDefault();
+ event({
+ type: "zoom",
+ scale: scale,
+ translate: translate
+ });
+ }
+ function mousedown() {
+ function mousemove() {
+ moved = 1;
+ translateTo(d3.mouse(target), l);
+ dispatch(event_);
+ }
+ function mouseup() {
+ if (moved) d3_eventCancel();
+ w.on("mousemove.zoom", null).on("mouseup.zoom", null);
+ if (moved && d3.event.target === eventTarget) w.on("click.zoom", click, true);
+ }
+ function click() {
+ d3_eventCancel();
+ w.on("click.zoom", null);
+ }
+ var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, moved = 0, w = d3.select(window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), l = location(d3.mouse(target));
+ window.focus();
+ d3_eventCancel();
+ }
+ function mousewheel() {
+ if (!translate0) translate0 = location(d3.mouse(this));
+ scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale);
+ translateTo(d3.mouse(this), translate0);
+ dispatch(event.of(this, arguments));
+ }
+ function mousemove() {
+ translate0 = null;
+ }
+ function dblclick() {
+ var p = d3.mouse(this), l = location(p);
+ scaleTo(d3.event.shiftKey ? scale / 2 : scale * 2);
+ translateTo(p, l);
+ dispatch(event.of(this, arguments));
+ }
+ function touchstart() {
+ var touches = d3.touches(this), now = Date.now();
+ scale0 = scale;
+ translate0 = {};
+ touches.forEach(function(t) {
+ translate0[t.identifier] = location(t);
+ });
+ d3_eventCancel();
+ if (touches.length === 1) {
+ if (now - touchtime < 500) {
+ var p = touches[0], l = location(touches[0]);
+ scaleTo(scale * 2);
+ translateTo(p, l);
+ dispatch(event.of(this, arguments));
+ }
+ touchtime = now;
+ }
+ }
+ function touchmove() {
+ var touches = d3.touches(this), p0 = touches[0], l0 = translate0[p0.identifier];
+ if (p1 = touches[1]) {
+ var p1, l1 = translate0[p1.identifier];
+ p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ];
+ l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ];
+ scaleTo(d3.event.scale * scale0);
+ }
+ translateTo(p0, l0);
+ touchtime = null;
+ dispatch(event.of(this, arguments));
+ }
+ var translate = [ 0, 0 ], translate0, scale = 1, scale0, scaleExtent = d3_behavior_zoomInfinity, event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime;
+ zoom.translate = function(x) {
+ if (!arguments.length) return translate;
+ translate = x.map(Number);
+ return zoom;
+ };
+ zoom.scale = function(x) {
+ if (!arguments.length) return scale;
+ scale = +x;
+ return zoom;
+ };
+ zoom.scaleExtent = function(x) {
+ if (!arguments.length) return scaleExtent;
+ scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number);
+ return zoom;
+ };
+ zoom.x = function(z) {
+ if (!arguments.length) return x1;
+ x1 = z;
+ x0 = z.copy();
+ return zoom;
+ };
+ zoom.y = function(z) {
+ if (!arguments.length) return y1;
+ y1 = z;
+ y0 = z.copy();
+ return zoom;
+ };
+ return d3.rebind(zoom, event, "on");
+ };
+ var d3_behavior_zoomDiv, d3_behavior_zoomInfinity = [ 0, Infinity ];
+ d3.layout = {};
+ d3.layout.bundle = function() {
+ return function(links) {
+ var paths = [], i = -1, n = links.length;
+ while (++i < n) paths.push(d3_layout_bundlePath(links[i]));
+ return paths;
+ };
+ };
+ d3.layout.chord = function() {
+ function relayout() {
+ var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
+ chords = [];
+ groups = [];
+ k = 0, i = -1;
+ while (++i < n) {
+ x = 0, j = -1;
+ while (++j < n) {
+ x += matrix[i][j];
+ }
+ groupSums.push(x);
+ subgroupIndex.push(d3.range(n));
+ k += x;
+ }
+ if (sortGroups) {
+ groupIndex.sort(function(a, b) {
+ return sortGroups(groupSums[a], groupSums[b]);
+ });
+ }
+ if (sortSubgroups) {
+ subgroupIndex.forEach(function(d, i) {
+ d.sort(function(a, b) {
+ return sortSubgroups(matrix[i][a], matrix[i][b]);
+ });
+ });
+ }
+ k = (2 * Math.PI - padding * n) / k;
+ x = 0, i = -1;
+ while (++i < n) {
+ x0 = x, j = -1;
+ while (++j < n) {
+ var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
+ subgroups[di + "-" + dj] = {
+ index: di,
+ subindex: dj,
+ startAngle: a0,
+ endAngle: a1,
+ value: v
+ };
+ }
+ groups[di] = {
+ index: di,
+ startAngle: x0,
+ endAngle: x,
+ value: (x - x0) / k
+ };
+ x += padding;
+ }
+ i = -1;
+ while (++i < n) {
+ j = i - 1;
+ while (++j < n) {
+ var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
+ if (source.value || target.value) {
+ chords.push(source.value < target.value ? {
+ source: target,
+ target: source
+ } : {
+ source: source,
+ target: target
+ });
+ }
+ }
+ }
+ if (sortChords) resort();
+ }
+ function resort() {
+ chords.sort(function(a, b) {
+ return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
+ });
+ }
+ var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
+ chord.matrix = function(x) {
+ if (!arguments.length) return matrix;
+ n = (matrix = x) && matrix.length;
+ chords = groups = null;
+ return chord;
+ };
+ chord.padding = function(x) {
+ if (!arguments.length) return padding;
+ padding = x;
+ chords = groups = null;
+ return chord;
+ };
+ chord.sortGroups = function(x) {
+ if (!arguments.length) return sortGroups;
+ sortGroups = x;
+ chords = groups = null;
+ return chord;
+ };
+ chord.sortSubgroups = function(x) {
+ if (!arguments.length) return sortSubgroups;
+ sortSubgroups = x;
+ chords = null;
+ return chord;
+ };
+ chord.sortChords = function(x) {
+ if (!arguments.length) return sortChords;
+ sortChords = x;
+ if (chords) resort();
+ return chord;
+ };
+ chord.chords = function() {
+ if (!chords) relayout();
+ return chords;
+ };
+ chord.groups = function() {
+ if (!groups) relayout();
+ return groups;
+ };
+ return chord;
+ };
+ d3.layout.force = function() {
+ function repulse(node) {
+ return function(quad, x1, y1, x2, y2) {
+ if (quad.point !== node) {
+ var dx = quad.cx - node.x, dy = quad.cy - node.y, dn = 1 / Math.sqrt(dx * dx + dy * dy);
+ if ((x2 - x1) * dn < theta) {
+ var k = quad.charge * dn * dn;
+ node.px -= dx * k;
+ node.py -= dy * k;
+ return true;
+ }
+ if (quad.point && isFinite(dn)) {
+ var k = quad.pointCharge * dn * dn;
+ node.px -= dx * k;
+ node.py -= dy * k;
+ }
+ }
+ return !quad.charge;
+ };
+ }
+ function dragmove(d) {
+ d.px = d3.event.x;
+ d.py = d3.event.y;
+ force.resume();
+ }
+ var force = {}, event = d3.dispatch("start", "tick", "end"), size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, gravity = .1, theta = .8, interval, nodes = [], links = [], distances, strengths, charges;
+ force.tick = function() {
+ if ((alpha *= .99) < .005) {
+ event.end({
+ type: "end",
+ alpha: alpha = 0
+ });
+ return true;
+ }
+ var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y;
+ for (i = 0; i < m; ++i) {
+ o = links[i];
+ s = o.source;
+ t = o.target;
+ x = t.x - s.x;
+ y = t.y - s.y;
+ if (l = x * x + y * y) {
+ l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
+ x *= l;
+ y *= l;
+ t.x -= x * (k = s.weight / (t.weight + s.weight));
+ t.y -= y * k;
+ s.x += x * (k = 1 - k);
+ s.y += y * k;
+ }
+ }
+ if (k = alpha * gravity) {
+ x = size[0] / 2;
+ y = size[1] / 2;
+ i = -1;
+ if (k) while (++i < n) {
+ o = nodes[i];
+ o.x += (x - o.x) * k;
+ o.y += (y - o.y) * k;
+ }
+ }
+ if (charge) {
+ d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
+ i = -1;
+ while (++i < n) {
+ if (!(o = nodes[i]).fixed) {
+ q.visit(repulse(o));
+ }
+ }
+ }
+ i = -1;
+ while (++i < n) {
+ o = nodes[i];
+ if (o.fixed) {
+ o.x = o.px;
+ o.y = o.py;
+ } else {
+ o.x -= (o.px - (o.px = o.x)) * friction;
+ o.y -= (o.py - (o.py = o.y)) * friction;
+ }
+ }
+ event.tick({
+ type: "tick",
+ alpha: alpha
+ });
+ };
+ force.nodes = function(x) {
+ if (!arguments.length) return nodes;
+ nodes = x;
+ return force;
+ };
+ force.links = function(x) {
+ if (!arguments.length) return links;
+ links = x;
+ return force;
+ };
+ force.size = function(x) {
+ if (!arguments.length) return size;
+ size = x;
+ return force;
+ };
+ force.linkDistance = function(x) {
+ if (!arguments.length) return linkDistance;
+ linkDistance = d3_functor(x);
+ return force;
+ };
+ force.distance = force.linkDistance;
+ force.linkStrength = function(x) {
+ if (!arguments.length) return linkStrength;
+ linkStrength = d3_functor(x);
+ return force;
+ };
+ force.friction = function(x) {
+ if (!arguments.length) return friction;
+ friction = x;
+ return force;
+ };
+ force.charge = function(x) {
+ if (!arguments.length) return charge;
+ charge = typeof x === "function" ? x : +x;
+ return force;
+ };
+ force.gravity = function(x) {
+ if (!arguments.length) return gravity;
+ gravity = x;
+ return force;
+ };
+ force.theta = function(x) {
+ if (!arguments.length) return theta;
+ theta = x;
+ return force;
+ };
+ force.alpha = function(x) {
+ if (!arguments.length) return alpha;
+ if (alpha) {
+ if (x > 0) alpha = x; else alpha = 0;
+ } else if (x > 0) {
+ event.start({
+ type: "start",
+ alpha: alpha = x
+ });
+ d3.timer(force.tick);
+ }
+ return force;
+ };
+ force.start = function() {
+ function position(dimension, size) {
+ var neighbors = neighbor(i), j = -1, m = neighbors.length, x;
+ while (++j < m) if (!isNaN(x = neighbors[j][dimension])) return x;
+ return Math.random() * size;
+ }
+ function neighbor() {
+ if (!neighbors) {
+ neighbors = [];
+ for (j = 0; j < n; ++j) {
+ neighbors[j] = [];
+ }
+ for (j = 0; j < m; ++j) {
+ var o = links[j];
+ neighbors[o.source.index].push(o.target);
+ neighbors[o.target.index].push(o.source);
+ }
+ }
+ return neighbors[i];
+ }
+ var i, j, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o;
+ for (i = 0; i < n; ++i) {
+ (o = nodes[i]).index = i;
+ o.weight = 0;
+ }
+ distances = [];
+ strengths = [];
+ for (i = 0; i < m; ++i) {
+ o = links[i];
+ if (typeof o.source == "number") o.source = nodes[o.source];
+ if (typeof o.target == "number") o.target = nodes[o.target];
+ distances[i] = linkDistance.call(this, o, i);
+ strengths[i] = linkStrength.call(this, o, i);
+ ++o.source.weight;
+ ++o.target.weight;
+ }
+ for (i = 0; i < n; ++i) {
+ o = nodes[i];
+ if (isNaN(o.x)) o.x = position("x", w);
+ if (isNaN(o.y)) o.y = position("y", h);
+ if (isNaN(o.px)) o.px = o.x;
+ if (isNaN(o.py)) o.py = o.y;
+ }
+ charges = [];
+ if (typeof charge === "function") {
+ for (i = 0; i < n; ++i) {
+ charges[i] = +charge.call(this, nodes[i], i);
+ }
+ } else {
+ for (i = 0; i < n; ++i) {
+ charges[i] = charge;
+ }
+ }
+ return force.resume();
+ };
+ force.resume = function() {
+ return force.alpha(.1);
+ };
+ force.stop = function() {
+ return force.alpha(0);
+ };
+ force.drag = function() {
+ if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart", d3_layout_forceDragstart).on("drag", dragmove).on("dragend", d3_layout_forceDragend);
+ this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
+ };
+ return d3.rebind(force, event, "on");
+ };
+ d3.layout.partition = function() {
+ function position(node, x, dx, dy) {
+ var children = node.children;
+ node.x = x;
+ node.y = node.depth * dy;
+ node.dx = dx;
+ node.dy = dy;
+ if (children && (n = children.length)) {
+ var i = -1, n, c, d;
+ dx = node.value ? dx / node.value : 0;
+ while (++i < n) {
+ position(c = children[i], x, d = c.value * dx, dy);
+ x += d;
+ }
+ }
+ }
+ function depth(node) {
+ var children = node.children, d = 0;
+ if (children && (n = children.length)) {
+ var i = -1, n;
+ while (++i < n) d = Math.max(d, depth(children[i]));
+ }
+ return 1 + d;
+ }
+ function partition(d, i) {
+ var nodes = hierarchy.call(this, d, i);
+ position(nodes[0], 0, size[0], size[1] / depth(nodes[0]));
+ return nodes;
+ }
+ var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ];
+ partition.size = function(x) {
+ if (!arguments.length) return size;
+ size = x;
+ return partition;
+ };
+ return d3_layout_hierarchyRebind(partition, hierarchy);
+ };
+ d3.layout.pie = function() {
+ function pie(data, i) {
+ var values = data.map(function(d, i) {
+ return +value.call(pie, d, i);
+ });
+ var a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle);
+ var k = ((typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - startAngle) / d3.sum(values);
+ var index = d3.range(data.length);
+ if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
+ return values[j] - values[i];
+ } : function(i, j) {
+ return sort(data[i], data[j]);
+ });
+ var arcs = [];
+ index.forEach(function(i) {
+ var d;
+ arcs[i] = {
+ data: data[i],
+ value: d = values[i],
+ startAngle: a,
+ endAngle: a += d * k
+ };
+ });
+ return arcs;
+ }
+ var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = 2 * Math.PI;
+ pie.value = function(x) {
+ if (!arguments.length) return value;
+ value = x;
+ return pie;
+ };
+ pie.sort = function(x) {
+ if (!arguments.length) return sort;
+ sort = x;
+ return pie;
+ };
+ pie.startAngle = function(x) {
+ if (!arguments.length) return startAngle;
+ startAngle = x;
+ return pie;
+ };
+ pie.endAngle = function(x) {
+ if (!arguments.length) return endAngle;
+ endAngle = x;
+ return pie;
+ };
+ return pie;
+ };
+ var d3_layout_pieSortByValue = {};
+ d3.layout.stack = function() {
+ function stack(data, index) {
+ var series = data.map(function(d, i) {
+ return values.call(stack, d, i);
+ });
+ var points = series.map(function(d, i) {
+ return d.map(function(v, i) {
+ return [ x.call(stack, v, i), y.call(stack, v, i) ];
+ });
+ });
+ var orders = order.call(stack, points, index);
+ series = d3.permute(series, orders);
+ points = d3.permute(points, orders);
+ var offsets = offset.call(stack, points, index);
+ var n = series.length, m = series[0].length, i, j, o;
+ for (j = 0; j < m; ++j) {
+ out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
+ for (i = 1; i < n; ++i) {
+ out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
+ }
+ }
+ return data;
+ }
+ var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY;
+ stack.values = function(x) {
+ if (!arguments.length) return values;
+ values = x;
+ return stack;
+ };
+ stack.order = function(x) {
+ if (!arguments.length) return order;
+ order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault;
+ return stack;
+ };
+ stack.offset = function(x) {
+ if (!arguments.length) return offset;
+ offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero;
+ return stack;
+ };
+ stack.x = function(z) {
+ if (!arguments.length) return x;
+ x = z;
+ return stack;
+ };
+ stack.y = function(z) {
+ if (!arguments.length) return y;
+ y = z;
+ return stack;
+ };
+ stack.out = function(z) {
+ if (!arguments.length) return out;
+ out = z;
+ return stack;
+ };
+ return stack;
+ };
+ var d3_layout_stackOrders = d3.map({
+ "inside-out": function(data) {
+ var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) {
+ return max[a] - max[b];
+ }), top = 0, bottom = 0, tops = [], bottoms = [];
+ for (i = 0; i < n; ++i) {
+ j = index[i];
+ if (top < bottom) {
+ top += sums[j];
+ tops.push(j);
+ } else {
+ bottom += sums[j];
+ bottoms.push(j);
+ }
+ }
+ return bottoms.reverse().concat(tops);
+ },
+ reverse: function(data) {
+ return d3.range(data.length).reverse();
+ },
+ "default": d3_layout_stackOrderDefault
+ });
+ var d3_layout_stackOffsets = d3.map({
+ silhouette: function(data) {
+ var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = [];
+ for (j = 0; j < m; ++j) {
+ for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
+ if (o > max) max = o;
+ sums.push(o);
+ }
+ for (j = 0; j < m; ++j) {
+ y0[j] = (max - sums[j]) / 2;
+ }
+ return y0;
+ },
+ wiggle: function(data) {
+ var n = data.length, x = data[0], m = x.length, max = 0, i, j, k, s1, s2, s3, dx, o, o0, y0 = [];
+ y0[0] = o = o0 = 0;
+ for (j = 1; j < m; ++j) {
+ for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
+ for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
+ for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
+ s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
+ }
+ s2 += s3 * data[i][j][1];
+ }
+ y0[j] = o -= s1 ? s2 / s1 * dx : 0;
+ if (o < o0) o0 = o;
+ }
+ for (j = 0; j < m; ++j) y0[j] -= o0;
+ return y0;
+ },
+ expand: function(data) {
+ var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = [];
+ for (j = 0; j < m; ++j) {
+ for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
+ if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k;
+ }
+ for (j = 0; j < m; ++j) y0[j] = 0;
+ return y0;
+ },
+ zero: d3_layout_stackOffsetZero
+ });
+ d3.layout.histogram = function() {
+ function histogram(data, i) {
+ var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x;
+ while (++i < m) {
+ bin = bins[i] = [];
+ bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
+ bin.y = 0;
+ }
+ if (m > 0) {
+ i = -1;
+ while (++i < n) {
+ x = values[i];
+ if (x >= range[0] && x <= range[1]) {
+ bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
+ bin.y += k;
+ bin.push(data[i]);
+ }
+ }
+ }
+ return bins;
+ }
+ var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges;
+ histogram.value = function(x) {
+ if (!arguments.length) return valuer;
+ valuer = x;
+ return histogram;
+ };
+ histogram.range = function(x) {
+ if (!arguments.length) return ranger;
+ ranger = d3_functor(x);
+ return histogram;
+ };
+ histogram.bins = function(x) {
+ if (!arguments.length) return binner;
+ binner = typeof x === "number" ? function(range) {
+ return d3_layout_histogramBinFixed(range, x);
+ } : d3_functor(x);
+ return histogram;
+ };
+ histogram.frequency = function(x) {
+ if (!arguments.length) return frequency;
+ frequency = !!x;
+ return histogram;
+ };
+ return histogram;
+ };
+ d3.layout.hierarchy = function() {
+ function recurse(data, depth, nodes) {
+ var childs = children.call(hierarchy, data, depth), node = d3_layout_hierarchyInline ? data : {
+ data: data
+ };
+ node.depth = depth;
+ nodes.push(node);
+ if (childs && (n = childs.length)) {
+ var i = -1, n, c = node.children = [], v = 0, j = depth + 1, d;
+ while (++i < n) {
+ d = recurse(childs[i], j, nodes);
+ d.parent = node;
+ c.push(d);
+ v += d.value;
+ }
+ if (sort) c.sort(sort);
+ if (value) node.value = v;
+ } else if (value) {
+ node.value = +value.call(hierarchy, data, depth) || 0;
+ }
+ return node;
+ }
+ function revalue(node, depth) {
+ var children = node.children, v = 0;
+ if (children && (n = children.length)) {
+ var i = -1, n, j = depth + 1;
+ while (++i < n) v += revalue(children[i], j);
+ } else if (value) {
+ v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0;
+ }
+ if (value) node.value = v;
+ return v;
+ }
+ function hierarchy(d) {
+ var nodes = [];
+ recurse(d, 0, nodes);
+ return nodes;
+ }
+ var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue;
+ hierarchy.sort = function(x) {
+ if (!arguments.length) return sort;
+ sort = x;
+ return hierarchy;
+ };
+ hierarchy.children = function(x) {
+ if (!arguments.length) return children;
+ children = x;
+ return hierarchy;
+ };
+ hierarchy.value = function(x) {
+ if (!arguments.length) return value;
+ value = x;
+ return hierarchy;
+ };
+ hierarchy.revalue = function(root) {
+ revalue(root, 0);
+ return root;
+ };
+ return hierarchy;
+ };
+ var d3_layout_hierarchyInline = false;
+ d3.layout.pack = function() {
+ function pack(d, i) {
+ var nodes = hierarchy.call(this, d, i), root = nodes[0];
+ root.x = 0;
+ root.y = 0;
+ d3_layout_treeVisitAfter(root, function(d) {
+ d.r = Math.sqrt(d.value);
+ });
+ d3_layout_treeVisitAfter(root, d3_layout_packSiblings);
+ var w = size[0], h = size[1], k = Math.max(2 * root.r / w, 2 * root.r / h);
+ if (padding > 0) {
+ var dr = padding * k / 2;
+ d3_layout_treeVisitAfter(root, function(d) {
+ d.r += dr;
+ });
+ d3_layout_treeVisitAfter(root, d3_layout_packSiblings);
+ d3_layout_treeVisitAfter(root, function(d) {
+ d.r -= dr;
+ });
+ k = Math.max(2 * root.r / w, 2 * root.r / h);
+ }
+ d3_layout_packTransform(root, w / 2, h / 2, 1 / k);
+ return nodes;
+ }
+ var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ];
+ pack.size = function(x) {
+ if (!arguments.length) return size;
+ size = x;
+ return pack;
+ };
+ pack.padding = function(_) {
+ if (!arguments.length) return padding;
+ padding = +_;
+ return pack;
+ };
+ return d3_layout_hierarchyRebind(pack, hierarchy);
+ };
+ d3.layout.cluster = function() {
+ function cluster(d, i) {
+ var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0, kx, ky;
+ d3_layout_treeVisitAfter(root, function(node) {
+ var children = node.children;
+ if (children && children.length) {
+ node.x = d3_layout_clusterX(children);
+ node.y = d3_layout_clusterY(children);
+ } else {
+ node.x = previousNode ? x += separation(node, previousNode) : 0;
+ node.y = 0;
+ previousNode = node;
+ }
+ });
+ var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2;
+ d3_layout_treeVisitAfter(root, function(node) {
+ node.x = (node.x - x0) / (x1 - x0) * size[0];
+ node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
+ });
+ return nodes;
+ }
+ var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ];
+ cluster.separation = function(x) {
+ if (!arguments.length) return separation;
+ separation = x;
+ return cluster;
+ };
+ cluster.size = function(x) {
+ if (!arguments.length) return size;
+ size = x;
+ return cluster;
+ };
+ return d3_layout_hierarchyRebind(cluster, hierarchy);
+ };
+ d3.layout.tree = function() {
+ function tree(d, i) {
+ function firstWalk(node, previousSibling) {
+ var children = node.children, layout = node._tree;
+ if (children && (n = children.length)) {
+ var n, firstChild = children[0], previousChild, ancestor = firstChild, child, i = -1;
+ while (++i < n) {
+ child = children[i];
+ firstWalk(child, previousChild);
+ ancestor = apportion(child, previousChild, ancestor);
+ previousChild = child;
+ }
+ d3_layout_treeShift(node);
+ var midpoint = .5 * (firstChild._tree.prelim + child._tree.prelim);
+ if (previousSibling) {
+ layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling);
+ layout.mod = layout.prelim - midpoint;
+ } else {
+ layout.prelim = midpoint;
+ }
+ } else {
+ if (previousSibling) {
+ layout.prelim = previousSibling._tree.prelim + separation(node, previousSibling);
+ }
+ }
+ }
+ function secondWalk(node, x) {
+ node.x = node._tree.prelim + x;
+ var children = node.children;
+ if (children && (n = children.length)) {
+ var i = -1, n;
+ x += node._tree.mod;
+ while (++i < n) {
+ secondWalk(children[i], x);
+ }
+ }
+ }
+ function apportion(node, previousSibling, ancestor) {
+ if (previousSibling) {
+ var vip = node, vop = node, vim = previousSibling, vom = node.parent.children[0], sip = vip._tree.mod, sop = vop._tree.mod, sim = vim._tree.mod, som = vom._tree.mod, shift;
+ while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
+ vom = d3_layout_treeLeft(vom);
+ vop = d3_layout_treeRight(vop);
+ vop._tree.ancestor = node;
+ shift = vim._tree.prelim + sim - vip._tree.prelim - sip + separation(vim, vip);
+ if (shift > 0) {
+ d3_layout_treeMove(d3_layout_treeAncestor(vim, node, ancestor), node, shift);
+ sip += shift;
+ sop += shift;
+ }
+ sim += vim._tree.mod;
+ sip += vip._tree.mod;
+ som += vom._tree.mod;
+ sop += vop._tree.mod;
+ }
+ if (vim && !d3_layout_treeRight(vop)) {
+ vop._tree.thread = vim;
+ vop._tree.mod += sim - sop;
+ }
+ if (vip && !d3_layout_treeLeft(vom)) {
+ vom._tree.thread = vip;
+ vom._tree.mod += sip - som;
+ ancestor = node;
+ }
+ }
+ return ancestor;
+ }
+ var nodes = hierarchy.call(this, d, i), root = nodes[0];
+ d3_layout_treeVisitAfter(root, function(node, previousSibling) {
+ node._tree = {
+ ancestor: node,
+ prelim: 0,
+ mod: 0,
+ change: 0,
+ shift: 0,
+ number: previousSibling ? previousSibling._tree.number + 1 : 0
+ };
+ });
+ firstWalk(root);
+ secondWalk(root, -root._tree.prelim);
+ var left = d3_layout_treeSearch(root, d3_layout_treeLeftmost), right = d3_layout_treeSearch(root, d3_layout_treeRightmost), deep = d3_layout_treeSearch(root, d3_layout_treeDeepest), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2, y1 = deep.depth || 1;
+ d3_layout_treeVisitAfter(root, function(node) {
+ node.x = (node.x - x0) / (x1 - x0) * size[0];
+ node.y = node.depth / y1 * size[1];
+ delete node._tree;
+ });
+ return nodes;
+ }
+ var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ];
+ tree.separation = function(x) {
+ if (!arguments.length) return separation;
+ separation = x;
+ return tree;
+ };
+ tree.size = function(x) {
+ if (!arguments.length) return size;
+ size = x;
+ return tree;
+ };
+ return d3_layout_hierarchyRebind(tree, hierarchy);
+ };
+ d3.layout.treemap = function() {
+ function scale(children, k) {
+ var i = -1, n = children.length, child, area;
+ while (++i < n) {
+ area = (child = children[i]).value * (k < 0 ? 0 : k);
+ child.area = isNaN(area) || area <= 0 ? 0 : area;
+ }
+ }
+ function squarify(node) {
+ var children = node.children;
+ if (children && children.length) {
+ var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = Math.min(rect.dx, rect.dy), n;
+ scale(remaining, rect.dx * rect.dy / node.value);
+ row.area = 0;
+ while ((n = remaining.length) > 0) {
+ row.push(child = remaining[n - 1]);
+ row.area += child.area;
+ if ((score = worst(row, u)) <= best) {
+ remaining.pop();
+ best = score;
+ } else {
+ row.area -= row.pop().area;
+ position(row, u, rect, false);
+ u = Math.min(rect.dx, rect.dy);
+ row.length = row.area = 0;
+ best = Infinity;
+ }
+ }
+ if (row.length) {
+ position(row, u, rect, true);
+ row.length = row.area = 0;
+ }
+ children.forEach(squarify);
+ }
+ }
+ function stickify(node) {
+ var children = node.children;
+ if (children && children.length) {
+ var rect = pad(node), remaining = children.slice(), child, row = [];
+ scale(remaining, rect.dx * rect.dy / node.value);
+ row.area = 0;
+ while (child = remaining.pop()) {
+ row.push(child);
+ row.area += child.area;
+ if (child.z != null) {
+ position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
+ row.length = row.area = 0;
+ }
+ }
+ children.forEach(stickify);
+ }
+ }
+ function worst(row, u) {
+ var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length;
+ while (++i < n) {
+ if (!(r = row[i].area)) continue;
+ if (r < rmin) rmin = r;
+ if (r > rmax) rmax = r;
+ }
+ s *= s;
+ u *= u;
+ return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity;
+ }
+ function position(row, u, rect, flush) {
+ var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o;
+ if (u == rect.dx) {
+ if (flush || v > rect.dy) v = rect.dy;
+ while (++i < n) {
+ o = row[i];
+ o.x = x;
+ o.y = y;
+ o.dy = v;
+ x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
+ }
+ o.z = true;
+ o.dx += rect.x + rect.dx - x;
+ rect.y += v;
+ rect.dy -= v;
+ } else {
+ if (flush || v > rect.dx) v = rect.dx;
+ while (++i < n) {
+ o = row[i];
+ o.x = x;
+ o.y = y;
+ o.dx = v;
+ y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
+ }
+ o.z = false;
+ o.dy += rect.y + rect.dy - y;
+ rect.x += v;
+ rect.dx -= v;
+ }
+ }
+ function treemap(d) {
+ var nodes = stickies || hierarchy(d), root = nodes[0];
+ root.x = 0;
+ root.y = 0;
+ root.dx = size[0];
+ root.dy = size[1];
+ if (stickies) hierarchy.revalue(root);
+ scale([ root ], root.dx * root.dy / root.value);
+ (stickies ? stickify : squarify)(root);
+ if (sticky) stickies = nodes;
+ return nodes;
+ }
+ var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, ratio = .5 * (1 + Math.sqrt(5));
+ treemap.size = function(x) {
+ if (!arguments.length) return size;
+ size = x;
+ return treemap;
+ };
+ treemap.padding = function(x) {
+ function padFunction(node) {
+ var p = x.call(treemap, node, node.depth);
+ return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p);
+ }
+ function padConstant(node) {
+ return d3_layout_treemapPad(node, x);
+ }
+ if (!arguments.length) return padding;
+ var type;
+ pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], padConstant) : padConstant;
+ return treemap;
+ };
+ treemap.round = function(x) {
+ if (!arguments.length) return round != Number;
+ round = x ? Math.round : Number;
+ return treemap;
+ };
+ treemap.sticky = function(x) {
+ if (!arguments.length) return sticky;
+ sticky = x;
+ stickies = null;
+ return treemap;
+ };
+ treemap.ratio = function(x) {
+ if (!arguments.length) return ratio;
+ ratio = x;
+ return treemap;
+ };
+ return d3_layout_hierarchyRebind(treemap, hierarchy);
+ };
+ d3.csv = d3_dsv(",", "text/csv");
+ d3.tsv = d3_dsv(" ", "text/tab-separated-values");
+ d3.geo = {};
+ var d3_geo_radians = Math.PI / 180;
+ d3.geo.azimuthal = function() {
+ function azimuthal(coordinates) {
+ var x1 = coordinates[0] * d3_geo_radians - x0, y1 = coordinates[1] * d3_geo_radians, cx1 = Math.cos(x1), sx1 = Math.sin(x1), cy1 = Math.cos(y1), sy1 = Math.sin(y1), cc = mode !== "orthographic" ? sy0 * sy1 + cy0 * cy1 * cx1 : null, c, k = mode === "stereographic" ? 1 / (1 + cc) : mode === "gnomonic" ? 1 / cc : mode === "equidistant" ? (c = Math.acos(cc), c ? c / Math.sin(c) : 0) : mode === "equalarea" ? Math.sqrt(2 / (1 + cc)) : 1, x = k * cy1 * sx1, y = k * (sy0 * cy1 * cx1 - cy0 * sy1);
+ return [ scale * x + translate[0], scale * y + translate[1] ];
+ }
+ var mode = "orthographic", origin, scale = 200, translate = [ 480, 250 ], x0, y0, cy0, sy0;
+ azimuthal.invert = function(coordinates) {
+ var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale, p = Math.sqrt(x * x + y * y), c = mode === "stereographic" ? 2 * Math.atan(p) : mode === "gnomonic" ? Math.atan(p) : mode === "equidistant" ? p : mode === "equalarea" ? 2 * Math.asin(.5 * p) : Math.asin(p), sc = Math.sin(c), cc = Math.cos(c);
+ return [ (x0 + Math.atan2(x * sc, p * cy0 * cc + y * sy0 * sc)) / d3_geo_radians, Math.asin(cc * sy0 - (p ? y * sc * cy0 / p : 0)) / d3_geo_radians ];
+ };
+ azimuthal.mode = function(x) {
+ if (!arguments.length) return mode;
+ mode = x + "";
+ return azimuthal;
+ };
+ azimuthal.origin = function(x) {
+ if (!arguments.length) return origin;
+ origin = x;
+ x0 = origin[0] * d3_geo_radians;
+ y0 = origin[1] * d3_geo_radians;
+ cy0 = Math.cos(y0);
+ sy0 = Math.sin(y0);
+ return azimuthal;
+ };
+ azimuthal.scale = function(x) {
+ if (!arguments.length) return scale;
+ scale = +x;
+ return azimuthal;
+ };
+ azimuthal.translate = function(x) {
+ if (!arguments.length) return translate;
+ translate = [ +x[0], +x[1] ];
+ return azimuthal;
+ };
+ return azimuthal.origin([ 0, 0 ]);
+ };
+ d3.geo.albers = function() {
+ function albers(coordinates) {
+ var t = n * (d3_geo_radians * coordinates[0] - lng0), p = Math.sqrt(C - 2 * n * Math.sin(d3_geo_radians * coordinates[1])) / n;
+ return [ scale * p * Math.sin(t) + translate[0], scale * (p * Math.cos(t) - p0) + translate[1] ];
+ }
+ function reload() {
+ var phi1 = d3_geo_radians * parallels[0], phi2 = d3_geo_radians * parallels[1], lat0 = d3_geo_radians * origin[1], s = Math.sin(phi1), c = Math.cos(phi1);
+ lng0 = d3_geo_radians * origin[0];
+ n = .5 * (s + Math.sin(phi2));
+ C = c * c + 2 * n * s;
+ p0 = Math.sqrt(C - 2 * n * Math.sin(lat0)) / n;
+ return albers;
+ }
+ var origin = [ -98, 38 ], parallels = [ 29.5, 45.5 ], scale = 1e3, translate = [ 480, 250 ], lng0, n, C, p0;
+ albers.invert = function(coordinates) {
+ var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale, p0y = p0 + y, t = Math.atan2(x, p0y), p = Math.sqrt(x * x + p0y * p0y);
+ return [ (lng0 + t / n) / d3_geo_radians, Math.asin((C - p * p * n * n) / (2 * n)) / d3_geo_radians ];
+ };
+ albers.origin = function(x) {
+ if (!arguments.length) return origin;
+ origin = [ +x[0], +x[1] ];
+ return reload();
+ };
+ albers.parallels = function(x) {
+ if (!arguments.length) return parallels;
+ parallels = [ +x[0], +x[1] ];
+ return reload();
+ };
+ albers.scale = function(x) {
+ if (!arguments.length) return scale;
+ scale = +x;
+ return albers;
+ };
+ albers.translate = function(x) {
+ if (!arguments.length) return translate;
+ translate = [ +x[0], +x[1] ];
+ return albers;
+ };
+ return reload();
+ };
+ d3.geo.albersUsa = function() {
+ function albersUsa(coordinates) {
+ var lon = coordinates[0], lat = coordinates[1];
+ return (lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48)(coordinates);
+ }
+ var lower48 = d3.geo.albers();
+ var alaska = d3.geo.albers().origin([ -160, 60 ]).parallels([ 55, 65 ]);
+ var hawaii = d3.geo.albers().origin([ -160, 20 ]).parallels([ 8, 18 ]);
+ var puertoRico = d3.geo.albers().origin([ -60, 10 ]).parallels([ 8, 18 ]);
+ albersUsa.scale = function(x) {
+ if (!arguments.length) return lower48.scale();
+ lower48.scale(x);
+ alaska.scale(x * .6);
+ hawaii.scale(x);
+ puertoRico.scale(x * 1.5);
+ return albersUsa.translate(lower48.translate());
+ };
+ albersUsa.translate = function(x) {
+ if (!arguments.length) return lower48.translate();
+ var dz = lower48.scale() / 1e3, dx = x[0], dy = x[1];
+ lower48.translate(x);
+ alaska.translate([ dx - 400 * dz, dy + 170 * dz ]);
+ hawaii.translate([ dx - 190 * dz, dy + 200 * dz ]);
+ puertoRico.translate([ dx + 580 * dz, dy + 430 * dz ]);
+ return albersUsa;
+ };
+ return albersUsa.scale(lower48.scale());
+ };
+ d3.geo.bonne = function() {
+ function bonne(coordinates) {
+ var x = coordinates[0] * d3_geo_radians - x0, y = coordinates[1] * d3_geo_radians - y0;
+ if (y1) {
+ var p = c1 + y1 - y, E = x * Math.cos(y) / p;
+ x = p * Math.sin(E);
+ y = p * Math.cos(E) - c1;
+ } else {
+ x *= Math.cos(y);
+ y *= -1;
+ }
+ return [ scale * x + translate[0], scale * y + translate[1] ];
+ }
+ var scale = 200, translate = [ 480, 250 ], x0, y0, y1, c1;
+ bonne.invert = function(coordinates) {
+ var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale;
+ if (y1) {
+ var c = c1 + y, p = Math.sqrt(x * x + c * c);
+ y = c1 + y1 - p;
+ x = x0 + p * Math.atan2(x, c) / Math.cos(y);
+ } else {
+ y *= -1;
+ x /= Math.cos(y);
+ }
+ return [ x / d3_geo_radians, y / d3_geo_radians ];
+ };
+ bonne.parallel = function(x) {
+ if (!arguments.length) return y1 / d3_geo_radians;
+ c1 = 1 / Math.tan(y1 = x * d3_geo_radians);
+ return bonne;
+ };
+ bonne.origin = function(x) {
+ if (!arguments.length) return [ x0 / d3_geo_radians, y0 / d3_geo_radians ];
+ x0 = x[0] * d3_geo_radians;
+ y0 = x[1] * d3_geo_radians;
+ return bonne;
+ };
+ bonne.scale = function(x) {
+ if (!arguments.length) return scale;
+ scale = +x;
+ return bonne;
+ };
+ bonne.translate = function(x) {
+ if (!arguments.length) return translate;
+ translate = [ +x[0], +x[1] ];
+ return bonne;
+ };
+ return bonne.origin([ 0, 0 ]).parallel(45);
+ };
+ d3.geo.equirectangular = function() {
+ function equirectangular(coordinates) {
+ var x = coordinates[0] / 360, y = -coordinates[1] / 360;
+ return [ scale * x + translate[0], scale * y + translate[1] ];
+ }
+ var scale = 500, translate = [ 480, 250 ];
+ equirectangular.invert = function(coordinates) {
+ var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale;
+ return [ 360 * x, -360 * y ];
+ };
+ equirectangular.scale = function(x) {
+ if (!arguments.length) return scale;
+ scale = +x;
+ return equirectangular;
+ };
+ equirectangular.translate = function(x) {
+ if (!arguments.length) return translate;
+ translate = [ +x[0], +x[1] ];
+ return equirectangular;
+ };
+ return equirectangular;
+ };
+ d3.geo.mercator = function() {
+ function mercator(coordinates) {
+ var x = coordinates[0] / 360, y = -(Math.log(Math.tan(Math.PI / 4 + coordinates[1] * d3_geo_radians / 2)) / d3_geo_radians) / 360;
+ return [ scale * x + translate[0], scale * Math.max(-.5, Math.min(.5, y)) + translate[1] ];
+ }
+ var scale = 500, translate = [ 480, 250 ];
+ mercator.invert = function(coordinates) {
+ var x = (coordinates[0] - translate[0]) / scale, y = (coordinates[1] - translate[1]) / scale;
+ return [ 360 * x, 2 * Math.atan(Math.exp(-360 * y * d3_geo_radians)) / d3_geo_radians - 90 ];
+ };
+ mercator.scale = function(x) {
+ if (!arguments.length) return scale;
+ scale = +x;
+ return mercator;
+ };
+ mercator.translate = function(x) {
+ if (!arguments.length) return translate;
+ translate = [ +x[0], +x[1] ];
+ return mercator;
+ };
+ return mercator;
+ };
+ d3.geo.path = function() {
+ function path(d, i) {
+ if (typeof pointRadius === "function") pointCircle = d3_path_circle(pointRadius.apply(this, arguments));
+ pathType(d);
+ var result = buffer.length ? buffer.join("") : null;
+ buffer = [];
+ return result;
+ }
+ function project(coordinates) {
+ return projection(coordinates).join(",");
+ }
+ function polygonArea(coordinates) {
+ var sum = area(coordinates[0]), i = 0, n = coordinates.length;
+ while (++i < n) sum -= area(coordinates[i]);
+ return sum;
+ }
+ function polygonCentroid(coordinates) {
+ var polygon = d3.geom.polygon(coordinates[0].map(projection)), area = polygon.area(), centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1), x = centroid[0], y = centroid[1], z = area, i = 0, n = coordinates.length;
+ while (++i < n) {
+ polygon = d3.geom.polygon(coordinates[i].map(projection));
+ area = polygon.area();
+ centroid = polygon.centroid(area < 0 ? (area *= -1, 1) : -1);
+ x -= centroid[0];
+ y -= centroid[1];
+ z -= area;
+ }
+ return [ x, y, 6 * z ];
+ }
+ function area(coordinates) {
+ return Math.abs(d3.geom.polygon(coordinates.map(projection)).area());
+ }
+ var pointRadius = 4.5, pointCircle = d3_path_circle(pointRadius), projection = d3.geo.albersUsa(), buffer = [];
+ var pathType = d3_geo_type({
+ FeatureCollection: function(o) {
+ var features = o.features, i = -1, n = features.length;
+ while (++i < n) buffer.push(pathType(features[i].geometry));
+ },
+ Feature: function(o) {
+ pathType(o.geometry);
+ },
+ Point: function(o) {
+ buffer.push("M", project(o.coordinates), pointCircle);
+ },
+ MultiPoint: function(o) {
+ var coordinates = o.coordinates, i = -1, n = coordinates.length;
+ while (++i < n) buffer.push("M", project(coordinates[i]), pointCircle);
+ },
+ LineString: function(o) {
+ var coordinates = o.coordinates, i = -1, n = coordinates.length;
+ buffer.push("M");
+ while (++i < n) buffer.push(project(coordinates[i]), "L");
+ buffer.pop();
+ },
+ MultiLineString: function(o) {
+ var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m;
+ while (++i < n) {
+ subcoordinates = coordinates[i];
+ j = -1;
+ m = subcoordinates.length;
+ buffer.push("M");
+ while (++j < m) buffer.push(project(subcoordinates[j]), "L");
+ buffer.pop();
+ }
+ },
+ Polygon: function(o) {
+ var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m;
+ while (++i < n) {
+ subcoordinates = coordinates[i];
+ j = -1;
+ if ((m = subcoordinates.length - 1) > 0) {
+ buffer.push("M");
+ while (++j < m) buffer.push(project(subcoordinates[j]), "L");
+ buffer[buffer.length - 1] = "Z";
+ }
+ }
+ },
+ MultiPolygon: function(o) {
+ var coordinates = o.coordinates, i = -1, n = coordinates.length, subcoordinates, j, m, subsubcoordinates, k, p;
+ while (++i < n) {
+ subcoordinates = coordinates[i];
+ j = -1;
+ m = subcoordinates.length;
+ while (++j < m) {
+ subsubcoordinates = subcoordinates[j];
+ k = -1;
+ if ((p = subsubcoordinates.length - 1) > 0) {
+ buffer.push("M");
+ while (++k < p) buffer.push(project(subsubcoordinates[k]), "L");
+ buffer[buffer.length - 1] = "Z";
+ }
+ }
+ }
+ },
+ GeometryCollection: function(o) {
+ var geometries = o.geometries, i = -1, n = geometries.length;
+ while (++i < n) buffer.push(pathType(geometries[i]));
+ }
+ });
+ var areaType = path.area = d3_geo_type({
+ FeatureCollection: function(o) {
+ var area = 0, features = o.features, i = -1, n = features.length;
+ while (++i < n) area += areaType(features[i]);
+ return area;
+ },
+ Feature: function(o) {
+ return areaType(o.geometry);
+ },
+ Polygon: function(o) {
+ return polygonArea(o.coordinates);
+ },
+ MultiPolygon: function(o) {
+ var sum = 0, coordinates = o.coordinates, i = -1, n = coordinates.length;
+ while (++i < n) sum += polygonArea(coordinates[i]);
+ return sum;
+ },
+ GeometryCollection: function(o) {
+ var sum = 0, geometries = o.geometries, i = -1, n = geometries.length;
+ while (++i < n) sum += areaType(geometries[i]);
+ return sum;
+ }
+ }, 0);
+ var centroidType = path.centroid = d3_geo_type({
+ Feature: function(o) {
+ return centroidType(o.geometry);
+ },
+ Polygon: function(o) {
+ var centroid = polygonCentroid(o.coordinates);
+ return [ centroid[0] / centroid[2], centroid[1] / centroid[2] ];
+ },
+ MultiPolygon: function(o) {
+ var area = 0, coordinates = o.coordinates, centroid, x = 0, y = 0, z = 0, i = -1, n = coordinates.length;
+ while (++i < n) {
+ centroid = polygonCentroid(coordinates[i]);
+ x += centroid[0];
+ y += centroid[1];
+ z += centroid[2];
+ }
+ return [ x / z, y / z ];
+ }
+ });
+ path.projection = function(x) {
+ projection = x;
+ return path;
+ };
+ path.pointRadius = function(x) {
+ if (typeof x === "function") pointRadius = x; else {
+ pointRadius = +x;
+ pointCircle = d3_path_circle(pointRadius);
+ }
+ return path;
+ };
+ return path;
+ };
+ d3.geo.bounds = function(feature) {
+ var left = Infinity, bottom = Infinity, right = -Infinity, top = -Infinity;
+ d3_geo_bounds(feature, function(x, y) {
+ if (x < left) left = x;
+ if (x > right) right = x;
+ if (y < bottom) bottom = y;
+ if (y > top) top = y;
+ });
+ return [ [ left, bottom ], [ right, top ] ];
+ };
+ var d3_geo_boundsTypes = {
+ Feature: d3_geo_boundsFeature,
+ FeatureCollection: d3_geo_boundsFeatureCollection,
+ GeometryCollection: d3_geo_boundsGeometryCollection,
+ LineString: d3_geo_boundsLineString,
+ MultiLineString: d3_geo_boundsMultiLineString,
+ MultiPoint: d3_geo_boundsLineString,
+ MultiPolygon: d3_geo_boundsMultiPolygon,
+ Point: d3_geo_boundsPoint,
+ Polygon: d3_geo_boundsPolygon
+ };
+ d3.geo.circle = function() {
+ function circle() {}
+ function visible(point) {
+ return arc.distance(point) < radians;
+ }
+ function clip(coordinates) {
+ var i = -1, n = coordinates.length, clipped = [], p0, p1, p2, d0, d1;
+ while (++i < n) {
+ d1 = arc.distance(p2 = coordinates[i]);
+ if (d1 < radians) {
+ if (p1) clipped.push(d3_geo_greatArcInterpolate(p1, p2)((d0 - radians) / (d0 - d1)));
+ clipped.push(p2);
+ p0 = p1 = null;
+ } else {
+ p1 = p2;
+ if (!p0 && clipped.length) {
+ clipped.push(d3_geo_greatArcInterpolate(clipped[clipped.length - 1], p1)((radians - d0) / (d1 - d0)));
+ p0 = p1;
+ }
+ }
+ d0 = d1;
+ }
+ p0 = coordinates[0];
+ p1 = clipped[0];
+ if (p1 && p2[0] === p0[0] && p2[1] === p0[1] && !(p2[0] === p1[0] && p2[1] === p1[1])) {
+ clipped.push(p1);
+ }
+ return resample(clipped);
+ }
+ function resample(coordinates) {
+ var i = 0, n = coordinates.length, j, m, resampled = n ? [ coordinates[0] ] : coordinates, resamples, origin = arc.source();
+ while (++i < n) {
+ resamples = arc.source(coordinates[i - 1])(coordinates[i]).coordinates;
+ for (j = 0, m = resamples.length; ++j < m; ) resampled.push(resamples[j]);
+ }
+ arc.source(origin);
+ return resampled;
+ }
+ var origin = [ 0, 0 ], degrees = 90 - .01, radians = degrees * d3_geo_radians, arc = d3.geo.greatArc().source(origin).target(d3_identity);
+ circle.clip = function(d) {
+ if (typeof origin === "function") arc.source(origin.apply(this, arguments));
+ return clipType(d) || null;
+ };
+ var clipType = d3_geo_type({
+ FeatureCollection: function(o) {
+ var features = o.features.map(clipType).filter(d3_identity);
+ return features && (o = Object.create(o), o.features = features, o);
+ },
+ Feature: function(o) {
+ var geometry = clipType(o.geometry);
+ return geometry && (o = Object.create(o), o.geometry = geometry, o);
+ },
+ Point: function(o) {
+ return visible(o.coordinates) && o;
+ },
+ MultiPoint: function(o) {
+ var coordinates = o.coordinates.filter(visible);
+ return coordinates.length && {
+ type: o.type,
+ coordinates: coordinates
+ };
+ },
+ LineString: function(o) {
+ var coordinates = clip(o.coordinates);
+ return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o);
+ },
+ MultiLineString: function(o) {
+ var coordinates = o.coordinates.map(clip).filter(function(d) {
+ return d.length;
+ });
+ return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o);
+ },
+ Polygon: function(o) {
+ var coordinates = o.coordinates.map(clip);
+ return coordinates[0].length && (o = Object.create(o), o.coordinates = coordinates, o);
+ },
+ MultiPolygon: function(o) {
+ var coordinates = o.coordinates.map(function(d) {
+ return d.map(clip);
+ }).filter(function(d) {
+ return d[0].length;
+ });
+ return coordinates.length && (o = Object.create(o), o.coordinates = coordinates, o);
+ },
+ GeometryCollection: function(o) {
+ var geometries = o.geometries.map(clipType).filter(d3_identity);
+ return geometries.length && (o = Object.create(o), o.geometries = geometries, o);
+ }
+ });
+ circle.origin = function(x) {
+ if (!arguments.length) return origin;
+ origin = x;
+ if (typeof origin !== "function") arc.source(origin);
+ return circle;
+ };
+ circle.angle = function(x) {
+ if (!arguments.length) return degrees;
+ radians = (degrees = +x) * d3_geo_radians;
+ return circle;
+ };
+ return d3.rebind(circle, arc, "precision");
+ };
+ d3.geo.greatArc = function() {
+ function greatArc() {
+ var d = greatArc.distance.apply(this, arguments), t = 0, dt = precision / d, coordinates = [ p0 ];
+ while ((t += dt) < 1) coordinates.push(interpolate(t));
+ coordinates.push(p1);
+ return {
+ type: "LineString",
+ coordinates: coordinates
+ };
+ }
+ var source = d3_geo_greatArcSource, p0, target = d3_geo_greatArcTarget, p1, precision = 6 * d3_geo_radians, interpolate = d3_geo_greatArcInterpolator();
+ greatArc.distance = function() {
+ if (typeof source === "function") interpolate.source(p0 = source.apply(this, arguments));
+ if (typeof target === "function") interpolate.target(p1 = target.apply(this, arguments));
+ return interpolate.distance();
+ };
+ greatArc.source = function(_) {
+ if (!arguments.length) return source;
+ source = _;
+ if (typeof source !== "function") interpolate.source(p0 = source);
+ return greatArc;
+ };
+ greatArc.target = function(_) {
+ if (!arguments.length) return target;
+ target = _;
+ if (typeof target !== "function") interpolate.target(p1 = target);
+ return greatArc;
+ };
+ greatArc.precision = function(_) {
+ if (!arguments.length) return precision / d3_geo_radians;
+ precision = _ * d3_geo_radians;
+ return greatArc;
+ };
+ return greatArc;
+ };
+ d3.geo.greatCircle = d3.geo.circle;
+ d3.geom = {};
+ d3.geom.contour = function(grid, start) {
+ var s = start || d3_geom_contourStart(grid), c = [], x = s[0], y = s[1], dx = 0, dy = 0, pdx = NaN, pdy = NaN, i = 0;
+ do {
+ i = 0;
+ if (grid(x - 1, y - 1)) i += 1;
+ if (grid(x, y - 1)) i += 2;
+ if (grid(x - 1, y)) i += 4;
+ if (grid(x, y)) i += 8;
+ if (i === 6) {
+ dx = pdy === -1 ? -1 : 1;
+ dy = 0;
+ } else if (i === 9) {
+ dx = 0;
+ dy = pdx === 1 ? -1 : 1;
+ } else {
+ dx = d3_geom_contourDx[i];
+ dy = d3_geom_contourDy[i];
+ }
+ if (dx != pdx && dy != pdy) {
+ c.push([ x, y ]);
+ pdx = dx;
+ pdy = dy;
+ }
+ x += dx;
+ y += dy;
+ } while (s[0] != x || s[1] != y);
+ return c;
+ };
+ var d3_geom_contourDx = [ 1, 0, 1, 1, -1, 0, -1, 1, 0, 0, 0, 0, -1, 0, -1, NaN ], d3_geom_contourDy = [ 0, -1, 0, 0, 0, -1, 0, 0, 1, -1, 1, 1, 0, -1, 0, NaN ];
+ d3.geom.hull = function(vertices) {
+ if (vertices.length < 3) return [];
+ var len = vertices.length, plen = len - 1, points = [], stack = [], i, j, h = 0, x1, y1, x2, y2, u, v, a, sp;
+ for (i = 1; i < len; ++i) {
+ if (vertices[i][1] < vertices[h][1]) {
+ h = i;
+ } else if (vertices[i][1] == vertices[h][1]) {
+ h = vertices[i][0] < vertices[h][0] ? i : h;
+ }
+ }
+ for (i = 0; i < len; ++i) {
+ if (i === h) continue;
+ y1 = vertices[i][1] - vertices[h][1];
+ x1 = vertices[i][0] - vertices[h][0];
+ points.push({
+ angle: Math.atan2(y1, x1),
+ index: i
+ });
+ }
+ points.sort(function(a, b) {
+ return a.angle - b.angle;
+ });
+ a = points[0].angle;
+ v = points[0].index;
+ u = 0;
+ for (i = 1; i < plen; ++i) {
+ j = points[i].index;
+ if (a == points[i].angle) {
+ x1 = vertices[v][0] - vertices[h][0];
+ y1 = vertices[v][1] - vertices[h][1];
+ x2 = vertices[j][0] - vertices[h][0];
+ y2 = vertices[j][1] - vertices[h][1];
+ if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) {
+ points[i].index = -1;
+ } else {
+ points[u].index = -1;
+ a = points[i].angle;
+ u = i;
+ v = j;
+ }
+ } else {
+ a = points[i].angle;
+ u = i;
+ v = j;
+ }
+ }
+ stack.push(h);
+ for (i = 0, j = 0; i < 2; ++j) {
+ if (points[j].index !== -1) {
+ stack.push(points[j].index);
+ i++;
+ }
+ }
+ sp = stack.length;
+ for (; j < plen; ++j) {
+ if (points[j].index === -1) continue;
+ while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) {
+ --sp;
+ }
+ stack[sp++] = points[j].index;
+ }
+ var poly = [];
+ for (i = 0; i < sp; ++i) {
+ poly.push(vertices[stack[i]]);
+ }
+ return poly;
+ };
+ d3.geom.polygon = function(coordinates) {
+ coordinates.area = function() {
+ var i = 0, n = coordinates.length, a = coordinates[n - 1][0] * coordinates[0][1], b = coordinates[n - 1][1] * coordinates[0][0];
+ while (++i < n) {
+ a += coordinates[i - 1][0] * coordinates[i][1];
+ b += coordinates[i - 1][1] * coordinates[i][0];
+ }
+ return (b - a) * .5;
+ };
+ coordinates.centroid = function(k) {
+ var i = -1, n = coordinates.length, x = 0, y = 0, a, b = coordinates[n - 1], c;
+ if (!arguments.length) k = -1 / (6 * coordinates.area());
+ while (++i < n) {
+ a = b;
+ b = coordinates[i];
+ c = a[0] * b[1] - b[0] * a[1];
+ x += (a[0] + b[0]) * c;
+ y += (a[1] + b[1]) * c;
+ }
+ return [ x * k, y * k ];
+ };
+ coordinates.clip = function(subject) {
+ var input, i = -1, n = coordinates.length, j, m, a = coordinates[n - 1], b, c, d;
+ while (++i < n) {
+ input = subject.slice();
+ subject.length = 0;
+ b = coordinates[i];
+ c = input[(m = input.length) - 1];
+ j = -1;
+ while (++j < m) {
+ d = input[j];
+ if (d3_geom_polygonInside(d, a, b)) {
+ if (!d3_geom_polygonInside(c, a, b)) {
+ subject.push(d3_geom_polygonIntersect(c, d, a, b));
+ }
+ subject.push(d);
+ } else if (d3_geom_polygonInside(c, a, b)) {
+ subject.push(d3_geom_polygonIntersect(c, d, a, b));
+ }
+ c = d;
+ }
+ a = b;
+ }
+ return subject;
+ };
+ return coordinates;
+ };
+ d3.geom.voronoi = function(vertices) {
+ var polygons = vertices.map(function() {
+ return [];
+ });
+ d3_voronoi_tessellate(vertices, function(e) {
+ var s1, s2, x1, x2, y1, y2;
+ if (e.a === 1 && e.b >= 0) {
+ s1 = e.ep.r;
+ s2 = e.ep.l;
+ } else {
+ s1 = e.ep.l;
+ s2 = e.ep.r;
+ }
+ if (e.a === 1) {
+ y1 = s1 ? s1.y : -1e6;
+ x1 = e.c - e.b * y1;
+ y2 = s2 ? s2.y : 1e6;
+ x2 = e.c - e.b * y2;
+ } else {
+ x1 = s1 ? s1.x : -1e6;
+ y1 = e.c - e.a * x1;
+ x2 = s2 ? s2.x : 1e6;
+ y2 = e.c - e.a * x2;
+ }
+ var v1 = [ x1, y1 ], v2 = [ x2, y2 ];
+ polygons[e.region.l.index].push(v1, v2);
+ polygons[e.region.r.index].push(v1, v2);
+ });
+ return polygons.map(function(polygon, i) {
+ var cx = vertices[i][0], cy = vertices[i][1];
+ polygon.forEach(function(v) {
+ v.angle = Math.atan2(v[0] - cx, v[1] - cy);
+ });
+ return polygon.sort(function(a, b) {
+ return a.angle - b.angle;
+ }).filter(function(d, i) {
+ return !i || d.angle - polygon[i - 1].angle > 1e-10;
+ });
+ });
+ };
+ var d3_voronoi_opposite = {
+ l: "r",
+ r: "l"
+ };
+ d3.geom.delaunay = function(vertices) {
+ var edges = vertices.map(function() {
+ return [];
+ }), triangles = [];
+ d3_voronoi_tessellate(vertices, function(e) {
+ edges[e.region.l.index].push(vertices[e.region.r.index]);
+ });
+ edges.forEach(function(edge, i) {
+ var v = vertices[i], cx = v[0], cy = v[1];
+ edge.forEach(function(v) {
+ v.angle = Math.atan2(v[0] - cx, v[1] - cy);
+ });
+ edge.sort(function(a, b) {
+ return a.angle - b.angle;
+ });
+ for (var j = 0, m = edge.length - 1; j < m; j++) {
+ triangles.push([ v, edge[j], edge[j + 1] ]);
+ }
+ });
+ return triangles;
+ };
+ d3.geom.quadtree = function(points, x1, y1, x2, y2) {
+ function insert(n, p, x1, y1, x2, y2) {
+ if (isNaN(p.x) || isNaN(p.y)) return;
+ if (n.leaf) {
+ var v = n.point;
+ if (v) {
+ if (Math.abs(v.x - p.x) + Math.abs(v.y - p.y) < .01) {
+ insertChild(n, p, x1, y1, x2, y2);
+ } else {
+ n.point = null;
+ insertChild(n, v, x1, y1, x2, y2);
+ insertChild(n, p, x1, y1, x2, y2);
+ }
+ } else {
+ n.point = p;
+ }
+ } else {
+ insertChild(n, p, x1, y1, x2, y2);
+ }
+ }
+ function insertChild(n, p, x1, y1, x2, y2) {
+ var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, right = p.x >= sx, bottom = p.y >= sy, i = (bottom << 1) + right;
+ n.leaf = false;
+ n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
+ if (right) x1 = sx; else x2 = sx;
+ if (bottom) y1 = sy; else y2 = sy;
+ insert(n, p, x1, y1, x2, y2);
+ }
+ var p, i = -1, n = points.length;
+ if (n && isNaN(points[0].x)) points = points.map(d3_geom_quadtreePoint);
+ if (arguments.length < 5) {
+ if (arguments.length === 3) {
+ y2 = x2 = y1;
+ y1 = x1;
+ } else {
+ x1 = y1 = Infinity;
+ x2 = y2 = -Infinity;
+ while (++i < n) {
+ p = points[i];
+ if (p.x < x1) x1 = p.x;
+ if (p.y < y1) y1 = p.y;
+ if (p.x > x2) x2 = p.x;
+ if (p.y > y2) y2 = p.y;
+ }
+ var dx = x2 - x1, dy = y2 - y1;
+ if (dx > dy) y2 = y1 + dx; else x2 = x1 + dy;
+ }
+ }
+ var root = d3_geom_quadtreeNode();
+ root.add = function(p) {
+ insert(root, p, x1, y1, x2, y2);
+ };
+ root.visit = function(f) {
+ d3_geom_quadtreeVisit(f, root, x1, y1, x2, y2);
+ };
+ points.forEach(root.add);
+ return root;
+ };
+ d3.time = {};
+ var d3_time = Date, d3_time_daySymbols = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ];
+ d3_time_utc.prototype = {
+ getDate: function() {
+ return this._.getUTCDate();
+ },
+ getDay: function() {
+ return this._.getUTCDay();
+ },
+ getFullYear: function() {
+ return this._.getUTCFullYear();
+ },
+ getHours: function() {
+ return this._.getUTCHours();
+ },
+ getMilliseconds: function() {
+ return this._.getUTCMilliseconds();
+ },
+ getMinutes: function() {
+ return this._.getUTCMinutes();
+ },
+ getMonth: function() {
+ return this._.getUTCMonth();
+ },
+ getSeconds: function() {
+ return this._.getUTCSeconds();
+ },
+ getTime: function() {
+ return this._.getTime();
+ },
+ getTimezoneOffset: function() {
+ return 0;
+ },
+ valueOf: function() {
+ return this._.valueOf();
+ },
+ setDate: function() {
+ d3_time_prototype.setUTCDate.apply(this._, arguments);
+ },
+ setDay: function() {
+ d3_time_prototype.setUTCDay.apply(this._, arguments);
+ },
+ setFullYear: function() {
+ d3_time_prototype.setUTCFullYear.apply(this._, arguments);
+ },
+ setHours: function() {
+ d3_time_prototype.setUTCHours.apply(this._, arguments);
+ },
+ setMilliseconds: function() {
+ d3_time_prototype.setUTCMilliseconds.apply(this._, arguments);
+ },
+ setMinutes: function() {
+ d3_time_prototype.setUTCMinutes.apply(this._, arguments);
+ },
+ setMonth: function() {
+ d3_time_prototype.setUTCMonth.apply(this._, arguments);
+ },
+ setSeconds: function() {
+ d3_time_prototype.setUTCSeconds.apply(this._, arguments);
+ },
+ setTime: function() {
+ d3_time_prototype.setTime.apply(this._, arguments);
+ }
+ };
+ var d3_time_prototype = Date.prototype;
+ var d3_time_formatDateTime = "%a %b %e %H:%M:%S %Y", d3_time_formatDate = "%m/%d/%y", d3_time_formatTime = "%H:%M:%S";
+ var d3_time_days = d3_time_daySymbols, d3_time_dayAbbreviations = d3_time_days.map(d3_time_formatAbbreviate), d3_time_months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], d3_time_monthAbbreviations = d3_time_months.map(d3_time_formatAbbreviate);
+ d3.time.format = function(template) {
+ function format(date) {
+ var string = [], i = -1, j = 0, c, f;
+ while (++i < n) {
+ if (template.charCodeAt(i) == 37) {
+ string.push(template.substring(j, i), (f = d3_time_formats[c = template.charAt(++i)]) ? f(date) : c);
+ j = i + 1;
+ }
+ }
+ string.push(template.substring(j, i));
+ return string.join("");
+ }
+ var n = template.length;
+ format.parse = function(string) {
+ var d = {
+ y: 1900,
+ m: 0,
+ d: 1,
+ H: 0,
+ M: 0,
+ S: 0,
+ L: 0
+ }, i = d3_time_parse(d, template, string, 0);
+ if (i != string.length) return null;
+ if ("p" in d) d.H = d.H % 12 + d.p * 12;
+ var date = new d3_time;
+ date.setFullYear(d.y, d.m, d.d);
+ date.setHours(d.H, d.M, d.S, d.L);
+ return date;
+ };
+ format.toString = function() {
+ return template;
+ };
+ return format;
+ };
+ var d3_time_zfill2 = d3.format("02d"), d3_time_zfill3 = d3.format("03d"), d3_time_zfill4 = d3.format("04d"), d3_time_sfill2 = d3.format("2d");
+ var d3_time_dayRe = d3_time_formatRe(d3_time_days), d3_time_dayAbbrevRe = d3_time_formatRe(d3_time_dayAbbreviations), d3_time_monthRe = d3_time_formatRe(d3_time_months), d3_time_monthLookup = d3_time_formatLookup(d3_time_months), d3_time_monthAbbrevRe = d3_time_formatRe(d3_time_monthAbbreviations), d3_time_monthAbbrevLookup = d3_time_formatLookup(d3_time_monthAbbreviations);
+ var d3_time_formats = {
+ a: function(d) {
+ return d3_time_dayAbbreviations[d.getDay()];
+ },
+ A: function(d) {
+ return d3_time_days[d.getDay()];
+ },
+ b: function(d) {
+ return d3_time_monthAbbreviations[d.getMonth()];
+ },
+ B: function(d) {
+ return d3_time_months[d.getMonth()];
+ },
+ c: d3.time.format(d3_time_formatDateTime),
+ d: function(d) {
+ return d3_time_zfill2(d.getDate());
+ },
+ e: function(d) {
+ return d3_time_sfill2(d.getDate());
+ },
+ H: function(d) {
+ return d3_time_zfill2(d.getHours());
+ },
+ I: function(d) {
+ return d3_time_zfill2(d.getHours() % 12 || 12);
+ },
+ j: function(d) {
+ return d3_time_zfill3(1 + d3.time.dayOfYear(d));
+ },
+ L: function(d) {
+ return d3_time_zfill3(d.getMilliseconds());
+ },
+ m: function(d) {
+ return d3_time_zfill2(d.getMonth() + 1);
+ },
+ M: function(d) {
+ return d3_time_zfill2(d.getMinutes());
+ },
+ p: function(d) {
+ return d.getHours() >= 12 ? "PM" : "AM";
+ },
+ S: function(d) {
+ return d3_time_zfill2(d.getSeconds());
+ },
+ U: function(d) {
+ return d3_time_zfill2(d3.time.sundayOfYear(d));
+ },
+ w: function(d) {
+ return d.getDay();
+ },
+ W: function(d) {
+ return d3_time_zfill2(d3.time.mondayOfYear(d));
+ },
+ x: d3.time.format(d3_time_formatDate),
+ X: d3.time.format(d3_time_formatTime),
+ y: function(d) {
+ return d3_time_zfill2(d.getFullYear() % 100);
+ },
+ Y: function(d) {
+ return d3_time_zfill4(d.getFullYear() % 1e4);
+ },
+ Z: d3_time_zone,
+ "%": function(d) {
+ return "%";
+ }
+ };
+ var d3_time_parsers = {
+ a: d3_time_parseWeekdayAbbrev,
+ A: d3_time_parseWeekday,
+ b: d3_time_parseMonthAbbrev,
+ B: d3_time_parseMonth,
+ c: d3_time_parseLocaleFull,
+ d: d3_time_parseDay,
+ e: d3_time_parseDay,
+ H: d3_time_parseHour24,
+ I: d3_time_parseHour24,
+ L: d3_time_parseMilliseconds,
+ m: d3_time_parseMonthNumber,
+ M: d3_time_parseMinutes,
+ p: d3_time_parseAmPm,
+ S: d3_time_parseSeconds,
+ x: d3_time_parseLocaleDate,
+ X: d3_time_parseLocaleTime,
+ y: d3_time_parseYear,
+ Y: d3_time_parseFullYear
+ };
+ var d3_time_numberRe = /^\s*\d+/;
+ var d3_time_amPmLookup = d3.map({
+ am: 0,
+ pm: 1
+ });
+ d3.time.format.utc = function(template) {
+ function format(date) {
+ try {
+ d3_time = d3_time_utc;
+ var utc = new d3_time;
+ utc._ = date;
+ return local(utc);
+ } finally {
+ d3_time = Date;
+ }
+ }
+ var local = d3.time.format(template);
+ format.parse = function(string) {
+ try {
+ d3_time = d3_time_utc;
+ var date = local.parse(string);
+ return date && date._;
+ } finally {
+ d3_time = Date;
+ }
+ };
+ format.toString = local.toString;
+ return format;
+ };
+ var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");
+ d3.time.format.iso = Date.prototype.toISOString ? d3_time_formatIsoNative : d3_time_formatIso;
+ d3_time_formatIsoNative.parse = function(string) {
+ var date = new Date(string);
+ return isNaN(date) ? null : date;
+ };
+ d3_time_formatIsoNative.toString = d3_time_formatIso.toString;
+ d3.time.second = d3_time_interval(function(date) {
+ return new d3_time(Math.floor(date / 1e3) * 1e3);
+ }, function(date, offset) {
+ date.setTime(date.getTime() + Math.floor(offset) * 1e3);
+ }, function(date) {
+ return date.getSeconds();
+ });
+ d3.time.seconds = d3.time.second.range;
+ d3.time.seconds.utc = d3.time.second.utc.range;
+ d3.time.minute = d3_time_interval(function(date) {
+ return new d3_time(Math.floor(date / 6e4) * 6e4);
+ }, function(date, offset) {
+ date.setTime(date.getTime() + Math.floor(offset) * 6e4);
+ }, function(date) {
+ return date.getMinutes();
+ });
+ d3.time.minutes = d3.time.minute.range;
+ d3.time.minutes.utc = d3.time.minute.utc.range;
+ d3.time.hour = d3_time_interval(function(date) {
+ var timezone = date.getTimezoneOffset() / 60;
+ return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
+ }, function(date, offset) {
+ date.setTime(date.getTime() + Math.floor(offset) * 36e5);
+ }, function(date) {
+ return date.getHours();
+ });
+ d3.time.hours = d3.time.hour.range;
+ d3.time.hours.utc = d3.time.hour.utc.range;
+ d3.time.day = d3_time_interval(function(date) {
+ var day = new d3_time(1970, 0);
+ day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
+ return day;
+ }, function(date, offset) {
+ date.setDate(date.getDate() + offset);
+ }, function(date) {
+ return date.getDate() - 1;
+ });
+ d3.time.days = d3.time.day.range;
+ d3.time.days.utc = d3.time.day.utc.range;
+ d3.time.dayOfYear = function(date) {
+ var year = d3.time.year(date);
+ return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5);
+ };
+ d3_time_daySymbols.forEach(function(day, i) {
+ day = day.toLowerCase();
+ i = 7 - i;
+ var interval = d3.time[day] = d3_time_interval(function(date) {
+ (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7);
+ return date;
+ }, function(date, offset) {
+ date.setDate(date.getDate() + Math.floor(offset) * 7);
+ }, function(date) {
+ var day = d3.time.year(date).getDay();
+ return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i);
+ });
+ d3.time[day + "s"] = interval.range;
+ d3.time[day + "s"].utc = interval.utc.range;
+ d3.time[day + "OfYear"] = function(date) {
+ var day = d3.time.year(date).getDay();
+ return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7);
+ };
+ });
+ d3.time.week = d3.time.sunday;
+ d3.time.weeks = d3.time.sunday.range;
+ d3.time.weeks.utc = d3.time.sunday.utc.range;
+ d3.time.weekOfYear = d3.time.sundayOfYear;
+ d3.time.month = d3_time_interval(function(date) {
+ date = d3.time.day(date);
+ date.setDate(1);
+ return date;
+ }, function(date, offset) {
+ date.setMonth(date.getMonth() + offset);
+ }, function(date) {
+ return date.getMonth();
+ });
+ d3.time.months = d3.time.month.range;
+ d3.time.months.utc = d3.time.month.utc.range;
+ d3.time.year = d3_time_interval(function(date) {
+ date = d3.time.day(date);
+ date.setMonth(0, 1);
+ return date;
+ }, function(date, offset) {
+ date.setFullYear(date.getFullYear() + offset);
+ }, function(date) {
+ return date.getFullYear();
+ });
+ d3.time.years = d3.time.year.range;
+ d3.time.years.utc = d3.time.year.utc.range;
+ var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ];
+ var d3_time_scaleLocalMethods = [ [ d3.time.second, 1 ], [ d3.time.second, 5 ], [ d3.time.second, 15 ], [ d3.time.second, 30 ], [ d3.time.minute, 1 ], [ d3.time.minute, 5 ], [ d3.time.minute, 15 ], [ d3.time.minute, 30 ], [ d3.time.hour, 1 ], [ d3.time.hour, 3 ], [ d3.time.hour, 6 ], [ d3.time.hour, 12 ], [ d3.time.day, 1 ], [ d3.time.day, 2 ], [ d3.time.week, 1 ], [ d3.time.month, 1 ], [ d3.time.month, 3 ], [ d3.time.year, 1 ] ];
+ var d3_time_scaleLocalFormats = [ [ d3.time.format("%Y"), function(d) {
+ return true;
+ } ], [ d3.time.format("%B"), function(d) {
+ return d.getMonth();
+ } ], [ d3.time.format("%b %d"), function(d) {
+ return d.getDate() != 1;
+ } ], [ d3.time.format("%a %d"), function(d) {
+ return d.getDay() && d.getDate() != 1;
+ } ], [ d3.time.format("%I %p"), function(d) {
+ return d.getHours();
+ } ], [ d3.time.format("%I:%M"), function(d) {
+ return d.getMinutes();
+ } ], [ d3.time.format(":%S"), function(d) {
+ return d.getSeconds();
+ } ], [ d3.time.format(".%L"), function(d) {
+ return d.getMilliseconds();
+ } ] ];
+ var d3_time_scaleLinear = d3.scale.linear(), d3_time_scaleLocalFormat = d3_time_scaleFormat(d3_time_scaleLocalFormats);
+ d3_time_scaleLocalMethods.year = function(extent, m) {
+ return d3_time_scaleLinear.domain(extent.map(d3_time_scaleGetYear)).ticks(m).map(d3_time_scaleSetYear);
+ };
+ d3.time.scale = function() {
+ return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat);
+ };
+ var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) {
+ return [ m[0].utc, m[1] ];
+ });
+ var d3_time_scaleUTCFormats = [ [ d3.time.format.utc("%Y"), function(d) {
+ return true;
+ } ], [ d3.time.format.utc("%B"), function(d) {
+ return d.getUTCMonth();
+ } ], [ d3.time.format.utc("%b %d"), function(d) {
+ return d.getUTCDate() != 1;
+ } ], [ d3.time.format.utc("%a %d"), function(d) {
+ return d.getUTCDay() && d.getUTCDate() != 1;
+ } ], [ d3.time.format.utc("%I %p"), function(d) {
+ return d.getUTCHours();
+ } ], [ d3.time.format.utc("%I:%M"), function(d) {
+ return d.getUTCMinutes();
+ } ], [ d3.time.format.utc(":%S"), function(d) {
+ return d.getUTCSeconds();
+ } ], [ d3.time.format.utc(".%L"), function(d) {
+ return d.getUTCMilliseconds();
+ } ] ];
+ var d3_time_scaleUTCFormat = d3_time_scaleFormat(d3_time_scaleUTCFormats);
+ d3_time_scaleUTCMethods.year = function(extent, m) {
+ return d3_time_scaleLinear.domain(extent.map(d3_time_scaleUTCGetYear)).ticks(m).map(d3_time_scaleUTCSetYear);
+ };
+ d3.time.scale.utc = function() {
+ return d3_time_scale(d3.scale.linear(), d3_time_scaleUTCMethods, d3_time_scaleUTCFormat);
+ };
+})(); \ No newline at end of file
diff --git a/src/fauxton/assets/js/libs/jquery.js b/src/fauxton/assets/js/libs/jquery.js
new file mode 100644
index 000000000..3774ff986
--- /dev/null
+++ b/src/fauxton/assets/js/libs/jquery.js
@@ -0,0 +1,9404 @@
+/*!
+ * jQuery JavaScript Library v1.7.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Wed Mar 21 12:46:34 2012 -0700
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+ navigator = window.navigator,
+ location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // A simple way to check for HTML strings or ID strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Check if a string has a non-whitespace character in it
+ rnotwhite = /\S/,
+
+ // Used for trimming whitespace
+ trimLeft = /^\s+/,
+ trimRight = /\s+$/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+ // Useragent RegExp
+ rwebkit = /(webkit)[ \/]([\w.]+)/,
+ ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+ rmsie = /(msie) ([\w.]+)/,
+ rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+ // Matches dashed string for camelizing
+ rdashAlpha = /-([a-z]|[0-9])/ig,
+ rmsPrefix = /^-ms-/,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // Keep a UserAgent string for use with jQuery.browser
+ userAgent = navigator.userAgent,
+
+ // For matching the engine and version of the browser
+ browserMatch,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // The ready event handler
+ DOMContentLoaded,
+
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ push = Array.prototype.push,
+ slice = Array.prototype.slice,
+ trim = String.prototype.trim,
+ indexOf = Array.prototype.indexOf,
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), or $(undefined)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // The body element only exists once, optimize finding it
+ if ( selector === "body" && !context && document.body ) {
+ this.context = document;
+ this[0] = document.body;
+ this.selector = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ // Are we dealing with HTML string or an ID?
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = quickExpr.exec( selector );
+ }
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context ? context.ownerDocument || context : document );
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ ret = rsingleTag.exec( selector );
+
+ if ( ret ) {
+ if ( jQuery.isPlainObject( context ) ) {
+ selector = [ document.createElement( ret[1] ) ];
+ jQuery.fn.attr.call( selector, context, true );
+
+ } else {
+ selector = [ doc.createElement( ret[1] ) ];
+ }
+
+ } else {
+ ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+ selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $("#id")
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.7.2",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return slice.call( this, 0 );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = this.constructor();
+
+ if ( jQuery.isArray( elems ) ) {
+ push.apply( ret, elems );
+
+ } else {
+ jQuery.merge( ret, elems );
+ }
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Attach the listeners
+ jQuery.bindReady();
+
+ // Add the callback
+ readyList.add( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ),
+ "slice", slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+ // Either a released hold or an DOMready/load event and not yet ready
+ if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.fireWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger( "ready" ).off( "ready" );
+ }
+ }
+ },
+
+ bindReady: function() {
+ if ( readyList ) {
+ return;
+ }
+
+ readyList = jQuery.Callbacks( "once memory" );
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var toplevel = false;
+
+ try {
+ toplevel = window.frameElement == null;
+ } catch(e) {}
+
+ if ( document.documentElement.doScroll && toplevel ) {
+ doScrollCheck();
+ }
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !hasOwn.call(obj, "constructor") &&
+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ parseJSON: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+ var xml, tmp;
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0,
+ length = object.length,
+ isObj = length === undefined || jQuery.isFunction( object );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.apply( object[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( object[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return object;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: trim ?
+ function( text ) {
+ return text == null ?
+ "" :
+ trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( array, results ) {
+ var ret = results || [];
+
+ if ( array != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ var type = jQuery.type( array );
+
+ if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+ push.call( ret, array );
+ } else {
+ jQuery.merge( ret, array );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array, i ) {
+ var len;
+
+ if ( array ) {
+ if ( indexOf ) {
+ return indexOf.call( array, elem, i );
+ }
+
+ len = array.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in array && array[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var i = first.length,
+ j = 0;
+
+ if ( typeof second.length === "number" ) {
+ for ( var l = second.length; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [], retVal;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key, ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ if ( typeof context === "string" ) {
+ var tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ var args = slice.call( arguments, 2 ),
+ proxy = function() {
+ return fn.apply( context, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Mutifunctional method to get and set values to a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+ var exec,
+ bulk = key == null,
+ i = 0,
+ length = elems.length;
+
+ // Sets many values
+ if ( key && typeof key === "object" ) {
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+ }
+ chainable = 1;
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = pass === undefined && jQuery.isFunction( value );
+
+ if ( bulk ) {
+ // Bulk operations only iterate when executing function values
+ if ( exec ) {
+ exec = fn;
+ fn = function( elem, key, value ) {
+ return exec.call( jQuery( elem ), value );
+ };
+
+ // Otherwise they run against the entire set
+ } else {
+ fn.call( elems, value );
+ fn = null;
+ }
+ }
+
+ if ( fn ) {
+ for (; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+ }
+
+ chainable = 1;
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ },
+
+ // Use of jQuery.browser is frowned upon.
+ // More details: http://docs.jquery.com/Utilities/jQuery.browser
+ uaMatch: function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = rwebkit.exec( ua ) ||
+ ropera.exec( ua ) ||
+ rmsie.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ },
+
+ sub: function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+ },
+
+ browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+ jQuery.browser[ browserMatch.browser ] = true;
+ jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+ jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+ trimLeft = /^[\s\xA0]+/;
+ trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+ DOMContentLoaded = function() {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ };
+
+} else if ( document.attachEvent ) {
+ DOMContentLoaded = function() {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+ if ( jQuery.isReady ) {
+ return;
+ }
+
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch(e) {
+ setTimeout( doScrollCheck, 1 );
+ return;
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+ var object = flagsCache[ flags ] = {},
+ i, length;
+ flags = flags.split( /\s+/ );
+ for ( i = 0, length = flags.length; i < length; i++ ) {
+ object[ flags[i] ] = true;
+ }
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * flags: an optional list of space-separated flags that will change how
+ * the callback list behaves
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( flags ) {
+
+ // Convert flags from String-formatted to Object-formatted
+ // (we check in cache first)
+ flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+ var // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = [],
+ // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Add one or several callbacks to the list
+ add = function( args ) {
+ var i,
+ length,
+ elem,
+ type,
+ actual;
+ for ( i = 0, length = args.length; i < length; i++ ) {
+ elem = args[ i ];
+ type = jQuery.type( elem );
+ if ( type === "array" ) {
+ // Inspect recursively
+ add( elem );
+ } else if ( type === "function" ) {
+ // Add if not in unique mode and callback is not in
+ if ( !flags.unique || !self.has( elem ) ) {
+ list.push( elem );
+ }
+ }
+ }
+ },
+ // Fire callbacks
+ fire = function( context, args ) {
+ args = args || [];
+ memory = !flags.memory || [ context, args ];
+ fired = true;
+ firing = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
+ memory = true; // Mark as halted
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( !flags.once ) {
+ if ( stack && stack.length ) {
+ memory = stack.shift();
+ self.fireWith( memory[ 0 ], memory[ 1 ] );
+ }
+ } else if ( memory === true ) {
+ self.disable();
+ } else {
+ list = [];
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ var length = list.length;
+ add( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away, unless previous
+ // firing was halted (stopOnFalse)
+ } else if ( memory && memory !== true ) {
+ firingStart = length;
+ fire( memory[ 0 ], memory[ 1 ] );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ var args = arguments,
+ argIndex = 0,
+ argLength = args.length;
+ for ( ; argIndex < argLength ; argIndex++ ) {
+ for ( var i = 0; i < list.length; i++ ) {
+ if ( args[ argIndex ] === list[ i ] ) {
+ // Handle firingIndex and firingLength
+ if ( firing ) {
+ if ( i <= firingLength ) {
+ firingLength--;
+ if ( i <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ // Remove the element
+ list.splice( i--, 1 );
+ // If we have some unicity property then
+ // we only need to do this once
+ if ( flags.unique ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ if ( list ) {
+ var i = 0,
+ length = list.length;
+ for ( ; i < length; i++ ) {
+ if ( fn === list[ i ] ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory || memory === true ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( stack ) {
+ if ( firing ) {
+ if ( !flags.once ) {
+ stack.push( [ context, args ] );
+ }
+ } else if ( !( flags.once && memory ) ) {
+ fire( context, args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+
+
+
+
+var // Static reference to slice
+ sliceDeferred = [].slice;
+
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var doneList = jQuery.Callbacks( "once memory" ),
+ failList = jQuery.Callbacks( "once memory" ),
+ progressList = jQuery.Callbacks( "memory" ),
+ state = "pending",
+ lists = {
+ resolve: doneList,
+ reject: failList,
+ notify: progressList
+ },
+ promise = {
+ done: doneList.add,
+ fail: failList.add,
+ progress: progressList.add,
+
+ state: function() {
+ return state;
+ },
+
+ // Deprecated
+ isResolved: doneList.fired,
+ isRejected: failList.fired,
+
+ then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+ deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
+ return this;
+ },
+ always: function() {
+ deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
+ return this;
+ },
+ pipe: function( fnDone, fnFail, fnProgress ) {
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( {
+ done: [ fnDone, "resolve" ],
+ fail: [ fnFail, "reject" ],
+ progress: [ fnProgress, "notify" ]
+ }, function( handler, data ) {
+ var fn = data[ 0 ],
+ action = data[ 1 ],
+ returned;
+ if ( jQuery.isFunction( fn ) ) {
+ deferred[ handler ](function() {
+ returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ });
+ } else {
+ deferred[ handler ]( newDefer[ action ] );
+ }
+ });
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ if ( obj == null ) {
+ obj = promise;
+ } else {
+ for ( var key in promise ) {
+ obj[ key ] = promise[ key ];
+ }
+ }
+ return obj;
+ }
+ },
+ deferred = promise.promise({}),
+ key;
+
+ for ( key in lists ) {
+ deferred[ key ] = lists[ key ].fire;
+ deferred[ key + "With" ] = lists[ key ].fireWith;
+ }
+
+ // Handle state
+ deferred.done( function() {
+ state = "resolved";
+ }, failList.disable, progressList.lock ).fail( function() {
+ state = "rejected";
+ }, doneList.disable, progressList.lock );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( firstParam ) {
+ var args = sliceDeferred.call( arguments, 0 ),
+ i = 0,
+ length = args.length,
+ pValues = new Array( length ),
+ count = length,
+ pCount = length,
+ deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+ firstParam :
+ jQuery.Deferred(),
+ promise = deferred.promise();
+ function resolveFunc( i ) {
+ return function( value ) {
+ args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ if ( !( --count ) ) {
+ deferred.resolveWith( deferred, args );
+ }
+ };
+ }
+ function progressFunc( i ) {
+ return function( value ) {
+ pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ deferred.notifyWith( promise, pValues );
+ };
+ }
+ if ( length > 1 ) {
+ for ( ; i < length; i++ ) {
+ if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+ args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+ } else {
+ --count;
+ }
+ }
+ if ( !count ) {
+ deferred.resolveWith( deferred, args );
+ }
+ } else if ( deferred !== firstParam ) {
+ deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+ }
+ return promise;
+ }
+});
+
+
+
+
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ fragment,
+ tds,
+ events,
+ eventName,
+ i,
+ isSupported,
+ div = document.createElement( "div" ),
+ documentElement = document.documentElement;
+
+ // Preliminary tests
+ div.setAttribute("className", "t");
+ div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+ all = div.getElementsByTagName( "*" );
+ a = div.getElementsByTagName( "a" )[ 0 ];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return {};
+ }
+
+ // First batch of supports tests
+ select = document.createElement( "select" );
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName( "input" )[ 0 ];
+
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.55/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form(#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true,
+ pixelMargin: true
+ };
+
+ // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead
+ jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat");
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent( "onclick" );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute("type", "radio");
+ support.radioValue = input.value === "t";
+
+ input.setAttribute("checked", "checked");
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for ( i in {
+ submit: 1,
+ change: 1,
+ focusin: 1
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ fragment.removeChild( div );
+
+ // Null elements to avoid leaks in IE
+ fragment = select = opt = div = input = null;
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, outer, inner, table, td, offsetSupport,
+ marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight,
+ paddingMarginBorderVisibility, paddingMarginBorder,
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ conMarginTop = 1;
+ paddingMarginBorder = "padding:0;margin:0;border:";
+ positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;";
+ paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;";
+ style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;";
+ html = "<div " + style + "display:block;'><div style='" + paddingMarginBorder + "0;display:block;overflow:hidden;'></div></div>" +
+ "<table " + style + "' cellpadding='0' cellspacing='0'>" +
+ "<tr><td></td></tr></table>";
+
+ container = document.createElement("div");
+ container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "<table><tr><td style='" + paddingMarginBorder + "0;display:none'></td><td>t</td></tr></table>";
+ tds = div.getElementsByTagName( "td" );
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ if ( window.getComputedStyle ) {
+ div.innerHTML = "";
+ marginDiv = document.createElement( "div" );
+ marginDiv.style.width = "0";
+ marginDiv.style.marginRight = "0";
+ div.style.width = "2px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+ }
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.innerHTML = "";
+ div.style.width = div.style.padding = "1px";
+ div.style.border = 0;
+ div.style.overflow = "hidden";
+ div.style.display = "inline";
+ div.style.zoom = 1;
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "block";
+ div.style.overflow = "visible";
+ div.innerHTML = "<div style='width:5px;'></div>";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+ }
+
+ div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility;
+ div.innerHTML = html;
+
+ outer = div.firstChild;
+ inner = outer.firstChild;
+ td = outer.nextSibling.firstChild.firstChild;
+
+ offsetSupport = {
+ doesNotAddBorder: ( inner.offsetTop !== 5 ),
+ doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
+ };
+
+ inner.style.position = "fixed";
+ inner.style.top = "20px";
+
+ // safari subtracts parent border width here which is 5px
+ offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
+ inner.style.position = inner.style.top = "";
+
+ outer.style.overflow = "hidden";
+ outer.style.position = "relative";
+
+ offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
+ offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
+
+ if ( window.getComputedStyle ) {
+ div.style.marginTop = "1%";
+ support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%";
+ }
+
+ if ( typeof container.style.zoom !== "undefined" ) {
+ container.style.zoom = 1;
+ }
+
+ body.removeChild( container );
+ marginDiv = div = container = null;
+
+ jQuery.extend( support, offsetSupport );
+ });
+
+ return support;
+})();
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ // Please use with caution
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var privateCache, thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
+ isEvents = name === "events";
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = ++jQuery.uuid;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ privateCache = thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Users should not attempt to inspect the internal events object using jQuery.data,
+ // it is undocumented and subject to change. But does anyone listen? No.
+ if ( isEvents && !thisCache[ name ] ) {
+ return privateCache.events;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ // Reference to internal data cache key
+ internalKey = jQuery.expando,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+
+ // See jQuery.data for more information
+ id = isNode ? elem[ internalKey ] : internalKey;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split( " " );
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject(cache[ id ]) ) {
+ return;
+ }
+ }
+
+ // Browsers that fail expando deletion also refuse to delete expandos on
+ // the window, but it will allow it on all other JS objects; other browsers
+ // don't care
+ // Ensure that `cache` is not a window object #10080
+ if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+ delete cache[ id ];
+ } else {
+ cache[ id ] = null;
+ }
+
+ // We destroyed the cache and need to eliminate the expando on the node to avoid
+ // false lookups in the cache for entries that no longer exist
+ if ( isNode ) {
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ internalKey ];
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+ } else {
+ elem[ internalKey ] = null;
+ }
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ if ( elem.nodeName ) {
+ var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ if ( match ) {
+ return !(match === true || elem.getAttribute("classid") !== match);
+ }
+ }
+
+ return true;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, part, attr, name, l,
+ elem = this[0],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attr = elem.attributes;
+ for ( l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split( ".", 2 );
+ parts[1] = parts[1] ? "." + parts[1] : "";
+ part = parts[1] + "!";
+
+ return jQuery.access( this, function( value ) {
+
+ if ( value === undefined ) {
+ data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && elem ) {
+ data = jQuery.data( elem, key );
+ data = dataAttr( elem, key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ }
+
+ parts[1] = value;
+ this.each(function() {
+ var self = jQuery( this );
+
+ self.triggerHandler( "setData" + part, parts );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + part, parts );
+ });
+ }, null, value, arguments.length > 1, null, false );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ jQuery.isNumeric( data ) ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ for ( var name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+ var deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ defer = jQuery._data( elem, deferDataKey );
+ if ( defer &&
+ ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
+ ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
+ // Give room for hard-coded callbacks to fire first
+ // and eventually mark/queue something else on the element
+ setTimeout( function() {
+ if ( !jQuery._data( elem, queueDataKey ) &&
+ !jQuery._data( elem, markDataKey ) ) {
+ jQuery.removeData( elem, deferDataKey, true );
+ defer.fire();
+ }
+ }, 0 );
+ }
+}
+
+jQuery.extend({
+
+ _mark: function( elem, type ) {
+ if ( elem ) {
+ type = ( type || "fx" ) + "mark";
+ jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
+ }
+ },
+
+ _unmark: function( force, elem, type ) {
+ if ( force !== true ) {
+ type = elem;
+ elem = force;
+ force = false;
+ }
+ if ( elem ) {
+ type = type || "fx";
+ var key = type + "mark",
+ count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
+ if ( count ) {
+ jQuery._data( elem, key, count );
+ } else {
+ jQuery.removeData( elem, key, true );
+ handleQueueMarkDefer( elem, type, "mark" );
+ }
+ }
+ },
+
+ queue: function( elem, type, data ) {
+ var q;
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ q = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !q || jQuery.isArray(data) ) {
+ q = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ q.push( data );
+ }
+ }
+ return q || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ fn = queue.shift(),
+ hooks = {};
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ jQuery._data( elem, type + ".run", hooks );
+ fn.call( elem, function() {
+ jQuery.dequeue( elem, type );
+ }, hooks );
+ }
+
+ if ( !queue.length ) {
+ jQuery.removeData( elem, type + "queue " + type + ".run", true );
+ handleQueueMarkDefer( elem, type, "queue" );
+ }
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, object ) {
+ if ( typeof type !== "string" ) {
+ object = type;
+ type = undefined;
+ }
+ type = type || "fx";
+ var defer = jQuery.Deferred(),
+ elements = this,
+ i = elements.length,
+ count = 1,
+ deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ tmp;
+ function resolve() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ }
+ while( i-- ) {
+ if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+ ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+ jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+ jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
+ count++;
+ tmp.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( object );
+ }
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+ rspace = /\s+/,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea)?$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute,
+ nodeHook, boolHook, fixSpecified;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classNames, i, l, elem, className, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( (value && typeof value === "string") || value === undefined ) {
+ classNames = ( value || "" ).split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 && elem.className ) {
+ if ( value ) {
+ className = (" " + elem.className + " ").replace( rclass, " " );
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ className = className.replace(" " + classNames[ c ] + " ", " ");
+ }
+ elem.className = jQuery.trim( className );
+
+ } else {
+ elem.className = "";
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space seperated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var self = jQuery(this), val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, i, max, option,
+ index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ i = one ? index : 0;
+ max = one ? index + 1 : options.length;
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ attrFn: {
+ val: true,
+ css: true,
+ html: true,
+ text: true,
+ data: true,
+ width: true,
+ height: true,
+ offset: true
+ },
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && name in jQuery.attrFn ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, "" + value );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, l, isBool,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+ attrNames = value.toLowerCase().split( rspace );
+ l = attrNames.length;
+
+ for ( ; i < l; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+ isBool = rboolean.test( name );
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ // Do not do this for boolean attributes (see #10870)
+ if ( !isBool ) {
+ jQuery.attr( elem, name, "" );
+ }
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( isBool && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ 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( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
+jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true,
+ coords: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
+ ret.nodeValue :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.nodeValue = value + "" );
+ }
+ };
+
+ // Apply the nodeHook to tabindex
+ jQuery.attrHooks.tabindex.set = nodeHook.set;
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = "" + value );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+
+
+
+
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
+ rhoverHack = /(?:^|\s)hover(\.\S+)?\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
+ quickParse = function( selector ) {
+ var quick = rquickIs.exec( selector );
+ if ( quick ) {
+ // 0 1 2 3
+ // [ _, tag, id, class ]
+ quick[1] = ( quick[1] || "" ).toLowerCase();
+ quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
+ }
+ return quick;
+ },
+ quickIs = function( elem, m ) {
+ var attrs = elem.attributes || {};
+ return (
+ (!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
+ (!m[2] || (attrs.id || {}).value === m[2]) &&
+ (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
+ );
+ },
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, quick, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ quick: selector && quickParse( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+ t, tns, type, origType, namespaces, origCount,
+ j, events, special, handle, eventType, handleObj;
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ handle = elemData.handle;
+ if ( handle ) {
+ handle.elem = null;
+ }
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, [ "events", "handle" ], true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var type = event.type || event,
+ namespaces = [],
+ cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ old = null;
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old && old === elem.ownerDocument ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = [].slice.call( arguments, 0 ),
+ run_all = !event.exclusive && !event.namespace,
+ special = jQuery.event.special[ event.type ] || {},
+ handlerQueue = [],
+ i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !(event.button && event.type === "click") ) {
+
+ // Pregenerate a single jQuery object for reuse with .is()
+ jqcur = jQuery(this);
+ jqcur.context = this.ownerDocument || this;
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+ // Don't process events on disabled elements (#6911, #8165)
+ if ( cur.disabled !== true ) {
+ selMatch = {};
+ matches = [];
+ jqcur[0] = cur;
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = (
+ handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
+ );
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
+ if ( event.metaKey === undefined ) {
+ event.metaKey = event.ctrlKey;
+ }
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady
+ },
+
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ if ( elem.detachEvent ) {
+ elem.detachEvent( "on" + type, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector,
+ ret;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !form._submit_attached ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ event._submit_bubble = true;
+ });
+ form._submit_attached = true;
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ postDispatch: function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( event._submit_bubble ) {
+ delete event._submit_bubble;
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ }
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ jQuery.event.simulate( "change", this, event, true );
+ }
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ elem._change_attached = true;
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) { // && selector != null
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ var handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( var type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+jQuery.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( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( jQuery.attrFn ) {
+ jQuery.attrFn[ name ] = true;
+ }
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ expando = "sizcache" + (Math.random() + '').replace('.', ''),
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true,
+ rBackslash = /\\/g,
+ rReturn = /\r\n/g,
+ rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+
+ var origContext = context;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var m, set, checkSet, extra, ret, cur, pop, i,
+ prune = true,
+ contextXML = Sizzle.isXML( context ),
+ parts = [],
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ do {
+ chunker.exec( "" );
+ m = chunker.exec( soFar );
+
+ if ( m ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+ } while ( m );
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context, seed );
+
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set, seed );
+ }
+ }
+
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+ ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set )[0] :
+ ret.set[0];
+ }
+
+ if ( context ) {
+ ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+ set = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set ) :
+ ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray( set );
+
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ cur = parts.pop();
+ pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+
+ } else if ( context && context.nodeType === 1 ) {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+
+ } else {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+ return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+ return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+ var set, i, len, match, type, left;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+ type = Expr.order[i];
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ left = match[1];
+ match.splice( 1, 1 );
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace( rBackslash, "" );
+ set = Expr.find[ type ]( match, context, isXML );
+
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( "*" ) :
+ [];
+ }
+
+ return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+ var match, anyFound,
+ type, found, item, filter, left,
+ i, pass,
+ old = expr,
+ result = [],
+ curLoop = set,
+ isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+ while ( expr && set.length ) {
+ for ( type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ filter = Expr.filter[ type ];
+ left = match[1];
+
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ pass = not ^ found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+
+ } else {
+ curLoop[i] = false;
+ }
+
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Utility function for retreiving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+ var i, node,
+ nodeType = elem.nodeType,
+ ret = "";
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent || innerText for elements
+ if ( typeof elem.textContent === 'string' ) {
+ return elem.textContent;
+ } else if ( typeof elem.innerText === 'string' ) {
+ // Replace IE's carriage returns
+ return elem.innerText.replace( rReturn, '' );
+ } else {
+ // Traverse it's children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( i = 0; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ if ( node.nodeType !== 8 ) {
+ ret += getText( node );
+ }
+ }
+ }
+ return ret;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+
+ leftMatch: {},
+
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+
+ attrHandle: {
+ href: function( elem ) {
+ return elem.getAttribute( "href" );
+ },
+ type: function( elem ) {
+ return elem.getAttribute( "type" );
+ }
+ },
+
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !rNonWord.test( part ),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+
+ ">": function( checkSet, part ) {
+ var elem,
+ isPartStr = typeof part === "string",
+ i = 0,
+ l = checkSet.length;
+
+ if ( isPartStr && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+
+ } else {
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+
+ "": function(checkSet, part, isXML){
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+ },
+
+ "~": function( checkSet, part, isXML ) {
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+ }
+ },
+
+ find: {
+ ID: function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ },
+
+ NAME: function( match, context ) {
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [],
+ results = context.getElementsByName( match[1] );
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+
+ TAG: function( match, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( match[1] );
+ }
+ }
+ },
+ preFilter: {
+ CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+ match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ ID: function( match ) {
+ return match[1].replace( rBackslash, "" );
+ },
+
+ TAG: function( match, curLoop ) {
+ return match[1].replace( rBackslash, "" ).toLowerCase();
+ },
+
+ CHILD: function( match ) {
+ if ( match[1] === "nth" ) {
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ match[2] = match[2].replace(/^\+|\s*/g, '');
+
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+ else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+
+ ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+ var name = match[1] = match[1].replace( rBackslash, "" );
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ // Handle if an un-quoted value was used
+ match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+
+ PSEUDO: function( match, curLoop, inplace, result, not ) {
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+
+ return false;
+ }
+
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+
+ POS: function( match ) {
+ match.unshift( true );
+
+ return match;
+ }
+ },
+
+ filters: {
+ enabled: function( elem ) {
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+
+ disabled: function( elem ) {
+ return elem.disabled === true;
+ },
+
+ checked: function( elem ) {
+ return elem.checked === true;
+ },
+
+ selected: function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ parent: function( elem ) {
+ return !!elem.firstChild;
+ },
+
+ empty: function( elem ) {
+ return !elem.firstChild;
+ },
+
+ has: function( elem, i, match ) {
+ return !!Sizzle( match[3], elem ).length;
+ },
+
+ header: function( elem ) {
+ return (/h\d/i).test( elem.nodeName );
+ },
+
+ text: function( elem ) {
+ var attr = elem.getAttribute( "type" ), type = elem.type;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+ },
+
+ radio: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+ },
+
+ checkbox: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+ },
+
+ file: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+ },
+
+ password: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+ },
+
+ submit: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "submit" === elem.type;
+ },
+
+ image: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+ },
+
+ reset: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "reset" === elem.type;
+ },
+
+ button: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && "button" === elem.type || name === "button";
+ },
+
+ input: function( elem ) {
+ return (/input|select|textarea|button/i).test( elem.nodeName );
+ },
+
+ focus: function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ }
+ },
+ setFilters: {
+ first: function( elem, i ) {
+ return i === 0;
+ },
+
+ last: function( elem, i, match, array ) {
+ return i === array.length - 1;
+ },
+
+ even: function( elem, i ) {
+ return i % 2 === 0;
+ },
+
+ odd: function( elem, i ) {
+ return i % 2 === 1;
+ },
+
+ lt: function( elem, i, match ) {
+ return i < match[3] - 0;
+ },
+
+ gt: function( elem, i, match ) {
+ return i > match[3] - 0;
+ },
+
+ nth: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ },
+
+ eq: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function( elem, match, i, array ) {
+ var name = match[1],
+ filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var j = 0, l = not.length; j < l; j++ ) {
+ if ( not[j] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ } else {
+ Sizzle.error( name );
+ }
+ },
+
+ CHILD: function( elem, match ) {
+ var first, last,
+ doneName, parent, cache,
+ count, diff,
+ type = match[1],
+ node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ /* falls through */
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ case "nth":
+ first = match[2];
+ last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ doneName = match[0];
+ parent = elem.parentNode;
+
+ if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
+ count = 0;
+
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+
+ parent[ expando ] = doneName;
+ }
+
+ diff = elem.nodeIndex - last;
+
+ if ( first === 0 ) {
+ return diff === 0;
+
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+
+ ID: function( elem, match ) {
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+
+ TAG: function( elem, match ) {
+ return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
+ },
+
+ CLASS: function( elem, match ) {
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+
+ ATTR: function( elem, match ) {
+ var name = match[1],
+ result = Sizzle.attr ?
+ Sizzle.attr( elem, name ) :
+ Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ !type && Sizzle.attr ?
+ result != null :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+
+ POS: function( elem, match, i, array ) {
+ var name = match[2],
+ filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS,
+ fescape = function(all, num){
+ return "\\" + (num - 0 + 1);
+ };
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+// Expose origPOS
+// "global" as in regardless of relation to brackets/parens
+Expr.match.globalPOS = origPOS;
+
+var makeArray = function( array, results ) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+ makeArray = function( array, results ) {
+ var i = 0,
+ ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+
+ } else {
+ for ( ; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+ };
+
+} else {
+ sortOrder = function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+ siblingCheck = function( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date()).getTime(),
+ root = document.documentElement;
+
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+
+ return m ?
+ m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+ [m] :
+ undefined :
+ [];
+ }
+ };
+
+ Expr.filter.ID = function( elem, match ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+
+ // release memory in IE
+ root = form = null;
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function( match, context ) {
+ var results = context.getElementsByTagName( match[1] );
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "<a href='#'></a>";
+
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+
+ Expr.attrHandle.href = function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ };
+ }
+
+ // release memory in IE
+ div = null;
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle,
+ div = document.createElement("div"),
+ id = "__sizzle__";
+
+ div.innerHTML = "<p class='TEST'></p>";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function( query, context, extra, seed ) {
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && !Sizzle.isXML(context) ) {
+ // See if we find a selector to speed up
+ var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+ if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+ // Speed-up: Sizzle("TAG")
+ if ( match[1] ) {
+ return makeArray( context.getElementsByTagName( query ), extra );
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+ return makeArray( context.getElementsByClassName( match[2] ), extra );
+ }
+ }
+
+ if ( context.nodeType === 9 ) {
+ // Speed-up: Sizzle("body")
+ // The body element only exists once, optimize finding it
+ if ( query === "body" && context.body ) {
+ return makeArray( [ context.body ], extra );
+
+ // Speed-up: Sizzle("#ID")
+ } else if ( match && match[3] ) {
+ var elem = context.getElementById( match[3] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id === match[3] ) {
+ return makeArray( [ elem ], extra );
+ }
+
+ } else {
+ return makeArray( [], extra );
+ }
+ }
+
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(qsaError) {}
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ var oldContext = context,
+ old = context.getAttribute( "id" ),
+ nid = old || id,
+ hasParent = context.parentNode,
+ relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+ if ( !old ) {
+ context.setAttribute( "id", nid );
+ } else {
+ nid = nid.replace( /'/g, "\\$&" );
+ }
+ if ( relativeHierarchySelector && hasParent ) {
+ context = context.parentNode;
+ }
+
+ try {
+ if ( !relativeHierarchySelector || hasParent ) {
+ return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+ }
+
+ } catch(pseudoError) {
+ } finally {
+ if ( !old ) {
+ oldContext.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ // release memory in IE
+ div = null;
+ })();
+}
+
+(function(){
+ var html = document.documentElement,
+ matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+ if ( matches ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9 fails this)
+ var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+ pseudoWorks = false;
+
+ try {
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( document.documentElement, "[test!='']:sizzle" );
+
+ } catch( pseudoError ) {
+ pseudoWorks = true;
+ }
+
+ Sizzle.matchesSelector = function( node, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+ if ( !Sizzle.isXML( node ) ) {
+ try {
+ if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+ var ret = matches.call( node, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || !disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9, so check for that
+ node.document && node.document.nodeType !== 11 ) {
+ return ret;
+ }
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle(expr, null, null, [node]).length > 0;
+ };
+ }
+})();
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function( match, context, isXML ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ // release memory in IE
+ div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+if ( document.documentElement.contains ) {
+ Sizzle.contains = function( a, b ) {
+ return a !== b && (a.contains ? a.contains(b) : true);
+ };
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+ Sizzle.contains = function( a, b ) {
+ return !!(a.compareDocumentPosition(b) & 16);
+ };
+
+} else {
+ Sizzle.contains = function() {
+ return false;
+ };
+}
+
+Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context, seed ) {
+ var match,
+ tmpSet = [],
+ later = "",
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet, seed );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+Sizzle.selectors.attrMap = {};
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+ // Note: This RegExp should be improved, or likely pulled from Sizzle
+ rmultiselector = /,/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ slice = Array.prototype.slice,
+ POS = jQuery.expr.match.globalPOS,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var self = this,
+ i, l;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ var ret = this.pushStack( "", "find", selector ),
+ length, n, r;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target );
+ return this.filter(function() {
+ for ( var i = 0, l = targets.length; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ POS.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var ret = [], i, l, cur = this[0];
+
+ // Array (deprecated as of jQuery 1.7)
+ if ( jQuery.isArray( selectors ) ) {
+ var level = 1;
+
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ for ( i = 0; i < selectors.length; i++ ) {
+
+ if ( jQuery( cur ).is( selectors[ i ] ) ) {
+ ret.push({ selector: selectors[ i ], elem: cur, level: level });
+ }
+ }
+
+ cur = cur.parentNode;
+ level++;
+ }
+
+ return ret;
+ }
+
+ // String
+ var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+
+ } else {
+ cur = cur.parentNode;
+ if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+ break;
+ }
+ }
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return jQuery.nth( elem, 2, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return jQuery.nth( elem, 2, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.makeArray( elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, slice.call( arguments ).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function( cur, result, dir, elem ) {
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] ) {
+ if ( cur.nodeType === 1 && ++num === result ) {
+ break;
+ }
+ }
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+}
+
+
+
+
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+ rtagName = /<([\w:]+)/,
+ rtbody = /<tbody/i,
+ rhtml = /<|&#?\w+;/,
+ rnoInnerhtml = /<(?:script|style)/i,
+ rnocache = /<(?:script|object|embed|option|style)/i,
+ rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /\/(java|ecma)script/i,
+ rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+ wrapMap = {
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+ legend: [ 1, "<fieldset>", "</fieldset>" ],
+ thead: [ 1, "<table>", "</table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ area: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ },
+ safeFragment = createSafeFragment( document );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+ text: function( value ) {
+ return jQuery.access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+ }, null, value, arguments.length );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function(i) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ } else if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ set.push.apply( set, this.toArray() );
+ return this.pushStack( set, "before", arguments );
+ }
+ },
+
+ after: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ } else if ( arguments.length ) {
+ var set = this.pushStack( this, "after", arguments );
+ set.push.apply( set, jQuery.clean(arguments) );
+ return set;
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return jQuery.access( this, function( value ) {
+ var elem = this[0] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined ) {
+ return elem.nodeType === 1 ?
+ elem.innerHTML.replace( rinlinejQuery, "" ) :
+ null;
+ }
+
+
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+ !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+ try {
+ for (; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ elem = this[i] || {};
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function( value ) {
+ if ( this[0] && this[0].parentNode ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery( value ).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling,
+ parent = this.parentNode;
+
+ jQuery( this ).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ } else {
+ return this.length ?
+ this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+ this;
+ }
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+ var results, first, fragment, parent,
+ value = args[0],
+ scripts = [];
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback, true );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call(this, i, table ? self.html() : undefined);
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ parent = value && value.parentNode;
+
+ // If we're in a fragment, just use that instead of building a new one
+ if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+ results = { fragment: parent };
+
+ } else {
+ results = jQuery.buildFragment( args, this, scripts );
+ }
+
+ fragment = results.fragment;
+
+ if ( fragment.childNodes.length === 1 ) {
+ first = fragment = fragment.firstChild;
+ } else {
+ first = fragment.firstChild;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+ callback.call(
+ table ?
+ root(this[i], first) :
+ this[i],
+ // Make sure that we do not leak memory by inadvertently discarding
+ // the original fragment (which might have attached data) instead of
+ // using it; in addition, use the original fragment object for the last
+ // item instead of first because it can end up being emptied incorrectly
+ // in certain situations (Bug #8070).
+ // Fragments from the fragment cache must always be cloned and never used
+ // in place.
+ results.cacheable || ( l > 1 && i < lastIndex ) ?
+ jQuery.clone( fragment, true, true ) :
+ fragment
+ );
+ }
+ }
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, function( i, elem ) {
+ if ( elem.src ) {
+ jQuery.ajax({
+ type: "GET",
+ global: false,
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+ } else {
+ jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ });
+ }
+ }
+
+ return this;
+ }
+});
+
+function root( elem, cur ) {
+ return jQuery.nodeName(elem, "table") ?
+ (elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+ elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var type, i, l,
+ oldData = jQuery._data( src ),
+ curData = jQuery._data( dest, oldData ),
+ events = oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+
+ // make the cloned public data object a copy from the original
+ if ( curData.data ) {
+ curData.data = jQuery.extend( {}, curData.data );
+ }
+}
+
+function cloneFixAttributes( src, dest ) {
+ var nodeName;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // clearAttributes removes the attributes, which we don't want,
+ // but also removes the attachEvent events, which we *do* want
+ if ( dest.clearAttributes ) {
+ dest.clearAttributes();
+ }
+
+ // mergeAttributes, in contrast, only merges back on the
+ // original attributes, not the events
+ if ( dest.mergeAttributes ) {
+ dest.mergeAttributes( src );
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ // IE6-8 fail to clone children inside object elements that use
+ // the proprietary classid attribute value (rather than the type
+ // attribute) to identify the type of content to display
+ if ( nodeName === "object" ) {
+ dest.outerHTML = src.outerHTML;
+
+ } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+ if ( src.checked ) {
+ dest.defaultChecked = dest.checked = src.checked;
+ }
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+
+ // IE blanks contents when cloning scripts
+ } else if ( nodeName === "script" && dest.text !== src.text ) {
+ dest.text = src.text;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+
+ // Clear flags for bubbling special change/submit events, they must
+ // be reattached when the newly cloned events are first activated
+ dest.removeAttribute( "_submit_attached" );
+ dest.removeAttribute( "_change_attached" );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+ var fragment, cacheable, cacheresults, doc,
+ first = args[ 0 ];
+
+ // nodes may contain either an explicit document object,
+ // a jQuery collection or context object.
+ // If nodes[0] contains a valid object to assign to doc
+ if ( nodes && nodes[0] ) {
+ doc = nodes[0].ownerDocument || nodes[0];
+ }
+
+ // Ensure that an attr object doesn't incorrectly stand in as a document object
+ // Chrome and Firefox seem to allow this to occur and will throw exception
+ // Fixes #8950
+ if ( !doc.createDocumentFragment ) {
+ doc = document;
+ }
+
+ // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+ if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
+ first.charAt(0) === "<" && !rnocache.test( first ) &&
+ (jQuery.support.checkClone || !rchecked.test( first )) &&
+ (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+ cacheable = true;
+
+ cacheresults = jQuery.fragments[ first ];
+ if ( cacheresults && cacheresults !== 1 ) {
+ fragment = cacheresults;
+ }
+ }
+
+ if ( !fragment ) {
+ fragment = doc.createDocumentFragment();
+ jQuery.clean( args, doc, fragment, scripts );
+ }
+
+ if ( cacheable ) {
+ jQuery.fragments[ first ] = cacheresults ? fragment : 1;
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = [],
+ insert = jQuery( selector ),
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+
+ } else {
+ for ( var i = 0, l = insert.length; i < l; i++ ) {
+ var elems = ( i > 0 ? this.clone(true) : this ).get();
+ jQuery( insert[i] )[ original ]( elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+function getAll( elem ) {
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ return elem.getElementsByTagName( "*" );
+
+ } else if ( typeof elem.querySelectorAll !== "undefined" ) {
+ return elem.querySelectorAll( "*" );
+
+ } else {
+ return [];
+ }
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( elem.type === "checkbox" || elem.type === "radio" ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+ var nodeName = ( elem.nodeName || "" ).toLowerCase();
+ if ( nodeName === "input" ) {
+ fixDefaultChecked( elem );
+ // Skip scripts, get other children
+ } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) {
+ jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+ }
+}
+
+// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js
+function shimCloneNode( elem ) {
+ var div = document.createElement( "div" );
+ safeFragment.appendChild( div );
+
+ div.innerHTML = elem.outerHTML;
+ return div.firstChild;
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var srcElements,
+ destElements,
+ i,
+ // IE<=8 does not properly clone detached, unknown element nodes
+ clone = jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ?
+ elem.cloneNode( true ) :
+ shimCloneNode( elem );
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+ // IE copies events bound via attachEvent when using cloneNode.
+ // Calling detachEvent on the clone will also remove the events
+ // from the original. In order to get around this, we use some
+ // proprietary methods to clear the events. Thanks to MooTools
+ // guys for this hotness.
+
+ cloneFixAttributes( elem, clone );
+
+ // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ // Weird iteration because IE will replace the length property
+ // with an element if you are cloning the body and one of the
+ // elements on the page has a name or id of "length"
+ for ( i = 0; srcElements[i]; ++i ) {
+ // Ensure that the destination node is not null; Fixes #9587
+ if ( destElements[i] ) {
+ cloneFixAttributes( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ cloneCopyEvent( elem, clone );
+
+ if ( deepDataAndEvents ) {
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneCopyEvent( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ srcElements = destElements = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ clean: function( elems, context, fragment, scripts ) {
+ var checkScriptType, script, j,
+ ret = [];
+
+ context = context || document;
+
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if ( typeof context.createElement === "undefined" ) {
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+ }
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" ) {
+ if ( !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+ } else {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),
+ wrap = wrapMap[ tag ] || wrapMap._default,
+ depth = wrap[0],
+ div = context.createElement("div"),
+ safeChildNodes = safeFragment.childNodes,
+ remove;
+
+ // Append wrapper element to unknown element safe doc fragment
+ if ( context === document ) {
+ // Use the fragment we've already created for this document
+ safeFragment.appendChild( div );
+ } else {
+ // Use a fragment created with the owner document
+ createSafeFragment( context ).appendChild( div );
+ }
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var hasBody = rtbody.test(elem),
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+
+ // Clear elements from DocumentFragment (safeFragment or otherwise)
+ // to avoid hoarding elements. Fixes #11356
+ if ( div ) {
+ div.parentNode.removeChild( div );
+
+ // Guard against -1 index exceptions in FF3.6
+ if ( safeChildNodes.length > 0 ) {
+ remove = safeChildNodes[ safeChildNodes.length - 1 ];
+
+ if ( remove && remove.parentNode ) {
+ remove.parentNode.removeChild( remove );
+ }
+ }
+ }
+ }
+ }
+
+ // Resets defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ var len;
+ if ( !jQuery.support.appendChecked ) {
+ if ( elem[0] && typeof (len = elem.length) === "number" ) {
+ for ( j = 0; j < len; j++ ) {
+ findInputs( elem[j] );
+ }
+ } else {
+ findInputs( elem );
+ }
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ ret = jQuery.merge( ret, elem );
+ }
+ }
+
+ if ( fragment ) {
+ checkScriptType = function( elem ) {
+ return !elem.type || rscriptType.test( elem.type );
+ };
+ for ( i = 0; ret[i]; i++ ) {
+ script = ret[i];
+ if ( scripts && jQuery.nodeName( script, "script" ) && (!script.type || rscriptType.test( script.type )) ) {
+ scripts.push( script.parentNode ? script.parentNode.removeChild( script ) : script );
+
+ } else {
+ if ( script.nodeType === 1 ) {
+ var jsTags = jQuery.grep( script.getElementsByTagName( "script" ), checkScriptType );
+
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ }
+ fragment.appendChild( script );
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems ) {
+ var data, id,
+ cache = jQuery.cache,
+ special = jQuery.event.special,
+ deleteExpando = jQuery.support.deleteExpando;
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+ continue;
+ }
+
+ id = elem[ jQuery.expando ];
+
+ if ( id ) {
+ data = cache[ id ];
+
+ if ( data && data.events ) {
+ for ( var type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+
+ // Null the DOM reference to avoid IE6/7/8 leak (#7054)
+ if ( data.handle ) {
+ data.handle.elem = null;
+ }
+ }
+
+ if ( deleteExpando ) {
+ delete elem[ jQuery.expando ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ }
+
+ delete cache[ id ];
+ }
+ }
+ }
+});
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ // fixed for IE9, see #8346
+ rupper = /([A-Z]|^ms)/g,
+ rnum = /^[\-+]?(?:\d*\.)?\d+$/i,
+ rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,
+ rrelNum = /^([\-+])=([\-+.\de]+)/,
+ rmargin = /^margin/,
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+
+ // order is important!
+ cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+
+ curCSS,
+
+ getComputedStyle,
+ currentStyle;
+
+jQuery.fn.css = function( name, value ) {
+ return jQuery.access( this, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+};
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+
+ } else {
+ return elem.style.opacity;
+ }
+ }
+ }
+ },
+
+ // Exclude the following css properties to add px
+ cssNumber: {
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, origName = jQuery.camelCase( name ),
+ style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+ name = jQuery.cssProps[ origName ] || origName;
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra ) {
+ var ret, hooks;
+
+ // Make sure that we're working with the right name
+ name = jQuery.camelCase( name );
+ hooks = jQuery.cssHooks[ name ];
+ name = jQuery.cssProps[ name ] || name;
+
+ // cssFloat needs a special treatment
+ if ( name === "cssFloat" ) {
+ name = "float";
+ }
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+ return ret;
+
+ // Otherwise, if a way to get the computed value exists, use that
+ } else if ( curCSS ) {
+ return curCSS( elem, name );
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {},
+ ret, name;
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+});
+
+// DEPRECATED in 1.3, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+ getComputedStyle = function( elem, name ) {
+ var ret, defaultView, computedStyle, width,
+ style = elem.style;
+
+ name = name.replace( rupper, "-$1" ).toLowerCase();
+
+ if ( (defaultView = elem.ownerDocument.defaultView) &&
+ (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+
+ ret = computedStyle.getPropertyValue( name );
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // WebKit uses "computed value (percentage if specified)" instead of "used value" for margins
+ // which is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( !jQuery.support.pixelMargin && computedStyle && rmargin.test( name ) && rnumnonpx.test( ret ) ) {
+ width = style.width;
+ style.width = ret;
+ ret = computedStyle.width;
+ style.width = width;
+ }
+
+ return ret;
+ };
+}
+
+if ( document.documentElement.currentStyle ) {
+ currentStyle = function( elem, name ) {
+ var left, rsLeft, uncomputed,
+ ret = elem.currentStyle && elem.currentStyle[ name ],
+ style = elem.style;
+
+ // Avoid setting ret to empty string here
+ // so we don't default to auto
+ if ( ret == null && style && (uncomputed = style[ name ]) ) {
+ ret = uncomputed;
+ }
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( rnumnonpx.test( ret ) ) {
+
+ // Remember the original values
+ left = style.left;
+ rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : ret;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property
+ var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ i = name === "width" ? 1 : 0,
+ len = 4;
+
+ if ( val > 0 ) {
+ if ( extra !== "border" ) {
+ for ( ; i < len; i += 2 ) {
+ if ( !extra ) {
+ val -= parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ }
+ if ( extra === "margin" ) {
+ val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ] ) ) || 0;
+ } else {
+ val -= parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ }
+ }
+
+ return val + "px";
+ }
+
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+
+ // Add padding, border, margin
+ if ( extra ) {
+ for ( ; i < len; i += 2 ) {
+ val += parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ if ( extra !== "padding" ) {
+ val += parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ if ( extra === "margin" ) {
+ val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ]) ) || 0;
+ }
+ }
+ }
+
+ return val + "px";
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ if ( elem.offsetWidth !== 0 ) {
+ return getWidthOrHeight( elem, name, extra );
+ } else {
+ return jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ });
+ }
+ }
+ },
+
+ set: function( elem, value ) {
+ return rnum.test( value ) ?
+ value + "px" :
+ value;
+ }
+ };
+});
+
+if ( !jQuery.support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+ ( parseFloat( RegExp.$1 ) / 100 ) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+ if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
+
+ // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+ // if "filter:" is present at all, clearType is disabled, we want to avoid this
+ // style.removeAttribute is IE Only, but so apparently is this code path...
+ style.removeAttribute( "filter" );
+
+ // if there there is no filter style applied in a css rule, we are done
+ if ( currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+}
+
+jQuery(function() {
+ // This hook cannot be added until DOM ready because the support test
+ // for it is not run until after DOM ready
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ return jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ return curCSS( elem, "margin-right" );
+ } else {
+ return elem.style.marginRight;
+ }
+ });
+ }
+ };
+ }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ var width = elem.offsetWidth,
+ height = elem.offsetHeight;
+
+ return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i,
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ],
+ expanded = {};
+
+ for ( i = 0; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+});
+
+
+
+
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rhash = /#.*$/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rquery = /\?/,
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+ rselectTextarea = /^(?:select|textarea)/i,
+ rspacesAjax = /\s+/,
+ rts = /([?&])_=[^&]*/,
+ rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Document location
+ ajaxLocation,
+
+ // Document location segments
+ ajaxLocParts,
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+ ajaxLocation = location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ if ( jQuery.isFunction( func ) ) {
+ var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+ i = 0,
+ length = dataTypes.length,
+ dataType,
+ list,
+ placeBefore;
+
+ // For each dataType in the dataTypeExpression
+ for ( ; i < length; i++ ) {
+ dataType = dataTypes[ i ];
+ // We control if we're asked to add before
+ // any existing element
+ placeBefore = /^\+/.test( dataType );
+ if ( placeBefore ) {
+ dataType = dataType.substr( 1 ) || "*";
+ }
+ list = structure[ dataType ] = structure[ dataType ] || [];
+ // then we add to the structure accordingly
+ list[ placeBefore ? "unshift" : "push" ]( func );
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+ dataType /* internal */, inspected /* internal */ ) {
+
+ dataType = dataType || options.dataTypes[ 0 ];
+ inspected = inspected || {};
+
+ inspected[ dataType ] = true;
+
+ var list = structure[ dataType ],
+ i = 0,
+ length = list ? list.length : 0,
+ executeOnly = ( structure === prefilters ),
+ selection;
+
+ for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+ selection = list[ i ]( options, originalOptions, jqXHR );
+ // If we got redirected to another dataType
+ // we try there if executing only and not done already
+ if ( typeof selection === "string" ) {
+ if ( !executeOnly || inspected[ selection ] ) {
+ selection = undefined;
+ } else {
+ options.dataTypes.unshift( selection );
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, selection, inspected );
+ }
+ }
+ }
+ // If we're only executing or nothing was selected
+ // we try the catchall dataType if not done already
+ if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, "*", inspected );
+ }
+ // unnecessary when only executing (prefilters)
+ // but it'll be ignored by the caller in that case
+ return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+}
+
+jQuery.fn.extend({
+ load: function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+
+ // Don't do a request if no elements are being requested
+ } else if ( !this.length ) {
+ return this;
+ }
+
+ var off = url.indexOf( " " );
+ if ( off >= 0 ) {
+ var selector = url.slice( off, url.length );
+ url = url.slice( 0, off );
+ }
+
+ // Default to a GET request
+ var type = "GET";
+
+ // If the second parameter was provided
+ if ( params ) {
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( typeof params === "object" ) {
+ params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+ type = "POST";
+ }
+ }
+
+ var self = this;
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+ type: type,
+ dataType: "html",
+ data: params,
+ // Complete callback (responseText is used internally)
+ complete: function( jqXHR, status, responseText ) {
+ // Store the response as specified by the jqXHR object
+ responseText = jqXHR.responseText;
+ // If successful, inject the HTML into all the matched elements
+ if ( jqXHR.isResolved() ) {
+ // #4825: Get the actual response in case
+ // a dataFilter is present in ajaxSettings
+ jqXHR.done(function( r ) {
+ responseText = r;
+ });
+ // See if a selector was specified
+ self.html( selector ?
+ // Create a dummy div to hold the results
+ jQuery("<div>")
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append(responseText.replace(rscript, ""))
+
+ // Locate the specified elements
+ .find(selector) :
+
+ // If not, just inject the full result
+ responseText );
+ }
+
+ if ( callback ) {
+ self.each( callback, [ responseText, status, jqXHR ] );
+ }
+ }
+ });
+
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ ( this.checked || rselectTextarea.test( this.nodeName ) ||
+ rinput.test( this.type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val, i ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+ jQuery.fn[ o ] = function( f ){
+ return this.on( o, f );
+ };
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ type: method,
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ };
+});
+
+jQuery.extend({
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ if ( settings ) {
+ // Building a settings object
+ ajaxExtend( target, jQuery.ajaxSettings );
+ } else {
+ // Extending ajaxSettings
+ settings = target;
+ target = jQuery.ajaxSettings;
+ }
+ ajaxExtend( target, settings );
+ return target;
+ },
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ text: "text/plain",
+ json: "application/json, text/javascript",
+ "*": allTypes
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText"
+ },
+
+ // List of data converters
+ // 1) key format is "source_type destination_type" (a single space in-between)
+ // 2) the catchall symbol "*" can be used for source_type
+ converters: {
+
+ // Convert anything to text
+ "* text": window.String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ context: true,
+ url: true
+ }
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events
+ // It's the callbackContext if one was provided in the options
+ // and if it's a DOM node or a jQuery collection
+ globalEventContext = callbackContext !== s &&
+ ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+ jQuery( callbackContext ) : jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks( "once memory" ),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // ifModified key
+ ifModifiedKey,
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // transport
+ transport,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // The jqXHR state
+ state = 0,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Fake xhr
+ jqXHR = {
+
+ readyState: 0,
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ if ( !state ) {
+ var lname = name.toLowerCase();
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match === undefined ? null : match;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ statusText = statusText || "abort";
+ if ( transport ) {
+ transport.abort( statusText );
+ }
+ done( 0, statusText );
+ return this;
+ }
+ };
+
+ // Callback for when everything is done
+ // It is defined here because jslint complains if it is declared
+ // at the end of the function (which would be more logical and readable)
+ function done( status, nativeStatusText, responses, headers ) {
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ var isSuccess,
+ success,
+ error,
+ statusText = nativeStatusText,
+ response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+ lastModified,
+ etag;
+
+ // If successful, handle type chaining
+ if ( status >= 200 && status < 300 || status === 304 ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+
+ if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+ jQuery.lastModified[ ifModifiedKey ] = lastModified;
+ }
+ if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+ jQuery.etag[ ifModifiedKey ] = etag;
+ }
+ }
+
+ // If not modified
+ if ( status === 304 ) {
+
+ statusText = "notmodified";
+ isSuccess = true;
+
+ // If we have data
+ } else {
+
+ try {
+ success = ajaxConvert( s, response );
+ statusText = "success";
+ isSuccess = true;
+ } catch(e) {
+ // We have a parsererror
+ statusText = "parsererror";
+ error = e;
+ }
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( !statusText || status ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+ }
+
+ // Attach deferreds
+ deferred.promise( jqXHR );
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+ jqXHR.complete = completeDeferred.add;
+
+ // Status-dependent callbacks
+ jqXHR.statusCode = function( map ) {
+ if ( map ) {
+ var tmp;
+ if ( state < 2 ) {
+ for ( tmp in map ) {
+ statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+ }
+ } else {
+ tmp = map[ jqXHR.status ];
+ jqXHR.then( tmp, tmp );
+ }
+ }
+ return this;
+ };
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+ // Determine if a cross-domain request is in order
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return false;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Get ifModifiedKey before adding the anti-cache parameter
+ ifModifiedKey = s.url;
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+
+ var ts = jQuery.now(),
+ // try replacing _= if it is there
+ ret = s.url.replace( rts, "$1_=" + ts );
+
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ ifModifiedKey = ifModifiedKey || s.url;
+ if ( jQuery.lastModified[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+ }
+ if ( jQuery.etag[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+ }
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already
+ jqXHR.abort();
+ return false;
+
+ }
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout( function(){
+ jqXHR.abort( "timeout" );
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch (e) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ // Serialize an array of form elements or a set of
+ // key/values into a query string
+ param: function( a, traditional ) {
+ var s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : value;
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( var prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+ }
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( var name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var contents = s.contents,
+ dataTypes = s.dataTypes,
+ responseFields = s.responseFields,
+ ct,
+ type,
+ finalDataType,
+ firstDataType;
+
+ // Fill responseXXX fields
+ for ( type in responseFields ) {
+ if ( type in responses ) {
+ jqXHR[ responseFields[type] ] = responses[ type ];
+ }
+ }
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+ // Apply the dataFilter if provided
+ if ( s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ var dataTypes = s.dataTypes,
+ converters = {},
+ i,
+ key,
+ length = dataTypes.length,
+ tmp,
+ // Current and previous dataTypes
+ current = dataTypes[ 0 ],
+ prev,
+ // Conversion expression
+ conversion,
+ // Conversion function
+ conv,
+ // Conversion functions (transitive conversion)
+ conv1,
+ conv2;
+
+ // For each dataType in the chain
+ for ( i = 1; i < length; i++ ) {
+
+ // Create converters map
+ // with lowercased keys
+ if ( i === 1 ) {
+ for ( key in s.converters ) {
+ if ( typeof key === "string" ) {
+ converters[ key.toLowerCase() ] = s.converters[ key ];
+ }
+ }
+ }
+
+ // Get the dataTypes
+ prev = current;
+ current = dataTypes[ i ];
+
+ // If current is auto dataType, update it to prev
+ if ( current === "*" ) {
+ current = prev;
+ // If no auto and dataTypes are actually different
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // Get the converter
+ conversion = prev + " " + current;
+ conv = converters[ conversion ] || converters[ "* " + current ];
+
+ // If there is no direct converter, search transitively
+ if ( !conv ) {
+ conv2 = undefined;
+ for ( conv1 in converters ) {
+ tmp = conv1.split( " " );
+ if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+ conv2 = converters[ tmp[1] + " " + current ];
+ if ( conv2 ) {
+ conv1 = converters[ conv1 ];
+ if ( conv1 === true ) {
+ conv = conv2;
+ } else if ( conv2 === true ) {
+ conv = conv1;
+ }
+ break;
+ }
+ }
+ }
+ }
+ // If we found no converter, dispatch an error
+ if ( !( conv || conv2 ) ) {
+ jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+ }
+ // If found converter is not an equivalence
+ if ( conv !== true ) {
+ // Convert with 1 or 2 converters accordingly
+ response = conv ? conv( response ) : conv2( conv1(response) );
+ }
+ }
+ }
+ return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+ jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ return jQuery.expando + "_" + ( jsc++ );
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var inspectData = ( typeof s.data === "string" ) && /^application\/x\-www\-form\-urlencoded/.test( s.contentType );
+
+ if ( s.dataTypes[ 0 ] === "jsonp" ||
+ s.jsonp !== false && ( jsre.test( s.url ) ||
+ inspectData && jsre.test( s.data ) ) ) {
+
+ var responseContainer,
+ jsonpCallback = s.jsonpCallback =
+ jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+ previous = window[ jsonpCallback ],
+ url = s.url,
+ data = s.data,
+ replace = "$1" + jsonpCallback + "$2";
+
+ if ( s.jsonp !== false ) {
+ url = url.replace( jsre, replace );
+ if ( s.url === url ) {
+ if ( inspectData ) {
+ data = data.replace( jsre, replace );
+ }
+ if ( s.data === data ) {
+ // Add callback manually
+ url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+ }
+ }
+ }
+
+ s.url = url;
+ s.data = data;
+
+ // Install callback
+ window[ jsonpCallback ] = function( response ) {
+ responseContainer = [ response ];
+ };
+
+ // Clean-up function
+ jqXHR.always(function() {
+ // Set callback back to previous value
+ window[ jsonpCallback ] = previous;
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( previous ) ) {
+ window[ jsonpCallback ]( responseContainer[ 0 ] );
+ }
+ });
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( jsonpCallback + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Delegate to script
+ return "script";
+ }
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /javascript|ecmascript/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement( "script" );
+
+ script.async = "async";
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( 0, 1 );
+ }
+ }
+ };
+ }
+});
+
+
+
+
+var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+ xhrOnUnloadAbort = window.ActiveXObject ? function() {
+ // Abort all pending requests
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( 0, 1 );
+ }
+ } : false,
+ xhrId = 0,
+ xhrCallbacks;
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+ } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+ /* Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+ function() {
+ return !this.isLocal && createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+ jQuery.extend( jQuery.support, {
+ ajax: !!xhr,
+ cors: !!xhr && ( "withCredentials" in xhr )
+ });
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var xhr = s.xhr(),
+ handle,
+ i;
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open( s.type, s.url, s.async, s.username, s.password );
+ } else {
+ xhr.open( s.type, s.url, s.async );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+ headers[ "X-Requested-With" ] = "XMLHttpRequest";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( _ ) {}
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( s.hasContent && s.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+
+ var status,
+ statusText,
+ responseHeaders,
+ responses,
+ xml;
+
+ // Firefox throws exceptions when accessing properties
+ // of an xhr when a network error occured
+ // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ if ( xhrOnUnloadAbort ) {
+ delete xhrCallbacks[ handle ];
+ }
+ }
+
+ // If it's an abort
+ if ( isAbort ) {
+ // Abort it manually if needed
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ status = xhr.status;
+ responseHeaders = xhr.getAllResponseHeaders();
+ responses = {};
+ xml = xhr.responseXML;
+
+ // Construct response list
+ if ( xml && xml.documentElement /* #4958 */ ) {
+ responses.xml = xml;
+ }
+
+ // When requesting binary data, IE6-9 will throw an exception
+ // on any attempt to access responseText (#11426)
+ try {
+ responses.text = xhr.responseText;
+ } catch( _ ) {
+ }
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && s.isLocal && !s.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ // if we're in sync mode or it's in cache
+ // and has been retrieved directly (IE6 & IE7)
+ // we need to manually fire the callback
+ if ( !s.async || xhr.readyState === 4 ) {
+ callback();
+ } else {
+ handle = ++xhrId;
+ if ( xhrOnUnloadAbort ) {
+ // Create the active xhrs callbacks list if needed
+ // and attach the unload handler
+ if ( !xhrCallbacks ) {
+ xhrCallbacks = {};
+ jQuery( window ).unload( xhrOnUnloadAbort );
+ }
+ // Add to list of active xhrs callbacks
+ xhrCallbacks[ handle ] = callback;
+ }
+ xhr.onreadystatechange = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback(0,1);
+ }
+ }
+ };
+ }
+ });
+}
+
+
+
+
+var elemdisplay = {},
+ iframe, iframeDoc,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+ timerId,
+ fxAttrs = [
+ // height animations
+ [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+ // width animations
+ [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+ // opacity animations
+ [ "opacity" ]
+ ],
+ fxNow;
+
+jQuery.fn.extend({
+ show: function( speed, easing, callback ) {
+ var elem, display;
+
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("show", 3), speed, easing, callback );
+
+ } else {
+ for ( var i = 0, j = this.length; i < j; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.style ) {
+ display = elem.style.display;
+
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+ display = elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( (display === "" && jQuery.css(elem, "display") === "none") ||
+ !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+ jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( i = 0; i < j; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.style ) {
+ display = elem.style.display;
+
+ if ( display === "" || display === "none" ) {
+ elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
+ }
+ }
+ }
+
+ return this;
+ }
+ },
+
+ hide: function( speed, easing, callback ) {
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("hide", 3), speed, easing, callback);
+
+ } else {
+ var elem, display,
+ i = 0,
+ j = this.length;
+
+ for ( ; i < j; i++ ) {
+ elem = this[i];
+ if ( elem.style ) {
+ display = jQuery.css( elem, "display" );
+
+ if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) {
+ jQuery._data( elem, "olddisplay", display );
+ }
+ }
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( i = 0; i < j; i++ ) {
+ if ( this[i].style ) {
+ this[i].style.display = "none";
+ }
+ }
+
+ return this;
+ }
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2, callback ) {
+ var bool = typeof fn === "boolean";
+
+ if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+ this._toggle.apply( this, arguments );
+
+ } else if ( fn == null || bool ) {
+ this.each(function() {
+ var state = bool ? fn : jQuery(this).is(":hidden");
+ jQuery(this)[ state ? "show" : "hide" ]();
+ });
+
+ } else {
+ this.animate(genFx("toggle", 3), fn, fn2, callback);
+ }
+
+ return this;
+ },
+
+ fadeTo: function( speed, to, easing, callback ) {
+ return this.filter(":hidden").css("opacity", 0).show().end()
+ .animate({opacity: to}, speed, easing, callback);
+ },
+
+ animate: function( prop, speed, easing, callback ) {
+ var optall = jQuery.speed( speed, easing, callback );
+
+ if ( jQuery.isEmptyObject( prop ) ) {
+ return this.each( optall.complete, [ false ] );
+ }
+
+ // Do not change referenced properties as per-property easing will be lost
+ prop = jQuery.extend( {}, prop );
+
+ function doAnimation() {
+ // XXX 'this' does not always have a nodeName when running the
+ // test suite
+
+ if ( optall.queue === false ) {
+ jQuery._mark( this );
+ }
+
+ var opt = jQuery.extend( {}, optall ),
+ isElement = this.nodeType === 1,
+ hidden = isElement && jQuery(this).is(":hidden"),
+ name, val, p, e, hooks, replace,
+ parts, start, end, unit,
+ method;
+
+ // will store per property easing and be used to determine when an animation is complete
+ opt.animatedProperties = {};
+
+ // first pass over propertys to expand / normalize
+ for ( p in prop ) {
+ name = jQuery.camelCase( p );
+ if ( p !== name ) {
+ prop[ name ] = prop[ p ];
+ delete prop[ p ];
+ }
+
+ if ( ( hooks = jQuery.cssHooks[ name ] ) && "expand" in hooks ) {
+ replace = hooks.expand( prop[ name ] );
+ delete prop[ name ];
+
+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'p' from above because we have the correct "name"
+ for ( p in replace ) {
+ if ( ! ( p in prop ) ) {
+ prop[ p ] = replace[ p ];
+ }
+ }
+ }
+ }
+
+ for ( name in prop ) {
+ val = prop[ name ];
+ // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
+ if ( jQuery.isArray( val ) ) {
+ opt.animatedProperties[ name ] = val[ 1 ];
+ val = prop[ name ] = val[ 0 ];
+ } else {
+ opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
+ }
+
+ if ( val === "hide" && hidden || val === "show" && !hidden ) {
+ return opt.complete.call( this );
+ }
+
+ if ( isElement && ( name === "height" || name === "width" ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ if ( jQuery.css( this, "display" ) === "inline" &&
+ jQuery.css( this, "float" ) === "none" ) {
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
+ this.style.display = "inline-block";
+
+ } else {
+ this.style.zoom = 1;
+ }
+ }
+ }
+ }
+
+ if ( opt.overflow != null ) {
+ this.style.overflow = "hidden";
+ }
+
+ for ( p in prop ) {
+ e = new jQuery.fx( this, opt, p );
+ val = prop[ p ];
+
+ if ( rfxtypes.test( val ) ) {
+
+ // Tracks whether to show or hide based on private
+ // data attached to the element
+ method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
+ if ( method ) {
+ jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
+ e[ method ]();
+ } else {
+ e[ val ]();
+ }
+
+ } else {
+ parts = rfxnum.exec( val );
+ start = e.cur();
+
+ if ( parts ) {
+ end = parseFloat( parts[2] );
+ unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+
+ // We need to compute starting value
+ if ( unit !== "px" ) {
+ jQuery.style( this, p, (end || 1) + unit);
+ start = ( (end || 1) / e.cur() ) * start;
+ jQuery.style( this, p, start + unit);
+ }
+
+ // If a +=/-= token was provided, we're doing a relative animation
+ if ( parts[1] ) {
+ end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
+ }
+
+ e.custom( start, end, unit );
+
+ } else {
+ e.custom( start, val, "" );
+ }
+ }
+ }
+
+ // For JS strict compliance
+ return true;
+ }
+
+ return optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+
+ stop: function( type, clearQueue, gotoEnd ) {
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var index,
+ hadTimers = false,
+ timers = jQuery.timers,
+ data = jQuery._data( this );
+
+ // clear marker counters if we know they won't be
+ if ( !gotoEnd ) {
+ jQuery._unmark( true, this );
+ }
+
+ function stopQueue( elem, data, index ) {
+ var hooks = data[ index ];
+ jQuery.removeData( elem, index, true );
+ hooks.stop( gotoEnd );
+ }
+
+ if ( type == null ) {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) {
+ stopQueue( this, data, index );
+ }
+ }
+ } else if ( data[ index = type + ".run" ] && data[ index ].stop ){
+ stopQueue( this, data, index );
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ if ( gotoEnd ) {
+
+ // force the next step to be the last
+ timers[ index ]( true );
+ } else {
+ timers[ index ].saveState();
+ }
+ hadTimers = true;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( !( gotoEnd && hadTimers ) ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ }
+
+});
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout( clearFxNow, 0 );
+ return ( fxNow = jQuery.now() );
+}
+
+function clearFxNow() {
+ fxNow = undefined;
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, num ) {
+ var obj = {};
+
+ jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() {
+ obj[ this ] = type;
+ });
+
+ return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx( "show", 1 ),
+ slideUp: genFx( "hide", 1 ),
+ slideToggle: genFx( "toggle", 1 ),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.extend({
+ speed: function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function( noUnmark ) {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ } else if ( noUnmark !== false ) {
+ jQuery._unmark( this );
+ }
+ };
+
+ return opt;
+ },
+
+ easing: {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return ( -Math.cos( p*Math.PI ) / 2 ) + 0.5;
+ }
+ },
+
+ timers: [],
+
+ fx: function( elem, options, prop ) {
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ options.orig = options.orig || {};
+ }
+
+});
+
+jQuery.fx.prototype = {
+ // Simple function for setting a style value
+ update: function() {
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this );
+ },
+
+ // Get the current size
+ cur: function() {
+ if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) {
+ return this.elem[ this.prop ];
+ }
+
+ var parsed,
+ r = jQuery.css( this.elem, this.prop );
+ // Empty strings, null, undefined and "auto" are converted to 0,
+ // complex values such as "rotate(1rad)" are returned as is,
+ // simple values such as "10px" are parsed to Float.
+ return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+ },
+
+ // Start an animation from one number to another
+ custom: function( from, to, unit ) {
+ var self = this,
+ fx = jQuery.fx;
+
+ this.startTime = fxNow || createFxNow();
+ this.end = to;
+ this.now = this.start = from;
+ this.pos = this.state = 0;
+ this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+
+ function t( gotoEnd ) {
+ return self.step( gotoEnd );
+ }
+
+ t.queue = this.options.queue;
+ t.elem = this.elem;
+ t.saveState = function() {
+ if ( jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
+ if ( self.options.hide ) {
+ jQuery._data( self.elem, "fxshow" + self.prop, self.start );
+ } else if ( self.options.show ) {
+ jQuery._data( self.elem, "fxshow" + self.prop, self.end );
+ }
+ }
+ };
+
+ if ( t() && jQuery.timers.push(t) && !timerId ) {
+ timerId = setInterval( fx.tick, fx.interval );
+ }
+ },
+
+ // Simple 'show' function
+ show: function() {
+ var dataShow = jQuery._data( this.elem, "fxshow" + this.prop );
+
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop );
+ this.options.show = true;
+
+ // Begin the animation
+ // Make sure that we start at a small width/height to avoid any flash of content
+ if ( dataShow !== undefined ) {
+ // This show is picking up where a previous hide or show left off
+ this.custom( this.cur(), dataShow );
+ } else {
+ this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() );
+ }
+
+ // Start by showing the element
+ jQuery( this.elem ).show();
+ },
+
+ // Simple 'hide' function
+ hide: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop );
+ this.options.hide = true;
+
+ // Begin the animation
+ this.custom( this.cur(), 0 );
+ },
+
+ // Each step of an animation
+ step: function( gotoEnd ) {
+ var p, n, complete,
+ t = fxNow || createFxNow(),
+ done = true,
+ elem = this.elem,
+ options = this.options;
+
+ if ( gotoEnd || t >= options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ options.animatedProperties[ this.prop ] = true;
+
+ for ( p in options.animatedProperties ) {
+ if ( options.animatedProperties[ p ] !== true ) {
+ done = false;
+ }
+ }
+
+ if ( done ) {
+ // Reset the overflow
+ if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+
+ jQuery.each( [ "", "X", "Y" ], function( index, value ) {
+ elem.style[ "overflow" + value ] = options.overflow[ index ];
+ });
+ }
+
+ // Hide the element if the "hide" operation was done
+ if ( options.hide ) {
+ jQuery( elem ).hide();
+ }
+
+ // Reset the properties, if the item has been hidden or shown
+ if ( options.hide || options.show ) {
+ for ( p in options.animatedProperties ) {
+ jQuery.style( elem, p, options.orig[ p ] );
+ jQuery.removeData( elem, "fxshow" + p, true );
+ // Toggle data is no longer needed
+ jQuery.removeData( elem, "toggle" + p, true );
+ }
+ }
+
+ // Execute the complete function
+ // in the event that the complete function throws an exception
+ // we must ensure it won't be called twice. #5684
+
+ complete = options.complete;
+ if ( complete ) {
+
+ options.complete = false;
+ complete.call( elem );
+ }
+ }
+
+ return false;
+
+ } else {
+ // classical easing cannot be used with an Infinity duration
+ if ( options.duration == Infinity ) {
+ this.now = t;
+ } else {
+ n = t - this.startTime;
+ this.state = n / options.duration;
+
+ // Perform the easing function, defaults to swing
+ this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration );
+ this.now = this.start + ( (this.end - this.start) * this.pos );
+ }
+ // Perform the next step of the animation
+ this.update();
+ }
+
+ return true;
+ }
+};
+
+jQuery.extend( jQuery.fx, {
+ tick: function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ },
+
+ interval: 13,
+
+ stop: function() {
+ clearInterval( timerId );
+ timerId = null;
+ },
+
+ speeds: {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+ },
+
+ step: {
+ opacity: function( fx ) {
+ jQuery.style( fx.elem, "opacity", fx.now );
+ },
+
+ _default: function( fx ) {
+ if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+ fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+ } else {
+ fx.elem[ fx.prop ] = fx.now;
+ }
+ }
+ }
+});
+
+// Ensure props that can't be negative don't go there on undershoot easing
+jQuery.each( fxAttrs.concat.apply( [], fxAttrs ), function( i, prop ) {
+ // exclude marginTop, marginLeft, marginBottom and marginRight from this list
+ if ( prop.indexOf( "margin" ) ) {
+ jQuery.fx.step[ prop ] = function( fx ) {
+ jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit );
+ };
+ }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+
+// Try to restore the default display value of an element
+function defaultDisplay( nodeName ) {
+
+ if ( !elemdisplay[ nodeName ] ) {
+
+ var body = document.body,
+ elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
+ display = elem.css( "display" );
+ elem.remove();
+
+ // If the simple way fails,
+ // get element's real default display by attaching it to a temp iframe
+ if ( display === "none" || display === "" ) {
+ // No iframe to use yet, so create it
+ if ( !iframe ) {
+ iframe = document.createElement( "iframe" );
+ iframe.frameBorder = iframe.width = iframe.height = 0;
+ }
+
+ body.appendChild( iframe );
+
+ // Create a cacheable copy of the iframe document on first call.
+ // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+ // document to it; WebKit & Firefox won't allow reusing the iframe document.
+ if ( !iframeDoc || !iframe.createElement ) {
+ iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+ iframeDoc.write( ( jQuery.support.boxModel ? "<!doctype html>" : "" ) + "<html><body>" );
+ iframeDoc.close();
+ }
+
+ elem = iframeDoc.createElement( nodeName );
+
+ iframeDoc.body.appendChild( elem );
+
+ display = jQuery.css( elem, "display" );
+ body.removeChild( iframe );
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+ }
+
+ return elemdisplay[ nodeName ];
+}
+
+
+
+
+var getOffset,
+ rtable = /^t(?:able|d|h)$/i,
+ rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+ getOffset = function( elem, doc, docElem, box ) {
+ try {
+ box = elem.getBoundingClientRect();
+ } catch(e) {}
+
+ // Make sure we're not dealing with a disconnected DOM node
+ if ( !box || !jQuery.contains( docElem, elem ) ) {
+ return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+ }
+
+ var body = doc.body,
+ win = getWindow( doc ),
+ clientTop = docElem.clientTop || body.clientTop || 0,
+ clientLeft = docElem.clientLeft || body.clientLeft || 0,
+ scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop,
+ scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
+ top = box.top + scrollTop - clientTop,
+ left = box.left + scrollLeft - clientLeft;
+
+ return { top: top, left: left };
+ };
+
+} else {
+ getOffset = function( elem, doc, docElem ) {
+ var computedStyle,
+ offsetParent = elem.offsetParent,
+ prevOffsetParent = elem,
+ body = doc.body,
+ defaultView = doc.defaultView,
+ prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+ top = elem.offsetTop,
+ left = elem.offsetLeft;
+
+ while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+ if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+ break;
+ }
+
+ computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+ top -= elem.scrollTop;
+ left -= elem.scrollLeft;
+
+ if ( elem === offsetParent ) {
+ top += elem.offsetTop;
+ left += elem.offsetLeft;
+
+ if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevOffsetParent = offsetParent;
+ offsetParent = elem.offsetParent;
+ }
+
+ if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevComputedStyle = computedStyle;
+ }
+
+ if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+ top += body.offsetTop;
+ left += body.offsetLeft;
+ }
+
+ if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+ top += Math.max( docElem.scrollTop, body.scrollTop );
+ left += Math.max( docElem.scrollLeft, body.scrollLeft );
+ }
+
+ return { top: top, left: left };
+ };
+}
+
+jQuery.fn.offset = function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var elem = this[0],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return null;
+ }
+
+ if ( elem === doc.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ return getOffset( elem, doc, doc.documentElement );
+};
+
+jQuery.offset = {
+
+ bodyOffset: function( body ) {
+ var top = body.offsetTop,
+ left = body.offsetLeft;
+
+ if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+ top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+ left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+ }
+
+ return { top: top, left: left };
+ },
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curCSSTop = jQuery.css( elem, "top" ),
+ curCSSLeft = jQuery.css( elem, "left" ),
+ calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+ props = {}, curPosition = {}, curTop, curLeft;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+
+ position: function() {
+ if ( !this[0] ) {
+ return null;
+ }
+
+ var elem = this[0],
+
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+ offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+ parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.body;
+ while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return jQuery.access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ jQuery.support.boxModel && win.document.documentElement[ method ] ||
+ win.document.body[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+
+
+
+
+// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ var clientProp = "client" + name,
+ scrollProp = "scroll" + name,
+ offsetProp = "offset" + name;
+
+ // innerHeight and innerWidth
+ jQuery.fn[ "inner" + name ] = function() {
+ var elem = this[0];
+ return elem ?
+ elem.style ?
+ parseFloat( jQuery.css( elem, type, "padding" ) ) :
+ this[ type ]() :
+ null;
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn[ "outer" + name ] = function( margin ) {
+ var elem = this[0];
+ return elem ?
+ elem.style ?
+ parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
+ this[ type ]() :
+ null;
+ };
+
+ jQuery.fn[ type ] = function( value ) {
+ return jQuery.access( this, function( elem, type, value ) {
+ var doc, docElemProp, orig, ret;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+ doc = elem.document;
+ docElemProp = doc.documentElement[ clientProp ];
+ return jQuery.support.boxModel && docElemProp ||
+ doc.body && doc.body[ clientProp ] || docElemProp;
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ doc = elem.documentElement;
+
+ // when a window > document, IE6 reports a offset[Width/Height] > client[Width/Height]
+ // so we can't use max, as it'll choose the incorrect offset[Width/Height]
+ // instead we use the correct client[Width/Height]
+ // support:IE6
+ if ( doc[ clientProp ] >= doc[ scrollProp ] ) {
+ return doc[ clientProp ];
+ }
+
+ return Math.max(
+ elem.body[ scrollProp ], doc[ scrollProp ],
+ elem.body[ offsetProp ], doc[ offsetProp ]
+ );
+ }
+
+ // Get width or height on the element
+ if ( value === undefined ) {
+ orig = jQuery.css( elem, type );
+ ret = parseFloat( orig );
+ return jQuery.isNumeric( ret ) ? ret : orig;
+ }
+
+ // Set the width or height on the element
+ jQuery( elem ).css( type, value );
+ }, type, value, arguments.length, null );
+ };
+});
+
+
+
+
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+ define( "jquery", [], function () { return jQuery; } );
+}
+
+
+
+})( window );
diff --git a/src/fauxton/assets/js/libs/jshint.js b/src/fauxton/assets/js/libs/jshint.js
new file mode 100644
index 000000000..6338ee399
--- /dev/null
+++ b/src/fauxton/assets/js/libs/jshint.js
@@ -0,0 +1,4529 @@
+/*!
+ * JSHint, by JSHint Community.
+ *
+ * Licensed under the same slightly modified MIT license that JSLint is.
+ * It stops evil-doers everywhere.
+ *
+ * JSHint is a derivative work of JSLint:
+ *
+ * Copyright (c) 2002 Douglas Crockford (www.JSLint.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom
+ * the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * The Software shall be used for Good, not Evil.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * JSHint was forked from the 2010-12-16 edition of JSLint.
+ *
+ */
+
+/*
+ JSHINT is a global function. It takes two parameters.
+
+ var myResult = JSHINT(source, option);
+
+ The first parameter is either a string or an array of strings. If it is a
+ string, it will be split on '\n' or '\r'. If it is an array of strings, it
+ is assumed that each string represents one line. The source can be a
+ JavaScript text or a JSON text.
+
+ The second parameter is an optional object of options which control the
+ operation of JSHINT. Most of the options are booleans: They are all
+ optional and have a default value of false. One of the options, predef,
+ can be an array of names, which will be used to declare global variables,
+ or an object whose keys are used as global names, with a boolean value
+ that determines if they are assignable.
+
+ If it checks out, JSHINT returns true. Otherwise, it returns false.
+
+ If false, you can inspect JSHINT.errors to find out the problems.
+ JSHINT.errors is an array of objects containing these members:
+
+ {
+ line : The line (relative to 1) at which the lint was found
+ character : The character (relative to 1) at which the lint was found
+ reason : The problem
+ evidence : The text line in which the problem occurred
+ raw : The raw message before the details were inserted
+ a : The first detail
+ b : The second detail
+ c : The third detail
+ d : The fourth detail
+ }
+
+ If a fatal error was found, a null will be the last element of the
+ JSHINT.errors array.
+
+ You can request a data structure which contains JSHint's results.
+
+ var myData = JSHINT.data();
+
+ It returns a structure with this form:
+
+ {
+ errors: [
+ {
+ line: NUMBER,
+ character: NUMBER,
+ reason: STRING,
+ evidence: STRING
+ }
+ ],
+ functions: [
+ name: STRING,
+ line: NUMBER,
+ character: NUMBER,
+ last: NUMBER,
+ lastcharacter: NUMBER,
+ param: [
+ STRING
+ ],
+ closure: [
+ STRING
+ ],
+ var: [
+ STRING
+ ],
+ exception: [
+ STRING
+ ],
+ outer: [
+ STRING
+ ],
+ unused: [
+ STRING
+ ],
+ global: [
+ STRING
+ ],
+ label: [
+ STRING
+ ]
+ ],
+ globals: [
+ STRING
+ ],
+ member: {
+ STRING: NUMBER
+ },
+ unused: [
+ {
+ name: STRING,
+ line: NUMBER
+ }
+ ],
+ implieds: [
+ {
+ name: STRING,
+ line: NUMBER
+ }
+ ],
+ urls: [
+ STRING
+ ],
+ json: BOOLEAN
+ }
+
+ Empty arrays will not be included.
+
+*/
+
+/*jshint
+ evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true,
+ undef: true, maxlen: 100, indent: 4, quotmark: double, unused: true
+*/
+
+/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)",
+ "(breakage)", "(character)", "(context)", "(error)", "(global)", "(identifier)", "(last)",
+ "(lastcharacter)", "(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)",
+ "(statement)", "(verb)", "(tokens)", "*", "+", "++", "-", "--", "\/", "<", "<=", "==",
+ "===", ">", ">=", $, $$, $A, $F, $H, $R, $break, $continue, $w, Abstract, Ajax,
+ __filename, __dirname, ActiveXObject, Array, ArrayBuffer, ArrayBufferView, Audio,
+ Autocompleter, Assets, Boolean, Builder, Buffer, Browser, COM, CScript, Canvas,
+ CustomAnimation, Class, Control, Chain, Color, Cookie, Core, DataView, Date,
+ Debug, Draggable, Draggables, Droppables, Document, DomReady, DOMReady, DOMParser, Drag,
+ E, Enumerator, Enumerable, Element, Elements, Error, Effect, EvalError, Event,
+ Events, FadeAnimation, Field, Flash, Float32Array, Float64Array, Form,
+ FormField, Frame, FormData, Function, Fx, GetObject, Group, Hash, HotKey,
+ HTMLElement, HTMLAnchorElement, HTMLBaseElement, HTMLBlockquoteElement,
+ HTMLBodyElement, HTMLBRElement, HTMLButtonElement, HTMLCanvasElement, HTMLDirectoryElement,
+ HTMLDivElement, HTMLDListElement, HTMLFieldSetElement,
+ HTMLFontElement, HTMLFormElement, HTMLFrameElement, HTMLFrameSetElement,
+ HTMLHeadElement, HTMLHeadingElement, HTMLHRElement, HTMLHtmlElement,
+ HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLIsIndexElement,
+ HTMLLabelElement, HTMLLayerElement, HTMLLegendElement, HTMLLIElement,
+ HTMLLinkElement, HTMLMapElement, HTMLMenuElement, HTMLMetaElement,
+ HTMLModElement, HTMLObjectElement, HTMLOListElement, HTMLOptGroupElement,
+ HTMLOptionElement, HTMLParagraphElement, HTMLParamElement, HTMLPreElement,
+ HTMLQuoteElement, HTMLScriptElement, HTMLSelectElement, HTMLStyleElement,
+ HtmlTable, HTMLTableCaptionElement, HTMLTableCellElement, HTMLTableColElement,
+ HTMLTableElement, HTMLTableRowElement, HTMLTableSectionElement,
+ HTMLTextAreaElement, HTMLTitleElement, HTMLUListElement, HTMLVideoElement,
+ Iframe, IframeShim, Image, importScripts, Int16Array, Int32Array, Int8Array,
+ Insertion, InputValidator, JSON, Keyboard, Locale, LN10, LN2, LOG10E, LOG2E,
+ MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem, MessageChannel, MessageEvent, MessagePort,
+ MoveAnimation, MooTools, MutationObserver, Native, NEGATIVE_INFINITY, Node, NodeFilter,
+ Number, Object, ObjectRange,
+ Option, Options, OverText, PI, POSITIVE_INFINITY, PeriodicalExecuter, Point, Position, Prototype,
+ RangeError, Rectangle, ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation,
+ SQRT1_2, SQRT2, ScrollBar, ScriptEngine, ScriptEngineBuildVersion,
+ ScriptEngineMajorVersion, ScriptEngineMinorVersion, Scriptaculous, Scroller,
+ Slick, Slider, Selector, SharedWorker, String, Style, SyntaxError, Sortable, Sortables,
+ SortableObserver, Sound, Spinner, System, Swiff, Text, TextArea, Template,
+ Timer, Tips, Type, TypeError, Toggle, Try, "use strict", unescape, URI, URIError, URL,
+ VBArray, WSH, WScript, XDomainRequest, Web, Window, XMLDOM, XMLHttpRequest, XMLSerializer,
+ XPathEvaluator, XPathException, XPathExpression, XPathNamespace, XPathNSResolver, XPathResult,
+ "\\", a, addEventListener, address, alert, apply, applicationCache, arguments, arity,
+ asi, atob, b, basic, basicToken, bitwise, block, blur, boolOptions, boss, browser, btoa, c,
+ call, callee, caller, camelcase, cases, charAt, charCodeAt, character, clearInterval,
+ clearTimeout, close, closed, closure, comment, condition, confirm, console, constructor,
+ content, couch, create, css, curly, d, data, datalist, dd, debug, decodeURI,
+ decodeURIComponent, defaultStatus, defineClass, deserialize, devel, document,
+ dojo, dijit, dojox, define, else, emit, encodeURI, encodeURIComponent,
+ eqeq, eqeqeq, eqnull, errors, es5, escape, esnext, eval, event, evidence, evil,
+ ex, exception, exec, exps, expr, exports, FileReader, first, floor, focus, forEach,
+ forin, fragment, frames, from, fromCharCode, fud, funcscope, funct, function, functions,
+ g, gc, getComputedStyle, getRow, getter, getterToken, GLOBAL, global, globals, globalstrict,
+ hasOwnProperty, help, history, i, id, identifier, immed, implieds, importPackage, include,
+ indent, indexOf, init, ins, instanceOf, isAlpha, isApplicationRunning, isArray,
+ isDigit, isFinite, isNaN, iterator, java, join, jshint,
+ JSHINT, json, jquery, jQuery, keys, label, labelled, last, lastcharacter, lastsemic, laxbreak,
+ laxcomma, latedef, lbp, led, left, length, line, load, loadClass, localStorage, location,
+ log, loopfunc, m, match, maxerr, maxlen, member,message, meta, module, moveBy,
+ moveTo, mootools, multistr, name, navigator, new, newcap, noarg, node, noempty, nomen,
+ nonew, nonstandard, nud, onbeforeunload, onblur, onerror, onevar, onecase, onfocus,
+ onload, onresize, onunload, open, openDatabase, openURL, opener, opera, options, outer, param,
+ parent, parseFloat, parseInt, passfail, plusplus, postMessage, predef, print, process, prompt,
+ proto, prototype, prototypejs, provides, push, quit, quotmark, range, raw, reach, reason, regexp,
+ readFile, readUrl, regexdash, removeEventListener, replace, report, require,
+ reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, respond, rhino, right,
+ runCommand, scroll, screen, scripturl, scrollBy, scrollTo, scrollbar, search, seal, self,
+ send, serialize, sessionStorage, setInterval, setTimeout, setter, setterToken, shift, slice,
+ smarttabs, sort, spawn, split, stack, status, start, strict, sub, substr, supernew, shadow,
+ supplant, sum, sync, test, toLowerCase, toString, toUpperCase, toint32, token, tokens, top,
+ trailing, type, typeOf, Uint16Array, Uint32Array, Uint8Array, undef, undefs, unused,
+ urls, validthis, value, valueOf, var, vars, version, WebSocket, withstmt, white, window, windows,
+ Worker, worker, wsh*/
+
+/*global exports: false */
+
+// We build the application inside a function so that we produce only a single
+// global variable. That function will be invoked immediately, and its return
+// value is the JSHINT function itself.
+
+var JSHINT = (function () {
+ "use strict";
+
+ var anonname, // The guessed name for anonymous functions.
+
+// These are operators that should not be used with the ! operator.
+
+ bang = {
+ "<" : true,
+ "<=" : true,
+ "==" : true,
+ "===": true,
+ "!==": true,
+ "!=" : true,
+ ">" : true,
+ ">=" : true,
+ "+" : true,
+ "-" : true,
+ "*" : true,
+ "/" : true,
+ "%" : true
+ },
+
+ // These are the JSHint boolean options.
+ boolOptions = {
+ asi : true, // if automatic semicolon insertion should be tolerated
+ bitwise : true, // if bitwise operators should not be allowed
+ boss : true, // if advanced usage of assignments should be allowed
+ browser : true, // if the standard browser globals should be predefined
+ camelcase : true, // if identifiers should be required in camel case
+ couch : true, // if CouchDB globals should be predefined
+ curly : true, // if curly braces around all blocks should be required
+ debug : true, // if debugger statements should be allowed
+ devel : true, // if logging globals should be predefined (console,
+ // alert, etc.)
+ dojo : true, // if Dojo Toolkit globals should be predefined
+ eqeqeq : true, // if === should be required
+ eqnull : true, // if == null comparisons should be tolerated
+ es5 : true, // if ES5 syntax should be allowed
+ esnext : true, // if es.next specific syntax should be allowed
+ evil : true, // if eval should be allowed
+ expr : true, // if ExpressionStatement should be allowed as Programs
+ forin : true, // if for in statements must filter
+ funcscope : true, // if only function scope should be used for scope tests
+ globalstrict: true, // if global "use strict"; should be allowed (also
+ // enables 'strict')
+ immed : true, // if immediate invocations must be wrapped in parens
+ iterator : true, // if the `__iterator__` property should be allowed
+ jquery : true, // if jQuery globals should be predefined
+ lastsemic : true, // if semicolons may be ommitted for the trailing
+ // statements inside of a one-line blocks.
+ latedef : true, // if the use before definition should not be tolerated
+ laxbreak : true, // if line breaks should not be checked
+ laxcomma : true, // if line breaks should not be checked around commas
+ loopfunc : true, // if functions should be allowed to be defined within
+ // loops
+ mootools : true, // if MooTools globals should be predefined
+ multistr : true, // allow multiline strings
+ newcap : true, // if constructor names must be capitalized
+ noarg : true, // if arguments.caller and arguments.callee should be
+ // disallowed
+ node : true, // if the Node.js environment globals should be
+ // predefined
+ noempty : true, // if empty blocks should be disallowed
+ nonew : true, // if using `new` for side-effects should be disallowed
+ nonstandard : true, // if non-standard (but widely adopted) globals should
+ // be predefined
+ nomen : true, // if names should be checked
+ onevar : true, // if only one var statement per function should be
+ // allowed
+ onecase : true, // if one case switch statements should be allowed
+ passfail : true, // if the scan should stop on first error
+ plusplus : true, // if increment/decrement should not be allowed
+ proto : true, // if the `__proto__` property should be allowed
+ prototypejs : true, // if Prototype and Scriptaculous globals should be
+ // predefined
+ regexdash : true, // if unescaped first/last dash (-) inside brackets
+ // should be tolerated
+ regexp : true, // if the . should not be allowed in regexp literals
+ rhino : true, // if the Rhino environment globals should be predefined
+ undef : true, // if variables should be declared before used
+ unused : true, // if variables should be always used
+ scripturl : true, // if script-targeted URLs should be tolerated
+ shadow : true, // if variable shadowing should be tolerated
+ smarttabs : true, // if smarttabs should be tolerated
+ // (http://www.emacswiki.org/emacs/SmartTabs)
+ strict : true, // require the "use strict"; pragma
+ sub : true, // if all forms of subscript notation are tolerated
+ supernew : true, // if `new function () { ... };` and `new Object;`
+ // should be tolerated
+ trailing : true, // if trailing whitespace rules apply
+ validthis : true, // if 'this' inside a non-constructor function is valid.
+ // This is a function scoped option only.
+ withstmt : true, // if with statements should be allowed
+ white : true, // if strict whitespace rules apply
+ worker : true, // if Web Worker script symbols should be allowed
+ wsh : true // if the Windows Scripting Host environment globals
+ // should be predefined
+ },
+
+ // These are the JSHint options that can take any value
+ // (we use this object to detect invalid options)
+ valOptions = {
+ maxlen: false,
+ indent: false,
+ maxerr: false,
+ predef: false,
+ quotmark: false //'single'|'double'|true
+ },
+
+ // These are JSHint boolean options which are shared with JSLint
+ // where the definition in JSHint is opposite JSLint
+ invertedOptions = {
+ bitwise : true,
+ forin : true,
+ newcap : true,
+ nomen : true,
+ plusplus : true,
+ regexp : true,
+ undef : true,
+ white : true,
+
+ // Inverted and renamed, use JSHint name here
+ eqeqeq : true,
+ onevar : true
+ },
+
+ // These are JSHint boolean options which are shared with JSLint
+ // where the name has been changed but the effect is unchanged
+ renamedOptions = {
+ eqeq : "eqeqeq",
+ vars : "onevar",
+ windows : "wsh"
+ },
+
+
+ // browser contains a set of global names which are commonly provided by a
+ // web browser environment.
+ browser = {
+ ArrayBuffer : false,
+ ArrayBufferView : false,
+ Audio : false,
+ addEventListener : false,
+ applicationCache : false,
+ atob : false,
+ blur : false,
+ btoa : false,
+ clearInterval : false,
+ clearTimeout : false,
+ close : false,
+ closed : false,
+ DataView : false,
+ DOMParser : false,
+ defaultStatus : false,
+ document : false,
+ event : false,
+ FileReader : false,
+ Float32Array : false,
+ Float64Array : false,
+ FormData : false,
+ focus : false,
+ frames : false,
+ getComputedStyle : false,
+ HTMLElement : false,
+ HTMLAnchorElement : false,
+ HTMLBaseElement : false,
+ HTMLBlockquoteElement : false,
+ HTMLBodyElement : false,
+ HTMLBRElement : false,
+ HTMLButtonElement : false,
+ HTMLCanvasElement : false,
+ HTMLDirectoryElement : false,
+ HTMLDivElement : false,
+ HTMLDListElement : false,
+ HTMLFieldSetElement : false,
+ HTMLFontElement : false,
+ HTMLFormElement : false,
+ HTMLFrameElement : false,
+ HTMLFrameSetElement : false,
+ HTMLHeadElement : false,
+ HTMLHeadingElement : false,
+ HTMLHRElement : false,
+ HTMLHtmlElement : false,
+ HTMLIFrameElement : false,
+ HTMLImageElement : false,
+ HTMLInputElement : false,
+ HTMLIsIndexElement : false,
+ HTMLLabelElement : false,
+ HTMLLayerElement : false,
+ HTMLLegendElement : false,
+ HTMLLIElement : false,
+ HTMLLinkElement : false,
+ HTMLMapElement : false,
+ HTMLMenuElement : false,
+ HTMLMetaElement : false,
+ HTMLModElement : false,
+ HTMLObjectElement : false,
+ HTMLOListElement : false,
+ HTMLOptGroupElement : false,
+ HTMLOptionElement : false,
+ HTMLParagraphElement : false,
+ HTMLParamElement : false,
+ HTMLPreElement : false,
+ HTMLQuoteElement : false,
+ HTMLScriptElement : false,
+ HTMLSelectElement : false,
+ HTMLStyleElement : false,
+ HTMLTableCaptionElement : false,
+ HTMLTableCellElement : false,
+ HTMLTableColElement : false,
+ HTMLTableElement : false,
+ HTMLTableRowElement : false,
+ HTMLTableSectionElement : false,
+ HTMLTextAreaElement : false,
+ HTMLTitleElement : false,
+ HTMLUListElement : false,
+ HTMLVideoElement : false,
+ history : false,
+ Int16Array : false,
+ Int32Array : false,
+ Int8Array : false,
+ Image : false,
+ length : false,
+ localStorage : false,
+ location : false,
+ MessageChannel : false,
+ MessageEvent : false,
+ MessagePort : false,
+ moveBy : false,
+ moveTo : false,
+ MutationObserver : false,
+ name : false,
+ Node : false,
+ NodeFilter : false,
+ navigator : false,
+ onbeforeunload : true,
+ onblur : true,
+ onerror : true,
+ onfocus : true,
+ onload : true,
+ onresize : true,
+ onunload : true,
+ open : false,
+ openDatabase : false,
+ opener : false,
+ Option : false,
+ parent : false,
+ print : false,
+ removeEventListener : false,
+ resizeBy : false,
+ resizeTo : false,
+ screen : false,
+ scroll : false,
+ scrollBy : false,
+ scrollTo : false,
+ sessionStorage : false,
+ setInterval : false,
+ setTimeout : false,
+ SharedWorker : false,
+ status : false,
+ top : false,
+ Uint16Array : false,
+ Uint32Array : false,
+ Uint8Array : false,
+ WebSocket : false,
+ window : false,
+ Worker : false,
+ XMLHttpRequest : false,
+ XMLSerializer : false,
+ XPathEvaluator : false,
+ XPathException : false,
+ XPathExpression : false,
+ XPathNamespace : false,
+ XPathNSResolver : false,
+ XPathResult : false
+ },
+
+ couch = {
+ "require" : false,
+ respond : false,
+ getRow : false,
+ emit : false,
+ send : false,
+ start : false,
+ sum : false,
+ log : false,
+ exports : false,
+ module : false,
+ provides : false
+ },
+
+ declared, // Globals that were declared using /*global ... */ syntax.
+
+ devel = {
+ alert : false,
+ confirm : false,
+ console : false,
+ Debug : false,
+ opera : false,
+ prompt : false
+ },
+
+ dojo = {
+ dojo : false,
+ dijit : false,
+ dojox : false,
+ define : false,
+ "require" : false
+ },
+
+ funct, // The current function
+
+ functionicity = [
+ "closure", "exception", "global", "label",
+ "outer", "unused", "var"
+ ],
+
+ functions, // All of the functions
+
+ global, // The global scope
+ implied, // Implied globals
+ inblock,
+ indent,
+ jsonmode,
+
+ jquery = {
+ "$" : false,
+ jQuery : false
+ },
+
+ lines,
+ lookahead,
+ member,
+ membersOnly,
+
+ mootools = {
+ "$" : false,
+ "$$" : false,
+ Assets : false,
+ Browser : false,
+ Chain : false,
+ Class : false,
+ Color : false,
+ Cookie : false,
+ Core : false,
+ Document : false,
+ DomReady : false,
+ DOMReady : false,
+ Drag : false,
+ Element : false,
+ Elements : false,
+ Event : false,
+ Events : false,
+ Fx : false,
+ Group : false,
+ Hash : false,
+ HtmlTable : false,
+ Iframe : false,
+ IframeShim : false,
+ InputValidator : false,
+ instanceOf : false,
+ Keyboard : false,
+ Locale : false,
+ Mask : false,
+ MooTools : false,
+ Native : false,
+ Options : false,
+ OverText : false,
+ Request : false,
+ Scroller : false,
+ Slick : false,
+ Slider : false,
+ Sortables : false,
+ Spinner : false,
+ Swiff : false,
+ Tips : false,
+ Type : false,
+ typeOf : false,
+ URI : false,
+ Window : false
+ },
+
+ nexttoken,
+
+ node = {
+ __filename : false,
+ __dirname : false,
+ Buffer : false,
+ console : false,
+ exports : true, // In Node it is ok to exports = module.exports = foo();
+ GLOBAL : false,
+ global : false,
+ module : false,
+ process : false,
+ require : false,
+ setTimeout : false,
+ clearTimeout : false,
+ setInterval : false,
+ clearInterval : false
+ },
+
+ noreach,
+ option,
+ predefined, // Global variables defined by option
+ prereg,
+ prevtoken,
+
+ prototypejs = {
+ "$" : false,
+ "$$" : false,
+ "$A" : false,
+ "$F" : false,
+ "$H" : false,
+ "$R" : false,
+ "$break" : false,
+ "$continue" : false,
+ "$w" : false,
+ Abstract : false,
+ Ajax : false,
+ Class : false,
+ Enumerable : false,
+ Element : false,
+ Event : false,
+ Field : false,
+ Form : false,
+ Hash : false,
+ Insertion : false,
+ ObjectRange : false,
+ PeriodicalExecuter: false,
+ Position : false,
+ Prototype : false,
+ Selector : false,
+ Template : false,
+ Toggle : false,
+ Try : false,
+ Autocompleter : false,
+ Builder : false,
+ Control : false,
+ Draggable : false,
+ Draggables : false,
+ Droppables : false,
+ Effect : false,
+ Sortable : false,
+ SortableObserver : false,
+ Sound : false,
+ Scriptaculous : false
+ },
+
+ quotmark,
+
+ rhino = {
+ defineClass : false,
+ deserialize : false,
+ gc : false,
+ help : false,
+ importPackage: false,
+ "java" : false,
+ load : false,
+ loadClass : false,
+ print : false,
+ quit : false,
+ readFile : false,
+ readUrl : false,
+ runCommand : false,
+ seal : false,
+ serialize : false,
+ spawn : false,
+ sync : false,
+ toint32 : false,
+ version : false
+ },
+
+ scope, // The current scope
+ stack,
+
+ // standard contains the global names that are provided by the
+ // ECMAScript standard.
+ standard = {
+ Array : false,
+ Boolean : false,
+ Date : false,
+ decodeURI : false,
+ decodeURIComponent : false,
+ encodeURI : false,
+ encodeURIComponent : false,
+ Error : false,
+ "eval" : false,
+ EvalError : false,
+ Function : false,
+ hasOwnProperty : false,
+ isFinite : false,
+ isNaN : false,
+ JSON : false,
+ Math : false,
+ Number : false,
+ Object : false,
+ parseInt : false,
+ parseFloat : false,
+ RangeError : false,
+ ReferenceError : false,
+ RegExp : false,
+ String : false,
+ SyntaxError : false,
+ TypeError : false,
+ URIError : false
+ },
+
+ // widely adopted global names that are not part of ECMAScript standard
+ nonstandard = {
+ escape : false,
+ unescape : false
+ },
+
+ directive,
+ syntax = {},
+ tab,
+ token,
+ urls,
+ useESNextSyntax,
+ warnings,
+
+ worker = {
+ importScripts : true,
+ postMessage : true,
+ self : true
+ },
+
+ wsh = {
+ ActiveXObject : true,
+ Enumerator : true,
+ GetObject : true,
+ ScriptEngine : true,
+ ScriptEngineBuildVersion : true,
+ ScriptEngineMajorVersion : true,
+ ScriptEngineMinorVersion : true,
+ VBArray : true,
+ WSH : true,
+ WScript : true,
+ XDomainRequest : true
+ };
+
+ // Regular expressions. Some of these are stupidly long.
+ var ax, cx, tx, nx, nxg, lx, ix, jx, ft;
+ (function () {
+ /*jshint maxlen:300 */
+
+ // unsafe comment or string
+ ax = /@cc|<\/?|script|\]\s*\]|<\s*!|&lt/i;
+
+ // unsafe characters that are silently deleted by one or more browsers
+ cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
+
+ // token
+ tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/;
+
+ // characters in strings that need escapement
+ nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
+ nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+
+ // star slash
+ lx = /\*\/|\/\*/;
+
+ // identifier
+ ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
+
+ // javascript url
+ jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i;
+
+ // catches /* falls through */ comments
+ ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/;
+ }());
+
+ function F() {} // Used by Object.create
+
+ function is_own(object, name) {
+ // The object.hasOwnProperty method fails when the property under consideration
+ // is named 'hasOwnProperty'. So we have to use this more convoluted form.
+ return Object.prototype.hasOwnProperty.call(object, name);
+ }
+
+ function checkOption(name, t) {
+ if (valOptions[name] === undefined && boolOptions[name] === undefined) {
+ warning("Bad option: '" + name + "'.", t);
+ }
+ }
+
+ function isString(obj) {
+ return Object.prototype.toString.call(obj) === "[object String]";
+ }
+
+ // Provide critical ES5 functions to ES3.
+
+ if (typeof Array.isArray !== "function") {
+ Array.isArray = function (o) {
+ return Object.prototype.toString.apply(o) === "[object Array]";
+ };
+ }
+
+ if (!Array.prototype.forEach) {
+ Array.prototype.forEach = function (fn, scope) {
+ var len = this.length;
+
+ for (var i = 0; i < len; i++) {
+ fn.call(scope || this, this[i], i, this);
+ }
+ };
+ }
+
+ if (typeof Object.create !== "function") {
+ Object.create = function (o) {
+ F.prototype = o;
+ return new F();
+ };
+ }
+
+ if (typeof Object.keys !== "function") {
+ Object.keys = function (o) {
+ var a = [], k;
+ for (k in o) {
+ if (is_own(o, k)) {
+ a.push(k);
+ }
+ }
+ return a;
+ };
+ }
+
+ // Non standard methods
+
+ function isAlpha(str) {
+ return (str >= "a" && str <= "z\uffff") ||
+ (str >= "A" && str <= "Z\uffff");
+ }
+
+ function isDigit(str) {
+ return (str >= "0" && str <= "9");
+ }
+
+ function supplant(str, data) {
+ return str.replace(/\{([^{}]*)\}/g, function (a, b) {
+ var r = data[b];
+ return typeof r === "string" || typeof r === "number" ? r : a;
+ });
+ }
+
+ function combine(t, o) {
+ var n;
+ for (n in o) {
+ if (is_own(o, n)) {
+ t[n] = o[n];
+ }
+ }
+ }
+
+ function assume() {
+ if (option.couch) {
+ combine(predefined, couch);
+ }
+
+ if (option.rhino) {
+ combine(predefined, rhino);
+ }
+
+ if (option.prototypejs) {
+ combine(predefined, prototypejs);
+ }
+
+ if (option.node) {
+ combine(predefined, node);
+ option.globalstrict = true;
+ }
+
+ if (option.devel) {
+ combine(predefined, devel);
+ }
+
+ if (option.dojo) {
+ combine(predefined, dojo);
+ }
+
+ if (option.browser) {
+ combine(predefined, browser);
+ }
+
+ if (option.nonstandard) {
+ combine(predefined, nonstandard);
+ }
+
+ if (option.jquery) {
+ combine(predefined, jquery);
+ }
+
+ if (option.mootools) {
+ combine(predefined, mootools);
+ }
+
+ if (option.worker) {
+ combine(predefined, worker);
+ }
+
+ if (option.wsh) {
+ combine(predefined, wsh);
+ }
+
+ if (option.esnext) {
+ useESNextSyntax();
+ }
+
+ if (option.globalstrict && option.strict !== false) {
+ option.strict = true;
+ }
+ }
+
+
+ // Produce an error warning.
+ function quit(message, line, chr) {
+ var percentage = Math.floor((line / lines.length) * 100);
+
+ throw {
+ name: "JSHintError",
+ line: line,
+ character: chr,
+ message: message + " (" + percentage + "% scanned).",
+ raw: message
+ };
+ }
+
+ function isundef(scope, m, t, a) {
+ return JSHINT.undefs.push([scope, m, t, a]);
+ }
+
+ function warning(m, t, a, b, c, d) {
+ var ch, l, w;
+ t = t || nexttoken;
+ if (t.id === "(end)") { // `~
+ t = token;
+ }
+ l = t.line || 0;
+ ch = t.from || 0;
+ w = {
+ id: "(error)",
+ raw: m,
+ evidence: lines[l - 1] || "",
+ line: l,
+ character: ch,
+ a: a,
+ b: b,
+ c: c,
+ d: d
+ };
+ w.reason = supplant(m, w);
+ JSHINT.errors.push(w);
+ if (option.passfail) {
+ quit("Stopping. ", l, ch);
+ }
+ warnings += 1;
+ if (warnings >= option.maxerr) {
+ quit("Too many errors.", l, ch);
+ }
+ return w;
+ }
+
+ function warningAt(m, l, ch, a, b, c, d) {
+ return warning(m, {
+ line: l,
+ from: ch
+ }, a, b, c, d);
+ }
+
+ function error(m, t, a, b, c, d) {
+ warning(m, t, a, b, c, d);
+ }
+
+ function errorAt(m, l, ch, a, b, c, d) {
+ return error(m, {
+ line: l,
+ from: ch
+ }, a, b, c, d);
+ }
+
+
+
+// lexical analysis and token construction
+
+ var lex = (function lex() {
+ var character, from, line, s;
+
+// Private lex methods
+
+ function nextLine() {
+ var at,
+ tw; // trailing whitespace check
+
+ if (line >= lines.length)
+ return false;
+
+ character = 1;
+ s = lines[line];
+ line += 1;
+
+ // If smarttabs option is used check for spaces followed by tabs only.
+ // Otherwise check for any occurence of mixed tabs and spaces.
+ // Tabs and one space followed by block comment is allowed.
+ if (option.smarttabs)
+ at = s.search(/ \t/);
+ else
+ at = s.search(/ \t|\t [^\*]/);
+
+ if (at >= 0)
+ warningAt("Mixed spaces and tabs.", line, at + 1);
+
+ s = s.replace(/\t/g, tab);
+ at = s.search(cx);
+
+ if (at >= 0)
+ warningAt("Unsafe character.", line, at);
+
+ if (option.maxlen && option.maxlen < s.length)
+ warningAt("Line too long.", line, s.length);
+
+ // Check for trailing whitespaces
+ tw = option.trailing && s.match(/^(.*?)\s+$/);
+ if (tw && !/^\s+$/.test(s)) {
+ warningAt("Trailing whitespace.", line, tw[1].length + 1);
+ }
+ return true;
+ }
+
+// Produce a token object. The token inherits from a syntax symbol.
+
+ function it(type, value) {
+ var i, t;
+
+ function checkName(name) {
+ if (!option.proto && name === "__proto__") {
+ warningAt("The '{a}' property is deprecated.", line, from, name);
+ return;
+ }
+
+ if (!option.iterator && name === "__iterator__") {
+ warningAt("'{a}' is only available in JavaScript 1.7.", line, from, name);
+ return;
+ }
+
+ // Check for dangling underscores unless we're in Node
+ // environment and this identifier represents built-in
+ // Node globals with underscores.
+
+ var hasDangling = /^(_+.*|.*_+)$/.test(name);
+
+ if (option.nomen && hasDangling && name !== "_") {
+ if (option.node && token.id !== "." && /^(__dirname|__filename)$/.test(name))
+ return;
+
+ warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", name);
+ return;
+ }
+
+ // Check for non-camelcase names. Names like MY_VAR and
+ // _myVar are okay though.
+
+ if (option.camelcase) {
+ if (name.replace(/^_+/, "").indexOf("_") > -1 && !name.match(/^[A-Z0-9_]*$/)) {
+ warningAt("Identifier '{a}' is not in camel case.", line, from, value);
+ }
+ }
+ }
+
+ if (type === "(color)" || type === "(range)") {
+ t = {type: type};
+ } else if (type === "(punctuator)" ||
+ (type === "(identifier)" && is_own(syntax, value))) {
+ t = syntax[value] || syntax["(error)"];
+ } else {
+ t = syntax[type];
+ }
+
+ t = Object.create(t);
+
+ if (type === "(string)" || type === "(range)") {
+ if (!option.scripturl && jx.test(value)) {
+ warningAt("Script URL.", line, from);
+ }
+ }
+
+ if (type === "(identifier)") {
+ t.identifier = true;
+ checkName(value);
+ }
+
+ t.value = value;
+ t.line = line;
+ t.character = character;
+ t.from = from;
+ i = t.id;
+ if (i !== "(endline)") {
+ prereg = i &&
+ (("(,=:[!&|?{};".indexOf(i.charAt(i.length - 1)) >= 0) ||
+ i === "return" ||
+ i === "case");
+ }
+ return t;
+ }
+
+ // Public lex methods
+ return {
+ init: function (source) {
+ if (typeof source === "string") {
+ lines = source
+ .replace(/\r\n/g, "\n")
+ .replace(/\r/g, "\n")
+ .split("\n");
+ } else {
+ lines = source;
+ }
+
+ // If the first line is a shebang (#!), make it a blank and move on.
+ // Shebangs are used by Node scripts.
+ if (lines[0] && lines[0].substr(0, 2) === "#!")
+ lines[0] = "";
+
+ line = 0;
+ nextLine();
+ from = 1;
+ },
+
+ range: function (begin, end) {
+ var c, value = "";
+ from = character;
+ if (s.charAt(0) !== begin) {
+ errorAt("Expected '{a}' and instead saw '{b}'.",
+ line, character, begin, s.charAt(0));
+ }
+ for (;;) {
+ s = s.slice(1);
+ character += 1;
+ c = s.charAt(0);
+ switch (c) {
+ case "":
+ errorAt("Missing '{a}'.", line, character, c);
+ break;
+ case end:
+ s = s.slice(1);
+ character += 1;
+ return it("(range)", value);
+ case "\\":
+ warningAt("Unexpected '{a}'.", line, character, c);
+ }
+ value += c;
+ }
+
+ },
+
+
+ // token -- this is called by advance to get the next token
+ token: function () {
+ var b, c, captures, d, depth, high, i, l, low, q, t, isLiteral, isInRange, n;
+
+ function match(x) {
+ var r = x.exec(s), r1;
+ if (r) {
+ l = r[0].length;
+ r1 = r[1];
+ c = r1.charAt(0);
+ s = s.substr(l);
+ from = character + l - r1.length;
+ character += l;
+ return r1;
+ }
+ }
+
+ function string(x) {
+ var c, j, r = "", allowNewLine = false;
+
+ if (jsonmode && x !== "\"") {
+ warningAt("Strings must use doublequote.",
+ line, character);
+ }
+
+ if (option.quotmark) {
+ if (option.quotmark === "single" && x !== "'") {
+ warningAt("Strings must use singlequote.",
+ line, character);
+ } else if (option.quotmark === "double" && x !== "\"") {
+ warningAt("Strings must use doublequote.",
+ line, character);
+ } else if (option.quotmark === true) {
+ quotmark = quotmark || x;
+ if (quotmark !== x) {
+ warningAt("Mixed double and single quotes.",
+ line, character);
+ }
+ }
+ }
+
+ function esc(n) {
+ var i = parseInt(s.substr(j + 1, n), 16);
+ j += n;
+ if (i >= 32 && i <= 126 &&
+ i !== 34 && i !== 92 && i !== 39) {
+ warningAt("Unnecessary escapement.", line, character);
+ }
+ character += n;
+ c = String.fromCharCode(i);
+ }
+ j = 0;
+unclosedString: for (;;) {
+ while (j >= s.length) {
+ j = 0;
+
+ var cl = line, cf = from;
+ if (!nextLine()) {
+ errorAt("Unclosed string.", cl, cf);
+ break unclosedString;
+ }
+
+ if (allowNewLine) {
+ allowNewLine = false;
+ } else {
+ warningAt("Unclosed string.", cl, cf);
+ }
+ }
+ c = s.charAt(j);
+ if (c === x) {
+ character += 1;
+ s = s.substr(j + 1);
+ return it("(string)", r, x);
+ }
+ if (c < " ") {
+ if (c === "\n" || c === "\r") {
+ break;
+ }
+ warningAt("Control character in string: {a}.",
+ line, character + j, s.slice(0, j));
+ } else if (c === "\\") {
+ j += 1;
+ character += 1;
+ c = s.charAt(j);
+ n = s.charAt(j + 1);
+ switch (c) {
+ case "\\":
+ case "\"":
+ case "/":
+ break;
+ case "\'":
+ if (jsonmode) {
+ warningAt("Avoid \\'.", line, character);
+ }
+ break;
+ case "b":
+ c = "\b";
+ break;
+ case "f":
+ c = "\f";
+ break;
+ case "n":
+ c = "\n";
+ break;
+ case "r":
+ c = "\r";
+ break;
+ case "t":
+ c = "\t";
+ break;
+ case "0":
+ c = "\0";
+ // Octal literals fail in strict mode
+ // check if the number is between 00 and 07
+ // where 'n' is the token next to 'c'
+ if (n >= 0 && n <= 7 && directive["use strict"]) {
+ warningAt(
+ "Octal literals are not allowed in strict mode.",
+ line, character);
+ }
+ break;
+ case "u":
+ esc(4);
+ break;
+ case "v":
+ if (jsonmode) {
+ warningAt("Avoid \\v.", line, character);
+ }
+ c = "\v";
+ break;
+ case "x":
+ if (jsonmode) {
+ warningAt("Avoid \\x-.", line, character);
+ }
+ esc(2);
+ break;
+ case "":
+ // last character is escape character
+ // always allow new line if escaped, but show
+ // warning if option is not set
+ allowNewLine = true;
+ if (option.multistr) {
+ if (jsonmode) {
+ warningAt("Avoid EOL escapement.", line, character);
+ }
+ c = "";
+ character -= 1;
+ break;
+ }
+ warningAt("Bad escapement of EOL. Use option multistr if needed.",
+ line, character);
+ break;
+ default:
+ warningAt("Bad escapement.", line, character);
+ }
+ }
+ r += c;
+ character += 1;
+ j += 1;
+ }
+ }
+
+ for (;;) {
+ if (!s) {
+ return it(nextLine() ? "(endline)" : "(end)", "");
+ }
+ t = match(tx);
+ if (!t) {
+ t = "";
+ c = "";
+ while (s && s < "!") {
+ s = s.substr(1);
+ }
+ if (s) {
+ errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1));
+ s = "";
+ }
+ } else {
+
+ // identifier
+
+ if (isAlpha(c) || c === "_" || c === "$") {
+ return it("(identifier)", t);
+ }
+
+ // number
+
+ if (isDigit(c)) {
+ if (!isFinite(Number(t))) {
+ warningAt("Bad number '{a}'.",
+ line, character, t);
+ }
+ if (isAlpha(s.substr(0, 1))) {
+ warningAt("Missing space after '{a}'.",
+ line, character, t);
+ }
+ if (c === "0") {
+ d = t.substr(1, 1);
+ if (isDigit(d)) {
+ if (token.id !== ".") {
+ warningAt("Don't use extra leading zeros '{a}'.",
+ line, character, t);
+ }
+ } else if (jsonmode && (d === "x" || d === "X")) {
+ warningAt("Avoid 0x-. '{a}'.",
+ line, character, t);
+ }
+ }
+ if (t.substr(t.length - 1) === ".") {
+ warningAt(
+"A trailing decimal point can be confused with a dot '{a}'.", line, character, t);
+ }
+ return it("(number)", t);
+ }
+ switch (t) {
+
+ // string
+
+ case "\"":
+ case "'":
+ return string(t);
+
+ // // comment
+
+ case "//":
+ s = "";
+ token.comment = true;
+ break;
+
+ // /* comment
+
+ case "/*":
+ for (;;) {
+ i = s.search(lx);
+ if (i >= 0) {
+ break;
+ }
+ if (!nextLine()) {
+ errorAt("Unclosed comment.", line, character);
+ }
+ }
+ character += i + 2;
+ if (s.substr(i, 1) === "/") {
+ errorAt("Nested comment.", line, character);
+ }
+ s = s.substr(i + 2);
+ token.comment = true;
+ break;
+
+ // /*members /*jshint /*global
+
+ case "/*members":
+ case "/*member":
+ case "/*jshint":
+ case "/*jslint":
+ case "/*global":
+ case "*/":
+ return {
+ value: t,
+ type: "special",
+ line: line,
+ character: character,
+ from: from
+ };
+
+ case "":
+ break;
+ // /
+ case "/":
+ if (token.id === "/=") {
+ errorAt("A regular expression literal can be confused with '/='.",
+ line, from);
+ }
+ if (prereg) {
+ depth = 0;
+ captures = 0;
+ l = 0;
+ for (;;) {
+ b = true;
+ c = s.charAt(l);
+ l += 1;
+ switch (c) {
+ case "":
+ errorAt("Unclosed regular expression.", line, from);
+ return quit("Stopping.", line, from);
+ case "/":
+ if (depth > 0) {
+ warningAt("{a} unterminated regular expression " +
+ "group(s).", line, from + l, depth);
+ }
+ c = s.substr(0, l - 1);
+ q = {
+ g: true,
+ i: true,
+ m: true
+ };
+ while (q[s.charAt(l)] === true) {
+ q[s.charAt(l)] = false;
+ l += 1;
+ }
+ character += l;
+ s = s.substr(l);
+ q = s.charAt(0);
+ if (q === "/" || q === "*") {
+ errorAt("Confusing regular expression.",
+ line, from);
+ }
+ return it("(regexp)", c);
+ case "\\":
+ c = s.charAt(l);
+ if (c < " ") {
+ warningAt(
+"Unexpected control character in regular expression.", line, from + l);
+ } else if (c === "<") {
+ warningAt(
+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+ }
+ l += 1;
+ break;
+ case "(":
+ depth += 1;
+ b = false;
+ if (s.charAt(l) === "?") {
+ l += 1;
+ switch (s.charAt(l)) {
+ case ":":
+ case "=":
+ case "!":
+ l += 1;
+ break;
+ default:
+ warningAt(
+"Expected '{a}' and instead saw '{b}'.", line, from + l, ":", s.charAt(l));
+ }
+ } else {
+ captures += 1;
+ }
+ break;
+ case "|":
+ b = false;
+ break;
+ case ")":
+ if (depth === 0) {
+ warningAt("Unescaped '{a}'.",
+ line, from + l, ")");
+ } else {
+ depth -= 1;
+ }
+ break;
+ case " ":
+ q = 1;
+ while (s.charAt(l) === " ") {
+ l += 1;
+ q += 1;
+ }
+ if (q > 1) {
+ warningAt(
+"Spaces are hard to count. Use {{a}}.", line, from + l, q);
+ }
+ break;
+ case "[":
+ c = s.charAt(l);
+ if (c === "^") {
+ l += 1;
+ if (option.regexp) {
+ warningAt("Insecure '{a}'.",
+ line, from + l, c);
+ } else if (s.charAt(l) === "]") {
+ errorAt("Unescaped '{a}'.",
+ line, from + l, "^");
+ }
+ }
+ if (c === "]") {
+ warningAt("Empty class.", line,
+ from + l - 1);
+ }
+ isLiteral = false;
+ isInRange = false;
+klass: do {
+ c = s.charAt(l);
+ l += 1;
+ switch (c) {
+ case "[":
+ case "^":
+ warningAt("Unescaped '{a}'.",
+ line, from + l, c);
+ if (isInRange) {
+ isInRange = false;
+ } else {
+ isLiteral = true;
+ }
+ break;
+ case "-":
+ if (isLiteral && !isInRange) {
+ isLiteral = false;
+ isInRange = true;
+ } else if (isInRange) {
+ isInRange = false;
+ } else if (s.charAt(l) === "]") {
+ isInRange = true;
+ } else {
+ if (option.regexdash !== (l === 2 || (l === 3 &&
+ s.charAt(1) === "^"))) {
+ warningAt("Unescaped '{a}'.",
+ line, from + l - 1, "-");
+ }
+ isLiteral = true;
+ }
+ break;
+ case "]":
+ if (isInRange && !option.regexdash) {
+ warningAt("Unescaped '{a}'.",
+ line, from + l - 1, "-");
+ }
+ break klass;
+ case "\\":
+ c = s.charAt(l);
+ if (c < " ") {
+ warningAt(
+"Unexpected control character in regular expression.", line, from + l);
+ } else if (c === "<") {
+ warningAt(
+"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
+ }
+ l += 1;
+
+ // \w, \s and \d are never part of a character range
+ if (/[wsd]/i.test(c)) {
+ if (isInRange) {
+ warningAt("Unescaped '{a}'.",
+ line, from + l, "-");
+ isInRange = false;
+ }
+ isLiteral = false;
+ } else if (isInRange) {
+ isInRange = false;
+ } else {
+ isLiteral = true;
+ }
+ break;
+ case "/":
+ warningAt("Unescaped '{a}'.",
+ line, from + l - 1, "/");
+
+ if (isInRange) {
+ isInRange = false;
+ } else {
+ isLiteral = true;
+ }
+ break;
+ case "<":
+ if (isInRange) {
+ isInRange = false;
+ } else {
+ isLiteral = true;
+ }
+ break;
+ default:
+ if (isInRange) {
+ isInRange = false;
+ } else {
+ isLiteral = true;
+ }
+ }
+ } while (c);
+ break;
+ case ".":
+ if (option.regexp) {
+ warningAt("Insecure '{a}'.", line,
+ from + l, c);
+ }
+ break;
+ case "]":
+ case "?":
+ case "{":
+ case "}":
+ case "+":
+ case "*":
+ warningAt("Unescaped '{a}'.", line,
+ from + l, c);
+ }
+ if (b) {
+ switch (s.charAt(l)) {
+ case "?":
+ case "+":
+ case "*":
+ l += 1;
+ if (s.charAt(l) === "?") {
+ l += 1;
+ }
+ break;
+ case "{":
+ l += 1;
+ c = s.charAt(l);
+ if (c < "0" || c > "9") {
+ warningAt(
+"Expected a number and instead saw '{a}'.", line, from + l, c);
+ }
+ l += 1;
+ low = +c;
+ for (;;) {
+ c = s.charAt(l);
+ if (c < "0" || c > "9") {
+ break;
+ }
+ l += 1;
+ low = +c + (low * 10);
+ }
+ high = low;
+ if (c === ",") {
+ l += 1;
+ high = Infinity;
+ c = s.charAt(l);
+ if (c >= "0" && c <= "9") {
+ l += 1;
+ high = +c;
+ for (;;) {
+ c = s.charAt(l);
+ if (c < "0" || c > "9") {
+ break;
+ }
+ l += 1;
+ high = +c + (high * 10);
+ }
+ }
+ }
+ if (s.charAt(l) !== "}") {
+ warningAt(
+"Expected '{a}' and instead saw '{b}'.", line, from + l, "}", c);
+ } else {
+ l += 1;
+ }
+ if (s.charAt(l) === "?") {
+ l += 1;
+ }
+ if (low > high) {
+ warningAt(
+"'{a}' should not be greater than '{b}'.", line, from + l, low, high);
+ }
+ }
+ }
+ }
+ c = s.substr(0, l - 1);
+ character += l;
+ s = s.substr(l);
+ return it("(regexp)", c);
+ }
+ return it("(punctuator)", t);
+
+ // punctuator
+
+ case "#":
+ return it("(punctuator)", t);
+ default:
+ return it("(punctuator)", t);
+ }
+ }
+ }
+ }
+ };
+ }());
+
+
+ function addlabel(t, type, token) {
+
+ if (t === "hasOwnProperty") {
+ warning("'hasOwnProperty' is a really bad name.");
+ }
+
+ // Define t in the current function in the current scope.
+ if (is_own(funct, t) && !funct["(global)"]) {
+ if (funct[t] === true) {
+ if (option.latedef)
+ warning("'{a}' was used before it was defined.", nexttoken, t);
+ } else {
+ if (!option.shadow && type !== "exception")
+ warning("'{a}' is already defined.", nexttoken, t);
+ }
+ }
+
+ funct[t] = type;
+
+ if (token) {
+ funct["(tokens)"][t] = token;
+ }
+
+ if (funct["(global)"]) {
+ global[t] = funct;
+ if (is_own(implied, t)) {
+ if (option.latedef)
+ warning("'{a}' was used before it was defined.", nexttoken, t);
+ delete implied[t];
+ }
+ } else {
+ scope[t] = funct;
+ }
+ }
+
+
+ function doOption() {
+ var nt = nexttoken;
+ var o = nt.value;
+ var quotmarkValue = option.quotmark;
+ var predef = {};
+ var b, obj, filter, t, tn, v;
+
+ switch (o) {
+ case "*/":
+ error("Unbegun comment.");
+ break;
+ case "/*members":
+ case "/*member":
+ o = "/*members";
+ if (!membersOnly) {
+ membersOnly = {};
+ }
+ obj = membersOnly;
+ option.quotmark = false;
+ break;
+ case "/*jshint":
+ case "/*jslint":
+ obj = option;
+ filter = boolOptions;
+ break;
+ case "/*global":
+ obj = predef;
+ break;
+ default:
+ error("What?");
+ }
+
+ t = lex.token();
+loop: for (;;) {
+ for (;;) {
+ if (t.type === "special" && t.value === "*/") {
+ break loop;
+ }
+ if (t.id !== "(endline)" && t.id !== ",") {
+ break;
+ }
+ t = lex.token();
+ }
+ if (t.type !== "(string)" && t.type !== "(identifier)" &&
+ o !== "/*members") {
+ error("Bad option.", t);
+ }
+
+ v = lex.token();
+ if (v.id === ":") {
+ v = lex.token();
+
+ if (obj === membersOnly) {
+ error("Expected '{a}' and instead saw '{b}'.", t, "*/", ":");
+ }
+
+ if (o === "/*jshint") {
+ checkOption(t.value, t);
+ }
+
+ if (t.value === "indent" && (o === "/*jshint" || o === "/*jslint")) {
+ b = +v.value;
+ if (typeof b !== "number" || !isFinite(b) || b <= 0 ||
+ Math.floor(b) !== b) {
+ error("Expected a small integer and instead saw '{a}'.",
+ v, v.value);
+ }
+ obj.white = true;
+ obj.indent = b;
+ } else if (t.value === "maxerr" && (o === "/*jshint" || o === "/*jslint")) {
+ b = +v.value;
+ if (typeof b !== "number" || !isFinite(b) || b <= 0 ||
+ Math.floor(b) !== b) {
+ error("Expected a small integer and instead saw '{a}'.",
+ v, v.value);
+ }
+ obj.maxerr = b;
+ } else if (t.value === "maxlen" && (o === "/*jshint" || o === "/*jslint")) {
+ b = +v.value;
+ if (typeof b !== "number" || !isFinite(b) || b <= 0 ||
+ Math.floor(b) !== b) {
+ error("Expected a small integer and instead saw '{a}'.",
+ v, v.value);
+ }
+ obj.maxlen = b;
+ } else if (t.value === "validthis") {
+ if (funct["(global)"]) {
+ error("Option 'validthis' can't be used in a global scope.");
+ } else {
+ if (v.value === "true" || v.value === "false")
+ obj[t.value] = v.value === "true";
+ else
+ error("Bad option value.", v);
+ }
+ } else if (t.value === "quotmark" && (o === "/*jshint")) {
+ switch (v.value) {
+ case "true":
+ obj.quotmark = true;
+ break;
+ case "false":
+ obj.quotmark = false;
+ break;
+ case "double":
+ case "single":
+ obj.quotmark = v.value;
+ break;
+ default:
+ error("Bad option value.", v);
+ }
+ } else if (v.value === "true" || v.value === "false") {
+ if (o === "/*jslint") {
+ tn = renamedOptions[t.value] || t.value;
+ obj[tn] = v.value === "true";
+ if (invertedOptions[tn] !== undefined) {
+ obj[tn] = !obj[tn];
+ }
+ } else {
+ obj[t.value] = v.value === "true";
+ }
+ } else {
+ error("Bad option value.", v);
+ }
+ t = lex.token();
+ } else {
+ if (o === "/*jshint" || o === "/*jslint") {
+ error("Missing option value.", t);
+ }
+ obj[t.value] = false;
+ t = v;
+ }
+ }
+
+ if (o === "/*members") {
+ option.quotmark = quotmarkValue;
+ }
+
+ combine(predefined, predef);
+
+ for (var key in predef) {
+ if (is_own(predef, key)) {
+ declared[key] = nt;
+ }
+ }
+
+ if (filter) {
+ assume();
+ }
+ }
+
+
+// We need a peek function. If it has an argument, it peeks that much farther
+// ahead. It is used to distinguish
+// for ( var i in ...
+// from
+// for ( var i = ...
+
+ function peek(p) {
+ var i = p || 0, j = 0, t;
+
+ while (j <= i) {
+ t = lookahead[j];
+ if (!t) {
+ t = lookahead[j] = lex.token();
+ }
+ j += 1;
+ }
+ return t;
+ }
+
+
+
+// Produce the next token. It looks for programming errors.
+
+ function advance(id, t) {
+ switch (token.id) {
+ case "(number)":
+ if (nexttoken.id === ".") {
+ warning("A dot following a number can be confused with a decimal point.", token);
+ }
+ break;
+ case "-":
+ if (nexttoken.id === "-" || nexttoken.id === "--") {
+ warning("Confusing minusses.");
+ }
+ break;
+ case "+":
+ if (nexttoken.id === "+" || nexttoken.id === "++") {
+ warning("Confusing plusses.");
+ }
+ break;
+ }
+
+ if (token.type === "(string)" || token.identifier) {
+ anonname = token.value;
+ }
+
+ if (id && nexttoken.id !== id) {
+ if (t) {
+ if (nexttoken.id === "(end)") {
+ warning("Unmatched '{a}'.", t, t.id);
+ } else {
+ warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
+ nexttoken, id, t.id, t.line, nexttoken.value);
+ }
+ } else if (nexttoken.type !== "(identifier)" ||
+ nexttoken.value !== id) {
+ warning("Expected '{a}' and instead saw '{b}'.",
+ nexttoken, id, nexttoken.value);
+ }
+ }
+
+ prevtoken = token;
+ token = nexttoken;
+ for (;;) {
+ nexttoken = lookahead.shift() || lex.token();
+ if (nexttoken.id === "(end)" || nexttoken.id === "(error)") {
+ return;
+ }
+ if (nexttoken.type === "special") {
+ doOption();
+ } else {
+ if (nexttoken.id !== "(endline)") {
+ break;
+ }
+ }
+ }
+ }
+
+
+// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it
+// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is
+// like .nud except that it is only used on the first token of a statement.
+// Having .fud makes it much easier to define statement-oriented languages like
+// JavaScript. I retained Pratt's nomenclature.
+
+// .nud Null denotation
+// .fud First null denotation
+// .led Left denotation
+// lbp Left binding power
+// rbp Right binding power
+
+// They are elements of the parsing method called Top Down Operator Precedence.
+
+ function expression(rbp, initial) {
+ var left, isArray = false, isObject = false;
+
+ if (nexttoken.id === "(end)")
+ error("Unexpected early end of program.", token);
+
+ advance();
+ if (initial) {
+ anonname = "anonymous";
+ funct["(verb)"] = token.value;
+ }
+ if (initial === true && token.fud) {
+ left = token.fud();
+ } else {
+ if (token.nud) {
+ left = token.nud();
+ } else {
+ if (nexttoken.type === "(number)" && token.id === ".") {
+ warning("A leading decimal point can be confused with a dot: '.{a}'.",
+ token, nexttoken.value);
+ advance();
+ return token;
+ } else {
+ error("Expected an identifier and instead saw '{a}'.",
+ token, token.id);
+ }
+ }
+ while (rbp < nexttoken.lbp) {
+ isArray = token.value === "Array";
+ isObject = token.value === "Object";
+
+ // #527, new Foo.Array(), Foo.Array(), new Foo.Object(), Foo.Object()
+ // Line breaks in IfStatement heads exist to satisfy the checkJSHint
+ // "Line too long." error.
+ if (left && (left.value || (left.first && left.first.value))) {
+ // If the left.value is not "new", or the left.first.value is a "."
+ // then safely assume that this is not "new Array()" and possibly
+ // not "new Object()"...
+ if (left.value !== "new" ||
+ (left.first && left.first.value && left.first.value === ".")) {
+ isArray = false;
+ // ...In the case of Object, if the left.value and token.value
+ // are not equal, then safely assume that this not "new Object()"
+ if (left.value !== token.value) {
+ isObject = false;
+ }
+ }
+ }
+
+ advance();
+ if (isArray && token.id === "(" && nexttoken.id === ")")
+ warning("Use the array literal notation [].", token);
+ if (isObject && token.id === "(" && nexttoken.id === ")")
+ warning("Use the object literal notation {}.", token);
+ if (token.led) {
+ left = token.led(left);
+ } else {
+ error("Expected an operator and instead saw '{a}'.",
+ token, token.id);
+ }
+ }
+ }
+ return left;
+ }
+
+
+// Functions for conformance of style.
+
+ function adjacent(left, right) {
+ left = left || token;
+ right = right || nexttoken;
+ if (option.white) {
+ if (left.character !== right.from && left.line === right.line) {
+ left.from += (left.character - left.from);
+ warning("Unexpected space after '{a}'.", left, left.value);
+ }
+ }
+ }
+
+ function nobreak(left, right) {
+ left = left || token;
+ right = right || nexttoken;
+ if (option.white && (left.character !== right.from || left.line !== right.line)) {
+ warning("Unexpected space before '{a}'.", right, right.value);
+ }
+ }
+
+ function nospace(left, right) {
+ left = left || token;
+ right = right || nexttoken;
+ if (option.white && !left.comment) {
+ if (left.line === right.line) {
+ adjacent(left, right);
+ }
+ }
+ }
+
+ function nonadjacent(left, right) {
+ if (option.white) {
+ left = left || token;
+ right = right || nexttoken;
+ if (left.value === ";" && right.value === ";") {
+ return;
+ }
+ if (left.line === right.line && left.character === right.from) {
+ left.from += (left.character - left.from);
+ warning("Missing space after '{a}'.",
+ left, left.value);
+ }
+ }
+ }
+
+ function nobreaknonadjacent(left, right) {
+ left = left || token;
+ right = right || nexttoken;
+ if (!option.laxbreak && left.line !== right.line) {
+ warning("Bad line breaking before '{a}'.", right, right.id);
+ } else if (option.white) {
+ left = left || token;
+ right = right || nexttoken;
+ if (left.character === right.from) {
+ left.from += (left.character - left.from);
+ warning("Missing space after '{a}'.",
+ left, left.value);
+ }
+ }
+ }
+
+ function indentation(bias) {
+ var i;
+ if (option.white && nexttoken.id !== "(end)") {
+ i = indent + (bias || 0);
+ if (nexttoken.from !== i) {
+ warning(
+"Expected '{a}' to have an indentation at {b} instead at {c}.",
+ nexttoken, nexttoken.value, i, nexttoken.from);
+ }
+ }
+ }
+
+ function nolinebreak(t) {
+ t = t || token;
+ if (t.line !== nexttoken.line) {
+ warning("Line breaking error '{a}'.", t, t.value);
+ }
+ }
+
+
+ function comma() {
+ if (token.line !== nexttoken.line) {
+ if (!option.laxcomma) {
+ if (comma.first) {
+ warning("Comma warnings can be turned off with 'laxcomma'");
+ comma.first = false;
+ }
+ warning("Bad line breaking before '{a}'.", token, nexttoken.id);
+ }
+ } else if (!token.comment && token.character !== nexttoken.from && option.white) {
+ token.from += (token.character - token.from);
+ warning("Unexpected space after '{a}'.", token, token.value);
+ }
+ advance(",");
+ nonadjacent(token, nexttoken);
+ }
+
+
+// Functional constructors for making the symbols that will be inherited by
+// tokens.
+
+ function symbol(s, p) {
+ var x = syntax[s];
+ if (!x || typeof x !== "object") {
+ syntax[s] = x = {
+ id: s,
+ lbp: p,
+ value: s
+ };
+ }
+ return x;
+ }
+
+
+ function delim(s) {
+ return symbol(s, 0);
+ }
+
+
+ function stmt(s, f) {
+ var x = delim(s);
+ x.identifier = x.reserved = true;
+ x.fud = f;
+ return x;
+ }
+
+
+ function blockstmt(s, f) {
+ var x = stmt(s, f);
+ x.block = true;
+ return x;
+ }
+
+
+ function reserveName(x) {
+ var c = x.id.charAt(0);
+ if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z")) {
+ x.identifier = x.reserved = true;
+ }
+ return x;
+ }
+
+
+ function prefix(s, f) {
+ var x = symbol(s, 150);
+ reserveName(x);
+ x.nud = (typeof f === "function") ? f : function () {
+ this.right = expression(150);
+ this.arity = "unary";
+ if (this.id === "++" || this.id === "--") {
+ if (option.plusplus) {
+ warning("Unexpected use of '{a}'.", this, this.id);
+ } else if ((!this.right.identifier || this.right.reserved) &&
+ this.right.id !== "." && this.right.id !== "[") {
+ warning("Bad operand.", this);
+ }
+ }
+ return this;
+ };
+ return x;
+ }
+
+
+ function type(s, f) {
+ var x = delim(s);
+ x.type = s;
+ x.nud = f;
+ return x;
+ }
+
+
+ function reserve(s, f) {
+ var x = type(s, f);
+ x.identifier = x.reserved = true;
+ return x;
+ }
+
+
+ function reservevar(s, v) {
+ return reserve(s, function () {
+ if (typeof v === "function") {
+ v(this);
+ }
+ return this;
+ });
+ }
+
+
+ function infix(s, f, p, w) {
+ var x = symbol(s, p);
+ reserveName(x);
+ x.led = function (left) {
+ if (!w) {
+ nobreaknonadjacent(prevtoken, token);
+ nonadjacent(token, nexttoken);
+ }
+ if (s === "in" && left.id === "!") {
+ warning("Confusing use of '{a}'.", left, "!");
+ }
+ if (typeof f === "function") {
+ return f(left, this);
+ } else {
+ this.left = left;
+ this.right = expression(p);
+ return this;
+ }
+ };
+ return x;
+ }
+
+
+ function relation(s, f) {
+ var x = symbol(s, 100);
+ x.led = function (left) {
+ nobreaknonadjacent(prevtoken, token);
+ nonadjacent(token, nexttoken);
+ var right = expression(100);
+ if ((left && left.id === "NaN") || (right && right.id === "NaN")) {
+ warning("Use the isNaN function to compare with NaN.", this);
+ } else if (f) {
+ f.apply(this, [left, right]);
+ }
+ if (left.id === "!") {
+ warning("Confusing use of '{a}'.", left, "!");
+ }
+ if (right.id === "!") {
+ warning("Confusing use of '{a}'.", right, "!");
+ }
+ this.left = left;
+ this.right = right;
+ return this;
+ };
+ return x;
+ }
+
+
+ function isPoorRelation(node) {
+ return node &&
+ ((node.type === "(number)" && +node.value === 0) ||
+ (node.type === "(string)" && node.value === "") ||
+ (node.type === "null" && !option.eqnull) ||
+ node.type === "true" ||
+ node.type === "false" ||
+ node.type === "undefined");
+ }
+
+
+ function assignop(s) {
+ symbol(s, 20).exps = true;
+ return infix(s, function (left, that) {
+ that.left = left;
+ if (predefined[left.value] === false &&
+ scope[left.value]["(global)"] === true) {
+ warning("Read only.", left);
+ } else if (left["function"]) {
+ warning("'{a}' is a function.", left, left.value);
+ }
+ if (left) {
+ if (option.esnext && funct[left.value] === "const") {
+ warning("Attempting to override '{a}' which is a constant", left, left.value);
+ }
+ if (left.id === "." || left.id === "[") {
+ if (!left.left || left.left.value === "arguments") {
+ warning("Bad assignment.", that);
+ }
+ that.right = expression(19);
+ return that;
+ } else if (left.identifier && !left.reserved) {
+ if (funct[left.value] === "exception") {
+ warning("Do not assign to the exception parameter.", left);
+ }
+ that.right = expression(19);
+ return that;
+ }
+ if (left === syntax["function"]) {
+ warning(
+"Expected an identifier in an assignment and instead saw a function invocation.",
+ token);
+ }
+ }
+ error("Bad assignment.", that);
+ }, 20);
+ }
+
+
+ function bitwise(s, f, p) {
+ var x = symbol(s, p);
+ reserveName(x);
+ x.led = (typeof f === "function") ? f : function (left) {
+ if (option.bitwise) {
+ warning("Unexpected use of '{a}'.", this, this.id);
+ }
+ this.left = left;
+ this.right = expression(p);
+ return this;
+ };
+ return x;
+ }
+
+
+ function bitwiseassignop(s) {
+ symbol(s, 20).exps = true;
+ return infix(s, function (left, that) {
+ if (option.bitwise) {
+ warning("Unexpected use of '{a}'.", that, that.id);
+ }
+ nonadjacent(prevtoken, token);
+ nonadjacent(token, nexttoken);
+ if (left) {
+ if (left.id === "." || left.id === "[" ||
+ (left.identifier && !left.reserved)) {
+ expression(19);
+ return that;
+ }
+ if (left === syntax["function"]) {
+ warning(
+"Expected an identifier in an assignment, and instead saw a function invocation.",
+ token);
+ }
+ return that;
+ }
+ error("Bad assignment.", that);
+ }, 20);
+ }
+
+
+ function suffix(s) {
+ var x = symbol(s, 150);
+ x.led = function (left) {
+ if (option.plusplus) {
+ warning("Unexpected use of '{a}'.", this, this.id);
+ } else if ((!left.identifier || left.reserved) &&
+ left.id !== "." && left.id !== "[") {
+ warning("Bad operand.", this);
+ }
+ this.left = left;
+ return this;
+ };
+ return x;
+ }
+
+
+ // fnparam means that this identifier is being defined as a function
+ // argument (see identifier())
+ function optionalidentifier(fnparam) {
+ if (nexttoken.identifier) {
+ advance();
+ if (token.reserved && !option.es5) {
+ // `undefined` as a function param is a common pattern to protect
+ // against the case when somebody does `undefined = true` and
+ // help with minification. More info: https://gist.github.com/315916
+ if (!fnparam || token.value !== "undefined") {
+ warning("Expected an identifier and instead saw '{a}' (a reserved word).",
+ token, token.id);
+ }
+ }
+ return token.value;
+ }
+ }
+
+ // fnparam means that this identifier is being defined as a function
+ // argument
+ function identifier(fnparam) {
+ var i = optionalidentifier(fnparam);
+ if (i) {
+ return i;
+ }
+ if (token.id === "function" && nexttoken.id === "(") {
+ warning("Missing name in function declaration.");
+ } else {
+ error("Expected an identifier and instead saw '{a}'.",
+ nexttoken, nexttoken.value);
+ }
+ }
+
+
+ function reachable(s) {
+ var i = 0, t;
+ if (nexttoken.id !== ";" || noreach) {
+ return;
+ }
+ for (;;) {
+ t = peek(i);
+ if (t.reach) {
+ return;
+ }
+ if (t.id !== "(endline)") {
+ if (t.id === "function") {
+ if (!option.latedef) {
+ break;
+ }
+ warning(
+"Inner functions should be listed at the top of the outer function.", t);
+ break;
+ }
+ warning("Unreachable '{a}' after '{b}'.", t, t.value, s);
+ break;
+ }
+ i += 1;
+ }
+ }
+
+
+ function statement(noindent) {
+ var i = indent, r, s = scope, t = nexttoken;
+
+ if (t.id === ";") {
+ advance(";");
+ return;
+ }
+
+// Is this a labelled statement?
+
+ if (t.identifier && !t.reserved && peek().id === ":") {
+ advance();
+ advance(":");
+ scope = Object.create(s);
+ addlabel(t.value, "label");
+ if (!nexttoken.labelled) {
+ warning("Label '{a}' on {b} statement.",
+ nexttoken, t.value, nexttoken.value);
+ }
+ if (jx.test(t.value + ":")) {
+ warning("Label '{a}' looks like a javascript url.",
+ t, t.value);
+ }
+ nexttoken.label = t.value;
+ t = nexttoken;
+ }
+
+// Parse the statement.
+
+ if (!noindent) {
+ indentation();
+ }
+ r = expression(0, true);
+
+ // Look for the final semicolon.
+ if (!t.block) {
+ if (!option.expr && (!r || !r.exps)) {
+ warning("Expected an assignment or function call and instead saw an expression.",
+ token);
+ } else if (option.nonew && r.id === "(" && r.left.id === "new") {
+ warning("Do not use 'new' for side effects.");
+ }
+
+ if (nexttoken.id === ",") {
+ return comma();
+ }
+
+ if (nexttoken.id !== ";") {
+ if (!option.asi) {
+ // If this is the last statement in a block that ends on
+ // the same line *and* option lastsemic is on, ignore the warning.
+ // Otherwise, complain about missing semicolon.
+ if (!option.lastsemic || nexttoken.id !== "}" ||
+ nexttoken.line !== token.line) {
+ warningAt("Missing semicolon.", token.line, token.character);
+ }
+ }
+ } else {
+ adjacent(token, nexttoken);
+ advance(";");
+ nonadjacent(token, nexttoken);
+ }
+ }
+
+// Restore the indentation.
+
+ indent = i;
+ scope = s;
+ return r;
+ }
+
+
+ function statements(startLine) {
+ var a = [], p;
+
+ while (!nexttoken.reach && nexttoken.id !== "(end)") {
+ if (nexttoken.id === ";") {
+ p = peek();
+ if (!p || p.id !== "(") {
+ warning("Unnecessary semicolon.");
+ }
+ advance(";");
+ } else {
+ a.push(statement(startLine === nexttoken.line));
+ }
+ }
+ return a;
+ }
+
+
+ /*
+ * read all directives
+ * recognizes a simple form of asi, but always
+ * warns, if it is used
+ */
+ function directives() {
+ var i, p, pn;
+
+ for (;;) {
+ if (nexttoken.id === "(string)") {
+ p = peek(0);
+ if (p.id === "(endline)") {
+ i = 1;
+ do {
+ pn = peek(i);
+ i = i + 1;
+ } while (pn.id === "(endline)");
+
+ if (pn.id !== ";") {
+ if (pn.id !== "(string)" && pn.id !== "(number)" &&
+ pn.id !== "(regexp)" && pn.identifier !== true &&
+ pn.id !== "}") {
+ break;
+ }
+ warning("Missing semicolon.", nexttoken);
+ } else {
+ p = pn;
+ }
+ } else if (p.id === "}") {
+ // directive with no other statements, warn about missing semicolon
+ warning("Missing semicolon.", p);
+ } else if (p.id !== ";") {
+ break;
+ }
+
+ indentation();
+ advance();
+ if (directive[token.value]) {
+ warning("Unnecessary directive \"{a}\".", token, token.value);
+ }
+
+ if (token.value === "use strict") {
+ option.newcap = true;
+ option.undef = true;
+ }
+
+ // there's no directive negation, so always set to true
+ directive[token.value] = true;
+
+ if (p.id === ";") {
+ advance(";");
+ }
+ continue;
+ }
+ break;
+ }
+ }
+
+
+ /*
+ * Parses a single block. A block is a sequence of statements wrapped in
+ * braces.
+ *
+ * ordinary - true for everything but function bodies and try blocks.
+ * stmt - true if block can be a single statement (e.g. in if/for/while).
+ * isfunc - true if block is a function body
+ */
+ function block(ordinary, stmt, isfunc) {
+ var a,
+ b = inblock,
+ old_indent = indent,
+ m,
+ s = scope,
+ t,
+ line,
+ d;
+
+ inblock = ordinary;
+ if (!ordinary || !option.funcscope) scope = Object.create(scope);
+ nonadjacent(token, nexttoken);
+ t = nexttoken;
+
+ if (nexttoken.id === "{") {
+ advance("{");
+ line = token.line;
+ if (nexttoken.id !== "}") {
+ indent += option.indent;
+ while (!ordinary && nexttoken.from > indent) {
+ indent += option.indent;
+ }
+
+ if (isfunc) {
+ m = {};
+ for (d in directive) {
+ if (is_own(directive, d)) {
+ m[d] = directive[d];
+ }
+ }
+ directives();
+
+ if (option.strict && funct["(context)"]["(global)"]) {
+ if (!m["use strict"] && !directive["use strict"]) {
+ warning("Missing \"use strict\" statement.");
+ }
+ }
+ }
+
+ a = statements(line);
+
+ if (isfunc) {
+ directive = m;
+ }
+
+ indent -= option.indent;
+ if (line !== nexttoken.line) {
+ indentation();
+ }
+ } else if (line !== nexttoken.line) {
+ indentation();
+ }
+ advance("}", t);
+ indent = old_indent;
+ } else if (!ordinary) {
+ error("Expected '{a}' and instead saw '{b}'.",
+ nexttoken, "{", nexttoken.value);
+ } else {
+ if (!stmt || option.curly)
+ warning("Expected '{a}' and instead saw '{b}'.",
+ nexttoken, "{", nexttoken.value);
+
+ noreach = true;
+ indent += option.indent;
+ // test indentation only if statement is in new line
+ a = [statement(nexttoken.line === token.line)];
+ indent -= option.indent;
+ noreach = false;
+ }
+ funct["(verb)"] = null;
+ if (!ordinary || !option.funcscope) scope = s;
+ inblock = b;
+ if (ordinary && option.noempty && (!a || a.length === 0)) {
+ warning("Empty block.");
+ }
+ return a;
+ }
+
+
+ function countMember(m) {
+ if (membersOnly && typeof membersOnly[m] !== "boolean") {
+ warning("Unexpected /*member '{a}'.", token, m);
+ }
+ if (typeof member[m] === "number") {
+ member[m] += 1;
+ } else {
+ member[m] = 1;
+ }
+ }
+
+
+ function note_implied(token) {
+ var name = token.value, line = token.line, a = implied[name];
+ if (typeof a === "function") {
+ a = false;
+ }
+
+ if (!a) {
+ a = [line];
+ implied[name] = a;
+ } else if (a[a.length - 1] !== line) {
+ a.push(line);
+ }
+ }
+
+
+ // Build the syntax table by declaring the syntactic elements of the language.
+
+ type("(number)", function () {
+ return this;
+ });
+
+ type("(string)", function () {
+ return this;
+ });
+
+ syntax["(identifier)"] = {
+ type: "(identifier)",
+ lbp: 0,
+ identifier: true,
+ nud: function () {
+ var v = this.value,
+ s = scope[v],
+ f;
+
+ if (typeof s === "function") {
+ // Protection against accidental inheritance.
+ s = undefined;
+ } else if (typeof s === "boolean") {
+ f = funct;
+ funct = functions[0];
+ addlabel(v, "var");
+ s = funct;
+ funct = f;
+ }
+
+ // The name is in scope and defined in the current function.
+ if (funct === s) {
+ // Change 'unused' to 'var', and reject labels.
+ switch (funct[v]) {
+ case "unused":
+ funct[v] = "var";
+ break;
+ case "unction":
+ funct[v] = "function";
+ this["function"] = true;
+ break;
+ case "function":
+ this["function"] = true;
+ break;
+ case "label":
+ warning("'{a}' is a statement label.", token, v);
+ break;
+ }
+ } else if (funct["(global)"]) {
+ // The name is not defined in the function. If we are in the global
+ // scope, then we have an undefined variable.
+ //
+ // Operators typeof and delete do not raise runtime errors even if
+ // the base object of a reference is null so no need to display warning
+ // if we're inside of typeof or delete.
+
+ if (option.undef && typeof predefined[v] !== "boolean") {
+ // Attempting to subscript a null reference will throw an
+ // error, even within the typeof and delete operators
+ if (!(anonname === "typeof" || anonname === "delete") ||
+ (nexttoken && (nexttoken.value === "." || nexttoken.value === "["))) {
+
+ isundef(funct, "'{a}' is not defined.", token, v);
+ }
+ }
+
+ note_implied(token);
+ } else {
+ // If the name is already defined in the current
+ // function, but not as outer, then there is a scope error.
+
+ switch (funct[v]) {
+ case "closure":
+ case "function":
+ case "var":
+ case "unused":
+ warning("'{a}' used out of scope.", token, v);
+ break;
+ case "label":
+ warning("'{a}' is a statement label.", token, v);
+ break;
+ case "outer":
+ case "global":
+ break;
+ default:
+ // If the name is defined in an outer function, make an outer entry,
+ // and if it was unused, make it var.
+ if (s === true) {
+ funct[v] = true;
+ } else if (s === null) {
+ warning("'{a}' is not allowed.", token, v);
+ note_implied(token);
+ } else if (typeof s !== "object") {
+ // Operators typeof and delete do not raise runtime errors even
+ // if the base object of a reference is null so no need to
+ // display warning if we're inside of typeof or delete.
+ if (option.undef) {
+ // Attempting to subscript a null reference will throw an
+ // error, even within the typeof and delete operators
+ if (!(anonname === "typeof" || anonname === "delete") ||
+ (nexttoken &&
+ (nexttoken.value === "." || nexttoken.value === "["))) {
+
+ isundef(funct, "'{a}' is not defined.", token, v);
+ }
+ }
+ funct[v] = true;
+ note_implied(token);
+ } else {
+ switch (s[v]) {
+ case "function":
+ case "unction":
+ this["function"] = true;
+ s[v] = "closure";
+ funct[v] = s["(global)"] ? "global" : "outer";
+ break;
+ case "var":
+ case "unused":
+ s[v] = "closure";
+ funct[v] = s["(global)"] ? "global" : "outer";
+ break;
+ case "closure":
+ funct[v] = s["(global)"] ? "global" : "outer";
+ break;
+ case "label":
+ warning("'{a}' is a statement label.", token, v);
+ }
+ }
+ }
+ }
+ return this;
+ },
+ led: function () {
+ error("Expected an operator and instead saw '{a}'.",
+ nexttoken, nexttoken.value);
+ }
+ };
+
+ type("(regexp)", function () {
+ return this;
+ });
+
+
+// ECMAScript parser
+
+ delim("(endline)");
+ delim("(begin)");
+ delim("(end)").reach = true;
+ delim("</").reach = true;
+ delim("<!");
+ delim("<!--");
+ delim("-->");
+ delim("(error)").reach = true;
+ delim("}").reach = true;
+ delim(")");
+ delim("]");
+ delim("\"").reach = true;
+ delim("'").reach = true;
+ delim(";");
+ delim(":").reach = true;
+ delim(",");
+ delim("#");
+ delim("@");
+ reserve("else");
+ reserve("case").reach = true;
+ reserve("catch");
+ reserve("default").reach = true;
+ reserve("finally");
+ reservevar("arguments", function (x) {
+ if (directive["use strict"] && funct["(global)"]) {
+ warning("Strict violation.", x);
+ }
+ });
+ reservevar("eval");
+ reservevar("false");
+ reservevar("Infinity");
+ reservevar("NaN");
+ reservevar("null");
+ reservevar("this", function (x) {
+ if (directive["use strict"] && !option.validthis && ((funct["(statement)"] &&
+ funct["(name)"].charAt(0) > "Z") || funct["(global)"])) {
+ warning("Possible strict violation.", x);
+ }
+ });
+ reservevar("true");
+ reservevar("undefined");
+ assignop("=", "assign", 20);
+ assignop("+=", "assignadd", 20);
+ assignop("-=", "assignsub", 20);
+ assignop("*=", "assignmult", 20);
+ assignop("/=", "assigndiv", 20).nud = function () {
+ error("A regular expression literal can be confused with '/='.");
+ };
+ assignop("%=", "assignmod", 20);
+ bitwiseassignop("&=", "assignbitand", 20);
+ bitwiseassignop("|=", "assignbitor", 20);
+ bitwiseassignop("^=", "assignbitxor", 20);
+ bitwiseassignop("<<=", "assignshiftleft", 20);
+ bitwiseassignop(">>=", "assignshiftright", 20);
+ bitwiseassignop(">>>=", "assignshiftrightunsigned", 20);
+ infix("?", function (left, that) {
+ that.left = left;
+ that.right = expression(10);
+ advance(":");
+ that["else"] = expression(10);
+ return that;
+ }, 30);
+
+ infix("||", "or", 40);
+ infix("&&", "and", 50);
+ bitwise("|", "bitor", 70);
+ bitwise("^", "bitxor", 80);
+ bitwise("&", "bitand", 90);
+ relation("==", function (left, right) {
+ var eqnull = option.eqnull && (left.value === "null" || right.value === "null");
+
+ if (!eqnull && option.eqeqeq)
+ warning("Expected '{a}' and instead saw '{b}'.", this, "===", "==");
+ else if (isPoorRelation(left))
+ warning("Use '{a}' to compare with '{b}'.", this, "===", left.value);
+ else if (isPoorRelation(right))
+ warning("Use '{a}' to compare with '{b}'.", this, "===", right.value);
+
+ return this;
+ });
+ relation("===");
+ relation("!=", function (left, right) {
+ var eqnull = option.eqnull &&
+ (left.value === "null" || right.value === "null");
+
+ if (!eqnull && option.eqeqeq) {
+ warning("Expected '{a}' and instead saw '{b}'.",
+ this, "!==", "!=");
+ } else if (isPoorRelation(left)) {
+ warning("Use '{a}' to compare with '{b}'.",
+ this, "!==", left.value);
+ } else if (isPoorRelation(right)) {
+ warning("Use '{a}' to compare with '{b}'.",
+ this, "!==", right.value);
+ }
+ return this;
+ });
+ relation("!==");
+ relation("<");
+ relation(">");
+ relation("<=");
+ relation(">=");
+ bitwise("<<", "shiftleft", 120);
+ bitwise(">>", "shiftright", 120);
+ bitwise(">>>", "shiftrightunsigned", 120);
+ infix("in", "in", 120);
+ infix("instanceof", "instanceof", 120);
+ infix("+", function (left, that) {
+ var right = expression(130);
+ if (left && right && left.id === "(string)" && right.id === "(string)") {
+ left.value += right.value;
+ left.character = right.character;
+ if (!option.scripturl && jx.test(left.value)) {
+ warning("JavaScript URL.", left);
+ }
+ return left;
+ }
+ that.left = left;
+ that.right = right;
+ return that;
+ }, 130);
+ prefix("+", "num");
+ prefix("+++", function () {
+ warning("Confusing pluses.");
+ this.right = expression(150);
+ this.arity = "unary";
+ return this;
+ });
+ infix("+++", function (left) {
+ warning("Confusing pluses.");
+ this.left = left;
+ this.right = expression(130);
+ return this;
+ }, 130);
+ infix("-", "sub", 130);
+ prefix("-", "neg");
+ prefix("---", function () {
+ warning("Confusing minuses.");
+ this.right = expression(150);
+ this.arity = "unary";
+ return this;
+ });
+ infix("---", function (left) {
+ warning("Confusing minuses.");
+ this.left = left;
+ this.right = expression(130);
+ return this;
+ }, 130);
+ infix("*", "mult", 140);
+ infix("/", "div", 140);
+ infix("%", "mod", 140);
+
+ suffix("++", "postinc");
+ prefix("++", "preinc");
+ syntax["++"].exps = true;
+
+ suffix("--", "postdec");
+ prefix("--", "predec");
+ syntax["--"].exps = true;
+ prefix("delete", function () {
+ var p = expression(0);
+ if (!p || (p.id !== "." && p.id !== "[")) {
+ warning("Variables should not be deleted.");
+ }
+ this.first = p;
+ return this;
+ }).exps = true;
+
+ prefix("~", function () {
+ if (option.bitwise) {
+ warning("Unexpected '{a}'.", this, "~");
+ }
+ expression(150);
+ return this;
+ });
+
+ prefix("!", function () {
+ this.right = expression(150);
+ this.arity = "unary";
+ if (bang[this.right.id] === true) {
+ warning("Confusing use of '{a}'.", this, "!");
+ }
+ return this;
+ });
+ prefix("typeof", "typeof");
+ prefix("new", function () {
+ var c = expression(155), i;
+ if (c && c.id !== "function") {
+ if (c.identifier) {
+ c["new"] = true;
+ switch (c.value) {
+ case "Number":
+ case "String":
+ case "Boolean":
+ case "Math":
+ case "JSON":
+ warning("Do not use {a} as a constructor.", token, c.value);
+ break;
+ case "Function":
+ if (!option.evil) {
+ warning("The Function constructor is eval.");
+ }
+ break;
+ case "Date":
+ case "RegExp":
+ break;
+ default:
+ if (c.id !== "function") {
+ i = c.value.substr(0, 1);
+ if (option.newcap && (i < "A" || i > "Z")) {
+ warning("A constructor name should start with an uppercase letter.",
+ token);
+ }
+ }
+ }
+ } else {
+ if (c.id !== "." && c.id !== "[" && c.id !== "(") {
+ warning("Bad constructor.", token);
+ }
+ }
+ } else {
+ if (!option.supernew)
+ warning("Weird construction. Delete 'new'.", this);
+ }
+ adjacent(token, nexttoken);
+ if (nexttoken.id !== "(" && !option.supernew) {
+ warning("Missing '()' invoking a constructor.",
+ token, token.value);
+ }
+ this.first = c;
+ return this;
+ });
+ syntax["new"].exps = true;
+
+ prefix("void").exps = true;
+
+ infix(".", function (left, that) {
+ adjacent(prevtoken, token);
+ nobreak();
+ var m = identifier();
+ if (typeof m === "string") {
+ countMember(m);
+ }
+ that.left = left;
+ that.right = m;
+ if (left && left.value === "arguments" && (m === "callee" || m === "caller")) {
+ if (option.noarg)
+ warning("Avoid arguments.{a}.", left, m);
+ else if (directive["use strict"])
+ error("Strict violation.");
+ } else if (!option.evil && left && left.value === "document" &&
+ (m === "write" || m === "writeln")) {
+ warning("document.write can be a form of eval.", left);
+ }
+ if (!option.evil && (m === "eval" || m === "execScript")) {
+ warning("eval is evil.");
+ }
+ return that;
+ }, 160, true);
+
+ infix("(", function (left, that) {
+ if (prevtoken.id !== "}" && prevtoken.id !== ")") {
+ nobreak(prevtoken, token);
+ }
+ nospace();
+ if (option.immed && !left.immed && left.id === "function") {
+ warning("Wrap an immediate function invocation in parentheses " +
+ "to assist the reader in understanding that the expression " +
+ "is the result of a function, and not the function itself.");
+ }
+ var n = 0,
+ p = [];
+ if (left) {
+ if (left.type === "(identifier)") {
+ if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
+ if (left.value !== "Number" && left.value !== "String" &&
+ left.value !== "Boolean" &&
+ left.value !== "Date") {
+ if (left.value === "Math") {
+ warning("Math is not a function.", left);
+ } else if (option.newcap) {
+ warning(
+"Missing 'new' prefix when invoking a constructor.", left);
+ }
+ }
+ }
+ }
+ }
+ if (nexttoken.id !== ")") {
+ for (;;) {
+ p[p.length] = expression(10);
+ n += 1;
+ if (nexttoken.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ }
+ advance(")");
+ nospace(prevtoken, token);
+ if (typeof left === "object") {
+ if (left.value === "parseInt" && n === 1) {
+ warning("Missing radix parameter.", left);
+ }
+ if (!option.evil) {
+ if (left.value === "eval" || left.value === "Function" ||
+ left.value === "execScript") {
+ warning("eval is evil.", left);
+ } else if (p[0] && p[0].id === "(string)" &&
+ (left.value === "setTimeout" ||
+ left.value === "setInterval")) {
+ warning(
+ "Implied eval is evil. Pass a function instead of a string.", left);
+ }
+ }
+ if (!left.identifier && left.id !== "." && left.id !== "[" &&
+ left.id !== "(" && left.id !== "&&" && left.id !== "||" &&
+ left.id !== "?") {
+ warning("Bad invocation.", left);
+ }
+ }
+ that.left = left;
+ return that;
+ }, 155, true).exps = true;
+
+ prefix("(", function () {
+ nospace();
+ if (nexttoken.id === "function") {
+ nexttoken.immed = true;
+ }
+ var v = expression(0);
+ advance(")", this);
+ nospace(prevtoken, token);
+ if (option.immed && v.id === "function") {
+ if (nexttoken.id === "(" ||
+ (nexttoken.id === "." && (peek().value === "call" || peek().value === "apply"))) {
+ warning(
+"Move the invocation into the parens that contain the function.", nexttoken);
+ } else {
+ warning(
+"Do not wrap function literals in parens unless they are to be immediately invoked.",
+ this);
+ }
+ }
+ return v;
+ });
+
+ infix("[", function (left, that) {
+ nobreak(prevtoken, token);
+ nospace();
+ var e = expression(0), s;
+ if (e && e.type === "(string)") {
+ if (!option.evil && (e.value === "eval" || e.value === "execScript")) {
+ warning("eval is evil.", that);
+ }
+ countMember(e.value);
+ if (!option.sub && ix.test(e.value)) {
+ s = syntax[e.value];
+ if (!s || !s.reserved) {
+ warning("['{a}'] is better written in dot notation.",
+ e, e.value);
+ }
+ }
+ }
+ advance("]", that);
+ nospace(prevtoken, token);
+ that.left = left;
+ that.right = e;
+ return that;
+ }, 160, true);
+
+ prefix("[", function () {
+ var b = token.line !== nexttoken.line;
+ this.first = [];
+ if (b) {
+ indent += option.indent;
+ if (nexttoken.from === indent + option.indent) {
+ indent += option.indent;
+ }
+ }
+ while (nexttoken.id !== "(end)") {
+ while (nexttoken.id === ",") {
+ warning("Extra comma.");
+ advance(",");
+ }
+ if (nexttoken.id === "]") {
+ break;
+ }
+ if (b && token.line !== nexttoken.line) {
+ indentation();
+ }
+ this.first.push(expression(10));
+ if (nexttoken.id === ",") {
+ comma();
+ if (nexttoken.id === "]" && !option.es5) {
+ warning("Extra comma.", token);
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if (b) {
+ indent -= option.indent;
+ indentation();
+ }
+ advance("]", this);
+ return this;
+ }, 160);
+
+
+ function property_name() {
+ var id = optionalidentifier(true);
+ if (!id) {
+ if (nexttoken.id === "(string)") {
+ id = nexttoken.value;
+ advance();
+ } else if (nexttoken.id === "(number)") {
+ id = nexttoken.value.toString();
+ advance();
+ }
+ }
+ return id;
+ }
+
+
+ function functionparams() {
+ var i, t = nexttoken, p = [];
+ advance("(");
+ nospace();
+ if (nexttoken.id === ")") {
+ advance(")");
+ return;
+ }
+ for (;;) {
+ i = identifier(true);
+ p.push(i);
+ addlabel(i, "unused", token);
+ if (nexttoken.id === ",") {
+ comma();
+ } else {
+ advance(")", t);
+ nospace(prevtoken, token);
+ return p;
+ }
+ }
+ }
+
+
+ function doFunction(i, statement) {
+ var f,
+ oldOption = option,
+ oldScope = scope;
+
+ option = Object.create(option);
+ scope = Object.create(scope);
+
+ funct = {
+ "(name)" : i || "\"" + anonname + "\"",
+ "(line)" : nexttoken.line,
+ "(character)": nexttoken.character,
+ "(context)" : funct,
+ "(breakage)" : 0,
+ "(loopage)" : 0,
+ "(scope)" : scope,
+ "(statement)": statement,
+ "(tokens)" : {}
+ };
+ f = funct;
+ token.funct = funct;
+ functions.push(funct);
+ if (i) {
+ addlabel(i, "function");
+ }
+ funct["(params)"] = functionparams();
+
+ block(false, false, true);
+ scope = oldScope;
+ option = oldOption;
+ funct["(last)"] = token.line;
+ funct["(lastcharacter)"] = token.character;
+ funct = funct["(context)"];
+ return f;
+ }
+
+
+ (function (x) {
+ x.nud = function () {
+ var b, f, i, p, t;
+ var props = {}; // All properties, including accessors
+
+ function saveProperty(name, token) {
+ if (props[name] && is_own(props, name))
+ warning("Duplicate member '{a}'.", nexttoken, i);
+ else
+ props[name] = {};
+
+ props[name].basic = true;
+ props[name].basicToken = token;
+ }
+
+ function saveSetter(name, token) {
+ if (props[name] && is_own(props, name)) {
+ if (props[name].basic || props[name].setter)
+ warning("Duplicate member '{a}'.", nexttoken, i);
+ } else {
+ props[name] = {};
+ }
+
+ props[name].setter = true;
+ props[name].setterToken = token;
+ }
+
+ function saveGetter(name) {
+ if (props[name] && is_own(props, name)) {
+ if (props[name].basic || props[name].getter)
+ warning("Duplicate member '{a}'.", nexttoken, i);
+ } else {
+ props[name] = {};
+ }
+
+ props[name].getter = true;
+ props[name].getterToken = token;
+ }
+
+ b = token.line !== nexttoken.line;
+ if (b) {
+ indent += option.indent;
+ if (nexttoken.from === indent + option.indent) {
+ indent += option.indent;
+ }
+ }
+ for (;;) {
+ if (nexttoken.id === "}") {
+ break;
+ }
+ if (b) {
+ indentation();
+ }
+ if (nexttoken.value === "get" && peek().id !== ":") {
+ advance("get");
+ if (!option.es5) {
+ error("get/set are ES5 features.");
+ }
+ i = property_name();
+ if (!i) {
+ error("Missing property name.");
+ }
+ saveGetter(i);
+ t = nexttoken;
+ adjacent(token, nexttoken);
+ f = doFunction();
+ p = f["(params)"];
+ if (p) {
+ warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i);
+ }
+ adjacent(token, nexttoken);
+ } else if (nexttoken.value === "set" && peek().id !== ":") {
+ advance("set");
+ if (!option.es5) {
+ error("get/set are ES5 features.");
+ }
+ i = property_name();
+ if (!i) {
+ error("Missing property name.");
+ }
+ saveSetter(i, nexttoken);
+ t = nexttoken;
+ adjacent(token, nexttoken);
+ f = doFunction();
+ p = f["(params)"];
+ if (!p || p.length !== 1) {
+ warning("Expected a single parameter in set {a} function.", t, i);
+ }
+ } else {
+ i = property_name();
+ saveProperty(i, nexttoken);
+ if (typeof i !== "string") {
+ break;
+ }
+ advance(":");
+ nonadjacent(token, nexttoken);
+ expression(10);
+ }
+
+ countMember(i);
+ if (nexttoken.id === ",") {
+ comma();
+ if (nexttoken.id === ",") {
+ warning("Extra comma.", token);
+ } else if (nexttoken.id === "}" && !option.es5) {
+ warning("Extra comma.", token);
+ }
+ } else {
+ break;
+ }
+ }
+ if (b) {
+ indent -= option.indent;
+ indentation();
+ }
+ advance("}", this);
+
+ // Check for lonely setters if in the ES5 mode.
+ if (option.es5) {
+ for (var name in props) {
+ if (is_own(props, name) && props[name].setter && !props[name].getter) {
+ warning("Setter is defined without getter.", props[name].setterToken);
+ }
+ }
+ }
+ return this;
+ };
+ x.fud = function () {
+ error("Expected to see a statement and instead saw a block.", token);
+ };
+ }(delim("{")));
+
+// This Function is called when esnext option is set to true
+// it adds the `const` statement to JSHINT
+
+ useESNextSyntax = function () {
+ var conststatement = stmt("const", function (prefix) {
+ var id, name, value;
+
+ this.first = [];
+ for (;;) {
+ nonadjacent(token, nexttoken);
+ id = identifier();
+ if (funct[id] === "const") {
+ warning("const '" + id + "' has already been declared");
+ }
+ if (funct["(global)"] && predefined[id] === false) {
+ warning("Redefinition of '{a}'.", token, id);
+ }
+ addlabel(id, "const");
+ if (prefix) {
+ break;
+ }
+ name = token;
+ this.first.push(token);
+
+ if (nexttoken.id !== "=") {
+ warning("const " +
+ "'{a}' is initialized to 'undefined'.", token, id);
+ }
+
+ if (nexttoken.id === "=") {
+ nonadjacent(token, nexttoken);
+ advance("=");
+ nonadjacent(token, nexttoken);
+ if (nexttoken.id === "undefined") {
+ warning("It is not necessary to initialize " +
+ "'{a}' to 'undefined'.", token, id);
+ }
+ if (peek(0).id === "=" && nexttoken.identifier) {
+ error("Constant {a} was not declared correctly.",
+ nexttoken, nexttoken.value);
+ }
+ value = expression(0);
+ name.first = value;
+ }
+
+ if (nexttoken.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ return this;
+ });
+ conststatement.exps = true;
+ };
+
+ var varstatement = stmt("var", function (prefix) {
+ // JavaScript does not have block scope. It only has function scope. So,
+ // declaring a variable in a block can have unexpected consequences.
+ var id, name, value;
+
+ if (funct["(onevar)"] && option.onevar) {
+ warning("Too many var statements.");
+ } else if (!funct["(global)"]) {
+ funct["(onevar)"] = true;
+ }
+
+ this.first = [];
+
+ for (;;) {
+ nonadjacent(token, nexttoken);
+ id = identifier();
+
+ if (option.esnext && funct[id] === "const") {
+ warning("const '" + id + "' has already been declared");
+ }
+
+ if (funct["(global)"] && predefined[id] === false) {
+ warning("Redefinition of '{a}'.", token, id);
+ }
+
+ addlabel(id, "unused", token);
+
+ if (prefix) {
+ break;
+ }
+
+ name = token;
+ this.first.push(token);
+
+ if (nexttoken.id === "=") {
+ nonadjacent(token, nexttoken);
+ advance("=");
+ nonadjacent(token, nexttoken);
+ if (nexttoken.id === "undefined") {
+ warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);
+ }
+ if (peek(0).id === "=" && nexttoken.identifier) {
+ error("Variable {a} was not declared correctly.",
+ nexttoken, nexttoken.value);
+ }
+ value = expression(0);
+ name.first = value;
+ }
+ if (nexttoken.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ return this;
+ });
+ varstatement.exps = true;
+
+ blockstmt("function", function () {
+ if (inblock) {
+ warning("Function declarations should not be placed in blocks. " +
+ "Use a function expression or move the statement to the top of " +
+ "the outer function.", token);
+
+ }
+ var i = identifier();
+ if (option.esnext && funct[i] === "const") {
+ warning("const '" + i + "' has already been declared");
+ }
+ adjacent(token, nexttoken);
+ addlabel(i, "unction", token);
+
+ doFunction(i, true);
+ if (nexttoken.id === "(" && nexttoken.line === token.line) {
+ error(
+"Function declarations are not invocable. Wrap the whole function invocation in parens.");
+ }
+ return this;
+ });
+
+ prefix("function", function () {
+ var i = optionalidentifier();
+ if (i) {
+ adjacent(token, nexttoken);
+ } else {
+ nonadjacent(token, nexttoken);
+ }
+ doFunction(i);
+ if (!option.loopfunc && funct["(loopage)"]) {
+ warning("Don't make functions within a loop.");
+ }
+ return this;
+ });
+
+ blockstmt("if", function () {
+ var t = nexttoken;
+ advance("(");
+ nonadjacent(this, t);
+ nospace();
+ expression(20);
+ if (nexttoken.id === "=") {
+ if (!option.boss)
+ warning("Expected a conditional expression and instead saw an assignment.");
+ advance("=");
+ expression(20);
+ }
+ advance(")", t);
+ nospace(prevtoken, token);
+ block(true, true);
+ if (nexttoken.id === "else") {
+ nonadjacent(token, nexttoken);
+ advance("else");
+ if (nexttoken.id === "if" || nexttoken.id === "switch") {
+ statement(true);
+ } else {
+ block(true, true);
+ }
+ }
+ return this;
+ });
+
+ blockstmt("try", function () {
+ var b, e, s;
+
+ block(false);
+ if (nexttoken.id === "catch") {
+ advance("catch");
+ nonadjacent(token, nexttoken);
+ advance("(");
+ s = scope;
+ scope = Object.create(s);
+ e = nexttoken.value;
+ if (nexttoken.type !== "(identifier)") {
+ warning("Expected an identifier and instead saw '{a}'.",
+ nexttoken, e);
+ } else {
+ addlabel(e, "exception");
+ }
+ advance();
+ advance(")");
+ block(false);
+ b = true;
+ scope = s;
+ }
+ if (nexttoken.id === "finally") {
+ advance("finally");
+ block(false);
+ return;
+ } else if (!b) {
+ error("Expected '{a}' and instead saw '{b}'.",
+ nexttoken, "catch", nexttoken.value);
+ }
+ return this;
+ });
+
+ blockstmt("while", function () {
+ var t = nexttoken;
+ funct["(breakage)"] += 1;
+ funct["(loopage)"] += 1;
+ advance("(");
+ nonadjacent(this, t);
+ nospace();
+ expression(20);
+ if (nexttoken.id === "=") {
+ if (!option.boss)
+ warning("Expected a conditional expression and instead saw an assignment.");
+ advance("=");
+ expression(20);
+ }
+ advance(")", t);
+ nospace(prevtoken, token);
+ block(true, true);
+ funct["(breakage)"] -= 1;
+ funct["(loopage)"] -= 1;
+ return this;
+ }).labelled = true;
+
+ blockstmt("with", function () {
+ var t = nexttoken;
+ if (directive["use strict"]) {
+ error("'with' is not allowed in strict mode.", token);
+ } else if (!option.withstmt) {
+ warning("Don't use 'with'.", token);
+ }
+
+ advance("(");
+ nonadjacent(this, t);
+ nospace();
+ expression(0);
+ advance(")", t);
+ nospace(prevtoken, token);
+ block(true, true);
+
+ return this;
+ });
+
+ blockstmt("switch", function () {
+ var t = nexttoken,
+ g = false;
+ funct["(breakage)"] += 1;
+ advance("(");
+ nonadjacent(this, t);
+ nospace();
+ this.condition = expression(20);
+ advance(")", t);
+ nospace(prevtoken, token);
+ nonadjacent(token, nexttoken);
+ t = nexttoken;
+ advance("{");
+ nonadjacent(token, nexttoken);
+ indent += option.indent;
+ this.cases = [];
+ for (;;) {
+ switch (nexttoken.id) {
+ case "case":
+ switch (funct["(verb)"]) {
+ case "break":
+ case "case":
+ case "continue":
+ case "return":
+ case "switch":
+ case "throw":
+ break;
+ default:
+ // You can tell JSHint that you don't use break intentionally by
+ // adding a comment /* falls through */ on a line just before
+ // the next `case`.
+ if (!ft.test(lines[nexttoken.line - 2])) {
+ warning(
+ "Expected a 'break' statement before 'case'.",
+ token);
+ }
+ }
+ indentation(-option.indent);
+ advance("case");
+ this.cases.push(expression(20));
+ g = true;
+ advance(":");
+ funct["(verb)"] = "case";
+ break;
+ case "default":
+ switch (funct["(verb)"]) {
+ case "break":
+ case "continue":
+ case "return":
+ case "throw":
+ break;
+ default:
+ if (!ft.test(lines[nexttoken.line - 2])) {
+ warning(
+ "Expected a 'break' statement before 'default'.",
+ token);
+ }
+ }
+ indentation(-option.indent);
+ advance("default");
+ g = true;
+ advance(":");
+ break;
+ case "}":
+ indent -= option.indent;
+ indentation();
+ advance("}", t);
+ if (this.cases.length === 1 || this.condition.id === "true" ||
+ this.condition.id === "false") {
+ if (!option.onecase)
+ warning("This 'switch' should be an 'if'.", this);
+ }
+ funct["(breakage)"] -= 1;
+ funct["(verb)"] = undefined;
+ return;
+ case "(end)":
+ error("Missing '{a}'.", nexttoken, "}");
+ return;
+ default:
+ if (g) {
+ switch (token.id) {
+ case ",":
+ error("Each value should have its own case label.");
+ return;
+ case ":":
+ g = false;
+ statements();
+ break;
+ default:
+ error("Missing ':' on a case clause.", token);
+ return;
+ }
+ } else {
+ if (token.id === ":") {
+ advance(":");
+ error("Unexpected '{a}'.", token, ":");
+ statements();
+ } else {
+ error("Expected '{a}' and instead saw '{b}'.",
+ nexttoken, "case", nexttoken.value);
+ return;
+ }
+ }
+ }
+ }
+ }).labelled = true;
+
+ stmt("debugger", function () {
+ if (!option.debug) {
+ warning("All 'debugger' statements should be removed.");
+ }
+ return this;
+ }).exps = true;
+
+ (function () {
+ var x = stmt("do", function () {
+ funct["(breakage)"] += 1;
+ funct["(loopage)"] += 1;
+ this.first = block(true);
+ advance("while");
+ var t = nexttoken;
+ nonadjacent(token, t);
+ advance("(");
+ nospace();
+ expression(20);
+ if (nexttoken.id === "=") {
+ if (!option.boss)
+ warning("Expected a conditional expression and instead saw an assignment.");
+ advance("=");
+ expression(20);
+ }
+ advance(")", t);
+ nospace(prevtoken, token);
+ funct["(breakage)"] -= 1;
+ funct["(loopage)"] -= 1;
+ return this;
+ });
+ x.labelled = true;
+ x.exps = true;
+ }());
+
+ blockstmt("for", function () {
+ var s, t = nexttoken;
+ funct["(breakage)"] += 1;
+ funct["(loopage)"] += 1;
+ advance("(");
+ nonadjacent(this, t);
+ nospace();
+ if (peek(nexttoken.id === "var" ? 1 : 0).id === "in") {
+ if (nexttoken.id === "var") {
+ advance("var");
+ varstatement.fud.call(varstatement, true);
+ } else {
+ switch (funct[nexttoken.value]) {
+ case "unused":
+ funct[nexttoken.value] = "var";
+ break;
+ case "var":
+ break;
+ default:
+ warning("Bad for in variable '{a}'.",
+ nexttoken, nexttoken.value);
+ }
+ advance();
+ }
+ advance("in");
+ expression(20);
+ advance(")", t);
+ s = block(true, true);
+ if (option.forin && s && (s.length > 1 || typeof s[0] !== "object" ||
+ s[0].value !== "if")) {
+ warning("The body of a for in should be wrapped in an if statement to filter " +
+ "unwanted properties from the prototype.", this);
+ }
+ funct["(breakage)"] -= 1;
+ funct["(loopage)"] -= 1;
+ return this;
+ } else {
+ if (nexttoken.id !== ";") {
+ if (nexttoken.id === "var") {
+ advance("var");
+ varstatement.fud.call(varstatement);
+ } else {
+ for (;;) {
+ expression(0, "for");
+ if (nexttoken.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ }
+ }
+ nolinebreak(token);
+ advance(";");
+ if (nexttoken.id !== ";") {
+ expression(20);
+ if (nexttoken.id === "=") {
+ if (!option.boss)
+ warning("Expected a conditional expression and instead saw an assignment.");
+ advance("=");
+ expression(20);
+ }
+ }
+ nolinebreak(token);
+ advance(";");
+ if (nexttoken.id === ";") {
+ error("Expected '{a}' and instead saw '{b}'.",
+ nexttoken, ")", ";");
+ }
+ if (nexttoken.id !== ")") {
+ for (;;) {
+ expression(0, "for");
+ if (nexttoken.id !== ",") {
+ break;
+ }
+ comma();
+ }
+ }
+ advance(")", t);
+ nospace(prevtoken, token);
+ block(true, true);
+ funct["(breakage)"] -= 1;
+ funct["(loopage)"] -= 1;
+ return this;
+ }
+ }).labelled = true;
+
+
+ stmt("break", function () {
+ var v = nexttoken.value;
+
+ if (funct["(breakage)"] === 0)
+ warning("Unexpected '{a}'.", nexttoken, this.value);
+
+ if (!option.asi)
+ nolinebreak(this);
+
+ if (nexttoken.id !== ";") {
+ if (token.line === nexttoken.line) {
+ if (funct[v] !== "label") {
+ warning("'{a}' is not a statement label.", nexttoken, v);
+ } else if (scope[v] !== funct) {
+ warning("'{a}' is out of scope.", nexttoken, v);
+ }
+ this.first = nexttoken;
+ advance();
+ }
+ }
+ reachable("break");
+ return this;
+ }).exps = true;
+
+
+ stmt("continue", function () {
+ var v = nexttoken.value;
+
+ if (funct["(breakage)"] === 0)
+ warning("Unexpected '{a}'.", nexttoken, this.value);
+
+ if (!option.asi)
+ nolinebreak(this);
+
+ if (nexttoken.id !== ";") {
+ if (token.line === nexttoken.line) {
+ if (funct[v] !== "label") {
+ warning("'{a}' is not a statement label.", nexttoken, v);
+ } else if (scope[v] !== funct) {
+ warning("'{a}' is out of scope.", nexttoken, v);
+ }
+ this.first = nexttoken;
+ advance();
+ }
+ } else if (!funct["(loopage)"]) {
+ warning("Unexpected '{a}'.", nexttoken, this.value);
+ }
+ reachable("continue");
+ return this;
+ }).exps = true;
+
+
+ stmt("return", function () {
+ if (this.line === nexttoken.line) {
+ if (nexttoken.id === "(regexp)")
+ warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");
+
+ if (nexttoken.id !== ";" && !nexttoken.reach) {
+ nonadjacent(token, nexttoken);
+ if (peek().value === "=" && !option.boss) {
+ warningAt("Did you mean to return a conditional instead of an assignment?",
+ token.line, token.character + 1);
+ }
+ this.first = expression(0);
+ }
+ } else if (!option.asi) {
+ nolinebreak(this); // always warn (Line breaking error)
+ }
+ reachable("return");
+ return this;
+ }).exps = true;
+
+
+ stmt("throw", function () {
+ nolinebreak(this);
+ nonadjacent(token, nexttoken);
+ this.first = expression(20);
+ reachable("throw");
+ return this;
+ }).exps = true;
+
+// Superfluous reserved words
+
+ reserve("class");
+ reserve("const");
+ reserve("enum");
+ reserve("export");
+ reserve("extends");
+ reserve("import");
+ reserve("super");
+
+ reserve("let");
+ reserve("yield");
+ reserve("implements");
+ reserve("interface");
+ reserve("package");
+ reserve("private");
+ reserve("protected");
+ reserve("public");
+ reserve("static");
+
+
+// Parse JSON
+
+ function jsonValue() {
+
+ function jsonObject() {
+ var o = {}, t = nexttoken;
+ advance("{");
+ if (nexttoken.id !== "}") {
+ for (;;) {
+ if (nexttoken.id === "(end)") {
+ error("Missing '}' to match '{' from line {a}.",
+ nexttoken, t.line);
+ } else if (nexttoken.id === "}") {
+ warning("Unexpected comma.", token);
+ break;
+ } else if (nexttoken.id === ",") {
+ error("Unexpected comma.", nexttoken);
+ } else if (nexttoken.id !== "(string)") {
+ warning("Expected a string and instead saw {a}.",
+ nexttoken, nexttoken.value);
+ }
+ if (o[nexttoken.value] === true) {
+ warning("Duplicate key '{a}'.",
+ nexttoken, nexttoken.value);
+ } else if ((nexttoken.value === "__proto__" &&
+ !option.proto) || (nexttoken.value === "__iterator__" &&
+ !option.iterator)) {
+ warning("The '{a}' key may produce unexpected results.",
+ nexttoken, nexttoken.value);
+ } else {
+ o[nexttoken.value] = true;
+ }
+ advance();
+ advance(":");
+ jsonValue();
+ if (nexttoken.id !== ",") {
+ break;
+ }
+ advance(",");
+ }
+ }
+ advance("}");
+ }
+
+ function jsonArray() {
+ var t = nexttoken;
+ advance("[");
+ if (nexttoken.id !== "]") {
+ for (;;) {
+ if (nexttoken.id === "(end)") {
+ error("Missing ']' to match '[' from line {a}.",
+ nexttoken, t.line);
+ } else if (nexttoken.id === "]") {
+ warning("Unexpected comma.", token);
+ break;
+ } else if (nexttoken.id === ",") {
+ error("Unexpected comma.", nexttoken);
+ }
+ jsonValue();
+ if (nexttoken.id !== ",") {
+ break;
+ }
+ advance(",");
+ }
+ }
+ advance("]");
+ }
+
+ switch (nexttoken.id) {
+ case "{":
+ jsonObject();
+ break;
+ case "[":
+ jsonArray();
+ break;
+ case "true":
+ case "false":
+ case "null":
+ case "(number)":
+ case "(string)":
+ advance();
+ break;
+ case "-":
+ advance("-");
+ if (token.character !== nexttoken.from) {
+ warning("Unexpected space after '-'.", token);
+ }
+ adjacent(token, nexttoken);
+ advance("(number)");
+ break;
+ default:
+ error("Expected a JSON value.", nexttoken);
+ }
+ }
+
+
+ // The actual JSHINT function itself.
+ var itself = function (s, o, g) {
+ var a, i, k, x,
+ optionKeys,
+ newOptionObj = {};
+
+ JSHINT.errors = [];
+ JSHINT.undefs = [];
+
+ predefined = Object.create(standard);
+ declared = Object.create(null);
+ combine(predefined, g || {});
+
+ if (!isString(s) && !Array.isArray(s)) {
+ errorAt("Input is neither a string nor an array of strings.", 0);
+ return false;
+ }
+
+ if (isString(s) && /^\s*$/g.test(s)) {
+ errorAt("Input is an empty string.", 0);
+ return false;
+ }
+
+ if (s.length === 0) {
+ errorAt("Input is an empty array.", 0);
+ return false;
+ }
+
+ if (o) {
+ a = o.predef;
+ if (a) {
+ if (Array.isArray(a)) {
+ for (i = 0; i < a.length; i += 1) {
+ predefined[a[i]] = true;
+ }
+ } else if (typeof a === "object") {
+ k = Object.keys(a);
+ for (i = 0; i < k.length; i += 1) {
+ predefined[k[i]] = !!a[k[i]];
+ }
+ }
+ }
+ optionKeys = Object.keys(o);
+ for (x = 0; x < optionKeys.length; x++) {
+ newOptionObj[optionKeys[x]] = o[optionKeys[x]];
+ }
+ }
+
+ option = newOptionObj;
+
+ option.indent = option.indent || 4;
+ option.maxerr = option.maxerr || 50;
+
+ tab = "";
+ for (i = 0; i < option.indent; i += 1) {
+ tab += " ";
+ }
+ indent = 1;
+ global = Object.create(predefined);
+ scope = global;
+ funct = {
+ "(global)": true,
+ "(name)": "(global)",
+ "(scope)": scope,
+ "(breakage)": 0,
+ "(loopage)": 0,
+ "(tokens)": {}
+ };
+ functions = [funct];
+ urls = [];
+ stack = null;
+ member = {};
+ membersOnly = null;
+ implied = {};
+ inblock = false;
+ lookahead = [];
+ jsonmode = false;
+ warnings = 0;
+ lex.init(s);
+ prereg = true;
+ directive = {};
+
+ prevtoken = token = nexttoken = syntax["(begin)"];
+
+ // Check options
+ for (var name in o) {
+ if (is_own(o, name)) {
+ checkOption(name, token);
+ }
+ }
+
+ assume();
+
+ // combine the passed globals after we've assumed all our options
+ combine(predefined, g || {});
+
+ //reset values
+ comma.first = true;
+ quotmark = undefined;
+
+ try {
+ advance();
+ switch (nexttoken.id) {
+ case "{":
+ case "[":
+ option.laxbreak = true;
+ jsonmode = true;
+ jsonValue();
+ break;
+ default:
+ directives();
+ if (directive["use strict"] && !option.globalstrict) {
+ warning("Use the function form of \"use strict\".", prevtoken);
+ }
+
+ statements();
+ }
+ advance((nexttoken && nexttoken.value !== ".") ? "(end)" : undefined);
+
+ var markDefined = function (name, context) {
+ do {
+ if (typeof context[name] === "string") {
+ // JSHINT marks unused variables as 'unused' and
+ // unused function declaration as 'unction'. This
+ // code changes such instances back 'var' and
+ // 'closure' so that the code in JSHINT.data()
+ // doesn't think they're unused.
+
+ if (context[name] === "unused")
+ context[name] = "var";
+ else if (context[name] === "unction")
+ context[name] = "closure";
+
+ return true;
+ }
+
+ context = context["(context)"];
+ } while (context);
+
+ return false;
+ };
+
+ var clearImplied = function (name, line) {
+ if (!implied[name])
+ return;
+
+ var newImplied = [];
+ for (var i = 0; i < implied[name].length; i += 1) {
+ if (implied[name][i] !== line)
+ newImplied.push(implied[name][i]);
+ }
+
+ if (newImplied.length === 0)
+ delete implied[name];
+ else
+ implied[name] = newImplied;
+ };
+
+ var checkUnused = function (func, key) {
+ var type = func[key];
+ var token = func["(tokens)"][key];
+
+ if (key.charAt(0) === "(")
+ return;
+
+ // 'undefined' is a special case for (function (window, undefined) { ... })();
+ // patterns.
+
+ if (key === "undefined")
+ return;
+
+ if (type !== "unused" && type !== "unction")
+ return;
+
+ warningAt("'{a}' is defined but never used.", token.line, token.character, key);
+ };
+
+ // Check queued 'x is not defined' instances to see if they're still undefined.
+ for (i = 0; i < JSHINT.undefs.length; i += 1) {
+ k = JSHINT.undefs[i].slice(0);
+
+ if (markDefined(k[2].value, k[0])) {
+ clearImplied(k[2].value, k[2].line);
+ } else {
+ warning.apply(warning, k.slice(1));
+ }
+ }
+
+ if (option.unused) {
+ functions.forEach(function (func) {
+ for (var key in func) {
+ if (is_own(func, key)) {
+ checkUnused(func, key);
+ }
+ }
+ });
+
+ for (var key in declared) {
+ if (is_own(declared, key)) {
+ if (!is_own(global, key)) {
+ warningAt("'{a}' is defined but never used.",
+ declared[key].line, declared[key].character, key);
+ }
+ }
+ }
+ }
+ } catch (e) {
+ if (e) {
+ var nt = nexttoken || {};
+ JSHINT.errors.push({
+ raw : e.raw,
+ reason : e.message,
+ line : e.line || nt.line,
+ character : e.character || nt.from
+ }, null);
+ }
+ }
+
+ return JSHINT.errors.length === 0;
+ };
+
+ // Data summary.
+ itself.data = function () {
+ var data = {
+ functions: [],
+ options: option
+ };
+ var implieds = [];
+ var members = [];
+ var unused = [];
+ var fu, f, i, j, n, v, globals;
+
+ if (itself.errors.length) {
+ data.errors = itself.errors;
+ }
+
+ if (jsonmode) {
+ data.json = true;
+ }
+
+ for (n in implied) {
+ if (is_own(implied, n)) {
+ implieds.push({
+ name: n,
+ line: implied[n]
+ });
+ }
+ }
+
+ if (implieds.length > 0) {
+ data.implieds = implieds;
+ }
+
+ if (urls.length > 0) {
+ data.urls = urls;
+ }
+
+ globals = Object.keys(scope);
+ if (globals.length > 0) {
+ data.globals = globals;
+ }
+
+ for (i = 1; i < functions.length; i += 1) {
+ f = functions[i];
+ fu = {};
+
+ for (j = 0; j < functionicity.length; j += 1) {
+ fu[functionicity[j]] = [];
+ }
+
+ for (n in f) {
+ if (is_own(f, n) && n.charAt(0) !== "(") {
+ v = f[n];
+
+ if (v === "unction") {
+ v = "unused";
+ }
+
+ if (Array.isArray(fu[v])) {
+ fu[v].push(n);
+
+ if (v === "unused") {
+ unused.push({
+ name: n,
+ line: f["(line)"],
+ "function": f["(name)"]
+ });
+ }
+ }
+ }
+ }
+
+ for (j = 0; j < functionicity.length; j += 1) {
+ if (fu[functionicity[j]].length === 0) {
+ delete fu[functionicity[j]];
+ }
+ }
+
+ fu.name = f["(name)"];
+ fu.param = f["(params)"];
+ fu.line = f["(line)"];
+ fu.character = f["(character)"];
+ fu.last = f["(last)"];
+ fu.lastcharacter = f["(lastcharacter)"];
+ data.functions.push(fu);
+ }
+
+ if (unused.length > 0) {
+ data.unused = unused;
+ }
+
+ members = [];
+ for (n in member) {
+ if (typeof member[n] === "number") {
+ data.member = member;
+ break;
+ }
+ }
+
+ return data;
+ };
+
+ itself.jshint = itself;
+
+ return itself;
+}());
+
+// Make JSHINT a Node module, if possible.
+if (typeof exports === "object" && exports) {
+ exports.JSHINT = JSHINT;
+}
diff --git a/src/fauxton/assets/js/libs/lodash.js b/src/fauxton/assets/js/libs/lodash.js
new file mode 100644
index 000000000..43ddcb208
--- /dev/null
+++ b/src/fauxton/assets/js/libs/lodash.js
@@ -0,0 +1,3554 @@
+/*!
+ * Lo-Dash v0.3.1 <http://lodash.com>
+ * Copyright 2012 John-David Dalton <http://allyoucanleet.com/>
+ * Based on Underscore.js 1.3.3, copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
+ * <http://documentcloud.github.com/underscore>
+ * Available under MIT license <http://lodash.com/license>
+ */
+;(function(window, undefined) {
+ 'use strict';
+
+ /** Detect free variable `exports` */
+ var freeExports = typeof exports == 'object' && exports &&
+ (typeof global == 'object' && global && global == global.global && (window = global), exports);
+
+ /**
+ * Detect the JScript [[DontEnum]] bug:
+ * In IE < 9 an objects own properties, shadowing non-enumerable ones, are
+ * made non-enumerable as well.
+ */
+ var hasDontEnumBug = !{ 'valueOf': 0 }.propertyIsEnumerable('valueOf');
+
+ /** Used to generate unique IDs */
+ var idCounter = 0;
+
+ /** Used to restore the original `_` reference in `noConflict` */
+ var oldDash = window._;
+
+ /** Used to detect if a method is native */
+ var reNative = RegExp('^' + ({}.valueOf + '')
+ .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&')
+ .replace(/valueOf|for [^\]]+/g, '.+?') + '$');
+
+ /** Used to match tokens in template text */
+ var reToken = /__token__(\d+)/g;
+
+ /** Used to match unescaped characters in HTML */
+ var reUnescapedHtml = /[&<"']/g;
+
+ /** Used to match unescaped characters in compiled string literals */
+ var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+ /** Used to fix the JScript [[DontEnum]] bug */
+ var shadowed = [
+ 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
+ 'toLocaleString', 'toString', 'valueOf'
+ ];
+
+ /** Used to make template sourceURLs easier to identify */
+ var templateCounter = 0;
+
+ /** Used to replace template delimiters */
+ var token = '__token__';
+
+ /** Used to store tokenized template text snippets */
+ var tokenized = [];
+
+ /** Detect if sourceURL syntax is usable without erroring */
+ try {
+ // Adobe's and Narwhal's JS engines will error
+ var useSourceURL = (Function('//@')(), true);
+ } catch(e){ }
+
+ /**
+ * Used to escape characters for inclusion in HTML.
+ * The `>` and `/` characters don't require escaping in HTML and have no
+ * special meaning unless they're part of a tag or an unquoted attribute value
+ * http://mathiasbynens.be/notes/ambiguous-ampersands (semi-related fun fact)
+ */
+ var htmlEscapes = {
+ '&': '&amp;',
+ '<': '&lt;',
+ '"': '&quot;',
+ "'": '&#x27;'
+ };
+
+ /** Used to determine if values are of the language type Object */
+ var objectTypes = {
+ 'boolean': false,
+ 'function': true,
+ 'object': true,
+ 'number': false,
+ 'string': false,
+ 'undefined': false
+ };
+
+ /** Used to escape characters for inclusion in compiled string literals */
+ var stringEscapes = {
+ '\\': '\\',
+ "'": "'",
+ '\n': 'n',
+ '\r': 'r',
+ '\t': 't',
+ '\u2028': 'u2028',
+ '\u2029': 'u2029'
+ };
+
+ /** Object#toString result shortcuts */
+ var arrayClass = '[object Array]',
+ boolClass = '[object Boolean]',
+ dateClass = '[object Date]',
+ funcClass = '[object Function]',
+ numberClass = '[object Number]',
+ regexpClass = '[object RegExp]',
+ stringClass = '[object String]';
+
+ /** Native prototype shortcuts */
+ var ArrayProto = Array.prototype,
+ ObjectProto = Object.prototype;
+
+ /** Native method shortcuts */
+ var concat = ArrayProto.concat,
+ hasOwnProperty = ObjectProto.hasOwnProperty,
+ push = ArrayProto.push,
+ slice = ArrayProto.slice,
+ toString = ObjectProto.toString;
+
+ /* Used if `Function#bind` exists and is inferred to be fast (i.e. all but V8) */
+ var nativeBind = reNative.test(nativeBind = slice.bind) &&
+ /\n|Opera/.test(nativeBind + toString.call(window.opera)) && nativeBind;
+
+ /* Native method shortcuts for methods with the same name as other `lodash` methods */
+ var nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray,
+ nativeIsFinite = window.isFinite,
+ nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys;
+
+ /** Timer shortcuts */
+ var clearTimeout = window.clearTimeout,
+ setTimeout = window.setTimeout;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The `lodash` function.
+ *
+ * @name _
+ * @constructor
+ * @param {Mixed} value The value to wrap in a `LoDash` instance.
+ * @returns {Object} Returns a `LoDash` instance.
+ */
+ function lodash(value) {
+ // allow invoking `lodash` without the `new` operator
+ return new LoDash(value);
+ }
+
+ /**
+ * Creates a `LoDash` instance that wraps a value to allow chaining.
+ *
+ * @private
+ * @constructor
+ * @param {Mixed} value The value to wrap.
+ */
+ function LoDash(value) {
+ // exit early if already wrapped
+ if (value && value._wrapped) {
+ return value;
+ }
+ this._wrapped = value;
+ }
+
+ /**
+ * By default, Lo-Dash uses ERB-style template delimiters, change the
+ * following template settings to use alternative delimiters.
+ *
+ * @static
+ * @memberOf _
+ * @type Object
+ */
+ lodash.templateSettings = {
+
+ /**
+ * Used to detect `data` property values to be HTML-escaped.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'escape': /<%-([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect code to be evaluated.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'evaluate': /<%([\s\S]+?)%>/g,
+
+ /**
+ * Used to detect `data` property values to inject.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type RegExp
+ */
+ 'interpolate': /<%=([\s\S]+?)%>/g,
+
+ /**
+ * Used to reference the data object in the template text.
+ *
+ * @static
+ * @memberOf _.templateSettings
+ * @type String
+ */
+ 'variable': 'obj'
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The template used to create iterator functions.
+ *
+ * @private
+ * @param {Obect} data The data object used to populate the text.
+ * @returns {String} Returns the interpolated text.
+ */
+ var iteratorTemplate = template(
+ // assign the `result` variable an initial value
+ 'var index, result<% if (init) { %> = <%= init %><% } %>;\n' +
+ // add code to exit early or do so if the first argument is falsey
+ '<%= exit %>;\n' +
+ // add code after the exit snippet but before the iteration branches
+ '<%= top %>;\n' +
+
+ // the following branch is for iterating arrays and array-like objects
+ '<% if (arrayBranch) { %>' +
+ 'var length = <%= firstArg %>.length; index = -1;' +
+ ' <% if (objectBranch) { %>\nif (length === length >>> 0) {<% } %>\n' +
+ ' <%= arrayBranch.beforeLoop %>;\n' +
+ ' while (<%= arrayBranch.loopExp %>) {\n' +
+ ' <%= arrayBranch.inLoop %>;\n' +
+ ' }' +
+ ' <% if (objectBranch) { %>\n}\n<% }' +
+ '}' +
+
+ // the following branch is for iterating an object's own/inherited properties
+ 'if (objectBranch) {' +
+ ' if (arrayBranch) { %>else {\n<% }' +
+ ' if (!hasDontEnumBug) { %> var skipProto = typeof <%= iteratedObject %> == \'function\';\n<% } %>' +
+ ' <%= objectBranch.beforeLoop %>;\n' +
+ ' for (<%= objectBranch.loopExp %>) {' +
+ ' \n<%' +
+ ' if (hasDontEnumBug) {' +
+ ' if (useHas) { %> if (<%= hasExp %>) {\n <% } %>' +
+ ' <%= objectBranch.inLoop %>;<%' +
+ ' if (useHas) { %>\n }<% }' +
+ ' }' +
+ ' else {' +
+ ' %>' +
+
+ // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+ // (if the prototype or a property on the prototype has been set)
+ // incorrectly sets a function's `prototype` property [[Enumerable]]
+ // value to `true`. Because of this Lo-Dash standardizes on skipping
+ // the the `prototype` property of functions regardless of its
+ // [[Enumerable]] value.
+ ' if (!(skipProto && index == \'prototype\')<% if (useHas) { %> && <%= hasExp %><% } %>) {\n' +
+ ' <%= objectBranch.inLoop %>;\n' +
+ ' }' +
+ ' <% } %>\n' +
+ ' }' +
+
+ // Because IE < 9 can't set the `[[Enumerable]]` attribute of an
+ // existing property and the `constructor` property of a prototype
+ // defaults to non-enumerable, Lo-Dash skips the `constructor`
+ // property when it infers it's iterating over a `prototype` object.
+ ' <% if (hasDontEnumBug) { %>\n' +
+ ' var ctor = <%= iteratedObject %>.constructor;\n' +
+ ' <% for (var k = 0; k < 7; k++) { %>\n' +
+ ' index = \'<%= shadowed[k] %>\';\n' +
+ ' if (<%' +
+ ' if (shadowed[k] == \'constructor\') {' +
+ ' %>!(ctor && ctor.prototype === <%= iteratedObject %>) && <%' +
+ ' } %><%= hasExp %>) {\n' +
+ ' <%= objectBranch.inLoop %>;\n' +
+ ' }<%' +
+ ' }' +
+ ' }' +
+ ' if (arrayBranch) { %>\n}<% }' +
+ '} %>\n' +
+
+ // add code to the bottom of the iteration function
+ '<%= bottom %>;\n' +
+ // finally, return the `result`
+ 'return result'
+ );
+
+ /**
+ * Reusable iterator options shared by
+ * `every`, `filter`, `find`, `forEach`, `forIn`, `forOwn`, `map`, `reject`, and `some`.
+ */
+ var baseIteratorOptions = {
+ 'args': 'collection, callback, thisArg',
+ 'init': 'collection',
+ 'top':
+ 'if (!callback) {\n' +
+ ' callback = identity\n' +
+ '}\n' +
+ 'else if (thisArg) {\n' +
+ ' callback = iteratorBind(callback, thisArg)\n' +
+ '}',
+ 'inLoop': 'callback(collection[index], index, collection)'
+ };
+
+ /** Reusable iterator options for `every` and `some` */
+ var everyIteratorOptions = {
+ 'init': 'true',
+ 'inLoop': 'if (!callback(collection[index], index, collection)) return !result'
+ };
+
+ /** Reusable iterator options for `defaults` and `extend` */
+ var extendIteratorOptions = {
+ 'args': 'object',
+ 'init': 'object',
+ 'top':
+ 'for (var source, sourceIndex = 1, length = arguments.length; sourceIndex < length; sourceIndex++) {\n' +
+ ' source = arguments[sourceIndex];\n' +
+ (hasDontEnumBug ? ' if (source) {' : ''),
+ 'loopExp': 'index in source',
+ 'useHas': false,
+ 'inLoop': 'object[index] = source[index]',
+ 'bottom': (hasDontEnumBug ? ' }\n' : '') + '}'
+ };
+
+ /** Reusable iterator options for `filter` and `reject` */
+ var filterIteratorOptions = {
+ 'init': '[]',
+ 'inLoop': 'callback(collection[index], index, collection) && result.push(collection[index])'
+ };
+
+ /** Reusable iterator options for `find`, `forEach`, `forIn`, and `forOwn` */
+ var forEachIteratorOptions = {
+ 'top': 'if (thisArg) callback = iteratorBind(callback, thisArg)'
+ };
+
+ /** Reusable iterator options for `forIn` and `forOwn` */
+ var forOwnIteratorOptions = {
+ 'inLoop': {
+ 'object': baseIteratorOptions.inLoop
+ }
+ };
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates compiled iteration functions. The iteration function will be created
+ * to iterate over only objects if the first argument of `options.args` is
+ * "object" or `options.inLoop.array` is falsey.
+ *
+ * @private
+ * @param {Object} [options1, options2, ...] The compile options objects.
+ *
+ * args - A string of comma separated arguments the iteration function will
+ * accept.
+ *
+ * init - A string to specify the initial value of the `result` variable.
+ *
+ * exit - A string of code to use in place of the default exit-early check
+ * of `if (!arguments[0]) return result`.
+ *
+ * top - A string of code to execute after the exit-early check but before
+ * the iteration branches.
+ *
+ * beforeLoop - A string or object containing an "array" or "object" property
+ * of code to execute before the array or object loops.
+ *
+ * loopExp - A string or object containing an "array" or "object" property
+ * of code to execute as the array or object loop expression.
+ *
+ * useHas - A boolean to specify whether or not to use `hasOwnProperty` checks
+ * in the object loop.
+ *
+ * inLoop - A string or object containing an "array" or "object" property
+ * of code to execute in the array or object loops.
+ *
+ * bottom - A string of code to execute after the iteration branches but
+ * before the `result` is returned.
+ *
+ * @returns {Function} Returns the compiled function.
+ */
+ function createIterator() {
+ var object,
+ prop,
+ value,
+ index = -1,
+ length = arguments.length;
+
+ // merge options into a template data object
+ var data = {
+ 'bottom': '',
+ 'exit': '',
+ 'init': '',
+ 'top': '',
+ 'arrayBranch': { 'beforeLoop': '', 'loopExp': '++index < length' },
+ 'objectBranch': { 'beforeLoop': '' }
+ };
+
+ while (++index < length) {
+ object = arguments[index];
+ for (prop in object) {
+ value = (value = object[prop]) == null ? '' : value;
+ // keep this regexp explicit for the build pre-process
+ if (/beforeLoop|loopExp|inLoop/.test(prop)) {
+ if (typeof value == 'string') {
+ value = { 'array': value, 'object': value };
+ }
+ data.arrayBranch[prop] = value.array;
+ data.objectBranch[prop] = value.object;
+ } else {
+ data[prop] = value;
+ }
+ }
+ }
+ // set additional template data values
+ var args = data.args,
+ arrayBranch = data.arrayBranch,
+ objectBranch = data.objectBranch,
+ firstArg = /^[^,]+/.exec(args)[0],
+ loopExp = objectBranch.loopExp,
+ iteratedObject = /\S+$/.exec(loopExp || firstArg)[0];
+
+ data.firstArg = firstArg;
+ data.hasDontEnumBug = hasDontEnumBug;
+ data.hasExp = 'hasOwnProperty.call(' + iteratedObject + ', index)';
+ data.iteratedObject = iteratedObject;
+ data.shadowed = shadowed;
+ data.useHas = data.useHas !== false;
+
+ if (!data.exit) {
+ data.exit = 'if (!' + firstArg + ') return result';
+ }
+ if (firstArg == 'object' || !arrayBranch.inLoop) {
+ data.arrayBranch = null;
+ }
+ if (!loopExp) {
+ objectBranch.loopExp = 'index in ' + iteratedObject;
+ }
+ // create the function factory
+ var factory = Function(
+ 'arrayClass, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes, ' +
+ 'stringClass, toString, undefined',
+ '"use strict"; return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}'
+ );
+ // return the compiled function
+ return factory(
+ arrayClass, funcClass, hasOwnProperty, identity, iteratorBind, objectTypes,
+ stringClass, toString
+ );
+ }
+
+ /**
+ * Used by `template()` to replace tokens with their corresponding code snippets.
+ *
+ * @private
+ * @param {String} match The matched token.
+ * @param {String} index The `tokenized` index of the code snippet.
+ * @returns {String} Returns the code snippet.
+ */
+ function detokenize(match, index) {
+ return tokenized[index];
+ }
+
+ /**
+ * Used by `template()` to escape characters for inclusion in compiled
+ * string literals.
+ *
+ * @private
+ * @param {String} match The matched character to escape.
+ * @returns {String} Returns the escaped character.
+ */
+ function escapeStringChar(match) {
+ return '\\' + stringEscapes[match];
+ }
+
+ /**
+ * Used by `escape()` to escape characters for inclusion in HTML.
+ *
+ * @private
+ * @param {String} match The matched character to escape.
+ * @returns {String} Returns the escaped character.
+ */
+ function escapeHtmlChar(match) {
+ return htmlEscapes[match];
+ }
+
+ /**
+ * Creates a new function that, when called, invokes `func` with the `this`
+ * binding of `thisArg` and the arguments (value, index, object).
+ *
+ * @private
+ * @param {Function} func The function to bind.
+ * @param {Mixed} [thisArg] The `this` binding of `func`.
+ * @returns {Function} Returns the new bound function.
+ */
+ function iteratorBind(func, thisArg) {
+ return function(value, index, object) {
+ return func.call(thisArg, value, index, object);
+ };
+ }
+
+ /**
+ * A no-operation function.
+ *
+ * @private
+ */
+ function noop() {
+ // no operation performed
+ }
+
+ /**
+ * A shim implementation of `Object.keys` that produces an array of the given
+ * object's own enumerable property names.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property names.
+ */
+ var shimKeys = createIterator({
+ 'args': 'object',
+ 'exit': 'if (!objectTypes[typeof object] || object === null) throw TypeError()',
+ 'init': '[]',
+ 'inLoop': 'result.push(index)'
+ });
+
+ /**
+ * Used by `template()` to replace "escape" template delimiters with tokens.
+ *
+ * @private
+ * @param {String} match The matched template delimiter.
+ * @param {String} value The delimiter value.
+ * @returns {String} Returns a token.
+ */
+ function tokenizeEscape(match, value) {
+ var index = tokenized.length;
+ tokenized[index] = "'+\n_.escape(" + value + ") +\n'";
+ return token + index;
+ }
+
+ /**
+ * Used by `template()` to replace "interpolate" template delimiters with tokens.
+ *
+ * @private
+ * @param {String} match The matched template delimiter.
+ * @param {String} value The delimiter value.
+ * @returns {String} Returns a token.
+ */
+ function tokenizeInterpolate(match, value) {
+ var index = tokenized.length;
+ tokenized[index] = "'+\n((__t = (" + value + ")) == null ? '' : __t) +\n'";
+ return token + index;
+ }
+
+ /**
+ * Used by `template()` to replace "evaluate" template delimiters with tokens.
+ *
+ * @private
+ * @param {String} match The matched template delimiter.
+ * @param {String} value The delimiter value.
+ * @returns {String} Returns a token.
+ */
+ function tokenizeEvaluate(match, value) {
+ var index = tokenized.length;
+ tokenized[index] = "';\n" + value + ";\n__p += '";
+ return token + index;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Checks if a given `target` value is present in a `collection` using strict
+ * equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @alias include
+ * @category Collections
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Mixed} target The value to check for.
+ * @returns {Boolean} Returns `true` if `target` value is found, else `false`.
+ * @example
+ *
+ * _.contains([1, 2, 3], 3);
+ * // => true
+ */
+ var contains = createIterator({
+ 'args': 'collection, target',
+ 'init': 'false',
+ 'inLoop': 'if (collection[index] === target) return true'
+ });
+
+ /**
+ * Checks if the `callback` returns a truthy value for **all** elements of a
+ * `collection`. The `callback` is bound to `thisArg` and invoked with 3
+ * arguments; for arrays they are (value, index, array) and for objects they
+ * are (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @alias all
+ * @category Collections
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Boolean} Returns `true` if all values pass the callback check, else `false`.
+ * @example
+ *
+ * _.every([true, 1, null, 'yes'], Boolean);
+ * // => false
+ */
+ var every = createIterator(baseIteratorOptions, everyIteratorOptions);
+
+ /**
+ * Examines each value in a `collection`, returning an array of all values the
+ * `callback` returns truthy for. The `callback` is bound to `thisArg` and
+ * invoked with 3 arguments; for arrays they are (value, index, array) and for
+ * objects they are (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @alias select
+ * @category Collections
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array} Returns a new array of values that passed callback check.
+ * @example
+ *
+ * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [2, 4, 6]
+ */
+ var filter = createIterator(baseIteratorOptions, filterIteratorOptions);
+
+ /**
+ * Examines each value in a `collection`, returning the first one the `callback`
+ * returns truthy for. The function returns as soon as it finds an acceptable
+ * value, and does not iterate over the entire `collection`. The `callback` is
+ * bound to `thisArg` and invoked with 3 arguments; for arrays they are
+ * (value, index, array) and for objects they are (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @alias detect
+ * @category Collections
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Mixed} Returns the value that passed the callback check, else `undefined`.
+ * @example
+ *
+ * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => 2
+ */
+ var find = createIterator(baseIteratorOptions, forEachIteratorOptions, {
+ 'init': '',
+ 'inLoop': 'if (callback(collection[index], index, collection)) return collection[index]'
+ });
+
+ /**
+ * Iterates over a `collection`, executing the `callback` for each value in the
+ * `collection`. The `callback` is bound to `thisArg` and invoked with 3
+ * arguments; for arrays they are (value, index, array) and for objects they
+ * are (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @alias each
+ * @category Collections
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array|Object} Returns the `collection`.
+ * @example
+ *
+ * _([1, 2, 3]).forEach(alert).join(',');
+ * // => alerts each number and returns '1,2,3'
+ *
+ * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert);
+ * // => alerts each number (order is not guaranteed)
+ */
+ var forEach = createIterator(baseIteratorOptions, forEachIteratorOptions);
+
+ /**
+ * Produces a new array of values by mapping each value in the `collection`
+ * through a transformation `callback`. The `callback` is bound to `thisArg`
+ * and invoked with 3 arguments; for arrays they are (value, index, array)
+ * and for objects they are (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @alias collect
+ * @category Collections
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array} Returns a new array of values returned by the callback.
+ * @example
+ *
+ * _.map([1, 2, 3], function(num) { return num * 3; });
+ * // => [3, 6, 9]
+ *
+ * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
+ * // => [3, 6, 9] (order is not guaranteed)
+ */
+ var map = createIterator(baseIteratorOptions, {
+ 'init': '',
+ 'exit': 'if (!collection) return []',
+ 'beforeLoop': {
+ 'array': 'result = Array(length)',
+ 'object': 'result = []'
+ },
+ 'inLoop': {
+ 'array': 'result[index] = callback(collection[index], index, collection)',
+ 'object': 'result.push(callback(collection[index], index, collection))'
+ }
+ });
+
+ /**
+ * Boils down a `collection` to a single value. The initial state of the
+ * reduction is `accumulator` and each successive step of it should be returned
+ * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4
+ * arguments; for arrays they are (accumulator, value, index, array) and for
+ * objects they are (accumulator, value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @alias foldl, inject
+ * @category Collections
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [accumulator] Initial value of the accumulator.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Mixed} Returns the accumulated value.
+ * @example
+ *
+ * var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; });
+ * // => 6
+ */
+ var reduce = createIterator({
+ 'args': 'collection, callback, accumulator, thisArg',
+ 'init': 'accumulator',
+ 'top':
+ 'var noaccum = arguments.length < 3;\n' +
+ 'if (thisArg) callback = iteratorBind(callback, thisArg)',
+ 'beforeLoop': {
+ 'array': 'if (noaccum) result = collection[++index]'
+ },
+ 'inLoop': {
+ 'array':
+ 'result = callback(result, collection[index], index, collection)',
+ 'object':
+ 'result = noaccum\n' +
+ ' ? (noaccum = false, collection[index])\n' +
+ ' : callback(result, collection[index], index, collection)'
+ }
+ });
+
+ /**
+ * The right-associative version of `_.reduce`.
+ *
+ * @static
+ * @memberOf _
+ * @alias foldr
+ * @category Collections
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [accumulator] Initial value of the accumulator.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Mixed} Returns the accumulated value.
+ * @example
+ *
+ * var list = [[0, 1], [2, 3], [4, 5]];
+ * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
+ * // => [4, 5, 2, 3, 0, 1]
+ */
+ function reduceRight(collection, callback, accumulator, thisArg) {
+ if (!collection) {
+ return accumulator;
+ }
+
+ var length = collection.length,
+ noaccum = arguments.length < 3;
+
+ if(thisArg) {
+ callback = iteratorBind(callback, thisArg);
+ }
+ if (length === length >>> 0) {
+ if (length && noaccum) {
+ accumulator = collection[--length];
+ }
+ while (length--) {
+ accumulator = callback(accumulator, collection[length], length, collection);
+ }
+ return accumulator;
+ }
+
+ var prop,
+ props = keys(collection);
+
+ length = props.length;
+ if (length && noaccum) {
+ accumulator = collection[props[--length]];
+ }
+ while (length--) {
+ prop = props[length];
+ accumulator = callback(accumulator, collection[prop], prop, collection);
+ }
+ return accumulator;
+ }
+
+ /**
+ * The opposite of `_.filter`, this method returns the values of a `collection`
+ * that `callback` does **not** return truthy for.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array} Returns a new array of values that did **not** pass the callback check.
+ * @example
+ *
+ * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
+ * // => [1, 3, 5]
+ */
+ var reject = createIterator(baseIteratorOptions, filterIteratorOptions, {
+ 'inLoop': '!' + filterIteratorOptions.inLoop
+ });
+
+ /**
+ * Checks if the `callback` returns a truthy value for **any** element of a
+ * `collection`. The function returns as soon as it finds passing value, and
+ * does not iterate over the entire `collection`. The `callback` is bound to
+ * `thisArg` and invoked with 3 arguments; for arrays they are
+ * (value, index, array) and for objects they are (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @alias any
+ * @category Collections
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Boolean} Returns `true` if any value passes the callback check, else `false`.
+ * @example
+ *
+ * _.some([null, 0, 'yes', false]);
+ * // => true
+ */
+ var some = createIterator(baseIteratorOptions, everyIteratorOptions, {
+ 'init': 'false',
+ 'inLoop': everyIteratorOptions.inLoop.replace('!', '')
+ });
+
+ /**
+ * Converts the `collection`, into an array. Useful for converting the
+ * `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Collections
+ * @param {Array|Object} collection The collection to convert.
+ * @returns {Array} Returns the new converted array.
+ * @example
+ *
+ * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
+ * // => [2, 3, 4]
+ */
+ function toArray(collection) {
+ if (!collection) {
+ return [];
+ }
+ if (toString.call(collection.toArray) == funcClass) {
+ return collection.toArray();
+ }
+ var length = collection.length;
+ if (length === length >>> 0) {
+ return slice.call(collection);
+ }
+ return values(collection);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Produces a new array with all falsey values of `array` removed. The values
+ * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to compact.
+ * @returns {Array} Returns a new filtered array.
+ * @example
+ *
+ * _.compact([0, 1, false, 2, '', 3]);
+ * // => [1, 2, 3]
+ */
+ function compact(array) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ if (array[index]) {
+ result.push(array[index]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Produces a new array of `array` values not present in the other arrays
+ * using strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {Array} [array1, array2, ...] Arrays to check.
+ * @returns {Array} Returns a new array of `array` values not present in the
+ * other arrays.
+ * @example
+ *
+ * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
+ * // => [1, 3, 4]
+ */
+ function difference(array) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var index = -1,
+ length = array.length,
+ flattened = concat.apply(result, slice.call(arguments, 1));
+
+ while (++index < length) {
+ if (indexOf(flattened, array[index]) < 0) {
+ result.push(array[index]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the first value of the `array`. Pass `n` to return the first `n` values
+ * of the `array`.
+ *
+ * @static
+ * @memberOf _
+ * @alias head, take
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n] The number of elements to return.
+ * @param {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Mixed} Returns the first value or an array of the first `n` values
+ * of `array`.
+ * @example
+ *
+ * _.first([5, 4, 3, 2, 1]);
+ * // => 5
+ */
+ function first(array, n, guard) {
+ if (array) {
+ return (n == undefined || guard) ? array[0] : slice.call(array, 0, n);
+ }
+ }
+
+ /**
+ * Flattens a nested array (the nesting can be to any depth). If `shallow` is
+ * truthy, `array` will only be flattened a single level.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to compact.
+ * @param {Boolean} shallow A flag to indicate only flattening a single level.
+ * @returns {Array} Returns a new flattened array.
+ * @example
+ *
+ * _.flatten([1, [2], [3, [[4]]]]);
+ * // => [1, 2, 3, 4];
+ *
+ * _.flatten([1, [2], [3, [[4]]]], true);
+ * // => [1, 2, 3, [[4]]];
+ */
+ function flatten(array, shallow) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var value,
+ index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ value = array[index];
+ if (isArray(value)) {
+ push.apply(result, shallow ? value : flatten(value));
+ } else {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Splits `array` into sets, grouped by the result of running each value
+ * through `callback`. The `callback` is bound to `thisArg` and invoked with 3
+ * arguments; (value, index, array). The `callback` argument may also be the
+ * name of a property to group by.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {Function|String} callback The function called per iteration or
+ * property name to group by.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Object} Returns an object of grouped values.
+ * @example
+ *
+ * _.groupBy([1.3, 2.1, 2.4], function(num) { return Math.floor(num); });
+ * // => { '1': [1.3], '2': [2.1, 2.4] }
+ *
+ * _.groupBy([1.3, 2.1, 2.4], function(num) { return this.floor(num); }, Math);
+ * // => { '1': [1.3], '2': [2.1, 2.4] }
+ *
+ * _.groupBy(['one', 'two', 'three'], 'length');
+ * // => { '3': ['one', 'two'], '5': ['three'] }
+ */
+ function groupBy(array, callback, thisArg) {
+ var result = {};
+ if (!array) {
+ return result;
+ }
+ var prop,
+ value,
+ index = -1,
+ isFunc = typeof callback == 'function',
+ length = array.length;
+
+ if (isFunc && thisArg) {
+ callback = iteratorBind(callback, thisArg);
+ }
+ while (++index < length) {
+ value = array[index];
+ prop = isFunc ? callback(value, index, array) : value[callback];
+ (hasOwnProperty.call(result, prop) ? result[prop] : result[prop] = []).push(value);
+ }
+ return result;
+ }
+
+ /**
+ * Gets the index at which the first occurrence of `value` is found using
+ * strict equality for comparisons, i.e. `===`. If the `array` is already
+ * sorted, passing `true` for `isSorted` will run a faster binary search.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Mixed} value The value to search for.
+ * @param {Boolean|Number} [fromIndex=0] The index to start searching from or
+ * `true` to perform a binary search on a sorted `array`.
+ * @returns {Number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 1
+ *
+ * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 4
+ *
+ * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
+ * // => 2
+ */
+ function indexOf(array, value, fromIndex) {
+ if (!array) {
+ return -1;
+ }
+ var index = -1,
+ length = array.length;
+
+ if (fromIndex) {
+ if (typeof fromIndex == 'number') {
+ index = (fromIndex < 0 ? Math.max(0, length + fromIndex) : fromIndex) - 1;
+ } else {
+ index = sortedIndex(array, value);
+ return array[index] === value ? index : -1;
+ }
+ }
+ while (++index < length) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Gets all but the last value of `array`. Pass `n` to exclude the last `n`
+ * values from the result.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n] The number of elements to return.
+ * @param {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Array} Returns all but the last value or `n` values of `array`.
+ * @example
+ *
+ * _.initial([3, 2, 1]);
+ * // => [3, 2]
+ */
+ function initial(array, n, guard) {
+ if (!array) {
+ return [];
+ }
+ return slice.call(array, 0, -((n == undefined || guard) ? 1 : n));
+ }
+
+ /**
+ * Computes the intersection of all the passed-in arrays.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} [array1, array2, ...] Arrays to process.
+ * @returns {Array} Returns a new array of unique values, in order, that are
+ * present in **all** of the arrays.
+ * @example
+ *
+ * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
+ * // => [1, 2]
+ */
+ function intersection(array) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var value,
+ index = -1,
+ length = array.length,
+ others = slice.call(arguments, 1);
+
+ while (++index < length) {
+ value = array[index];
+ if (indexOf(result, value) < 0 &&
+ every(others, function(other) { return indexOf(other, value) > -1; })) {
+ result.push(value);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Invokes the method named by `methodName` on each element of `array`.
+ * Additional arguments will be passed to each invoked method. If `methodName`
+ * is a function it will be invoked for, and `this` bound to, each element
+ * of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {Function|String} methodName The name of the method to invoke or
+ * the function invoked per iteration.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+ * @returns {Array} Returns a new array of values returned from each invoked method.
+ * @example
+ *
+ * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+ * // => [[1, 5, 7], [1, 2, 3]]
+ *
+ * _.invoke([123, 456], String.prototype.split, '');
+ * // => [['1', '2', '3'], ['4', '5', '6']]
+ */
+ function invoke(array, methodName) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var args = slice.call(arguments, 2),
+ index = -1,
+ length = array.length,
+ isFunc = typeof methodName == 'function';
+
+ while (++index < length) {
+ result[index] = (isFunc ? methodName : array[index][methodName]).apply(array[index], args);
+ }
+ return result;
+ }
+
+ /**
+ * Gets the last value of the `array`. Pass `n` to return the lasy `n` values
+ * of the `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n] The number of elements to return.
+ * @param {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Mixed} Returns the last value or an array of the last `n` values
+ * of `array`.
+ * @example
+ *
+ * _.last([3, 2, 1]);
+ * // => 1
+ */
+ function last(array, n, guard) {
+ if (array) {
+ var length = array.length;
+ return (n == undefined || guard) ? array[length - 1] : slice.call(array, -n || length);
+ }
+ }
+
+ /**
+ * Gets the index at which the last occurrence of `value` is found using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to search.
+ * @param {Mixed} value The value to search for.
+ * @param {Number} [fromIndex=array.length-1] The index to start searching from.
+ * @returns {Number} Returns the index of the matched value or `-1`.
+ * @example
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
+ * // => 4
+ *
+ * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
+ * // => 1
+ */
+ function lastIndexOf(array, value, fromIndex) {
+ if (!array) {
+ return -1;
+ }
+ var index = array.length;
+ if (fromIndex && typeof fromIndex == 'number') {
+ index = (fromIndex < 0 ? Math.max(0, index + fromIndex) : Math.min(fromIndex, index - 1)) + 1;
+ }
+ while (index--) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Retrieves the maximum value of an `array`. If `callback` is passed,
+ * it will be executed for each value in the `array` to generate the
+ * criterion by which the value is ranked. The `callback` is bound to
+ * `thisArg` and invoked with 3 arguments; (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [callback] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Mixed} Returns the maximum value.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * _.max(stooges, function(stooge) { return stooge.age; });
+ * // => { 'name': 'curly', 'age': 60 };
+ */
+ function max(array, callback, thisArg) {
+ var computed = -Infinity,
+ result = computed;
+
+ if (!array) {
+ return result;
+ }
+ var current,
+ index = -1,
+ length = array.length;
+
+ if (!callback) {
+ while (++index < length) {
+ if (array[index] > result) {
+ result = array[index];
+ }
+ }
+ return result;
+ }
+ if (thisArg) {
+ callback = iteratorBind(callback, thisArg);
+ }
+ while (++index < length) {
+ current = callback(array[index], index, array);
+ if (current > computed) {
+ computed = current;
+ result = array[index];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the minimum value of an `array`. If `callback` is passed,
+ * it will be executed for each value in the `array` to generate the
+ * criterion by which the value is ranked. The `callback` is bound to `thisArg`
+ * and invoked with 3 arguments; (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {Function} [callback] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Mixed} Returns the minimum value.
+ * @example
+ *
+ * _.min([10, 5, 100, 2, 1000]);
+ * // => 2
+ */
+ function min(array, callback, thisArg) {
+ var computed = Infinity,
+ result = computed;
+
+ if (!array) {
+ return result;
+ }
+ var current,
+ index = -1,
+ length = array.length;
+
+ if (!callback) {
+ while (++index < length) {
+ if (array[index] < result) {
+ result = array[index];
+ }
+ }
+ return result;
+ }
+ if (thisArg) {
+ callback = iteratorBind(callback, thisArg);
+ }
+ while (++index < length) {
+ current = callback(array[index], index, array);
+ if (current < computed) {
+ computed = current;
+ result = array[index];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Retrieves the value of a specified property from all elements in `array`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {String} property The property to pluck.
+ * @returns {Array} Returns a new array of property values.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * _.pluck(stooges, 'name');
+ * // => ['moe', 'larry', 'curly']
+ */
+ function pluck(array, property) {
+ if (!array) {
+ return [];
+ }
+ var index = -1,
+ length = array.length,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = array[index][property];
+ }
+ return result;
+ }
+
+ /**
+ * Creates an array of numbers (positive and/or negative) progressing from
+ * `start` up to but not including `stop`. This method is a port of Python's
+ * `range()` function. See http://docs.python.org/library/functions.html#range.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Number} [start=0] The start of the range.
+ * @param {Number} end The end of the range.
+ * @param {Number} [step=1] The value to increment or descrement by.
+ * @returns {Array} Returns a new range array.
+ * @example
+ *
+ * _.range(10);
+ * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ *
+ * _.range(1, 11);
+ * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ *
+ * _.range(0, 30, 5);
+ * // => [0, 5, 10, 15, 20, 25]
+ *
+ * _.range(0, -10, -1);
+ * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
+ *
+ * _.range(0);
+ * // => []
+ */
+ function range(start, end, step) {
+ step || (step = 1);
+ if (arguments.length < 2) {
+ end = start || 0;
+ start = 0;
+ }
+
+ var index = -1,
+ length = Math.max(Math.ceil((end - start) / step), 0),
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = start;
+ start += step;
+ }
+ return result;
+ }
+
+ /**
+ * The opposite of `_.initial`, this method gets all but the first value of
+ * `array`. Pass `n` to exclude the first `n` values from the result.
+ *
+ * @static
+ * @memberOf _
+ * @alias tail
+ * @category Arrays
+ * @param {Array} array The array to query.
+ * @param {Number} [n] The number of elements to return.
+ * @param {Object} [guard] Internally used to allow this method to work with
+ * others like `_.map` without using their callback `index` argument for `n`.
+ * @returns {Array} Returns all but the first value or `n` values of `array`.
+ * @example
+ *
+ * _.rest([3, 2, 1]);
+ * // => [2, 1]
+ */
+ function rest(array, n, guard) {
+ if (!array) {
+ return [];
+ }
+ return slice.call(array, (n == undefined || guard) ? 1 : n);
+ }
+
+ /**
+ * Produces a new array of shuffled `array` values, using a version of the
+ * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to shuffle.
+ * @returns {Array} Returns a new shuffled array.
+ * @example
+ *
+ * _.shuffle([1, 2, 3, 4, 5, 6]);
+ * // => [4, 1, 6, 3, 5, 2]
+ */
+ function shuffle(array) {
+ if (!array) {
+ return [];
+ }
+ var rand,
+ index = -1,
+ length = array.length,
+ result = Array(length);
+
+ while (++index < length) {
+ rand = Math.floor(Math.random() * (index + 1));
+ result[index] = result[rand];
+ result[rand] = array[index];
+ }
+ return result;
+ }
+
+ /**
+ * Produces a new sorted array, ranked in ascending order by the results of
+ * running each element of `array` through `callback`. The `callback` is
+ * bound to `thisArg` and invoked with 3 arguments; (value, index, array). The
+ * `callback` argument may also be the name of a property to sort by (e.g. 'length').
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {Function|String} callback The function called per iteration or
+ * property name to sort by.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array} Returns a new array of sorted values.
+ * @example
+ *
+ * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
+ * // => [3, 1, 2]
+ *
+ * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
+ * // => [3, 1, 2]
+ *
+ * _.sortBy(['larry', 'brendan', 'moe'], 'length');
+ * // => ['moe', 'larry', 'brendan']
+ */
+ function sortBy(array, callback, thisArg) {
+ if (!array) {
+ return [];
+ }
+ if (typeof callback == 'string') {
+ var prop = callback;
+ callback = function(array) { return array[prop]; };
+ } else if (thisArg) {
+ callback = iteratorBind(callback, thisArg);
+ }
+ var index = -1,
+ length = array.length,
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = {
+ 'criteria': callback(array[index], index, array),
+ 'value': array[index]
+ };
+ }
+ result.sort(function(left, right) {
+ var a = left.criteria,
+ b = right.criteria;
+
+ if (a === undefined) {
+ return 1;
+ }
+ if (b === undefined) {
+ return -1;
+ }
+ return a < b ? -1 : a > b ? 1 : 0;
+ });
+
+ while (length--) {
+ result[length] = result[length].value;
+ }
+ return result;
+ }
+
+ /**
+ * Uses a binary search to determine the smallest index at which the `value`
+ * should be inserted into `array` in order to maintain the sort order of the
+ * sorted `array`. If `callback` is passed, it will be executed for `value` and
+ * each element in `array` to compute their sort ranking. The `callback` is
+ * bound to `thisArg` and invoked with 1 argument; (value).
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to iterate over.
+ * @param {Mixed} value The value to evaluate.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Number} Returns the index at which the value should be inserted
+ * into `array`.
+ * @example
+ *
+ * _.sortedIndex([20, 30, 40], 35);
+ * // => 2
+ *
+ * var dict = {
+ * 'wordToNumber': { 'twenty': 20, 'thirty': 30, 'thirty-five': 35, 'fourty': 40 }
+ * };
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) {
+ * return dict.wordToNumber[word];
+ * });
+ * // => 2
+ *
+ * _.sortedIndex(['twenty', 'thirty', 'fourty'], 'thirty-five', function(word) {
+ * return this.wordToNumber[word];
+ * }, dict);
+ * // => 2
+ */
+ function sortedIndex(array, value, callback, thisArg) {
+ if (!array) {
+ return 0;
+ }
+ var mid,
+ low = 0,
+ high = array.length;
+
+ if (callback) {
+ value = callback.call(thisArg, value);
+ while (low < high) {
+ mid = (low + high) >>> 1;
+ callback.call(thisArg, array[mid]) < value ? low = mid + 1 : high = mid;
+ }
+ } else {
+ while (low < high) {
+ mid = (low + high) >>> 1;
+ array[mid] < value ? low = mid + 1 : high = mid;
+ }
+ }
+ return low;
+ }
+
+ /**
+ * Computes the union of the passed-in arrays.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} [array1, array2, ...] Arrays to process.
+ * @returns {Array} Returns a new array of unique values, in order, that are
+ * present in one or more of the arrays.
+ * @example
+ *
+ * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
+ * // => [1, 2, 3, 101, 10]
+ */
+ function union() {
+ var index = -1,
+ result = [],
+ flattened = concat.apply(result, arguments),
+ length = flattened.length;
+
+ while (++index < length) {
+ if (indexOf(result, flattened[index]) < 0) {
+ result.push(flattened[index]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Produces a duplicate-value-free version of the `array` using strict equality
+ * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true`
+ * for `isSorted` will run a faster algorithm. If `callback` is passed,
+ * each value of `array` is passed through a transformation `callback` before
+ * uniqueness is computed. The `callback` is bound to `thisArg` and invoked
+ * with 3 arguments; (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @alias unique
+ * @category Arrays
+ * @param {Array} array The array to process.
+ * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted.
+ * @param {Function} [callback=identity] The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Array} Returns a duplicate-value-free array.
+ * @example
+ *
+ * _.uniq([1, 2, 1, 3, 1]);
+ * // => [1, 2, 3]
+ *
+ * _.uiq([1, 1, 2, 2, 3], true);
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); });
+ * // => [1, 2, 3]
+ *
+ * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math);
+ * // => [1, 2, 3]
+ */
+ function uniq(array, isSorted, callback, thisArg) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var computed,
+ index = -1,
+ length = array.length,
+ seen = [];
+
+ // juggle arguments
+ if (typeof isSorted == 'function') {
+ thisArg = callback;
+ callback = isSorted;
+ isSorted = false;
+ }
+ if (!callback) {
+ callback = identity;
+ } else if (thisArg) {
+ callback = iteratorBind(callback, thisArg);
+ }
+ while (++index < length) {
+ computed = callback(array[index], index, array);
+ if (isSorted
+ ? !index || seen[seen.length - 1] !== computed
+ : indexOf(seen, computed) < 0
+ ) {
+ seen.push(computed);
+ result.push(array[index]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Produces a new array with all occurrences of the passed values removed using
+ * strict equality for comparisons, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} array The array to filter.
+ * @param {Mixed} [value1, value2, ...] Values to remove.
+ * @returns {Array} Returns a new filtered array.
+ * @example
+ *
+ * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
+ * // => [2, 3, 4]
+ */
+ function without(array) {
+ var result = [];
+ if (!array) {
+ return result;
+ }
+ var excluded = slice.call(arguments, 1),
+ index = -1,
+ length = array.length;
+
+ while (++index < length) {
+ if (indexOf(excluded, array[index]) < 0) {
+ result.push(array[index]);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Merges together the values of each of the arrays with the value at the
+ * corresponding position. Useful for separate data sources that are coordinated
+ * through matching array indexes. For a matrix of nested arrays, `_.zip.apply(...)`
+ * can transpose the matrix in a similar fashion.
+ *
+ * @static
+ * @memberOf _
+ * @category Arrays
+ * @param {Array} [array1, array2, ...] Arrays to process.
+ * @returns {Array} Returns a new array of merged arrays.
+ * @example
+ *
+ * _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
+ * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]]
+ */
+ function zip(array) {
+ if (!array) {
+ return [];
+ }
+ var index = -1,
+ length = max(pluck(arguments, 'length')),
+ result = Array(length);
+
+ while (++index < length) {
+ result[index] = pluck(arguments, index);
+ }
+ return result;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Creates a new function that is restricted to executing only after it is
+ * called `n` times.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Number} n The number of times the function must be called before
+ * it is executed.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var renderNotes = _.after(notes.length, render);
+ * _.forEach(notes, function(note) {
+ * note.asyncSave({ 'success': renderNotes });
+ * });
+ * // `renderNotes` is run once, after all notes have saved
+ */
+ function after(n, func) {
+ if (n < 1) {
+ return func();
+ }
+ return function() {
+ if (--n < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that, when called, invokes `func` with the `this`
+ * binding of `thisArg` and prepends any additional `bind` arguments to those
+ * passed to the bound function. Lazy defined methods may be bound by passing
+ * the object they are bound to as `func` and the method name as `thisArg`.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function|Object} func The function to bind or the object the method belongs to.
+ * @param {Mixed} [thisArg] The `this` binding of `func` or the method name.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * // basic bind
+ * var func = function(greeting) {
+ * return greeting + ': ' + this.name;
+ * };
+ *
+ * func = _.bind(func, { 'name': 'moe' }, 'hi');
+ * func();
+ * // => 'hi: moe'
+ *
+ * // lazy bind
+ * var object = {
+ * 'name': 'moe',
+ * 'greet': function(greeting) {
+ * return greeting + ': ' + this.name;
+ * }
+ * };
+ *
+ * var func = _.bind(object, 'greet', 'hi');
+ * func();
+ * // => 'hi: moe'
+ *
+ * object.greet = function(greeting) {
+ * return greeting + ', ' + this.name + '!';
+ * };
+ *
+ * func();
+ * // => 'hi, moe!'
+ */
+ function bind(func, thisArg) {
+ var methodName,
+ isFunc = toString.call(func) == funcClass;
+
+ // juggle arguments
+ if (!isFunc) {
+ methodName = thisArg;
+ thisArg = func;
+ }
+ // use if `Function#bind` is faster
+ else if (nativeBind) {
+ return nativeBind.call.apply(nativeBind, arguments);
+ }
+
+ var partialArgs = slice.call(arguments, 2);
+
+ function bound() {
+ // `Function#bind` spec
+ // http://es5.github.com/#x15.3.4.5
+ var args = arguments,
+ thisBinding = thisArg;
+
+ if (!isFunc) {
+ func = thisArg[methodName];
+ }
+ if (partialArgs.length) {
+ args = args.length
+ ? concat.apply(partialArgs, args)
+ : partialArgs;
+ }
+ if (this instanceof bound) {
+ // get `func` instance if `bound` is invoked in a `new` expression
+ noop.prototype = func.prototype;
+ thisBinding = new noop;
+
+ // mimic the constructor's `return` behavior
+ // http://es5.github.com/#x13.2.2
+ var result = func.apply(thisBinding, args);
+ return objectTypes[typeof result] && result !== null
+ ? result
+ : thisBinding
+ }
+ return func.apply(thisBinding, args);
+ }
+
+ return bound;
+ }
+
+ /**
+ * Binds methods on `object` to `object`, overwriting the existing method.
+ * If no method names are provided, all the function properties of `object`
+ * will be bound.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Object} object The object to bind and assign the bound methods to.
+ * @param {String} [methodName1, methodName2, ...] Method names on the object to bind.
+ * @returns {Object} Returns the `object`.
+ * @example
+ *
+ * var buttonView = {
+ * 'label': 'lodash',
+ * 'onClick': function() { alert('clicked: ' + this.label); },
+ * 'onHover': function() { console.log('hovering: ' + this.label); }
+ * };
+ *
+ * _.bindAll(buttonView);
+ * jQuery('#lodash_button').on('click', buttonView.onClick);
+ * // => When the button is clicked, `this.label` will have the correct value
+ */
+ function bindAll(object) {
+ var funcs = arguments,
+ index = 1;
+
+ if (funcs.length == 1) {
+ index = 0;
+ funcs = functions(object);
+ }
+ for (var length = funcs.length; index < length; index++) {
+ object[funcs[index]] = bind(object[funcs[index]], object);
+ }
+ return object;
+ }
+
+ /**
+ * Creates a new function that is the composition of the passed functions,
+ * where each function consumes the return value of the function that follows.
+ * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} [func1, func2, ...] Functions to compose.
+ * @returns {Function} Returns the new composed function.
+ * @example
+ *
+ * var greet = function(name) { return 'hi: ' + name; };
+ * var exclaim = function(statement) { return statement + '!'; };
+ * var welcome = _.compose(exclaim, greet);
+ * welcome('moe');
+ * // => 'hi: moe!'
+ */
+ function compose() {
+ var funcs = arguments;
+ return function() {
+ var args = arguments,
+ length = funcs.length;
+
+ while (length--) {
+ args = [funcs[length].apply(this, args)];
+ }
+ return args[0];
+ };
+ }
+
+ /**
+ * Creates a new function that will delay the execution of `func` until after
+ * `wait` milliseconds have elapsed since the last time it was invoked. Pass
+ * `true` for `immediate` to cause debounce to invoke `func` on the leading,
+ * instead of the trailing, edge of the `wait` timeout. Subsequent calls to
+ * the debounced function will return the result of the last `func` call.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to debounce.
+ * @param {Number} wait The number of milliseconds to delay.
+ * @param {Boolean} immediate A flag to indicate execution is on the leading
+ * edge of the timeout.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * var lazyLayout = _.debounce(calculateLayout, 300);
+ * jQuery(window).on('resize', lazyLayout);
+ */
+ function debounce(func, wait, immediate) {
+ var args,
+ result,
+ thisArg,
+ timeoutId;
+
+ function delayed() {
+ timeoutId = undefined;
+ if (!immediate) {
+ func.apply(thisArg, args);
+ }
+ }
+
+ return function() {
+ var isImmediate = immediate && !timeoutId;
+ args = arguments;
+ thisArg = this;
+
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(delayed, wait);
+
+ if (isImmediate) {
+ result = func.apply(thisArg, args);
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Executes the `func` function after `wait` milliseconds. Additional arguments
+ * are passed to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to delay.
+ * @param {Number} wait The number of milliseconds to delay execution.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with.
+ * @returns {Number} Returns the `setTimeout` timeout id.
+ * @example
+ *
+ * var log = _.bind(console.log, console);
+ * _.delay(log, 1000, 'logged later');
+ * // => 'logged later' (Appears after one second.)
+ */
+ function delay(func, wait) {
+ var args = slice.call(arguments, 2);
+ return setTimeout(function() { return func.apply(undefined, args); }, wait);
+ }
+
+ /**
+ * Defers executing the `func` function until the current call stack has cleared.
+ * Additional arguments are passed to `func` when it is invoked.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to defer.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with.
+ * @returns {Number} Returns the `setTimeout` timeout id.
+ * @example
+ *
+ * _.defer(function() { alert('deferred'); });
+ * // returns from the function before `alert` is called
+ */
+ function defer(func) {
+ var args = slice.call(arguments, 1);
+ return setTimeout(function() { return func.apply(undefined, args); }, 1);
+ }
+
+ /**
+ * Creates a new function that memoizes the result of `func`. If `resolver` is
+ * passed, it will be used to determine the cache key for storing the result
+ * based on the arguments passed to the memoized function. By default, the first
+ * argument passed to the memoized function is used as the cache key.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] A function used to resolve the cache key.
+ * @returns {Function} Returns the new memoizing function.
+ * @example
+ *
+ * var fibonacci = _.memoize(function(n) {
+ * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
+ * });
+ */
+ function memoize(func, resolver) {
+ var cache = {};
+ return function() {
+ var prop = resolver ? resolver.apply(this, arguments) : arguments[0];
+ return hasOwnProperty.call(cache, prop)
+ ? cache[prop]
+ : (cache[prop] = func.apply(this, arguments));
+ };
+ }
+
+ /**
+ * Creates a new function that is restricted to one execution. Repeat calls to
+ * the function will return the value of the first call.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var initialize = _.once(createApplication);
+ * initialize();
+ * initialize();
+ * // Application is only created once.
+ */
+ function once(func) {
+ var result,
+ ran = false;
+
+ return function() {
+ if (ran) {
+ return result;
+ }
+ ran = true;
+ result = func.apply(this, arguments);
+ return result;
+ };
+ }
+
+ /**
+ * Creates a new function that, when called, invokes `func` with any additional
+ * `partial` arguments prepended to those passed to the partially applied
+ * function. This method is similar `bind`, except it does **not** alter the
+ * `this` binding.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * var greet = function(greeting, name) { return greeting + ': ' + name; };
+ * var hi = _.partial(greet, 'hi');
+ * hi('moe');
+ * // => 'hi: moe'
+ */
+ function partial(func) {
+ var args = slice.call(arguments, 1),
+ argsLength = args.length;
+
+ return function() {
+ var result,
+ others = arguments;
+
+ if (others.length) {
+ args.length = argsLength;
+ push.apply(args, others);
+ }
+ result = args.length == 1 ? func.call(this, args[0]) : func.apply(this, args);
+ args.length = argsLength;
+ return result;
+ };
+ }
+
+ /**
+ * Creates a new function that, when executed, will only call the `func`
+ * function at most once per every `wait` milliseconds. If the throttled function
+ * is invoked more than once, `func` will also be called on the trailing edge
+ * of the `wait` timeout. Subsequent calls to the throttled function will
+ * return the result of the last `func` call.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to throttle.
+ * @param {Number} wait The number of milliseconds to throttle executions to.
+ * @returns {Function} Returns the new throttled function.
+ * @example
+ *
+ * var throttled = _.throttle(updatePosition, 100);
+ * jQuery(window).on('scroll', throttled);
+ */
+ function throttle(func, wait) {
+ var args,
+ result,
+ thisArg,
+ timeoutId,
+ lastCalled = 0;
+
+ function trailingCall() {
+ lastCalled = new Date;
+ timeoutId = undefined;
+ func.apply(thisArg, args);
+ }
+
+ return function() {
+ var now = new Date,
+ remain = wait - (now - lastCalled);
+
+ args = arguments;
+ thisArg = this;
+
+ if (remain <= 0) {
+ lastCalled = now;
+ result = func.apply(thisArg, args);
+ }
+ else if (!timeoutId) {
+ timeoutId = setTimeout(trailingCall, remain);
+ }
+ return result;
+ };
+ }
+
+ /**
+ * Create a new function that passes the `func` function to the `wrapper`
+ * function as its first argument. Additional arguments are appended to those
+ * passed to the `wrapper` function.
+ *
+ * @static
+ * @memberOf _
+ * @category Functions
+ * @param {Function} func The function to wrap.
+ * @param {Function} wrapper The wrapper function.
+ * @param {Mixed} [arg1, arg2, ...] Arguments to append to those passed to the wrapper.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var hello = function(name) { return 'hello: ' + name; };
+ * hello = _.wrap(hello, function(func) {
+ * return 'before, ' + func('moe') + ', after';
+ * });
+ * hello();
+ * // => 'before, hello: moe, after'
+ */
+ function wrap(func, wrapper) {
+ return function() {
+ var args = [func];
+ if (arguments.length) {
+ push.apply(args, arguments);
+ }
+ return wrapper.apply(this, args);
+ };
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Create a shallow clone of the `value`. Any nested objects or arrays will be
+ * assigned by reference and not cloned.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to clone.
+ * @returns {Mixed} Returns the cloned `value`.
+ * @example
+ *
+ * _.clone({ 'name': 'moe' });
+ * // => { 'name': 'moe' };
+ */
+ function clone(value) {
+ return objectTypes[typeof value] && value !== null
+ ? (isArray(value) ? value.slice() : extend({}, value))
+ : value;
+ }
+
+ /**
+ * Assigns missing properties in `object` with default values from the defaults
+ * objects. As soon as a property is set, additional defaults of the same
+ * property will be ignored.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to populate.
+ * @param {Object} [defaults1, defaults2, ...] The defaults objects to apply to `object`.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var iceCream = { 'flavor': 'chocolate' };
+ * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' });
+ * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' }
+ */
+ var defaults = createIterator(extendIteratorOptions, {
+ 'inLoop': 'if (object[index] == undefined)' + extendIteratorOptions.inLoop
+ });
+
+ /**
+ * Copies enumerable properties from the source objects to the `destination` object.
+ * Subsequent sources will overwrite propery assignments of previous sources.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The destination object.
+ * @param {Object} [source1, source2, ...] The source objects.
+ * @returns {Object} Returns the destination object.
+ * @example
+ *
+ * _.extend({ 'name': 'moe' }, { 'age': 40 });
+ * // => { 'name': 'moe', 'age': 40 }
+ */
+ var extend = createIterator(extendIteratorOptions);
+
+ /**
+ * Iterates over `object`'s own and inherited enumerable properties, executing
+ * the `callback` for each property. The `callback` is bound to `thisArg` and
+ * invoked with 3 arguments; (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Object} Returns the `object`.
+ * @example
+ *
+ * function Dog(name) {
+ * this.name = name;
+ * }
+ *
+ * Dog.prototype.bark = function() {
+ * alert('Woof, woof!');
+ * };
+ *
+ * _.forIn(new Dog('Dagny'), function(value, key) {
+ * alert(key);
+ * });
+ * // => alerts 'name' and 'bark' (order is not guaranteed)
+ */
+ var forIn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions, {
+ 'useHas': false
+ });
+
+ /**
+ * Iterates over `object`'s own enumerable properties, executing the `callback`
+ * for each property. The `callback` is bound to `thisArg` and invoked with 3
+ * arguments; (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to iterate over.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @returns {Object} Returns the `object`.
+ * @example
+ *
+ * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+ * alert(key);
+ * });
+ * // => alerts '0', '1', and 'length' (order is not guaranteed)
+ */
+ var forOwn = createIterator(baseIteratorOptions, forEachIteratorOptions, forOwnIteratorOptions);
+
+ /**
+ * Produces a sorted array of the enumerable properties, own and inherited,
+ * of `object` that have function values.
+ *
+ * @static
+ * @memberOf _
+ * @alias methods
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property names that have function values.
+ * @example
+ *
+ * _.functions(_);
+ * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
+ */
+ var functions = createIterator({
+ 'args': 'object',
+ 'init': '[]',
+ 'useHas': false,
+ 'inLoop': 'if (toString.call(object[index]) == funcClass) result.push(index)',
+ 'bottom': 'result.sort()'
+ });
+
+ /**
+ * Checks if the specified object `property` exists and is a direct property,
+ * instead of an inherited property.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to check.
+ * @param {String} property The property to check for.
+ * @returns {Boolean} Returns `true` if key is a direct property, else `false`.
+ * @example
+ *
+ * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
+ * // => true
+ */
+ function has(object, property) {
+ return hasOwnProperty.call(object, property);
+ }
+
+ /**
+ * Checks if a `value` is an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+ * @example
+ *
+ * (function() { return _.isArguments(arguments); })(1, 2, 3);
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
+ var isArguments = function(value) {
+ return toString.call(value) == '[object Arguments]';
+ };
+ // fallback for browser like IE < 9 which detect `arguments` as `[object Object]`
+ if (!isArguments(arguments)) {
+ isArguments = function(value) {
+ return !!(value && hasOwnProperty.call(value, 'callee'));
+ };
+ }
+
+ /**
+ * Checks if a `value` is an array.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is an array, else `false`.
+ * @example
+ *
+ * (function() { return _.isArray(arguments); })();
+ * // => false
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ */
+ var isArray = nativeIsArray || function(value) {
+ return toString.call(value) == arrayClass;
+ };
+
+ /**
+ * Checks if a `value` is a boolean (`true` or `false`) value.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a boolean value, else `false`.
+ * @example
+ *
+ * _.isBoolean(null);
+ * // => false
+ */
+ function isBoolean(value) {
+ return value === true || value === false || toString.call(value) == boolClass;
+ }
+
+ /**
+ * Checks if a `value` is a date.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a date, else `false`.
+ * @example
+ *
+ * _.isDate(new Date);
+ * // => true
+ */
+ function isDate(value) {
+ return toString.call(value) == dateClass;
+ }
+
+ /**
+ * Checks if a `value` is a DOM element.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a DOM element, else `false`.
+ * @example
+ *
+ * _.isElement(document.body);
+ * // => true
+ */
+ function isElement(value) {
+ return !!(value && value.nodeType == 1);
+ }
+
+ /**
+ * Checks if a `value` is empty. Arrays or strings with a length of `0` and
+ * objects with no own enumerable properties are considered "empty".
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object|String} value The value to inspect.
+ * @returns {Boolean} Returns `true` if the `value` is empty, else `false`.
+ * @example
+ *
+ * _.isEmpty([1, 2, 3]);
+ * // => false
+ *
+ * _.isEmpty({});
+ * // => true
+ */
+ var isEmpty = createIterator({
+ 'args': 'value',
+ 'init': 'true',
+ 'top':
+ 'var className = toString.call(value);\n' +
+ 'if (className == arrayClass || className == stringClass) return !value.length',
+ 'inLoop': {
+ 'object': 'return false'
+ }
+ });
+
+ /**
+ * Performs a deep comparison between two values to determine if they are
+ * equivalent to each other.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} a The value to compare.
+ * @param {Mixed} b The other value to compare.
+ * @param {Array} [stack] Internally used to keep track of "seen" objects to
+ * avoid circular references.
+ * @returns {Boolean} Returns `true` if the values are equvalent, else `false`.
+ * @example
+ *
+ * var moe = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] };
+ * var clone = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] };
+ *
+ * moe == clone;
+ * // => false
+ *
+ * _.isEqual(moe, clone);
+ * // => true
+ */
+ function isEqual(a, b, stack) {
+ stack || (stack = []);
+
+ // exit early for identical values
+ if (a === b) {
+ // treat `+0` vs. `-0` as not equal
+ return a !== 0 || (1 / a == 1 / b);
+ }
+ // a strict comparison is necessary because `null == undefined`
+ if (a == undefined || b == undefined) {
+ return a === b;
+ }
+ // unwrap any wrapped objects
+ if (a._chain) {
+ a = a._wrapped;
+ }
+ if (b._chain) {
+ b = b._wrapped;
+ }
+ // invoke a custom `isEqual` method if one is provided
+ if (a.isEqual && toString.call(a.isEqual) == funcClass) {
+ return a.isEqual(b);
+ }
+ if (b.isEqual && toString.call(b.isEqual) == funcClass) {
+ return b.isEqual(a);
+ }
+ // compare [[Class]] names
+ var className = toString.call(a);
+ if (className != toString.call(b)) {
+ return false;
+ }
+ switch (className) {
+ // strings, numbers, dates, and booleans are compared by value
+ case stringClass:
+ // primitives and their corresponding object instances are equivalent;
+ // thus, `'5'` is quivalent to `new String('5')`
+ return a == String(b);
+
+ case numberClass:
+ // treat `NaN` vs. `NaN` as equal
+ return a != +a
+ ? b != +b
+ // but treat `+0` vs. `-0` as not equal
+ : (a == 0 ? (1 / a == 1 / b) : a == +b);
+
+ case boolClass:
+ case dateClass:
+ // coerce dates and booleans to numeric values, dates to milliseconds and booleans to 1 or 0;
+ // treat invalid dates coerced to `NaN` as not equal
+ return +a == +b;
+
+ // regexps are compared by their source and flags
+ case regexpClass:
+ return a.source == b.source &&
+ a.global == b.global &&
+ a.multiline == b.multiline &&
+ a.ignoreCase == b.ignoreCase;
+ }
+ if (typeof a != 'object' || typeof b != 'object') {
+ return false;
+ }
+ // Assume equality for cyclic structures. The algorithm for detecting cyclic
+ // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+ var length = stack.length;
+ while (length--) {
+ // Linear search. Performance is inversely proportional to the number of
+ // unique nested structures.
+ if (stack[length] == a) {
+ return true;
+ }
+ }
+
+ var index = -1,
+ result = true,
+ size = 0;
+
+ // add the first collection to the stack of traversed objects
+ stack.push(a);
+
+ // recursively compare objects and arrays
+ if (className == arrayClass) {
+ // compare array lengths to determine if a deep comparison is necessary
+ size = a.length;
+ result = size == b.length;
+
+ if (result) {
+ // deep compare the contents, ignoring non-numeric properties
+ while (size--) {
+ if (!(result = isEqual(a[size], b[size], stack))) {
+ break;
+ }
+ }
+ }
+ } else {
+ // objects with different constructors are not equivalent
+ if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) {
+ return false;
+ }
+ // deep compare objects.
+ for (var prop in a) {
+ if (hasOwnProperty.call(a, prop)) {
+ // count the number of properties.
+ size++;
+ // deep compare each property value.
+ if (!(result = hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack))) {
+ break;
+ }
+ }
+ }
+ // ensure both objects have the same number of properties
+ if (result) {
+ for (prop in b) {
+ // Adobe's JS engine, embedded in applications like InDesign, has a
+ // bug that causes `!size--` to throw an error so it must be wrapped
+ // in parentheses.
+ // https://github.com/documentcloud/underscore/issues/355
+ if (hasOwnProperty.call(b, prop) && !(size--)) {
+ break;
+ }
+ }
+ result = !size;
+ }
+ // handle JScript [[DontEnum]] bug
+ if (result && hasDontEnumBug) {
+ while (++index < 7) {
+ prop = shadowed[index];
+ if (hasOwnProperty.call(a, prop)) {
+ if (!(result = hasOwnProperty.call(b, prop) && isEqual(a[prop], b[prop], stack))) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ // remove the first collection from the stack of traversed objects
+ stack.pop();
+ return result;
+ }
+
+ /**
+ * Checks if a `value` is a finite number.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a finite number, else `false`.
+ * @example
+ *
+ * _.isFinite(-101);
+ * // => true
+ *
+ * _.isFinite('10');
+ * // => false
+ *
+ * _.isFinite(Infinity);
+ * // => false
+ */
+ function isFinite(value) {
+ return nativeIsFinite(value) && toString.call(value) == numberClass;
+ }
+
+ /**
+ * Checks if a `value` is a function.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(''.concat);
+ * // => true
+ */
+ function isFunction(value) {
+ return toString.call(value) == funcClass;
+ }
+
+ /**
+ * Checks if a `value` is the language type of Object.
+ * (e.g. arrays, functions, objects, regexps, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject(1);
+ * // => false
+ */
+ function isObject(value) {
+ // check if the value is the ECMAScript language type of Object
+ // http://es5.github.com/#x8
+ return objectTypes[typeof value] && value !== null;
+ }
+
+ /**
+ * Checks if a `value` is `NaN`.
+ * Note: This is not the same as native `isNaN`, which will return true for
+ * `undefined` and other values. See http://es5.github.com/#x15.1.2.4.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is `NaN`, else `false`.
+ * @example
+ *
+ * _.isNaN(NaN);
+ * // => true
+ *
+ * _.isNaN(new Number(NaN));
+ * // => true
+ *
+ * isNaN(undefined);
+ * // => true
+ *
+ * _.isNaN(undefined);
+ * // => false
+ */
+ function isNaN(value) {
+ // `NaN` as a primitive is the only value that is not equal to itself
+ // (perform the [[Class]] check first to avoid errors with some host objects in IE)
+ return toString.call(value) == numberClass && value != +value
+ }
+
+ /**
+ * Checks if a `value` is `null`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is `null`, else `false`.
+ * @example
+ *
+ * _.isNull(null);
+ * // => true
+ *
+ * _.isNull(undefined);
+ * // => false
+ */
+ function isNull(value) {
+ return value === null;
+ }
+
+ /**
+ * Checks if a `value` is a number.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a number, else `false`.
+ * @example
+ *
+ * _.isNumber(8.4 * 5;
+ * // => true
+ */
+ function isNumber(value) {
+ return toString.call(value) == numberClass;
+ }
+
+ /**
+ * Checks if a `value` is a regular expression.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a regular expression, else `false`.
+ * @example
+ *
+ * _.isRegExp(/moe/);
+ * // => true
+ */
+ function isRegExp(value) {
+ return toString.call(value) == regexpClass;
+ }
+
+ /**
+ * Checks if a `value` is a string.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is a string, else `false`.
+ * @example
+ *
+ * _.isString('moe');
+ * // => true
+ */
+ function isString(value) {
+ return toString.call(value) == stringClass;
+ }
+
+ /**
+ * Checks if a `value` is `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to check.
+ * @returns {Boolean} Returns `true` if the `value` is `undefined`, else `false`.
+ * @example
+ *
+ * _.isUndefined(void 0);
+ * // => true
+ */
+ function isUndefined(value) {
+ return value === undefined;
+ }
+
+ /**
+ * Produces an array of object`'s own enumerable property names.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property names.
+ * @example
+ *
+ * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => ['one', 'two', 'three'] (order is not guaranteed)
+ */
+ var keys = !nativeKeys ? shimKeys : function(object) {
+ // avoid iterating over the `prototype` property
+ return typeof object == 'function'
+ ? shimKeys(object)
+ : nativeKeys(object);
+ };
+
+ /**
+ * Creates an object composed of the specified properties. Property names may
+ * be specified as individual arguments or as arrays of property names.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to pluck.
+ * @param {Object} [prop1, prop2, ...] The properties to pick.
+ * @returns {Object} Returns an object composed of the picked properties.
+ * @example
+ *
+ * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age');
+ * // => { 'name': 'moe', 'age': 40 }
+ */
+ function pick(object) {
+ var prop,
+ index = 0,
+ props = concat.apply(ArrayProto, arguments),
+ length = props.length,
+ result = {};
+
+ // start `index` at `1` to skip `object`
+ while (++index < length) {
+ prop = props[index];
+ if (prop in object) {
+ result[prop] = object[prop];
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the size of `value` by returning `value.length` if `value` is a string
+ * or array, or the number of own enumerable properties if `value` is an object.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Array|Object|String} value The value to inspect.
+ * @returns {Number} Returns `value.length` if `value` is a string or array,
+ * or the number of own enumerable properties if `value` is an object.
+ * @example
+ *
+ * _.size([1, 2]);
+ * // => 2
+ *
+ * _.size({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => 3
+ *
+ * _.size('curly');
+ * // => 5
+ */
+ function size(value) {
+ var className = toString.call(value);
+ return className == arrayClass || className == stringClass
+ ? value.length
+ : keys(value).length;
+ }
+
+ /**
+ * Invokes `interceptor` with the `value` as the first argument, and then returns
+ * `value`. The primary purpose of this method is to "tap into" a method chain,
+ * in order to performoperations on intermediate results within the chain.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Mixed} value The value to pass to `callback`.
+ * @param {Function} interceptor The function to invoke.
+ * @returns {Mixed} Returns `value`.
+ * @example
+ *
+ * _.chain([1,2,3,200])
+ * .filter(function(num) { return num % 2 == 0; })
+ * .tap(alert)
+ * .map(function(num) { return num * num })
+ * .value();
+ * // => // [2, 200] (alerted)
+ * // => [4, 40000]
+ */
+ function tap(value, interceptor) {
+ interceptor(value);
+ return value;
+ }
+
+ /**
+ * Produces an array of `object`'s own enumerable property values.
+ *
+ * @static
+ * @memberOf _
+ * @category Objects
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns a new array of property values.
+ * @example
+ *
+ * _.values({ 'one': 1, 'two': 2, 'three': 3 });
+ * // => [1, 2, 3]
+ */
+ var values = createIterator({
+ 'args': 'object',
+ 'init': '[]',
+ 'inLoop': 'result.push(object[index])'
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Escapes a string for inclusion in HTML, replacing `&`, `<`, `"`, and `'`
+ * characters.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {String} string The string to escape.
+ * @returns {String} Returns the escaped string.
+ * @example
+ *
+ * _.escape('Curly, Larry & Moe');
+ * // => "Curly, Larry &amp; Moe"
+ */
+ function escape(string) {
+ return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar);
+ }
+
+ /**
+ * This function returns the first argument passed to it.
+ * Note: It is used throughout Lo-Dash as a default callback.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Mixed} value Any value.
+ * @returns {Mixed} Returns `value`.
+ * @example
+ *
+ * var moe = { 'name': 'moe' };
+ * moe === _.identity(moe);
+ * // => true
+ */
+ function identity(value) {
+ return value;
+ }
+
+ /**
+ * Adds functions properties of `object` to the `lodash` function and chainable
+ * wrapper.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} object The object of function properties to add to `lodash`.
+ * @example
+ *
+ * _.mixin({
+ * 'capitalize': function(string) {
+ * return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
+ * }
+ * });
+ *
+ * _.capitalize('curly');
+ * // => 'Curly'
+ *
+ * _('larry').capitalize();
+ * // => 'Larry'
+ */
+ function mixin(object) {
+ forEach(functions(object), function(methodName) {
+ var func = lodash[methodName] = object[methodName];
+
+ LoDash.prototype[methodName] = function() {
+ var args = [this._wrapped];
+ if (arguments.length) {
+ push.apply(args, arguments);
+ }
+ var result = func.apply(lodash, args);
+ if (this._chain) {
+ result = new LoDash(result);
+ result._chain = true;
+ }
+ return result;
+ };
+ });
+ }
+
+ /**
+ * Reverts the '_' variable to its previous value and returns a reference to
+ * the `lodash` function.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @returns {Function} Returns the `lodash` function.
+ * @example
+ *
+ * var lodash = _.noConflict();
+ */
+ function noConflict() {
+ window._ = oldDash;
+ return this;
+ }
+
+ /**
+ * Resolves the value of `property` on `object`. If `property` is a function
+ * it will be invoked and its result returned, else the property value is
+ * returned. If `object` is falsey, then `null` is returned.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Object} object The object to inspect.
+ * @param {String} property The property to get the result of.
+ * @returns {Mixed} Returns the resolved value.
+ * @example
+ *
+ * var object = {
+ * 'cheese': 'crumpets',
+ * 'stuff': function() {
+ * return 'nonsense';
+ * }
+ * };
+ *
+ * _.result(object, 'cheese');
+ * // => 'crumpets'
+ *
+ * _.result(object, 'stuff');
+ * // => 'nonsense'
+ */
+ function result(object, property) {
+ // based on Backbone's private `getValue` function
+ // https://github.com/documentcloud/backbone/blob/0.9.2/backbone.js#L1419-1424
+ if (!object) {
+ return null;
+ }
+ var value = object[property];
+ return toString.call(value) == funcClass ? object[property]() : value;
+ }
+
+ /**
+ * A micro-templating method, similar to John Resig's implementation.
+ * Lo-Dash templating handles arbitrary delimiters, preserves whitespace, and
+ * correctly escapes quotes within interpolated code.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {String} text The template text.
+ * @param {Obect} data The data object used to populate the text.
+ * @param {Object} options The options object.
+ * @returns {Function|String} Returns a compiled function when no `data` object
+ * is given, else it returns the interpolated text.
+ * @example
+ *
+ * // using compiled template
+ * var compiled = _.template('hello: <%= name %>');
+ * compiled({ 'name': 'moe' });
+ * // => 'hello: moe'
+ *
+ * var list = '<% _.forEach(people, function(name) { %> <li><%= name %></li> <% }); %>';
+ * _.template(list, { 'people': ['moe', 'curly', 'larry'] });
+ * // => '<li>moe</li><li>curly</li><li>larry</li>'
+ *
+ * var template = _.template('<b><%- value %></b>');
+ * template({ 'value': '<script>' });
+ * // => '<b>&lt;script></b>'
+ *
+ * // using `print`
+ * var compiled = _.template('<% print("Hello " + epithet); %>');
+ * compiled({ 'epithet': 'stooge' });
+ * // => 'Hello stooge.'
+ *
+ * // using custom template settings
+ * _.templateSettings = {
+ * 'interpolate': /\{\{(.+?)\}\}/g
+ * };
+ *
+ * var template = _.template('Hello {{ name }}!');
+ * template({ 'name': 'Mustache' });
+ * // => 'Hello Mustache!'
+ *
+ * // using the `variable` option
+ * _.template('<%= data.hasWith %>', { 'hasWith': 'no' }, { 'variable': 'data' });
+ * // => 'no'
+ *
+ * // using the `source` property
+ * <script>
+ * JST.project = <%= _.template(jstText).source %>;
+ * </script>
+ */
+ function template(text, data, options) {
+ options || (options = {});
+
+ var result,
+ defaults = lodash.templateSettings,
+ escapeDelimiter = options.escape,
+ evaluateDelimiter = options.evaluate,
+ interpolateDelimiter = options.interpolate,
+ variable = options.variable;
+
+ // use template defaults if no option is provided
+ if (escapeDelimiter == null) {
+ escapeDelimiter = defaults.escape;
+ }
+ if (evaluateDelimiter == null) {
+ evaluateDelimiter = defaults.evaluate;
+ }
+ if (interpolateDelimiter == null) {
+ interpolateDelimiter = defaults.interpolate;
+ }
+
+ // tokenize delimiters to avoid escaping them
+ if (escapeDelimiter) {
+ text = text.replace(escapeDelimiter, tokenizeEscape);
+ }
+ if (interpolateDelimiter) {
+ text = text.replace(interpolateDelimiter, tokenizeInterpolate);
+ }
+ if (evaluateDelimiter) {
+ text = text.replace(evaluateDelimiter, tokenizeEvaluate);
+ }
+
+ // escape characters that cannot be included in string literals and
+ // detokenize delimiter code snippets
+ text = "__p='" + text
+ .replace(reUnescapedString, escapeStringChar)
+ .replace(reToken, detokenize) + "';\n";
+
+ // clear stored code snippets
+ tokenized.length = 0;
+
+ // if `options.variable` is not specified, add `data` to the top of the scope chain
+ if (!variable) {
+ variable = defaults.variable;
+ text = 'with (' + variable + ' || {}) {\n' + text + '\n}\n';
+ }
+
+ text = 'function(' + variable + ') {\n' +
+ 'var __p, __t, __j = Array.prototype.join;\n' +
+ 'function print() { __p += __j.call(arguments, \'\') }\n' +
+ text +
+ 'return __p\n}';
+
+ // add a sourceURL for easier debugging
+ // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
+ if (useSourceURL) {
+ text += '\n//@ sourceURL=/lodash/template/source[' + (templateCounter++) + ']';
+ }
+
+ result = Function('_', 'return ' + text)(lodash);
+
+ if (data) {
+ return result(data);
+ }
+ // provide the compiled function's source via its `toString()` method, in
+ // supported environments, or the `source` property as a convenience for
+ // build time precompilation
+ result.source = text;
+ return result;
+ }
+
+ /**
+ * Executes the `callback` function `n` times. The `callback` is bound to
+ * `thisArg` and invoked with 1 argument; (index).
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {Number} n The number of times to execute the callback.
+ * @param {Function} callback The function called per iteration.
+ * @param {Mixed} [thisArg] The `this` binding for the callback.
+ * @example
+ *
+ * _.times(3, function() { genie.grantWish(); });
+ * // => calls `genie.grantWish()` 3 times
+ *
+ * _.times(3, function() { this.grantWish(); }, genie);
+ * // => also calls `genie.grantWish()` 3 times
+ */
+ function times(n, callback, thisArg) {
+ var index = -1;
+ if (thisArg) {
+ while (++index < n) {
+ callback.call(thisArg, index);
+ }
+ } else {
+ while (++index < n) {
+ callback(index);
+ }
+ }
+ }
+
+ /**
+ * Generates a unique id. If `prefix` is passed, the id will be appended to it.
+ *
+ * @static
+ * @memberOf _
+ * @category Utilities
+ * @param {String} [prefix] The value to prefix the id with.
+ * @returns {Number|String} Returns a numeric id if no prefix is passed, else
+ * a string id may be returned.
+ * @example
+ *
+ * _.uniqueId('contact_');
+ * // => 'contact_104'
+ */
+ function uniqueId(prefix) {
+ var id = idCounter++;
+ return prefix ? prefix + id : id;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * Wraps the value in a `lodash` chainable object.
+ *
+ * @static
+ * @memberOf _
+ * @category Chaining
+ * @param {Mixed} value The value to wrap.
+ * @returns {Object} Returns the `lodash` chainable object.
+ * @example
+ *
+ * var stooges = [
+ * { 'name': 'moe', 'age': 40 },
+ * { 'name': 'larry', 'age': 50 },
+ * { 'name': 'curly', 'age': 60 }
+ * ];
+ *
+ * var youngest = _.chain(stooges)
+ * .sortBy(function(stooge) { return stooge.age; })
+ * .map(function(stooge) { return stooge.name + ' is ' + stooge.age; })
+ * .first()
+ * .value();
+ * // => 'moe is 40'
+ */
+ function chain(value) {
+ value = new LoDash(value);
+ value._chain = true;
+ return value;
+ }
+
+ /**
+ * Extracts the value from a wrapped chainable object.
+ *
+ * @name chain
+ * @memberOf _
+ * @category Chaining
+ * @returns {Mixed} Returns the wrapped object.
+ * @example
+ *
+ * _([1, 2, 3]).value();
+ * // => [1, 2, 3]
+ */
+ function wrapperChain() {
+ this._chain = true;
+ return this;
+ }
+
+ /**
+ * Extracts the value from a wrapped chainable object.
+ *
+ * @name value
+ * @memberOf _
+ * @category Chaining
+ * @returns {Mixed} Returns the wrapped object.
+ * @example
+ *
+ * _([1, 2, 3]).value();
+ * // => [1, 2, 3]
+ */
+ function wrapperValue() {
+ return this._wrapped;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * The semantic version number.
+ *
+ * @static
+ * @memberOf _
+ * @type String
+ */
+ lodash.VERSION = '0.3.1';
+
+ // assign static methods
+ lodash.after = after;
+ lodash.bind = bind;
+ lodash.bindAll = bindAll;
+ lodash.chain = chain;
+ lodash.clone = clone;
+ lodash.compact = compact;
+ lodash.compose = compose;
+ lodash.contains = contains;
+ lodash.debounce = debounce;
+ lodash.defaults = defaults;
+ lodash.defer = defer;
+ lodash.delay = delay;
+ lodash.difference = difference;
+ lodash.escape = escape;
+ lodash.every = every;
+ lodash.extend = extend;
+ lodash.filter = filter;
+ lodash.find = find;
+ lodash.first = first;
+ lodash.flatten = flatten;
+ lodash.forEach = forEach;
+ lodash.forIn = forIn;
+ lodash.forOwn = forOwn;
+ lodash.functions = functions;
+ lodash.groupBy = groupBy;
+ lodash.has = has;
+ lodash.identity = identity;
+ lodash.indexOf = indexOf;
+ lodash.initial = initial;
+ lodash.intersection = intersection;
+ lodash.invoke = invoke;
+ lodash.isArguments = isArguments;
+ lodash.isArray = isArray;
+ lodash.isBoolean = isBoolean;
+ lodash.isDate = isDate;
+ lodash.isElement = isElement;
+ lodash.isEmpty = isEmpty;
+ lodash.isEqual = isEqual;
+ lodash.isFinite = isFinite;
+ lodash.isFunction = isFunction;
+ lodash.isNaN = isNaN;
+ lodash.isNull = isNull;
+ lodash.isNumber = isNumber;
+ lodash.isObject = isObject;
+ lodash.isRegExp = isRegExp;
+ lodash.isString = isString;
+ lodash.isUndefined = isUndefined;
+ lodash.keys = keys;
+ lodash.last = last;
+ lodash.lastIndexOf = lastIndexOf;
+ lodash.map = map;
+ lodash.max = max;
+ lodash.memoize = memoize;
+ lodash.min = min;
+ lodash.mixin = mixin;
+ lodash.noConflict = noConflict;
+ lodash.once = once;
+ lodash.partial = partial;
+ lodash.pick = pick;
+ lodash.pluck = pluck;
+ lodash.range = range;
+ lodash.reduce = reduce;
+ lodash.reduceRight = reduceRight;
+ lodash.reject = reject;
+ lodash.rest = rest;
+ lodash.result = result;
+ lodash.shuffle = shuffle;
+ lodash.size = size;
+ lodash.some = some;
+ lodash.sortBy = sortBy;
+ lodash.sortedIndex = sortedIndex;
+ lodash.tap = tap;
+ lodash.template = template;
+ lodash.throttle = throttle;
+ lodash.times = times;
+ lodash.toArray = toArray;
+ lodash.union = union;
+ lodash.uniq = uniq;
+ lodash.uniqueId = uniqueId;
+ lodash.values = values;
+ lodash.without = without;
+ lodash.wrap = wrap;
+ lodash.zip = zip;
+
+ // assign aliases
+ lodash.all = every;
+ lodash.any = some;
+ lodash.collect = map;
+ lodash.detect = find;
+ lodash.each = forEach;
+ lodash.foldl = reduce;
+ lodash.foldr = reduceRight;
+ lodash.head = first;
+ lodash.include = contains;
+ lodash.inject = reduce;
+ lodash.methods = functions;
+ lodash.select = filter;
+ lodash.tail = rest;
+ lodash.take = first;
+ lodash.unique = uniq;
+
+ // add pseudo private properties used and removed during the build process
+ lodash._iteratorTemplate = iteratorTemplate;
+ lodash._shimKeys = shimKeys;
+
+ /*--------------------------------------------------------------------------*/
+
+ // assign private `LoDash` constructor's prototype
+ LoDash.prototype = lodash.prototype;
+
+ // add all static functions to `LoDash.prototype`
+ mixin(lodash);
+
+ // add `LoDash.prototype.chain` after calling `mixin()` to avoid overwriting
+ // it with the wrapped `lodash.chain`
+ LoDash.prototype.chain = wrapperChain;
+ LoDash.prototype.value = wrapperValue;
+
+ // add all mutator Array functions to the wrapper.
+ forEach(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
+ var func = ArrayProto[methodName];
+
+ LoDash.prototype[methodName] = function() {
+ var value = this._wrapped;
+ func.apply(value, arguments);
+
+ // IE compatibility mode and IE < 9 have buggy Array `shift()` and `splice()`
+ // functions that fail to remove the last element, `value[0]`, of
+ // array-like objects even though the `length` property is set to `0`.
+ // The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
+ // is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
+ if (value.length === 0) {
+ delete value[0];
+ }
+ if (this._chain) {
+ value = new LoDash(value);
+ value._chain = true;
+ }
+ return value;
+ };
+ });
+
+ // add all accessor Array functions to the wrapper.
+ forEach(['concat', 'join', 'slice'], function(methodName) {
+ var func = ArrayProto[methodName];
+
+ LoDash.prototype[methodName] = function() {
+ var value = this._wrapped,
+ result = func.apply(value, arguments);
+
+ if (this._chain) {
+ result = new LoDash(result);
+ result._chain = true;
+ }
+ return result;
+ };
+ });
+
+ /*--------------------------------------------------------------------------*/
+
+ // expose Lo-Dash
+ // some AMD build optimizers, like r.js, check for specific condition patterns like the following:
+ if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+ // Expose Lo-Dash to the global object even when an AMD loader is present in
+ // case Lo-Dash was injected by a third-party script and not intended to be
+ // loaded as a module. The global assignment can be reverted in the Lo-Dash
+ // module via its `noConflict()` method.
+ window._ = lodash;
+
+ // define as an anonymous module so, through path mapping, it can be
+ // referenced as the "underscore" module
+ define(function() {
+ return lodash;
+ });
+ }
+ // check for `exports` after `define` in case a build optimizer adds an `exports` object
+ else if (freeExports) {
+ // in Node.js or RingoJS v0.8.0+
+ if (typeof module == 'object' && module && module.exports == freeExports) {
+ (module.exports = lodash)._ = lodash;
+ }
+ // in Narwhal or RingoJS v0.7.0-
+ else {
+ freeExports._ = lodash;
+ }
+ }
+ else {
+ // in a browser or Rhino
+ window._ = lodash;
+ }
+}(this));
diff --git a/src/fauxton/assets/js/libs/nv.d3.js b/src/fauxton/assets/js/libs/nv.d3.js
new file mode 100755
index 000000000..2824f9b49
--- /dev/null
+++ b/src/fauxton/assets/js/libs/nv.d3.js
@@ -0,0 +1,11286 @@
+(function(){
+
+var nv = window.nv || {};
+
+nv.version = '0.0.1a';
+nv.dev = true //set false when in production
+
+window.nv = nv;
+
+nv.tooltip = {}; // For the tooltip system
+nv.utils = {}; // Utility subsystem
+nv.models = {}; //stores all the possible models/components
+nv.charts = {}; //stores all the ready to use charts
+nv.graphs = []; //stores all the graphs currently on the page
+nv.logs = {}; //stores some statistics and potential error messages
+
+nv.dispatch = d3.dispatch('render_start', 'render_end');
+
+// *************************************************************************
+// Development render timers - disabled if dev = false
+
+if (nv.dev) {
+ nv.dispatch.on('render_start', function(e) {
+ nv.logs.startTime = +new Date();
+ });
+
+ nv.dispatch.on('render_end', function(e) {
+ nv.logs.endTime = +new Date();
+ nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
+ nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
+ });
+}
+
+// ********************************************
+// Public Core NV functions
+
+// Logs all arguments, and returns the last so you can test things in place
+nv.log = function() {
+ if (nv.dev && console.log && console.log.apply)
+ console.log.apply(console, arguments)
+ else if (nv.dev && console.log && Function.prototype.bind) {
+ var log = Function.prototype.bind.call(console.log, console);
+ log.apply(console, arguments);
+ }
+ return arguments[arguments.length - 1];
+};
+
+
+nv.render = function render(step) {
+ step = step || 1; // number of graphs to generate in each timout loop
+
+ render.active = true;
+ nv.dispatch.render_start();
+
+ setTimeout(function() {
+ var chart, graph;
+
+ for (var i = 0; i < step && (graph = render.queue[i]); i++) {
+ chart = graph.generate();
+ if (typeof graph.callback == typeof(Function)) graph.callback(chart);
+ nv.graphs.push(chart);
+ }
+
+ render.queue.splice(0, i);
+
+ if (render.queue.length) setTimeout(arguments.callee, 0);
+ else { nv.render.active = false; nv.dispatch.render_end(); }
+ }, 0);
+};
+
+nv.render.active = false;
+nv.render.queue = [];
+
+nv.addGraph = function(obj) {
+ if (typeof arguments[0] === typeof(Function))
+ obj = {generate: arguments[0], callback: arguments[1]};
+
+ nv.render.queue.push(obj);
+
+ if (!nv.render.active) nv.render();
+};
+
+nv.identity = function(d) { return d; };
+
+nv.strip = function(s) { return s.replace(/(\s|&)/g,''); };
+
+function daysInMonth(month,year) {
+ return (new Date(year, month+1, 0)).getDate();
+}
+
+function d3_time_range(floor, step, number) {
+ return function(t0, t1, dt) {
+ var time = floor(t0), times = [];
+ if (time < t0) step(time);
+ if (dt > 1) {
+ while (time < t1) {
+ var date = new Date(+time);
+ if ((number(date) % dt === 0)) times.push(date);
+ step(time);
+ }
+ } else {
+ while (time < t1) { times.push(new Date(+time)); step(time); }
+ }
+ return times;
+ };
+}
+
+d3.time.monthEnd = function(date) {
+ return new Date(date.getFullYear(), date.getMonth(), 0);
+};
+
+d3.time.monthEnds = d3_time_range(d3.time.monthEnd, function(date) {
+ date.setUTCDate(date.getUTCDate() + 1);
+ date.setDate(daysInMonth(date.getMonth() + 1, date.getFullYear()));
+ }, function(date) {
+ return date.getMonth();
+ }
+);
+
+
+/*****
+ * A no-frills tooltip implementation.
+ *****/
+
+
+(function() {
+
+ var nvtooltip = window.nv.tooltip = {};
+
+ nvtooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
+
+ var container = document.createElement('div');
+ container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
+
+ gravity = gravity || 's';
+ dist = dist || 20;
+
+ var body = parentContainer ? parentContainer : document.getElementsByTagName('body')[0];
+
+ container.innerHTML = content;
+ container.style.left = 0;
+ container.style.top = 0;
+ container.style.opacity = 0;
+
+ body.appendChild(container);
+
+ var height = parseInt(container.offsetHeight),
+ width = parseInt(container.offsetWidth),
+ windowWidth = nv.utils.windowSize().width,
+ windowHeight = nv.utils.windowSize().height,
+ scrollTop = window.scrollY,
+ scrollLeft = window.scrollX,
+ left, top;
+
+ windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
+ windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
+
+ var tooltipTop = function ( Elem ) {
+ var offsetTop = top;
+ do {
+ if( !isNaN( Elem.offsetTop ) ) {
+ offsetTop += (Elem.offsetTop);
+ }
+ } while( Elem = Elem.offsetParent );
+ return offsetTop;
+ }
+
+ var tooltipLeft = function ( Elem ) {
+ var offsetLeft = left;
+ do {
+ if( !isNaN( Elem.offsetLeft ) ) {
+ offsetLeft += (Elem.offsetLeft);
+ }
+ } while( Elem = Elem.offsetParent );
+ return offsetLeft;
+ }
+
+ switch (gravity) {
+ case 'e':
+ left = pos[0] - width - dist;
+ top = pos[1] - (height / 2);
+ var tLeft = tooltipLeft(container);
+ var tTop = tooltipTop(container);
+ if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
+ if (tTop < scrollTop) top = scrollTop - tTop + top;
+ if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
+ break;
+ case 'w':
+ left = pos[0] + dist;
+ top = pos[1] - (height / 2);
+ if (tLeft + width > windowWidth) left = pos[0] - width - dist;
+ if (tTop < scrollTop) top = scrollTop + 5;
+ if (tTop + height > scrollTop + windowHeight) top = scrollTop - height - 5;
+ break;
+ case 'n':
+ left = pos[0] - (width / 2) - 5;
+ top = pos[1] + dist;
+ var tLeft = tooltipLeft(container);
+ var tTop = tooltipTop(container);
+ if (tLeft < scrollLeft) left = scrollLeft + 5;
+ if (tLeft + width > windowWidth) left = left - width/2 + 5;
+ if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
+ break;
+ case 's':
+ left = pos[0] - (width / 2);
+ top = pos[1] - height - dist;
+ var tLeft = tooltipLeft(container);
+ var tTop = tooltipTop(container);
+ if (tLeft < scrollLeft) left = scrollLeft + 5;
+ if (tLeft + width > windowWidth) left = left - width/2 + 5;
+ if (scrollTop > tTop) top = scrollTop;
+ break;
+ }
+
+
+ container.style.left = left+'px';
+ container.style.top = top+'px';
+ container.style.opacity = 1;
+ container.style.position = 'absolute'; //fix scroll bar issue
+ container.style.pointerEvents = 'none'; //fix scroll bar issue
+
+ return container;
+ };
+
+ nvtooltip.cleanup = function() {
+
+ // Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
+ var tooltips = document.getElementsByClassName('nvtooltip');
+ var purging = [];
+ while(tooltips.length) {
+ purging.push(tooltips[0]);
+ tooltips[0].style.transitionDelay = '0 !important';
+ tooltips[0].style.opacity = 0;
+ tooltips[0].className = 'nvtooltip-pending-removal';
+ }
+
+
+ setTimeout(function() {
+
+ while (purging.length) {
+ var removeMe = purging.pop();
+ removeMe.parentNode.removeChild(removeMe);
+ }
+ }, 500);
+ };
+
+
+})();
+
+nv.utils.windowSize = function() {
+ // Sane defaults
+ var size = {width: 640, height: 480};
+
+ // Earlier IE uses Doc.body
+ if (document.body && document.body.offsetWidth) {
+ size.width = document.body.offsetWidth;
+ size.height = document.body.offsetHeight;
+ }
+
+ // IE can use depending on mode it is in
+ if (document.compatMode=='CSS1Compat' &&
+ document.documentElement &&
+ document.documentElement.offsetWidth ) {
+ size.width = document.documentElement.offsetWidth;
+ size.height = document.documentElement.offsetHeight;
+ }
+
+ // Most recent browsers use
+ if (window.innerWidth && window.innerHeight) {
+ size.width = window.innerWidth;
+ size.height = window.innerHeight;
+ }
+ return (size);
+};
+
+
+
+// Easy way to bind multiple functions to window.onresize
+// TODO: give a way to remove a function after its bound, other than removing alkl of them
+nv.utils.windowResize = function(fun){
+ var oldresize = window.onresize;
+
+ window.onresize = function(e) {
+ if (typeof oldresize == 'function') oldresize(e);
+ fun(e);
+ }
+}
+
+// Backwards compatible way to implement more d3-like coloring of graphs.
+// If passed an array, wrap it in a function which implements the old default
+// behavior
+nv.utils.getColor = function(color) {
+ if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back
+
+ if( Object.prototype.toString.call( color ) === '[object Array]' )
+ return function(d, i) { return d.color || color[i % color.length]; };
+ else
+ return color;
+ //can't really help it if someone passes rubbish as color
+}
+
+// Default color chooser uses the index of an object as before.
+nv.utils.defaultColor = function() {
+ var colors = d3.scale.category20().range();
+ return function(d, i) { return d.color || colors[i % colors.length] };
+}
+
+
+// Returns a color function that takes the result of 'getKey' for each series and
+// looks for a corresponding color from the dictionary,
+nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
+ getKey = getKey || function(series) { return series.key }; // use default series.key if getKey is undefined
+ defaultColors = defaultColors || d3.scale.category20().range(); //default color function
+
+ var defIndex = defaultColors.length; //current default color (going in reverse)
+
+ return function(series, index) {
+ var key = getKey(series);
+
+ if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over
+
+ if (typeof dictionary[key] !== "undefined")
+ return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key];
+ else
+ return defaultColors[--defIndex]; // no match in dictionary, use default color
+ }
+}
+
+
+
+// From the PJAX example on d3js.org, while this is not really directly needed
+// it's a very cool method for doing pjax, I may expand upon it a little bit,
+// open to suggestions on anything that may be useful
+nv.utils.pjax = function(links, content) {
+ d3.selectAll(links).on("click", function() {
+ history.pushState(this.href, this.textContent, this.href);
+ load(this.href);
+ d3.event.preventDefault();
+ });
+
+ function load(href) {
+ d3.html(href, function(fragment) {
+ var target = d3.select(content).node();
+ target.parentNode.replaceChild(d3.select(fragment).select(content).node(), target);
+ nv.utils.pjax(links, content);
+ });
+ }
+
+ d3.select(window).on("popstate", function() {
+ if (d3.event.state) load(d3.event.state);
+ });
+}
+
+nv.models.axis = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var axis = d3.svg.axis()
+ ;
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 75 //only used for tickLabel currently
+ , height = 60 //only used for tickLabel currently
+ , scale = d3.scale.linear()
+ , axisLabelText = null
+ , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
+ , highlightZero = true
+ , rotateLabels = 0
+ , rotateYLabel = true
+ , staggerLabels = false
+ , isOrdinal = false
+ , ticks = null
+ ;
+
+ axis
+ .scale(scale)
+ .orient('bottom')
+ .tickFormat(function(d) { return d })
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var scale0;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this);
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g')
+
+ //------------------------------------------------------------
+
+
+ if (ticks !== null)
+ axis.ticks(ticks);
+ else if (axis.orient() == 'top' || axis.orient() == 'bottom')
+ axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
+
+
+ //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
+
+
+ d3.transition(g)
+ .call(axis);
+
+ scale0 = scale0 || axis.scale();
+
+ var fmt = axis.tickFormat();
+ if (fmt == null) {
+ fmt = scale0.tickFormat();
+ }
+
+ var axisLabel = g.selectAll('text.nv-axislabel')
+ .data([axisLabelText || null]);
+ axisLabel.exit().remove();
+ switch (axis.orient()) {
+ case 'top':
+ axisLabel.enter().append('text').attr('class', 'nv-axislabel')
+ .attr('text-anchor', 'middle')
+ .attr('y', 0);
+ var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
+ axisLabel
+ .attr('x', w/2);
+ if (showMaxMin) {
+ var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
+ .data(scale.domain());
+ axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
+ axisMaxMin.exit().remove();
+ axisMaxMin
+ .attr('transform', function(d,i) {
+ return 'translate(' + scale(d) + ',0)'
+ })
+ .select('text')
+ .attr('dy', '0em')
+ .attr('y', -axis.tickPadding())
+ .attr('text-anchor', 'middle')
+ .text(function(d,i) {
+ var v = fmt(d);
+ return ('' + v).match('NaN') ? '' : v;
+ });
+ d3.transition(axisMaxMin)
+ .attr('transform', function(d,i) {
+ return 'translate(' + scale.range()[i] + ',0)'
+ });
+ }
+ break;
+ case 'bottom':
+ var xLabelMargin = 36;
+ var maxTextWidth = 30;
+ var xTicks = g.selectAll('g').select("text");
+ if (rotateLabels%360) {
+ //Calculate the longest xTick width
+ xTicks.each(function(d,i){
+ var width = this.getBBox().width;
+ if(width > maxTextWidth) maxTextWidth = width;
+ });
+ //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
+ var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
+ var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
+ //Rotate all xTicks
+ xTicks
+ .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
+ .attr('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
+ }
+ axisLabel.enter().append('text').attr('class', 'nv-axislabel')
+ .attr('text-anchor', 'middle')
+ .attr('y', xLabelMargin);
+ var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
+ axisLabel
+ .attr('x', w/2);
+ if (showMaxMin) {
+ //if (showMaxMin && !isOrdinal) {
+ var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
+ //.data(scale.domain())
+ .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
+ axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
+ axisMaxMin.exit().remove();
+ axisMaxMin
+ .attr('transform', function(d,i) {
+ return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
+ })
+ .select('text')
+ .attr('dy', '.71em')
+ .attr('y', axis.tickPadding())
+ .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
+ .attr('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
+ .text(function(d,i) {
+ var v = fmt(d);
+ return ('' + v).match('NaN') ? '' : v;
+ });
+ d3.transition(axisMaxMin)
+ .attr('transform', function(d,i) {
+ //return 'translate(' + scale.range()[i] + ',0)'
+ //return 'translate(' + scale(d) + ',0)'
+ return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
+ });
+ }
+ if (staggerLabels)
+ xTicks
+ .attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' });
+
+ break;
+ case 'right':
+ axisLabel.enter().append('text').attr('class', 'nv-axislabel')
+ .attr('text-anchor', rotateYLabel ? 'middle' : 'begin')
+ .attr('transform', rotateYLabel ? 'rotate(90)' : '')
+ .attr('y', rotateYLabel ? (-Math.max(margin.right,width) + 12) : -10); //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
+ axisLabel
+ .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
+ if (showMaxMin) {
+ var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
+ .data(scale.domain());
+ axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
+ .style('opacity', 0);
+ axisMaxMin.exit().remove();
+ axisMaxMin
+ .attr('transform', function(d,i) {
+ return 'translate(0,' + scale(d) + ')'
+ })
+ .select('text')
+ .attr('dy', '.32em')
+ .attr('y', 0)
+ .attr('x', axis.tickPadding())
+ .attr('text-anchor', 'start')
+ .text(function(d,i) {
+ var v = fmt(d);
+ return ('' + v).match('NaN') ? '' : v;
+ });
+ d3.transition(axisMaxMin)
+ .attr('transform', function(d,i) {
+ return 'translate(0,' + scale.range()[i] + ')'
+ })
+ .select('text')
+ .style('opacity', 1);
+ }
+ break;
+ case 'left':
+ /*
+ //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
+ var yTicks = g.selectAll('g').select("text");
+ yTicks.each(function(d,i){
+ var labelPadding = this.getBBox().width + axis.tickPadding() + 16;
+ if(labelPadding > width) width = labelPadding;
+ });
+ */
+ axisLabel.enter().append('text').attr('class', 'nv-axislabel')
+ .attr('text-anchor', rotateYLabel ? 'middle' : 'end')
+ .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
+ .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 12) : -10); //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
+ axisLabel
+ .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
+ if (showMaxMin) {
+ var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
+ .data(scale.domain());
+ axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
+ .style('opacity', 0);
+ axisMaxMin.exit().remove();
+ axisMaxMin
+ .attr('transform', function(d,i) {
+ return 'translate(0,' + scale0(d) + ')'
+ })
+ .select('text')
+ .attr('dy', '.32em')
+ .attr('y', 0)
+ .attr('x', -axis.tickPadding())
+ .attr('text-anchor', 'end')
+ .text(function(d,i) {
+ var v = fmt(d);
+ return ('' + v).match('NaN') ? '' : v;
+ });
+ d3.transition(axisMaxMin)
+ .attr('transform', function(d,i) {
+ return 'translate(0,' + scale.range()[i] + ')'
+ })
+ .select('text')
+ .style('opacity', 1);
+ }
+ break;
+ }
+ axisLabel
+ .text(function(d) { return d });
+
+
+ if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
+ //check if max and min overlap other values, if so, hide the values that overlap
+ g.selectAll('g') // the g's wrapping each tick
+ .each(function(d,i) {
+ d3.select(this).select('text').attr('opacity', 1);
+ if (scale(d) < scale.range()[1] + 10 || scale(d) > scale.range()[0] - 10) { // 10 is assuming text height is 16... if d is 0, leave it!
+ if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
+ d3.select(this).attr('opacity', 0);
+
+ d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
+ }
+ });
+
+ //if Max and Min = 0 only show min, Issue #281
+ if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0)
+ wrap.selectAll('g.nv-axisMaxMin')
+ .style('opacity', function(d,i) { return !i ? 1 : 0 });
+
+ }
+
+ if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
+ var maxMinRange = [];
+ wrap.selectAll('g.nv-axisMaxMin')
+ .each(function(d,i) {
+ try {
+ if (i) // i== 1, max position
+ maxMinRange.push(scale(d) - this.getBBox().width - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
+ else // i==0, min position
+ maxMinRange.push(scale(d) + this.getBBox().width + 4)
+ }catch (err) {
+ if (i) // i== 1, max position
+ maxMinRange.push(scale(d) - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
+ else // i==0, min position
+ maxMinRange.push(scale(d) + 4)
+ }
+ });
+ g.selectAll('g') // the g's wrapping each tick
+ .each(function(d,i) {
+ if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
+ if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
+ d3.select(this).remove();
+ else
+ d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
+ }
+ });
+ }
+
+
+ //highlight zero line ... Maybe should not be an option and should just be in CSS?
+ if (highlightZero)
+ g.selectAll('line.tick')
+ .filter(function(d) { return !parseFloat(Math.round(d*100000)/1000000) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
+ .classed('zero', true);
+
+ //store old scales for use in transitions on update
+ scale0 = scale.copy();
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.axis = axis;
+
+ d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat');
+ d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use
+
+ chart.margin = function(_) {
+ if(!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ }
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.ticks = function(_) {
+ if (!arguments.length) return ticks;
+ ticks = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.axisLabel = function(_) {
+ if (!arguments.length) return axisLabelText;
+ axisLabelText = _;
+ return chart;
+ }
+
+ chart.showMaxMin = function(_) {
+ if (!arguments.length) return showMaxMin;
+ showMaxMin = _;
+ return chart;
+ }
+
+ chart.highlightZero = function(_) {
+ if (!arguments.length) return highlightZero;
+ highlightZero = _;
+ return chart;
+ }
+
+ chart.scale = function(_) {
+ if (!arguments.length) return scale;
+ scale = _;
+ axis.scale(scale);
+ isOrdinal = typeof scale.rangeBands === 'function';
+ d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands');
+ return chart;
+ }
+
+ chart.rotateYLabel = function(_) {
+ if(!arguments.length) return rotateYLabel;
+ rotateYLabel = _;
+ return chart;
+ }
+
+ chart.rotateLabels = function(_) {
+ if(!arguments.length) return rotateLabels;
+ rotateLabels = _;
+ return chart;
+ }
+
+ chart.staggerLabels = function(_) {
+ if (!arguments.length) return staggerLabels;
+ staggerLabels = _;
+ return chart;
+ };
+
+
+ //============================================================
+
+
+ return chart;
+}
+//TODO: consider deprecating and using multibar with single series for this
+nv.models.historicalBar = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 960
+ , height = 500
+ , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+ , x = d3.scale.linear()
+ , y = d3.scale.linear()
+ , getX = function(d) { return d.x }
+ , getY = function(d) { return d.y }
+ , forceX = []
+ , forceY = [0]
+ , padData = false
+ , clipEdge = true
+ , color = nv.utils.defaultColor()
+ , xDomain
+ , yDomain
+ , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ container = d3.select(this);
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ))
+
+ if (padData)
+ x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
+ else
+ x.range([0, availableWidth]);
+
+ y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
+ .range([availableHeight, 0]);
+
+ // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
+ if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
+ if (x.domain()[0] === x.domain()[1])
+ x.domain()[0] ?
+ x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
+ : x.domain([-1,1]);
+
+ if (y.domain()[0] === y.domain()[1])
+ y.domain()[0] ?
+ y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
+ : y.domain([-1,1]);
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-bar').data([data[0].values]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bar');
+ var defsEnter = wrapEnter.append('defs');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-bars');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+ container
+ .on('click', function(d,i) {
+ dispatch.chartClick({
+ data: d,
+ index: i,
+ pos: d3.event,
+ id: id
+ });
+ });
+
+
+ defsEnter.append('clipPath')
+ .attr('id', 'nv-chart-clip-path-' + id)
+ .append('rect');
+
+ wrap.select('#nv-chart-clip-path-' + id + ' rect')
+ .attr('width', availableWidth)
+ .attr('height', availableHeight);
+
+ g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
+
+
+
+ var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
+ .data(function(d) { return d });
+
+ bars.exit().remove();
+
+
+ var barsEnter = bars.enter().append('rect')
+ //.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
+ .attr('x', 0 )
+ .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
+ .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
+ .on('mouseover', function(d,i) {
+ d3.select(this).classed('hover', true);
+ dispatch.elementMouseover({
+ point: d,
+ series: data[0],
+ pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
+ pointIndex: i,
+ seriesIndex: 0,
+ e: d3.event
+ });
+
+ })
+ .on('mouseout', function(d,i) {
+ d3.select(this).classed('hover', false);
+ dispatch.elementMouseout({
+ point: d,
+ series: data[0],
+ pointIndex: i,
+ seriesIndex: 0,
+ e: d3.event
+ });
+ })
+ .on('click', function(d,i) {
+ dispatch.elementClick({
+ //label: d[label],
+ value: getY(d,i),
+ data: d,
+ index: i,
+ pos: [x(getX(d,i)), y(getY(d,i))],
+ e: d3.event,
+ id: id
+ });
+ d3.event.stopPropagation();
+ })
+ .on('dblclick', function(d,i) {
+ dispatch.elementDblClick({
+ //label: d[label],
+ value: getY(d,i),
+ data: d,
+ index: i,
+ pos: [x(getX(d,i)), y(getY(d,i))],
+ e: d3.event,
+ id: id
+ });
+ d3.event.stopPropagation();
+ });
+
+ bars
+ .attr('fill', function(d,i) { return color(d, i); })
+ .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
+ .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; }) //TODO: better width calculations that don't assume always uniform data spacing;w
+ .attr('width', (availableWidth / data[0].values.length) * .9 )
+
+
+ d3.transition(bars)
+ //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
+ .attr('y', function(d,i) {
+ return getY(d,i) < 0 ?
+ y(0) :
+ y(0) - y(getY(d,i)) < 1 ?
+ y(0) - 1 :
+ y(getY(d,i))
+ })
+ .attr('height', function(d,i) { return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) });
+ //.order(); // not sure if this makes any sense for this model
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = _;
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = _;
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.xScale = function(_) {
+ if (!arguments.length) return x;
+ x = _;
+ return chart;
+ };
+
+ chart.yScale = function(_) {
+ if (!arguments.length) return y;
+ y = _;
+ return chart;
+ };
+
+ chart.xDomain = function(_) {
+ if (!arguments.length) return xDomain;
+ xDomain = _;
+ return chart;
+ };
+
+ chart.yDomain = function(_) {
+ if (!arguments.length) return yDomain;
+ yDomain = _;
+ return chart;
+ };
+
+ chart.forceX = function(_) {
+ if (!arguments.length) return forceX;
+ forceX = _;
+ return chart;
+ };
+
+ chart.forceY = function(_) {
+ if (!arguments.length) return forceY;
+ forceY = _;
+ return chart;
+ };
+
+ chart.padData = function(_) {
+ if (!arguments.length) return padData;
+ padData = _;
+ return chart;
+ };
+
+ chart.clipEdge = function(_) {
+ if (!arguments.length) return clipEdge;
+ clipEdge = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ chart.id = function(_) {
+ if (!arguments.length) return id;
+ id = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+// Chart design based on the recommendations of Stephen Few. Implementation
+// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
+// http://projects.instantcognition.com/protovis/bulletchart/
+
+nv.models.bullet = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , orient = 'left' // TODO top & bottom
+ , reverse = false
+ , ranges = function(d) { return d.ranges }
+ , markers = function(d) { return d.markers }
+ , measures = function(d) { return d.measures }
+ , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
+ , width = 380
+ , height = 30
+ , tickFormat = null
+ , color = nv.utils.getColor(['#1f77b4'])
+ , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(d, i) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ container = d3.select(this);
+
+ var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
+ markerz = markers.call(this, d, i).slice().sort(d3.descending),
+ measurez = measures.call(this, d, i).slice().sort(d3.descending);
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ // Compute the new x-scale.
+ var x1 = d3.scale.linear()
+ .domain( d3.extent(d3.merge([forceX, rangez])) )
+ .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
+
+ // Retrieve the old x-scale, if this is an update.
+ var x0 = this.__chart__ || d3.scale.linear()
+ .domain([0, Infinity])
+ .range(x1.range());
+
+ // Stash the new scale.
+ this.__chart__ = x1;
+
+
+ var rangeMin = d3.min(rangez), //rangez[2]
+ rangeMax = d3.max(rangez), //rangez[0]
+ rangeAvg = rangez[1];
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
+ gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
+ gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
+ gEnter.append('rect').attr('class', 'nv-measure');
+ gEnter.append('path').attr('class', 'nv-markerTriangle');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+
+ var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
+ w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
+ var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
+ xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
+
+
+ g.select('rect.nv-rangeMax')
+ .attr('height', availableHeight)
+ .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
+ .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
+ .datum(rangeMax > 0 ? rangeMax : rangeMin)
+ /*
+ .attr('x', rangeMin < 0 ?
+ rangeMax > 0 ?
+ x1(rangeMin)
+ : x1(rangeMax)
+ : x1(0))
+ */
+
+ g.select('rect.nv-rangeAvg')
+ .attr('height', availableHeight)
+ .attr('width', w1(rangeAvg))
+ .attr('x', xp1(rangeAvg))
+ .datum(rangeAvg)
+ /*
+ .attr('width', rangeMax <= 0 ?
+ x1(rangeMax) - x1(rangeAvg)
+ : x1(rangeAvg) - x1(rangeMin))
+ .attr('x', rangeMax <= 0 ?
+ x1(rangeAvg)
+ : x1(rangeMin))
+ */
+
+ g.select('rect.nv-rangeMin')
+ .attr('height', availableHeight)
+ .attr('width', w1(rangeMax))
+ .attr('x', xp1(rangeMax))
+ .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
+ .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
+ .datum(rangeMax > 0 ? rangeMin : rangeMax)
+ /*
+ .attr('width', rangeMax <= 0 ?
+ x1(rangeAvg) - x1(rangeMin)
+ : x1(rangeMax) - x1(rangeAvg))
+ .attr('x', rangeMax <= 0 ?
+ x1(rangeMin)
+ : x1(rangeAvg))
+ */
+
+ g.select('rect.nv-measure')
+ .style('fill', color)
+ .attr('height', availableHeight / 3)
+ .attr('y', availableHeight / 3)
+ .attr('width', measurez < 0 ?
+ x1(0) - x1(measurez[0])
+ : x1(measurez[0]) - x1(0))
+ .attr('x', xp1(measurez))
+ .on('mouseover', function() {
+ dispatch.elementMouseover({
+ value: measurez[0],
+ label: 'Current',
+ pos: [x1(measurez[0]), availableHeight/2]
+ })
+ })
+ .on('mouseout', function() {
+ dispatch.elementMouseout({
+ value: measurez[0],
+ label: 'Current'
+ })
+ })
+
+ var h3 = availableHeight / 6;
+ if (markerz[0]) {
+ g.selectAll('path.nv-markerTriangle')
+ .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
+ .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
+ .on('mouseover', function() {
+ dispatch.elementMouseover({
+ value: markerz[0],
+ label: 'Previous',
+ pos: [x1(markerz[0]), availableHeight/2]
+ })
+ })
+ .on('mouseout', function() {
+ dispatch.elementMouseout({
+ value: markerz[0],
+ label: 'Previous'
+ })
+ });
+ } else {
+ g.selectAll('path.nv-markerTriangle').remove();
+ }
+
+
+ wrap.selectAll('.nv-range')
+ .on('mouseover', function(d,i) {
+ var label = !i ? "Maximum" : i == 1 ? "Mean" : "Minimum";
+
+ dispatch.elementMouseover({
+ value: d,
+ label: label,
+ pos: [x1(d), availableHeight/2]
+ })
+ })
+ .on('mouseout', function(d,i) {
+ var label = !i ? "Maximum" : i == 1 ? "Mean" : "Minimum";
+
+ dispatch.elementMouseout({
+ value: d,
+ label: label
+ })
+ })
+
+/* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY
+ // Update the range rects.
+ var range = g.selectAll('rect.nv-range')
+ .data(rangez);
+
+ range.enter().append('rect')
+ .attr('class', function(d, i) { return 'nv-range nv-s' + i; })
+ .attr('width', w0)
+ .attr('height', availableHeight)
+ .attr('x', reverse ? x0 : 0)
+ .on('mouseover', function(d,i) {
+ dispatch.elementMouseover({
+ value: d,
+ label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable
+ pos: [x1(d), availableHeight/2]
+ })
+ })
+ .on('mouseout', function(d,i) {
+ dispatch.elementMouseout({
+ value: d,
+ label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable
+ })
+ })
+
+ d3.transition(range)
+ .attr('x', reverse ? x1 : 0)
+ .attr('width', w1)
+ .attr('height', availableHeight);
+
+
+ // Update the measure rects.
+ var measure = g.selectAll('rect.nv-measure')
+ .data(measurez);
+
+ measure.enter().append('rect')
+ .attr('class', function(d, i) { return 'nv-measure nv-s' + i; })
+ .style('fill', function(d,i) { return color(d,i ) })
+ .attr('width', w0)
+ .attr('height', availableHeight / 3)
+ .attr('x', reverse ? x0 : 0)
+ .attr('y', availableHeight / 3)
+ .on('mouseover', function(d) {
+ dispatch.elementMouseover({
+ value: d,
+ label: 'Current', //TODO: make these labels a variable
+ pos: [x1(d), availableHeight/2]
+ })
+ })
+ .on('mouseout', function(d) {
+ dispatch.elementMouseout({
+ value: d,
+ label: 'Current' //TODO: make these labels a variable
+ })
+ })
+
+ d3.transition(measure)
+ .attr('width', w1)
+ .attr('height', availableHeight / 3)
+ .attr('x', reverse ? x1 : 0)
+ .attr('y', availableHeight / 3);
+
+
+
+ // Update the marker lines.
+ var marker = g.selectAll('path.nv-markerTriangle')
+ .data(markerz);
+
+ var h3 = availableHeight / 6;
+ marker.enter().append('path')
+ .attr('class', 'nv-markerTriangle')
+ .attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' })
+ .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
+ .on('mouseover', function(d,i) {
+ dispatch.elementMouseover({
+ value: d,
+ label: 'Previous',
+ pos: [x1(d), availableHeight/2]
+ })
+ })
+ .on('mouseout', function(d,i) {
+ dispatch.elementMouseout({
+ value: d,
+ label: 'Previous'
+ })
+ });
+
+ d3.transition(marker)
+ .attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' });
+
+ marker.exit().remove();
+*/
+
+ });
+
+ // d3.timer.flush(); // Not needed?
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+
+ // left, right, top, bottom
+ chart.orient = function(_) {
+ if (!arguments.length) return orient;
+ orient = _;
+ reverse = orient == 'right' || orient == 'bottom';
+ return chart;
+ };
+
+ // ranges (bad, satisfactory, good)
+ chart.ranges = function(_) {
+ if (!arguments.length) return ranges;
+ ranges = _;
+ return chart;
+ };
+
+ // markers (previous, goal)
+ chart.markers = function(_) {
+ if (!arguments.length) return markers;
+ markers = _;
+ return chart;
+ };
+
+ // measures (actual, forecast)
+ chart.measures = function(_) {
+ if (!arguments.length) return measures;
+ measures = _;
+ return chart;
+ };
+
+ chart.forceX = function(_) {
+ if (!arguments.length) return forceX;
+ forceX = _;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.tickFormat = function(_) {
+ if (!arguments.length) return tickFormat;
+ tickFormat = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+};
+
+
+
+// Chart design based on the recommendations of Stephen Few. Implementation
+// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
+// http://projects.instantcognition.com/protovis/bulletchart/
+nv.models.bulletChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var bullet = nv.models.bullet()
+ ;
+
+ var orient = 'left' // TODO top & bottom
+ , reverse = false
+ , margin = {top: 5, right: 40, bottom: 20, left: 120}
+ , ranges = function(d) { return d.ranges }
+ , markers = function(d) { return d.markers }
+ , measures = function(d) { return d.measures }
+ , width = null
+ , height = 55
+ , tickFormat = null
+ , tooltips = true
+ , tooltip = function(key, x, y, e, graph) {
+ return '<h3>' + x + '</h3>' +
+ '<p>' + y + '</p>'
+ }
+ , noData = 'No Data Available.'
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var showTooltip = function(e, offsetElement) {
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
+ top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
+ content = tooltip(e.key, e.label, e.value, e, chart);
+
+ nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
+ };
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(d, i) {
+ var container = d3.select(this);
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ that = this;
+
+
+ chart.update = function() { chart(selection) };
+ chart.container = this;
+
+ //------------------------------------------------------------
+ // Display No Data message if there's nothing to show.
+
+ if (!d || !ranges.call(this, d, i)) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', 18 + margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+
+ var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
+ markerz = markers.call(this, d, i).slice().sort(d3.descending),
+ measurez = measures.call(this, d, i).slice().sort(d3.descending);
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-bulletWrap');
+ gEnter.append('g').attr('class', 'nv-titles');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+ // Compute the new x-scale.
+ var x1 = d3.scale.linear()
+ .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
+ .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
+
+ // Retrieve the old x-scale, if this is an update.
+ var x0 = this.__chart__ || d3.scale.linear()
+ .domain([0, Infinity])
+ .range(x1.range());
+
+ // Stash the new scale.
+ this.__chart__ = x1;
+
+ /*
+ // Derive width-scales from the x-scales.
+ var w0 = bulletWidth(x0),
+ w1 = bulletWidth(x1);
+
+ function bulletWidth(x) {
+ var x0 = x(0);
+ return function(d) {
+ return Math.abs(x(d) - x(0));
+ };
+ }
+
+ function bulletTranslate(x) {
+ return function(d) {
+ return 'translate(' + x(d) + ',0)';
+ };
+ }
+ */
+
+ var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
+ w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
+
+
+ var title = gEnter.select('.nv-titles').append('g')
+ .attr('text-anchor', 'end')
+ .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
+ title.append('text')
+ .attr('class', 'nv-title')
+ .text(function(d) { return d.title; });
+
+ title.append('text')
+ .attr('class', 'nv-subtitle')
+ .attr('dy', '1em')
+ .text(function(d) { return d.subtitle; });
+
+
+
+ bullet
+ .width(availableWidth)
+ .height(availableHeight)
+
+ var bulletWrap = g.select('.nv-bulletWrap');
+
+ d3.transition(bulletWrap).call(bullet);
+
+
+
+ // Compute the tick format.
+ var format = tickFormat || x1.tickFormat( availableWidth / 100 );
+
+ // Update the tick groups.
+ var tick = g.selectAll('g.nv-tick')
+ .data(x1.ticks( availableWidth / 50 ), function(d) {
+ return this.textContent || format(d);
+ });
+
+ // Initialize the ticks with the old scale, x0.
+ var tickEnter = tick.enter().append('g')
+ .attr('class', 'nv-tick')
+ .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
+ .style('opacity', 1e-6);
+
+ tickEnter.append('line')
+ .attr('y1', availableHeight)
+ .attr('y2', availableHeight * 7 / 6);
+
+ tickEnter.append('text')
+ .attr('text-anchor', 'middle')
+ .attr('dy', '1em')
+ .attr('y', availableHeight * 7 / 6)
+ .text(format);
+
+
+ // Transition the updating ticks to the new scale, x1.
+ var tickUpdate = d3.transition(tick)
+ .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
+ .style('opacity', 1);
+
+ tickUpdate.select('line')
+ .attr('y1', availableHeight)
+ .attr('y2', availableHeight * 7 / 6);
+
+ tickUpdate.select('text')
+ .attr('y', availableHeight * 7 / 6);
+
+ // Transition the exiting ticks to the new scale, x1.
+ d3.transition(tick.exit())
+ .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
+ .style('opacity', 1e-6)
+ .remove();
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ dispatch.on('tooltipShow', function(e) {
+ e.key = data[0].title;
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+ //============================================================
+
+ });
+
+ d3.timer.flush();
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ bullet.dispatch.on('elementMouseover.tooltip', function(e) {
+ dispatch.tooltipShow(e);
+ });
+
+ bullet.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+ chart.bullet = bullet;
+
+ d3.rebind(chart, bullet, 'color');
+
+ // left, right, top, bottom
+ chart.orient = function(x) {
+ if (!arguments.length) return orient;
+ orient = x;
+ reverse = orient == 'right' || orient == 'bottom';
+ return chart;
+ };
+
+ // ranges (bad, satisfactory, good)
+ chart.ranges = function(x) {
+ if (!arguments.length) return ranges;
+ ranges = x;
+ return chart;
+ };
+
+ // markers (previous, goal)
+ chart.markers = function(x) {
+ if (!arguments.length) return markers;
+ markers = x;
+ return chart;
+ };
+
+ // measures (actual, forecast)
+ chart.measures = function(x) {
+ if (!arguments.length) return measures;
+ measures = x;
+ return chart;
+ };
+
+ chart.width = function(x) {
+ if (!arguments.length) return width;
+ width = x;
+ return chart;
+ };
+
+ chart.height = function(x) {
+ if (!arguments.length) return height;
+ height = x;
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.tickFormat = function(x) {
+ if (!arguments.length) return tickFormat;
+ tickFormat = x;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+};
+
+
+
+nv.models.cumulativeLineChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var lines = nv.models.line()
+ , xAxis = nv.models.axis()
+ , yAxis = nv.models.axis()
+ , legend = nv.models.legend()
+ , controls = nv.models.legend()
+ ;
+
+ var margin = {top: 30, right: 30, bottom: 50, left: 60}
+ , color = nv.utils.defaultColor()
+ , width = null
+ , height = null
+ , showLegend = true
+ , tooltips = true
+ , showControls = true
+ , rescaleY = true
+ , tooltip = function(key, x, y, e, graph) {
+ return '<h3>' + key + '</h3>' +
+ '<p>' + y + ' at ' + x + '</p>'
+ }
+ , x //can be accessed via chart.xScale()
+ , y //can be accessed via chart.yScale()
+ , id = lines.id()
+ , state = { index: 0, rescaleY: rescaleY }
+ , noData = 'No Data Available.'
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
+ ;
+
+ xAxis
+ .orient('bottom')
+ .tickPadding(7)
+ ;
+ yAxis
+ .orient('left')
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var dx = d3.scale.linear()
+ , index = {i: 0, x: 0}
+ ;
+
+ var showTooltip = function(e, offsetElement) {
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
+ y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
+ content = tooltip(e.series.key, x, y, e, chart);
+
+ nv.tooltip.show([left, top], content, null, null, offsetElement);
+ };
+
+/*
+ //Moved to see if we can get better behavior to fix issue #315
+ var indexDrag = d3.behavior.drag()
+ .on('dragstart', dragStart)
+ .on('drag', dragMove)
+ .on('dragend', dragEnd);
+
+ function dragStart(d,i) {
+ d3.select(chart.container)
+ .style('cursor', 'ew-resize');
+ }
+
+ function dragMove(d,i) {
+ d.x += d3.event.dx;
+ d.i = Math.round(dx.invert(d.x));
+
+ d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)');
+ chart.update();
+ }
+
+ function dragEnd(d,i) {
+ d3.select(chart.container)
+ .style('cursor', 'auto');
+ chart.update();
+ }
+*/
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this).classed('nv-chart-' + id, true),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+
+ chart.update = function() { chart(selection) };
+ chart.container = this;
+
+
+
+ var indexDrag = d3.behavior.drag()
+ .on('dragstart', dragStart)
+ .on('drag', dragMove)
+ .on('dragend', dragEnd);
+
+
+ function dragStart(d,i) {
+ d3.select(chart.container)
+ .style('cursor', 'ew-resize');
+ }
+
+ function dragMove(d,i) {
+ index.x = d3.event.x;
+ index.i = Math.round(dx.invert(index.x));
+ updateZero();
+ }
+
+ function dragEnd(d,i) {
+ d3.select(chart.container)
+ .style('cursor', 'auto');
+
+ // update state and send stateChange with new index
+ state.index = index.i;
+ dispatch.stateChange(state);
+ }
+
+
+
+
+ //------------------------------------------------------------
+ // Display No Data message if there's nothing to show.
+
+ if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = lines.xScale();
+ y = lines.yScale();
+
+
+ if (!rescaleY) {
+ var seriesDomains = data
+ .filter(function(series) { return !series.disabled })
+ .map(function(series,i) {
+ var initialDomain = d3.extent(series.values, lines.y());
+
+ //account for series being disabled when losing 95% or more
+ if (initialDomain[0] < -.95) initialDomain[0] = -.95;
+
+ return [
+ (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
+ (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
+ ];
+ });
+
+ var completeDomain = [
+ d3.min(seriesDomains, function(d) { return d[0] }),
+ d3.max(seriesDomains, function(d) { return d[1] })
+ ]
+
+ lines.yDomain(completeDomain);
+ } else {
+ lines.yDomain(null);
+ }
+
+
+ dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length
+ .range([0, availableWidth])
+ .clamp(true);
+
+ //------------------------------------------------------------
+
+
+ var data = indexify(index.i, data);
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-x nv-axis');
+ gEnter.append('g').attr('class', 'nv-y nv-axis');
+ gEnter.append('g').attr('class', 'nv-background');
+ gEnter.append('g').attr('class', 'nv-linesWrap');
+ gEnter.append('g').attr('class', 'nv-legendWrap');
+ gEnter.append('g').attr('class', 'nv-controlsWrap');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Legend
+
+ if (showLegend) {
+ legend.width(availableWidth);
+
+ g.select('.nv-legendWrap')
+ .datum(data)
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+ g.select('.nv-legendWrap')
+ .attr('transform', 'translate(0,' + (-margin.top) +')')
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Controls
+
+ if (showControls) {
+ var controlsData = [
+ { key: 'Re-scale y-axis', disabled: !rescaleY }
+ ];
+
+ controls.width(140).color(['#444', '#444', '#444']);
+ g.select('.nv-controlsWrap')
+ .datum(controlsData)
+ .attr('transform', 'translate(0,' + (-margin.top) +')')
+ .call(controls);
+ }
+
+ //------------------------------------------------------------
+
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+
+ // Show error if series goes below 100%
+ var tempDisabled = data.filter(function(d) { return d.tempDisabled });
+
+ wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
+ if (tempDisabled.length) {
+ wrap.append('text').attr('class', 'tempDisabled')
+ .attr('x', availableWidth / 2)
+ .attr('y', '-.71em')
+ .style('text-anchor', 'end')
+ .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
+ }
+
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ gEnter.select('.nv-background')
+ .append('rect');
+
+ g.select('.nv-background rect')
+ .attr('width', availableWidth)
+ .attr('height', availableHeight);
+
+ lines
+ //.x(function(d) { return d.x })
+ .y(function(d) { return d.display.y })
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled }));
+
+
+
+ var linesWrap = g.select('.nv-linesWrap')
+ .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
+
+ //d3.transition(linesWrap).call(lines);
+ linesWrap.call(lines);
+
+
+ var indexLine = linesWrap.selectAll('.nv-indexLine')
+ .data([index]);
+ indexLine.enter().append('rect').attr('class', 'nv-indexLine')
+ .attr('width', 3)
+ .attr('x', -2)
+ .attr('fill', 'red')
+ .attr('fill-opacity', .5)
+ .call(indexDrag)
+
+ indexLine
+ .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
+ .attr('height', availableHeight)
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Axes
+
+ xAxis
+ .scale(x)
+ //Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates)
+ .ticks( Math.min(data[0].values.length,availableWidth/70) )
+ .tickSize(-availableHeight, 0);
+
+ g.select('.nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + y.range()[0] + ')');
+ d3.transition(g.select('.nv-x.nv-axis'))
+ .call(xAxis);
+
+
+ yAxis
+ .scale(y)
+ .ticks( availableHeight / 36 )
+ .tickSize( -availableWidth, 0);
+
+ d3.transition(g.select('.nv-y.nv-axis'))
+ .call(yAxis);
+
+ //------------------------------------------------------------
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+
+ function updateZero() {
+ indexLine
+ .data([index]);
+
+ chart.update();
+ }
+
+ g.select('.nv-background rect')
+ .on('click', function() {
+ index.x = d3.mouse(this)[0];
+ index.i = Math.round(dx.invert(index.x));
+
+ // update state and send stateChange with new index
+ state.index = index.i;
+ dispatch.stateChange(state);
+
+ updateZero();
+ });
+
+ lines.dispatch.on('elementClick', function(e) {
+ index.i = e.pointIndex;
+ index.x = dx(index.i);
+
+ // update state and send stateChange with new index
+ state.index = index.i;
+ dispatch.stateChange(state);
+
+ updateZero();
+ });
+
+ controls.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+ rescaleY = !d.disabled;
+
+ state.rescaleY = rescaleY;
+ dispatch.stateChange(state);
+
+ //selection.transition().call(chart);
+ selection.call(chart);
+ });
+
+
+ legend.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+
+ if (!data.filter(function(d) { return !d.disabled }).length) {
+ data.map(function(d) {
+ d.disabled = false;
+ wrap.selectAll('.nv-series').classed('disabled', false);
+ return d;
+ });
+ }
+
+ state.disabled = data.map(function(d) { return !!d.disabled });
+ dispatch.stateChange(state);
+
+ //selection.transition().call(chart);
+ selection.call(chart);
+ });
+
+/*
+ //
+ legend.dispatch.on('legendMouseover', function(d, i) {
+ d.hover = true;
+ selection.transition().call(chart)
+ });
+
+ legend.dispatch.on('legendMouseout', function(d, i) {
+ d.hover = false;
+ selection.transition().call(chart)
+ });
+*/
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+
+ // Update chart from a state object passed to event handler
+ dispatch.on('changeState', function(e) {
+
+ if (typeof e.disabled !== 'undefined') {
+ data.forEach(function(series,i) {
+ series.disabled = e.disabled[i];
+ });
+
+ state.disabled = e.disabled;
+ }
+
+
+ if (typeof e.index !== 'undefined') {
+ index.i = e.index;
+ index.x = dx(index.i);
+
+ state.index = e.index;
+
+ indexLine
+ .data([index]);
+ }
+
+
+ if (typeof e.rescaleY !== 'undefined') {
+ rescaleY = e.rescaleY;
+ }
+
+ selection.call(chart);
+ });
+
+ //============================================================
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ lines.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ lines.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.dispatch = dispatch;
+ chart.lines = lines;
+ chart.legend = legend;
+ chart.xAxis = xAxis;
+ chart.yAxis = yAxis;
+
+ d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ legend.color(color);
+ return chart;
+ };
+
+ chart.rescaleY = function(_) {
+ if (!arguments.length) return rescaleY;
+ rescaleY = _
+ return rescaleY;
+ };
+
+ chart.showControls = function(_) {
+ if (!arguments.length) return showControls;
+ showControls = _;
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.state = function(_) {
+ if (!arguments.length) return state;
+ state = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ //============================================================
+ // Functions
+ //------------------------------------------------------------
+
+ /* Normalize the data according to an index point. */
+ function indexify(idx, data) {
+ return data.map(function(line, i) {
+ var v = lines.y()(line.values[idx], idx);
+
+ //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
+ if (v < -.95) {
+ //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically currect till it hits 100)
+ line.tempDisabled = true;
+ return line;
+ }
+
+ line.tempDisabled = false;
+
+ line.values = line.values.map(function(point, pointIndex) {
+ point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) };
+ return point;
+ })
+
+ return line;
+ })
+ }
+
+ //============================================================
+
+
+ return chart;
+}
+//TODO: consider deprecating by adding necessary features to multiBar model
+nv.models.discreteBar = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 960
+ , height = 500
+ , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+ , x = d3.scale.ordinal()
+ , y = d3.scale.linear()
+ , getX = function(d) { return d.x }
+ , getY = function(d) { return d.y }
+ , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
+ , color = nv.utils.defaultColor()
+ , showValues = false
+ , valueFormat = d3.format(',.2f')
+ , xDomain
+ , yDomain
+ , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
+ , rectClass = 'discreteBar'
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var x0, y0;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ container = d3.select(this);
+
+
+ //add series index to each data point for reference
+ data = data.map(function(series, i) {
+ series.values = series.values.map(function(point) {
+ point.series = i;
+ return point;
+ });
+ return series;
+ });
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ // remap and flatten the data for use in calculating the scales' domains
+ var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
+ data.map(function(d) {
+ return d.values.map(function(d,i) {
+ return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
+ })
+ });
+
+ x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
+ .rangeBands([0, availableWidth], .1);
+
+ y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
+
+
+ // If showValues, pad the Y axis range to account for label height
+ if (showValues) y.range([availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
+ else y.range([availableHeight, 0]);
+
+ //store old scales if they exist
+ x0 = x0 || x;
+ y0 = y0 || y.copy().range([y(0),y(0)]);
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-groups');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+
+ //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
+ var groups = wrap.select('.nv-groups').selectAll('.nv-group')
+ .data(function(d) { return d }, function(d) { return d.key });
+ groups.enter().append('g')
+ .style('stroke-opacity', 1e-6)
+ .style('fill-opacity', 1e-6);
+ d3.transition(groups.exit())
+ .style('stroke-opacity', 1e-6)
+ .style('fill-opacity', 1e-6)
+ .remove();
+ groups
+ .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
+ .classed('hover', function(d) { return d.hover });
+ d3.transition(groups)
+ .style('stroke-opacity', 1)
+ .style('fill-opacity', .75);
+
+
+ var bars = groups.selectAll('g.nv-bar')
+ .data(function(d) { return d.values });
+
+ bars.exit().remove();
+
+
+ var barsEnter = bars.enter().append('g')
+ .attr('transform', function(d,i,j) {
+ return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
+ })
+ .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
+ d3.select(this).classed('hover', true);
+ dispatch.elementMouseover({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ })
+ .on('mouseout', function(d,i) {
+ d3.select(this).classed('hover', false);
+ dispatch.elementMouseout({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ })
+ .on('click', function(d,i) {
+ dispatch.elementClick({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ d3.event.stopPropagation();
+ })
+ .on('dblclick', function(d,i) {
+ dispatch.elementDblClick({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ d3.event.stopPropagation();
+ });
+
+ barsEnter.append('rect')
+ .attr('height', 0)
+ .attr('width', x.rangeBand() * .9 / data.length )
+
+ if (showValues) {
+ barsEnter.append('text')
+ .attr('text-anchor', 'middle')
+ bars.select('text')
+ .attr('x', x.rangeBand() * .9 / 2)
+ .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
+ .text(function(d,i) { return valueFormat(getY(d,i)) });
+ } else {
+ bars.selectAll('text').remove();
+ }
+
+ bars
+ .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
+ .style('fill', function(d,i) { return d.color || color(d,i) })
+ .style('stroke', function(d,i) { return d.color || color(d,i) })
+ .select('rect')
+ .attr('class', rectClass)
+ .attr('width', x.rangeBand() * .9 / data.length);
+ d3.transition(bars)
+ //.delay(function(d,i) { return i * 1200 / data[0].values.length })
+ .attr('transform', function(d,i) {
+ var left = x(getX(d,i)) + x.rangeBand() * .05,
+ top = getY(d,i) < 0 ?
+ y(0) :
+ y(0) - y(getY(d,i)) < 1 ?
+ y(0) - 1 : //make 1 px positive bars show up above y=0
+ y(getY(d,i));
+
+ return 'translate(' + left + ', ' + top + ')'
+ })
+ .select('rect')
+ .attr('height', function(d,i) {
+ return Math.max(Math.abs(y(getY(d,i)) - y(0)) || 1)
+ });
+
+
+ //store old scales for use in transitions on update
+ x0 = x.copy();
+ y0 = y.copy();
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = _;
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = _;
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.xScale = function(_) {
+ if (!arguments.length) return x;
+ x = _;
+ return chart;
+ };
+
+ chart.yScale = function(_) {
+ if (!arguments.length) return y;
+ y = _;
+ return chart;
+ };
+
+ chart.xDomain = function(_) {
+ if (!arguments.length) return xDomain;
+ xDomain = _;
+ return chart;
+ };
+
+ chart.yDomain = function(_) {
+ if (!arguments.length) return yDomain;
+ yDomain = _;
+ return chart;
+ };
+
+ chart.forceY = function(_) {
+ if (!arguments.length) return forceY;
+ forceY = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ chart.id = function(_) {
+ if (!arguments.length) return id;
+ id = _;
+ return chart;
+ };
+
+ chart.showValues = function(_) {
+ if (!arguments.length) return showValues;
+ showValues = _;
+ return chart;
+ };
+
+ chart.valueFormat= function(_) {
+ if (!arguments.length) return valueFormat;
+ valueFormat = _;
+ return chart;
+ };
+
+ chart.rectClass= function(_) {
+ if (!arguments.length) return rectClass;
+ rectClass = _;
+ return chart;
+ }
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.discreteBarChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var discretebar = nv.models.discreteBar()
+ , xAxis = nv.models.axis()
+ , yAxis = nv.models.axis()
+ ;
+
+ var margin = {top: 15, right: 10, bottom: 50, left: 60}
+ , width = null
+ , height = null
+ , color = nv.utils.getColor()
+ , staggerLabels = false
+ , tooltips = true
+ , tooltip = function(key, x, y, e, graph) {
+ return '<h3>' + x + '</h3>' +
+ '<p>' + y + '</p>'
+ }
+ , x
+ , y
+ , noData = "No Data Available."
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
+ ;
+
+ xAxis
+ .orient('bottom')
+ .highlightZero(false)
+ .showMaxMin(false)
+ .tickFormat(function(d) { return d })
+ ;
+ yAxis
+ .orient('left')
+ .tickFormat(d3.format(',.1f'))
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var showTooltip = function(e, offsetElement) {
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
+ y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
+ content = tooltip(e.series.key, x, y, e, chart);
+
+ nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
+ };
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+
+ chart.update = function() { selection.transition().call(chart); };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display No Data message if there's nothing to show.
+
+ if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = discretebar.xScale();
+ y = discretebar.yScale();
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
+ var defsEnter = gEnter.append('defs');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-x nv-axis');
+ gEnter.append('g').attr('class', 'nv-y nv-axis');
+ gEnter.append('g').attr('class', 'nv-barsWrap');
+
+ g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ discretebar
+ .width(availableWidth)
+ .height(availableHeight);
+
+
+ var barsWrap = g.select('.nv-barsWrap')
+ .datum(data.filter(function(d) { return !d.disabled }))
+
+ d3.transition(barsWrap).call(discretebar);
+
+ //------------------------------------------------------------
+
+
+
+ defsEnter.append('clipPath')
+ .attr('id', 'nv-x-label-clip-' + discretebar.id())
+ .append('rect');
+
+ g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
+ .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
+ .attr('height', 16)
+ .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
+
+
+ //------------------------------------------------------------
+ // Setup Axes
+
+ xAxis
+ .scale(x)
+ .ticks( availableWidth / 100 )
+ .tickSize(-availableHeight, 0);
+
+ g.select('.nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
+ //d3.transition(g.select('.nv-x.nv-axis'))
+ g.select('.nv-x.nv-axis').transition().duration(0)
+ .call(xAxis);
+
+
+ var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
+
+ if (staggerLabels) {
+ xTicks
+ .selectAll('text')
+ .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
+ }
+
+ yAxis
+ .scale(y)
+ .ticks( availableHeight / 36 )
+ .tickSize( -availableWidth, 0);
+
+ d3.transition(g.select('.nv-y.nv-axis'))
+ .call(yAxis);
+
+ //------------------------------------------------------------
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+ //============================================================
+
+
+ });
+
+ return chart;
+ }
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.dispatch = dispatch;
+ chart.discretebar = discretebar;
+ chart.xAxis = xAxis;
+ chart.yAxis = yAxis;
+
+ d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ discretebar.color(color);
+ return chart;
+ };
+
+ chart.staggerLabels = function(_) {
+ if (!arguments.length) return staggerLabels;
+ staggerLabels = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.distribution = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 400 //technically width or height depending on x or y....
+ , size = 8
+ , axis = 'x' // 'x' or 'y'... horizontal or vertical
+ , getData = function(d) { return d[axis] } // defaults d.x or d.y
+ , color = nv.utils.defaultColor()
+ , scale = d3.scale.linear()
+ , domain
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var scale0;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
+ naxis = axis == 'x' ? 'y' : 'x',
+ container = d3.select(this);
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ scale0 = scale0 || scale;
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-distribution').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
+
+ //------------------------------------------------------------
+
+
+ var distWrap = g.selectAll('g.nv-dist')
+ .data(function(d) { return d }, function(d) { return d.key });
+
+ distWrap.enter().append('g');
+ distWrap
+ .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
+ .style('stroke', function(d,i) { return color(d, i) });
+
+ var dist = distWrap.selectAll('line.nv-dist' + axis)
+ .data(function(d) { return d.values })
+ dist.enter().append('line')
+ .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
+ .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
+ d3.transition(distWrap.exit().selectAll('line.nv-dist' + axis))
+ .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
+ .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
+ .style('stroke-opacity', 0)
+ .remove();
+ dist
+ .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
+ .attr(naxis + '1', 0)
+ .attr(naxis + '2', size);
+ d3.transition(dist)
+ .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
+ .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
+
+
+ scale0 = scale.copy();
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.axis = function(_) {
+ if (!arguments.length) return axis;
+ axis = _;
+ return chart;
+ };
+
+ chart.size = function(_) {
+ if (!arguments.length) return size;
+ size = _;
+ return chart;
+ };
+
+ chart.getData = function(_) {
+ if (!arguments.length) return getData;
+ getData = d3.functor(_);
+ return chart;
+ };
+
+ chart.scale = function(_) {
+ if (!arguments.length) return scale;
+ scale = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.indentedTree = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0} //TODO: implement, maybe as margin on the containing div
+ , width = 960
+ , height = 500
+ , color = nv.utils.defaultColor()
+ , id = Math.floor(Math.random() * 10000)
+ , header = true
+ , noData = "No Data Available."
+ , childIndent = 20
+ , columns = [{key:'key', label: 'Name', type:'text'}] //TODO: consider functions like chart.addColumn, chart.removeColumn, instead of a block like this
+ , tableClass = null
+ , iconOpen = 'images/grey-plus.png' //TODO: consider removing this and replacing with a '+' or '-' unless user defines images
+ , iconClose = 'images/grey-minus.png'
+ , dispatch = d3.dispatch('elementClick', 'elementDblclick', 'elementMouseover', 'elementMouseout')
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var i = 0,
+ depth = 1;
+
+ var tree = d3.layout.tree()
+ .children(function(d) { return d.values })
+ .size([height, childIndent]); //Not sure if this is needed now that the result is HTML
+
+ chart.update = function() { selection.transition().call(chart) };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display No Data message if there's nothing to show.
+ if (!data[0]) data[0] = {key: noData};
+
+ //------------------------------------------------------------
+
+
+ var nodes = tree.nodes(data[0]);
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = d3.select(this).selectAll('div').data([[nodes]]);
+ var wrapEnter = wrap.enter().append('div').attr('class', 'nvd3 nv-wrap nv-indentedtree');
+ var tableEnter = wrapEnter.append('table');
+ var table = wrap.select('table').attr('width', '100%').attr('class', tableClass);
+
+ //------------------------------------------------------------
+
+
+ if (header) {
+ var thead = tableEnter.append('thead');
+
+ var theadRow1 = thead.append('tr');
+
+ columns.forEach(function(column) {
+ theadRow1
+ .append('th')
+ .attr('width', column.width ? column.width : '10%')
+ .style('text-align', column.type == 'numeric' ? 'right' : 'left')
+ .append('span')
+ .text(column.label);
+ });
+ }
+
+
+ var tbody = table.selectAll('tbody')
+ .data(function(d) {return d });
+ tbody.enter().append('tbody');
+
+
+
+ //compute max generations
+ depth = d3.max(nodes, function(node) { return node.depth });
+ tree.size([height, depth * childIndent]); //TODO: see if this is necessary at all
+
+
+ // Update the nodes…
+ var node = tbody.selectAll('tr')
+ .data(function(d) { return d }, function(d) { return d.id || (d.id == ++i)});
+ //.style('display', 'table-row'); //TODO: see if this does anything
+
+ node.exit().remove();
+
+
+ node.select('img.nv-treeicon')
+ .attr('src', icon)
+ .classed('folded', folded);
+
+ var nodeEnter = node.enter().append('tr');
+
+
+ columns.forEach(function(column, index) {
+
+ var nodeName = nodeEnter.append('td')
+ .style('padding-left', function(d) { return (index ? 0 : d.depth * childIndent + 12 + (icon(d) ? 0 : 16)) + 'px' }, 'important') //TODO: check why I did the ternary here
+ .style('text-align', column.type == 'numeric' ? 'right' : 'left');
+
+
+ if (index == 0) {
+ nodeName.append('img')
+ .classed('nv-treeicon', true)
+ .classed('nv-folded', folded)
+ .attr('src', icon)
+ .style('width', '14px')
+ .style('height', '14px')
+ .style('padding', '0 1px')
+ .style('display', function(d) { return icon(d) ? 'inline-block' : 'none'; })
+ .on('click', click);
+ }
+
+
+ nodeName.append('span')
+ .attr('class', d3.functor(column.classes) )
+ .text(function(d) { return column.format ? column.format(d) :
+ (d[column.key] || '-') });
+
+ if (column.showCount)
+ nodeName.append('span')
+ .attr('class', 'nv-childrenCount')
+ .text(function(d) {
+ return ((d.values && d.values.length) || (d._values && d._values.length)) ?
+ '(' + ((d.values && d.values.length) || (d._values && d._values.length)) + ')'
+ : ''
+ });
+
+
+ if (column.click)
+ nodeName.select('span').on('click', column.click);
+
+ });
+
+
+ node
+ .order()
+ .on('click', function(d) {
+ dispatch.elementClick({
+ row: this, //TODO: decide whether or not this should be consistent with scatter/line events or should be an html link (a href)
+ data: d,
+ pos: [d.x, d.y]
+ });
+ })
+ .on('dblclick', function(d) {
+ dispatch.elementDblclick({
+ row: this,
+ data: d,
+ pos: [d.x, d.y]
+ });
+ })
+ .on('mouseover', function(d) {
+ dispatch.elementMouseover({
+ row: this,
+ data: d,
+ pos: [d.x, d.y]
+ });
+ })
+ .on('mouseout', function(d) {
+ dispatch.elementMouseout({
+ row: this,
+ data: d,
+ pos: [d.x, d.y]
+ });
+ });
+
+
+
+
+ // Toggle children on click.
+ function click(d, _, unshift) {
+ d3.event.stopPropagation();
+
+ if(d3.event.shiftKey && !unshift) {
+ //If you shift-click, it'll toggle fold all the children, instead of itself
+ d3.event.shiftKey = false;
+ d.values && d.values.forEach(function(node){
+ if (node.values || node._values) {
+ click(node, 0, true);
+ }
+ });
+ return true;
+ }
+ if(!hasChildren(d)) {
+ //download file
+ //window.location.href = d.url;
+ return true;
+ }
+ if (d.values) {
+ d._values = d.values;
+ d.values = null;
+ } else {
+ d.values = d._values;
+ d._values = null;
+ }
+ chart.update();
+ }
+
+
+ function icon(d) {
+ return (d._values && d._values.length) ? iconOpen : (d.values && d.values.length) ? iconClose : '';
+ }
+
+ function folded(d) {
+ return (d._values && d._values.length);
+ }
+
+ function hasChildren(d) {
+ var values = d.values || d._values;
+
+ return (values && values.length);
+ }
+
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ scatter.color(color);
+ return chart;
+ };
+
+ chart.id = function(_) {
+ if (!arguments.length) return id;
+ id = _;
+ return chart;
+ };
+
+ chart.header = function(_) {
+ if (!arguments.length) return header;
+ header = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ chart.columns = function(_) {
+ if (!arguments.length) return columns;
+ columns = _;
+ return chart;
+ };
+
+ chart.tableClass = function(_) {
+ if (!arguments.length) return tableClass;
+ tableClass = _;
+ return chart;
+ };
+
+ chart.iconOpen = function(_){
+ if (!arguments.length) return iconOpen;
+ iconOpen = _;
+ return chart;
+ }
+
+ chart.iconClose = function(_){
+ if (!arguments.length) return iconClose;
+ iconClose = _;
+ return chart;
+ }
+
+ //============================================================
+
+
+ return chart;
+}
+nv.models.legend = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 5, right: 0, bottom: 5, left: 0}
+ , width = 400
+ , height = 20
+ , getKey = function(d) { return d.key }
+ , color = nv.utils.defaultColor()
+ , align = true
+ , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout')
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ container = d3.select(this);
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-legend').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
+ var g = wrap.select('g');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+ var series = g.selectAll('.nv-series')
+ .data(function(d) { return d });
+ var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
+ .on('mouseover', function(d,i) {
+ dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
+ })
+ .on('mouseout', function(d,i) {
+ dispatch.legendMouseout(d,i);
+ })
+ .on('click', function(d,i) {
+ dispatch.legendClick(d,i);
+ })
+ .on('dblclick', function(d,i) {
+ dispatch.legendDblclick(d,i);
+ });
+ seriesEnter.append('circle')
+ .style('stroke-width', 2)
+ .attr('r', 5);
+ seriesEnter.append('text')
+ .attr('text-anchor', 'start')
+ .attr('dy', '.32em')
+ .attr('dx', '8');
+ series.classed('disabled', function(d) { return d.disabled });
+ series.exit().remove();
+ series.select('circle')
+ .style('fill', function(d,i) { return d.color || color(d,i)})
+ .style('stroke', function(d,i) { return d.color || color(d, i) });
+ series.select('text').text(getKey);
+
+
+ //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
+
+ // NEW ALIGNING CODE, TODO: clean up
+ if (align) {
+ var seriesWidths = [];
+ series.each(function(d,i) {
+ seriesWidths.push(d3.select(this).select('text').node().getComputedTextLength() + 28); // 28 is ~ the width of the circle plus some padding
+ });
+
+ //nv.log('Series Widths: ', JSON.stringify(seriesWidths));
+
+ var seriesPerRow = 0;
+ var legendWidth = 0;
+ var columnWidths = [];
+
+ while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
+ columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
+ legendWidth += seriesWidths[seriesPerRow++];
+ }
+
+
+ while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
+ columnWidths = [];
+ seriesPerRow--;
+
+ for (k = 0; k < seriesWidths.length; k++) {
+ if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
+ columnWidths[k % seriesPerRow] = seriesWidths[k];
+ }
+
+ legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
+ return prev + cur;
+ });
+ }
+ //console.log(columnWidths, legendWidth, seriesPerRow);
+
+ var xPositions = [];
+ for (var i = 0, curX = 0; i < seriesPerRow; i++) {
+ xPositions[i] = curX;
+ curX += columnWidths[i];
+ }
+
+ series
+ .attr('transform', function(d, i) {
+ return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
+ });
+
+ //position legend as far right as possible within the total width
+ g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
+
+ height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
+
+ } else {
+
+ var ypos = 5,
+ newxpos = 5,
+ maxwidth = 0,
+ xpos;
+ series
+ .attr('transform', function(d, i) {
+ var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
+ xpos = newxpos;
+
+ if (width < margin.left + margin.right + xpos + length) {
+ newxpos = xpos = 5;
+ ypos += 20;
+ }
+
+ newxpos += length;
+ if (newxpos > maxwidth) maxwidth = newxpos;
+
+ return 'translate(' + xpos + ',' + ypos + ')';
+ });
+
+ //position legend as far right as possible within the total width
+ g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
+
+ height = margin.top + margin.bottom + ypos + 15;
+
+ }
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.key = function(_) {
+ if (!arguments.length) return getKey;
+ getKey = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ chart.align = function(_) {
+ if (!arguments.length) return align;
+ align = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.line = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var scatter = nv.models.scatter()
+ ;
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 960
+ , height = 500
+ , color = nv.utils.defaultColor() // a function that returns a color
+ , getX = function(d) { return d.x } // accessor to get the x value from a data point
+ , getY = function(d) { return d.y } // accessor to get the y value from a data point
+ , defined = function(d,i) { return !isNaN(getY(d,i)) && getY(d,i) !== null } // allows a line to be not continous when it is not defined
+ , isArea = function(d) { return d.area } // decides if a line is an area or just a line
+ , clipEdge = false // if true, masks lines within x and y scale
+ , x //can be accessed via chart.xScale()
+ , y //can be accessed via chart.yScale()
+ , interpolate = "linear" // controls the line interpolation
+ ;
+
+ scatter
+ .size(16) // default size
+ .sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var x0, y0 //used to store previous scales
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ container = d3.select(this);
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = scatter.xScale();
+ y = scatter.yScale();
+
+ x0 = x0 || x;
+ y0 = y0 || y;
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
+ var defsEnter = wrapEnter.append('defs');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g')
+
+ gEnter.append('g').attr('class', 'nv-groups');
+ gEnter.append('g').attr('class', 'nv-scatterWrap');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+
+
+ scatter
+ .width(availableWidth)
+ .height(availableHeight)
+
+ var scatterWrap = wrap.select('.nv-scatterWrap');
+ //.datum(data); // Data automatically trickles down from the wrap
+
+ d3.transition(scatterWrap).call(scatter);
+
+
+
+ defsEnter.append('clipPath')
+ .attr('id', 'nv-edge-clip-' + scatter.id())
+ .append('rect');
+
+ wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
+ .attr('width', availableWidth)
+ .attr('height', availableHeight);
+
+ g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
+ scatterWrap
+ .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
+
+
+
+
+ var groups = wrap.select('.nv-groups').selectAll('.nv-group')
+ .data(function(d) { return d }, function(d) { return d.key });
+ groups.enter().append('g')
+ .style('stroke-opacity', 1e-6)
+ .style('fill-opacity', 1e-6);
+ d3.transition(groups.exit())
+ .style('stroke-opacity', 1e-6)
+ .style('fill-opacity', 1e-6)
+ .remove();
+ groups
+ .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
+ .classed('hover', function(d) { return d.hover })
+ .style('fill', function(d,i){ return color(d, i) })
+ .style('stroke', function(d,i){ return color(d, i)});
+ d3.transition(groups)
+ .style('stroke-opacity', 1)
+ .style('fill-opacity', .5);
+
+
+
+ var areaPaths = groups.selectAll('path.nv-area')
+ .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
+ areaPaths.enter().append('path')
+ .attr('class', 'nv-area')
+ .attr('d', function(d) {
+ return d3.svg.area()
+ .interpolate(interpolate)
+ .defined(defined)
+ .x(function(d,i) { return x0(getX(d,i)) })
+ .y0(function(d,i) { return y0(getY(d,i)) })
+ .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
+ //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
+ .apply(this, [d.values])
+ });
+ d3.transition(groups.exit().selectAll('path.nv-area'))
+ .attr('d', function(d) {
+ return d3.svg.area()
+ .interpolate(interpolate)
+ .defined(defined)
+ .x(function(d,i) { return x0(getX(d,i)) })
+ .y0(function(d,i) { return y0(getY(d,i)) })
+ .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
+ //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
+ .apply(this, [d.values])
+ });
+ d3.transition(areaPaths)
+ .attr('d', function(d) {
+ return d3.svg.area()
+ .interpolate(interpolate)
+ .defined(defined)
+ .x(function(d,i) { return x0(getX(d,i)) })
+ .y0(function(d,i) { return y0(getY(d,i)) })
+ .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
+ //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
+ .apply(this, [d.values])
+ });
+
+
+
+ var linePaths = groups.selectAll('path.nv-line')
+ .data(function(d) { return [d.values] });
+ linePaths.enter().append('path')
+ .attr('class', 'nv-line')
+ .attr('d',
+ d3.svg.line()
+ .interpolate(interpolate)
+ .defined(defined)
+ .x(function(d,i) { return x0(getX(d,i)) })
+ .y(function(d,i) { return y0(getY(d,i)) })
+ );
+ d3.transition(groups.exit().selectAll('path.nv-line'))
+ .attr('d',
+ d3.svg.line()
+ .interpolate(interpolate)
+ .defined(defined)
+ .x(function(d,i) { return x(getX(d,i)) })
+ .y(function(d,i) { return y(getY(d,i)) })
+ );
+ d3.transition(linePaths)
+ .attr('d',
+ d3.svg.line()
+ .interpolate(interpolate)
+ .defined(defined)
+ .x(function(d,i) { return x(getX(d,i)) })
+ .y(function(d,i) { return y(getY(d,i)) })
+ );
+
+
+
+ //store old scales for use in transitions on update
+ x0 = x.copy();
+ y0 = y.copy();
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = scatter.dispatch;
+ chart.scatter = scatter;
+
+ d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'padData');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = _;
+ scatter.x(_);
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = _;
+ scatter.y(_);
+ return chart;
+ };
+
+ chart.clipEdge = function(_) {
+ if (!arguments.length) return clipEdge;
+ clipEdge = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ scatter.color(color);
+ return chart;
+ };
+
+ chart.interpolate = function(_) {
+ if (!arguments.length) return interpolate;
+ interpolate = _;
+ return chart;
+ };
+
+ chart.defined = function(_) {
+ if (!arguments.length) return defined;
+ defined = _;
+ return chart;
+ };
+
+ chart.isArea = function(_) {
+ if (!arguments.length) return isArea;
+ isArea = d3.functor(_);
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.lineChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var lines = nv.models.line()
+ , xAxis = nv.models.axis()
+ , yAxis = nv.models.axis()
+ , legend = nv.models.legend()
+ ;
+
+//set margin.right to 23 to fit dates on the x-axis within the chart
+ var margin = {top: 30, right: 20, bottom: 50, left: 60}
+ , color = nv.utils.defaultColor()
+ , width = null
+ , height = null
+ , showLegend = true
+ , tooltips = true
+ , tooltip = function(key, x, y, e, graph) {
+ return '<h3>' + key + '</h3>' +
+ '<p>' + y + ' at ' + x + '</p>'
+ }
+ , x
+ , y
+ , state = {}
+ , noData = 'No Data Available.'
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
+ ;
+
+ xAxis
+ .orient('bottom')
+ .tickPadding(7)
+ ;
+ yAxis
+ .orient('left')
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var showTooltip = function(e, offsetElement) {
+
+ // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
+ if (offsetElement) {
+ var svg = d3.select(offsetElement).select('svg');
+ var viewBox = svg.attr('viewBox');
+ if (viewBox) {
+ viewBox = viewBox.split(' ');
+ var ratio = parseInt(svg.style('width')) / viewBox[2];
+ e.pos[0] = e.pos[0] * ratio;
+ e.pos[1] = e.pos[1] * ratio;
+ }
+ }
+
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
+ y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
+ content = tooltip(e.series.key, x, y, e, chart);
+
+ nv.tooltip.show([left, top], content, null, null, offsetElement);
+ };
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+
+ chart.update = function() { chart(selection) };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display noData message if there's nothing to show.
+
+ if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = lines.xScale();
+ y = lines.yScale();
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-x nv-axis');
+ gEnter.append('g').attr('class', 'nv-y nv-axis');
+ gEnter.append('g').attr('class', 'nv-linesWrap');
+ gEnter.append('g').attr('class', 'nv-legendWrap');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Legend
+
+ if (showLegend) {
+ legend.width(availableWidth);
+
+ g.select('.nv-legendWrap')
+ .datum(data)
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+ wrap.select('.nv-legendWrap')
+ .attr('transform', 'translate(0,' + (-margin.top) +')')
+ }
+
+ //------------------------------------------------------------
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ lines
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled }));
+
+
+ var linesWrap = g.select('.nv-linesWrap')
+ .datum(data.filter(function(d) { return !d.disabled }))
+
+ d3.transition(linesWrap).call(lines);
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Axes
+
+ xAxis
+ .scale(x)
+ .ticks( availableWidth / 100 )
+ .tickSize(-availableHeight, 0);
+
+ g.select('.nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + y.range()[0] + ')');
+ d3.transition(g.select('.nv-x.nv-axis'))
+ .call(xAxis);
+
+
+ yAxis
+ .scale(y)
+ .ticks( availableHeight / 36 )
+ .tickSize( -availableWidth, 0);
+
+ d3.transition(g.select('.nv-y.nv-axis'))
+ .call(yAxis);
+
+ //------------------------------------------------------------
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ legend.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+
+ if (!data.filter(function(d) { return !d.disabled }).length) {
+ data.map(function(d) {
+ d.disabled = false;
+ wrap.selectAll('.nv-series').classed('disabled', false);
+ return d;
+ });
+ }
+
+ state.disabled = data.map(function(d) { return !!d.disabled });
+ dispatch.stateChange(state);
+
+ selection.transition().call(chart);
+ });
+
+/*
+ legend.dispatch.on('legendMouseover', function(d, i) {
+ d.hover = true;
+ selection.transition().call(chart)
+ });
+
+ legend.dispatch.on('legendMouseout', function(d, i) {
+ d.hover = false;
+ selection.transition().call(chart)
+ });
+*/
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+
+ dispatch.on('changeState', function(e) {
+
+ if (typeof e.disabled !== 'undefined') {
+ data.forEach(function(series,i) {
+ series.disabled = e.disabled[i];
+ });
+
+ state.disabled = e.disabled;
+ }
+
+ selection.call(chart);
+ });
+
+ //============================================================
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ lines.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ lines.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.dispatch = dispatch;
+ chart.lines = lines;
+ chart.legend = legend;
+ chart.xAxis = xAxis;
+ chart.yAxis = yAxis;
+
+ d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ legend.color(color);
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.state = function(_) {
+ if (!arguments.length) return state;
+ state = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.linePlusBarChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var lines = nv.models.line()
+ , bars = nv.models.historicalBar()
+ , xAxis = nv.models.axis()
+ , y1Axis = nv.models.axis()
+ , y2Axis = nv.models.axis()
+ , legend = nv.models.legend()
+ ;
+
+ var margin = {top: 30, right: 60, bottom: 50, left: 60}
+ , width = null
+ , height = null
+ , getX = function(d) { return d.x }
+ , getY = function(d) { return d.y }
+ , color = nv.utils.defaultColor()
+ , showLegend = true
+ , tooltips = true
+ , tooltip = function(key, x, y, e, graph) {
+ return '<h3>' + key + '</h3>' +
+ '<p>' + y + ' at ' + x + '</p>';
+ }
+ , x
+ , y1
+ , y2
+ , noData = "No Data Available."
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
+ ;
+
+ bars
+ .padData(true)
+ ;
+ lines
+ .clipEdge(false)
+ .padData(true)
+ ;
+ xAxis
+ .orient('bottom')
+ .tickPadding(7)
+ .highlightZero(false)
+ ;
+ y1Axis
+ .orient('left')
+ ;
+ y2Axis
+ .orient('right')
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var state = {},
+ showTooltip = function(e, offsetElement) {
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
+ y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
+ content = tooltip(e.series.key, x, y, e, chart);
+
+ nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
+ }
+ ;
+
+ //------------------------------------------------------------
+
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+ chart.update = function() { chart(selection) };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display No Data message if there's nothing to show.
+
+ if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
+ var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
+
+ //x = xAxis.scale();
+ x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale();
+ //x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above
+ y1 = bars.yScale();
+ y2 = lines.yScale();
+
+ //------------------------------------------------------------
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-x nv-axis');
+ gEnter.append('g').attr('class', 'nv-y1 nv-axis');
+ gEnter.append('g').attr('class', 'nv-y2 nv-axis');
+ gEnter.append('g').attr('class', 'nv-barsWrap');
+ gEnter.append('g').attr('class', 'nv-linesWrap');
+ gEnter.append('g').attr('class', 'nv-legendWrap');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Legend
+
+ if (showLegend) {
+ legend.width( availableWidth / 2 );
+
+ g.select('.nv-legendWrap')
+ .datum(data.map(function(series) {
+ series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
+ series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
+ return series;
+ }))
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+ g.select('.nv-legendWrap')
+ .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
+ }
+
+ //------------------------------------------------------------
+
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+
+ lines
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }))
+
+ bars
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled && data[i].bar }))
+
+
+
+ var barsWrap = g.select('.nv-barsWrap')
+ .datum(dataBars.length ? dataBars : [{values:[]}])
+
+ var linesWrap = g.select('.nv-linesWrap')
+ .datum(!dataLines[0].disabled ? dataLines : [{values:[]}] );
+ //.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] );
+
+ d3.transition(barsWrap).call(bars);
+ d3.transition(linesWrap).call(lines);
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Axes
+
+ xAxis
+ .scale(x)
+ .ticks( availableWidth / 100 )
+ .tickSize(-availableHeight, 0);
+
+ g.select('.nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + y1.range()[0] + ')');
+ d3.transition(g.select('.nv-x.nv-axis'))
+ .call(xAxis);
+
+
+ y1Axis
+ .scale(y1)
+ .ticks( availableHeight / 36 )
+ .tickSize(-availableWidth, 0);
+
+ d3.transition(g.select('.nv-y1.nv-axis'))
+ .style('opacity', dataBars.length ? 1 : 0)
+ .call(y1Axis);
+
+
+ y2Axis
+ .scale(y2)
+ .ticks( availableHeight / 36 )
+ .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
+
+ g.select('.nv-y2.nv-axis')
+ .style('opacity', dataLines.length ? 1 : 0)
+ .attr('transform', 'translate(' + availableWidth + ',0)');
+ //.attr('transform', 'translate(' + x.range()[1] + ',0)');
+
+ d3.transition(g.select('.nv-y2.nv-axis'))
+ .call(y2Axis);
+
+ //------------------------------------------------------------
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ legend.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+
+ if (!data.filter(function(d) { return !d.disabled }).length) {
+ data.map(function(d) {
+ d.disabled = false;
+ wrap.selectAll('.nv-series').classed('disabled', false);
+ return d;
+ });
+ }
+
+ state.disabled = data.map(function(d) { return !!d.disabled });
+ dispatch.stateChange(state);
+
+ selection.transition().call(chart);
+ });
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+
+ // Update chart from a state object passed to event handler
+ dispatch.on('changeState', function(e) {
+
+ if (typeof e.disabled !== 'undefined') {
+ data.forEach(function(series,i) {
+ series.disabled = e.disabled[i];
+ });
+
+ state.disabled = e.disabled;
+ }
+
+ selection.call(chart);
+ });
+
+ //============================================================
+
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ lines.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ lines.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ bars.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ bars.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.dispatch = dispatch;
+ chart.legend = legend;
+ chart.lines = lines;
+ chart.bars = bars;
+ chart.xAxis = xAxis;
+ chart.y1Axis = y1Axis;
+ chart.y2Axis = y2Axis;
+
+ d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
+ //TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
+ //d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = _;
+ lines.x(_);
+ bars.x(_);
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = _;
+ lines.y(_);
+ bars.y(_);
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ legend.color(color);
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.state = function(_) {
+ if (!arguments.length) return state;
+ state = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.lineWithFocusChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var lines = nv.models.line()
+ , lines2 = nv.models.line()
+ , xAxis = nv.models.axis()
+ , yAxis = nv.models.axis()
+ , x2Axis = nv.models.axis()
+ , y2Axis = nv.models.axis()
+ , legend = nv.models.legend()
+ , brush = d3.svg.brush()
+ ;
+
+ var margin = {top: 30, right: 30, bottom: 30, left: 60}
+ , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
+ , color = nv.utils.defaultColor()
+ , width = null
+ , height = null
+ , height2 = 100
+ , x
+ , y
+ , x2
+ , y2
+ , showLegend = true
+ , brushExtent = null
+ , tooltips = true
+ , tooltip = function(key, x, y, e, graph) {
+ return '<h3>' + key + '</h3>' +
+ '<p>' + y + ' at ' + x + '</p>'
+ }
+ , noData = "No Data Available."
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
+ ;
+
+ lines
+ .clipEdge(true)
+ ;
+ lines2
+ .interactive(false)
+ ;
+ xAxis
+ .orient('bottom')
+ .tickPadding(5)
+ ;
+ yAxis
+ .orient('left')
+ ;
+ x2Axis
+ .orient('bottom')
+ .tickPadding(5)
+ ;
+ y2Axis
+ .orient('left')
+ ;
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var showTooltip = function(e, offsetElement) {
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
+ y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
+ content = tooltip(e.series.key, x, y, e, chart);
+
+ nv.tooltip.show([left, top], content, null, null, offsetElement);
+ };
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight1 = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom - height2,
+ availableHeight2 = height2 - margin2.top - margin2.bottom;
+
+ chart.update = function() { chart(selection) };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display No Data message if there's nothing to show.
+
+ if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight1 / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = lines.xScale();
+ y = lines.yScale();
+ x2 = lines2.xScale();
+ y2 = lines2.yScale();
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-legendWrap');
+
+ var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
+ focusEnter.append('g').attr('class', 'nv-x nv-axis');
+ focusEnter.append('g').attr('class', 'nv-y nv-axis');
+ focusEnter.append('g').attr('class', 'nv-linesWrap');
+
+ var contextEnter = gEnter.append('g').attr('class', 'nv-context');
+ contextEnter.append('g').attr('class', 'nv-x nv-axis');
+ contextEnter.append('g').attr('class', 'nv-y nv-axis');
+ contextEnter.append('g').attr('class', 'nv-linesWrap');
+ contextEnter.append('g').attr('class', 'nv-brushBackground');
+ contextEnter.append('g').attr('class', 'nv-x nv-brush');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Legend
+
+ if (showLegend) {
+ legend.width(availableWidth);
+
+ g.select('.nv-legendWrap')
+ .datum(data)
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight1 = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom - height2;
+ }
+
+ g.select('.nv-legendWrap')
+ .attr('transform', 'translate(0,' + (-margin.top) +')')
+ }
+
+ //------------------------------------------------------------
+
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ lines
+ .width(availableWidth)
+ .height(availableHeight1)
+ .color(
+ data
+ .map(function(d,i) {
+ return d.color || color(d, i);
+ })
+ .filter(function(d,i) {
+ return !data[i].disabled;
+ })
+ );
+
+ lines2
+ .defined(lines.defined())
+ .width(availableWidth)
+ .height(availableHeight2)
+ .color(
+ data
+ .map(function(d,i) {
+ return d.color || color(d, i);
+ })
+ .filter(function(d,i) {
+ return !data[i].disabled;
+ })
+ );
+
+ g.select('.nv-context')
+ .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
+
+ var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
+ .datum(data.filter(function(d) { return !d.disabled }))
+
+ d3.transition(contextLinesWrap).call(lines2);
+
+ //------------------------------------------------------------
+
+
+ /*
+ var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
+ .datum(data.filter(function(d) { return !d.disabled }))
+
+ d3.transition(focusLinesWrap).call(lines);
+ */
+
+
+ //------------------------------------------------------------
+ // Setup Main (Focus) Axes
+
+ xAxis
+ .scale(x)
+ .ticks( availableWidth / 100 )
+ .tickSize(-availableHeight1, 0);
+
+ yAxis
+ .scale(y)
+ .ticks( availableHeight1 / 36 )
+ .tickSize( -availableWidth, 0);
+
+ g.select('.nv-focus .nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + availableHeight1 + ')');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Brush
+
+ brush
+ .x(x2)
+ .on('brush', onBrush);
+
+ if (brushExtent) brush.extent(brushExtent);
+
+ var brushBG = g.select('.nv-brushBackground').selectAll('g')
+ .data([brushExtent || brush.extent()])
+
+ var brushBGenter = brushBG.enter()
+ .append('g');
+
+ brushBGenter.append('rect')
+ .attr('class', 'left')
+ .attr('x', 0)
+ .attr('y', 0)
+ .attr('height', availableHeight2);
+
+ brushBGenter.append('rect')
+ .attr('class', 'right')
+ .attr('x', 0)
+ .attr('y', 0)
+ .attr('height', availableHeight2);
+
+ gBrush = g.select('.nv-x.nv-brush')
+ .call(brush);
+ gBrush.selectAll('rect')
+ //.attr('y', -5)
+ .attr('height', availableHeight2);
+ gBrush.selectAll('.resize').append('path').attr('d', resizePath);
+
+ onBrush();
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Secondary (Context) Axes
+
+ x2Axis
+ .scale(x2)
+ .ticks( availableWidth / 100 )
+ .tickSize(-availableHeight2, 0);
+
+ g.select('.nv-context .nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + y2.range()[0] + ')');
+ d3.transition(g.select('.nv-context .nv-x.nv-axis'))
+ .call(x2Axis);
+
+
+ y2Axis
+ .scale(y2)
+ .ticks( availableHeight2 / 36 )
+ .tickSize( -availableWidth, 0);
+
+ d3.transition(g.select('.nv-context .nv-y.nv-axis'))
+ .call(y2Axis);
+
+ g.select('.nv-context .nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + y2.range()[0] + ')');
+
+ //------------------------------------------------------------
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ legend.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+
+ if (!data.filter(function(d) { return !d.disabled }).length) {
+ data.map(function(d) {
+ d.disabled = false;
+ wrap.selectAll('.nv-series').classed('disabled', false);
+ return d;
+ });
+ }
+
+ selection.transition().call(chart);
+ });
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Functions
+ //------------------------------------------------------------
+
+ // Taken from crossfilter (http://square.github.com/crossfilter/)
+ function resizePath(d) {
+ var e = +(d == 'e'),
+ x = e ? 1 : -1,
+ y = availableHeight2 / 3;
+ return 'M' + (.5 * x) + ',' + y
+ + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
+ + 'V' + (2 * y - 6)
+ + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
+ + 'Z'
+ + 'M' + (2.5 * x) + ',' + (y + 8)
+ + 'V' + (2 * y - 8)
+ + 'M' + (4.5 * x) + ',' + (y + 8)
+ + 'V' + (2 * y - 8);
+ }
+
+
+ function updateBrushBG() {
+ if (!brush.empty()) brush.extent(brushExtent);
+ brushBG
+ .data([brush.empty() ? x2.domain() : brushExtent])
+ .each(function(d,i) {
+ var leftWidth = x2(d[0]) - x.range()[0],
+ rightWidth = x.range()[1] - x2(d[1]);
+ d3.select(this).select('.left')
+ .attr('width', leftWidth < 0 ? 0 : leftWidth);
+
+ d3.select(this).select('.right')
+ .attr('x', x2(d[1]))
+ .attr('width', rightWidth < 0 ? 0 : rightWidth);
+ });
+ }
+
+
+ function onBrush() {
+ brushExtent = brush.empty() ? null : brush.extent();
+ extent = brush.empty() ? x2.domain() : brush.extent();
+
+
+ dispatch.brush({extent: extent, brush: brush});
+
+
+ updateBrushBG();
+
+ // Update Main (Focus)
+ var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
+ .datum(
+ data
+ .filter(function(d) { return !d.disabled })
+ .map(function(d,i) {
+ return {
+ key: d.key,
+ values: d.values.filter(function(d,i) {
+ return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
+ })
+ }
+ })
+ );
+ d3.transition(focusLinesWrap).call(lines);
+
+
+ // Update Main (Focus) Axes
+ d3.transition(g.select('.nv-focus .nv-x.nv-axis'))
+ .call(xAxis);
+ d3.transition(g.select('.nv-focus .nv-y.nv-axis'))
+ .call(yAxis);
+ }
+
+ //============================================================
+
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ lines.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ lines.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.dispatch = dispatch;
+ chart.legend = legend;
+ chart.lines = lines;
+ chart.lines2 = lines2;
+ chart.xAxis = xAxis;
+ chart.yAxis = yAxis;
+ chart.x2Axis = x2Axis;
+ chart.y2Axis = y2Axis;
+
+ d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
+
+ chart.x = function(_) {
+ if (!arguments.length) return lines.x;
+ lines.x(_);
+ lines2.x(_);
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return lines.y;
+ lines.y(_);
+ lines2.y(_);
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.margin2 = function(_) {
+ if (!arguments.length) return margin2;
+ margin2 = _;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.height2 = function(_) {
+ if (!arguments.length) return height2;
+ height2 = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color =nv.utils.getColor(_);
+ legend.color(color);
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.interpolate = function(_) {
+ if (!arguments.length) return lines.interpolate();
+ lines.interpolate(_);
+ lines2.interpolate(_);
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ // Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below
+ chart.xTickFormat = function(_) {
+ if (!arguments.length) return xAxis.tickFormat();
+ xAxis.tickFormat(_);
+ x2Axis.tickFormat(_);
+ return chart;
+ };
+
+ chart.yTickFormat = function(_) {
+ if (!arguments.length) return yAxis.tickFormat();
+ yAxis.tickFormat(_);
+ y2Axis.tickFormat(_);
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.multiBar = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 960
+ , height = 500
+ , x = d3.scale.ordinal()
+ , y = d3.scale.linear()
+ , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+ , getX = function(d) { return d.x }
+ , getY = function(d) { return d.y }
+ , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
+ , clipEdge = true
+ , stacked = false
+ , color = nv.utils.defaultColor()
+ , delay = 1200
+ , xDomain
+ , yDomain
+ , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var x0, y0 //used to store previous scales
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ container = d3.select(this);
+
+ if (stacked)
+ data = d3.layout.stack()
+ .offset('zero')
+ .values(function(d){ return d.values })
+ .y(getY)
+ (data);
+
+
+ //add series index to each data point for reference
+ data = data.map(function(series, i) {
+ series.values = series.values.map(function(point) {
+ point.series = i;
+ return point;
+ });
+ return series;
+ });
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ // remap and flatten the data for use in calculating the scales' domains
+ var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
+ data.map(function(d) {
+ return d.values.map(function(d,i) {
+ return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
+ })
+ });
+
+ x .domain(d3.merge(seriesData).map(function(d) { return d.x }))
+ .rangeBands([0, availableWidth], .1);
+
+ y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY)))
+ .range([availableHeight, 0]);
+
+
+ // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
+ if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
+ if (x.domain()[0] === x.domain()[1])
+ x.domain()[0] ?
+ x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
+ : x.domain([-1,1]);
+
+ if (y.domain()[0] === y.domain()[1])
+ y.domain()[0] ?
+ y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
+ : y.domain([-1,1]);
+
+
+ x0 = x0 || x;
+ y0 = y0 || y;
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
+ var defsEnter = wrapEnter.append('defs');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g')
+
+ gEnter.append('g').attr('class', 'nv-groups');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+
+ defsEnter.append('clipPath')
+ .attr('id', 'nv-edge-clip-' + id)
+ .append('rect');
+ wrap.select('#nv-edge-clip-' + id + ' rect')
+ .attr('width', availableWidth)
+ .attr('height', availableHeight);
+
+ g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
+
+
+
+ var groups = wrap.select('.nv-groups').selectAll('.nv-group')
+ .data(function(d) { return d }, function(d) { return d.key });
+ groups.enter().append('g')
+ .style('stroke-opacity', 1e-6)
+ .style('fill-opacity', 1e-6);
+ d3.transition(groups.exit())
+ //.style('stroke-opacity', 1e-6)
+ //.style('fill-opacity', 1e-6)
+ .selectAll('rect.nv-bar')
+ .delay(function(d,i) { return i * delay/ data[0].values.length })
+ .attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) })
+ .attr('height', 0)
+ .remove();
+ groups
+ .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
+ .classed('hover', function(d) { return d.hover })
+ .style('fill', function(d,i){ return color(d, i) })
+ .style('stroke', function(d,i){ return color(d, i) });
+ d3.transition(groups)
+ .style('stroke-opacity', 1)
+ .style('fill-opacity', .75);
+
+
+ var bars = groups.selectAll('rect.nv-bar')
+ .data(function(d) { return d.values });
+
+ bars.exit().remove();
+
+
+ var barsEnter = bars.enter().append('rect')
+ .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
+ .attr('x', function(d,i,j) {
+ return stacked ? 0 : (j * x.rangeBand() / data.length )
+ })
+ .attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
+ .attr('height', 0)
+ .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
+ bars
+ .style('fill', function(d,i,j){ return color(d, j, i); })
+ .style('stroke', function(d,i,j){ return color(d, j, i); })
+ .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
+ d3.select(this).classed('hover', true);
+ dispatch.elementMouseover({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ })
+ .on('mouseout', function(d,i) {
+ d3.select(this).classed('hover', false);
+ dispatch.elementMouseout({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ })
+ .on('click', function(d,i) {
+ dispatch.elementClick({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ d3.event.stopPropagation();
+ })
+ .on('dblclick', function(d,i) {
+ dispatch.elementDblClick({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ d3.event.stopPropagation();
+ });
+ bars
+ .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
+ .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
+ if (stacked)
+ d3.transition(bars)
+ .delay(function(d,i) { return i * delay / data[0].values.length })
+ .attr('y', function(d,i) {
+ return y(getY(d,i) + (stacked ? d.y0 : 0));
+ })
+ .attr('height', function(d,i) {
+ return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
+ })
+ .each('end', function() {
+ d3.transition(d3.select(this))
+ .attr('x', function(d,i) {
+ return stacked ? 0 : (d.series * x.rangeBand() / data.length )
+ })
+ .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
+ })
+ else
+ d3.transition(bars)
+ .delay(function(d,i) { return i * delay/ data[0].values.length })
+ .attr('x', function(d,i) {
+ return d.series * x.rangeBand() / data.length
+ })
+ .attr('width', x.rangeBand() / data.length)
+ .each('end', function() {
+ d3.transition(d3.select(this))
+ .attr('y', function(d,i) {
+ return getY(d,i) < 0 ?
+ y(0) :
+ y(0) - y(getY(d,i)) < 1 ?
+ y(0) - 1 :
+ y(getY(d,i))
+ })
+ .attr('height', function(d,i) {
+ return Math.max(Math.abs(y(getY(d,i)) - y(0)),1);
+ });
+ })
+
+
+ //store old scales for use in transitions on update
+ x0 = x.copy();
+ y0 = y.copy();
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = _;
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = _;
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.xScale = function(_) {
+ if (!arguments.length) return x;
+ x = _;
+ return chart;
+ };
+
+ chart.yScale = function(_) {
+ if (!arguments.length) return y;
+ y = _;
+ return chart;
+ };
+
+ chart.xDomain = function(_) {
+ if (!arguments.length) return xDomain;
+ xDomain = _;
+ return chart;
+ };
+
+ chart.yDomain = function(_) {
+ if (!arguments.length) return yDomain;
+ yDomain = _;
+ return chart;
+ };
+
+ chart.forceY = function(_) {
+ if (!arguments.length) return forceY;
+ forceY = _;
+ return chart;
+ };
+
+ chart.stacked = function(_) {
+ if (!arguments.length) return stacked;
+ stacked = _;
+ return chart;
+ };
+
+ chart.clipEdge = function(_) {
+ if (!arguments.length) return clipEdge;
+ clipEdge = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ chart.id = function(_) {
+ if (!arguments.length) return id;
+ id = _;
+ return chart;
+ };
+
+ chart.delay = function(_) {
+ if (!arguments.length) return delay;
+ delay = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.multiBarChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var multibar = nv.models.multiBar()
+ , xAxis = nv.models.axis()
+ , yAxis = nv.models.axis()
+ , legend = nv.models.legend()
+ , controls = nv.models.legend()
+ ;
+
+ var margin = {top: 30, right: 20, bottom: 30, left: 60}
+ , width = null
+ , height = null
+ , color = nv.utils.defaultColor()
+ , showControls = true
+ , showLegend = true
+ , reduceXTicks = true // if false a tick will show for every data point
+ , rotateLabels = 0
+ , tooltips = true
+ , tooltip = function(key, x, y, e, graph) {
+ return '<h3>' + key + '</h3>' +
+ '<p>' + y + ' on ' + x + '</p>'
+ }
+ , x //can be accessed via chart.xScale()
+ , y //can be accessed via chart.yScale()
+ , state = { stacked: false }
+ , noData = "No Data Available."
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
+ ;
+
+ multibar
+ .stacked(false)
+ ;
+ xAxis
+ .orient('bottom')
+ .tickPadding(7)
+ .highlightZero(false)
+ .showMaxMin(false)
+ .tickFormat(function(d) { return d })
+ ;
+ yAxis
+ .orient('left')
+ .tickFormat(d3.format(',.1f'))
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var showTooltip = function(e, offsetElement) {
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
+ y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
+ content = tooltip(e.series.key, x, y, e, chart);
+
+ nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
+ };
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+ chart.update = function() { selection.transition().call(chart) };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display noData message if there's nothing to show.
+
+ if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = multibar.xScale();
+ y = multibar.yScale();
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-x nv-axis');
+ gEnter.append('g').attr('class', 'nv-y nv-axis');
+ gEnter.append('g').attr('class', 'nv-barsWrap');
+ gEnter.append('g').attr('class', 'nv-legendWrap');
+ gEnter.append('g').attr('class', 'nv-controlsWrap');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Legend
+
+ if (showLegend) {
+ legend.width(availableWidth / 2);
+
+ g.select('.nv-legendWrap')
+ .datum(data)
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+ g.select('.nv-legendWrap')
+ .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Controls
+
+ if (showControls) {
+ var controlsData = [
+ { key: 'Grouped', disabled: multibar.stacked() },
+ { key: 'Stacked', disabled: !multibar.stacked() }
+ ];
+
+ controls.width(180).color(['#444', '#444', '#444']);
+ g.select('.nv-controlsWrap')
+ .datum(controlsData)
+ .attr('transform', 'translate(0,' + (-margin.top) +')')
+ .call(controls);
+ }
+
+ //------------------------------------------------------------
+
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ multibar
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled }))
+
+
+ var barsWrap = g.select('.nv-barsWrap')
+ .datum(data.filter(function(d) { return !d.disabled }))
+
+ d3.transition(barsWrap).call(multibar);
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Axes
+
+ xAxis
+ .scale(x)
+ .ticks( availableWidth / 100 )
+ .tickSize(-availableHeight, 0);
+
+ g.select('.nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + y.range()[0] + ')');
+ d3.transition(g.select('.nv-x.nv-axis'))
+ .call(xAxis);
+
+ var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
+
+ xTicks
+ .selectAll('line, text')
+ .style('opacity', 1)
+
+ if (reduceXTicks)
+ xTicks
+ .filter(function(d,i) {
+ return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
+ })
+ .selectAll('text, line')
+ .style('opacity', 0);
+
+ if(rotateLabels)
+ xTicks
+ .selectAll('text')
+ .attr('transform', function(d,i,j) { return 'rotate('+rotateLabels+' 0,0)' })
+ .attr('text-transform', rotateLabels > 0 ? 'start' : 'end');
+
+ g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
+ .style('opacity', 1);
+
+ yAxis
+ .scale(y)
+ .ticks( availableHeight / 36 )
+ .tickSize( -availableWidth, 0);
+
+ d3.transition(g.select('.nv-y.nv-axis'))
+ .call(yAxis);
+
+ //------------------------------------------------------------
+
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ legend.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+
+ if (!data.filter(function(d) { return !d.disabled }).length) {
+ data.map(function(d) {
+ d.disabled = false;
+ wrap.selectAll('.nv-series').classed('disabled', false);
+ return d;
+ });
+ }
+
+ state.disabled = data.map(function(d) { return !!d.disabled });
+ dispatch.stateChange(state);
+
+ selection.transition().call(chart);
+ });
+
+ controls.dispatch.on('legendClick', function(d,i) {
+ if (!d.disabled) return;
+ controlsData = controlsData.map(function(s) {
+ s.disabled = true;
+ return s;
+ });
+ d.disabled = false;
+
+ switch (d.key) {
+ case 'Grouped':
+ multibar.stacked(false);
+ break;
+ case 'Stacked':
+ multibar.stacked(true);
+ break;
+ }
+
+ state.stacked = multibar.stacked();
+ dispatch.stateChange(state);
+
+ selection.transition().call(chart);
+ });
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode)
+ });
+
+ // Update chart from a state object passed to event handler
+ dispatch.on('changeState', function(e) {
+
+ if (typeof e.disabled !== 'undefined') {
+ data.forEach(function(series,i) {
+ series.disabled = e.disabled[i];
+ });
+
+ state.disabled = e.disabled;
+ }
+
+ if (typeof e.stacked !== 'undefined') {
+ multibar.stacked(e.stacked);
+ state.stacked = e.stacked;
+ }
+
+ selection.call(chart);
+ });
+
+ //============================================================
+
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ multibar.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ multibar.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.dispatch = dispatch;
+ chart.multibar = multibar;
+ chart.legend = legend;
+ chart.xAxis = xAxis;
+ chart.yAxis = yAxis;
+
+ d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'stacked', 'delay');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ legend.color(color);
+ return chart;
+ };
+
+ chart.showControls = function(_) {
+ if (!arguments.length) return showControls;
+ showControls = _;
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.reduceXTicks= function(_) {
+ if (!arguments.length) return reduceXTicks;
+ reduceXTicks = _;
+ return chart;
+ };
+
+ chart.rotateLabels = function(_) {
+ if (!arguments.length) return rotateLabels;
+ rotateLabels = _;
+ return chart;
+ }
+
+ chart.tooltip = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.state = function(_) {
+ if (!arguments.length) return state;
+ state = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.multiBarHorizontal = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 960
+ , height = 500
+ , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+ , x = d3.scale.ordinal()
+ , y = d3.scale.linear()
+ , getX = function(d) { return d.x }
+ , getY = function(d) { return d.y }
+ , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
+ , color = nv.utils.defaultColor()
+ , stacked = false
+ , showValues = false
+ , valuePadding = 60
+ , valueFormat = d3.format(',.2f')
+ , delay = 1200
+ , xDomain
+ , yDomain
+ , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var x0, y0 //used to store previous scales
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ container = d3.select(this);
+
+
+ if (stacked)
+ data = d3.layout.stack()
+ .offset('zero')
+ .values(function(d){ return d.values })
+ .y(getY)
+ (data);
+
+
+ //add series index to each data point for reference
+ data = data.map(function(series, i) {
+ series.values = series.values.map(function(point) {
+ point.series = i;
+ return point;
+ });
+ return series;
+ });
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ // remap and flatten the data for use in calculating the scales' domains
+ var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
+ data.map(function(d) {
+ return d.values.map(function(d,i) {
+ return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
+ })
+ });
+
+ x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
+ .rangeBands([0, availableHeight], .1);
+
+ y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY)))
+
+ if (showValues && !stacked)
+ y.range([(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
+ else
+ y.range([0, availableWidth]);
+
+ x0 = x0 || x;
+ y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
+ var defsEnter = wrapEnter.append('defs');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-groups');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+
+ var groups = wrap.select('.nv-groups').selectAll('.nv-group')
+ .data(function(d) { return d }, function(d) { return d.key });
+ groups.enter().append('g')
+ .style('stroke-opacity', 1e-6)
+ .style('fill-opacity', 1e-6);
+ d3.transition(groups.exit())
+ .style('stroke-opacity', 1e-6)
+ .style('fill-opacity', 1e-6)
+ .remove();
+ groups
+ .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
+ .classed('hover', function(d) { return d.hover })
+ .style('fill', function(d,i){ return color(d, i) })
+ .style('stroke', function(d,i){ return color(d, i) });
+ d3.transition(groups)
+ .style('stroke-opacity', 1)
+ .style('fill-opacity', .75);
+
+
+ var bars = groups.selectAll('g.nv-bar')
+ .data(function(d) { return d.values });
+
+ bars.exit().remove();
+
+
+ var barsEnter = bars.enter().append('g')
+ .attr('transform', function(d,i,j) {
+ return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
+ });
+
+ barsEnter.append('rect')
+ .attr('width', 0)
+ .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
+
+ bars
+ .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
+ d3.select(this).classed('hover', true);
+ dispatch.elementMouseover({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ })
+ .on('mouseout', function(d,i) {
+ d3.select(this).classed('hover', false);
+ dispatch.elementMouseout({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ })
+ .on('click', function(d,i) {
+ dispatch.elementClick({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ d3.event.stopPropagation();
+ })
+ .on('dblclick', function(d,i) {
+ dispatch.elementDblClick({
+ value: getY(d,i),
+ point: d,
+ series: data[d.series],
+ pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
+ pointIndex: i,
+ seriesIndex: d.series,
+ e: d3.event
+ });
+ d3.event.stopPropagation();
+ });
+
+ if (showValues && !stacked) {
+ barsEnter.append('text')
+ .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
+ bars.select('text')
+ .attr('y', x.rangeBand() / 2)
+ .attr('dy', '-.32em')
+ .text(function(d,i) { return valueFormat(getY(d,i)) })
+ d3.transition(bars)
+ //.delay(function(d,i) { return i * delay / data[0].values.length })
+ .select('text')
+ .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
+ } else {
+ bars.selectAll('text').remove();
+ }
+
+ bars
+ .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
+ //.attr('transform', function(d,i,j) {
+ //return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + x(getX(d,i)) + ')'
+ //})
+ if (stacked)
+ d3.transition(bars)
+ //.delay(function(d,i) { return i * delay / data[0].values.length })
+ .attr('transform', function(d,i) {
+ //return 'translate(' + y(d.y0) + ',0)'
+ return 'translate(' + y(d.y0) + ',' + x(getX(d,i)) + ')'
+ })
+ .select('rect')
+ .attr('width', function(d,i) {
+ return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
+ })
+ .attr('height', x.rangeBand() );
+ else
+ d3.transition(bars)
+ //.delay(function(d,i) { return i * delay / data[0].values.length })
+ .attr('transform', function(d,i) {
+ //TODO: stacked must be all positive or all negative, not both?
+ return 'translate(' +
+ (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
+ + ',' +
+ (d.series * x.rangeBand() / data.length
+ +
+ x(getX(d,i)) )
+ + ')'
+ })
+ .select('rect')
+ .attr('height', x.rangeBand() / data.length )
+ .attr('width', function(d,i) {
+ return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
+ });
+
+
+ //store old scales for use in transitions on update
+ x0 = x.copy();
+ y0 = y.copy();
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = _;
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = _;
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.xScale = function(_) {
+ if (!arguments.length) return x;
+ x = _;
+ return chart;
+ };
+
+ chart.yScale = function(_) {
+ if (!arguments.length) return y;
+ y = _;
+ return chart;
+ };
+
+ chart.xDomain = function(_) {
+ if (!arguments.length) return xDomain;
+ xDomain = _;
+ return chart;
+ };
+
+ chart.yDomain = function(_) {
+ if (!arguments.length) return yDomain;
+ yDomain = _;
+ return chart;
+ };
+
+ chart.forceY = function(_) {
+ if (!arguments.length) return forceY;
+ forceY = _;
+ return chart;
+ };
+
+ chart.stacked = function(_) {
+ if (!arguments.length) return stacked;
+ stacked = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ chart.id = function(_) {
+ if (!arguments.length) return id;
+ id = _;
+ return chart;
+ };
+
+ chart.delay = function(_) {
+ if (!arguments.length) return delay;
+ delay = _;
+ return chart;
+ };
+
+ chart.showValues = function(_) {
+ if (!arguments.length) return showValues;
+ showValues = _;
+ return chart;
+ };
+
+ chart.valueFormat= function(_) {
+ if (!arguments.length) return valueFormat;
+ valueFormat = _;
+ return chart;
+ };
+
+ chart.valuePadding = function(_) {
+ if (!arguments.length) return valuePadding;
+ valuePadding = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.multiBarHorizontalChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var multibar = nv.models.multiBarHorizontal()
+ , xAxis = nv.models.axis()
+ , yAxis = nv.models.axis()
+ , legend = nv.models.legend().height(30)
+ , controls = nv.models.legend().height(30)
+ ;
+
+ var margin = {top: 30, right: 20, bottom: 50, left: 60}
+ , width = null
+ , height = null
+ , color = nv.utils.defaultColor()
+ , showControls = true
+ , showLegend = true
+ , stacked = false
+ , tooltips = true
+ , tooltip = function(key, x, y, e, graph) {
+ return '<h3>' + key + ' - ' + x + '</h3>' +
+ '<p>' + y + '</p>'
+ }
+ , x //can be accessed via chart.xScale()
+ , y //can be accessed via chart.yScale()
+ , state = { stacked: stacked }
+ , noData = 'No Data Available.'
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
+ ;
+
+ multibar
+ .stacked(stacked)
+ ;
+ xAxis
+ .orient('left')
+ .tickPadding(5)
+ .highlightZero(false)
+ .showMaxMin(false)
+ .tickFormat(function(d) { return d })
+ ;
+ yAxis
+ .orient('bottom')
+ .tickFormat(d3.format(',.1f'))
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var showTooltip = function(e, offsetElement) {
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
+ y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
+ content = tooltip(e.series.key, x, y, e, chart);
+
+ nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
+ };
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+ chart.update = function() { selection.transition().call(chart) };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display No Data message if there's nothing to show.
+
+ if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = multibar.xScale();
+ y = multibar.yScale();
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-x nv-axis');
+ gEnter.append('g').attr('class', 'nv-y nv-axis');
+ gEnter.append('g').attr('class', 'nv-barsWrap');
+ gEnter.append('g').attr('class', 'nv-legendWrap');
+ gEnter.append('g').attr('class', 'nv-controlsWrap');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Legend
+
+ if (showLegend) {
+ legend.width(availableWidth / 2);
+
+ g.select('.nv-legendWrap')
+ .datum(data)
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+ g.select('.nv-legendWrap')
+ .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Controls
+
+ if (showControls) {
+ var controlsData = [
+ { key: 'Grouped', disabled: multibar.stacked() },
+ { key: 'Stacked', disabled: !multibar.stacked() }
+ ];
+
+ controls.width(180).color(['#444', '#444', '#444']);
+ g.select('.nv-controlsWrap')
+ .datum(controlsData)
+ .attr('transform', 'translate(0,' + (-margin.top) +')')
+ .call(controls);
+ }
+
+ //------------------------------------------------------------
+
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ multibar
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled }))
+
+
+ var barsWrap = g.select('.nv-barsWrap')
+ .datum(data.filter(function(d) { return !d.disabled }))
+
+ d3.transition(barsWrap).call(multibar);
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Axes
+
+ xAxis
+ .scale(x)
+ .ticks( availableHeight / 24 )
+ .tickSize(-availableWidth, 0);
+
+ d3.transition(g.select('.nv-x.nv-axis'))
+ .call(xAxis);
+
+ var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
+
+ xTicks
+ .selectAll('line, text')
+ .style('opacity', 1)
+
+
+ yAxis
+ .scale(y)
+ .ticks( availableWidth / 100 )
+ .tickSize( -availableHeight, 0);
+
+ g.select('.nv-y.nv-axis')
+ .attr('transform', 'translate(0,' + availableHeight + ')');
+ d3.transition(g.select('.nv-y.nv-axis'))
+ .call(yAxis);
+
+ //------------------------------------------------------------
+
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ legend.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+
+ if (!data.filter(function(d) { return !d.disabled }).length) {
+ data.map(function(d) {
+ d.disabled = false;
+ wrap.selectAll('.nv-series').classed('disabled', false);
+ return d;
+ });
+ }
+
+ state.disabled = data.map(function(d) { return !!d.disabled });
+ dispatch.stateChange(state);
+
+ selection.transition().call(chart);
+ });
+
+ controls.dispatch.on('legendClick', function(d,i) {
+ if (!d.disabled) return;
+ controlsData = controlsData.map(function(s) {
+ s.disabled = true;
+ return s;
+ });
+ d.disabled = false;
+
+ switch (d.key) {
+ case 'Grouped':
+ multibar.stacked(false);
+ break;
+ case 'Stacked':
+ multibar.stacked(true);
+ break;
+ }
+
+ state.stacked = multibar.stacked();
+ dispatch.stateChange(state);
+
+ selection.transition().call(chart);
+ });
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+ // Update chart from a state object passed to event handler
+ dispatch.on('changeState', function(e) {
+
+ if (typeof e.disabled !== 'undefined') {
+ data.forEach(function(series,i) {
+ series.disabled = e.disabled[i];
+ });
+
+ state.disabled = e.disabled;
+ }
+
+ if (typeof e.stacked !== 'undefined') {
+ multibar.stacked(e.stacked);
+ state.stacked = e.stacked;
+ }
+
+ selection.call(chart);
+ });
+ //============================================================
+
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ multibar.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ multibar.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.dispatch = dispatch;
+ chart.multibar = multibar;
+ chart.legend = legend;
+ chart.xAxis = xAxis;
+ chart.yAxis = yAxis;
+
+ d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'forceX', 'forceY', 'clipEdge', 'id', 'delay', 'showValues', 'valueFormat', 'stacked');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ legend.color(color);
+ return chart;
+ };
+
+ chart.showControls = function(_) {
+ if (!arguments.length) return showControls;
+ showControls = _;
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.tooltip = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.state = function(_) {
+ if (!arguments.length) return state;
+ state = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+nv.models.multiChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 30, right: 20, bottom: 50, left: 60},
+ color = d3.scale.category20().range(),
+ width = null,
+ height = null,
+ showLegend = true,
+ tooltips = true,
+ tooltip = function(key, x, y, e, graph) {
+ return '<h3>' + key + '</h3>' +
+ '<p>' + y + ' at ' + x + '</p>'
+ },
+ x, y; //can be accessed via chart.lines.[x/y]Scale()
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var x = d3.scale.linear(),
+ yScale1 = d3.scale.linear(),
+ yScale2 = d3.scale.linear(),
+
+ lines1 = nv.models.line().yScale(yScale1),
+ lines2 = nv.models.line().yScale(yScale2),
+
+ bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
+ bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
+
+ stack1 = nv.models.stackedArea().yScale(yScale1),
+ stack2 = nv.models.stackedArea().yScale(yScale2),
+
+ xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
+ yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
+ yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
+
+ legend = nv.models.legend().height(30),
+ dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
+
+ var showTooltip = function(e, offsetElement) {
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
+ y = (e.series.bar ? yAxis1 : yAxis2).tickFormat()(lines1.y()(e.point, e.pointIndex)),
+ content = tooltip(e.series.key, x, y, e, chart);
+
+ nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
+ };
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+ var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1})
+ var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2})
+ var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1})
+ var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2})
+ var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1})
+ var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2})
+
+ var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
+ .map(function(d) {
+ return d.values.map(function(d,i) {
+ return { x: d.x, y: d.y }
+ })
+ })
+
+ var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
+ .map(function(d) {
+ return d.values.map(function(d,i) {
+ return { x: d.x, y: d.y }
+ })
+ })
+
+ x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
+ .range([0, availableWidth]);
+
+ var wrap = container.selectAll('g.wrap.multiChart').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
+
+ gEnter.append('g').attr('class', 'x axis');
+ gEnter.append('g').attr('class', 'y1 axis');
+ gEnter.append('g').attr('class', 'y2 axis');
+ gEnter.append('g').attr('class', 'lines1Wrap');
+ gEnter.append('g').attr('class', 'lines2Wrap');
+ gEnter.append('g').attr('class', 'bars1Wrap');
+ gEnter.append('g').attr('class', 'bars2Wrap');
+ gEnter.append('g').attr('class', 'stack1Wrap');
+ gEnter.append('g').attr('class', 'stack2Wrap');
+ gEnter.append('g').attr('class', 'legendWrap');
+
+ var g = wrap.select('g');
+
+ if (showLegend) {
+ legend.width( availableWidth / 2 );
+
+ g.select('.legendWrap')
+ .datum(data.map(function(series) {
+ series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
+ series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
+ return series;
+ }))
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+ g.select('.legendWrap')
+ .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
+ }
+
+
+ lines1
+ .width(availableWidth)
+ .height(availableHeight)
+ .interpolate("monotone")
+ .color(data.map(function(d,i) {
+ return d.color || color[i % color.length];
+ }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
+
+ lines2
+ .width(availableWidth)
+ .height(availableHeight)
+ .interpolate("monotone")
+ .color(data.map(function(d,i) {
+ return d.color || color[i % color.length];
+ }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
+
+ bars1
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color[i % color.length];
+ }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
+
+ bars2
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color[i % color.length];
+ }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
+
+ stack1
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color[i % color.length];
+ }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
+
+ stack2
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color[i % color.length];
+ }).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
+
+ g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+
+ var lines1Wrap = g.select('.lines1Wrap')
+ .datum(dataLines1)
+ var bars1Wrap = g.select('.bars1Wrap')
+ .datum(dataBars1)
+ var stack1Wrap = g.select('.stack1Wrap')
+ .datum(dataStack1)
+
+ var lines2Wrap = g.select('.lines2Wrap')
+ .datum(dataLines2)
+ var bars2Wrap = g.select('.bars2Wrap')
+ .datum(dataBars2)
+ var stack2Wrap = g.select('.stack2Wrap')
+ .datum(dataStack2)
+
+ var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
+ return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
+ }).concat([{x:0, y:0}]) : []
+ var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
+ return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
+ }).concat([{x:0, y:0}]) : []
+
+ yScale1 .domain(d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
+ .range([0, availableHeight])
+
+ yScale2 .domain(d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
+ .range([0, availableHeight])
+
+ lines1.yDomain(yScale1.domain())
+ bars1.yDomain(yScale1.domain())
+ stack1.yDomain(yScale1.domain())
+
+ lines2.yDomain(yScale2.domain())
+ bars2.yDomain(yScale2.domain())
+ stack2.yDomain(yScale2.domain())
+
+ if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
+ if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
+
+ if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
+ if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
+
+ if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
+ if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
+
+
+
+ xAxis
+ .ticks( availableWidth / 100 )
+ .tickSize(-availableHeight, 0);
+
+ g.select('.x.axis')
+ .attr('transform', 'translate(0,' + availableHeight + ')');
+ d3.transition(g.select('.x.axis'))
+ .call(xAxis);
+
+ yAxis1
+ .ticks( availableHeight / 36 )
+ .tickSize( -availableWidth, 0);
+
+
+ d3.transition(g.select('.y1.axis'))
+ .call(yAxis1);
+
+ yAxis2
+ .ticks( availableHeight / 36 )
+ .tickSize( -availableWidth, 0);
+
+ d3.transition(g.select('.y2.axis'))
+ .call(yAxis2);
+
+ g.select('.y2.axis')
+ .style('opacity', series2.length ? 1 : 0)
+ .attr('transform', 'translate(' + x.range()[1] + ',0)');
+
+ legend.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+
+ if (!data.filter(function(d) { return !d.disabled }).length) {
+ data.map(function(d) {
+ d.disabled = false;
+ wrap.selectAll('.series').classed('disabled', false);
+ return d;
+ });
+ }
+ selection.transition().call(chart);
+ });
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+ });
+
+ chart.update = function() { chart(selection) };
+ chart.container = this;
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ lines1.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ lines1.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ lines2.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ lines2.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ bars1.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ bars1.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ bars2.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ bars2.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ stack1.dispatch.on('tooltipShow', function(e) {
+ //disable tooltips when value ~= 0
+ //// TODO: consider removing points from voronoi that have 0 value instead of this hack
+ if (!Math.round(stack1.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
+ setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
+ return false;
+ }
+
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
+ dispatch.tooltipShow(e);
+ });
+
+ stack1.dispatch.on('tooltipHide', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ stack2.dispatch.on('tooltipShow', function(e) {
+ //disable tooltips when value ~= 0
+ //// TODO: consider removing points from voronoi that have 0 value instead of this hack
+ if (!Math.round(stack2.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
+ setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
+ return false;
+ }
+
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
+ dispatch.tooltipShow(e);
+ });
+
+ stack2.dispatch.on('tooltipHide', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ lines1.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ lines1.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ lines2.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ lines2.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+
+
+ //============================================================
+ // Global getters and setters
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+ chart.lines1 = lines1;
+ chart.lines2 = lines2;
+ chart.bars1 = bars1;
+ chart.bars2 = bars2;
+ chart.stack1 = stack1;
+ chart.stack2 = stack2;
+ chart.xAxis = xAxis;
+ chart.yAxis1 = yAxis1;
+ chart.yAxis2 = yAxis2;
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = _;
+ lines1.x(_);
+ bars1.x(_);
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = _;
+ lines1.y(_);
+ bars1.y(_);
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin = _;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = _;
+ legend.color(_);
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ return chart;
+}
+
+
+nv.models.ohlcBar = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 960
+ , height = 500
+ , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+ , x = d3.scale.linear()
+ , y = d3.scale.linear()
+ , getX = function(d) { return d.x }
+ , getY = function(d) { return d.y }
+ , getOpen = function(d) { return d.open }
+ , getClose = function(d) { return d.close }
+ , getHigh = function(d) { return d.high }
+ , getLow = function(d) { return d.low }
+ , forceX = []
+ , forceY = []
+ , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
+ , clipEdge = true
+ , color = nv.utils.defaultColor()
+ , xDomain
+ , yDomain
+ , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
+ ;
+
+ //============================================================
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ //TODO: store old scales for transitions
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ container = d3.select(this);
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
+
+ if (padData)
+ x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
+ else
+ x.range([0, availableWidth]);
+
+ y .domain(yDomain || [
+ d3.min(data[0].values.map(getLow).concat(forceY)),
+ d3.max(data[0].values.map(getHigh).concat(forceY))
+ ])
+ .range([availableHeight, 0]);
+
+ // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
+ if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
+ if (x.domain()[0] === x.domain()[1])
+ x.domain()[0] ?
+ x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
+ : x.domain([-1,1]);
+
+ if (y.domain()[0] === y.domain()[1])
+ y.domain()[0] ?
+ y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
+ : y.domain([-1,1]);
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
+ var defsEnter = wrapEnter.append('defs');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-ticks');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+ container
+ .on('click', function(d,i) {
+ dispatch.chartClick({
+ data: d,
+ index: i,
+ pos: d3.event,
+ id: id
+ });
+ });
+
+
+ defsEnter.append('clipPath')
+ .attr('id', 'nv-chart-clip-path-' + id)
+ .append('rect');
+
+ wrap.select('#nv-chart-clip-path-' + id + ' rect')
+ .attr('width', availableWidth)
+ .attr('height', availableHeight);
+
+ g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
+
+
+
+ var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
+ .data(function(d) { return d });
+
+ ticks.exit().remove();
+
+
+ var ticksEnter = ticks.enter().append('path')
+ .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
+ .attr('d', function(d,i) {
+ var w = (availableWidth / data[0].values.length) * .9;
+ return 'm0,0l0,'
+ + (y(getOpen(d,i))
+ - y(getHigh(d,i)))
+ + 'l'
+ + (-w/2)
+ + ',0l'
+ + (w/2)
+ + ',0l0,'
+ + (y(getLow(d,i)) - y(getOpen(d,i)))
+ + 'l0,'
+ + (y(getClose(d,i))
+ - y(getLow(d,i)))
+ + 'l'
+ + (w/2)
+ + ',0l'
+ + (-w/2)
+ + ',0z';
+ })
+ .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
+ //.attr('fill', function(d,i) { return color[0]; })
+ //.attr('stroke', function(d,i) { return color[0]; })
+ //.attr('x', 0 )
+ //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
+ //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
+ .on('mouseover', function(d,i) {
+ d3.select(this).classed('hover', true);
+ dispatch.elementMouseover({
+ point: d,
+ series: data[0],
+ pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
+ pointIndex: i,
+ seriesIndex: 0,
+ e: d3.event
+ });
+
+ })
+ .on('mouseout', function(d,i) {
+ d3.select(this).classed('hover', false);
+ dispatch.elementMouseout({
+ point: d,
+ series: data[0],
+ pointIndex: i,
+ seriesIndex: 0,
+ e: d3.event
+ });
+ })
+ .on('click', function(d,i) {
+ dispatch.elementClick({
+ //label: d[label],
+ value: getY(d,i),
+ data: d,
+ index: i,
+ pos: [x(getX(d,i)), y(getY(d,i))],
+ e: d3.event,
+ id: id
+ });
+ d3.event.stopPropagation();
+ })
+ .on('dblclick', function(d,i) {
+ dispatch.elementDblClick({
+ //label: d[label],
+ value: getY(d,i),
+ data: d,
+ index: i,
+ pos: [x(getX(d,i)), y(getY(d,i))],
+ e: d3.event,
+ id: id
+ });
+ d3.event.stopPropagation();
+ });
+
+ ticks
+ .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
+ d3.transition(ticks)
+ .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
+ .attr('d', function(d,i) {
+ var w = (availableWidth / data[0].values.length) * .9;
+ return 'm0,0l0,'
+ + (y(getOpen(d,i))
+ - y(getHigh(d,i)))
+ + 'l'
+ + (-w/2)
+ + ',0l'
+ + (w/2)
+ + ',0l0,'
+ + (y(getLow(d,i))
+ - y(getOpen(d,i)))
+ + 'l0,'
+ + (y(getClose(d,i))
+ - y(getLow(d,i)))
+ + 'l'
+ + (w/2)
+ + ',0l'
+ + (-w/2)
+ + ',0z';
+ })
+ //.attr('width', (availableWidth / data[0].values.length) * .9 )
+
+
+ //d3.transition(ticks)
+ //.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
+ //.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
+ //.order(); // not sure if this makes any sense for this model
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = _;
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = _;
+ return chart;
+ };
+
+ chart.open = function(_) {
+ if (!arguments.length) return getOpen;
+ getOpen = _;
+ return chart;
+ };
+
+ chart.close = function(_) {
+ if (!arguments.length) return getClose;
+ getClose = _;
+ return chart;
+ };
+
+ chart.high = function(_) {
+ if (!arguments.length) return getHigh;
+ getHigh = _;
+ return chart;
+ };
+
+ chart.low = function(_) {
+ if (!arguments.length) return getLow;
+ getLow = _;
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.xScale = function(_) {
+ if (!arguments.length) return x;
+ x = _;
+ return chart;
+ };
+
+ chart.yScale = function(_) {
+ if (!arguments.length) return y;
+ y = _;
+ return chart;
+ };
+
+ chart.xDomain = function(_) {
+ if (!arguments.length) return xDomain;
+ xDomain = _;
+ return chart;
+ };
+
+ chart.yDomain = function(_) {
+ if (!arguments.length) return yDomain;
+ yDomain = _;
+ return chart;
+ };
+
+ chart.forceX = function(_) {
+ if (!arguments.length) return forceX;
+ forceX = _;
+ return chart;
+ };
+
+ chart.forceY = function(_) {
+ if (!arguments.length) return forceY;
+ forceY = _;
+ return chart;
+ };
+
+ chart.padData = function(_) {
+ if (!arguments.length) return padData;
+ padData = _;
+ return chart;
+ };
+
+ chart.clipEdge = function(_) {
+ if (!arguments.length) return clipEdge;
+ clipEdge = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ chart.id = function(_) {
+ if (!arguments.length) return id;
+ id = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.pie = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 500
+ , height = 500
+ , getValues = function(d) { return d.values }
+ , getX = function(d) { return d.x }
+ , getY = function(d) { return d.y }
+ , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
+ , color = nv.utils.defaultColor()
+ , valueFormat = d3.format(',.2f')
+ , showLabels = true
+ , donutLabelsOutside = false
+ , labelThreshold = .02 //if slice percentage is under this, don't show label
+ , donut = false
+ , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ radius = Math.min(availableWidth, availableHeight) / 2,
+ container = d3.select(this);
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ //var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]);
+ var wrap = container.selectAll('.nv-wrap.nv-pie').data([getValues(data[0])]);
+ var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-pie');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+ g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
+
+ //------------------------------------------------------------
+
+
+ container
+ .on('click', function(d,i) {
+ dispatch.chartClick({
+ data: d,
+ index: i,
+ pos: d3.event,
+ id: id
+ });
+ });
+
+
+ var arc = d3.svg.arc()
+ .outerRadius((radius-(radius / 5)));
+
+ if (donut) arc.innerRadius(radius / 2);
+
+
+ // Setup the Pie chart and choose the data element
+ var pie = d3.layout.pie()
+ .sort(null)
+ .value(function(d) { return d.disabled ? 0 : getY(d) });
+
+ var slices = wrap.select('.nv-pie').selectAll('.nv-slice')
+ .data(pie);
+
+ slices.exit().remove();
+
+ var ae = slices.enter().append('g')
+ .attr('class', 'nv-slice')
+ .on('mouseover', function(d,i){
+ d3.select(this).classed('hover', true);
+ dispatch.elementMouseover({
+ label: getX(d.data),
+ value: getY(d.data),
+ point: d.data,
+ pointIndex: i,
+ pos: [d3.event.pageX, d3.event.pageY],
+ id: id
+ });
+ })
+ .on('mouseout', function(d,i){
+ d3.select(this).classed('hover', false);
+ dispatch.elementMouseout({
+ label: getX(d.data),
+ value: getY(d.data),
+ point: d.data,
+ index: i,
+ id: id
+ });
+ })
+ .on('click', function(d,i) {
+ dispatch.elementClick({
+ label: getX(d.data),
+ value: getY(d.data),
+ point: d.data,
+ index: i,
+ pos: d3.event,
+ id: id
+ });
+ d3.event.stopPropagation();
+ })
+ .on('dblclick', function(d,i) {
+ dispatch.elementDblClick({
+ label: getX(d.data),
+ value: getY(d.data),
+ point: d.data,
+ index: i,
+ pos: d3.event,
+ id: id
+ });
+ d3.event.stopPropagation();
+ });
+
+ slices
+ .attr('fill', function(d,i) { return color(d, i); })
+ .attr('stroke', function(d,i) { return color(d, i); });
+
+ var paths = ae.append('path')
+ .each(function(d) { this._current = d; });
+ //.attr('d', arc);
+
+ d3.transition(slices.select('path'))
+ .attr('d', arc)
+ .attrTween('d', arcTween);
+
+ if (showLabels) {
+ // This does the normal label
+ var labelsArc = arc;
+ if (donutLabelsOutside) {
+ labelsArc = d3.svg.arc().outerRadius(arc.outerRadius())
+ }
+
+ ae.append("g").classed("nv-label", true)
+ .each(function(d, i) {
+ var group = d3.select(this);
+
+ group
+ .attr('transform', function(d) {
+ d.outerRadius = radius + 10; // Set Outer Coordinate
+ d.innerRadius = radius + 15; // Set Inner Coordinate
+ return 'translate(' + labelsArc.centroid(d) + ')'
+ });
+
+ group.append('rect')
+ .style('stroke', '#fff')
+ .style('fill', '#fff')
+ .attr("rx", 3)
+ .attr("ry", 3);
+
+ group.append('text')
+ .style('text-anchor', 'middle') //center the text on it's origin
+ .style('fill', '#000')
+
+
+ });
+
+ slices.select(".nv-label").transition()
+ .attr('transform', function(d) {
+ d.outerRadius = radius + 10; // Set Outer Coordinate
+ d.innerRadius = radius + 15; // Set Inner Coordinate
+ return 'translate(' + labelsArc.centroid(d) + ')';
+ });
+
+ slices.each(function(d, i) {
+ var slice = d3.select(this);
+
+ slice
+ .select(".nv-label text")
+ .text(function(d, i) {
+ var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
+ return (d.value && percent > labelThreshold) ? getX(d.data) : '';
+ });
+
+ var textBox = slice.select('text').node().getBBox();
+ slice.select(".nv-label rect")
+ .attr("width", textBox.width + 10)
+ .attr("height", textBox.height + 10)
+ .attr("transform", function() {
+ return "translate(" + [textBox.x - 5, textBox.y - 5] + ")";
+ });
+ });
+ }
+
+
+ // Computes the angle of an arc, converting from radians to degrees.
+ function angle(d) {
+ var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
+ return a > 90 ? a - 180 : a;
+ }
+
+ function arcTween(a) {
+ if (!donut) a.innerRadius = 0;
+ var i = d3.interpolate(this._current, a);
+ this._current = i(0);
+ return function(t) {
+ return arc(i(t));
+ };
+ }
+
+ function tweenPie(b) {
+ b.innerRadius = 0;
+ var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
+ return function(t) {
+ return arc(i(t));
+ };
+ }
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.values = function(_) {
+ if (!arguments.length) return getValues;
+ getValues = _;
+ return chart;
+ };
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = _;
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = d3.functor(_);
+ return chart;
+ };
+
+ chart.showLabels = function(_) {
+ if (!arguments.length) return showLabels;
+ showLabels = _;
+ return chart;
+ };
+
+ chart.donutLabelsOutside = function(_) {
+ if (!arguments.length) return donutLabelsOutside;
+ donutLabelsOutside = _;
+ return chart;
+ };
+
+ chart.donut = function(_) {
+ if (!arguments.length) return donut;
+ donut = _;
+ return chart;
+ };
+
+ chart.id = function(_) {
+ if (!arguments.length) return id;
+ id = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ chart.valueFormat = function(_) {
+ if (!arguments.length) return valueFormat;
+ valueFormat = _;
+ return chart;
+ };
+
+ chart.labelThreshold = function(_) {
+ if (!arguments.length) return labelThreshold;
+ labelThreshold = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.pieChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var pie = nv.models.pie()
+ , legend = nv.models.legend()
+ ;
+
+ var margin = {top: 30, right: 20, bottom: 20, left: 20}
+ , width = null
+ , height = null
+ , showLegend = true
+ , color = nv.utils.defaultColor()
+ , tooltips = true
+ , tooltip = function(key, y, e, graph) {
+ return '<h3>' + key + '</h3>' +
+ '<p>' + y + '</p>'
+ }
+ , state = {}
+ , noData = "No Data Available."
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var showTooltip = function(e, offsetElement) {
+ var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
+ top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
+ y = pie.valueFormat()(pie.y()(e.point)),
+ content = tooltip(pie.x()(e.point), y, e, chart);
+
+ nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
+ };
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+ chart.update = function() { chart(selection); };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display No Data message if there's nothing to show.
+
+ if (!data || !data.length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-pieWrap');
+ gEnter.append('g').attr('class', 'nv-legendWrap');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Legend
+
+ if (showLegend) {
+ legend
+ .width( availableWidth )
+ .key(pie.x());
+
+ wrap.select('.nv-legendWrap')
+ .datum(pie.values()(data[0]))
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+ wrap.select('.nv-legendWrap')
+ .attr('transform', 'translate(0,' + (-margin.top) +')');
+ }
+
+ //------------------------------------------------------------
+
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ pie
+ .width(availableWidth)
+ .height(availableHeight);
+
+
+ var pieWrap = g.select('.nv-pieWrap')
+ .datum(data);
+
+ d3.transition(pieWrap).call(pie);
+
+ //------------------------------------------------------------
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ legend.dispatch.on('legendClick', function(d,i, that) {
+ d.disabled = !d.disabled;
+
+ if (!pie.values()(data[0]).filter(function(d) { return !d.disabled }).length) {
+ pie.values()(data[0]).map(function(d) {
+ d.disabled = false;
+ wrap.selectAll('.nv-series').classed('disabled', false);
+ return d;
+ });
+ }
+
+ state.disabled = data[0].map(function(d) { return !!d.disabled });
+ dispatch.stateChange(state);
+
+ selection.transition().call(chart)
+ });
+
+ pie.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ // Update chart from a state object passed to event handler
+ dispatch.on('changeState', function(e) {
+
+ if (typeof e.disabled !== 'undefined') {
+ data[0].forEach(function(series,i) {
+ series.disabled = e.disabled[i];
+ });
+
+ state.disabled = e.disabled;
+ }
+
+ selection.call(chart);
+ });
+
+ //============================================================
+
+
+ });
+
+ return chart;
+ }
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ pie.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e);
+ });
+
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.legend = legend;
+ chart.dispatch = dispatch;
+ chart.pie = pie;
+
+ d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'id', 'showLabels', 'donutLabelsOutside', 'donut', 'labelThreshold');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ legend.color(color);
+ pie.color(color);
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.state = function(_) {
+ if (!arguments.length) return state;
+ state = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.scatter = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 960
+ , height = 500
+ , color = nv.utils.defaultColor() // chooses color
+ , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
+ , x = d3.scale.linear()
+ , y = d3.scale.linear()
+ , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
+ , getX = function(d) { return d.x } // accessor to get the x value
+ , getY = function(d) { return d.y } // accessor to get the y value
+ , getSize = function(d) { return d.size || 1} // accessor to get the point size
+ , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
+ , onlyCircles = true // Set to false to use shapes
+ , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
+ , forceY = [] // List of numbers to Force into the Y scale
+ , forceSize = [] // List of numbers to Force into the Size scale
+ , interactive = true // If true, plots a voronoi overlay for advanced point interection
+ , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
+ , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
+ , clipEdge = false // if true, masks points within x and y scale
+ , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
+ , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
+ , xDomain = null // Override x domain (skips the calculation from data)
+ , yDomain = null // Override y domain
+ , sizeDomain = null // Override point size domain
+ , sizeRange = null
+ , singlePoint = false
+ , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout')
+ , useVoronoi = true
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var x0, y0, z0 // used to store previous scales
+ , timeoutID
+ , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ container = d3.select(this);
+
+ //add series index to each data point for reference
+ data = data.map(function(series, i) {
+ series.values = series.values.map(function(point) {
+ point.series = i;
+ return point;
+ });
+ return series;
+ });
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ // remap and flatten the data for use in calculating the scales' domains
+ var seriesData = (xDomain && yDomain && sizeDomain) ? [] : // if we know xDomain and yDomain and sizeDomain, no need to calculate.... if Size is constant remember to set sizeDomain to speed up performance
+ d3.merge(
+ data.map(function(d) {
+ return d.values.map(function(d,i) {
+ return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
+ })
+ })
+ );
+
+ x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x }).concat(forceX)))
+
+ if (padData)
+ x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
+ else
+ x.range([0, availableWidth]);
+
+ y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
+ .range([availableHeight, 0]);
+
+ z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
+ .range(sizeRange || [16, 256]);
+
+ // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
+ if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
+ if (x.domain()[0] === x.domain()[1])
+ x.domain()[0] ?
+ x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
+ : x.domain([-1,1]);
+
+ if (y.domain()[0] === y.domain()[1])
+ y.domain()[0] ?
+ y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
+ : y.domain([-1,1]);
+
+
+ x0 = x0 || x;
+ y0 = y0 || y;
+ z0 = z0 || z;
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
+ var defsEnter = wrapEnter.append('defs');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-groups');
+ gEnter.append('g').attr('class', 'nv-point-paths');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+ defsEnter.append('clipPath')
+ .attr('id', 'nv-edge-clip-' + id)
+ .append('rect');
+
+ wrap.select('#nv-edge-clip-' + id + ' rect')
+ .attr('width', availableWidth)
+ .attr('height', availableHeight);
+
+ g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
+
+
+ function updateInteractiveLayer() {
+
+ if (!interactive) return false;
+
+ var eventElements;
+
+ var vertices = d3.merge(data.map(function(group, groupIndex) {
+ return group.values
+ .map(function(point, pointIndex) {
+ // *Adding noise to make duplicates very unlikely
+ // **Injecting series and point index for reference
+ return [x(getX(point,pointIndex)) * (Math.random() / 1e12 + 1) , y(getY(point,pointIndex)) * (Math.random() / 1e12 + 1), groupIndex, pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
+ })
+ .filter(function(pointArray, pointIndex) {
+ return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
+ })
+ })
+ );
+
+
+
+ //inject series and point index for reference into voronoi
+ if (useVoronoi === true) {
+
+ if (clipVoronoi) {
+ var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips')
+ .data([id])
+ .enter();
+
+ pointClipsEnter.append('clipPath')
+ .attr('class', 'nv-point-clips')
+ .attr('id', 'nv-points-clip-' + id);
+
+ var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle')
+ .data(vertices);
+ pointClips.enter().append('circle')
+ .attr('r', clipRadius);
+ pointClips.exit().remove();
+ pointClips
+ .attr('cx', function(d) { return d[0] })
+ .attr('cy', function(d) { return d[1] });
+
+ wrap.select('.nv-point-paths')
+ .attr('clip-path', 'url(#nv-points-clip-' + id + ')');
+ }
+
+
+ if(vertices.length < 3) {
+ // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
+ vertices.push([x.range()[0] - 2000, y.range()[0] - 2000, null, null]);
+ vertices.push([x.range()[1] + 2000, y.range()[1] + 2000, null, null]);
+ vertices.push([x.range()[0] - 2000, y.range()[0] + 2000, null, null]);
+ vertices.push([x.range()[1] + 2000, y.range()[1] - 2000, null, null]);
+ }
+
+ var bounds = d3.geom.polygon([
+ [-10,-10],
+ [-10,height + 10],
+ [width + 10,height + 10],
+ [width + 10,-10]
+ ]);
+
+ var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
+ return {
+ 'data': bounds.clip(d),
+ 'series': vertices[i][2],
+ 'point': vertices[i][3]
+ }
+ });
+
+
+
+ var pointPaths = wrap.select('.nv-point-paths').selectAll('path')
+ .data(voronoi);
+ pointPaths.enter().append('path')
+ .attr('class', function(d,i) { return 'nv-path-'+i; });
+ pointPaths.exit().remove();
+ pointPaths
+ .attr('d', function(d) { return 'M' + d.data.join('L') + 'Z'; });
+
+ pointPaths
+ .on('click', function(d) {
+ if (needsUpdate) return 0;
+ var series = data[d.series],
+ point = series.values[d.point];
+
+ dispatch.elementClick({
+ point: point,
+ series: series,
+ pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
+ seriesIndex: d.series,
+ pointIndex: d.point
+ });
+ })
+ .on('mouseover', function(d) {
+ if (needsUpdate) return 0;
+ var series = data[d.series],
+ point = series.values[d.point];
+
+ dispatch.elementMouseover({
+ point: point,
+ series: series,
+ pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
+ seriesIndex: d.series,
+ pointIndex: d.point
+ });
+ })
+ .on('mouseout', function(d, i) {
+ if (needsUpdate) return 0;
+ var series = data[d.series],
+ point = series.values[d.point];
+
+ dispatch.elementMouseout({
+ point: point,
+ series: series,
+ seriesIndex: d.series,
+ pointIndex: d.point
+ });
+ });
+
+
+ } else {
+ /*
+ // bring data in form needed for click handlers
+ var dataWithPoints = vertices.map(function(d, i) {
+ return {
+ 'data': d,
+ 'series': vertices[i][2],
+ 'point': vertices[i][3]
+ }
+ });
+ */
+
+ // add event handlers to points instead voronoi paths
+ wrap.select('.nv-groups').selectAll('.nv-group')
+ .selectAll('.nv-point')
+ //.data(dataWithPoints)
+ //.style('pointer-events', 'auto') // recativate events, disabled by css
+ .on('click', function(d,i) {
+ //nv.log('test', d, i);
+ if (needsUpdate) return 0;
+ var series = data[d.series],
+ point = series.values[i];
+
+ dispatch.elementClick({
+ point: point,
+ series: series,
+ pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
+ seriesIndex: d.series,
+ pointIndex: i
+ });
+ })
+ .on('mouseover', function(d,i) {
+ if (needsUpdate) return 0;
+ var series = data[d.series],
+ point = series.values[i];
+
+ dispatch.elementMouseover({
+ point: point,
+ series: series,
+ pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
+ seriesIndex: d.series,
+ pointIndex: i
+ });
+ })
+ .on('mouseout', function(d,i) {
+ if (needsUpdate) return 0;
+ var series = data[d.series],
+ point = series.values[i];
+
+ dispatch.elementMouseout({
+ point: point,
+ series: series,
+ seriesIndex: d.series,
+ pointIndex: i
+ });
+ });
+ }
+
+ needsUpdate = false;
+ }
+
+ needsUpdate = true;
+
+ var groups = wrap.select('.nv-groups').selectAll('.nv-group')
+ .data(function(d) { return d }, function(d) { return d.key });
+ groups.enter().append('g')
+ .style('stroke-opacity', 1e-6)
+ .style('fill-opacity', 1e-6);
+ d3.transition(groups.exit())
+ .style('stroke-opacity', 1e-6)
+ .style('fill-opacity', 1e-6)
+ .remove();
+ groups
+ .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
+ .classed('hover', function(d) { return d.hover });
+ d3.transition(groups)
+ .style('fill', function(d,i) { return color(d, i) })
+ .style('stroke', function(d,i) { return color(d, i) })
+ .style('stroke-opacity', 1)
+ .style('fill-opacity', .5);
+
+
+ if (onlyCircles) {
+
+ var points = groups.selectAll('circle.nv-point')
+ .data(function(d) { return d.values });
+ points.enter().append('circle')
+ .attr('cx', function(d,i) { return x0(getX(d,i)) })
+ .attr('cy', function(d,i) { return y0(getY(d,i)) })
+ .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
+ points.exit().remove();
+ d3.transition(groups.exit().selectAll('path.nv-point'))
+ .attr('cx', function(d,i) { return x(getX(d,i)) })
+ .attr('cy', function(d,i) { return y(getY(d,i)) })
+ .remove();
+ points.attr('class', function(d,i) { return 'nv-point nv-point-' + i });
+ d3.transition(points)
+ .attr('cx', function(d,i) { return x(getX(d,i)) })
+ .attr('cy', function(d,i) { return y(getY(d,i)) })
+ .attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
+
+ } else {
+
+ var points = groups.selectAll('path.nv-point')
+ .data(function(d) { return d.values });
+ points.enter().append('path')
+ .attr('transform', function(d,i) {
+ return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
+ })
+ .attr('d',
+ d3.svg.symbol()
+ .type(getShape)
+ .size(function(d,i) { return z(getSize(d,i)) })
+ );
+ points.exit().remove();
+ d3.transition(groups.exit().selectAll('path.nv-point'))
+ .attr('transform', function(d,i) {
+ return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
+ })
+ .remove();
+ points.attr('class', function(d,i) { return 'nv-point nv-point-' + i });
+ d3.transition(points)
+ .attr('transform', function(d,i) {
+ //nv.log(d,i,getX(d,i), x(getX(d,i)));
+ return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
+ })
+ .attr('d',
+ d3.svg.symbol()
+ .type(getShape)
+ .size(function(d,i) { return z(getSize(d,i)) })
+ );
+ }
+
+
+ // Delay updating the invisible interactive layer for smoother animation
+ clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
+ timeoutID = setTimeout(updateInteractiveLayer, 300);
+ //updateInteractiveLayer();
+
+ //store old scales for use in transitions on update
+ x0 = x.copy();
+ y0 = y.copy();
+ z0 = z.copy();
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ dispatch.on('elementMouseover.point', function(d) {
+ if (interactive)
+ d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex)
+ .classed('hover', true);
+ });
+
+ dispatch.on('elementMouseout.point', function(d) {
+ if (interactive)
+ d3.select('.nv-chart-' + id + ' .nv-series-' + d.seriesIndex + ' .nv-point-' + d.pointIndex)
+ .classed('hover', false);
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = d3.functor(_);
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = d3.functor(_);
+ return chart;
+ };
+
+ chart.size = function(_) {
+ if (!arguments.length) return getSize;
+ getSize = d3.functor(_);
+ return chart;
+ };
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.xScale = function(_) {
+ if (!arguments.length) return x;
+ x = _;
+ return chart;
+ };
+
+ chart.yScale = function(_) {
+ if (!arguments.length) return y;
+ y = _;
+ return chart;
+ };
+
+ chart.zScale = function(_) {
+ if (!arguments.length) return z;
+ z = _;
+ return chart;
+ };
+
+ chart.xDomain = function(_) {
+ if (!arguments.length) return xDomain;
+ xDomain = _;
+ return chart;
+ };
+
+ chart.yDomain = function(_) {
+ if (!arguments.length) return yDomain;
+ yDomain = _;
+ return chart;
+ };
+
+ chart.sizeDomain = function(_) {
+ if (!arguments.length) return sizeDomain;
+ sizeDomain = _;
+ return chart;
+ };
+
+ chart.sizeRange = function(_) {
+ if (!arguments.length) return sizeRange;
+ sizeRange = _;
+ return chart;
+ };
+
+ chart.forceX = function(_) {
+ if (!arguments.length) return forceX;
+ forceX = _;
+ return chart;
+ };
+
+ chart.forceY = function(_) {
+ if (!arguments.length) return forceY;
+ forceY = _;
+ return chart;
+ };
+
+ chart.forceSize = function(_) {
+ if (!arguments.length) return forceSize;
+ forceSize = _;
+ return chart;
+ };
+
+ chart.interactive = function(_) {
+ if (!arguments.length) return interactive;
+ interactive = _;
+ return chart;
+ };
+
+ chart.pointActive = function(_) {
+ if (!arguments.length) return pointActive;
+ pointActive = _;
+ return chart;
+ };
+
+ chart.padData = function(_) {
+ if (!arguments.length) return padData;
+ padData = _;
+ return chart;
+ };
+
+ chart.clipEdge = function(_) {
+ if (!arguments.length) return clipEdge;
+ clipEdge = _;
+ return chart;
+ };
+
+ chart.clipVoronoi= function(_) {
+ if (!arguments.length) return clipVoronoi;
+ clipVoronoi = _;
+ return chart;
+ };
+
+ chart.useVoronoi= function(_) {
+ if (!arguments.length) return useVoronoi;
+ useVoronoi = _;
+ if (useVoronoi === false) {
+ clipVoronoi = false;
+ }
+ return chart;
+ };
+
+ chart.clipRadius = function(_) {
+ if (!arguments.length) return clipRadius;
+ clipRadius = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ chart.shape = function(_) {
+ if (!arguments.length) return getShape;
+ getShape = _;
+ return chart;
+ };
+
+ chart.onlyCircles = function(_) {
+ if (!arguments.length) return onlyCircles;
+ onlyCircles = _;
+ return chart;
+ };
+
+ chart.id = function(_) {
+ if (!arguments.length) return id;
+ id = _;
+ return chart;
+ };
+
+ chart.singlePoint = function(_) {
+ if (!arguments.length) return singlePoint;
+ singlePoint = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.scatterChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var scatter = nv.models.scatter()
+ , xAxis = nv.models.axis()
+ , yAxis = nv.models.axis()
+ , legend = nv.models.legend()
+ , controls = nv.models.legend()
+ , distX = nv.models.distribution()
+ , distY = nv.models.distribution()
+ ;
+
+ var margin = {top: 30, right: 20, bottom: 50, left: 75}
+ , width = null
+ , height = null
+ , color = nv.utils.defaultColor()
+ , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
+ , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
+ , xPadding = 0
+ , yPadding = 0
+ , showDistX = false
+ , showDistY = false
+ , showLegend = true
+ , showControls = !!d3.fisheye
+ , fisheye = 0
+ , pauseFisheye = false
+ , tooltips = true
+ , tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
+ , tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
+ //, tooltip = function(key, x, y) { return '<h3>' + key + '</h3>' }
+ , tooltip = null
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
+ , noData = "No Data Available."
+ ;
+
+ scatter
+ .xScale(x)
+ .yScale(y)
+ ;
+ xAxis
+ .orient('bottom')
+ .tickPadding(10)
+ ;
+ yAxis
+ .orient('left')
+ .tickPadding(10)
+ ;
+ distX
+ .axis('x')
+ ;
+ distY
+ .axis('y')
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var state = {},
+ x0, y0;
+
+ var showTooltip = function(e, offsetElement) {
+ //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
+
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
+ leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
+ topY = e.pos[1] + ( offsetElement.offsetTop || 0),
+ xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
+ yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
+
+ if( tooltipX != null )
+ nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
+ if( tooltipY != null )
+ nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
+ if( tooltip != null )
+ nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
+ };
+
+ var controlsData = [
+ { key: 'Magnify', disabled: true }
+ ];
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+ chart.update = function() { chart(selection) };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display noData message if there's nothing to show.
+
+ if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x0 = x0 || x;
+ y0 = y0 || y;
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g')
+
+ // background for pointer events
+ gEnter.append('rect').attr('class', 'nvd3 nv-background')
+
+ gEnter.append('g').attr('class', 'nv-x nv-axis');
+ gEnter.append('g').attr('class', 'nv-y nv-axis');
+ gEnter.append('g').attr('class', 'nv-scatterWrap');
+ gEnter.append('g').attr('class', 'nv-distWrap');
+ gEnter.append('g').attr('class', 'nv-legendWrap');
+ gEnter.append('g').attr('class', 'nv-controlsWrap');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Legend
+
+ if (showLegend) {
+ legend.width( availableWidth / 2 );
+
+ wrap.select('.nv-legendWrap')
+ .datum(data)
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+ wrap.select('.nv-legendWrap')
+ .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Controls
+
+ if (showControls) {
+ controls.width(180).color(['#444']);
+ g.select('.nv-controlsWrap')
+ .datum(controlsData)
+ .attr('transform', 'translate(0,' + (-margin.top) +')')
+ .call(controls);
+ }
+
+ //------------------------------------------------------------
+
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ scatter
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled }))
+
+ wrap.select('.nv-scatterWrap')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(scatter);
+
+
+ //Adjust for x and y padding
+ if (xPadding) {
+ var xRange = x.domain()[1] - x.domain()[0];
+ x.domain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]);
+ }
+
+ if (yPadding) {
+ var yRange = y.domain()[1] - y.domain()[0];
+ y.domain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]);
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Axes
+
+ xAxis
+ .scale(x)
+ .ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 )
+ .tickSize( -availableHeight , 0);
+
+ g.select('.nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + y.range()[0] + ')')
+ .call(xAxis);
+
+
+ yAxis
+ .scale(y)
+ .ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 )
+ .tickSize( -availableWidth, 0);
+
+ g.select('.nv-y.nv-axis')
+ .call(yAxis);
+
+
+ if (showDistX) {
+ distX
+ .getData(scatter.x())
+ .scale(x)
+ .width(availableWidth)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled }));
+ gEnter.select('.nv-distWrap').append('g')
+ .attr('class', 'nv-distributionX');
+ g.select('.nv-distributionX')
+ .attr('transform', 'translate(0,' + y.range()[0] + ')')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(distX);
+ }
+
+ if (showDistY) {
+ distY
+ .getData(scatter.y())
+ .scale(y)
+ .width(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled }));
+ gEnter.select('.nv-distWrap').append('g')
+ .attr('class', 'nv-distributionY');
+ g.select('.nv-distributionY')
+ .attr('transform', 'translate(-' + distY.size() + ',0)')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(distY);
+ }
+
+ //------------------------------------------------------------
+
+
+
+
+ if (d3.fisheye) {
+ g.select('.nv-background')
+ .attr('width', availableWidth)
+ .attr('height', availableHeight);
+
+ g.select('.nv-background').on('mousemove', updateFisheye);
+ g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
+ scatter.dispatch.on('elementClick.freezeFisheye', function() {
+ pauseFisheye = !pauseFisheye;
+ });
+ }
+
+
+ function updateFisheye() {
+ if (pauseFisheye) {
+ g.select('.nv-point-paths').style('pointer-events', 'all');
+ return false;
+ }
+
+ g.select('.nv-point-paths').style('pointer-events', 'none' );
+
+ var mouse = d3.mouse(this);
+ x.distortion(fisheye).focus(mouse[0]);
+ y.distortion(fisheye).focus(mouse[1]);
+
+ g.select('.nv-scatterWrap')
+ .call(scatter);
+
+ g.select('.nv-x.nv-axis').call(xAxis);
+ g.select('.nv-y.nv-axis').call(yAxis);
+ g.select('.nv-distributionX')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(distX);
+ g.select('.nv-distributionY')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(distY);
+ }
+
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ controls.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+
+ fisheye = d.disabled ? 0 : 2.5;
+ g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
+ g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
+
+ if (d.disabled) {
+ x.distortion(fisheye).focus(0);
+ y.distortion(fisheye).focus(0);
+
+ g.select('.nv-scatterWrap').call(scatter);
+ g.select('.nv-x.nv-axis').call(xAxis);
+ g.select('.nv-y.nv-axis').call(yAxis);
+ } else {
+ pauseFisheye = false;
+ }
+
+ chart(selection);
+ });
+
+ legend.dispatch.on('legendClick', function(d,i, that) {
+ d.disabled = !d.disabled;
+
+ if (!data.filter(function(d) { return !d.disabled }).length) {
+ data.map(function(d) {
+ d.disabled = false;
+ wrap.selectAll('.nv-series').classed('disabled', false);
+ return d;
+ });
+ }
+
+ state.disabled = data.map(function(d) { return !!d.disabled });
+ dispatch.stateChange(state);
+
+ chart(selection);
+ });
+
+ /*
+ legend.dispatch.on('legendMouseover', function(d, i) {
+ d.hover = true;
+ chart(selection);
+ });
+
+ legend.dispatch.on('legendMouseout', function(d, i) {
+ d.hover = false;
+ chart(selection);
+ });
+ */
+
+ scatter.dispatch.on('elementMouseover.tooltip', function(e) {
+ d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
+ .attr('y1', e.pos[1] - availableHeight);
+ d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
+ .attr('x2', e.pos[0] + distX.size());
+
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+ // Update chart from a state object passed to event handler
+ dispatch.on('changeState', function(e) {
+
+ if (typeof e.disabled !== 'undefined') {
+ data.forEach(function(series,i) {
+ series.disabled = e.disabled[i];
+ });
+
+ state.disabled = e.disabled;
+ }
+
+ selection.call(chart);
+ });
+
+ //============================================================
+
+
+ //store old scales for use in transitions on update
+ x0 = x.copy();
+ y0 = y.copy();
+
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ scatter.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+
+ d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
+ .attr('y1', 0);
+ d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
+ .attr('x2', distY.size());
+ });
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.dispatch = dispatch;
+ chart.scatter = scatter;
+ chart.legend = legend;
+ chart.controls = controls;
+ chart.xAxis = xAxis;
+ chart.yAxis = yAxis;
+ chart.distX = distX;
+ chart.distY = distY;
+
+ d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ legend.color(color);
+ distX.color(color);
+ distY.color(color);
+ return chart;
+ };
+
+ chart.showDistX = function(_) {
+ if (!arguments.length) return showDistX;
+ showDistX = _;
+ return chart;
+ };
+
+ chart.showDistY = function(_) {
+ if (!arguments.length) return showDistY;
+ showDistY = _;
+ return chart;
+ };
+
+ chart.showControls = function(_) {
+ if (!arguments.length) return showControls;
+ showControls = _;
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.fisheye = function(_) {
+ if (!arguments.length) return fisheye;
+ fisheye = _;
+ return chart;
+ };
+
+ chart.xPadding = function(_) {
+ if (!arguments.length) return xPadding;
+ xPadding = _;
+ return chart;
+ };
+
+ chart.yPadding = function(_) {
+ if (!arguments.length) return yPadding;
+ yPadding = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.tooltipXContent = function(_) {
+ if (!arguments.length) return tooltipX;
+ tooltipX = _;
+ return chart;
+ };
+
+ chart.tooltipYContent = function(_) {
+ if (!arguments.length) return tooltipY;
+ tooltipY = _;
+ return chart;
+ };
+
+ chart.state = function(_) {
+ if (!arguments.length) return state;
+ state = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.scatterPlusLineChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var scatter = nv.models.scatter()
+ , xAxis = nv.models.axis()
+ , yAxis = nv.models.axis()
+ , legend = nv.models.legend()
+ , controls = nv.models.legend()
+ , distX = nv.models.distribution()
+ , distY = nv.models.distribution()
+ ;
+
+ var margin = {top: 30, right: 20, bottom: 50, left: 75}
+ , width = null
+ , height = null
+ , color = nv.utils.defaultColor()
+ , x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
+ , y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
+ , showDistX = false
+ , showDistY = false
+ , showLegend = true
+ , showControls = !!d3.fisheye
+ , fisheye = 0
+ , pauseFisheye = false
+ , tooltips = true
+ , tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
+ , tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
+ , tooltip = function(key, x, y, date) { return '<h3>' + key + '</h3>'
+ + '<p>' + date + '</p>' }
+ //, tooltip = null
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
+ , noData = "No Data Available."
+ ;
+
+ scatter
+ .xScale(x)
+ .yScale(y)
+ ;
+ xAxis
+ .orient('bottom')
+ .tickPadding(10)
+ ;
+ yAxis
+ .orient('left')
+ .tickPadding(10)
+ ;
+ distX
+ .axis('x')
+ ;
+ distY
+ .axis('y')
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var state = {},
+ x0, y0;
+
+ var showTooltip = function(e, offsetElement) {
+ //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
+
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
+ leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
+ topY = e.pos[1] + ( offsetElement.offsetTop || 0),
+ xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
+ yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
+
+ if( tooltipX != null )
+ nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
+ if( tooltipY != null )
+ nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
+ if( tooltip != null )
+ nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
+ };
+
+ var controlsData = [
+ { key: 'Magnify', disabled: true }
+ ];
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+ chart.update = function() { chart(selection) };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display noData message if there's nothing to show.
+
+ if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = scatter.xScale();
+ y = scatter.yScale();
+
+ x0 = x0 || x;
+ y0 = y0 || y;
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g')
+
+ // background for pointer events
+ gEnter.append('rect').attr('class', 'nvd3 nv-background')
+
+ gEnter.append('g').attr('class', 'nv-x nv-axis');
+ gEnter.append('g').attr('class', 'nv-y nv-axis');
+ gEnter.append('g').attr('class', 'nv-scatterWrap');
+ gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
+ gEnter.append('g').attr('class', 'nv-distWrap');
+ gEnter.append('g').attr('class', 'nv-legendWrap');
+ gEnter.append('g').attr('class', 'nv-controlsWrap');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Legend
+
+ if (showLegend) {
+ legend.width( availableWidth / 2 );
+
+ wrap.select('.nv-legendWrap')
+ .datum(data)
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+ wrap.select('.nv-legendWrap')
+ .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Controls
+
+ if (showControls) {
+ controls.width(180).color(['#444']);
+ g.select('.nv-controlsWrap')
+ .datum(controlsData)
+ .attr('transform', 'translate(0,' + (-margin.top) +')')
+ .call(controls);
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ scatter
+ .width(availableWidth)
+ .height(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled }))
+
+ wrap.select('.nv-scatterWrap')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(scatter);
+
+
+ wrap.select('.nv-regressionLinesWrap')
+ .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
+
+ var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
+ .data(function(d) { return d });
+
+ var reglines = regWrap.enter()
+ .append('g').attr('class', 'nv-regLines')
+ .append('line').attr('class', 'nv-regLine')
+ .style('stroke-opacity', 0);
+
+ //d3.transition(regWrap.selectAll('.nv-regLines line'))
+ regWrap.selectAll('.nv-regLines line')
+ .attr('x1', x.range()[0])
+ .attr('x2', x.range()[1])
+ .attr('y1', function(d,i) { return y(x.domain()[0] * d.slope + d.intercept) })
+ .attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) })
+ .style('stroke', function(d,i,j) { return color(d,j) })
+ .style('stroke-opacity', function(d,i) {
+ return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
+ });
+
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Axes
+
+ xAxis
+ .scale(x)
+ .ticks( xAxis.ticks() ? xAxis.ticks() : availableWidth / 100 )
+ .tickSize( -availableHeight , 0);
+
+ g.select('.nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + y.range()[0] + ')')
+ .call(xAxis);
+
+
+ yAxis
+ .scale(y)
+ .ticks( yAxis.ticks() ? yAxis.ticks() : availableHeight / 36 )
+ .tickSize( -availableWidth, 0);
+
+ g.select('.nv-y.nv-axis')
+ .call(yAxis);
+
+
+ if (showDistX) {
+ distX
+ .getData(scatter.x())
+ .scale(x)
+ .width(availableWidth)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled }));
+ gEnter.select('.nv-distWrap').append('g')
+ .attr('class', 'nv-distributionX');
+ g.select('.nv-distributionX')
+ .attr('transform', 'translate(0,' + y.range()[0] + ')')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(distX);
+ }
+
+ if (showDistY) {
+ distY
+ .getData(scatter.y())
+ .scale(y)
+ .width(availableHeight)
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled }));
+ gEnter.select('.nv-distWrap').append('g')
+ .attr('class', 'nv-distributionY');
+ g.select('.nv-distributionY')
+ .attr('transform', 'translate(-' + distY.size() + ',0)')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(distY);
+ }
+
+ //------------------------------------------------------------
+
+
+
+
+ if (d3.fisheye) {
+ g.select('.nv-background')
+ .attr('width', availableWidth)
+ .attr('height', availableHeight);
+
+ g.select('.nv-background').on('mousemove', updateFisheye);
+ g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
+ scatter.dispatch.on('elementClick.freezeFisheye', function() {
+ pauseFisheye = !pauseFisheye;
+ });
+ }
+
+
+ function updateFisheye() {
+ if (pauseFisheye) {
+ g.select('.nv-point-paths').style('pointer-events', 'all');
+ return false;
+ }
+
+ g.select('.nv-point-paths').style('pointer-events', 'none' );
+
+ var mouse = d3.mouse(this);
+ x.distortion(fisheye).focus(mouse[0]);
+ y.distortion(fisheye).focus(mouse[1]);
+
+ g.select('.nv-scatterWrap')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(scatter);
+ g.select('.nv-x.nv-axis').call(xAxis);
+ g.select('.nv-y.nv-axis').call(yAxis);
+ g.select('.nv-distributionX')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(distX);
+ g.select('.nv-distributionY')
+ .datum(data.filter(function(d) { return !d.disabled }))
+ .call(distY);
+ }
+
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ controls.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+
+ fisheye = d.disabled ? 0 : 2.5;
+ g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
+ g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
+
+ if (d.disabled) {
+ x.distortion(fisheye).focus(0);
+ y.distortion(fisheye).focus(0);
+
+ g.select('.nv-scatterWrap').call(scatter);
+ g.select('.nv-x.nv-axis').call(xAxis);
+ g.select('.nv-y.nv-axis').call(yAxis);
+ } else {
+ pauseFisheye = false;
+ }
+
+ chart(selection);
+ });
+
+ legend.dispatch.on('legendClick', function(d,i, that) {
+ d.disabled = !d.disabled;
+
+ if (!data.filter(function(d) { return !d.disabled }).length) {
+ data.map(function(d) {
+ d.disabled = false;
+ wrap.selectAll('.nv-series').classed('disabled', false);
+ return d;
+ });
+ }
+
+ state.disabled = data.map(function(d) { return !!d.disabled });
+ dispatch.stateChange(state);
+
+ chart(selection);
+ });
+
+ /*
+ legend.dispatch.on('legendMouseover', function(d, i) {
+ d.hover = true;
+ chart(selection);
+ });
+
+ legend.dispatch.on('legendMouseout', function(d, i) {
+ d.hover = false;
+ chart(selection);
+ });
+ */
+
+ scatter.dispatch.on('elementMouseover.tooltip', function(e) {
+ d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
+ .attr('y1', e.pos[1] - availableHeight);
+ d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
+ .attr('x2', e.pos[0] + distX.size());
+
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
+ dispatch.tooltipShow(e);
+ });
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+ // Update chart from a state object passed to event handler
+ dispatch.on('changeState', function(e) {
+
+ if (typeof e.disabled !== 'undefined') {
+ data.forEach(function(series,i) {
+ series.disabled = e.disabled[i];
+ });
+
+ state.disabled = e.disabled;
+ }
+
+ selection.call(chart);
+ });
+
+ //============================================================
+
+
+ //store old scales for use in transitions on update
+ x0 = x.copy();
+ y0 = y.copy();
+
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ scatter.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+
+ d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
+ .attr('y1', 0);
+ d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
+ .attr('x2', distY.size());
+ });
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.dispatch = dispatch;
+ chart.scatter = scatter;
+ chart.legend = legend;
+ chart.controls = controls;
+ chart.xAxis = xAxis;
+ chart.yAxis = yAxis;
+ chart.distX = distX;
+ chart.distY = distY;
+
+ d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ legend.color(color);
+ distX.color(color);
+ distY.color(color);
+ return chart;
+ };
+
+ chart.showDistX = function(_) {
+ if (!arguments.length) return showDistX;
+ showDistX = _;
+ return chart;
+ };
+
+ chart.showDistY = function(_) {
+ if (!arguments.length) return showDistY;
+ showDistY = _;
+ return chart;
+ };
+
+ chart.showControls = function(_) {
+ if (!arguments.length) return showControls;
+ showControls = _;
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.fisheye = function(_) {
+ if (!arguments.length) return fisheye;
+ fisheye = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.tooltipXContent = function(_) {
+ if (!arguments.length) return tooltipX;
+ tooltipX = _;
+ return chart;
+ };
+
+ chart.tooltipYContent = function(_) {
+ if (!arguments.length) return tooltipY;
+ tooltipY = _;
+ return chart;
+ };
+
+ chart.state = function(_) {
+ if (!arguments.length) return state;
+ state = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.sparkline = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 2, right: 0, bottom: 2, left: 0}
+ , width = 400
+ , height = 32
+ , animate = true
+ , x = d3.scale.linear()
+ , y = d3.scale.linear()
+ , getX = function(d) { return d.x }
+ , getY = function(d) { return d.y }
+ , color = nv.utils.getColor(['#000'])
+ , xDomain
+ , yDomain
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ container = d3.select(this);
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x .domain(xDomain || d3.extent(data, getX ))
+ .range([0, availableWidth]);
+
+ y .domain(yDomain || d3.extent(data, getY ))
+ .range([availableHeight, 0]);
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
+
+ //------------------------------------------------------------
+
+
+ var paths = wrap.selectAll('path')
+ .data(function(d) { return [d] });
+ paths.enter().append('path');
+ paths.exit().remove();
+ paths
+ .style('stroke', function(d,i) { return d.color || color(d, i) })
+ .attr('d', d3.svg.line()
+ .x(function(d,i) { return x(getX(d,i)) })
+ .y(function(d,i) { return y(getY(d,i)) })
+ );
+
+
+ // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
+ var points = wrap.selectAll('circle.nv-point')
+ .data(function(data) {
+ var yValues = data.map(function(d, i) { return getY(d,i); });
+ function pointIndex(index) {
+ if (index != -1) {
+ var result = data[index];
+ result.pointIndex = index;
+ return result;
+ } else {
+ return null;
+ }
+ }
+ var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
+ minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
+ currentPoint = pointIndex(yValues.length - 1);
+ return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
+ });
+ points.enter().append('circle');
+ points.exit().remove();
+ points
+ .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
+ .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
+ .attr('r', 2)
+ .attr('class', function(d,i) {
+ return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
+ getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
+ });
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = d3.functor(_);
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = d3.functor(_);
+ return chart;
+ };
+
+ chart.xScale = function(_) {
+ if (!arguments.length) return x;
+ x = _;
+ return chart;
+ };
+
+ chart.yScale = function(_) {
+ if (!arguments.length) return y;
+ y = _;
+ return chart;
+ };
+
+ chart.xDomain = function(_) {
+ if (!arguments.length) return xDomain;
+ xDomain = _;
+ return chart;
+ };
+
+ chart.yDomain = function(_) {
+ if (!arguments.length) return yDomain;
+ yDomain = _;
+ return chart;
+ };
+
+ chart.animate = function(_) {
+ if (!arguments.length) return animate;
+ animate = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.sparklinePlus = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var sparkline = nv.models.sparkline();
+
+ var margin = {top: 15, right: 100, bottom: 10, left: 50}
+ , width = null
+ , height = null
+ , x
+ , y
+ , index = []
+ , paused = false
+ , xTickFormat = d3.format(',r')
+ , yTickFormat = d3.format(',.2f')
+ , showValue = true
+ , alignValue = true
+ , rightAlignValue = false
+ , noData = "No Data Available."
+ ;
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this);
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+ var currentValue = sparkline.y()(data[data.length-1], data.length-1);
+
+ chart.update = function() { chart(selection) };
+ chart.container = this;
+
+
+ //------------------------------------------------------------
+ // Display No Data message if there's nothing to show.
+
+ if (!data || !data.length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = sparkline.xScale();
+ y = sparkline.yScale();
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-sparklineWrap');
+ gEnter.append('g').attr('class', 'nv-valueWrap');
+ gEnter.append('g').attr('class', 'nv-hoverArea');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ var sparklineWrap = g.select('.nv-sparklineWrap');
+
+ sparkline
+ .width(availableWidth)
+ .height(availableHeight);
+
+ sparklineWrap
+ .call(sparkline);
+
+ //------------------------------------------------------------
+
+
+ var valueWrap = g.select('.nv-valueWrap');
+
+ var value = valueWrap.selectAll('.nv-currentValue')
+ .data([currentValue]);
+
+ value.enter().append('text').attr('class', 'nv-currentValue')
+ .attr('dx', rightAlignValue ? -8 : 8)
+ .attr('dy', '.9em')
+ .style('text-anchor', rightAlignValue ? 'end' : 'start');
+
+ value
+ .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
+ .attr('y', alignValue ? function(d) { return y(d) } : 0)
+ .style('fill', sparkline.color()(data[data.length-1], data.length-1))
+ .text(yTickFormat(currentValue));
+
+
+
+ gEnter.select('.nv-hoverArea').append('rect')
+ .on('mousemove', sparklineHover)
+ .on('click', function() { paused = !paused })
+ .on('mouseout', function() { index = []; updateValueLine(); });
+ //.on('mouseout', function() { index = null; updateValueLine(); });
+
+ g.select('.nv-hoverArea rect')
+ .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
+ .attr('width', availableWidth + margin.left + margin.right)
+ .attr('height', availableHeight + margin.top);
+
+
+
+ function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way
+ if (paused) return;
+
+ var hoverValue = g.selectAll('.nv-hoverValue').data(index)
+
+ var hoverEnter = hoverValue.enter()
+ .append('g').attr('class', 'nv-hoverValue')
+ .style('stroke-opacity', 0)
+ .style('fill-opacity', 0);
+
+ hoverValue.exit()
+ .transition().duration(250)
+ .style('stroke-opacity', 0)
+ .style('fill-opacity', 0)
+ .remove();
+
+ hoverValue
+ .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
+ .transition().duration(250)
+ .style('stroke-opacity', 1)
+ .style('fill-opacity', 1);
+
+ if (!index.length) return;
+
+ hoverEnter.append('line')
+ .attr('x1', 0)
+ .attr('y1', -margin.top)
+ .attr('x2', 0)
+ .attr('y2', availableHeight);
+
+
+ hoverEnter.append('text').attr('class', 'nv-xValue')
+ .attr('x', -6)
+ .attr('y', -margin.top)
+ .attr('text-anchor', 'end')
+ .attr('dy', '.9em')
+
+
+ g.select('.nv-hoverValue .nv-xValue')
+ .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
+
+ hoverEnter.append('text').attr('class', 'nv-yValue')
+ .attr('x', 6)
+ .attr('y', -margin.top)
+ .attr('text-anchor', 'start')
+ .attr('dy', '.9em')
+
+ g.select('.nv-hoverValue .nv-yValue')
+ .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
+
+ }
+
+
+ function sparklineHover() {
+ if (paused) return;
+
+ var pos = d3.mouse(this)[0] - margin.left;
+
+ function getClosestIndex(data, x) {
+ var distance = Math.abs(sparkline.x()(data[0], 0) - x);
+ var closestIndex = 0;
+ for (var i = 0; i < data.length; i++){
+ if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
+ distance = Math.abs(sparkline.x()(data[i], i) - x);
+ closestIndex = i;
+ }
+ }
+ return closestIndex;
+ }
+
+ index = [getClosestIndex(data, Math.round(x.invert(pos)))];
+
+ updateValueLine();
+ }
+
+ });
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.sparkline = sparkline;
+
+ d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.xTickFormat = function(_) {
+ if (!arguments.length) return xTickFormat;
+ xTickFormat = _;
+ return chart;
+ };
+
+ chart.yTickFormat = function(_) {
+ if (!arguments.length) return yTickFormat;
+ yTickFormat = _;
+ return chart;
+ };
+
+ chart.showValue = function(_) {
+ if (!arguments.length) return showValue;
+ showValue = _;
+ return chart;
+ };
+
+ chart.alignValue = function(_) {
+ if (!arguments.length) return alignValue;
+ alignValue = _;
+ return chart;
+ };
+
+ chart.rightAlignValue = function(_) {
+ if (!arguments.length) return rightAlignValue;
+ rightAlignValue = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.stackedArea = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var margin = {top: 0, right: 0, bottom: 0, left: 0}
+ , width = 960
+ , height = 500
+ , color = nv.utils.defaultColor() // a function that computes the color
+ , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
+ , getX = function(d) { return d.x } // accessor to get the x value from a data point
+ , getY = function(d) { return d.y } // accessor to get the y value from a data point
+ , style = 'stack'
+ , offset = 'zero'
+ , order = 'default'
+ , interpolate = 'linear' // controls the line interpolation
+ , clipEdge = false // if true, masks lines within x and y scale
+ , x //can be accessed via chart.xScale()
+ , y //can be accessed via chart.yScale()
+ , scatter = nv.models.scatter()
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout')
+ ;
+
+ scatter
+ .size(2.2) // default size
+ .sizeDomain([2.2]) // all the same size by default
+ ;
+
+ /************************************
+ * offset:
+ * 'wiggle' (stream)
+ * 'zero' (stacked)
+ * 'expand' (normalize to 100%)
+ * 'silhouette' (simple centered)
+ *
+ * order:
+ * 'inside-out' (stream)
+ * 'default' (input order)
+ ************************************/
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var availableWidth = width - margin.left - margin.right,
+ availableHeight = height - margin.top - margin.bottom,
+ container = d3.select(this);
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = scatter.xScale();
+ y = scatter.yScale();
+
+ //------------------------------------------------------------
+
+
+ // Injecting point index into each point because d3.layout.stack().out does not give index
+ // ***Also storing getY(d,i) as stackedY so that it can be set to 0 if series is disabled
+ data = data.map(function(aseries, i) {
+ aseries.values = aseries.values.map(function(d, j) {
+ d.index = j;
+ d.stackedY = aseries.disabled ? 0 : getY(d,j);
+ return d;
+ })
+ return aseries;
+ });
+
+
+ data = d3.layout.stack()
+ .order(order)
+ .offset(offset)
+ .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
+ .x(getX)
+ .y(function(d) { return d.stackedY })
+ .out(function(d, y0, y) {
+ d.display = {
+ y: y,
+ y0: y0
+ };
+ })
+ (data);
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
+ var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
+ var defsEnter = wrapEnter.append('defs');
+ var gEnter = wrapEnter.append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-areaWrap');
+ gEnter.append('g').attr('class', 'nv-scatterWrap');
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+ //------------------------------------------------------------
+
+
+ scatter
+ .width(availableWidth)
+ .height(availableHeight)
+ .x(getX)
+ .y(function(d) { return d.display.y + d.display.y0 })
+ .forceY([0])
+ .color(data.map(function(d,i) {
+ return d.color || color(d, i);
+ }).filter(function(d,i) { return !data[i].disabled }));
+
+
+ var scatterWrap = g.select('.nv-scatterWrap')
+ .datum(data.filter(function(d) { return !d.disabled }))
+
+ //d3.transition(scatterWrap).call(scatter);
+ scatterWrap.call(scatter);
+
+
+
+
+
+ defsEnter.append('clipPath')
+ .attr('id', 'nv-edge-clip-' + id)
+ .append('rect');
+
+ wrap.select('#nv-edge-clip-' + id + ' rect')
+ .attr('width', availableWidth)
+ .attr('height', availableHeight);
+
+ g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
+
+
+
+
+ var area = d3.svg.area()
+ .x(function(d,i) { return x(getX(d,i)) })
+ .y0(function(d) { return y(d.display.y0) })
+ .y1(function(d) { return y(d.display.y + d.display.y0) })
+ .interpolate(interpolate);
+
+ var zeroArea = d3.svg.area()
+ .x(function(d,i) { return x(getX(d,i)) })
+ .y0(function(d) { return y(d.display.y0) })
+ .y1(function(d) { return y(d.display.y0) });
+
+
+ var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
+ .data(function(d) { return d });
+ //.data(function(d) { return d }, function(d) { return d.key });
+ path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
+ .on('mouseover', function(d,i) {
+ d3.select(this).classed('hover', true);
+ dispatch.areaMouseover({
+ point: d,
+ series: d.key,
+ pos: [d3.event.pageX, d3.event.pageY],
+ seriesIndex: i
+ });
+ })
+ .on('mouseout', function(d,i) {
+ d3.select(this).classed('hover', false);
+ dispatch.areaMouseout({
+ point: d,
+ series: d.key,
+ pos: [d3.event.pageX, d3.event.pageY],
+ seriesIndex: i
+ });
+ })
+ .on('click', function(d,i) {
+ d3.select(this).classed('hover', false);
+ dispatch.areaClick({
+ point: d,
+ series: d.key,
+ pos: [d3.event.pageX, d3.event.pageY],
+ seriesIndex: i
+ });
+ })
+ //d3.transition(path.exit())
+ path.exit()
+ .attr('d', function(d,i) { return zeroArea(d.values,i) })
+ .remove();
+ path
+ .style('fill', function(d,i){ return d.color || color(d, i) })
+ .style('stroke', function(d,i){ return d.color || color(d, i) });
+ //d3.transition(path)
+ path
+ .attr('d', function(d,i) { return area(d.values,i) })
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ scatter.dispatch.on('elementMouseover.area', function(e) {
+ g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
+ });
+ scatter.dispatch.on('elementMouseout.area', function(e) {
+ g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
+ });
+
+ //============================================================
+
+ });
+
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ scatter.dispatch.on('elementClick.area', function(e) {
+ dispatch.areaClick(e);
+ })
+ scatter.dispatch.on('elementMouseover.tooltip', function(e) {
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
+ dispatch.tooltipShow(e);
+ });
+ scatter.dispatch.on('elementMouseout.tooltip', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Global getters and setters
+ //------------------------------------------------------------
+
+ chart.dispatch = dispatch;
+ chart.scatter = scatter;
+
+ d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius');
+
+ chart.x = function(_) {
+ if (!arguments.length) return getX;
+ getX = d3.functor(_);
+ return chart;
+ };
+
+ chart.y = function(_) {
+ if (!arguments.length) return getY;
+ getY = d3.functor(_);
+ return chart;
+ }
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return width;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return height;
+ height = _;
+ return chart;
+ };
+
+ chart.clipEdge = function(_) {
+ if (!arguments.length) return clipEdge;
+ clipEdge = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ return chart;
+ };
+
+ chart.offset = function(_) {
+ if (!arguments.length) return offset;
+ offset = _;
+ return chart;
+ };
+
+ chart.order = function(_) {
+ if (!arguments.length) return order;
+ order = _;
+ return chart;
+ };
+
+ //shortcut for offset + order
+ chart.style = function(_) {
+ if (!arguments.length) return style;
+ style = _;
+
+ switch (style) {
+ case 'stack':
+ chart.offset('zero');
+ chart.order('default');
+ break;
+ case 'stream':
+ chart.offset('wiggle');
+ chart.order('inside-out');
+ break;
+ case 'stream-center':
+ chart.offset('silhouette');
+ chart.order('inside-out');
+ break;
+ case 'expand':
+ chart.offset('expand');
+ chart.order('default');
+ break;
+ }
+
+ return chart;
+ };
+
+ chart.interpolate = function(_) {
+ if (!arguments.length) return interpolate;
+ interpolate = _;
+ return interpolate;
+
+ };
+
+ //============================================================
+
+
+ return chart;
+}
+
+nv.models.stackedAreaChart = function() {
+
+ //============================================================
+ // Public Variables with Default Settings
+ //------------------------------------------------------------
+
+ var stacked = nv.models.stackedArea()
+ , xAxis = nv.models.axis()
+ , yAxis = nv.models.axis()
+ , legend = nv.models.legend()
+ , controls = nv.models.legend()
+ ;
+
+ var margin = {top: 30, right: 25, bottom: 50, left: 60}
+ , width = null
+ , height = null
+ , color = nv.utils.defaultColor() // a function that takes in d, i and returns color
+ , showControls = true
+ , showLegend = true
+ , tooltips = true
+ , tooltip = function(key, x, y, e, graph) {
+ return '<h3>' + key + '</h3>' +
+ '<p>' + y + ' on ' + x + '</p>'
+ }
+ , x //can be accessed via chart.xScale()
+ , y //can be accessed via chart.yScale()
+ , yAxisTickFormat = d3.format(',.2f')
+ , state = { style: stacked.style() }
+ , noData = 'No Data Available.'
+ , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
+ ;
+
+ xAxis
+ .orient('bottom')
+ .tickPadding(7)
+ ;
+ yAxis
+ .orient('left')
+ ;
+ stacked.scatter
+ .pointActive(function(d) {
+ //console.log(stacked.y()(d), !!Math.round(stacked.y()(d) * 100));
+ return !!Math.round(stacked.y()(d) * 100);
+ })
+ ;
+
+ //============================================================
+
+
+ //============================================================
+ // Private Variables
+ //------------------------------------------------------------
+
+ var showTooltip = function(e, offsetElement) {
+ var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
+ top = e.pos[1] + ( offsetElement.offsetTop || 0),
+ x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
+ y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
+ content = tooltip(e.series.key, x, y, e, chart);
+
+ nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
+ };
+
+ //============================================================
+
+
+ function chart(selection) {
+ selection.each(function(data) {
+ var container = d3.select(this),
+ that = this;
+
+ var availableWidth = (width || parseInt(container.style('width')) || 960)
+ - margin.left - margin.right,
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+
+ chart.update = function() { chart(selection) };
+ chart.container = this;
+
+ //------------------------------------------------------------
+ // Display No Data message if there's nothing to show.
+
+ if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
+ var noDataText = container.selectAll('.nv-noData').data([noData]);
+
+ noDataText.enter().append('text')
+ .attr('class', 'nvd3 nv-noData')
+ .attr('dy', '-.7em')
+ .style('text-anchor', 'middle');
+
+ noDataText
+ .attr('x', margin.left + availableWidth / 2)
+ .attr('y', margin.top + availableHeight / 2)
+ .text(function(d) { return d });
+
+ return chart;
+ } else {
+ container.selectAll('.nv-noData').remove();
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Scales
+
+ x = stacked.xScale();
+ y = stacked.yScale();
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup containers and skeleton of chart
+
+ var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
+ var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
+ var g = wrap.select('g');
+
+ gEnter.append('g').attr('class', 'nv-x nv-axis');
+ gEnter.append('g').attr('class', 'nv-y nv-axis');
+ gEnter.append('g').attr('class', 'nv-stackedWrap');
+ gEnter.append('g').attr('class', 'nv-legendWrap');
+ gEnter.append('g').attr('class', 'nv-controlsWrap');
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Legend
+
+ if (showLegend) {
+ legend
+ .width( availableWidth * 2 / 3 );
+
+ g.select('.nv-legendWrap')
+ .datum(data)
+ .call(legend);
+
+ if ( margin.top != legend.height()) {
+ margin.top = legend.height();
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+ g.select('.nv-legendWrap')
+ .attr('transform', 'translate(' + ( availableWidth * 1 / 3 ) + ',' + (-margin.top) +')');
+ }
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Controls
+
+ if (showControls) {
+ var controlsData = [
+ { key: 'Stacked', disabled: stacked.offset() != 'zero' },
+ { key: 'Stream', disabled: stacked.offset() != 'wiggle' },
+ { key: 'Expanded', disabled: stacked.offset() != 'expand' }
+ ];
+
+ controls
+ .width( Math.min(280, availableWidth * 1 / 3) )
+ .color(['#444', '#444', '#444']);
+
+ g.select('.nv-controlsWrap')
+ .datum(controlsData)
+ .call(controls);
+
+
+ if ( margin.top != Math.max(controls.height(), legend.height()) ) {
+ margin.top = Math.max(controls.height(), legend.height());
+ availableHeight = (height || parseInt(container.style('height')) || 400)
+ - margin.top - margin.bottom;
+ }
+
+
+ g.select('.nv-controlsWrap')
+ .attr('transform', 'translate(0,' + (-margin.top) +')');
+ }
+
+ //------------------------------------------------------------
+
+
+ wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
+
+
+ //------------------------------------------------------------
+ // Main Chart Component(s)
+
+ stacked
+ .width(availableWidth)
+ .height(availableHeight)
+
+ var stackedWrap = g.select('.nv-stackedWrap')
+ .datum(data);
+ //d3.transition(stackedWrap).call(stacked);
+ stackedWrap.call(stacked);
+
+ //------------------------------------------------------------
+
+
+ //------------------------------------------------------------
+ // Setup Axes
+
+ xAxis
+ .scale(x)
+ .ticks( availableWidth / 100 )
+ .tickSize( -availableHeight, 0);
+
+ g.select('.nv-x.nv-axis')
+ .attr('transform', 'translate(0,' + availableHeight + ')');
+ //d3.transition(g.select('.nv-x.nv-axis'))
+ g.select('.nv-x.nv-axis')
+ .transition().duration(0)
+ .call(xAxis);
+
+ yAxis
+ .scale(y)
+ .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36)
+ .tickSize(-availableWidth, 0)
+ .setTickFormat(stacked.offset() == 'expand' ? d3.format('%') : yAxisTickFormat);
+
+ //d3.transition(g.select('.nv-y.nv-axis'))
+ g.select('.nv-y.nv-axis')
+ .transition().duration(0)
+ .call(yAxis);
+
+ //------------------------------------------------------------
+
+
+ //============================================================
+ // Event Handling/Dispatching (in chart's scope)
+ //------------------------------------------------------------
+
+ stacked.dispatch.on('areaClick.toggle', function(e) {
+ if (data.filter(function(d) { return !d.disabled }).length === 1)
+ data = data.map(function(d) {
+ d.disabled = false;
+ return d
+ });
+ else
+ data = data.map(function(d,i) {
+ d.disabled = (i != e.seriesIndex);
+ return d
+ });
+
+ //selection.transition().call(chart);
+ chart(selection);
+ });
+
+ legend.dispatch.on('legendClick', function(d,i) {
+ d.disabled = !d.disabled;
+
+ if (!data.filter(function(d) { return !d.disabled }).length) {
+ data.map(function(d) {
+ d.disabled = false;
+ return d;
+ });
+ }
+
+ state.disabled = data.map(function(d) { return !!d.disabled });
+ dispatch.stateChange(state);
+
+ //selection.transition().call(chart);
+ chart(selection);
+ });
+
+ controls.dispatch.on('legendClick', function(d,i) {
+ if (!d.disabled) return;
+
+ controlsData = controlsData.map(function(s) {
+ s.disabled = true;
+ return s;
+ });
+ d.disabled = false;
+
+ switch (d.key) {
+ case 'Stacked':
+ stacked.style('stack');
+ break;
+ case 'Stream':
+ stacked.style('stream');
+ break;
+ case 'Expanded':
+ stacked.style('expand');
+ break;
+ }
+
+ state.style = stacked.style();
+ dispatch.stateChange(state);
+
+ //selection.transition().call(chart);
+ chart(selection);
+ });
+
+ dispatch.on('tooltipShow', function(e) {
+ if (tooltips) showTooltip(e, that.parentNode);
+ });
+
+ // Update chart from a state object passed to event handler
+ dispatch.on('changeState', function(e) {
+
+ if (typeof e.disabled !== 'undefined') {
+ data.forEach(function(series,i) {
+ series.disabled = e.disabled[i];
+ });
+
+ state.disabled = e.disabled;
+ }
+
+ if (typeof e.style !== 'undefined') {
+ stacked.style(e.style);
+ }
+
+ selection.call(chart);
+ });
+
+ });
+
+
+ return chart;
+ }
+
+
+ //============================================================
+ // Event Handling/Dispatching (out of chart's scope)
+ //------------------------------------------------------------
+
+ stacked.dispatch.on('tooltipShow', function(e) {
+ //disable tooltips when value ~= 0
+ //// TODO: consider removing points from voronoi that have 0 value instead of this hack
+ /*
+ if (!Math.round(stacked.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
+ setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
+ return false;
+ }
+ */
+
+ e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
+ dispatch.tooltipShow(e);
+ });
+
+ stacked.dispatch.on('tooltipHide', function(e) {
+ dispatch.tooltipHide(e);
+ });
+
+ dispatch.on('tooltipHide', function() {
+ if (tooltips) nv.tooltip.cleanup();
+ });
+
+ //============================================================
+
+
+ //============================================================
+ // Expose Public Variables
+ //------------------------------------------------------------
+
+ // expose chart's sub-components
+ chart.dispatch = dispatch;
+ chart.stacked = stacked;
+ chart.legend = legend;
+ chart.controls = controls;
+ chart.xAxis = xAxis;
+ chart.yAxis = yAxis;
+
+ d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'sizeDomain', 'interactive', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate');
+
+ chart.margin = function(_) {
+ if (!arguments.length) return margin;
+ margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
+ margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
+ margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
+ margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
+ return chart;
+ };
+
+ chart.width = function(_) {
+ if (!arguments.length) return getWidth;
+ width = _;
+ return chart;
+ };
+
+ chart.height = function(_) {
+ if (!arguments.length) return getHeight;
+ height = _;
+ return chart;
+ };
+
+ chart.color = function(_) {
+ if (!arguments.length) return color;
+ color = nv.utils.getColor(_);
+ legend.color(color);
+ stacked.color(color);
+ return chart;
+ };
+
+ chart.showControls = function(_) {
+ if (!arguments.length) return showControls;
+ showControls = _;
+ return chart;
+ };
+
+ chart.showLegend = function(_) {
+ if (!arguments.length) return showLegend;
+ showLegend = _;
+ return chart;
+ };
+
+ chart.tooltip = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.tooltips = function(_) {
+ if (!arguments.length) return tooltips;
+ tooltips = _;
+ return chart;
+ };
+
+ chart.tooltipContent = function(_) {
+ if (!arguments.length) return tooltip;
+ tooltip = _;
+ return chart;
+ };
+
+ chart.state = function(_) {
+ if (!arguments.length) return state;
+ state = _;
+ return chart;
+ };
+
+ chart.noData = function(_) {
+ if (!arguments.length) return noData;
+ noData = _;
+ return chart;
+ };
+
+ yAxis.setTickFormat = yAxis.tickFormat;
+ yAxis.tickFormat = function(_) {
+ if (!arguments.length) return yAxisTickFormat;
+ yAxisTickFormat = _;
+ return yAxis;
+ };
+
+ //============================================================
+
+ return chart;
+}
+})(); \ No newline at end of file
diff --git a/src/fauxton/assets/js/libs/require.js b/src/fauxton/assets/js/libs/require.js
new file mode 100644
index 000000000..0c6f15280
--- /dev/null
+++ b/src/fauxton/assets/js/libs/require.js
@@ -0,0 +1,2037 @@
+/** vim: et:ts=4:sw=4:sts=4
+ * @license RequireJS 2.0.2 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/jrburke/requirejs for details
+ */
+/*jslint regexp: true, nomen: true */
+/*global window, navigator, document, importScripts, jQuery, setTimeout, opera */
+
+var requirejs, require, define;
+(function (global) {
+ 'use strict';
+
+ var version = '2.0.2',
+ commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
+ cjsRequireRegExp = /require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
+ jsSuffixRegExp = /\.js$/,
+ currDirRegExp = /^\.\//,
+ ostring = Object.prototype.toString,
+ ap = Array.prototype,
+ aps = ap.slice,
+ apsp = ap.splice,
+ isBrowser = !!(typeof window !== 'undefined' && navigator && document),
+ isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
+ //PS3 indicates loaded and complete, but need to wait for complete
+ //specifically. Sequence is 'loading', 'loaded', execution,
+ // then 'complete'. The UA check is unfortunate, but not sure how
+ //to feature test w/o causing perf issues.
+ readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ?
+ /^complete$/ : /^(complete|loaded)$/,
+ defContextName = '_',
+ //Oh the tragedy, detecting opera. See the usage of isOpera for reason.
+ isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]',
+ contexts = {},
+ cfg = {},
+ globalDefQueue = [],
+ useInteractive = false,
+ req, s, head, baseElement, dataMain, src,
+ interactiveScript, currentlyAddingScript, mainScript, subPath;
+
+ function isFunction(it) {
+ return ostring.call(it) === '[object Function]';
+ }
+
+ function isArray(it) {
+ return ostring.call(it) === '[object Array]';
+ }
+
+ /**
+ * Helper function for iterating over an array. If the func returns
+ * a true value, it will break out of the loop.
+ */
+ function each(ary, func) {
+ if (ary) {
+ var i;
+ for (i = 0; i < ary.length; i += 1) {
+ if (ary[i] && func(ary[i], i, ary)) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper function for iterating over an array backwards. If the func
+ * returns a true value, it will break out of the loop.
+ */
+ function eachReverse(ary, func) {
+ if (ary) {
+ var i;
+ for (i = ary.length - 1; i > -1; i -= 1) {
+ if (ary[i] && func(ary[i], i, ary)) {
+ break;
+ }
+ }
+ }
+ }
+
+ function hasProp(obj, prop) {
+ return obj.hasOwnProperty(prop);
+ }
+
+ /**
+ * Cycles over properties in an object and calls a function for each
+ * property value. If the function returns a truthy value, then the
+ * iteration is stopped.
+ */
+ function eachProp(obj, func) {
+ var prop;
+ for (prop in obj) {
+ if (obj.hasOwnProperty(prop)) {
+ if (func(obj[prop], prop)) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Simple function to mix in properties from source into target,
+ * but only if target does not already have a property of the same name.
+ * This is not robust in IE for transferring methods that match
+ * Object.prototype names, but the uses of mixin here seem unlikely to
+ * trigger a problem related to that.
+ */
+ function mixin(target, source, force, deepStringMixin) {
+ if (source) {
+ eachProp(source, function (value, prop) {
+ if (force || !hasProp(target, prop)) {
+ if (deepStringMixin && typeof value !== 'string') {
+ if (!target[prop]) {
+ target[prop] = {};
+ }
+ mixin(target[prop], value, force, deepStringMixin);
+ } else {
+ target[prop] = value;
+ }
+ }
+ });
+ }
+ return target;
+ }
+
+ //Similar to Function.prototype.bind, but the 'this' object is specified
+ //first, since it is easier to read/figure out what 'this' will be.
+ function bind(obj, fn) {
+ return function () {
+ return fn.apply(obj, arguments);
+ };
+ }
+
+ function scripts() {
+ return document.getElementsByTagName('script');
+ }
+
+ //Allow getting a global that expressed in
+ //dot notation, like 'a.b.c'.
+ function getGlobal(value) {
+ if (!value) {
+ return value;
+ }
+ var g = global;
+ each(value.split('.'), function (part) {
+ g = g[part];
+ });
+ return g;
+ }
+
+ function makeContextModuleFunc(func, relMap, enableBuildCallback) {
+ return function () {
+ //A version of a require function that passes a moduleName
+ //value for items that may need to
+ //look up paths relative to the moduleName
+ var args = aps.call(arguments, 0), lastArg;
+ if (enableBuildCallback &&
+ isFunction((lastArg = args[args.length - 1]))) {
+ lastArg.__requireJsBuild = true;
+ }
+ args.push(relMap);
+ return func.apply(null, args);
+ };
+ }
+
+ function addRequireMethods(req, context, relMap) {
+ each([
+ ['toUrl'],
+ ['undef'],
+ ['defined', 'requireDefined'],
+ ['specified', 'requireSpecified']
+ ], function (item) {
+ var prop = item[1] || item[0];
+ req[item[0]] = context ? makeContextModuleFunc(context[prop], relMap) :
+ //If no context, then use default context. Reference from
+ //contexts instead of early binding to default context, so
+ //that during builds, the latest instance of the default
+ //context with its config gets used.
+ function () {
+ var ctx = contexts[defContextName];
+ return ctx[prop].apply(ctx, arguments);
+ };
+ });
+ }
+
+ /**
+ * Constructs an error with a pointer to an URL with more information.
+ * @param {String} id the error ID that maps to an ID on a web page.
+ * @param {String} message human readable error.
+ * @param {Error} [err] the original error, if there is one.
+ *
+ * @returns {Error}
+ */
+ function makeError(id, msg, err, requireModules) {
+ var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id);
+ e.requireType = id;
+ e.requireModules = requireModules;
+ if (err) {
+ e.originalError = err;
+ }
+ return e;
+ }
+
+ if (typeof define !== 'undefined') {
+ //If a define is already in play via another AMD loader,
+ //do not overwrite.
+ return;
+ }
+
+ if (typeof requirejs !== 'undefined') {
+ if (isFunction(requirejs)) {
+ //Do not overwrite and existing requirejs instance.
+ return;
+ }
+ cfg = requirejs;
+ requirejs = undefined;
+ }
+
+ //Allow for a require config object
+ if (typeof require !== 'undefined' && !isFunction(require)) {
+ //assume it is a config object.
+ cfg = require;
+ require = undefined;
+ }
+
+ function newContext(contextName) {
+ var config = {
+ waitSeconds: 7,
+ baseUrl: './',
+ paths: {},
+ pkgs: {},
+ shim: {}
+ },
+ registry = {},
+ undefEvents = {},
+ defQueue = [],
+ defined = {},
+ urlFetched = {},
+ requireCounter = 1,
+ unnormalizedCounter = 1,
+ //Used to track the order in which modules
+ //should be executed, by the order they
+ //load. Important for consistent cycle resolution
+ //behavior.
+ waitAry = [],
+ inCheckLoaded, Module, context, handlers,
+ checkLoadedTimeoutId;
+
+ /**
+ * Trims the . and .. from an array of path segments.
+ * It will keep a leading path segment if a .. will become
+ * the first path segment, to help with module name lookups,
+ * which act like paths, but can be remapped. But the end result,
+ * all paths that use this function should look normalized.
+ * NOTE: this method MODIFIES the input array.
+ * @param {Array} ary the array of path segments.
+ */
+ function trimDots(ary) {
+ var i, part;
+ for (i = 0; ary[i]; i+= 1) {
+ part = ary[i];
+ if (part === '.') {
+ ary.splice(i, 1);
+ i -= 1;
+ } else if (part === '..') {
+ if (i === 1 && (ary[2] === '..' || ary[0] === '..')) {
+ //End of the line. Keep at least one non-dot
+ //path segment at the front so it can be mapped
+ //correctly to disk. Otherwise, there is likely
+ //no path mapping for a path starting with '..'.
+ //This can still fail, but catches the most reasonable
+ //uses of ..
+ break;
+ } else if (i > 0) {
+ ary.splice(i - 1, 2);
+ i -= 2;
+ }
+ }
+ }
+ }
+
+ /**
+ * Given a relative module name, like ./something, normalize it to
+ * a real name that can be mapped to a path.
+ * @param {String} name the relative name
+ * @param {String} baseName a real name that the name arg is relative
+ * to.
+ * @param {Boolean} applyMap apply the map config to the value. Should
+ * only be done if this normalization is for a dependency ID.
+ * @returns {String} normalized name
+ */
+ function normalize(name, baseName, applyMap) {
+ var baseParts = baseName && baseName.split('/'),
+ map = config.map,
+ starMap = map && map['*'],
+ pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment,
+ foundMap;
+
+ //Adjust any relative paths.
+ if (name && name.charAt(0) === '.') {
+ //If have a base name, try to normalize against it,
+ //otherwise, assume it is a top-level require that will
+ //be relative to baseUrl in the end.
+ if (baseName) {
+ if (config.pkgs[baseName]) {
+ //If the baseName is a package name, then just treat it as one
+ //name to concat the name with.
+ baseParts = [baseName];
+ } else {
+ //Convert baseName to array, and lop off the last part,
+ //so that . matches that 'directory' and not name of the baseName's
+ //module. For instance, baseName of 'one/two/three', maps to
+ //'one/two/three.js', but we want the directory, 'one/two' for
+ //this normalization.
+ baseParts = baseParts.slice(0, baseParts.length - 1);
+ }
+
+ name = baseParts.concat(name.split('/'));
+ trimDots(name);
+
+ //Some use of packages may use a . path to reference the
+ //'main' module name, so normalize for that.
+ pkgConfig = config.pkgs[(pkgName = name[0])];
+ name = name.join('/');
+ if (pkgConfig && name === pkgName + '/' + pkgConfig.main) {
+ name = pkgName;
+ }
+ } else if (name.indexOf('./') === 0) {
+ // No baseName, so this is ID is resolved relative
+ // to baseUrl, pull off the leading dot.
+ name = name.substring(2);
+ }
+ }
+
+ //Apply map config if available.
+ if (applyMap && (baseParts || starMap) && map) {
+ nameParts = name.split('/');
+
+ for (i = nameParts.length; i > 0; i -= 1) {
+ nameSegment = nameParts.slice(0, i).join('/');
+
+ if (baseParts) {
+ //Find the longest baseName segment match in the config.
+ //So, do joins on the biggest to smallest lengths of baseParts.
+ for (j = baseParts.length; j > 0; j -= 1) {
+ mapValue = map[baseParts.slice(0, j).join('/')];
+
+ //baseName segment has config, find if it has one for
+ //this name.
+ if (mapValue) {
+ mapValue = mapValue[nameSegment];
+ if (mapValue) {
+ //Match, update name to the new value.
+ foundMap = mapValue;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!foundMap && starMap && starMap[nameSegment]) {
+ foundMap = starMap[nameSegment];
+ }
+
+ if (foundMap) {
+ nameParts.splice(0, i, foundMap);
+ name = nameParts.join('/');
+ break;
+ }
+ }
+ }
+
+ return name;
+ }
+
+ function removeScript(name) {
+ if (isBrowser) {
+ each(scripts(), function (scriptNode) {
+ if (scriptNode.getAttribute('data-requiremodule') === name &&
+ scriptNode.getAttribute('data-requirecontext') === context.contextName) {
+ scriptNode.parentNode.removeChild(scriptNode);
+ return true;
+ }
+ });
+ }
+ }
+
+ function hasPathFallback(id) {
+ var pathConfig = config.paths[id];
+ if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) {
+ removeScript(id);
+ //Pop off the first array value, since it failed, and
+ //retry
+ pathConfig.shift();
+ context.undef(id);
+ context.require([id]);
+ return true;
+ }
+ }
+
+ /**
+ * Creates a module mapping that includes plugin prefix, module
+ * name, and path. If parentModuleMap is provided it will
+ * also normalize the name via require.normalize()
+ *
+ * @param {String} name the module name
+ * @param {String} [parentModuleMap] parent module map
+ * for the module name, used to resolve relative names.
+ * @param {Boolean} isNormalized: is the ID already normalized.
+ * This is true if this call is done for a define() module ID.
+ * @param {Boolean} applyMap: apply the map config to the ID.
+ * Should only be true if this map is for a dependency.
+ *
+ * @returns {Object}
+ */
+ function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
+ var index = name ? name.indexOf('!') : -1,
+ prefix = null,
+ parentName = parentModuleMap ? parentModuleMap.name : null,
+ originalName = name,
+ isDefine = true,
+ normalizedName = '',
+ url, pluginModule, suffix;
+
+ //If no name, then it means it is a require call, generate an
+ //internal name.
+ if (!name) {
+ isDefine = false;
+ name = '_@r' + (requireCounter += 1);
+ }
+
+ if (index !== -1) {
+ prefix = name.substring(0, index);
+ name = name.substring(index + 1, name.length);
+ }
+
+ if (prefix) {
+ prefix = normalize(prefix, parentName, applyMap);
+ pluginModule = defined[prefix];
+ }
+
+ //Account for relative paths if there is a base name.
+ if (name) {
+ if (prefix) {
+ if (pluginModule && pluginModule.normalize) {
+ //Plugin is loaded, use its normalize method.
+ normalizedName = pluginModule.normalize(name, function (name) {
+ return normalize(name, parentName, applyMap);
+ });
+ } else {
+ normalizedName = normalize(name, parentName, applyMap);
+ }
+ } else {
+ //A regular module.
+ normalizedName = normalize(name, parentName, applyMap);
+
+ //Calculate url for the module, if it has a name.
+ //Use name here since nameToUrl also calls normalize,
+ //and for relative names that are outside the baseUrl
+ //this causes havoc. Was thinking of just removing
+ //parentModuleMap to avoid extra normalization, but
+ //normalize() still does a dot removal because of
+ //issue #142, so just pass in name here and redo
+ //the normalization. Paths outside baseUrl are just
+ //messy to support.
+ url = context.nameToUrl(name, null, parentModuleMap);
+ }
+ }
+
+ //If the id is a plugin id that cannot be determined if it needs
+ //normalization, stamp it with a unique ID so two matching relative
+ //ids that may conflict can be separate.
+ suffix = prefix && !pluginModule && !isNormalized ?
+ '_unnormalized' + (unnormalizedCounter += 1) :
+ '';
+
+ return {
+ prefix: prefix,
+ name: normalizedName,
+ parentMap: parentModuleMap,
+ unnormalized: !!suffix,
+ url: url,
+ originalName: originalName,
+ isDefine: isDefine,
+ id: (prefix ?
+ prefix + '!' + normalizedName :
+ normalizedName) + suffix
+ };
+ }
+
+ function getModule(depMap) {
+ var id = depMap.id,
+ mod = registry[id];
+
+ if (!mod) {
+ mod = registry[id] = new context.Module(depMap);
+ }
+
+ return mod;
+ }
+
+ function on(depMap, name, fn) {
+ var id = depMap.id,
+ mod = registry[id];
+
+ if (hasProp(defined, id) &&
+ (!mod || mod.defineEmitComplete)) {
+ if (name === 'defined') {
+ fn(defined[id]);
+ }
+ } else {
+ getModule(depMap).on(name, fn);
+ }
+ }
+
+ function onError(err, errback) {
+ var ids = err.requireModules,
+ notified = false;
+
+ if (errback) {
+ errback(err);
+ } else {
+ each(ids, function (id) {
+ var mod = registry[id];
+ if (mod) {
+ //Set error on module, so it skips timeout checks.
+ mod.error = err;
+ if (mod.events.error) {
+ notified = true;
+ mod.emit('error', err);
+ }
+ }
+ });
+
+ if (!notified) {
+ req.onError(err);
+ }
+ }
+ }
+
+ /**
+ * Internal method to transfer globalQueue items to this context's
+ * defQueue.
+ */
+ function takeGlobalQueue() {
+ //Push all the globalDefQueue items into the context's defQueue
+ if (globalDefQueue.length) {
+ //Array splice in the values since the context code has a
+ //local var ref to defQueue, so cannot just reassign the one
+ //on context.
+ apsp.apply(defQueue,
+ [defQueue.length - 1, 0].concat(globalDefQueue));
+ globalDefQueue = [];
+ }
+ }
+
+ /**
+ * Helper function that creates a require function object to give to
+ * modules that ask for it as a dependency. It needs to be specific
+ * per module because of the implication of path mappings that may
+ * need to be relative to the module name.
+ */
+ function makeRequire(mod, enableBuildCallback, altRequire) {
+ var relMap = mod && mod.map,
+ modRequire = makeContextModuleFunc(altRequire || context.require,
+ relMap,
+ enableBuildCallback);
+
+ addRequireMethods(modRequire, context, relMap);
+ modRequire.isBrowser = isBrowser;
+
+ return modRequire;
+ }
+
+ handlers = {
+ 'require': function (mod) {
+ return makeRequire(mod);
+ },
+ 'exports': function (mod) {
+ mod.usingExports = true;
+ if (mod.map.isDefine) {
+ return (mod.exports = defined[mod.map.id] = {});
+ }
+ },
+ 'module': function (mod) {
+ return (mod.module = {
+ id: mod.map.id,
+ uri: mod.map.url,
+ config: function () {
+ return (config.config && config.config[mod.map.id]) || {};
+ },
+ exports: defined[mod.map.id]
+ });
+ }
+ };
+
+ function removeWaiting(id) {
+ //Clean up machinery used for waiting modules.
+ delete registry[id];
+
+ each(waitAry, function (mod, i) {
+ if (mod.map.id === id) {
+ waitAry.splice(i, 1);
+ if (!mod.defined) {
+ context.waitCount -= 1;
+ }
+ return true;
+ }
+ });
+ }
+
+ function findCycle(mod, traced) {
+ var id = mod.map.id,
+ depArray = mod.depMaps,
+ foundModule;
+
+ //Do not bother with unitialized modules or not yet enabled
+ //modules.
+ if (!mod.inited) {
+ return;
+ }
+
+ //Found the cycle.
+ if (traced[id]) {
+ return mod;
+ }
+
+ traced[id] = true;
+
+ //Trace through the dependencies.
+ each(depArray, function (depMap) {
+ var depId = depMap.id,
+ depMod = registry[depId];
+
+ if (!depMod) {
+ return;
+ }
+
+ if (!depMod.inited || !depMod.enabled) {
+ //Dependency is not inited, so this cannot
+ //be used to determine a cycle.
+ foundModule = null;
+ delete traced[id];
+ return true;
+ }
+
+ //mixin traced to a new object for each dependency, so that
+ //sibling dependencies in this object to not generate a
+ //false positive match on a cycle. Ideally an Object.create
+ //type of prototype delegation would be used here, but
+ //optimizing for file size vs. execution speed since hopefully
+ //the trees are small for circular dependency scans relative
+ //to the full app perf.
+ return (foundModule = findCycle(depMod, mixin({}, traced)));
+ });
+
+ return foundModule;
+ }
+
+ function forceExec(mod, traced, uninited) {
+ var id = mod.map.id,
+ depArray = mod.depMaps;
+
+ if (!mod.inited || !mod.map.isDefine) {
+ return;
+ }
+
+ if (traced[id]) {
+ return defined[id];
+ }
+
+ traced[id] = mod;
+
+ each(depArray, function(depMap) {
+ var depId = depMap.id,
+ depMod = registry[depId],
+ value;
+
+ if (handlers[depId]) {
+ return;
+ }
+
+ if (depMod) {
+ if (!depMod.inited || !depMod.enabled) {
+ //Dependency is not inited,
+ //so this module cannot be
+ //given a forced value yet.
+ uninited[id] = true;
+ return;
+ }
+
+ //Get the value for the current dependency
+ value = forceExec(depMod, traced, uninited);
+
+ //Even with forcing it may not be done,
+ //in particular if the module is waiting
+ //on a plugin resource.
+ if (!uninited[depId]) {
+ mod.defineDepById(depId, value);
+ }
+ }
+ });
+
+ mod.check(true);
+
+ return defined[id];
+ }
+
+ function modCheck(mod) {
+ mod.check();
+ }
+
+ function checkLoaded() {
+ var waitInterval = config.waitSeconds * 1000,
+ //It is possible to disable the wait interval by using waitSeconds of 0.
+ expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
+ noLoads = [],
+ stillLoading = false,
+ needCycleCheck = true,
+ map, modId, err, usingPathFallback;
+
+ //Do not bother if this call was a result of a cycle break.
+ if (inCheckLoaded) {
+ return;
+ }
+
+ inCheckLoaded = true;
+
+ //Figure out the state of all the modules.
+ eachProp(registry, function (mod) {
+ map = mod.map;
+ modId = map.id;
+
+ //Skip things that are not enabled or in error state.
+ if (!mod.enabled) {
+ return;
+ }
+
+ if (!mod.error) {
+ //If the module should be executed, and it has not
+ //been inited and time is up, remember it.
+ if (!mod.inited && expired) {
+ if (hasPathFallback(modId)) {
+ usingPathFallback = true;
+ stillLoading = true;
+ } else {
+ noLoads.push(modId);
+ removeScript(modId);
+ }
+ } else if (!mod.inited && mod.fetched && map.isDefine) {
+ stillLoading = true;
+ if (!map.prefix) {
+ //No reason to keep looking for unfinished
+ //loading. If the only stillLoading is a
+ //plugin resource though, keep going,
+ //because it may be that a plugin resource
+ //is waiting on a non-plugin cycle.
+ return (needCycleCheck = false);
+ }
+ }
+ }
+ });
+
+ if (expired && noLoads.length) {
+ //If wait time expired, throw error of unloaded modules.
+ err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
+ err.contextName = context.contextName;
+ return onError(err);
+ }
+
+ //Not expired, check for a cycle.
+ if (needCycleCheck) {
+
+ each(waitAry, function (mod) {
+ if (mod.defined) {
+ return;
+ }
+
+ var cycleMod = findCycle(mod, {}),
+ traced = {};
+
+ if (cycleMod) {
+ forceExec(cycleMod, traced, {});
+
+ //traced modules may have been
+ //removed from the registry, but
+ //their listeners still need to
+ //be called.
+ eachProp(traced, modCheck);
+ }
+ });
+
+ //Now that dependencies have
+ //been satisfied, trigger the
+ //completion check that then
+ //notifies listeners.
+ eachProp(registry, modCheck);
+ }
+
+ //If still waiting on loads, and the waiting load is something
+ //other than a plugin resource, or there are still outstanding
+ //scripts, then just try back later.
+ if ((!expired || usingPathFallback) && stillLoading) {
+ //Something is still waiting to load. Wait for it, but only
+ //if a timeout is not already in effect.
+ if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
+ checkLoadedTimeoutId = setTimeout(function () {
+ checkLoadedTimeoutId = 0;
+ checkLoaded();
+ }, 50);
+ }
+ }
+
+ inCheckLoaded = false;
+ }
+
+ Module = function (map) {
+ this.events = undefEvents[map.id] || {};
+ this.map = map;
+ this.shim = config.shim[map.id];
+ this.depExports = [];
+ this.depMaps = [];
+ this.depMatched = [];
+ this.pluginMaps = {};
+ this.depCount = 0;
+
+ /* this.exports this.factory
+ this.depMaps = [],
+ this.enabled, this.fetched
+ */
+ };
+
+ Module.prototype = {
+ init: function(depMaps, factory, errback, options) {
+ options = options || {};
+
+ //Do not do more inits if already done. Can happen if there
+ //are multiple define calls for the same module. That is not
+ //a normal, common case, but it is also not unexpected.
+ if (this.inited) {
+ return;
+ }
+
+ this.factory = factory;
+
+ if (errback) {
+ //Register for errors on this module.
+ this.on('error', errback);
+ } else if (this.events.error) {
+ //If no errback already, but there are error listeners
+ //on this module, set up an errback to pass to the deps.
+ errback = bind(this, function (err) {
+ this.emit('error', err);
+ });
+ }
+
+ //Do a copy of the dependency array, so that
+ //source inputs are not modified. For example
+ //"shim" deps are passed in here directly, and
+ //doing a direct modification of the depMaps array
+ //would affect that config.
+ this.depMaps = depMaps && depMaps.slice(0);
+ this.depMaps.rjsSkipMap = depMaps.rjsSkipMap;
+
+ this.errback = errback;
+
+ //Indicate this module has be initialized
+ this.inited = true;
+
+ this.ignore = options.ignore;
+
+ //Could have option to init this module in enabled mode,
+ //or could have been previously marked as enabled. However,
+ //the dependencies are not known until init is called. So
+ //if enabled previously, now trigger dependencies as enabled.
+ if (options.enabled || this.enabled) {
+ //Enable this module and dependencies.
+ //Will call this.check()
+ this.enable();
+ } else {
+ this.check();
+ }
+ },
+
+ defineDepById: function (id, depExports) {
+ var i;
+
+ //Find the index for this dependency.
+ each(this.depMaps, function (map, index) {
+ if (map.id === id) {
+ i = index;
+ return true;
+ }
+ });
+
+ return this.defineDep(i, depExports);
+ },
+
+ defineDep: function (i, depExports) {
+ //Because of cycles, defined callback for a given
+ //export can be called more than once.
+ if (!this.depMatched[i]) {
+ this.depMatched[i] = true;
+ this.depCount -= 1;
+ this.depExports[i] = depExports;
+ }
+ },
+
+ fetch: function () {
+ if (this.fetched) {
+ return;
+ }
+ this.fetched = true;
+
+ context.startTime = (new Date()).getTime();
+
+ var map = this.map;
+
+ //If the manager is for a plugin managed resource,
+ //ask the plugin to load it now.
+ if (this.shim) {
+ makeRequire(this, true)(this.shim.deps || [], bind(this, function () {
+ return map.prefix ? this.callPlugin() : this.load();
+ }));
+ } else {
+ //Regular dependency.
+ return map.prefix ? this.callPlugin() : this.load();
+ }
+ },
+
+ load: function() {
+ var url = this.map.url;
+
+ //Regular dependency.
+ if (!urlFetched[url]) {
+ urlFetched[url] = true;
+ context.load(this.map.id, url);
+ }
+ },
+
+ /**
+ * Checks is the module is ready to define itself, and if so,
+ * define it. If the silent argument is true, then it will just
+ * define, but not notify listeners, and not ask for a context-wide
+ * check of all loaded modules. That is useful for cycle breaking.
+ */
+ check: function (silent) {
+ if (!this.enabled || this.enabling) {
+ return;
+ }
+
+ var id = this.map.id,
+ depExports = this.depExports,
+ exports = this.exports,
+ factory = this.factory,
+ err, cjsModule;
+
+ if (!this.inited) {
+ this.fetch();
+ } else if (this.error) {
+ this.emit('error', this.error);
+ } else if (!this.defining) {
+ //The factory could trigger another require call
+ //that would result in checking this module to
+ //define itself again. If already in the process
+ //of doing that, skip this work.
+ this.defining = true;
+
+ if (this.depCount < 1 && !this.defined) {
+ if (isFunction(factory)) {
+ //If there is an error listener, favor passing
+ //to that instead of throwing an error.
+ if (this.events.error) {
+ try {
+ exports = context.execCb(id, factory, depExports, exports);
+ } catch (e) {
+ err = e;
+ }
+ } else {
+ exports = context.execCb(id, factory, depExports, exports);
+ }
+
+ if (this.map.isDefine) {
+ //If setting exports via 'module' is in play,
+ //favor that over return value and exports. After that,
+ //favor a non-undefined return value over exports use.
+ cjsModule = this.module;
+ if (cjsModule &&
+ cjsModule.exports !== undefined &&
+ //Make sure it is not already the exports value
+ cjsModule.exports !== this.exports) {
+ exports = cjsModule.exports;
+ } else if (exports === undefined && this.usingExports) {
+ //exports already set the defined value.
+ exports = this.exports;
+ }
+ }
+
+ if (err) {
+ err.requireMap = this.map;
+ err.requireModules = [this.map.id];
+ err.requireType = 'define';
+ return onError((this.error = err));
+ }
+
+ } else {
+ //Just a literal value
+ exports = factory;
+ }
+
+ this.exports = exports;
+
+ if (this.map.isDefine && !this.ignore) {
+ defined[id] = exports;
+
+ if (req.onResourceLoad) {
+ req.onResourceLoad(context, this.map, this.depMaps);
+ }
+ }
+
+ //Clean up
+ delete registry[id];
+
+ this.defined = true;
+ context.waitCount -= 1;
+ if (context.waitCount === 0) {
+ //Clear the wait array used for cycles.
+ waitAry = [];
+ }
+ }
+
+ //Finished the define stage. Allow calling check again
+ //to allow define notifications below in the case of a
+ //cycle.
+ this.defining = false;
+
+ if (!silent) {
+ if (this.defined && !this.defineEmitted) {
+ this.defineEmitted = true;
+ this.emit('defined', this.exports);
+ this.defineEmitComplete = true;
+ }
+ }
+ }
+ },
+
+ callPlugin: function() {
+ var map = this.map,
+ id = map.id,
+ pluginMap = makeModuleMap(map.prefix, null, false, true);
+
+ on(pluginMap, 'defined', bind(this, function (plugin) {
+ var name = this.map.name,
+ parentName = this.map.parentMap ? this.map.parentMap.name : null,
+ load, normalizedMap, normalizedMod;
+
+ //If current map is not normalized, wait for that
+ //normalized name to load instead of continuing.
+ if (this.map.unnormalized) {
+ //Normalize the ID if the plugin allows it.
+ if (plugin.normalize) {
+ name = plugin.normalize(name, function (name) {
+ return normalize(name, parentName, true);
+ }) || '';
+ }
+
+ normalizedMap = makeModuleMap(map.prefix + '!' + name,
+ this.map.parentMap,
+ false,
+ true);
+ on(normalizedMap,
+ 'defined', bind(this, function (value) {
+ this.init([], function () { return value; }, null, {
+ enabled: true,
+ ignore: true
+ });
+ }));
+ normalizedMod = registry[normalizedMap.id];
+ if (normalizedMod) {
+ if (this.events.error) {
+ normalizedMod.on('error', bind(this, function (err) {
+ this.emit('error', err);
+ }));
+ }
+ normalizedMod.enable();
+ }
+
+ return;
+ }
+
+ load = bind(this, function (value) {
+ this.init([], function () { return value; }, null, {
+ enabled: true
+ });
+ });
+
+ load.error = bind(this, function (err) {
+ this.inited = true;
+ this.error = err;
+ err.requireModules = [id];
+
+ //Remove temp unnormalized modules for this module,
+ //since they will never be resolved otherwise now.
+ eachProp(registry, function (mod) {
+ if (mod.map.id.indexOf(id + '_unnormalized') === 0) {
+ removeWaiting(mod.map.id);
+ }
+ });
+
+ onError(err);
+ });
+
+ //Allow plugins to load other code without having to know the
+ //context or how to 'complete' the load.
+ load.fromText = function (moduleName, text) {
+ /*jslint evil: true */
+ var hasInteractive = useInteractive;
+
+ //Turn off interactive script matching for IE for any define
+ //calls in the text, then turn it back on at the end.
+ if (hasInteractive) {
+ useInteractive = false;
+ }
+
+ //Prime the system by creating a module instance for
+ //it.
+ getModule(makeModuleMap(moduleName));
+
+ req.exec(text);
+
+ if (hasInteractive) {
+ useInteractive = true;
+ }
+
+ //Support anonymous modules.
+ context.completeLoad(moduleName);
+ };
+
+ //Use parentName here since the plugin's name is not reliable,
+ //could be some weird string with no path that actually wants to
+ //reference the parentName's path.
+ plugin.load(map.name, makeRequire(map.parentMap, true, function (deps, cb) {
+ deps.rjsSkipMap = true;
+ return context.require(deps, cb);
+ }), load, config);
+ }));
+
+ context.enable(pluginMap, this);
+ this.pluginMaps[pluginMap.id] = pluginMap;
+ },
+
+ enable: function () {
+ this.enabled = true;
+
+ if (!this.waitPushed) {
+ waitAry.push(this);
+ context.waitCount += 1;
+ this.waitPushed = true;
+ }
+
+ //Set flag mentioning that the module is enabling,
+ //so that immediate calls to the defined callbacks
+ //for dependencies do not trigger inadvertent load
+ //with the depCount still being zero.
+ this.enabling = true;
+
+ //Enable each dependency
+ each(this.depMaps, bind(this, function (depMap, i) {
+ var id, mod, handler;
+
+ if (typeof depMap === 'string') {
+ //Dependency needs to be converted to a depMap
+ //and wired up to this module.
+ depMap = makeModuleMap(depMap,
+ (this.map.isDefine ? this.map : this.map.parentMap),
+ false,
+ !this.depMaps.rjsSkipMap);
+ this.depMaps[i] = depMap;
+
+ handler = handlers[depMap.id];
+
+ if (handler) {
+ this.depExports[i] = handler(this);
+ return;
+ }
+
+ this.depCount += 1;
+
+ on(depMap, 'defined', bind(this, function (depExports) {
+ this.defineDep(i, depExports);
+ this.check();
+ }));
+
+ if (this.errback) {
+ on(depMap, 'error', this.errback);
+ }
+ }
+
+ id = depMap.id;
+ mod = registry[id];
+
+ //Skip special modules like 'require', 'exports', 'module'
+ //Also, don't call enable if it is already enabled,
+ //important in circular dependency cases.
+ if (!handlers[id] && mod && !mod.enabled) {
+ context.enable(depMap, this);
+ }
+ }));
+
+ //Enable each plugin that is used in
+ //a dependency
+ eachProp(this.pluginMaps, bind(this, function (pluginMap) {
+ var mod = registry[pluginMap.id];
+ if (mod && !mod.enabled) {
+ context.enable(pluginMap, this);
+ }
+ }));
+
+ this.enabling = false;
+
+ this.check();
+ },
+
+ on: function(name, cb) {
+ var cbs = this.events[name];
+ if (!cbs) {
+ cbs = this.events[name] = [];
+ }
+ cbs.push(cb);
+ },
+
+ emit: function (name, evt) {
+ each(this.events[name], function (cb) {
+ cb(evt);
+ });
+ if (name === 'error') {
+ //Now that the error handler was triggered, remove
+ //the listeners, since this broken Module instance
+ //can stay around for a while in the registry/waitAry.
+ delete this.events[name];
+ }
+ }
+ };
+
+ function callGetModule(args) {
+ getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);
+ }
+
+ function removeListener(node, func, name, ieName) {
+ //Favor detachEvent because of IE9
+ //issue, see attachEvent/addEventListener comment elsewhere
+ //in this file.
+ if (node.detachEvent && !isOpera) {
+ //Probably IE. If not it will throw an error, which will be
+ //useful to know.
+ if (ieName) {
+ node.detachEvent(ieName, func);
+ }
+ } else {
+ node.removeEventListener(name, func, false);
+ }
+ }
+
+ /**
+ * Given an event from a script node, get the requirejs info from it,
+ * and then removes the event listeners on the node.
+ * @param {Event} evt
+ * @returns {Object}
+ */
+ function getScriptData(evt) {
+ //Using currentTarget instead of target for Firefox 2.0's sake. Not
+ //all old browsers will be supported, but this one was easy enough
+ //to support and still makes sense.
+ var node = evt.currentTarget || evt.srcElement;
+
+ //Remove the listeners once here.
+ removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange');
+ removeListener(node, context.onScriptError, 'error');
+
+ return {
+ node: node,
+ id: node && node.getAttribute('data-requiremodule')
+ };
+ }
+
+ return (context = {
+ config: config,
+ contextName: contextName,
+ registry: registry,
+ defined: defined,
+ urlFetched: urlFetched,
+ waitCount: 0,
+ defQueue: defQueue,
+ Module: Module,
+ makeModuleMap: makeModuleMap,
+
+ /**
+ * Set a configuration for the context.
+ * @param {Object} cfg config object to integrate.
+ */
+ configure: function (cfg) {
+ //Make sure the baseUrl ends in a slash.
+ if (cfg.baseUrl) {
+ if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
+ cfg.baseUrl += '/';
+ }
+ }
+
+ //Save off the paths and packages since they require special processing,
+ //they are additive.
+ var pkgs = config.pkgs,
+ shim = config.shim,
+ paths = config.paths,
+ map = config.map;
+
+ //Mix in the config values, favoring the new values over
+ //existing ones in context.config.
+ mixin(config, cfg, true);
+
+ //Merge paths.
+ config.paths = mixin(paths, cfg.paths, true);
+
+ //Merge map
+ if (cfg.map) {
+ config.map = mixin(map || {}, cfg.map, true, true);
+ }
+
+ //Merge shim
+ if (cfg.shim) {
+ eachProp(cfg.shim, function (value, id) {
+ //Normalize the structure
+ if (isArray(value)) {
+ value = {
+ deps: value
+ };
+ }
+ if (value.exports && !value.exports.__buildReady) {
+ value.exports = context.makeShimExports(value.exports);
+ }
+ shim[id] = value;
+ });
+ config.shim = shim;
+ }
+
+ //Adjust packages if necessary.
+ if (cfg.packages) {
+ each(cfg.packages, function (pkgObj) {
+ var location;
+
+ pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
+ location = pkgObj.location;
+
+ //Create a brand new object on pkgs, since currentPackages can
+ //be passed in again, and config.pkgs is the internal transformed
+ //state for all package configs.
+ pkgs[pkgObj.name] = {
+ name: pkgObj.name,
+ location: location || pkgObj.name,
+ //Remove leading dot in main, so main paths are normalized,
+ //and remove any trailing .js, since different package
+ //envs have different conventions: some use a module name,
+ //some use a file name.
+ main: (pkgObj.main || 'main')
+ .replace(currDirRegExp, '')
+ .replace(jsSuffixRegExp, '')
+ };
+ });
+
+ //Done with modifications, assing packages back to context config
+ config.pkgs = pkgs;
+ }
+
+ //If there are any "waiting to execute" modules in the registry,
+ //update the maps for them, since their info, like URLs to load,
+ //may have changed.
+ eachProp(registry, function (mod, id) {
+ mod.map = makeModuleMap(id);
+ });
+
+ //If a deps array or a config callback is specified, then call
+ //require with those args. This is useful when require is defined as a
+ //config object before require.js is loaded.
+ if (cfg.deps || cfg.callback) {
+ context.require(cfg.deps || [], cfg.callback);
+ }
+ },
+
+ makeShimExports: function (exports) {
+ var func;
+ if (typeof exports === 'string') {
+ func = function () {
+ return getGlobal(exports);
+ };
+ //Save the exports for use in nodefine checking.
+ func.exports = exports;
+ return func;
+ } else {
+ return function () {
+ return exports.apply(global, arguments);
+ };
+ }
+ },
+
+ requireDefined: function (id, relMap) {
+ return hasProp(defined, makeModuleMap(id, relMap, false, true).id);
+ },
+
+ requireSpecified: function (id, relMap) {
+ id = makeModuleMap(id, relMap, false, true).id;
+ return hasProp(defined, id) || hasProp(registry, id);
+ },
+
+ require: function (deps, callback, errback, relMap) {
+ var moduleName, id, map, requireMod, args;
+ if (typeof deps === 'string') {
+ if (isFunction(callback)) {
+ //Invalid call
+ return onError(makeError('requireargs', 'Invalid require call'), errback);
+ }
+
+ //Synchronous access to one module. If require.get is
+ //available (as in the Node adapter), prefer that.
+ //In this case deps is the moduleName and callback is
+ //the relMap
+ if (req.get) {
+ return req.get(context, deps, callback);
+ }
+
+ //Just return the module wanted. In this scenario, the
+ //second arg (if passed) is just the relMap.
+ moduleName = deps;
+ relMap = callback;
+
+ //Normalize module name, if it contains . or ..
+ map = makeModuleMap(moduleName, relMap, false, true);
+ id = map.id;
+
+ if (!hasProp(defined, id)) {
+ return onError(makeError('notloaded', 'Module name "' +
+ id +
+ '" has not been loaded yet for context: ' +
+ contextName));
+ }
+ return defined[id];
+ }
+
+ //Callback require. Normalize args. if callback or errback is
+ //not a function, it means it is a relMap. Test errback first.
+ if (errback && !isFunction(errback)) {
+ relMap = errback;
+ errback = undefined;
+ }
+ if (callback && !isFunction(callback)) {
+ relMap = callback;
+ callback = undefined;
+ }
+
+ //Any defined modules in the global queue, intake them now.
+ takeGlobalQueue();
+
+ //Make sure any remaining defQueue items get properly processed.
+ while (defQueue.length) {
+ args = defQueue.shift();
+ if (args[0] === null) {
+ return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
+ } else {
+ //args are id, deps, factory. Should be normalized by the
+ //define() function.
+ callGetModule(args);
+ }
+ }
+
+ //Mark all the dependencies as needing to be loaded.
+ requireMod = getModule(makeModuleMap(null, relMap));
+
+ requireMod.init(deps, callback, errback, {
+ enabled: true
+ });
+
+ checkLoaded();
+
+ return context.require;
+ },
+
+ undef: function (id) {
+ var map = makeModuleMap(id, null, true),
+ mod = registry[id];
+
+ delete defined[id];
+ delete urlFetched[map.url];
+ delete undefEvents[id];
+
+ if (mod) {
+ //Hold on to listeners in case the
+ //module will be attempted to be reloaded
+ //using a different config.
+ if (mod.events.defined) {
+ undefEvents[id] = mod.events;
+ }
+
+ removeWaiting(id);
+ }
+ },
+
+ /**
+ * Called to enable a module if it is still in the registry
+ * awaiting enablement. parent module is passed in for context,
+ * used by the optimizer.
+ */
+ enable: function (depMap, parent) {
+ var mod = registry[depMap.id];
+ if (mod) {
+ getModule(depMap).enable();
+ }
+ },
+
+ /**
+ * Internal method used by environment adapters to complete a load event.
+ * A load event could be a script load or just a load pass from a synchronous
+ * load call.
+ * @param {String} moduleName the name of the module to potentially complete.
+ */
+ completeLoad: function (moduleName) {
+ var shim = config.shim[moduleName] || {},
+ shExports = shim.exports && shim.exports.exports,
+ found, args, mod;
+
+ takeGlobalQueue();
+
+ while (defQueue.length) {
+ args = defQueue.shift();
+ if (args[0] === null) {
+ args[0] = moduleName;
+ //If already found an anonymous module and bound it
+ //to this name, then this is some other anon module
+ //waiting for its completeLoad to fire.
+ if (found) {
+ break;
+ }
+ found = true;
+ } else if (args[0] === moduleName) {
+ //Found matching define call for this script!
+ found = true;
+ }
+
+ callGetModule(args);
+ }
+
+ //Do this after the cycle of callGetModule in case the result
+ //of those calls/init calls changes the registry.
+ mod = registry[moduleName];
+
+ if (!found &&
+ !defined[moduleName] &&
+ mod && !mod.inited) {
+ if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
+ if (hasPathFallback(moduleName)) {
+ return;
+ } else {
+ return onError(makeError('nodefine',
+ 'No define call for ' + moduleName,
+ null,
+ [moduleName]));
+ }
+ } else {
+ //A script that does not call define(), so just simulate
+ //the call for it.
+ callGetModule([moduleName, (shim.deps || []), shim.exports]);
+ }
+ }
+
+ checkLoaded();
+ },
+
+ /**
+ * Converts a module name + .extension into an URL path.
+ * *Requires* the use of a module name. It does not support using
+ * plain URLs like nameToUrl.
+ */
+ toUrl: function (moduleNamePlusExt, relModuleMap) {
+ var index = moduleNamePlusExt.lastIndexOf('.'),
+ ext = null;
+
+ if (index !== -1) {
+ ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
+ moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
+ }
+
+ return context.nameToUrl(moduleNamePlusExt, ext, relModuleMap);
+ },
+
+ /**
+ * Converts a module name to a file path. Supports cases where
+ * moduleName may actually be just an URL.
+ */
+ nameToUrl: function (moduleName, ext, relModuleMap) {
+ var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url,
+ parentPath;
+
+ //Normalize module name if have a base relative module name to work from.
+ moduleName = normalize(moduleName, relModuleMap && relModuleMap.id, true);
+
+ //If a colon is in the URL, it indicates a protocol is used and it is just
+ //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?)
+ //or ends with .js, then assume the user meant to use an url and not a module id.
+ //The slash is important for protocol-less URLs as well as full paths.
+ if (req.jsExtRegExp.test(moduleName)) {
+ //Just a plain path, not module name lookup, so just return it.
+ //Add extension if it is included. This is a bit wonky, only non-.js things pass
+ //an extension, this method probably needs to be reworked.
+ url = moduleName + (ext || '');
+ } else {
+ //A module that needs to be converted to a path.
+ paths = config.paths;
+ pkgs = config.pkgs;
+
+ syms = moduleName.split('/');
+ //For each module name segment, see if there is a path
+ //registered for it. Start with most specific name
+ //and work up from it.
+ for (i = syms.length; i > 0; i -= 1) {
+ parentModule = syms.slice(0, i).join('/');
+ pkg = pkgs[parentModule];
+ parentPath = paths[parentModule];
+ if (parentPath) {
+ //If an array, it means there are a few choices,
+ //Choose the one that is desired
+ if (isArray(parentPath)) {
+ parentPath = parentPath[0];
+ }
+ syms.splice(0, i, parentPath);
+ break;
+ } else if (pkg) {
+ //If module name is just the package name, then looking
+ //for the main module.
+ if (moduleName === pkg.name) {
+ pkgPath = pkg.location + '/' + pkg.main;
+ } else {
+ pkgPath = pkg.location;
+ }
+ syms.splice(0, i, pkgPath);
+ break;
+ }
+ }
+
+ //Join the path parts together, then figure out if baseUrl is needed.
+ url = syms.join('/') + (ext || '.js');
+ url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
+ }
+
+ return config.urlArgs ? url +
+ ((url.indexOf('?') === -1 ? '?' : '&') +
+ config.urlArgs) : url;
+ },
+
+ //Delegates to req.load. Broken out as a separate function to
+ //allow overriding in the optimizer.
+ load: function (id, url) {
+ req.load(context, id, url);
+ },
+
+ /**
+ * Executes a module callack function. Broken out as a separate function
+ * solely to allow the build system to sequence the files in the built
+ * layer in the right sequence.
+ *
+ * @private
+ */
+ execCb: function (name, callback, args, exports) {
+ return callback.apply(exports, args);
+ },
+
+ /**
+ * callback for script loads, used to check status of loading.
+ *
+ * @param {Event} evt the event from the browser for the script
+ * that was loaded.
+ */
+ onScriptLoad: function (evt) {
+ //Using currentTarget instead of target for Firefox 2.0's sake. Not
+ //all old browsers will be supported, but this one was easy enough
+ //to support and still makes sense.
+ if (evt.type === 'load' ||
+ (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
+ //Reset interactive script so a script node is not held onto for
+ //to long.
+ interactiveScript = null;
+
+ //Pull out the name of the module and the context.
+ var data = getScriptData(evt);
+ context.completeLoad(data.id);
+ }
+ },
+
+ /**
+ * Callback for script errors.
+ */
+ onScriptError: function (evt) {
+ var data = getScriptData(evt);
+ if (!hasPathFallback(data.id)) {
+ return onError(makeError('scripterror', 'Script error', evt, [data.id]));
+ }
+ }
+ });
+ }
+
+ /**
+ * Main entry point.
+ *
+ * If the only argument to require is a string, then the module that
+ * is represented by that string is fetched for the appropriate context.
+ *
+ * If the first argument is an array, then it will be treated as an array
+ * of dependency string names to fetch. An optional function callback can
+ * be specified to execute when all of those dependencies are available.
+ *
+ * Make a local req variable to help Caja compliance (it assumes things
+ * on a require that are not standardized), and to give a short
+ * name for minification/local scope use.
+ */
+ req = requirejs = function (deps, callback, errback, optional) {
+
+ //Find the right context, use default
+ var contextName = defContextName,
+ context, config;
+
+ // Determine if have config object in the call.
+ if (!isArray(deps) && typeof deps !== 'string') {
+ // deps is a config object
+ config = deps;
+ if (isArray(callback)) {
+ // Adjust args if there are dependencies
+ deps = callback;
+ callback = errback;
+ errback = optional;
+ } else {
+ deps = [];
+ }
+ }
+
+ if (config && config.context) {
+ contextName = config.context;
+ }
+
+ context = contexts[contextName];
+ if (!context) {
+ context = contexts[contextName] = req.s.newContext(contextName);
+ }
+
+ if (config) {
+ context.configure(config);
+ }
+
+ return context.require(deps, callback, errback);
+ };
+
+ /**
+ * Support require.config() to make it easier to cooperate with other
+ * AMD loaders on globally agreed names.
+ */
+ req.config = function (config) {
+ return req(config);
+ };
+
+ /**
+ * Export require as a global, but only if it does not already exist.
+ */
+ if (!require) {
+ require = req;
+ }
+
+ req.version = version;
+
+ //Used to filter out dependencies that are already paths.
+ req.jsExtRegExp = /^\/|:|\?|\.js$/;
+ req.isBrowser = isBrowser;
+ s = req.s = {
+ contexts: contexts,
+ newContext: newContext
+ };
+
+ //Create default context.
+ req({});
+
+ //Exports some context-sensitive methods on global require, using
+ //default context if no context specified.
+ addRequireMethods(req);
+
+ if (isBrowser) {
+ head = s.head = document.getElementsByTagName('head')[0];
+ //If BASE tag is in play, using appendChild is a problem for IE6.
+ //When that browser dies, this can be removed. Details in this jQuery bug:
+ //http://dev.jquery.com/ticket/2709
+ baseElement = document.getElementsByTagName('base')[0];
+ if (baseElement) {
+ head = s.head = baseElement.parentNode;
+ }
+ }
+
+ /**
+ * Any errors that require explicitly generates will be passed to this
+ * function. Intercept/override it if you want custom error handling.
+ * @param {Error} err the error object.
+ */
+ req.onError = function (err) {
+ throw err;
+ };
+
+ /**
+ * Does the request to load a module for the browser case.
+ * Make this a separate function to allow other environments
+ * to override it.
+ *
+ * @param {Object} context the require context to find state.
+ * @param {String} moduleName the name of the module.
+ * @param {Object} url the URL to the module.
+ */
+ req.load = function (context, moduleName, url) {
+ var config = (context && context.config) || {},
+ node;
+ if (isBrowser) {
+ //In the browser so use a script tag
+ node = config.xhtml ?
+ document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
+ document.createElement('script');
+ node.type = config.scriptType || 'text/javascript';
+ node.charset = 'utf-8';
+
+ node.setAttribute('data-requirecontext', context.contextName);
+ node.setAttribute('data-requiremodule', moduleName);
+
+ //Set up load listener. Test attachEvent first because IE9 has
+ //a subtle issue in its addEventListener and script onload firings
+ //that do not match the behavior of all other browsers with
+ //addEventListener support, which fire the onload event for a
+ //script right after the script execution. See:
+ //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution
+ //UNFORTUNATELY Opera implements attachEvent but does not follow the script
+ //script execution mode.
+ if (node.attachEvent &&
+ //Check if node.attachEvent is artificially added by custom script or
+ //natively supported by browser
+ //read https://github.com/jrburke/requirejs/issues/187
+ //if we can NOT find [native code] then it must NOT natively supported.
+ //in IE8, node.attachEvent does not have toString()
+ //Note the test for "[native code" with no closing brace, see:
+ //https://github.com/jrburke/requirejs/issues/273
+ !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
+ !isOpera) {
+ //Probably IE. IE (at least 6-8) do not fire
+ //script onload right after executing the script, so
+ //we cannot tie the anonymous define call to a name.
+ //However, IE reports the script as being in 'interactive'
+ //readyState at the time of the define call.
+ useInteractive = true;
+
+ node.attachEvent('onreadystatechange', context.onScriptLoad);
+ //It would be great to add an error handler here to catch
+ //404s in IE9+. However, onreadystatechange will fire before
+ //the error handler, so that does not help. If addEvenListener
+ //is used, then IE will fire error before load, but we cannot
+ //use that pathway given the connect.microsoft.com issue
+ //mentioned above about not doing the 'script execute,
+ //then fire the script load event listener before execute
+ //next script' that other browsers do.
+ //Best hope: IE10 fixes the issues,
+ //and then destroys all installs of IE 6-9.
+ //node.attachEvent('onerror', context.onScriptError);
+ } else {
+ node.addEventListener('load', context.onScriptLoad, false);
+ node.addEventListener('error', context.onScriptError, false);
+ }
+ node.src = url;
+
+ //For some cache cases in IE 6-8, the script executes before the end
+ //of the appendChild execution, so to tie an anonymous define
+ //call to the module name (which is stored on the node), hold on
+ //to a reference to this node, but clear after the DOM insertion.
+ currentlyAddingScript = node;
+ if (baseElement) {
+ head.insertBefore(node, baseElement);
+ } else {
+ head.appendChild(node);
+ }
+ currentlyAddingScript = null;
+
+ return node;
+ } else if (isWebWorker) {
+ //In a web worker, use importScripts. This is not a very
+ //efficient use of importScripts, importScripts will block until
+ //its script is downloaded and evaluated. However, if web workers
+ //are in play, the expectation that a build has been done so that
+ //only one script needs to be loaded anyway. This may need to be
+ //reevaluated if other use cases become common.
+ importScripts(url);
+
+ //Account for anonymous modules
+ context.completeLoad(moduleName);
+ }
+ };
+
+ function getInteractiveScript() {
+ if (interactiveScript && interactiveScript.readyState === 'interactive') {
+ return interactiveScript;
+ }
+
+ eachReverse(scripts(), function (script) {
+ if (script.readyState === 'interactive') {
+ return (interactiveScript = script);
+ }
+ });
+ return interactiveScript;
+ }
+
+ //Look for a data-main script attribute, which could also adjust the baseUrl.
+ if (isBrowser) {
+ //Figure out baseUrl. Get it from the script tag with require.js in it.
+ eachReverse(scripts(), function (script) {
+ //Set the 'head' where we can append children by
+ //using the script's parent.
+ if (!head) {
+ head = script.parentNode;
+ }
+
+ //Look for a data-main attribute to set main script for the page
+ //to load. If it is there, the path to data main becomes the
+ //baseUrl, if it is not already set.
+ dataMain = script.getAttribute('data-main');
+ if (dataMain) {
+
+ //Pull off the directory of data-main for use as the
+ //baseUrl.
+ src = dataMain.split('/');
+ mainScript = src.pop();
+ subPath = src.length ? src.join('/') + '/' : './';
+
+ //Set final baseUrl if there is not already an explicit one.
+ if (!cfg.baseUrl) {
+ cfg.baseUrl = subPath;
+ }
+
+ //Strip off any trailing .js since dataMain is now
+ //like a module name.
+ dataMain = mainScript.replace(jsSuffixRegExp, '');
+
+ //Put the data-main script in the files to load.
+ cfg.deps = cfg.deps ? cfg.deps.concat(dataMain) : [dataMain];
+
+ return true;
+ }
+ });
+ }
+
+ /**
+ * The function that handles definitions of modules. Differs from
+ * require() in that a string for the module should be the first argument,
+ * and the function to execute after dependencies are loaded should
+ * return a value to define the module corresponding to the first argument's
+ * name.
+ */
+ define = function (name, deps, callback) {
+ var node, context;
+
+ //Allow for anonymous functions
+ if (typeof name !== 'string') {
+ //Adjust args appropriately
+ callback = deps;
+ deps = name;
+ name = null;
+ }
+
+ //This module may not have dependencies
+ if (!isArray(deps)) {
+ callback = deps;
+ deps = [];
+ }
+
+ //If no name, and callback is a function, then figure out if it a
+ //CommonJS thing with dependencies.
+ if (!deps.length && isFunction(callback)) {
+ //Remove comments from the callback string,
+ //look for require calls, and pull them into the dependencies,
+ //but only if there are function args.
+ if (callback.length) {
+ callback
+ .toString()
+ .replace(commentRegExp, '')
+ .replace(cjsRequireRegExp, function (match, dep) {
+ deps.push(dep);
+ });
+
+ //May be a CommonJS thing even without require calls, but still
+ //could use exports, and module. Avoid doing exports and module
+ //work though if it just needs require.
+ //REQUIRES the function to expect the CommonJS variables in the
+ //order listed below.
+ deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps);
+ }
+ }
+
+ //If in IE 6-8 and hit an anonymous define() call, do the interactive
+ //work.
+ if (useInteractive) {
+ node = currentlyAddingScript || getInteractiveScript();
+ if (node) {
+ if (!name) {
+ name = node.getAttribute('data-requiremodule');
+ }
+ context = contexts[node.getAttribute('data-requirecontext')];
+ }
+ }
+
+ //Always save off evaluating the def call until the script onload handler.
+ //This allows multiple modules to be in a file without prematurely
+ //tracing dependencies, and allows for anonymous module support,
+ //where the module name is not known until the script onload event
+ //occurs. If no context, use the global queue, and get it processed
+ //in the onscript load callback.
+ (context ? context.defQueue : globalDefQueue).push([name, deps, callback]);
+ };
+
+ define.amd = {
+ jQuery: true
+ };
+
+
+ /**
+ * Executes the text. Normally just uses eval, but can be modified
+ * to use a better, environment-specific call. Only used for transpiling
+ * loader plugins, not for plain JS modules.
+ * @param {String} text the text to execute/evaluate.
+ */
+ req.exec = function (text) {
+ /*jslint evil: true */
+ return eval(text);
+ };
+
+ //Set up with config info.
+ req(cfg);
+}(this));
diff --git a/src/fauxton/assets/js/plugins/backbone.layoutmanager.js b/src/fauxton/assets/js/plugins/backbone.layoutmanager.js
new file mode 100644
index 000000000..201895d7b
--- /dev/null
+++ b/src/fauxton/assets/js/plugins/backbone.layoutmanager.js
@@ -0,0 +1,781 @@
+/*!
+ * backbone.layoutmanager.js v0.7.2
+ * Copyright 2012, Tim Branyen (@tbranyen)
+ * backbone.layoutmanager.js may be freely distributed under the MIT license.
+ */
+(function(window) {
+
+"use strict";
+
+// Hoisted, referenced at the bottom of the source. This caches a list of all
+// LayoutManager options at definition time.
+var keys;
+
+// Localize global dependency references.
+var Backbone = window.Backbone;
+var _ = window._;
+var $ = window.$;
+
+// Maintain references to the two `Backbone.View` functions that are
+// overwritten so that they can be proxied.
+var _configure = Backbone.View.prototype._configure;
+var render = Backbone.View.prototype.render;
+
+// Cache these methods for performance.
+var aPush = Array.prototype.push;
+var aConcat = Array.prototype.concat;
+var aSplice = Array.prototype.splice;
+
+// LayoutManager is a wrapper around a `Backbone.View`.
+var LayoutManager = Backbone.View.extend({
+ // This named function allows for significantly easier debugging.
+ constructor: function Layout(options) {
+ // Options may not always be passed to the constructor, this ensures it is
+ // always an object.
+ options = options || {};
+
+ // Grant this View superpowers.
+ LayoutManager.setupView(this, options);
+
+ // Have Backbone set up the rest of this View.
+ Backbone.View.call(this, options);
+ },
+
+ // Shorthand to `setView` function with the `append` flag set.
+ insertView: function(selector, view) {
+ // If the `view` argument exists, then a selector was passed in. This code
+ // path will forward the selector on to `setView`.
+ if (view) {
+ return this.setView(selector, view, true);
+ }
+
+ // If no `view` argument is defined, then assume the first argument is the
+ // View, somewhat now confusingly named `selector`.
+ return this.setView(selector, true);
+ },
+
+ // Iterate over an object and ensure every value is wrapped in an array to
+ // ensure they will be appended, then pass that object to `setViews`.
+ insertViews: function(views) {
+ // If an array of views was passed it should be inserted into the
+ // root view. Much like calling insertView without a selector
+ if (_.isArray(views)) {
+ return this.setViews({'': views});
+ }
+
+ _.each(views, function(view, selector) {
+ views[selector] = _.isArray(view) ? view : [view];
+ });
+
+ return this.setViews(views);
+ },
+
+ // Returns the View that matches the `getViews` filter function.
+ getView: function(fn) {
+ return this.getViews(fn).first().value();
+ },
+
+ // Provide a filter function to get a flattened array of all the subviews.
+ // If the filter function is omitted it will return all subviews. If a
+ // String is passed instead, it will return the Views for that selector.
+ getViews: function(fn) {
+ // Generate an array of all top level (no deeply nested) Views flattened.
+ var views = _.chain(this.views).map(function(view) {
+ return _.isArray(view) ? view : [view];
+ }, this).flatten().value();
+
+ // If the filter argument is a String, then return a chained Version of the
+ // elements.
+ if (typeof fn === "string") {
+ return _.chain([this.views[fn]]).flatten();
+ }
+
+ // If a filter function is provided, run it on all Views and return a
+ // wrapped chain. Otherwise, simply return a wrapped chain of all Views.
+ return _.chain(typeof fn === "function" ? _.filter(views, fn) : views);
+ },
+
+ // This takes in a partial name and view instance and assigns them to
+ // the internal collection of views. If a view is not a LayoutManager
+ // instance, then mix in the LayoutManager prototype. This ensures
+ // all Views can be used successfully.
+ //
+ // Must definitely wrap any render method passed in or defaults to a
+ // typical render function `return layout(this).render()`.
+ setView: function(name, view, append) {
+ var manager, existing, options;
+ // Parent view, the one you are setting a View on.
+ var root = this;
+
+ // If no name was passed, use an empty string and shift all arguments.
+ if (typeof name !== "string") {
+ append = view;
+ view = name;
+ name = "";
+ }
+
+ // If the parent views object doesn't exist... create it.
+ this.views = this.views || {};
+
+ // Shorthand the `__manager__` property.
+ manager = view.__manager__;
+
+ // Shorthand the View that potentially already exists.
+ existing = this.views[name];
+
+ // If the View has not been properly set up, throw an Error message
+ // indicating that the View needs `manage: true` set.
+ if (!manager) {
+ throw new Error("Please set `View#manage` property with selector '" +
+ name + "' to `true`.");
+ }
+
+ // Assign options.
+ options = view._options();
+
+ // Add reference to the parentView.
+ manager.parent = root;
+
+ // Add reference to the placement selector used.
+ manager.selector = name;
+
+ // Code path is less complex for Views that are not being appended. Simply
+ // remove existing Views and bail out with the assignment.
+ if (!append) {
+ // If the View we are adding has already been rendered, simply inject it
+ // into the parent.
+ if (manager.hasRendered) {
+ options.partial(root.el, manager.selector, view.el, manager.append);
+ }
+
+ // Ensure remove is called when swapping View's.
+ if (existing) {
+ // If the views are an array, iterate and remove each individually.
+ _.each(aConcat.call([], existing), function(nestedView) {
+ nestedView.remove();
+ });
+ }
+
+ // Assign to main views object and return for chainability.
+ return this.views[name] = view;
+ }
+
+ // Ensure this.views[name] is an array and push this View to the end.
+ this.views[name] = aConcat.call([], existing || [], view);
+
+ // Put the view into `append` mode.
+ manager.append = true;
+
+ return view;
+ },
+
+ // Allows the setting of multiple views instead of a single view.
+ setViews: function(views) {
+ // Iterate over all the views and use the View's view method to assign.
+ _.each(views, function(view, name) {
+ // If the view is an array put all views into insert mode.
+ if (_.isArray(view)) {
+ return _.each(view, function(view) {
+ this.insertView(name, view);
+ }, this);
+ }
+
+ // Assign each view using the view function.
+ this.setView(name, view);
+ }, this);
+
+ // Allow for chaining
+ return this;
+ },
+
+ // By default this should find all nested views and render them into
+ // the this.el and call done once all of them have successfully been
+ // resolved.
+ //
+ // This function returns a promise that can be chained to determine
+ // once all subviews and main view have been rendered into the view.el.
+ render: function() {
+ var root = this;
+ var options = root._options();
+ var manager = root.__manager__;
+ var parent = manager.parent;
+ var rentManager = parent && parent.__manager__;
+ var def = options.deferred();
+
+ // Triggered once the render has succeeded.
+ function resolve() {
+ var next;
+
+ // If there is a parent, attach.
+ if (parent) {
+ if (!options.contains(parent.el, root.el)) {
+ options.partial(parent.el, manager.selector, root.el,
+ manager.append);
+ }
+ }
+
+ // Ensure events are always correctly bound after rendering.
+ root.delegateEvents();
+
+ // Set this View as successfully rendered.
+ manager.hasRendered = true;
+
+ // Resolve the deferred.
+ def.resolveWith(root, [root]);
+
+ // Only process the queue if it exists.
+ if (next = manager.queue.shift()) {
+ // Ensure that the next render is only called after all other
+ // `done` handlers have completed. This will prevent `render`
+ // callbacks from firing out of order.
+ next();
+ } else {
+ // Once the queue is depleted, remove it, the render process has
+ // completed.
+ delete manager.queue;
+ }
+
+ // Reusable function for triggering the afterRender callback and event
+ // and setting the hasRendered flag.
+ function completeRender() {
+ var afterRender = options.afterRender;
+
+ if (afterRender) {
+ afterRender.call(root, root);
+ }
+
+ // Always emit an afterRender event.
+ root.trigger("afterRender", root);
+ }
+
+ // Special case for when a parent View that has not been rendered is
+ // involved.
+ if (rentManager && !rentManager.hasRendered) {
+ // Wait until the parent View has finished rendering, which could be
+ // asynchronous, and trigger afterRender on this View once it has
+ // compeleted.
+ return parent.on("afterRender", function() {
+ // Wish we had `once` for this...
+ parent.off("afterRender", null, this);
+
+ // Trigger the afterRender and set hasRendered.
+ completeRender();
+ }, root);
+ }
+
+ // This View and its parent have both rendered.
+ completeRender();
+ }
+
+ // Actually facilitate a render.
+ function actuallyRender() {
+ var options = root._options();
+ var manager = root.__manager__;
+ var parent = manager.parent;
+ var rentManager = parent && parent.__manager__;
+
+ // The `_viewRender` method is broken out to abstract away from having
+ // too much code in `processRender`.
+ root._render(LayoutManager._viewRender, options).done(function() {
+ // If there are no children to worry about, complete the render
+ // instantly.
+ if (!_.keys(root.views).length) {
+ return resolve();
+ }
+
+ // Create a list of promises to wait on until rendering is done.
+ // Since this method will run on all children as well, its sufficient
+ // for a full hierarchical.
+ var promises = _.map(root.views, function(view) {
+ var append = _.isArray(view);
+
+ // If items are being inserted, they will be in a non-zero length
+ // Array.
+ if (append && view.length) {
+ // Only need to wait for the first View to complete, the rest
+ // will be synchronous, by virtue of having the template cached.
+ return view[0].render().pipe(function() {
+ // Map over all the View's to be inserted and call render on
+ // them all. Once they have all resolved, resolve the other
+ // deferred.
+ return options.when(_.map(view.slice(1), function(insertView) {
+ return insertView.render();
+ }));
+ });
+ }
+
+ // Only return the fetch deferred, resolve the main deferred after
+ // the element has been attached to it's parent.
+ return !append ? view.render() : view;
+ });
+
+ // Once all nested Views have been rendered, resolve this View's
+ // deferred.
+ options.when(promises).done(function() {
+ resolve();
+ });
+ });
+ }
+
+ // Another render is currently happening if there is an existing queue, so
+ // push a closure to render later into the queue.
+ if (manager.queue) {
+ aPush.call(manager.queue, function() {
+ actuallyRender();
+ });
+ } else {
+ manager.queue = [];
+
+ // This the first `render`, preceeding the `queue` so render
+ // immediately.
+ actuallyRender(root, def);
+ }
+
+ // Add the View to the deferred so that `view.render().view.el` is
+ // possible.
+ def.view = root;
+
+ // This is the promise that determines if the `render` function has
+ // completed or not.
+ return def;
+ },
+
+ // Ensure the cleanup function is called whenever remove is called.
+ remove: function() {
+ // Force remove itself from its parent.
+ LayoutManager._removeView(this, true);
+
+ // Call the original remove function.
+ return this._remove.apply(this, arguments);
+ },
+
+ // Merge instance and global options.
+ _options: function() {
+ // Instance overrides take precedence, fallback to prototype options. In
+ // Lo-Dash, `_.extend` will not copy over inherited properties, so the
+ // `this.constructor.prototype` was added in to cover that case.
+ return _.extend({}, this, this.constructor.prototype,
+ LayoutManager.prototype.options, this.options);
+ }
+},
+{
+ // Clearable cache.
+ _cache: {},
+
+ // Creates a deferred and returns a function to call when finished.
+ _makeAsync: function(options, done) {
+ var handler = options.deferred();
+
+ // Used to handle asynchronous renders.
+ handler.async = function() {
+ handler._isAsync = true;
+
+ return done;
+ };
+
+ return handler;
+ },
+
+ // This gets passed to all _render methods. The `root` value here is passed
+ // from the `manage(this).render()` line in the `_render` function
+ _viewRender: function(root, options) {
+ var url, contents, fetchAsync;
+ var manager = root.__manager__;
+
+ // This function is responsible for pairing the rendered template into
+ // the DOM element.
+ function applyTemplate(rendered) {
+ // Actually put the rendered contents into the element.
+ if (rendered) {
+ options.html(root.el, rendered);
+ }
+
+ // Resolve only after fetch and render have succeeded.
+ fetchAsync.resolveWith(root, [root]);
+ }
+
+ // Once the template is successfully fetched, use its contents to proceed.
+ // Context argument is first, since it is bound for partial application
+ // reasons.
+ function done(context, contents) {
+ // Store the rendered template someplace so it can be re-assignable.
+ var rendered;
+ // This allows the `render` method to be asynchronous as well as `fetch`.
+ var renderAsync = LayoutManager._makeAsync(options, function(rendered) {
+ applyTemplate(rendered);
+ });
+
+ // Ensure the cache is up-to-date.
+ LayoutManager.cache(url, contents);
+
+ // Render the View into the el property.
+ if (contents) {
+ rendered = options.render.call(renderAsync, contents, context);
+ }
+
+ // If the function was synchronous, continue execution.
+ if (!renderAsync._isAsync) {
+ applyTemplate(rendered);
+ }
+ }
+
+ return {
+ // This `render` function is what gets called inside of the View render,
+ // when `manage(this).render` is called. Returns a promise that can be
+ // used to know when the element has been rendered into its parent.
+ render: function() {
+ var context;
+ var data = options.serialize || options.data;
+ var template = root.template || options.template;
+
+ // If data is a function, immediately call it.
+ if (_.isFunction(data)) {
+ data = data.call(root);
+ }
+
+ // This allows for `var done = this.async()` and then `done(contents)`.
+ fetchAsync = LayoutManager._makeAsync(options, function(contents) {
+ done(data, contents);
+ });
+
+ // Set the url to the prefix + the view's template property.
+ if (typeof template === "string") {
+ url = options.prefix + template;
+ }
+
+ // Check if contents are already cached and if they are, simply process
+ // the template with the correct data.
+ if (contents = LayoutManager.cache(url)) {
+ done(data, contents, url);
+
+ return fetchAsync;
+ }
+
+ // Fetch layout and template contents.
+ if (typeof template === "string") {
+ contents = options.fetch.call(fetchAsync, options.prefix + template);
+ // If its not a string just pass the object/function/whatever.
+ } else if (template != null) {
+ contents = options.fetch.call(fetchAsync, template);
+ }
+
+ // If the function was synchronous, continue execution.
+ if (!fetchAsync._isAsync) {
+ done(data, contents);
+ }
+
+ return fetchAsync;
+ }
+ };
+ },
+
+ // Remove all nested Views.
+ _removeViews: function(root, force) {
+ // Shift arguments around.
+ if (typeof root === "boolean") {
+ force = root;
+ root = this;
+ }
+
+ // Allow removeView to be called on instances.
+ root = root || this;
+
+ // Iterate over all of the nested View's and remove.
+ root.getViews().each(function(view) {
+ // Force doesn't care about if a View has rendered or not.
+ if (view.__manager__.hasRendered || force) {
+ LayoutManager._removeView(view, force);
+ }
+ });
+ },
+
+ // Remove a single nested View.
+ _removeView: function(view, force) {
+ var parentViews;
+ // Shorthand the manager for easier access.
+ var manager = view.__manager__;
+ // Test for keep.
+ var keep = typeof view.keep === "boolean" ? view.keep : view.options.keep;
+
+ // Only remove views that do not have `keep` attribute set, unless the
+ // View is in `append` mode and the force flag is set.
+ if (!keep && (manager.append === true || force)) {
+ // Clean out the events.
+ LayoutManager.cleanViews(view);
+
+ // Since we are removing this view, force subviews to remove
+ view._removeViews(true);
+
+ // Remove the View completely.
+ view.$el.remove();
+
+ // Bail out early if no parent exists.
+ if (!manager.parent) { return; }
+
+ // Assign (if they exist) the sibling Views to a property.
+ parentViews = manager.parent.views[manager.selector];
+
+ // If this is an array of items remove items that are not marked to
+ // keep.
+ if (_.isArray(parentViews)) {
+ // Remove duplicate Views.
+ return _.each(_.clone(parentViews), function(view, i) {
+ // If the managers match, splice off this View.
+ if (view && view.__manager__ === manager) {
+ aSplice.call(parentViews, i, 1);
+ }
+ });
+ }
+
+ // Otherwise delete the parent selector.
+ delete manager.parent.views[manager.selector];
+ }
+ },
+
+ // Accept either a single view or an array of views to clean of all DOM
+ // events internal model and collection references and all Backbone.Events.
+ cleanViews: function(views) {
+ // Clear out all existing views.
+ _.each(aConcat.call([], views), function(view) {
+ // Remove all custom events attached to this View.
+ view.unbind();
+
+ // Automatically unbind `model`.
+ if (view.model instanceof Backbone.Model) {
+ view.model.off(null, null, view);
+ }
+
+ // Automatically unbind `collection`.
+ if (view.collection instanceof Backbone.Collection) {
+ view.collection.off(null, null, view);
+ }
+
+ // If a custom cleanup method was provided on the view, call it after
+ // the initial cleanup is done
+ if (view.cleanup) {
+ view.cleanup.call(view);
+ }
+ });
+ },
+
+ // Cache templates into LayoutManager._cache.
+ cache: function(path, contents) {
+ // If template path is found in the cache, return the contents.
+ if (path in this._cache) {
+ return this._cache[path];
+ // Ensure path and contents aren't undefined.
+ } else if (path != null && contents != null) {
+ return this._cache[path] = contents;
+ }
+
+ // If the template is not in the cache, return undefined.
+ },
+
+ // This static method allows for global configuration of LayoutManager.
+ configure: function(opts) {
+ _.extend(LayoutManager.prototype.options, opts);
+
+ // Allow LayoutManager to manage Backbone.View.prototype.
+ if (opts.manage) {
+ Backbone.View.prototype.manage = true;
+ }
+ },
+
+ // Configure a View to work with the LayoutManager plugin.
+ setupView: function(view, options) {
+ // If the View has already been setup, no need to do it again.
+ if (view.__manager__) {
+ return;
+ }
+
+ var views, declaredViews, viewOptions;
+ var proto = Backbone.LayoutManager.prototype;
+ var viewOverrides = _.pick(view, keys);
+
+ // Ensure necessary properties are set.
+ _.defaults(view, {
+ // Ensure a view always has a views object.
+ views: {},
+
+ // Internal state object used to store whether or not a View has been
+ // taken over by layout manager and if it has been rendered into the DOM.
+ __manager__: {},
+
+ // Add the ability to remove all Views.
+ _removeViews: LayoutManager._removeViews,
+
+ // Add the ability to remove itself.
+ _removeView: LayoutManager._removeView
+
+ // Mix in all LayoutManager prototype properties as well.
+ }, LayoutManager.prototype);
+
+ // Extend the options with the prototype and passed options.
+ options = view.options = _.defaults(options || {}, view.options,
+ proto.options);
+
+ // Ensure view events are properly copied over.
+ viewOptions = _.pick(options, aConcat.call(["events"],
+ _.values(options.events)));
+
+ // Merge the View options into the View.
+ _.extend(view, viewOptions);
+
+ // If the View still has the Backbone.View#render method, remove it. Don't
+ // want it accidentally overriding the LM render.
+ if (viewOverrides.render === LayoutManager.prototype.render ||
+ viewOverrides.render === Backbone.View.prototype.render) {
+ delete viewOverrides.render;
+ }
+
+ // Pick out the specific properties that can be dynamically added at
+ // runtime and ensure they are available on the view object.
+ _.extend(options, viewOverrides);
+
+ // By default the original Remove function is the Backbone.View one.
+ view._remove = Backbone.View.prototype.remove;
+
+ // Always use this render function when using LayoutManager.
+ view._render = function(manage, options) {
+ // Keep the view consistent between callbacks and deferreds.
+ var view = this;
+ // Shorthand the manager.
+ var manager = view.__manager__;
+ // Cache these properties.
+ var beforeRender = options.beforeRender;
+
+ // Ensure all nested Views are properly scrubbed if re-rendering.
+ if (manager.hasRendered) {
+ this._removeViews();
+ }
+
+ // If a beforeRender function is defined, call it.
+ if (beforeRender) {
+ beforeRender.call(this, this);
+ }
+
+ // Always emit a beforeRender event.
+ this.trigger("beforeRender", this);
+
+ // Render!
+ return manage(this, options).render();
+ };
+
+ // Ensure the render is always set correctly.
+ view.render = LayoutManager.prototype.render;
+
+ // If the user provided their own remove override, use that instead of the
+ // default.
+ if (view.remove !== proto.remove) {
+ view._remove = view.remove;
+ view.remove = proto.remove;
+ }
+
+ // Normalize views to exist on either instance or options, default to
+ // options.
+ views = options.views || view.views;
+
+ // Set the internal views, only if selectors have been provided.
+ if (_.keys(views).length) {
+ // Keep original object declared containing Views.
+ declaredViews = views;
+
+ // Reset the property to avoid duplication or overwritting.
+ view.views = {};
+
+ // Set the declared Views.
+ view.setViews(declaredViews);
+ }
+
+ // If a template is passed use that instead.
+ if (view.options.template) {
+ view.options.template = options.template;
+ // Ensure the template is mapped over.
+ } else if (view.template) {
+ options.template = view.template;
+
+ // Remove it from the instance.
+ delete view.template;
+ }
+ }
+});
+
+// Convenience assignment to make creating Layout's slightly shorter.
+Backbone.Layout = Backbone.LayoutView = Backbone.LayoutManager = LayoutManager;
+// Tack on the version.
+LayoutManager.VERSION = "0.7.2";
+
+// Override _configure to provide extra functionality that is necessary in
+// order for the render function reference to be bound during initialize.
+Backbone.View.prototype._configure = function() {
+ // Run the original _configure.
+ var retVal = _configure.apply(this, arguments);
+
+ // If manage is set, do it!
+ if (this.manage) {
+ // Set up this View.
+ LayoutManager.setupView(this);
+ }
+
+ // Act like nothing happened.
+ return retVal;
+};
+
+// Default configuration options; designed to be overriden.
+LayoutManager.prototype.options = {
+ // Prefix template/layout paths.
+ prefix: "",
+
+ // Can be used to supply a different deferred implementation.
+ deferred: function() {
+ return $.Deferred();
+ },
+
+ // Fetch is passed a path and is expected to return template contents as a
+ // function or string.
+ fetch: function(path) {
+ return _.template($(path).html());
+ },
+
+ // This is the most common way you will want to partially apply a view into
+ // a layout.
+ partial: function(root, name, el, append) {
+ // If no selector is specified, assume the parent should be added to.
+ var $root = name ? $(root).find(name) : $(root);
+
+ // Use the append method if append argument is true.
+ this[append ? "append" : "html"]($root, el);
+ },
+
+ // Override this with a custom HTML method, passed a root element and an
+ // element to replace the innerHTML with.
+ html: function(root, el) {
+ $(root).html(el);
+ },
+
+ // Very similar to HTML except this one will appendChild.
+ append: function(root, el) {
+ $(root).append(el);
+ },
+
+ // Return a deferred for when all promises resolve/reject.
+ when: function(promises) {
+ return $.when.apply(null, promises);
+ },
+
+ // By default, render using underscore's templating.
+ render: function(template, context) {
+ return template(context);
+ },
+
+ // A method to determine if a View contains another.
+ contains: function(parent, child) {
+ return $.contains(parent, child);
+ }
+};
+
+// Maintain a list of the keys at define time.
+keys = _.keys(LayoutManager.prototype.options);
+
+})(this);
+
diff --git a/src/fauxton/assets/js/plugins/codemirror-javascript.js b/src/fauxton/assets/js/plugins/codemirror-javascript.js
new file mode 100644
index 000000000..65f11c5bf
--- /dev/null
+++ b/src/fauxton/assets/js/plugins/codemirror-javascript.js
@@ -0,0 +1,361 @@
+CodeMirror.defineMode("javascript", function(config, parserConfig) {
+ var indentUnit = config.indentUnit;
+ var jsonMode = parserConfig.json;
+
+ // Tokenizer
+
+ var keywords = function(){
+ function kw(type) {return {type: type, style: "keyword"};}
+ var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
+ var operator = kw("operator"), atom = {type: "atom", style: "atom"};
+ return {
+ "if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
+ "return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
+ "var": kw("var"), "const": kw("var"), "let": kw("var"),
+ "function": kw("function"), "catch": kw("catch"),
+ "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
+ "in": operator, "typeof": operator, "instanceof": operator,
+ "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
+ };
+ }();
+
+ var isOperatorChar = /[+\-*&%=<>!?|]/;
+
+ function chain(stream, state, f) {
+ state.tokenize = f;
+ return f(stream, state);
+ }
+
+ function nextUntilUnescaped(stream, end) {
+ var escaped = false, next;
+ while ((next = stream.next()) != null) {
+ if (next == end && !escaped)
+ return false;
+ escaped = !escaped && next == "\\";
+ }
+ return escaped;
+ }
+
+ // Used as scratch variables to communicate multiple values without
+ // consing up tons of objects.
+ var type, content;
+ function ret(tp, style, cont) {
+ type = tp; content = cont;
+ return style;
+ }
+
+ function jsTokenBase(stream, state) {
+ var ch = stream.next();
+ if (ch == '"' || ch == "'")
+ return chain(stream, state, jsTokenString(ch));
+ else if (/[\[\]{}\(\),;\:\.]/.test(ch))
+ return ret(ch);
+ else if (ch == "0" && stream.eat(/x/i)) {
+ stream.eatWhile(/[\da-f]/i);
+ return ret("number", "number");
+ }
+ else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
+ stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
+ return ret("number", "number");
+ }
+ else if (ch == "/") {
+ if (stream.eat("*")) {
+ return chain(stream, state, jsTokenComment);
+ }
+ else if (stream.eat("/")) {
+ stream.skipToEnd();
+ return ret("comment", "comment");
+ }
+ else if (state.reAllowed) {
+ nextUntilUnescaped(stream, "/");
+ stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
+ return ret("regexp", "string-2");
+ }
+ else {
+ stream.eatWhile(isOperatorChar);
+ return ret("operator", null, stream.current());
+ }
+ }
+ else if (ch == "#") {
+ stream.skipToEnd();
+ return ret("error", "error");
+ }
+ else if (isOperatorChar.test(ch)) {
+ stream.eatWhile(isOperatorChar);
+ return ret("operator", null, stream.current());
+ }
+ else {
+ stream.eatWhile(/[\w\$_]/);
+ var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
+ return (known && state.kwAllowed) ? ret(known.type, known.style, word) :
+ ret("variable", "variable", word);
+ }
+ }
+
+ function jsTokenString(quote) {
+ return function(stream, state) {
+ if (!nextUntilUnescaped(stream, quote))
+ state.tokenize = jsTokenBase;
+ return ret("string", "string");
+ };
+ }
+
+ function jsTokenComment(stream, state) {
+ var maybeEnd = false, ch;
+ while (ch = stream.next()) {
+ if (ch == "/" && maybeEnd) {
+ state.tokenize = jsTokenBase;
+ break;
+ }
+ maybeEnd = (ch == "*");
+ }
+ return ret("comment", "comment");
+ }
+
+ // Parser
+
+ var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
+
+ function JSLexical(indented, column, type, align, prev, info) {
+ this.indented = indented;
+ this.column = column;
+ this.type = type;
+ this.prev = prev;
+ this.info = info;
+ if (align != null) this.align = align;
+ }
+
+ function inScope(state, varname) {
+ for (var v = state.localVars; v; v = v.next)
+ if (v.name == varname) return true;
+ }
+
+ function parseJS(state, style, type, content, stream) {
+ var cc = state.cc;
+ // Communicate our context to the combinators.
+ // (Less wasteful than consing up a hundred closures on every call.)
+ cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
+
+ if (!state.lexical.hasOwnProperty("align"))
+ state.lexical.align = true;
+
+ while(true) {
+ var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
+ if (combinator(type, content)) {
+ while(cc.length && cc[cc.length - 1].lex)
+ cc.pop()();
+ if (cx.marked) return cx.marked;
+ if (type == "variable" && inScope(state, content)) return "variable-2";
+ return style;
+ }
+ }
+ }
+
+ // Combinator utils
+
+ var cx = {state: null, column: null, marked: null, cc: null};
+ function pass() {
+ for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
+ }
+ function cont() {
+ pass.apply(null, arguments);
+ return true;
+ }
+ function register(varname) {
+ var state = cx.state;
+ if (state.context) {
+ cx.marked = "def";
+ for (var v = state.localVars; v; v = v.next)
+ if (v.name == varname) return;
+ state.localVars = {name: varname, next: state.localVars};
+ }
+ }
+
+ // Combinators
+
+ var defaultVars = {name: "this", next: {name: "arguments"}};
+ function pushcontext() {
+ if (!cx.state.context) cx.state.localVars = defaultVars;
+ cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
+ }
+ function popcontext() {
+ cx.state.localVars = cx.state.context.vars;
+ cx.state.context = cx.state.context.prev;
+ }
+ function pushlex(type, info) {
+ var result = function() {
+ var state = cx.state;
+ state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)
+ };
+ result.lex = true;
+ return result;
+ }
+ function poplex() {
+ var state = cx.state;
+ if (state.lexical.prev) {
+ if (state.lexical.type == ")")
+ state.indented = state.lexical.indented;
+ state.lexical = state.lexical.prev;
+ }
+ }
+ poplex.lex = true;
+
+ function expect(wanted) {
+ return function expecting(type) {
+ if (type == wanted) return cont();
+ else if (wanted == ";") return pass();
+ else return cont(arguments.callee);
+ };
+ }
+
+ function statement(type) {
+ if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
+ if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
+ if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
+ if (type == "{") return cont(pushlex("}"), block, poplex);
+ if (type == ";") return cont();
+ if (type == "function") return cont(functiondef);
+ if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
+ poplex, statement, poplex);
+ if (type == "variable") return cont(pushlex("stat"), maybelabel);
+ if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
+ block, poplex, poplex);
+ if (type == "case") return cont(expression, expect(":"));
+ if (type == "default") return cont(expect(":"));
+ if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
+ statement, poplex, popcontext);
+ return pass(pushlex("stat"), expression, expect(";"), poplex);
+ }
+ function expression(type) {
+ if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
+ if (type == "function") return cont(functiondef);
+ if (type == "keyword c") return cont(maybeexpression);
+ if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
+ if (type == "operator") return cont(expression);
+ if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
+ if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
+ return cont();
+ }
+ function maybeexpression(type) {
+ if (type.match(/[;\}\)\],]/)) return pass();
+ return pass(expression);
+ }
+
+ function maybeoperator(type, value) {
+ if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
+ if (type == "operator" || type == ":") return cont(expression);
+ if (type == ";") return;
+ if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
+ if (type == ".") return cont(property, maybeoperator);
+ if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
+ }
+ function maybelabel(type) {
+ if (type == ":") return cont(poplex, statement);
+ return pass(maybeoperator, expect(";"), poplex);
+ }
+ function property(type) {
+ if (type == "variable") {cx.marked = "property"; return cont();}
+ }
+ function objprop(type) {
+ if (type == "variable") cx.marked = "property";
+ if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
+ }
+ function commasep(what, end) {
+ function proceed(type) {
+ if (type == ",") return cont(what, proceed);
+ if (type == end) return cont();
+ return cont(expect(end));
+ }
+ return function commaSeparated(type) {
+ if (type == end) return cont();
+ else return pass(what, proceed);
+ };
+ }
+ function block(type) {
+ if (type == "}") return cont();
+ return pass(statement, block);
+ }
+ function vardef1(type, value) {
+ if (type == "variable"){register(value); return cont(vardef2);}
+ return cont();
+ }
+ function vardef2(type, value) {
+ if (value == "=") return cont(expression, vardef2);
+ if (type == ",") return cont(vardef1);
+ }
+ function forspec1(type) {
+ if (type == "var") return cont(vardef1, forspec2);
+ if (type == ";") return pass(forspec2);
+ if (type == "variable") return cont(formaybein);
+ return pass(forspec2);
+ }
+ function formaybein(type, value) {
+ if (value == "in") return cont(expression);
+ return cont(maybeoperator, forspec2);
+ }
+ function forspec2(type, value) {
+ if (type == ";") return cont(forspec3);
+ if (value == "in") return cont(expression);
+ return cont(expression, expect(";"), forspec3);
+ }
+ function forspec3(type) {
+ if (type != ")") cont(expression);
+ }
+ function functiondef(type, value) {
+ if (type == "variable") {register(value); return cont(functiondef);}
+ if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
+ }
+ function funarg(type, value) {
+ if (type == "variable") {register(value); return cont();}
+ }
+
+ // Interface
+
+ return {
+ startState: function(basecolumn) {
+ return {
+ tokenize: jsTokenBase,
+ reAllowed: true,
+ kwAllowed: true,
+ cc: [],
+ lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
+ localVars: parserConfig.localVars,
+ context: parserConfig.localVars && {vars: parserConfig.localVars},
+ indented: 0
+ };
+ },
+
+ token: function(stream, state) {
+ if (stream.sol()) {
+ if (!state.lexical.hasOwnProperty("align"))
+ state.lexical.align = false;
+ state.indented = stream.indentation();
+ }
+ if (stream.eatSpace()) return null;
+ var style = state.tokenize(stream, state);
+ if (type == "comment") return style;
+ state.reAllowed = !!(type == "operator" || type == "keyword c" || type.match(/^[\[{}\(,;:]$/));
+ state.kwAllowed = type != '.';
+ return parseJS(state, style, type, content, stream);
+ },
+
+ indent: function(state, textAfter) {
+ if (state.tokenize != jsTokenBase) return 0;
+ var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
+ if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
+ var type = lexical.type, closing = firstChar == type;
+ if (type == "vardef") return lexical.indented + 4;
+ else if (type == "form" && firstChar == "{") return lexical.indented;
+ else if (type == "stat" || type == "form") return lexical.indented + indentUnit;
+ else if (lexical.info == "switch" && !closing)
+ return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
+ else if (lexical.align) return lexical.column + (closing ? 0 : 1);
+ else return lexical.indented + (closing ? 0 : indentUnit);
+ },
+
+ electricChars: ":{}"
+ };
+});
+
+CodeMirror.defineMIME("text/javascript", "javascript");
+CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
diff --git a/src/fauxton/assets/js/plugins/prettify.js b/src/fauxton/assets/js/plugins/prettify.js
new file mode 100644
index 000000000..eef5ad7e6
--- /dev/null
+++ b/src/fauxton/assets/js/plugins/prettify.js
@@ -0,0 +1,28 @@
+var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
+[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
+f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
+(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
+{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
+t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
+"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
+l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
+q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
+"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
+a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
+for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
+m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
+a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
+j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
+H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
+I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
+["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
+/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
+["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
+hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
+!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
+250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
+PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
diff --git a/src/fauxton/assets/less/bootstrap/accordion.less b/src/fauxton/assets/less/bootstrap/accordion.less
new file mode 100644
index 000000000..d63523bc8
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/accordion.less
@@ -0,0 +1,34 @@
+//
+// Accordion
+// --------------------------------------------------
+
+
+// Parent container
+.accordion {
+ margin-bottom: @baseLineHeight;
+}
+
+// Group == heading + body
+.accordion-group {
+ margin-bottom: 2px;
+ border: 1px solid #e5e5e5;
+ .border-radius(@baseBorderRadius);
+}
+.accordion-heading {
+ border-bottom: 0;
+}
+.accordion-heading .accordion-toggle {
+ display: block;
+ padding: 8px 15px;
+}
+
+// General toggle styles
+.accordion-toggle {
+ cursor: pointer;
+}
+
+// Inner needs the styles because you can't animate properly with any styles on the element
+.accordion-inner {
+ padding: 9px 15px;
+ border-top: 1px solid #e5e5e5;
+}
diff --git a/src/fauxton/assets/less/bootstrap/alerts.less b/src/fauxton/assets/less/bootstrap/alerts.less
new file mode 100644
index 000000000..9abb226d6
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/alerts.less
@@ -0,0 +1,65 @@
+//
+// Alerts
+// --------------------------------------------------
+
+
+// Base styles
+// -------------------------
+
+.alert {
+ padding: 8px 35px 8px 14px;
+ margin-bottom: @baseLineHeight;
+ text-shadow: 0 1px 0 rgba(255,255,255,.5);
+ background-color: @warningBackground;
+ border: 1px solid @warningBorder;
+ .border-radius(@baseBorderRadius);
+ color: @warningText;
+}
+.alert h4 {
+ margin: 0;
+}
+
+// Adjust close link position
+.alert .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ line-height: @baseLineHeight;
+}
+
+
+// Alternate styles
+// -------------------------
+
+.alert-success {
+ background-color: @successBackground;
+ border-color: @successBorder;
+ color: @successText;
+}
+.alert-danger,
+.alert-error {
+ background-color: @errorBackground;
+ border-color: @errorBorder;
+ color: @errorText;
+}
+.alert-info {
+ background-color: @infoBackground;
+ border-color: @infoBorder;
+ color: @infoText;
+}
+
+
+// Block alerts
+// -------------------------
+
+.alert-block {
+ padding-top: 14px;
+ padding-bottom: 14px;
+}
+.alert-block > p,
+.alert-block > ul {
+ margin-bottom: 0;
+}
+.alert-block p + p {
+ margin-top: 5px;
+}
diff --git a/src/fauxton/assets/less/bootstrap/bootstrap.less b/src/fauxton/assets/less/bootstrap/bootstrap.less
new file mode 100644
index 000000000..14bb3f044
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/bootstrap.less
@@ -0,0 +1,63 @@
+/*!
+ * Bootstrap v2.2.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+// CSS Reset
+@import "reset.less";
+
+// Core variables and mixins
+@import "variables.less"; // Modify this for custom colors, font-sizes, etc
+@import "mixins.less";
+
+// Grid system and page structure
+@import "scaffolding.less";
+@import "grid.less";
+@import "layouts.less";
+
+// Base CSS
+@import "type.less";
+@import "code.less";
+@import "forms.less";
+@import "tables.less";
+
+// Components: common
+@import "sprites.less";
+@import "dropdowns.less";
+@import "wells.less";
+@import "component-animations.less";
+@import "close.less";
+
+// Components: Buttons & Alerts
+@import "buttons.less";
+@import "button-groups.less";
+@import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less
+
+// Components: Nav
+@import "navs.less";
+@import "navbar.less";
+@import "breadcrumbs.less";
+@import "pagination.less";
+@import "pager.less";
+
+// Components: Popovers
+@import "modals.less";
+@import "tooltip.less";
+@import "popovers.less";
+
+// Components: Misc
+@import "thumbnails.less";
+@import "media.less";
+@import "labels-badges.less";
+@import "progress-bars.less";
+@import "accordion.less";
+@import "carousel.less";
+@import "hero-unit.less";
+
+// Utility classes
+@import "utilities.less"; // Has to be last to override when necessary
diff --git a/src/fauxton/assets/less/bootstrap/breadcrumbs.less b/src/fauxton/assets/less/bootstrap/breadcrumbs.less
new file mode 100644
index 000000000..76fbe30ff
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/breadcrumbs.less
@@ -0,0 +1,24 @@
+//
+// Breadcrumbs
+// --------------------------------------------------
+
+
+.breadcrumb {
+ padding: 8px 15px;
+ margin: 0 0 @baseLineHeight;
+ list-style: none;
+ background-color: #f5f5f5;
+ .border-radius(@baseBorderRadius);
+ li {
+ display: inline-block;
+ .ie7-inline-block();
+ text-shadow: 0 1px 0 @white;
+ }
+ .divider {
+ padding: 0 5px;
+ color: #ccc;
+ }
+ .active {
+ color: @grayLight;
+ }
+}
diff --git a/src/fauxton/assets/less/bootstrap/button-groups.less b/src/fauxton/assets/less/bootstrap/button-groups.less
new file mode 100644
index 000000000..46837e628
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/button-groups.less
@@ -0,0 +1,242 @@
+//
+// Button groups
+// --------------------------------------------------
+
+
+// Make the div behave like a button
+.btn-group {
+ position: relative;
+ display: inline-block;
+ .ie7-inline-block();
+ font-size: 0; // remove as part 1 of font-size inline-block hack
+ vertical-align: middle; // match .btn alignment given font-size hack above
+ white-space: nowrap; // prevent buttons from wrapping when in tight spaces (e.g., the table on the tests page)
+ .ie7-restore-left-whitespace();
+}
+
+// Space out series of button groups
+.btn-group + .btn-group {
+ margin-left: 5px;
+}
+
+// Optional: Group multiple button groups together for a toolbar
+.btn-toolbar {
+ font-size: 0; // Hack to remove whitespace that results from using inline-block
+ margin-top: @baseLineHeight / 2;
+ margin-bottom: @baseLineHeight / 2;
+ .btn + .btn,
+ .btn-group + .btn,
+ .btn + .btn-group {
+ margin-left: 5px;
+ }
+}
+
+// Float them, remove border radius, then re-add to first and last elements
+.btn-group > .btn {
+ position: relative;
+ .border-radius(0);
+}
+.btn-group > .btn + .btn {
+ margin-left: -1px;
+}
+.btn-group > .btn,
+.btn-group > .dropdown-menu {
+ font-size: @baseFontSize; // redeclare as part 2 of font-size inline-block hack
+}
+
+// Reset fonts for other sizes
+.btn-group > .btn-mini {
+ font-size: 11px;
+}
+.btn-group > .btn-small {
+ font-size: 12px;
+}
+.btn-group > .btn-large {
+ font-size: 16px;
+}
+
+// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match
+.btn-group > .btn:first-child {
+ margin-left: 0;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+ border-top-left-radius: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ border-bottom-left-radius: 4px;
+}
+// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it
+.btn-group > .btn:last-child,
+.btn-group > .dropdown-toggle {
+ -webkit-border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ border-bottom-right-radius: 4px;
+}
+// Reset corners for large buttons
+.btn-group > .btn.large:first-child {
+ margin-left: 0;
+ -webkit-border-top-left-radius: 6px;
+ -moz-border-radius-topleft: 6px;
+ border-top-left-radius: 6px;
+ -webkit-border-bottom-left-radius: 6px;
+ -moz-border-radius-bottomleft: 6px;
+ border-bottom-left-radius: 6px;
+}
+.btn-group > .btn.large:last-child,
+.btn-group > .large.dropdown-toggle {
+ -webkit-border-top-right-radius: 6px;
+ -moz-border-radius-topright: 6px;
+ border-top-right-radius: 6px;
+ -webkit-border-bottom-right-radius: 6px;
+ -moz-border-radius-bottomright: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+// On hover/focus/active, bring the proper btn to front
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active {
+ z-index: 2;
+}
+
+// On active and open, don't show outline
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0;
+}
+
+
+
+// Split button dropdowns
+// ----------------------
+
+// Give the line between buttons some depth
+.btn-group > .btn + .dropdown-toggle {
+ padding-left: 8px;
+ padding-right: 8px;
+ .box-shadow(~"inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)");
+ *padding-top: 5px;
+ *padding-bottom: 5px;
+}
+.btn-group > .btn-mini + .dropdown-toggle {
+ padding-left: 5px;
+ padding-right: 5px;
+ *padding-top: 2px;
+ *padding-bottom: 2px;
+}
+.btn-group > .btn-small + .dropdown-toggle {
+ *padding-top: 5px;
+ *padding-bottom: 4px;
+}
+.btn-group > .btn-large + .dropdown-toggle {
+ padding-left: 12px;
+ padding-right: 12px;
+ *padding-top: 7px;
+ *padding-bottom: 7px;
+}
+
+.btn-group.open {
+
+ // The clickable button for toggling the menu
+ // Remove the gradient and set the same inset shadow as the :active state
+ .dropdown-toggle {
+ background-image: none;
+ .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)");
+ }
+
+ // Keep the hover's background when dropdown is open
+ .btn.dropdown-toggle {
+ background-color: @btnBackgroundHighlight;
+ }
+ .btn-primary.dropdown-toggle {
+ background-color: @btnPrimaryBackgroundHighlight;
+ }
+ .btn-warning.dropdown-toggle {
+ background-color: @btnWarningBackgroundHighlight;
+ }
+ .btn-danger.dropdown-toggle {
+ background-color: @btnDangerBackgroundHighlight;
+ }
+ .btn-success.dropdown-toggle {
+ background-color: @btnSuccessBackgroundHighlight;
+ }
+ .btn-info.dropdown-toggle {
+ background-color: @btnInfoBackgroundHighlight;
+ }
+ .btn-inverse.dropdown-toggle {
+ background-color: @btnInverseBackgroundHighlight;
+ }
+}
+
+
+// Reposition the caret
+.btn .caret {
+ margin-top: 8px;
+ margin-left: 0;
+}
+// Carets in other button sizes
+.btn-mini .caret,
+.btn-small .caret,
+.btn-large .caret {
+ margin-top: 6px;
+}
+.btn-large .caret {
+ border-left-width: 5px;
+ border-right-width: 5px;
+ border-top-width: 5px;
+}
+// Upside down carets for .dropup
+.dropup .btn-large .caret {
+ border-bottom-width: 5px;
+}
+
+
+
+// Account for other colors
+.btn-primary,
+.btn-warning,
+.btn-danger,
+.btn-info,
+.btn-success,
+.btn-inverse {
+ .caret {
+ border-top-color: @white;
+ border-bottom-color: @white;
+ }
+}
+
+
+
+// Vertical button groups
+// ----------------------
+
+.btn-group-vertical {
+ display: inline-block; // makes buttons only take up the width they need
+ .ie7-inline-block();
+}
+.btn-group-vertical .btn {
+ display: block;
+ float: none;
+ width: 100%;
+ .border-radius(0);
+}
+.btn-group-vertical .btn + .btn {
+ margin-left: 0;
+ margin-top: -1px;
+}
+.btn-group-vertical .btn:first-child {
+ .border-radius(4px 4px 0 0);
+}
+.btn-group-vertical .btn:last-child {
+ .border-radius(0 0 4px 4px);
+}
+.btn-group-vertical .btn-large:first-child {
+ .border-radius(6px 6px 0 0);
+}
+.btn-group-vertical .btn-large:last-child {
+ .border-radius(0 0 6px 6px);
+}
diff --git a/src/fauxton/assets/less/bootstrap/buttons.less b/src/fauxton/assets/less/bootstrap/buttons.less
new file mode 100644
index 000000000..63f2d86c8
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/buttons.less
@@ -0,0 +1,232 @@
+//
+// Buttons
+// --------------------------------------------------
+
+
+// Base styles
+// --------------------------------------------------
+
+// Core
+.btn {
+ display: inline-block;
+ .ie7-inline-block();
+ padding: 4px 12px;
+ margin-bottom: 0; // For input.btn
+ font-size: @baseFontSize;
+ line-height: @baseLineHeight;
+ *line-height: @baseLineHeight;
+ text-align: center;
+ vertical-align: middle;
+ cursor: pointer;
+ .buttonBackground(@btnBackground, @btnBackgroundHighlight, @grayDark, 0 1px 1px rgba(255,255,255,.75));
+ border: 1px solid @btnBorder;
+ *border: 0; // Remove the border to prevent IE7's black border on input:focus
+ border-bottom-color: darken(@btnBorder, 10%);
+ .border-radius(@baseBorderRadius);
+ .ie7-restore-left-whitespace(); // Give IE7 some love
+ .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)");
+
+ // Hover state
+ &:hover {
+ color: @grayDark;
+ text-decoration: none;
+ background-color: darken(@white, 10%);
+ *background-color: darken(@white, 15%); /* Buttons in IE7 don't get borders, so darken on hover */
+ background-position: 0 -15px;
+
+ // transition is only when going to hover, otherwise the background
+ // behind the gradient (there for IE<=9 fallback) gets mismatched
+ .transition(background-position .1s linear);
+ }
+
+ // Focus state for keyboard and accessibility
+ &:focus {
+ .tab-focus();
+ }
+
+ // Active state
+ &.active,
+ &:active {
+ background-color: darken(@white, 10%);
+ background-color: darken(@white, 15%) e("\9");
+ background-image: none;
+ outline: 0;
+ .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)");
+ }
+
+ // Disabled state
+ &.disabled,
+ &[disabled] {
+ cursor: default;
+ background-color: darken(@white, 10%);
+ background-image: none;
+ .opacity(65);
+ .box-shadow(none);
+ }
+
+}
+
+
+
+// Button Sizes
+// --------------------------------------------------
+
+// Large
+.btn-large {
+ padding: @paddingLarge;
+ font-size: @fontSizeLarge;
+ .border-radius(@borderRadiusLarge);
+}
+.btn-large [class^="icon-"],
+.btn-large [class*=" icon-"] {
+ margin-top: 2px;
+}
+
+// Small
+.btn-small {
+ padding: @paddingSmall;
+ font-size: @fontSizeSmall;
+ .border-radius(@borderRadiusSmall);
+}
+.btn-small [class^="icon-"],
+.btn-small [class*=" icon-"] {
+ margin-top: 0;
+}
+
+// Mini
+.btn-mini {
+ padding: @paddingMini;
+ font-size: @fontSizeMini;
+ .border-radius(@borderRadiusSmall);
+}
+
+
+// Block button
+// -------------------------
+
+.btn-block {
+ display: block;
+ width: 100%;
+ padding-left: 0;
+ padding-right: 0;
+ .box-sizing(border-box);
+}
+
+// Vertically space out multiple block buttons
+.btn-block + .btn-block {
+ margin-top: 5px;
+}
+
+// Specificity overrides
+input[type="submit"],
+input[type="reset"],
+input[type="button"] {
+ &.btn-block {
+ width: 100%;
+ }
+}
+
+
+
+// Alternate buttons
+// --------------------------------------------------
+
+// Provide *some* extra contrast for those who can get it
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+ color: rgba(255,255,255,.75);
+}
+
+// Set the backgrounds
+// -------------------------
+.btn {
+ // reset here as of 2.0.3 due to Recess property order
+ border-color: #c5c5c5;
+ border-color: rgba(0,0,0,.15) rgba(0,0,0,.15) rgba(0,0,0,.25);
+}
+.btn-primary {
+ .buttonBackground(@btnPrimaryBackground, @btnPrimaryBackgroundHighlight);
+}
+// Warning appears are orange
+.btn-warning {
+ .buttonBackground(@btnWarningBackground, @btnWarningBackgroundHighlight);
+}
+// Danger and error appear as red
+.btn-danger {
+ .buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight);
+}
+// Success appears as green
+.btn-success {
+ .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight);
+}
+// Info appears as a neutral blue
+.btn-info {
+ .buttonBackground(@btnInfoBackground, @btnInfoBackgroundHighlight);
+}
+// Inverse appears as dark gray
+.btn-inverse {
+ .buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight);
+}
+
+
+// Cross-browser Jank
+// --------------------------------------------------
+
+button.btn,
+input[type="submit"].btn {
+
+ // Firefox 3.6 only I believe
+ &::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+ }
+
+ // IE7 has some default padding on button controls
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+
+ &.btn-large {
+ *padding-top: 7px;
+ *padding-bottom: 7px;
+ }
+ &.btn-small {
+ *padding-top: 3px;
+ *padding-bottom: 3px;
+ }
+ &.btn-mini {
+ *padding-top: 1px;
+ *padding-bottom: 1px;
+ }
+}
+
+
+// Link buttons
+// --------------------------------------------------
+
+// Make a button look and behave like a link
+.btn-link,
+.btn-link:active,
+.btn-link[disabled] {
+ background-color: transparent;
+ background-image: none;
+ .box-shadow(none);
+}
+.btn-link {
+ border-color: transparent;
+ cursor: pointer;
+ color: @linkColor;
+ .border-radius(0);
+}
+.btn-link:hover {
+ color: @linkColorHover;
+ text-decoration: underline;
+ background-color: transparent;
+}
+.btn-link[disabled]:hover {
+ color: @grayDark;
+ text-decoration: none;
+}
diff --git a/src/fauxton/assets/less/bootstrap/carousel.less b/src/fauxton/assets/less/bootstrap/carousel.less
new file mode 100644
index 000000000..33f98ac4d
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/carousel.less
@@ -0,0 +1,131 @@
+//
+// Carousel
+// --------------------------------------------------
+
+
+.carousel {
+ position: relative;
+ margin-bottom: @baseLineHeight;
+ line-height: 1;
+}
+
+.carousel-inner {
+ overflow: hidden;
+ width: 100%;
+ position: relative;
+}
+
+.carousel {
+
+ .item {
+ display: none;
+ position: relative;
+ .transition(.6s ease-in-out left);
+ }
+
+ // Account for jankitude on images
+ .item > img {
+ display: block;
+ line-height: 1;
+ }
+
+ .active,
+ .next,
+ .prev { display: block; }
+
+ .active {
+ left: 0;
+ }
+
+ .next,
+ .prev {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+
+ .next {
+ left: 100%;
+ }
+ .prev {
+ left: -100%;
+ }
+ .next.left,
+ .prev.right {
+ left: 0;
+ }
+
+ .active.left {
+ left: -100%;
+ }
+ .active.right {
+ left: 100%;
+ }
+
+}
+
+// Left/right controls for nav
+// ---------------------------
+
+.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: @white;
+ text-align: center;
+ background: @grayDarker;
+ border: 3px solid @white;
+ .border-radius(23px);
+ .opacity(50);
+
+ // we can't have this transition here
+ // because webkit cancels the carousel
+ // animation if you trip this while
+ // in the middle of another animation
+ // ;_;
+ // .transition(opacity .2s linear);
+
+ // Reposition the right one
+ &.right {
+ left: auto;
+ right: 15px;
+ }
+
+ // Hover state
+ &:hover {
+ color: @white;
+ text-decoration: none;
+ .opacity(90);
+ }
+}
+
+
+// Caption for text below images
+// -----------------------------
+
+.carousel-caption {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ padding: 15px;
+ background: @grayDark;
+ background: rgba(0,0,0,.75);
+}
+.carousel-caption h4,
+.carousel-caption p {
+ color: @white;
+ line-height: @baseLineHeight;
+}
+.carousel-caption h4 {
+ margin: 0 0 5px;
+}
+.carousel-caption p {
+ margin-bottom: 0;
+}
diff --git a/src/fauxton/assets/less/bootstrap/close.less b/src/fauxton/assets/less/bootstrap/close.less
new file mode 100644
index 000000000..c71a508f3
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/close.less
@@ -0,0 +1,31 @@
+//
+// Close icons
+// --------------------------------------------------
+
+
+.close {
+ float: right;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: @baseLineHeight;
+ color: @black;
+ text-shadow: 0 1px 0 rgba(255,255,255,1);
+ .opacity(20);
+ &:hover {
+ color: @black;
+ text-decoration: none;
+ cursor: pointer;
+ .opacity(40);
+ }
+}
+
+// Additional properties for button version
+// iOS requires the button element instead of an anchor tag.
+// If you want the anchor version, it requires `href="#"`.
+button.close {
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ -webkit-appearance: none;
+} \ No newline at end of file
diff --git a/src/fauxton/assets/less/bootstrap/code.less b/src/fauxton/assets/less/bootstrap/code.less
new file mode 100644
index 000000000..5495b15ec
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/code.less
@@ -0,0 +1,58 @@
+//
+// Code (inline and blocK)
+// --------------------------------------------------
+
+
+// Inline and block code styles
+code,
+pre {
+ padding: 0 3px 2px;
+ #font > #family > .monospace;
+ font-size: @baseFontSize - 2;
+ color: @grayDark;
+ .border-radius(3px);
+}
+
+// Inline code
+code {
+ padding: 2px 4px;
+ color: #d14;
+ background-color: #f7f7f9;
+ border: 1px solid #e1e1e8;
+}
+
+// Blocks of code
+pre {
+ display: block;
+ padding: (@baseLineHeight - 1) / 2;
+ margin: 0 0 @baseLineHeight / 2;
+ font-size: @baseFontSize - 1; // 14px to 13px
+ line-height: @baseLineHeight;
+ word-break: break-all;
+ word-wrap: break-word;
+ white-space: pre;
+ white-space: pre-wrap;
+ background-color: #f5f5f5;
+ border: 1px solid #ccc; // fallback for IE7-8
+ border: 1px solid rgba(0,0,0,.15);
+ .border-radius(@baseBorderRadius);
+
+ // Make prettyprint styles more spaced out for readability
+ &.prettyprint {
+ margin-bottom: @baseLineHeight;
+ }
+
+ // Account for some code outputs that place code tags in pre tags
+ code {
+ padding: 0;
+ color: inherit;
+ background-color: transparent;
+ border: 0;
+ }
+}
+
+// Enable scrollable blocks of code
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll;
+} \ No newline at end of file
diff --git a/src/fauxton/assets/less/bootstrap/component-animations.less b/src/fauxton/assets/less/bootstrap/component-animations.less
new file mode 100644
index 000000000..d614263a7
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/component-animations.less
@@ -0,0 +1,22 @@
+//
+// Component animations
+// --------------------------------------------------
+
+
+.fade {
+ opacity: 0;
+ .transition(opacity .15s linear);
+ &.in {
+ opacity: 1;
+ }
+}
+
+.collapse {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ .transition(height .35s ease);
+ &.in {
+ height: auto;
+ }
+}
diff --git a/src/fauxton/assets/less/bootstrap/dropdowns.less b/src/fauxton/assets/less/bootstrap/dropdowns.less
new file mode 100644
index 000000000..26ca0f9ea
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/dropdowns.less
@@ -0,0 +1,237 @@
+//
+// Dropdown menus
+// --------------------------------------------------
+
+
+// Use the .menu class on any <li> element within the topbar or ul.tabs and you'll get some superfancy dropdowns
+.dropup,
+.dropdown {
+ position: relative;
+}
+.dropdown-toggle {
+ // The caret makes the toggle a bit too tall in IE7
+ *margin-bottom: -3px;
+}
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+ outline: 0;
+}
+
+// Dropdown arrow/caret
+// --------------------
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ vertical-align: top;
+ border-top: 4px solid @black;
+ border-right: 4px solid transparent;
+ border-left: 4px solid transparent;
+ content: "";
+}
+
+// Place the caret
+.dropdown .caret {
+ margin-top: 8px;
+ margin-left: 2px;
+}
+
+// The dropdown menu (ul)
+// ----------------------
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: @zindexDropdown;
+ display: none; // none by default, but block on "open" of the menu
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0; // override default ul
+ list-style: none;
+ background-color: @dropdownBackground;
+ border: 1px solid #ccc; // Fallback for IE7-8
+ border: 1px solid @dropdownBorder;
+ *border-right-width: 2px;
+ *border-bottom-width: 2px;
+ .border-radius(6px);
+ .box-shadow(0 5px 10px rgba(0,0,0,.2));
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+
+ // Aligns the dropdown menu to right
+ &.pull-right {
+ right: 0;
+ left: auto;
+ }
+
+ // Dividers (basically an hr) within the dropdown
+ .divider {
+ .nav-divider(@dropdownDividerTop, @dropdownDividerBottom);
+ }
+
+ // Links within the dropdown menu
+ li > a {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: @baseLineHeight;
+ color: @dropdownLinkColor;
+ white-space: nowrap;
+ }
+}
+
+// Hover state
+// -----------
+.dropdown-menu li > a:hover,
+.dropdown-menu li > a:focus,
+.dropdown-submenu:hover > a {
+ text-decoration: none;
+ color: @dropdownLinkColorHover;
+ #gradient > .vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%));
+}
+
+// Active state
+// ------------
+.dropdown-menu .active > a,
+.dropdown-menu .active > a:hover {
+ color: @dropdownLinkColorActive;
+ text-decoration: none;
+ outline: 0;
+ #gradient > .vertical(@dropdownLinkBackgroundActive, darken(@dropdownLinkBackgroundActive, 5%));
+}
+
+// Disabled state
+// --------------
+// Gray out text and ensure the hover state remains gray
+.dropdown-menu .disabled > a,
+.dropdown-menu .disabled > a:hover {
+ color: @grayLight;
+}
+// Nuke hover effects
+.dropdown-menu .disabled > a:hover {
+ text-decoration: none;
+ background-color: transparent;
+ background-image: none; // Remove CSS gradient
+ cursor: default;
+}
+
+// Open state for the dropdown
+// ---------------------------
+.open {
+ // IE7's z-index only goes to the nearest positioned ancestor, which would
+ // make the menu appear below buttons that appeared later on the page
+ *z-index: @zindexDropdown;
+
+ & > .dropdown-menu {
+ display: block;
+ }
+}
+
+// Right aligned dropdowns
+// ---------------------------
+.pull-right > .dropdown-menu {
+ right: 0;
+ left: auto;
+}
+
+// Allow for dropdowns to go bottom up (aka, dropup-menu)
+// ------------------------------------------------------
+// Just add .dropup after the standard .dropdown class and you're set, bro.
+// TODO: abstract this so that the navbar fixed styles are not placed here?
+.dropup,
+.navbar-fixed-bottom .dropdown {
+ // Reverse the caret
+ .caret {
+ border-top: 0;
+ border-bottom: 4px solid @black;
+ content: "";
+ }
+ // Different positioning for bottom up menu
+ .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 1px;
+ }
+}
+
+// Sub menus
+// ---------------------------
+.dropdown-submenu {
+ position: relative;
+}
+// Default dropdowns
+.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;
+}
+
+// Dropups
+.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;
+}
+
+// Caret to indicate there is a submenu
+.dropdown-submenu > a:after {
+ display: block;
+ content: " ";
+ float: right;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+ border-width: 5px 0 5px 5px;
+ border-left-color: darken(@dropdownBackground, 20%);
+ margin-top: 5px;
+ margin-right: -10px;
+}
+.dropdown-submenu:hover > a:after {
+ border-left-color: @dropdownLinkColorHover;
+}
+
+// Left aligned submenus
+.dropdown-submenu.pull-left {
+ // Undo the float
+ // Yes, this is awkward since .pull-left adds a float, but it sticks to our conventions elsewhere.
+ float: none;
+
+ // Positioning the submenu
+ > .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;
+ }
+}
+
+// Tweak nav headers
+// -----------------
+// Increase padding from 15px to 20px on sides
+.dropdown .dropdown-menu .nav-header {
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+// Typeahead
+// ---------
+.typeahead {
+ margin-top: 2px; // give it some space to breathe
+ .border-radius(@baseBorderRadius);
+}
diff --git a/src/fauxton/assets/less/bootstrap/forms.less b/src/fauxton/assets/less/bootstrap/forms.less
new file mode 100644
index 000000000..e142f2ac3
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/forms.less
@@ -0,0 +1,683 @@
+//
+// Forms
+// --------------------------------------------------
+
+
+// GENERAL STYLES
+// --------------
+
+// Make all forms have space below them
+form {
+ margin: 0 0 @baseLineHeight;
+}
+
+fieldset {
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+
+// Groups of fields with labels on top (legends)
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: @baseLineHeight;
+ font-size: @baseFontSize * 1.5;
+ line-height: @baseLineHeight * 2;
+ color: @grayDark;
+ border: 0;
+ border-bottom: 1px solid #e5e5e5;
+
+ // Small
+ small {
+ font-size: @baseLineHeight * .75;
+ color: @grayLight;
+ }
+}
+
+// Set font for forms
+label,
+input,
+button,
+select,
+textarea {
+ #font > .shorthand(@baseFontSize,normal,@baseLineHeight); // Set size, weight, line-height here
+}
+input,
+button,
+select,
+textarea {
+ font-family: @baseFontFamily; // And only set font-family here for those that need it (note the missing label element)
+}
+
+// Identify controls by their labels
+label {
+ display: block;
+ margin-bottom: 5px;
+}
+
+// Form controls
+// -------------------------
+
+// Shared size and type resets
+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: @baseLineHeight;
+ padding: 4px 6px;
+ margin-bottom: @baseLineHeight / 2;
+ font-size: @baseFontSize;
+ line-height: @baseLineHeight;
+ color: @gray;
+ .border-radius(@inputBorderRadius);
+ vertical-align: middle;
+}
+
+// Reset appearance properties for textual inputs and textarea
+// Declare width for legacy (can't be on input[type=*] selectors or it's too specific)
+input,
+textarea,
+.uneditable-input {
+ width: 206px; // plus 12px padding and 2px border
+}
+// Reset height since textareas have rows
+textarea {
+ height: auto;
+}
+// Everything else
+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: @inputBackground;
+ border: 1px solid @inputBorder;
+ .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));
+ .transition(~"border linear .2s, box-shadow linear .2s");
+
+ // Focus state
+ &:focus {
+ border-color: rgba(82,168,236,.8);
+ outline: 0;
+ outline: thin dotted \9; /* IE6-9 */
+ .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6)");
+ }
+}
+
+// Position radios and checkboxes better
+input[type="radio"],
+input[type="checkbox"] {
+ margin: 4px 0 0;
+ *margin-top: 0; /* IE7 */
+ margin-top: 1px \9; /* IE8-9 */
+ line-height: normal;
+ cursor: pointer;
+}
+
+// Reset width of input images, buttons, radios, checkboxes
+input[type="file"],
+input[type="image"],
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+input[type="radio"],
+input[type="checkbox"] {
+ width: auto; // Override of generic input selector
+}
+
+// Set the height of select and file controls to match text inputs
+select,
+input[type="file"] {
+ height: @inputHeight; /* In IE7, the height of the select element cannot be changed by height, only font-size */
+ *margin-top: 4px; /* For IE7, add top margin to align select with labels */
+ line-height: @inputHeight;
+}
+
+// Make select elements obey height by applying a border
+select {
+ width: 220px; // default input width + 10px of padding that doesn't get applied
+ border: 1px solid @inputBorder;
+ background-color: @inputBackground; // Chrome on Linux and Mobile Safari need background-color
+}
+
+// Make multiple select elements height not fixed
+select[multiple],
+select[size] {
+ height: auto;
+}
+
+// Focus for select, file, radio, and checkbox
+select:focus,
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+ .tab-focus();
+}
+
+
+// Uneditable inputs
+// -------------------------
+
+// Make uneditable inputs look inactive
+.uneditable-input,
+.uneditable-textarea {
+ color: @grayLight;
+ background-color: darken(@inputBackground, 1%);
+ border-color: @inputBorder;
+ .box-shadow(inset 0 1px 2px rgba(0,0,0,.025));
+ cursor: not-allowed;
+}
+
+// For text that needs to appear as an input but should not be an input
+.uneditable-input {
+ overflow: hidden; // prevent text from wrapping, but still cut it off like an input does
+ white-space: nowrap;
+}
+
+// Make uneditable textareas behave like a textarea
+.uneditable-textarea {
+ width: auto;
+ height: auto;
+}
+
+
+// Placeholder
+// -------------------------
+
+// Placeholder text gets special styles because when browsers invalidate entire lines if it doesn't understand a selector
+input,
+textarea {
+ .placeholder();
+}
+
+
+// CHECKBOXES & RADIOS
+// -------------------
+
+// Indent the labels to position radios/checkboxes as hanging
+.radio,
+.checkbox {
+ min-height: @baseLineHeight; // clear the floating input if there is no label text
+ padding-left: 20px;
+}
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+ float: left;
+ margin-left: -20px;
+}
+
+// Move the options list down to align with labels
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+ padding-top: 5px; // has to be padding because margin collaspes
+}
+
+// Radios and checkboxes on same line
+// TODO v3: Convert .inline to .control-inline
+.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; // space out consecutive inline controls
+}
+
+
+
+// INPUT SIZES
+// -----------
+
+// General classes for quick sizes
+.input-mini { width: 60px; }
+.input-small { width: 90px; }
+.input-medium { width: 150px; }
+.input-large { width: 210px; }
+.input-xlarge { width: 270px; }
+.input-xxlarge { width: 530px; }
+
+// Grid style input sizes
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input[class*="span"],
+// Redeclare since the fluid row class is more specific
+.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;
+}
+// Ensure input-prepend/append never wraps
+.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;
+}
+
+
+
+// GRID SIZING FOR INPUTS
+// ----------------------
+
+// Grid sizes
+#grid > .input(@gridColumnWidth, @gridGutterWidth);
+
+// Control row for multiple inputs per line
+.controls-row {
+ .clearfix(); // Clear the float from controls
+}
+
+// Float to collapse white-space for proper grid alignment
+.controls-row [class*="span"],
+// Redeclare the fluid grid collapse since we undo the float for inputs
+.row-fluid .controls-row [class*="span"] {
+ float: left;
+}
+// Explicity set top padding on all checkboxes/radios, not just first-child
+.controls-row .checkbox[class*="span"],
+.controls-row .radio[class*="span"] {
+ padding-top: 5px;
+}
+
+
+
+
+// DISABLED STATE
+// --------------
+
+// Disabled and read-only inputs
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+ cursor: not-allowed;
+ background-color: @inputDisabledBackground;
+}
+// Explicitly reset the colors here
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"][readonly],
+input[type="checkbox"][readonly] {
+ background-color: transparent;
+}
+
+
+
+
+// FORM FIELD FEEDBACK STATES
+// --------------------------
+
+// Warning
+.control-group.warning {
+ .formFieldState(@warningText, @warningText, @warningBackground);
+}
+// Error
+.control-group.error {
+ .formFieldState(@errorText, @errorText, @errorBackground);
+}
+// Success
+.control-group.success {
+ .formFieldState(@successText, @successText, @successBackground);
+}
+// Success
+.control-group.info {
+ .formFieldState(@infoText, @infoText, @infoBackground);
+}
+
+// HTML5 invalid states
+// Shares styles with the .control-group.error above
+input:focus:required:invalid,
+textarea:focus:required:invalid,
+select:focus:required:invalid {
+ color: #b94a48;
+ border-color: #ee5f5b;
+ &:focus {
+ border-color: darken(#ee5f5b, 10%);
+ @shadow: 0 0 6px lighten(#ee5f5b, 20%);
+ .box-shadow(@shadow);
+ }
+}
+
+
+
+// FORM ACTIONS
+// ------------
+
+.form-actions {
+ padding: (@baseLineHeight - 1) 20px @baseLineHeight;
+ margin-top: @baseLineHeight;
+ margin-bottom: @baseLineHeight;
+ background-color: @formActionsBackground;
+ border-top: 1px solid #e5e5e5;
+ .clearfix(); // Adding clearfix to allow for .pull-right button containers
+}
+
+
+
+// HELP TEXT
+// ---------
+
+.help-block,
+.help-inline {
+ color: lighten(@textColor, 15%); // lighten the text some for contrast
+}
+
+.help-block {
+ display: block; // account for any element using help-block
+ margin-bottom: @baseLineHeight / 2;
+}
+
+.help-inline {
+ display: inline-block;
+ .ie7-inline-block();
+ vertical-align: middle;
+ padding-left: 5px;
+}
+
+
+
+// INPUT GROUPS
+// ------------
+
+// Allow us to put symbols and text within the input field for a cleaner look
+.input-append,
+.input-prepend {
+ margin-bottom: 5px;
+ font-size: 0; // white space collapse hack
+ white-space: nowrap; // Prevent span and input from separating
+
+ // Reset the white space collapse hack
+ input,
+ select,
+ .uneditable-input,
+ .dropdown-menu {
+ font-size: @baseFontSize;
+ }
+
+ input,
+ select,
+ .uneditable-input {
+ position: relative; // placed here by default so that on :focus we can place the input above the .add-on for full border and box-shadow goodness
+ margin-bottom: 0; // prevent bottom margin from screwing up alignment in stacked forms
+ *margin-left: 0;
+ vertical-align: top;
+ .border-radius(0 @inputBorderRadius @inputBorderRadius 0);
+ // Make input on top when focused so blue border and shadow always show
+ &:focus {
+ z-index: 2;
+ }
+ }
+ .add-on {
+ display: inline-block;
+ width: auto;
+ height: @baseLineHeight;
+ min-width: 16px;
+ padding: 4px 5px;
+ font-size: @baseFontSize;
+ font-weight: normal;
+ line-height: @baseLineHeight;
+ text-align: center;
+ text-shadow: 0 1px 0 @white;
+ background-color: @grayLighter;
+ border: 1px solid #ccc;
+ }
+ .add-on,
+ .btn {
+ vertical-align: top;
+ .border-radius(0);
+ }
+ .active {
+ background-color: lighten(@green, 30);
+ border-color: @green;
+ }
+}
+
+.input-prepend {
+ .add-on,
+ .btn {
+ margin-right: -1px;
+ }
+ .add-on:first-child,
+ .btn:first-child {
+ // FYI, `.btn:first-child` accounts for a button group that's prepended
+ .border-radius(@inputBorderRadius 0 0 @inputBorderRadius);
+ }
+}
+
+.input-append {
+ input,
+ select,
+ .uneditable-input {
+ .border-radius(@inputBorderRadius 0 0 @inputBorderRadius);
+ + .btn-group .btn {
+ .border-radius(0 @inputBorderRadius @inputBorderRadius 0);
+ }
+ }
+ .add-on,
+ .btn,
+ .btn-group {
+ margin-left: -1px;
+ }
+ .add-on:last-child,
+ .btn:last-child {
+ .border-radius(0 @inputBorderRadius @inputBorderRadius 0);
+ }
+}
+
+// Remove all border-radius for inputs with both prepend and append
+.input-prepend.input-append {
+ input,
+ select,
+ .uneditable-input {
+ .border-radius(0);
+ + .btn-group .btn {
+ .border-radius(0 @inputBorderRadius @inputBorderRadius 0);
+ }
+ }
+ .add-on:first-child,
+ .btn:first-child {
+ margin-right: -1px;
+ .border-radius(@inputBorderRadius 0 0 @inputBorderRadius);
+ }
+ .add-on:last-child,
+ .btn:last-child {
+ margin-left: -1px;
+ .border-radius(0 @inputBorderRadius @inputBorderRadius 0);
+ }
+ .btn-group:first-child {
+ margin-left: 0;
+ }
+}
+
+
+
+
+// SEARCH FORM
+// -----------
+
+input.search-query {
+ padding-right: 14px;
+ padding-right: 4px \9;
+ padding-left: 14px;
+ padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */
+ margin-bottom: 0; // Remove the default margin on all inputs
+ .border-radius(15px);
+}
+
+/* Allow for input prepend/append in search forms */
+.form-search .input-append .search-query,
+.form-search .input-prepend .search-query {
+ .border-radius(0); // Override due to specificity
+}
+.form-search .input-append .search-query {
+ .border-radius(14px 0 0 14px);
+}
+.form-search .input-append .btn {
+ .border-radius(0 14px 14px 0);
+}
+.form-search .input-prepend .search-query {
+ .border-radius(0 14px 14px 0);
+}
+.form-search .input-prepend .btn {
+ .border-radius(14px 0 0 14px);
+}
+
+
+
+
+// HORIZONTAL & VERTICAL FORMS
+// ---------------------------
+
+// Common properties
+// -----------------
+
+.form-search,
+.form-inline,
+.form-horizontal {
+ input,
+ textarea,
+ select,
+ .help-inline,
+ .uneditable-input,
+ .input-prepend,
+ .input-append {
+ display: inline-block;
+ .ie7-inline-block();
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ // Re-hide hidden elements due to specifity
+ .hide {
+ display: none;
+ }
+}
+.form-search label,
+.form-inline label,
+.form-search .btn-group,
+.form-inline .btn-group {
+ display: inline-block;
+}
+// Remove margin for input-prepend/-append
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+ margin-bottom: 0;
+}
+// Inline checkbox/radio labels (remove padding on left)
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+ padding-left: 0;
+ margin-bottom: 0;
+ vertical-align: middle;
+}
+// Remove float and margin, set to inline-block
+.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;
+}
+
+
+// Margin to space out fieldsets
+.control-group {
+ margin-bottom: @baseLineHeight / 2;
+}
+
+// Legend collapses margin, so next element is responsible for spacing
+legend + .control-group {
+ margin-top: @baseLineHeight;
+ -webkit-margin-top-collapse: separate;
+}
+
+// Horizontal-specific styles
+// --------------------------
+
+.form-horizontal {
+ // Increase spacing between groups
+ .control-group {
+ margin-bottom: @baseLineHeight;
+ .clearfix();
+ }
+ // Float the labels left
+ .control-label {
+ float: left;
+ width: @horizontalComponentOffset - 20;
+ padding-top: 5px;
+ text-align: right;
+ }
+ // Move over all input controls and content
+ .controls {
+ // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend
+ // don't inherit the margin of the parent, in this case .controls
+ *display: inline-block;
+ *padding-left: 20px;
+ margin-left: @horizontalComponentOffset;
+ *margin-left: 0;
+ &:first-child {
+ *padding-left: @horizontalComponentOffset;
+ }
+ }
+ // Remove bottom margin on block level help text since that's accounted for on .control-group
+ .help-block {
+ margin-bottom: 0;
+ }
+ // And apply it only to .help-block instances that follow a form control
+ input,
+ select,
+ textarea {
+ + .help-block {
+ margin-top: @baseLineHeight / 2;
+ }
+ }
+ // Move over buttons in .form-actions to align with .controls
+ .form-actions {
+ padding-left: @horizontalComponentOffset;
+ }
+}
diff --git a/src/fauxton/assets/less/bootstrap/grid.less b/src/fauxton/assets/less/bootstrap/grid.less
new file mode 100644
index 000000000..750d20351
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/grid.less
@@ -0,0 +1,21 @@
+//
+// Grid system
+// --------------------------------------------------
+
+
+// Fixed (940px)
+#grid > .core(@gridColumnWidth, @gridGutterWidth);
+
+// Fluid (940px)
+#grid > .fluid(@fluidGridColumnWidth, @fluidGridGutterWidth);
+
+// Reset utility classes due to specificity
+[class*="span"].hide,
+.row-fluid [class*="span"].hide {
+ display: none;
+}
+
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+ float: right;
+}
diff --git a/src/fauxton/assets/less/bootstrap/hero-unit.less b/src/fauxton/assets/less/bootstrap/hero-unit.less
new file mode 100644
index 000000000..763d86aee
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/hero-unit.less
@@ -0,0 +1,25 @@
+//
+// Hero unit
+// --------------------------------------------------
+
+
+.hero-unit {
+ padding: 60px;
+ margin-bottom: 30px;
+ font-size: 18px;
+ font-weight: 200;
+ line-height: @baseLineHeight * 1.5;
+ color: @heroUnitLeadColor;
+ background-color: @heroUnitBackground;
+ .border-radius(6px);
+ h1 {
+ margin-bottom: 0;
+ font-size: 60px;
+ line-height: 1;
+ color: @heroUnitHeadingColor;
+ letter-spacing: -1px;
+ }
+ li {
+ line-height: @baseLineHeight * 1.5; // Reset since we specify in type.less
+ }
+}
diff --git a/src/fauxton/assets/less/bootstrap/labels-badges.less b/src/fauxton/assets/less/bootstrap/labels-badges.less
new file mode 100644
index 000000000..d118a0190
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/labels-badges.less
@@ -0,0 +1,74 @@
+//
+// Labels and badges
+// --------------------------------------------------
+
+
+// Base classes
+.label,
+.badge {
+ display: inline-block;
+ padding: 2px 4px;
+ font-size: @baseFontSize * .846;
+ font-weight: bold;
+ line-height: 14px; // ensure proper line-height if floated
+ color: @white;
+ vertical-align: baseline;
+ white-space: nowrap;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.25);
+ background-color: @grayLight;
+}
+// Set unique padding and border-radii
+.label {
+ .border-radius(3px);
+}
+.badge {
+ padding-left: 9px;
+ padding-right: 9px;
+ .border-radius(9px);
+}
+
+// Hover state, but only for links
+a {
+ &.label:hover,
+ &.badge:hover {
+ color: @white;
+ text-decoration: none;
+ cursor: pointer;
+ }
+}
+
+// Colors
+// Only give background-color difference to links (and to simplify, we don't qualifty with `a` but [href] attribute)
+.label,
+.badge {
+ // Important (red)
+ &-important { background-color: @errorText; }
+ &-important[href] { background-color: darken(@errorText, 10%); }
+ // Warnings (orange)
+ &-warning { background-color: @orange; }
+ &-warning[href] { background-color: darken(@orange, 10%); }
+ // Success (green)
+ &-success { background-color: @successText; }
+ &-success[href] { background-color: darken(@successText, 10%); }
+ // Info (turquoise)
+ &-info { background-color: @infoText; }
+ &-info[href] { background-color: darken(@infoText, 10%); }
+ // Inverse (black)
+ &-inverse { background-color: @grayDark; }
+ &-inverse[href] { background-color: darken(@grayDark, 10%); }
+}
+
+// Quick fix for labels/badges in buttons
+.btn {
+ .label,
+ .badge {
+ position: relative;
+ top: -1px;
+ }
+}
+.btn-mini {
+ .label,
+ .badge {
+ top: 0;
+ }
+}
diff --git a/src/fauxton/assets/less/bootstrap/layouts.less b/src/fauxton/assets/less/bootstrap/layouts.less
new file mode 100644
index 000000000..24a206211
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/layouts.less
@@ -0,0 +1,16 @@
+//
+// Layouts
+// --------------------------------------------------
+
+
+// Container (centered, fixed-width layouts)
+.container {
+ .container-fixed();
+}
+
+// Fluid layouts (left aligned, with sidebar, min- & max-width content)
+.container-fluid {
+ padding-right: @gridGutterWidth;
+ padding-left: @gridGutterWidth;
+ .clearfix();
+} \ No newline at end of file
diff --git a/src/fauxton/assets/less/bootstrap/media.less b/src/fauxton/assets/less/bootstrap/media.less
new file mode 100644
index 000000000..1decab71d
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/media.less
@@ -0,0 +1,55 @@
+// Media objects
+// Source: http://stubbornella.org/content/?p=497
+// --------------------------------------------------
+
+
+// Common styles
+// -------------------------
+
+// Clear the floats
+.media,
+.media-body {
+ overflow: hidden;
+ *overflow: visible;
+ zoom: 1;
+}
+
+// Proper spacing between instances of .media
+.media,
+.media .media {
+ margin-top: 15px;
+}
+.media:first-child {
+ margin-top: 0;
+}
+
+// For images and videos, set to block
+.media-object {
+ display: block;
+}
+
+// Reset margins on headings for tighter default spacing
+.media-heading {
+ margin: 0 0 5px;
+}
+
+
+// Media image alignment
+// -------------------------
+
+.media .pull-left {
+ margin-right: 10px;
+}
+.media .pull-right {
+ margin-left: 10px;
+}
+
+
+// Media list variation
+// -------------------------
+
+// Undo default ul/ol styles
+.media-list {
+ margin-left: 0;
+ list-style: none;
+}
diff --git a/src/fauxton/assets/less/bootstrap/mixins.less b/src/fauxton/assets/less/bootstrap/mixins.less
new file mode 100644
index 000000000..98aa2b8a5
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/mixins.less
@@ -0,0 +1,686 @@
+//
+// Mixins
+// --------------------------------------------------
+
+
+// UTILITY MIXINS
+// --------------------------------------------------
+
+// Clearfix
+// --------
+// For clearing floats like a boss h5bp.com/q
+.clearfix {
+ *zoom: 1;
+ &:before,
+ &:after {
+ display: table;
+ content: "";
+ // Fixes Opera/contenteditable bug:
+ // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952
+ line-height: 0;
+ }
+ &:after {
+ clear: both;
+ }
+}
+
+// Webkit-style focus
+// ------------------
+.tab-focus() {
+ // Default
+ outline: thin dotted #333;
+ // Webkit
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+// Center-align a block level element
+// ----------------------------------
+.center-block() {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+// IE7 inline-block
+// ----------------
+.ie7-inline-block() {
+ *display: inline; /* IE7 inline-block hack */
+ *zoom: 1;
+}
+
+// IE7 likes to collapse whitespace on either side of the inline-block elements.
+// Ems because we're attempting to match the width of a space character. Left
+// version is for form buttons, which typically come after other elements, and
+// right version is for icons, which come before. Applying both is ok, but it will
+// mean that space between those elements will be .6em (~2 space characters) in IE7,
+// instead of the 1 space in other browsers.
+.ie7-restore-left-whitespace() {
+ *margin-left: .3em;
+
+ &:first-child {
+ *margin-left: 0;
+ }
+}
+
+.ie7-restore-right-whitespace() {
+ *margin-right: .3em;
+}
+
+// Sizing shortcuts
+// -------------------------
+.size(@height, @width) {
+ width: @width;
+ height: @height;
+}
+.square(@size) {
+ .size(@size, @size);
+}
+
+// Placeholder text
+// -------------------------
+.placeholder(@color: @placeholderText) {
+ &:-moz-placeholder {
+ color: @color;
+ }
+ &:-ms-input-placeholder {
+ color: @color;
+ }
+ &::-webkit-input-placeholder {
+ color: @color;
+ }
+}
+
+// Text overflow
+// -------------------------
+// Requires inline-block or block for proper styling
+.text-overflow() {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+// CSS image replacement
+// -------------------------
+// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+
+// FONTS
+// --------------------------------------------------
+
+#font {
+ #family {
+ .serif() {
+ font-family: @serifFontFamily;
+ }
+ .sans-serif() {
+ font-family: @sansFontFamily;
+ }
+ .monospace() {
+ font-family: @monoFontFamily;
+ }
+ }
+ .shorthand(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) {
+ font-size: @size;
+ font-weight: @weight;
+ line-height: @lineHeight;
+ }
+ .serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) {
+ #font > #family > .serif;
+ #font > .shorthand(@size, @weight, @lineHeight);
+ }
+ .sans-serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) {
+ #font > #family > .sans-serif;
+ #font > .shorthand(@size, @weight, @lineHeight);
+ }
+ .monospace(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) {
+ #font > #family > .monospace;
+ #font > .shorthand(@size, @weight, @lineHeight);
+ }
+}
+
+
+// FORMS
+// --------------------------------------------------
+
+// Block level inputs
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: @inputHeight; // Make inputs at least the height of their button counterpart (base line-height + padding + border)
+ .box-sizing(border-box); // Makes inputs behave like true block-level elements
+}
+
+
+
+// Mixin for form field states
+.formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) {
+ // Set the text color
+ > label,
+ .help-block,
+ .help-inline {
+ color: @textColor;
+ }
+ // Style inputs accordingly
+ .checkbox,
+ .radio,
+ input,
+ select,
+ textarea {
+ color: @textColor;
+ }
+ input,
+ select,
+ textarea {
+ border-color: @borderColor;
+ .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work
+ &:focus {
+ border-color: darken(@borderColor, 10%);
+ @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@borderColor, 20%);
+ .box-shadow(@shadow);
+ }
+ }
+ // Give a small background color for input-prepend/-append
+ .input-prepend .add-on,
+ .input-append .add-on {
+ color: @textColor;
+ background-color: @backgroundColor;
+ border-color: @textColor;
+ }
+}
+
+
+
+// CSS3 PROPERTIES
+// --------------------------------------------------
+
+// Border Radius
+.border-radius(@radius) {
+ -webkit-border-radius: @radius;
+ -moz-border-radius: @radius;
+ border-radius: @radius;
+}
+
+// Single Corner Border Radius
+.border-top-left-radius(@radius) {
+ -webkit-border-top-left-radius: @radius;
+ -moz-border-radius-topleft: @radius;
+ border-top-left-radius: @radius;
+}
+.border-top-right-radius(@radius) {
+ -webkit-border-top-right-radius: @radius;
+ -moz-border-radius-topright: @radius;
+ border-top-right-radius: @radius;
+}
+.border-bottom-right-radius(@radius) {
+ -webkit-border-bottom-right-radius: @radius;
+ -moz-border-radius-bottomright: @radius;
+ border-bottom-right-radius: @radius;
+}
+.border-bottom-left-radius(@radius) {
+ -webkit-border-bottom-left-radius: @radius;
+ -moz-border-radius-bottomleft: @radius;
+ border-bottom-left-radius: @radius;
+}
+
+// Single Side Border Radius
+.border-top-radius(@radius) {
+ .border-top-right-radius(@radius);
+ .border-top-left-radius(@radius);
+}
+.border-right-radius(@radius) {
+ .border-top-right-radius(@radius);
+ .border-bottom-right-radius(@radius);
+}
+.border-bottom-radius(@radius) {
+ .border-bottom-right-radius(@radius);
+ .border-bottom-left-radius(@radius);
+}
+.border-left-radius(@radius) {
+ .border-top-left-radius(@radius);
+ .border-bottom-left-radius(@radius);
+}
+
+// Drop shadows
+.box-shadow(@shadow) {
+ -webkit-box-shadow: @shadow;
+ -moz-box-shadow: @shadow;
+ box-shadow: @shadow;
+}
+
+// Transitions
+.transition(@transition) {
+ -webkit-transition: @transition;
+ -moz-transition: @transition;
+ -o-transition: @transition;
+ transition: @transition;
+}
+.transition-delay(@transition-delay) {
+ -webkit-transition-delay: @transition-delay;
+ -moz-transition-delay: @transition-delay;
+ -o-transition-delay: @transition-delay;
+ transition-delay: @transition-delay;
+}
+
+// Transformations
+.rotate(@degrees) {
+ -webkit-transform: rotate(@degrees);
+ -moz-transform: rotate(@degrees);
+ -ms-transform: rotate(@degrees);
+ -o-transform: rotate(@degrees);
+ transform: rotate(@degrees);
+}
+.scale(@ratio) {
+ -webkit-transform: scale(@ratio);
+ -moz-transform: scale(@ratio);
+ -ms-transform: scale(@ratio);
+ -o-transform: scale(@ratio);
+ transform: scale(@ratio);
+}
+.translate(@x, @y) {
+ -webkit-transform: translate(@x, @y);
+ -moz-transform: translate(@x, @y);
+ -ms-transform: translate(@x, @y);
+ -o-transform: translate(@x, @y);
+ transform: translate(@x, @y);
+}
+.skew(@x, @y) {
+ -webkit-transform: skew(@x, @y);
+ -moz-transform: skew(@x, @y);
+ -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twitter/bootstrap/issues/4885
+ -o-transform: skew(@x, @y);
+ transform: skew(@x, @y);
+ -webkit-backface-visibility: hidden; // See https://github.com/twitter/bootstrap/issues/5319
+}
+.translate3d(@x, @y, @z) {
+ -webkit-transform: translate3d(@x, @y, @z);
+ -moz-transform: translate3d(@x, @y, @z);
+ -o-transform: translate3d(@x, @y, @z);
+ transform: translate3d(@x, @y, @z);
+}
+
+// Backface visibility
+// Prevent browsers from flickering when using CSS 3D transforms.
+// Default value is `visible`, but can be changed to `hidden
+// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples
+.backface-visibility(@visibility){
+ -webkit-backface-visibility: @visibility;
+ -moz-backface-visibility: @visibility;
+ backface-visibility: @visibility;
+}
+
+// Background clipping
+// Heads up: FF 3.6 and under need "padding" instead of "padding-box"
+.background-clip(@clip) {
+ -webkit-background-clip: @clip;
+ -moz-background-clip: @clip;
+ background-clip: @clip;
+}
+
+// Background sizing
+.background-size(@size) {
+ -webkit-background-size: @size;
+ -moz-background-size: @size;
+ -o-background-size: @size;
+ background-size: @size;
+}
+
+
+// Box sizing
+.box-sizing(@boxmodel) {
+ -webkit-box-sizing: @boxmodel;
+ -moz-box-sizing: @boxmodel;
+ box-sizing: @boxmodel;
+}
+
+// User select
+// For selecting text on the page
+.user-select(@select) {
+ -webkit-user-select: @select;
+ -moz-user-select: @select;
+ -ms-user-select: @select;
+ -o-user-select: @select;
+ user-select: @select;
+}
+
+// Resize anything
+.resizable(@direction) {
+ resize: @direction; // Options: horizontal, vertical, both
+ overflow: auto; // Safari fix
+}
+
+// CSS3 Content Columns
+.content-columns(@columnCount, @columnGap: @gridGutterWidth) {
+ -webkit-column-count: @columnCount;
+ -moz-column-count: @columnCount;
+ column-count: @columnCount;
+ -webkit-column-gap: @columnGap;
+ -moz-column-gap: @columnGap;
+ column-gap: @columnGap;
+}
+
+// Optional hyphenation
+.hyphens(@mode: auto) {
+ word-wrap: break-word;
+ -webkit-hyphens: @mode;
+ -moz-hyphens: @mode;
+ -ms-hyphens: @mode;
+ -o-hyphens: @mode;
+ hyphens: @mode;
+}
+
+// Opacity
+.opacity(@opacity) {
+ opacity: @opacity / 100;
+ filter: ~"alpha(opacity=@{opacity})";
+}
+
+
+
+// BACKGROUNDS
+// --------------------------------------------------
+
+// Add an alphatransparency value to any background or border color (via Elyse Holladay)
+#translucent {
+ .background(@color: @white, @alpha: 1) {
+ background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha);
+ }
+ .border(@color: @white, @alpha: 1) {
+ border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha);
+ .background-clip(padding-box);
+ }
+}
+
+// Gradient Bar Colors for buttons and alerts
+.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) {
+ color: @textColor;
+ text-shadow: @textShadow;
+ #gradient > .vertical(@primaryColor, @secondaryColor);
+ border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%);
+ border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%);
+}
+
+// Gradients
+#gradient {
+ .horizontal(@startColor: #555, @endColor: #333) {
+ background-color: @endColor;
+ background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+
+ background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+
+ background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+
+ background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10
+ background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10
+ background-repeat: repeat-x;
+ filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down
+ }
+ .vertical(@startColor: #555, @endColor: #333) {
+ background-color: mix(@startColor, @endColor, 60%);
+ background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+
+ background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+
+ background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10
+ background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10
+ background-repeat: repeat-x;
+ filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down
+ }
+ .directional(@startColor: #555, @endColor: #333, @deg: 45deg) {
+ background-color: @endColor;
+ background-repeat: repeat-x;
+ background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+
+ background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+
+ background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10
+ background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10
+ }
+ .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) {
+ background-color: mix(@midColor, @endColor, 80%);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor));
+ background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor);
+ background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor);
+ background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor);
+ background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor);
+ background-repeat: no-repeat;
+ filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback
+ }
+ .radial(@innerColor: #555, @outerColor: #333) {
+ background-color: @outerColor;
+ background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor));
+ background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor);
+ background-image: -moz-radial-gradient(circle, @innerColor, @outerColor);
+ background-image: -o-radial-gradient(circle, @innerColor, @outerColor);
+ background-repeat: no-repeat;
+ }
+ .striped(@color: #555, @angle: 45deg) {
+ background-color: @color;
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
+ background-image: -o-linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(@angle, rgba(255,255,255,.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, transparent 75%, transparent);
+ }
+}
+// Reset filters for IE
+.reset-filter() {
+ filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
+}
+
+
+
+// COMPONENT MIXINS
+// --------------------------------------------------
+
+// Horizontal dividers
+// -------------------------
+// Dividers (basically an hr) within dropdowns and nav lists
+.nav-divider(@top: #e5e5e5, @bottom: @white) {
+ // IE7 needs a set width since we gave a height. Restricting just
+ // to IE7 to keep the 1px left/right space in other browsers.
+ // It is unclear where IE is getting the extra space that we need
+ // to negative-margin away, but so it goes.
+ *width: 100%;
+ height: 1px;
+ margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px
+ *margin: -5px 0 5px;
+ overflow: hidden;
+ background-color: @top;
+ border-bottom: 1px solid @bottom;
+}
+
+// Button backgrounds
+// ------------------
+.buttonBackground(@startColor, @endColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) {
+ // gradientBar will set the background to a pleasing blend of these, to support IE<=9
+ .gradientBar(@startColor, @endColor, @textColor, @textShadow);
+ *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+ .reset-filter();
+
+ // in these cases the gradient won't cover the background, so we override
+ &:hover, &:active, &.active, &.disabled, &[disabled] {
+ color: @textColor;
+ background-color: @endColor;
+ *background-color: darken(@endColor, 5%);
+ }
+
+ // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves
+ &:active,
+ &.active {
+ background-color: darken(@endColor, 10%) e("\9");
+ }
+}
+
+// Navbar vertical align
+// -------------------------
+// Vertically center elements in the navbar.
+// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin.
+.navbarVerticalAlign(@elementHeight) {
+ margin-top: (@navbarHeight - @elementHeight) / 2;
+}
+
+
+
+// Grid System
+// -----------
+
+// Centered container element
+.container-fixed() {
+ margin-right: auto;
+ margin-left: auto;
+ .clearfix();
+}
+
+// Table columns
+.tableColumns(@columnSpan: 1) {
+ float: none; // undo default grid column styles
+ width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells
+ margin-left: 0; // undo default grid column styles
+}
+
+// Make a Grid
+// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior
+.makeRow() {
+ margin-left: @gridGutterWidth * -1;
+ .clearfix();
+}
+.makeColumn(@columns: 1, @offset: 0) {
+ float: left;
+ margin-left: (@gridColumnWidth * @offset) + (@gridGutterWidth * (@offset - 1)) + (@gridGutterWidth * 2);
+ width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1));
+}
+
+// The Grid
+#grid {
+
+ .core (@gridColumnWidth, @gridGutterWidth) {
+
+ .spanX (@index) when (@index > 0) {
+ (~".span@{index}") { .span(@index); }
+ .spanX(@index - 1);
+ }
+ .spanX (0) {}
+
+ .offsetX (@index) when (@index > 0) {
+ (~".offset@{index}") { .offset(@index); }
+ .offsetX(@index - 1);
+ }
+ .offsetX (0) {}
+
+ .offset (@columns) {
+ margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1));
+ }
+
+ .span (@columns) {
+ width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1));
+ }
+
+ .row {
+ margin-left: @gridGutterWidth * -1;
+ .clearfix();
+ }
+
+ [class*="span"] {
+ float: left;
+ min-height: 1px; // prevent collapsing columns
+ margin-left: @gridGutterWidth;
+ }
+
+ // Set the container width, and override it for fixed navbars in media queries
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container { .span(@gridColumns); }
+
+ // generate .spanX and .offsetX
+ .spanX (@gridColumns);
+ .offsetX (@gridColumns);
+
+ }
+
+ .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) {
+
+ .spanX (@index) when (@index > 0) {
+ (~".span@{index}") { .span(@index); }
+ .spanX(@index - 1);
+ }
+ .spanX (0) {}
+
+ .offsetX (@index) when (@index > 0) {
+ (~'.offset@{index}') { .offset(@index); }
+ (~'.offset@{index}:first-child') { .offsetFirstChild(@index); }
+ .offsetX(@index - 1);
+ }
+ .offsetX (0) {}
+
+ .offset (@columns) {
+ margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth*2);
+ *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + (@fluidGridGutterWidth*2) - (.5 / @gridRowWidth * 100 * 1%);
+ }
+
+ .offsetFirstChild (@columns) {
+ margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth);
+ *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%);
+ }
+
+ .span (@columns) {
+ width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1));
+ *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%);
+ }
+
+ .row-fluid {
+ width: 100%;
+ .clearfix();
+ [class*="span"] {
+ .input-block-level();
+ float: left;
+ margin-left: @fluidGridGutterWidth;
+ *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%);
+ }
+ [class*="span"]:first-child {
+ margin-left: 0;
+ }
+
+ // Space grid-sized controls properly if multiple per line
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: @fluidGridGutterWidth;
+ }
+
+ // generate .spanX and .offsetX
+ .spanX (@gridColumns);
+ .offsetX (@gridColumns);
+ }
+
+ }
+
+ .input(@gridColumnWidth, @gridGutterWidth) {
+
+ .spanX (@index) when (@index > 0) {
+ (~"input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index}") { .span(@index); }
+ .spanX(@index - 1);
+ }
+ .spanX (0) {}
+
+ .span(@columns) {
+ width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 14;
+ }
+
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0; // override margin-left from core grid system
+ }
+
+ // Space grid-sized controls properly if multiple per line
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: @gridGutterWidth;
+ }
+
+ // generate .spanX
+ .spanX (@gridColumns);
+
+ }
+
+}
diff --git a/src/fauxton/assets/less/bootstrap/modals.less b/src/fauxton/assets/less/bootstrap/modals.less
new file mode 100644
index 000000000..90b86670f
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/modals.less
@@ -0,0 +1,94 @@
+//
+// Modals
+// --------------------------------------------------
+
+// Background
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: @zindexModalBackdrop;
+ background-color: @black;
+ // Fade for backdrop
+ &.fade { opacity: 0; }
+}
+
+.modal-backdrop,
+.modal-backdrop.fade.in {
+ .opacity(80);
+}
+
+// Base modal
+.modal {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ z-index: @zindexModal;
+ width: 560px;
+ margin: -250px 0 0 -280px;
+ background-color: @white;
+ border: 1px solid #999;
+ border: 1px solid rgba(0,0,0,.3);
+ *border: 1px solid #999; /* IE6-7 */
+ .border-radius(6px);
+ .box-shadow(0 3px 7px rgba(0,0,0,0.3));
+ .background-clip(padding-box);
+ // Remove focus outline from opened modal
+ outline: none;
+
+ &.fade {
+ .transition(e('opacity .3s linear, top .3s ease-out'));
+ top: -25%;
+ }
+ &.fade.in { top: 50%; }
+}
+.modal-header {
+ padding: 9px 15px;
+ border-bottom: 1px solid #eee;
+ // Close icon
+ .close { margin-top: 2px; }
+ // Heading
+ h3 {
+ margin: 0;
+ line-height: 30px;
+ }
+}
+
+// Body (where all modal content resides)
+.modal-body {
+ overflow-y: auto;
+ max-height: 400px;
+ padding: 15px;
+}
+// Remove bottom margin if need be
+.modal-form {
+ margin-bottom: 0;
+}
+
+// Footer (for actions)
+.modal-footer {
+ padding: 14px 15px 15px;
+ margin-bottom: 0;
+ text-align: right; // right align buttons
+ background-color: #f5f5f5;
+ border-top: 1px solid #ddd;
+ .border-radius(0 0 6px 6px);
+ .box-shadow(inset 0 1px 0 @white);
+ .clearfix(); // clear it in case folks use .pull-* classes on buttons
+
+ // Properly space out buttons
+ .btn + .btn {
+ margin-left: 5px;
+ margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs
+ }
+ // but override that for button groups
+ .btn-group .btn + .btn {
+ margin-left: -1px;
+ }
+ // and override it for block buttons as well
+ .btn-block + .btn-block {
+ margin-left: 0;
+ }
+}
diff --git a/src/fauxton/assets/less/bootstrap/navbar.less b/src/fauxton/assets/less/bootstrap/navbar.less
new file mode 100644
index 000000000..f69e04899
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/navbar.less
@@ -0,0 +1,475 @@
+//
+// Navbars (Redux)
+// --------------------------------------------------
+
+
+// COMMON STYLES
+// -------------
+
+// Base class and wrapper
+.navbar {
+ overflow: visible;
+ margin-bottom: @baseLineHeight;
+ color: @navbarText;
+
+ // Fix for IE7's bad z-indexing so dropdowns don't appear below content that follows the navbar
+ *position: relative;
+ *z-index: 2;
+}
+
+// Inner for background effects
+// Gradient is applied to its own element because overflow visible is not honored by IE when filter is present
+.navbar-inner {
+ min-height: @navbarHeight;
+ padding-left: 20px;
+ padding-right: 20px;
+ #gradient > .vertical(@navbarBackgroundHighlight, @navbarBackground);
+ border: 1px solid @navbarBorder;
+ .border-radius(@baseBorderRadius);
+ .box-shadow(0 1px 4px rgba(0,0,0,.065));
+
+ // Prevent floats from breaking the navbar
+ .clearfix();
+}
+
+// Set width to auto for default container
+// We then reset it for fixed navbars in the #gridSystem mixin
+.navbar .container {
+ width: auto;
+}
+
+// Override the default collapsed state
+.nav-collapse.collapse {
+ height: auto;
+ overflow: visible;
+}
+
+
+// Brand: website or project name
+// -------------------------
+.navbar .brand {
+ float: left;
+ display: block;
+ // Vertically center the text given @navbarHeight
+ padding: ((@navbarHeight - @baseLineHeight) / 2) 20px ((@navbarHeight - @baseLineHeight) / 2);
+ margin-left: -20px; // negative indent to left-align the text down the page
+ font-size: 20px;
+ font-weight: 200;
+ color: @navbarBrandColor;
+ text-shadow: 0 1px 0 @navbarBackgroundHighlight;
+ &:hover {
+ text-decoration: none;
+ }
+}
+
+// Plain text in topbar
+// -------------------------
+.navbar-text {
+ margin-bottom: 0;
+ line-height: @navbarHeight;
+}
+
+// Janky solution for now to account for links outside the .nav
+// -------------------------
+.navbar-link {
+ color: @navbarLinkColor;
+ &:hover {
+ color: @navbarLinkColorHover;
+ }
+}
+
+// Dividers in navbar
+// -------------------------
+.navbar .divider-vertical {
+ height: @navbarHeight;
+ margin: 0 9px;
+ border-left: 1px solid @navbarBackground;
+ border-right: 1px solid @navbarBackgroundHighlight;
+}
+
+// Buttons in navbar
+// -------------------------
+.navbar .btn,
+.navbar .btn-group {
+ .navbarVerticalAlign(30px); // Vertically center in navbar
+}
+.navbar .btn-group .btn,
+.navbar .input-prepend .btn,
+.navbar .input-append .btn {
+ margin-top: 0; // then undo the margin here so we don't accidentally double it
+}
+
+// Navbar forms
+// -------------------------
+.navbar-form {
+ margin-bottom: 0; // remove default bottom margin
+ .clearfix();
+ input,
+ select,
+ .radio,
+ .checkbox {
+ .navbarVerticalAlign(30px); // Vertically center in navbar
+ }
+ input,
+ select,
+ .btn {
+ display: inline-block;
+ margin-bottom: 0;
+ }
+ input[type="image"],
+ input[type="checkbox"],
+ input[type="radio"] {
+ margin-top: 3px;
+ }
+ .input-append,
+ .input-prepend {
+ margin-top: 6px;
+ white-space: nowrap; // preven two items from separating within a .navbar-form that has .pull-left
+ input {
+ margin-top: 0; // remove the margin on top since it's on the parent
+ }
+ }
+}
+
+// Navbar search
+// -------------------------
+.navbar-search {
+ position: relative;
+ float: left;
+ .navbarVerticalAlign(30px); // Vertically center in navbar
+ margin-bottom: 0;
+ .search-query {
+ margin-bottom: 0;
+ padding: 4px 14px;
+ #font > .sans-serif(13px, normal, 1);
+ .border-radius(15px); // redeclare because of specificity of the type attribute
+ }
+}
+
+
+
+// Static navbar
+// -------------------------
+
+.navbar-static-top {
+ position: static;
+ margin-bottom: 0; // remove 18px margin for default navbar
+ .navbar-inner {
+ .border-radius(0);
+ }
+}
+
+
+
+// Fixed navbar
+// -------------------------
+
+// Shared (top/bottom) styles
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ position: fixed;
+ right: 0;
+ left: 0;
+ z-index: @zindexFixedNavbar;
+ margin-bottom: 0; // remove 18px margin for default navbar
+}
+.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-left: 0;
+ padding-right: 0;
+ .border-radius(0);
+}
+
+// Reset container width
+// Required here as we reset the width earlier on and the grid mixins don't override early enough
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+ #grid > .core > .span(@gridColumns);
+}
+
+// Fixed to top
+.navbar-fixed-top {
+ top: 0;
+}
+.navbar-fixed-top,
+.navbar-static-top {
+ .navbar-inner {
+ .box-shadow(~"0 1px 10px rgba(0,0,0,.1)");
+ }
+}
+
+// Fixed to bottom
+.navbar-fixed-bottom {
+ bottom: 0;
+ .navbar-inner {
+ .box-shadow(~"0 -1px 10px rgba(0,0,0,.1)");
+ }
+}
+
+
+
+// NAVIGATION
+// ----------
+
+.navbar .nav {
+ position: relative;
+ left: 0;
+ display: block;
+ float: left;
+ margin: 0 10px 0 0;
+}
+.navbar .nav.pull-right {
+ float: right; // redeclare due to specificity
+ margin-right: 0; // remove margin on float right nav
+}
+.navbar .nav > li {
+ float: left;
+}
+
+// Links
+.navbar .nav > li > a {
+ float: none;
+ // Vertically center the text given @navbarHeight
+ padding: ((@navbarHeight - @baseLineHeight) / 2) 15px ((@navbarHeight - @baseLineHeight) / 2);
+ color: @navbarLinkColor;
+ text-decoration: none;
+ text-shadow: 0 1px 0 @navbarBackgroundHighlight;
+}
+.navbar .nav .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+// Hover
+.navbar .nav > li > a:focus,
+.navbar .nav > li > a:hover {
+ background-color: @navbarLinkBackgroundHover; // "transparent" is default to differentiate :hover from .active
+ color: @navbarLinkColorHover;
+ text-decoration: none;
+}
+
+// Active nav items
+.navbar .nav > .active > a,
+.navbar .nav > .active > a:hover,
+.navbar .nav > .active > a:focus {
+ color: @navbarLinkColorActive;
+ text-decoration: none;
+ background-color: @navbarLinkBackgroundActive;
+ .box-shadow(inset 0 3px 8px rgba(0,0,0,.125));
+}
+
+// Navbar button for toggling navbar items in responsive layouts
+// These definitions need to come after '.navbar .btn'
+.navbar .btn-navbar {
+ display: none;
+ float: right;
+ padding: 7px 10px;
+ margin-left: 5px;
+ margin-right: 5px;
+ .buttonBackground(darken(@navbarBackgroundHighlight, 5%), darken(@navbarBackground, 5%));
+ .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075)");
+}
+.navbar .btn-navbar .icon-bar {
+ display: block;
+ width: 18px;
+ height: 2px;
+ background-color: #f5f5f5;
+ .border-radius(1px);
+ .box-shadow(0 1px 0 rgba(0,0,0,.25));
+}
+.btn-navbar .icon-bar + .icon-bar {
+ margin-top: 3px;
+}
+
+
+
+// Dropdown menus
+// --------------
+
+// Menu position and menu carets
+.navbar .nav > li > .dropdown-menu {
+ &:before {
+ content: '';
+ display: inline-block;
+ border-left: 7px solid transparent;
+ border-right: 7px solid transparent;
+ border-bottom: 7px solid #ccc;
+ border-bottom-color: @dropdownBorder;
+ position: absolute;
+ top: -7px;
+ left: 9px;
+ }
+ &:after {
+ content: '';
+ display: inline-block;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ border-bottom: 6px solid @dropdownBackground;
+ position: absolute;
+ top: -6px;
+ left: 10px;
+ }
+}
+// Menu position and menu caret support for dropups via extra dropup class
+.navbar-fixed-bottom .nav > li > .dropdown-menu {
+ &:before {
+ border-top: 7px solid #ccc;
+ border-top-color: @dropdownBorder;
+ border-bottom: 0;
+ bottom: -7px;
+ top: auto;
+ }
+ &:after {
+ border-top: 6px solid @dropdownBackground;
+ border-bottom: 0;
+ bottom: -6px;
+ top: auto;
+ }
+}
+
+// Remove background color from open dropdown
+.navbar .nav li.dropdown.open > .dropdown-toggle,
+.navbar .nav li.dropdown.active > .dropdown-toggle,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle {
+ background-color: @navbarLinkBackgroundActive;
+ color: @navbarLinkColorActive;
+}
+.navbar .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: @navbarLinkColor;
+ border-bottom-color: @navbarLinkColor;
+}
+.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: @navbarLinkColorActive;
+ border-bottom-color: @navbarLinkColorActive;
+}
+
+// Right aligned menus need alt position
+.navbar .pull-right > li > .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right {
+ left: auto;
+ right: 0;
+ &:before {
+ left: auto;
+ right: 12px;
+ }
+ &:after {
+ left: auto;
+ right: 13px;
+ }
+ .dropdown-menu {
+ left: auto;
+ right: 100%;
+ margin-left: 0;
+ margin-right: -1px;
+ .border-radius(6px 0 6px 6px);
+ }
+}
+
+
+// Inverted navbar
+// -------------------------
+
+.navbar-inverse {
+ color: @navbarInverseText;
+
+ .navbar-inner {
+ #gradient > .vertical(@navbarInverseBackgroundHighlight, @navbarInverseBackground);
+ border-color: @navbarInverseBorder;
+ }
+
+ .brand,
+ .nav > li > a {
+ color: @navbarInverseLinkColor;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.25);
+ &:hover {
+ color: @navbarInverseLinkColorHover;
+ }
+ }
+
+ .nav > li > a:focus,
+ .nav > li > a:hover {
+ background-color: @navbarInverseLinkBackgroundHover;
+ color: @navbarInverseLinkColorHover;
+ }
+
+ .nav .active > a,
+ .nav .active > a:hover,
+ .nav .active > a:focus {
+ color: @navbarInverseLinkColorActive;
+ background-color: @navbarInverseLinkBackgroundActive;
+ }
+
+ // Inline text links
+ .navbar-link {
+ color: @navbarInverseLinkColor;
+ &:hover {
+ color: @navbarInverseLinkColorHover;
+ }
+ }
+
+ // Dividers in navbar
+ .divider-vertical {
+ border-left-color: @navbarInverseBackground;
+ border-right-color: @navbarInverseBackgroundHighlight;
+ }
+
+ // Dropdowns
+ .nav li.dropdown.open > .dropdown-toggle,
+ .nav li.dropdown.active > .dropdown-toggle,
+ .nav li.dropdown.open.active > .dropdown-toggle {
+ background-color: @navbarInverseLinkBackgroundActive;
+ color: @navbarInverseLinkColorActive;
+ }
+ .nav li.dropdown > .dropdown-toggle .caret {
+ border-top-color: @navbarInverseLinkColor;
+ border-bottom-color: @navbarInverseLinkColor;
+ }
+ .nav li.dropdown.open > .dropdown-toggle .caret,
+ .nav li.dropdown.active > .dropdown-toggle .caret,
+ .nav li.dropdown.open.active > .dropdown-toggle .caret {
+ border-top-color: @navbarInverseLinkColorActive;
+ border-bottom-color: @navbarInverseLinkColorActive;
+ }
+
+ // Navbar search
+ .navbar-search {
+ .search-query {
+ color: @white;
+ background-color: @navbarInverseSearchBackground;
+ border-color: @navbarInverseSearchBorder;
+ .box-shadow(~"inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15)");
+ .transition(none);
+ .placeholder(@navbarInverseSearchPlaceholderColor);
+
+ // Focus states (we use .focused since IE7-8 and down doesn't support :focus)
+ &:focus,
+ &.focused {
+ padding: 5px 15px;
+ color: @grayDark;
+ text-shadow: 0 1px 0 @white;
+ background-color: @navbarInverseSearchBackgroundFocus;
+ border: 0;
+ .box-shadow(0 0 3px rgba(0,0,0,.15));
+ outline: 0;
+ }
+ }
+ }
+
+ // Navbar collapse button
+ .btn-navbar {
+ .buttonBackground(darken(@navbarInverseBackgroundHighlight, 5%), darken(@navbarInverseBackground, 5%));
+ }
+
+}
+
+
+
diff --git a/src/fauxton/assets/less/bootstrap/navs.less b/src/fauxton/assets/less/bootstrap/navs.less
new file mode 100644
index 000000000..1944f8441
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/navs.less
@@ -0,0 +1,385 @@
+//
+// Navs
+// --------------------------------------------------
+
+
+// BASE CLASS
+// ----------
+
+.nav {
+ margin-left: 0;
+ margin-bottom: @baseLineHeight;
+ list-style: none;
+}
+
+// Make links block level
+.nav > li > a {
+ display: block;
+}
+.nav > li > a:hover {
+ text-decoration: none;
+ background-color: @grayLighter;
+}
+
+// Redeclare pull classes because of specifity
+.nav > .pull-right {
+ float: right;
+}
+
+// Nav headers (for dropdowns and lists)
+.nav-header {
+ display: block;
+ padding: 3px 15px;
+ font-size: 11px;
+ font-weight: bold;
+ line-height: @baseLineHeight;
+ color: @grayLight;
+ text-shadow: 0 1px 0 rgba(255,255,255,.5);
+ text-transform: uppercase;
+}
+// Space them out when they follow another list item (link)
+.nav li + .nav-header {
+ margin-top: 9px;
+}
+
+
+
+// NAV LIST
+// --------
+
+.nav-list {
+ padding-left: 15px;
+ padding-right: 15px;
+ margin-bottom: 0;
+}
+.nav-list > li > a,
+.nav-list .nav-header {
+ margin-left: -15px;
+ margin-right: -15px;
+ text-shadow: 0 1px 0 rgba(255,255,255,.5);
+}
+.nav-list > li > a {
+ padding: 3px 15px;
+}
+.nav-list > .active > a,
+.nav-list > .active > a:hover {
+ color: @white;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.2);
+ background-color: @linkColor;
+}
+.nav-list [class^="icon-"],
+.nav-list [class*=" icon-"] {
+ margin-right: 2px;
+}
+// Dividers (basically an hr) within the dropdown
+.nav-list .divider {
+ .nav-divider();
+}
+
+
+
+// TABS AND PILLS
+// -------------
+
+// Common styles
+.nav-tabs,
+.nav-pills {
+ .clearfix();
+}
+.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; // keeps the overall height an even number
+}
+
+// TABS
+// ----
+
+// Give the tabs something to sit on
+.nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+// Make the list-items overlay the bottom border
+.nav-tabs > li {
+ margin-bottom: -1px;
+}
+// Actual tabs (as links)
+.nav-tabs > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ line-height: @baseLineHeight;
+ border: 1px solid transparent;
+ .border-radius(4px 4px 0 0);
+ &:hover {
+ border-color: @grayLighter @grayLighter #ddd;
+ }
+}
+// Active state, and it's :hover to override normal :hover
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover {
+ color: @gray;
+ background-color: @bodyBackground;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+ cursor: default;
+}
+
+
+// PILLS
+// -----
+
+// Links rendered as pills
+.nav-pills > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ .border-radius(5px);
+}
+
+// Active state
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover {
+ color: @white;
+ background-color: @linkColor;
+}
+
+
+
+// STACKED NAV
+// -----------
+
+// Stacked tabs and pills
+.nav-stacked > li {
+ float: none;
+}
+.nav-stacked > li > a {
+ margin-right: 0; // no need for the gap between nav items
+}
+
+// Tabs
+.nav-tabs.nav-stacked {
+ border-bottom: 0;
+}
+.nav-tabs.nav-stacked > li > a {
+ border: 1px solid #ddd;
+ .border-radius(0);
+}
+.nav-tabs.nav-stacked > li:first-child > a {
+ .border-top-radius(4px);
+}
+.nav-tabs.nav-stacked > li:last-child > a {
+ .border-bottom-radius(4px);
+}
+.nav-tabs.nav-stacked > li > a:hover {
+ border-color: #ddd;
+ z-index: 2;
+}
+
+// Pills
+.nav-pills.nav-stacked > li > a {
+ margin-bottom: 3px;
+}
+.nav-pills.nav-stacked > li:last-child > a {
+ margin-bottom: 1px; // decrease margin to match sizing of stacked tabs
+}
+
+
+
+// DROPDOWNS
+// ---------
+
+.nav-tabs .dropdown-menu {
+ .border-radius(0 0 6px 6px); // remove the top rounded corners here since there is a hard edge above the menu
+}
+.nav-pills .dropdown-menu {
+ .border-radius(6px); // make rounded corners match the pills
+}
+
+// Default dropdown links
+// -------------------------
+// Make carets use linkColor to start
+.nav .dropdown-toggle .caret {
+ border-top-color: @linkColor;
+ border-bottom-color: @linkColor;
+ margin-top: 6px;
+}
+.nav .dropdown-toggle:hover .caret {
+ border-top-color: @linkColorHover;
+ border-bottom-color: @linkColorHover;
+}
+/* move down carets for tabs */
+.nav-tabs .dropdown-toggle .caret {
+ margin-top: 8px;
+}
+
+// Active dropdown links
+// -------------------------
+.nav .active .dropdown-toggle .caret {
+ border-top-color: #fff;
+ border-bottom-color: #fff;
+}
+.nav-tabs .active .dropdown-toggle .caret {
+ border-top-color: @gray;
+ border-bottom-color: @gray;
+}
+
+// Active:hover dropdown links
+// -------------------------
+.nav > .dropdown.active > a:hover {
+ cursor: pointer;
+}
+
+// Open dropdowns
+// -------------------------
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > li.dropdown.open.active > a:hover {
+ color: @white;
+ background-color: @grayLight;
+ border-color: @grayLight;
+}
+.nav li.dropdown.open .caret,
+.nav li.dropdown.open.active .caret,
+.nav li.dropdown.open a:hover .caret {
+ border-top-color: @white;
+ border-bottom-color: @white;
+ .opacity(100);
+}
+
+// Dropdowns in stacked tabs
+.tabs-stacked .open > a:hover {
+ border-color: @grayLight;
+}
+
+
+
+// TABBABLE
+// --------
+
+
+// COMMON STYLES
+// -------------
+
+// Clear any floats
+.tabbable {
+ .clearfix();
+}
+.tab-content {
+ overflow: auto; // prevent content from running below tabs
+}
+
+// Remove border on bottom, left, right
+.tabs-below > .nav-tabs,
+.tabs-right > .nav-tabs,
+.tabs-left > .nav-tabs {
+ border-bottom: 0;
+}
+
+// Show/hide tabbable areas
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+ display: none;
+}
+.tab-content > .active,
+.pill-content > .active {
+ display: block;
+}
+
+
+// BOTTOM
+// ------
+
+.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 {
+ .border-radius(0 0 4px 4px);
+ &:hover {
+ border-bottom-color: transparent;
+ border-top-color: #ddd;
+ }
+}
+.tabs-below > .nav-tabs > .active > a,
+.tabs-below > .nav-tabs > .active > a:hover {
+ border-color: transparent #ddd #ddd #ddd;
+}
+
+// LEFT & RIGHT
+// ------------
+
+// Common styles
+.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 on the left
+.tabs-left > .nav-tabs {
+ float: left;
+ margin-right: 19px;
+ border-right: 1px solid #ddd;
+}
+.tabs-left > .nav-tabs > li > a {
+ margin-right: -1px;
+ .border-radius(4px 0 0 4px);
+}
+.tabs-left > .nav-tabs > li > a:hover {
+ border-color: @grayLighter #ddd @grayLighter @grayLighter;
+}
+.tabs-left > .nav-tabs .active > a,
+.tabs-left > .nav-tabs .active > a:hover {
+ border-color: #ddd transparent #ddd #ddd;
+ *border-right-color: @white;
+}
+
+// Tabs on the right
+.tabs-right > .nav-tabs {
+ float: right;
+ margin-left: 19px;
+ border-left: 1px solid #ddd;
+}
+.tabs-right > .nav-tabs > li > a {
+ margin-left: -1px;
+ .border-radius(0 4px 4px 0);
+}
+.tabs-right > .nav-tabs > li > a:hover {
+ border-color: @grayLighter @grayLighter @grayLighter #ddd;
+}
+.tabs-right > .nav-tabs .active > a,
+.tabs-right > .nav-tabs .active > a:hover {
+ border-color: #ddd #ddd #ddd transparent;
+ *border-left-color: @white;
+}
+
+
+
+// DISABLED STATES
+// ---------------
+
+// Gray out text
+.nav > .disabled > a {
+ color: @grayLight;
+}
+// Nuke hover effects
+.nav > .disabled > a:hover {
+ text-decoration: none;
+ background-color: transparent;
+ cursor: default;
+}
diff --git a/src/fauxton/assets/less/bootstrap/pager.less b/src/fauxton/assets/less/bootstrap/pager.less
new file mode 100644
index 000000000..da2425367
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/pager.less
@@ -0,0 +1,41 @@
+//
+// Pager pagination
+// --------------------------------------------------
+
+
+.pager {
+ margin: @baseLineHeight 0;
+ list-style: none;
+ text-align: center;
+ .clearfix();
+}
+.pager li {
+ display: inline;
+}
+.pager li > a,
+.pager li > span {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #fff;
+ border: 1px solid #ddd;
+ .border-radius(15px);
+}
+.pager li > a:hover {
+ 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 > span {
+ color: @grayLight;
+ background-color: #fff;
+ cursor: default;
+} \ No newline at end of file
diff --git a/src/fauxton/assets/less/bootstrap/pagination.less b/src/fauxton/assets/less/bootstrap/pagination.less
new file mode 100644
index 000000000..e35d3f4a8
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/pagination.less
@@ -0,0 +1,121 @@
+//
+// Pagination (multiple pages)
+// --------------------------------------------------
+
+// Space out pagination from surrounding content
+.pagination {
+ margin: @baseLineHeight 0;
+}
+
+.pagination ul {
+ // Allow for text-based alignment
+ display: inline-block;
+ .ie7-inline-block();
+ // Reset default ul styles
+ margin-left: 0;
+ margin-bottom: 0;
+ // Visuals
+ .border-radius(@baseBorderRadius);
+ .box-shadow(0 1px 2px rgba(0,0,0,.05));
+}
+.pagination ul > li {
+ display: inline; // Remove list-style and block-level defaults
+}
+.pagination ul > li > a,
+.pagination ul > li > span {
+ float: left; // Collapse white-space
+ padding: 4px 12px;
+ line-height: @baseLineHeight;
+ text-decoration: none;
+ background-color: @paginationBackground;
+ border: 1px solid @paginationBorder;
+ border-left-width: 0;
+}
+.pagination ul > li > a:hover,
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+ background-color: @paginationActiveBackground;
+}
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+ color: @grayLight;
+ cursor: default;
+}
+.pagination ul > .disabled > span,
+.pagination ul > .disabled > a,
+.pagination ul > .disabled > a:hover {
+ color: @grayLight;
+ background-color: transparent;
+ cursor: default;
+}
+.pagination ul > li:first-child > a,
+.pagination ul > li:first-child > span {
+ border-left-width: 1px;
+ .border-left-radius(@baseBorderRadius);
+}
+.pagination ul > li:last-child > a,
+.pagination ul > li:last-child > span {
+ .border-right-radius(@baseBorderRadius);
+}
+
+
+// Alignment
+// --------------------------------------------------
+
+.pagination-centered {
+ text-align: center;
+}
+.pagination-right {
+ text-align: right;
+}
+
+
+// Sizing
+// --------------------------------------------------
+
+// Large
+.pagination-large {
+ ul > li > a,
+ ul > li > span {
+ padding: @paddingLarge;
+ font-size: @fontSizeLarge;
+ }
+ ul > li:first-child > a,
+ ul > li:first-child > span {
+ .border-left-radius(@borderRadiusLarge);
+ }
+ ul > li:last-child > a,
+ ul > li:last-child > span {
+ .border-right-radius(@borderRadiusLarge);
+ }
+}
+
+// Small and mini
+.pagination-mini,
+.pagination-small {
+ ul > li:first-child > a,
+ ul > li:first-child > span {
+ .border-left-radius(@borderRadiusSmall);
+ }
+ ul > li:last-child > a,
+ ul > li:last-child > span {
+ .border-right-radius(@borderRadiusSmall);
+ }
+}
+
+// Small
+.pagination-small {
+ ul > li > a,
+ ul > li > span {
+ padding: @paddingSmall;
+ font-size: @fontSizeSmall;
+ }
+}
+// Mini
+.pagination-mini {
+ ul > li > a,
+ ul > li > span {
+ padding: @paddingMini;
+ font-size: @fontSizeMini;
+ }
+}
diff --git a/src/fauxton/assets/less/bootstrap/popovers.less b/src/fauxton/assets/less/bootstrap/popovers.less
new file mode 100644
index 000000000..a4c4bb0e0
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/popovers.less
@@ -0,0 +1,117 @@
+//
+// Popovers
+// --------------------------------------------------
+
+
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: @zindexPopover;
+ display: none;
+ width: 236px;
+ padding: 1px;
+ background-color: @popoverBackground;
+ -webkit-background-clip: padding-box;
+ -moz-background-clip: padding;
+ background-clip: padding-box;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0,0,0,.2);
+ .border-radius(6px);
+ .box-shadow(0 5px 10px rgba(0,0,0,.2));
+
+ // Offset the popover to account for the popover arrow
+ &.top { margin-top: -10px; }
+ &.right { margin-left: 10px; }
+ &.bottom { margin-top: 10px; }
+ &.left { margin-left: -10px; }
+
+}
+
+.popover-title {
+ margin: 0; // reset heading margin
+ padding: 8px 14px;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 18px;
+ background-color: @popoverTitleBackground;
+ border-bottom: 1px solid darken(@popoverTitleBackground, 5%);
+ .border-radius(5px 5px 0 0);
+}
+
+.popover-content {
+ padding: 9px 14px;
+ p, ul, ol {
+ margin-bottom: 0;
+ }
+}
+
+// Arrows
+.popover .arrow,
+.popover .arrow:after {
+ position: absolute;
+ display: inline-block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+.popover .arrow:after {
+ content: "";
+ z-index: -1;
+}
+
+.popover {
+ &.top .arrow {
+ bottom: -@popoverArrowWidth;
+ left: 50%;
+ margin-left: -@popoverArrowWidth;
+ border-width: @popoverArrowWidth @popoverArrowWidth 0;
+ border-top-color: @popoverArrowColor;
+ &:after {
+ border-width: @popoverArrowOuterWidth @popoverArrowOuterWidth 0;
+ border-top-color: @popoverArrowOuterColor;
+ bottom: -1px;
+ left: -@popoverArrowOuterWidth;
+ }
+ }
+ &.right .arrow {
+ top: 50%;
+ left: -@popoverArrowWidth;
+ margin-top: -@popoverArrowWidth;
+ border-width: @popoverArrowWidth @popoverArrowWidth @popoverArrowWidth 0;
+ border-right-color: @popoverArrowColor;
+ &:after {
+ border-width: @popoverArrowOuterWidth @popoverArrowOuterWidth @popoverArrowOuterWidth 0;
+ border-right-color: @popoverArrowOuterColor;
+ bottom: -@popoverArrowOuterWidth;
+ left: -1px;
+ }
+ }
+ &.bottom .arrow {
+ top: -@popoverArrowWidth;
+ left: 50%;
+ margin-left: -@popoverArrowWidth;
+ border-width: 0 @popoverArrowWidth @popoverArrowWidth;
+ border-bottom-color: @popoverArrowColor;
+ &:after {
+ border-width: 0 @popoverArrowOuterWidth @popoverArrowOuterWidth;
+ border-bottom-color: @popoverArrowOuterColor;
+ top: -1px;
+ left: -@popoverArrowOuterWidth;
+ }
+ }
+ &.left .arrow {
+ top: 50%;
+ right: -@popoverArrowWidth;
+ margin-top: -@popoverArrowWidth;
+ border-width: @popoverArrowWidth 0 @popoverArrowWidth @popoverArrowWidth;
+ border-left-color: @popoverArrowColor;
+ &:after {
+ border-width: @popoverArrowOuterWidth 0 @popoverArrowOuterWidth @popoverArrowOuterWidth;
+ border-left-color: @popoverArrowOuterColor;
+ bottom: -@popoverArrowOuterWidth;
+ right: -1px;
+ }
+ }
+}
diff --git a/src/fauxton/assets/less/bootstrap/progress-bars.less b/src/fauxton/assets/less/bootstrap/progress-bars.less
new file mode 100644
index 000000000..5e0c3dda0
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/progress-bars.less
@@ -0,0 +1,122 @@
+//
+// Progress bars
+// --------------------------------------------------
+
+
+// ANIMATIONS
+// ----------
+
+// Webkit
+@-webkit-keyframes progress-bar-stripes {
+ from { background-position: 40px 0; }
+ to { background-position: 0 0; }
+}
+
+// Firefox
+@-moz-keyframes progress-bar-stripes {
+ from { background-position: 40px 0; }
+ to { background-position: 0 0; }
+}
+
+// IE9
+@-ms-keyframes progress-bar-stripes {
+ from { background-position: 40px 0; }
+ to { background-position: 0 0; }
+}
+
+// Opera
+@-o-keyframes progress-bar-stripes {
+ from { background-position: 0 0; }
+ to { background-position: 40px 0; }
+}
+
+// Spec
+@keyframes progress-bar-stripes {
+ from { background-position: 40px 0; }
+ to { background-position: 0 0; }
+}
+
+
+
+// THE BARS
+// --------
+
+// Outer container
+.progress {
+ overflow: hidden;
+ height: @baseLineHeight;
+ margin-bottom: @baseLineHeight;
+ #gradient > .vertical(#f5f5f5, #f9f9f9);
+ .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));
+ .border-radius(@baseBorderRadius);
+}
+
+// Bar of progress
+.progress .bar {
+ width: 0%;
+ height: 100%;
+ color: @white;
+ float: left;
+ font-size: 12px;
+ text-align: center;
+ text-shadow: 0 -1px 0 rgba(0,0,0,.25);
+ #gradient > .vertical(#149bdf, #0480be);
+ .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));
+ .box-sizing(border-box);
+ .transition(width .6s ease);
+}
+.progress .bar + .bar {
+ .box-shadow(~"inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15)");
+}
+
+// Striped bars
+.progress-striped .bar {
+ #gradient > .striped(#149bdf);
+ .background-size(40px 40px);
+}
+
+// Call animation for the active one
+.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;
+}
+
+
+
+// COLORS
+// ------
+
+// Danger (red)
+.progress-danger .bar, .progress .bar-danger {
+ #gradient > .vertical(#ee5f5b, #c43c35);
+}
+.progress-danger.progress-striped .bar, .progress-striped .bar-danger {
+ #gradient > .striped(#ee5f5b);
+}
+
+// Success (green)
+.progress-success .bar, .progress .bar-success {
+ #gradient > .vertical(#62c462, #57a957);
+}
+.progress-success.progress-striped .bar, .progress-striped .bar-success {
+ #gradient > .striped(#62c462);
+}
+
+// Info (teal)
+.progress-info .bar, .progress .bar-info {
+ #gradient > .vertical(#5bc0de, #339bb9);
+}
+.progress-info.progress-striped .bar, .progress-striped .bar-info {
+ #gradient > .striped(#5bc0de);
+}
+
+// Warning (orange)
+.progress-warning .bar, .progress .bar-warning {
+ #gradient > .vertical(lighten(@orange, 15%), @orange);
+}
+.progress-warning.progress-striped .bar, .progress-striped .bar-warning {
+ #gradient > .striped(lighten(@orange, 15%));
+}
diff --git a/src/fauxton/assets/less/bootstrap/reset.less b/src/fauxton/assets/less/bootstrap/reset.less
new file mode 100644
index 000000000..2abdee462
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/reset.less
@@ -0,0 +1,138 @@
+//
+// Modals
+// Adapted from http://github.com/necolas/normalize.css
+// --------------------------------------------------
+
+
+// Display in IE6-9 and FF3
+// -------------------------
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+ display: block;
+}
+
+// Display block in IE6-9 and FF3
+// -------------------------
+
+audio,
+canvas,
+video {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+}
+
+// Prevents modern browsers from displaying 'audio' without controls
+// -------------------------
+
+audio:not([controls]) {
+ display: none;
+}
+
+// Base settings
+// -------------------------
+
+html {
+ font-size: 100%;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+// Focus states
+a:focus {
+ .tab-focus();
+}
+// Hover & Active
+a:hover,
+a:active {
+ outline: 0;
+}
+
+// Prevents sub and sup affecting line-height in all browsers
+// -------------------------
+
+sub,
+sup {
+ position: relative;
+ font-size: 75%;
+ line-height: 0;
+ vertical-align: baseline;
+}
+sup {
+ top: -0.5em;
+}
+sub {
+ bottom: -0.25em;
+}
+
+// Img border in a's and image quality
+// -------------------------
+
+img {
+ /* Responsive images (ensure images don't scale beyond their parents) */
+ max-width: 100%; /* Part 1: Set a maxium relative to the parent */
+ width: auto\9; /* IE7-8 need help adjusting responsive images */
+ height: auto; /* Part 2: Scale the height according to the width, otherwise you get stretching */
+
+ vertical-align: middle;
+ border: 0;
+ -ms-interpolation-mode: bicubic;
+}
+
+// Prevent max-width from affecting Google Maps
+#map_canvas img,
+.google-maps img {
+ max-width: none;
+}
+
+// Forms
+// -------------------------
+
+// Font size in all browsers, margin changes, misc consistency
+button,
+input,
+select,
+textarea {
+ margin: 0;
+ font-size: 100%;
+ vertical-align: middle;
+}
+button,
+input {
+ *overflow: visible; // Inner spacing ie IE6/7
+ line-height: normal; // FF3/4 have !important on line-height in UA stylesheet
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner { // Inner padding and border oddities in FF3/4
+ padding: 0;
+ border: 0;
+}
+button,
+html input[type="button"], // Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls.
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; // Corrects inability to style clickable `input` types in iOS.
+ cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others.
+}
+input[type="search"] { // Appearance in Safari/Chrome
+ -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; // Inner-padding issues in Chrome OSX, Safari 5
+}
+textarea {
+ overflow: auto; // Remove vertical scrollbar in IE6-9
+ vertical-align: top; // Readability and alignment cross-browser
+}
diff --git a/src/fauxton/assets/less/bootstrap/responsive-1200px-min.less b/src/fauxton/assets/less/bootstrap/responsive-1200px-min.less
new file mode 100644
index 000000000..4f35ba6ca
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/responsive-1200px-min.less
@@ -0,0 +1,28 @@
+//
+// Responsive: Large desktop and up
+// --------------------------------------------------
+
+
+@media (min-width: 1200px) {
+
+ // Fixed grid
+ #grid > .core(@gridColumnWidth1200, @gridGutterWidth1200);
+
+ // Fluid grid
+ #grid > .fluid(@fluidGridColumnWidth1200, @fluidGridGutterWidth1200);
+
+ // Input grid
+ #grid > .input(@gridColumnWidth1200, @gridGutterWidth1200);
+
+ // Thumbnails
+ .thumbnails {
+ margin-left: -@gridGutterWidth1200;
+ }
+ .thumbnails > li {
+ margin-left: @gridGutterWidth1200;
+ }
+ .row-fluid .thumbnails {
+ margin-left: 0;
+ }
+
+}
diff --git a/src/fauxton/assets/less/bootstrap/responsive-767px-max.less b/src/fauxton/assets/less/bootstrap/responsive-767px-max.less
new file mode 100644
index 000000000..1d5c1239c
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/responsive-767px-max.less
@@ -0,0 +1,193 @@
+//
+// Responsive: Landscape phone to desktop/tablet
+// --------------------------------------------------
+
+
+@media (max-width: 767px) {
+
+ // Padding to set content in a bit
+ body {
+ padding-left: 20px;
+ padding-right: 20px;
+ }
+ // Negative indent the now static "fixed" navbar
+ .navbar-fixed-top,
+ .navbar-fixed-bottom,
+ .navbar-static-top {
+ margin-left: -20px;
+ margin-right: -20px;
+ }
+ // Remove padding on container given explicit padding set on body
+ .container-fluid {
+ padding: 0;
+ }
+
+ // TYPOGRAPHY
+ // ----------
+ // Reset horizontal dl
+ .dl-horizontal {
+ dt {
+ float: none;
+ clear: none;
+ width: auto;
+ text-align: left;
+ }
+ dd {
+ margin-left: 0;
+ }
+ }
+
+ // GRID & CONTAINERS
+ // -----------------
+ // Remove width from containers
+ .container {
+ width: auto;
+ }
+ // Fluid rows
+ .row-fluid {
+ width: 100%;
+ }
+ // Undo negative margin on rows and thumbnails
+ .row,
+ .thumbnails {
+ margin-left: 0;
+ }
+ .thumbnails > li {
+ float: none;
+ margin-left: 0; // Reset the default margin for all li elements when no .span* classes are present
+ }
+ // Make all grid-sized elements block level again
+ [class*="span"],
+ .uneditable-input[class*="span"], // Makes uneditable inputs full-width when using grid sizing
+ .row-fluid [class*="span"] {
+ float: none;
+ display: block;
+ width: 100%;
+ margin-left: 0;
+ .box-sizing(border-box);
+ }
+ .span12,
+ .row-fluid .span12 {
+ width: 100%;
+ .box-sizing(border-box);
+ }
+ .row-fluid [class*="offset"]:first-child {
+ margin-left: 0;
+ }
+
+ // FORM FIELDS
+ // -----------
+ // Make span* classes full width
+ .input-large,
+ .input-xlarge,
+ .input-xxlarge,
+ input[class*="span"],
+ select[class*="span"],
+ textarea[class*="span"],
+ .uneditable-input {
+ .input-block-level();
+ }
+ // But don't let it screw up prepend/append inputs
+ .input-prepend input,
+ .input-append input,
+ .input-prepend input[class*="span"],
+ .input-append input[class*="span"] {
+ display: inline-block; // redeclare so they don't wrap to new lines
+ width: auto;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 0;
+ }
+
+ // Modals
+ .modal {
+ position: fixed;
+ top: 20px;
+ left: 20px;
+ right: 20px;
+ width: auto;
+ margin: 0;
+ &.fade { top: -100px; }
+ &.fade.in { top: 20px; }
+ }
+
+}
+
+
+
+// UP TO LANDSCAPE PHONE
+// ---------------------
+
+@media (max-width: 480px) {
+
+ // Smooth out the collapsing/expanding nav
+ .nav-collapse {
+ -webkit-transform: translate3d(0, 0, 0); // activate the GPU
+ }
+
+ // Block level the page header small tag for readability
+ .page-header h1 small {
+ display: block;
+ line-height: @baseLineHeight;
+ }
+
+ // Update checkboxes for iOS
+ input[type="checkbox"],
+ input[type="radio"] {
+ border: 1px solid #ccc;
+ }
+
+ // Remove the horizontal form styles
+ .form-horizontal {
+ .control-label {
+ float: none;
+ width: auto;
+ padding-top: 0;
+ text-align: left;
+ }
+ // Move over all input controls and content
+ .controls {
+ margin-left: 0;
+ }
+ // Move the options list down to align with labels
+ .control-list {
+ padding-top: 0; // has to be padding because margin collaspes
+ }
+ // Move over buttons in .form-actions to align with .controls
+ .form-actions {
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+ }
+
+ // Medias
+ // Reset float and spacing to stack
+ .media .pull-left,
+ .media .pull-right {
+ float: none;
+ display: block;
+ margin-bottom: 10px;
+ }
+ // Remove side margins since we stack instead of indent
+ .media-object {
+ margin-right: 0;
+ margin-left: 0;
+ }
+
+ // Modals
+ .modal {
+ top: 10px;
+ left: 10px;
+ right: 10px;
+ }
+ .modal-header .close {
+ padding: 10px;
+ margin: -10px;
+ }
+
+ // Carousel
+ .carousel-caption {
+ position: static;
+ }
+
+}
diff --git a/src/fauxton/assets/less/bootstrap/responsive-768px-979px.less b/src/fauxton/assets/less/bootstrap/responsive-768px-979px.less
new file mode 100644
index 000000000..8e8c486a0
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/responsive-768px-979px.less
@@ -0,0 +1,19 @@
+//
+// Responsive: Tablet to desktop
+// --------------------------------------------------
+
+
+@media (min-width: 768px) and (max-width: 979px) {
+
+ // Fixed grid
+ #grid > .core(@gridColumnWidth768, @gridGutterWidth768);
+
+ // Fluid grid
+ #grid > .fluid(@fluidGridColumnWidth768, @fluidGridGutterWidth768);
+
+ // Input grid
+ #grid > .input(@gridColumnWidth768, @gridGutterWidth768);
+
+ // No need to reset .thumbnails here since it's the same @gridGutterWidth
+
+}
diff --git a/src/fauxton/assets/less/bootstrap/responsive-navbar.less b/src/fauxton/assets/less/bootstrap/responsive-navbar.less
new file mode 100644
index 000000000..2a0b0c057
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/responsive-navbar.less
@@ -0,0 +1,185 @@
+//
+// Responsive: Navbar
+// --------------------------------------------------
+
+
+// TABLETS AND BELOW
+// -----------------
+@media (max-width: @navbarCollapseWidth) {
+
+ // UNFIX THE TOPBAR
+ // ----------------
+ // Remove any padding from the body
+ body {
+ padding-top: 0;
+ }
+ // Unfix the navbars
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ position: static;
+ }
+ .navbar-fixed-top {
+ margin-bottom: @baseLineHeight;
+ }
+ .navbar-fixed-bottom {
+ margin-top: @baseLineHeight;
+ }
+ .navbar-fixed-top .navbar-inner,
+ .navbar-fixed-bottom .navbar-inner {
+ padding: 5px;
+ }
+ .navbar .container {
+ width: auto;
+ padding: 0;
+ }
+ // Account for brand name
+ .navbar .brand {
+ padding-left: 10px;
+ padding-right: 10px;
+ margin: 0 0 0 -5px;
+ }
+
+ // COLLAPSIBLE NAVBAR
+ // ------------------
+ // Nav collapse clears brand
+ .nav-collapse {
+ clear: both;
+ }
+ // Block-level the nav
+ .nav-collapse .nav {
+ float: none;
+ margin: 0 0 (@baseLineHeight / 2);
+ }
+ .nav-collapse .nav > li {
+ float: none;
+ }
+ .nav-collapse .nav > li > a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > .divider-vertical {
+ display: none;
+ }
+ .nav-collapse .nav .nav-header {
+ color: @navbarText;
+ text-shadow: none;
+ }
+ // Nav and dropdown links in navbar
+ .nav-collapse .nav > li > a,
+ .nav-collapse .dropdown-menu a {
+ padding: 9px 15px;
+ font-weight: bold;
+ color: @navbarLinkColor;
+ .border-radius(3px);
+ }
+ // Buttons
+ .nav-collapse .btn {
+ padding: 4px 10px 4px;
+ font-weight: normal;
+ .border-radius(@baseBorderRadius);
+ }
+ .nav-collapse .dropdown-menu li + li a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > li > a:hover,
+ .nav-collapse .dropdown-menu a:hover {
+ background-color: @navbarBackground;
+ }
+ .navbar-inverse .nav-collapse .nav > li > a,
+ .navbar-inverse .nav-collapse .dropdown-menu a {
+ color: @navbarInverseLinkColor;
+ }
+ .navbar-inverse .nav-collapse .nav > li > a:hover,
+ .navbar-inverse .nav-collapse .dropdown-menu a:hover {
+ background-color: @navbarInverseBackground;
+ }
+ // Buttons in the navbar
+ .nav-collapse.in .btn-group {
+ margin-top: 5px;
+ padding: 0;
+ }
+ // Dropdowns in the navbar
+ .nav-collapse .dropdown-menu {
+ position: static;
+ top: auto;
+ left: auto;
+ float: none;
+ display: none;
+ max-width: none;
+ margin: 0 15px;
+ padding: 0;
+ background-color: transparent;
+ border: none;
+ .border-radius(0);
+ .box-shadow(none);
+ }
+ .nav-collapse .open > .dropdown-menu {
+ display: block;
+ }
+
+ .nav-collapse .dropdown-menu:before,
+ .nav-collapse .dropdown-menu:after {
+ display: none;
+ }
+ .nav-collapse .dropdown-menu .divider {
+ display: none;
+ }
+ .nav-collapse .nav > li > .dropdown-menu {
+ &:before,
+ &:after {
+ display: none;
+ }
+ }
+ // Forms in navbar
+ .nav-collapse .navbar-form,
+ .nav-collapse .navbar-search {
+ float: none;
+ padding: (@baseLineHeight / 2) 15px;
+ margin: (@baseLineHeight / 2) 0;
+ border-top: 1px solid @navbarBackground;
+ border-bottom: 1px solid @navbarBackground;
+ .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1)");
+ }
+ .navbar-inverse .nav-collapse .navbar-form,
+ .navbar-inverse .nav-collapse .navbar-search {
+ border-top-color: @navbarInverseBackground;
+ border-bottom-color: @navbarInverseBackground;
+ }
+ // Pull right (secondary) nav content
+ .navbar .nav-collapse .nav.pull-right {
+ float: none;
+ margin-left: 0;
+ }
+ // Hide everything in the navbar save .brand and toggle button */
+ .nav-collapse,
+ .nav-collapse.collapse {
+ overflow: hidden;
+ height: 0;
+ }
+ // Navbar button
+ .navbar .btn-navbar {
+ display: block;
+ }
+
+ // STATIC NAVBAR
+ // -------------
+ .navbar-static .navbar-inner {
+ padding-left: 10px;
+ padding-right: 10px;
+ }
+
+
+}
+
+
+// DEFAULT DESKTOP
+// ---------------
+
+@media (min-width: @navbarCollapseDesktopWidth) {
+
+ // Required to make the collapsing navbar work on regular desktops
+ .nav-collapse.collapse {
+ height: auto !important;
+ overflow: visible !important;
+ }
+
+}
diff --git a/src/fauxton/assets/less/bootstrap/responsive-utilities.less b/src/fauxton/assets/less/bootstrap/responsive-utilities.less
new file mode 100644
index 000000000..2c3f6c15f
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/responsive-utilities.less
@@ -0,0 +1,43 @@
+//
+// Responsive: Utility classes
+// --------------------------------------------------
+
+
+// Hide from screenreaders and browsers
+// Credit: HTML5 Boilerplate
+.hidden {
+ display: none;
+ visibility: hidden;
+}
+
+// Visibility utilities
+
+// For desktops
+.visible-phone { display: none !important; }
+.visible-tablet { display: none !important; }
+.hidden-phone { }
+.hidden-tablet { }
+.hidden-desktop { display: none !important; }
+.visible-desktop { display: inherit !important; }
+
+// Tablets & small desktops only
+@media (min-width: 768px) and (max-width: 979px) {
+ // Hide everything else
+ .hidden-desktop { display: inherit !important; }
+ .visible-desktop { display: none !important ; }
+ // Show
+ .visible-tablet { display: inherit !important; }
+ // Hide
+ .hidden-tablet { display: none !important; }
+}
+
+// Phones only
+@media (max-width: 767px) {
+ // Hide everything else
+ .hidden-desktop { display: inherit !important; }
+ .visible-desktop { display: none !important; }
+ // Show
+ .visible-phone { display: inherit !important; } // Use inherit to restore previous behavior
+ // Hide
+ .hidden-phone { display: none !important; }
+}
diff --git a/src/fauxton/assets/less/bootstrap/responsive.less b/src/fauxton/assets/less/bootstrap/responsive.less
new file mode 100644
index 000000000..aa28baaec
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/responsive.less
@@ -0,0 +1,48 @@
+/*!
+ * Bootstrap Responsive v2.2.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+
+// Responsive.less
+// For phone and tablet devices
+// -------------------------------------------------------------
+
+
+// REPEAT VARIABLES & MIXINS
+// -------------------------
+// Required since we compile the responsive stuff separately
+
+@import "variables.less"; // Modify this for custom colors, font-sizes, etc
+@import "mixins.less";
+
+
+// RESPONSIVE CLASSES
+// ------------------
+
+@import "responsive-utilities.less";
+
+
+// MEDIA QUERIES
+// ------------------
+
+// Large desktops
+@import "responsive-1200px-min.less";
+
+// Tablets to regular desktops
+@import "responsive-768px-979px.less";
+
+// Phones to portrait tablets and narrow desktops
+@import "responsive-767px-max.less";
+
+
+// RESPONSIVE NAVBAR
+// ------------------
+
+// From 979px and below, show a button to toggle navbar contents
+@import "responsive-navbar.less";
diff --git a/src/fauxton/assets/less/bootstrap/scaffolding.less b/src/fauxton/assets/less/bootstrap/scaffolding.less
new file mode 100644
index 000000000..7a7496a64
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/scaffolding.less
@@ -0,0 +1,52 @@
+//
+// Scaffolding
+// --------------------------------------------------
+
+
+// Body reset
+// -------------------------
+
+body {
+ margin: 0;
+ font-family: @baseFontFamily;
+ font-size: @baseFontSize;
+ line-height: @baseLineHeight;
+ color: @textColor;
+ background-color: @bodyBackground;
+}
+
+
+// Links
+// -------------------------
+
+a {
+ color: @linkColor;
+ text-decoration: none;
+}
+a:hover {
+ color: @linkColorHover;
+ text-decoration: underline;
+}
+
+
+// Images
+// -------------------------
+
+// Rounded corners
+.img-rounded {
+ .border-radius(6px);
+}
+
+// Add polaroid-esque trim
+.img-polaroid {
+ padding: 4px;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0,0,0,.2);
+ .box-shadow(0 1px 3px rgba(0,0,0,.1));
+}
+
+// Perfect circle
+.img-circle {
+ .border-radius(500px); // crank the border-radius so it works with most reasonably sized images
+}
diff --git a/src/fauxton/assets/less/bootstrap/sprites.less b/src/fauxton/assets/less/bootstrap/sprites.less
new file mode 100644
index 000000000..9cd2ae3bf
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/sprites.less
@@ -0,0 +1,193 @@
+//
+// Sprites
+// --------------------------------------------------
+
+
+// ICONS
+// -----
+
+// All icons receive the styles of the <i> tag with a base class
+// of .i and are then given a unique class to add width, height,
+// and background-position. Your resulting HTML will look like
+// <i class="icon-inbox"></i>.
+
+// For the white version of the icons, just add the .icon-white class:
+// <i class="icon-inbox icon-white"></i>
+
+[class^="icon-"],
+[class*=" icon-"] {
+ display: inline-block;
+ width: 14px;
+ height: 14px;
+ .ie7-restore-right-whitespace();
+ line-height: 14px;
+ vertical-align: text-top;
+ background-image: url("@{iconSpritePath}");
+ background-position: 14px 14px;
+ background-repeat: no-repeat;
+ margin-top: 1px;
+}
+
+/* White icons with optional class, or on hover/active states of certain elements */
+.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:hover > [class*=" icon-"],
+.dropdown-menu > .active > a > [class^="icon-"],
+.dropdown-menu > .active > a > [class*=" icon-"],
+.dropdown-submenu:hover > a > [class^="icon-"],
+.dropdown-submenu:hover > a > [class*=" icon-"] {
+ background-image: url("@{iconWhiteSpritePath}");
+}
+
+.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; } // 1px off
+.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; } // 1px off
+.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; } // 1px off
+.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; } // 1px off
+.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 { background-position: -216px -120px; width: 16px; }
+.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; } // 1px, 1px off
+.icon-retweet { background-position: -336px -120px; }
+.icon-shopping-cart { background-position: -360px -120px; }
+.icon-folder-close { background-position: -384px -120px; }
+.icon-folder-open { background-position: -408px -120px; width: 16px; }
+.icon-resize-vertical { background-position: -432px -119px; } // 1px, 1px off
+.icon-resize-horizontal { background-position: -456px -118px; } // 1px, 2px off
+
+.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; }
diff --git a/src/fauxton/assets/less/bootstrap/tables.less b/src/fauxton/assets/less/bootstrap/tables.less
new file mode 100644
index 000000000..3f2c7f783
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/tables.less
@@ -0,0 +1,236 @@
+//
+// Tables
+// --------------------------------------------------
+
+
+// BASE TABLES
+// -----------------
+
+table {
+ max-width: 100%;
+ background-color: @tableBackground;
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+// BASELINE STYLES
+// ---------------
+
+.table {
+ width: 100%;
+ margin-bottom: @baseLineHeight;
+ // Cells
+ th,
+ td {
+ padding: 8px;
+ line-height: @baseLineHeight;
+ text-align: left;
+ vertical-align: top;
+ border-top: 1px solid @tableBorder;
+ }
+ th {
+ font-weight: bold;
+ }
+ // Bottom align for column headings
+ thead th {
+ vertical-align: bottom;
+ }
+ // Remove top border from thead by default
+ caption + thead tr:first-child th,
+ caption + thead tr:first-child td,
+ colgroup + thead tr:first-child th,
+ colgroup + thead tr:first-child td,
+ thead:first-child tr:first-child th,
+ thead:first-child tr:first-child td {
+ border-top: 0;
+ }
+ // Account for multiple tbody instances
+ tbody + tbody {
+ border-top: 2px solid @tableBorder;
+ }
+}
+
+
+
+// CONDENSED TABLE W/ HALF PADDING
+// -------------------------------
+
+.table-condensed {
+ th,
+ td {
+ padding: 4px 5px;
+ }
+}
+
+
+// BORDERED VERSION
+// ----------------
+
+.table-bordered {
+ border: 1px solid @tableBorder;
+ border-collapse: separate; // Done so we can round those corners!
+ *border-collapse: collapse; // IE7 can't round corners anyway
+ border-left: 0;
+ .border-radius(@baseBorderRadius);
+ th,
+ td {
+ border-left: 1px solid @tableBorder;
+ }
+ // Prevent a double border
+ caption + thead tr:first-child th,
+ caption + tbody tr:first-child th,
+ caption + tbody tr:first-child td,
+ colgroup + thead tr:first-child th,
+ colgroup + tbody tr:first-child th,
+ colgroup + tbody tr:first-child td,
+ thead:first-child tr:first-child th,
+ tbody:first-child tr:first-child th,
+ tbody:first-child tr:first-child td {
+ border-top: 0;
+ }
+ // For first th or td in the first row in the first thead or tbody
+ thead:first-child tr:first-child th:first-child,
+ tbody:first-child tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+ }
+ thead:first-child tr:first-child th:last-child,
+ tbody:first-child tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ }
+ // For first th or td in the first row in the first thead or tbody
+ thead:last-child tr:last-child th:first-child,
+ tbody:last-child tr:last-child td:first-child,
+ tfoot:last-child tr:last-child td:first-child {
+ .border-radius(0 0 0 4px);
+ -webkit-border-bottom-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ }
+ thead:last-child tr:last-child th:last-child,
+ tbody:last-child tr:last-child td:last-child,
+ tfoot:last-child tr:last-child td:last-child {
+ -webkit-border-bottom-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ }
+
+ // Special fixes to round the left border on the first td/th
+ caption + thead tr:first-child th:first-child,
+ caption + tbody tr:first-child td:first-child,
+ colgroup + thead tr:first-child th:first-child,
+ colgroup + tbody tr:first-child td:first-child {
+ -webkit-border-top-left-radius: 4px;
+ border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+ }
+ caption + thead tr:first-child th:last-child,
+ caption + tbody tr:first-child td:last-child,
+ colgroup + thead tr:first-child th:last-child,
+ colgroup + tbody tr:first-child td:last-child {
+ -webkit-border-top-right-radius: 4px;
+ border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ }
+
+}
+
+
+
+
+// ZEBRA-STRIPING
+// --------------
+
+// Default zebra-stripe styles (alternating gray and transparent backgrounds)
+.table-striped {
+ tbody {
+ tr:nth-child(odd) td,
+ tr:nth-child(odd) th {
+ background-color: @tableBackgroundAccent;
+ }
+ }
+}
+
+
+// HOVER EFFECT
+// ------------
+// Placed here since it has to come after the potential zebra striping
+.table-hover {
+ tbody {
+ tr:hover td,
+ tr:hover th {
+ background-color: @tableBackgroundHover;
+ }
+ }
+}
+
+
+// TABLE CELL SIZING
+// -----------------
+
+// Reset default grid behavior
+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; // undo default grid column styles
+ margin-left: 0; // undo default grid column styles
+}
+
+// Change the column widths to account for td/th padding
+.table td,
+.table th {
+ &.span1 { .tableColumns(1); }
+ &.span2 { .tableColumns(2); }
+ &.span3 { .tableColumns(3); }
+ &.span4 { .tableColumns(4); }
+ &.span5 { .tableColumns(5); }
+ &.span6 { .tableColumns(6); }
+ &.span7 { .tableColumns(7); }
+ &.span8 { .tableColumns(8); }
+ &.span9 { .tableColumns(9); }
+ &.span10 { .tableColumns(10); }
+ &.span11 { .tableColumns(11); }
+ &.span12 { .tableColumns(12); }
+}
+
+
+
+// TABLE BACKGROUNDS
+// -----------------
+// Exact selectors below required to override .table-striped
+
+.table tbody tr {
+ &.success td {
+ background-color: @successBackground;
+ }
+ &.error td {
+ background-color: @errorBackground;
+ }
+ &.warning td {
+ background-color: @warningBackground;
+ }
+ &.info td {
+ background-color: @infoBackground;
+ }
+}
+
+// Hover states for .table-hover
+.table-hover tbody tr {
+ &.success:hover td {
+ background-color: darken(@successBackground, 5%);
+ }
+ &.error:hover td {
+ background-color: darken(@errorBackground, 5%);
+ }
+ &.warning:hover td {
+ background-color: darken(@warningBackground, 5%);
+ }
+ &.info:hover td {
+ background-color: darken(@infoBackground, 5%);
+ }
+}
diff --git a/src/fauxton/assets/less/bootstrap/tests/buttons.html b/src/fauxton/assets/less/bootstrap/tests/buttons.html
new file mode 100644
index 000000000..5fe7f664b
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/tests/buttons.html
@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Buttons &middot; Bootstrap</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le styles -->
+ <link href="../../docs/assets/css/bootstrap.css" rel="stylesheet">
+ <style>
+ body {
+ padding-top: 30px;
+ padding-bottom: 30px;
+ }
+ </style>
+ <link href="../../docs/assets/css/bootstrap-responsive.css" rel="stylesheet">
+
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+
+ <!-- Le fav and touch icons -->
+ <link rel="shortcut icon" href="../../docs/assets/ico/favicon.ico">
+ <link rel="apple-touch-icon-precomposed" sizes="144x144" href="../../docs/assets/ico/apple-touch-icon-144-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="114x114" href="../../docs/assets/ico/apple-touch-icon-114-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="72x72" href="../../docs/assets/ico/apple-touch-icon-72-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" href="../../docs/assets/ico/apple-touch-icon-57-precomposed.png">
+ </head>
+
+ <body>
+
+ <div class="container">
+
+ <h2>Dropups</h2>
+ <div class="btn-toolbar">
+ <div class="btn-group dropup">
+ <button class="btn">Dropup</button>
+ <button class="btn dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Separated link</a></li>
+ </ul>
+ </div><!-- /btn-group -->
+ <div class="btn-group dropup">
+ <button class="btn btn-primary">Dropup</button>
+ <button class="btn btn-primary dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Separated link</a></li>
+ </ul>
+ </div><!-- /btn-group -->
+ <div class="btn-group dropup">
+ <button class="btn btn-danger">Dropup</button>
+ <button class="btn btn-danger dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Separated link</a></li>
+ </ul>
+ </div><!-- /btn-group -->
+ <div class="btn-group dropup">
+ <button class="btn btn-warning">Dropup</button>
+ <button class="btn btn-warning dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Separated link</a></li>
+ </ul>
+ </div><!-- /btn-group -->
+ <div class="btn-group dropup">
+ <button class="btn btn-success">Dropup</button>
+ <button class="btn btn-success dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Separated link</a></li>
+ </ul>
+ </div><!-- /btn-group -->
+ <div class="btn-group dropup">
+ <button class="btn btn-info">Dropup</button>
+ <button class="btn btn-info dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Separated link</a></li>
+ </ul>
+ </div><!-- /btn-group -->
+ <div class="btn-group dropup">
+ <button class="btn btn-inverse">Dropup</button>
+ <button class="btn btn-inverse dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Separated link</a></li>
+ </ul>
+ </div><!-- /btn-group -->
+ </div><!-- /btn-toolbar -->
+
+
+ </div> <!-- /container -->
+
+ <!-- Le javascript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="../../docs/assets/js/jquery.js"></script>
+ <script src="../../docs/assets/js/bootstrap-transition.js"></script>
+ <script src="../../docs/assets/js/bootstrap-alert.js"></script>
+ <script src="../../docs/assets/js/bootstrap-modal.js"></script>
+ <script src="../../docs/assets/js/bootstrap-dropdown.js"></script>
+ <script src="../../docs/assets/js/bootstrap-scrollspy.js"></script>
+ <script src="../../docs/assets/js/bootstrap-tab.js"></script>
+ <script src="../../docs/assets/js/bootstrap-tooltip.js"></script>
+ <script src="../../docs/assets/js/bootstrap-popover.js"></script>
+ <script src="../../docs/assets/js/bootstrap-button.js"></script>
+ <script src="../../docs/assets/js/bootstrap-collapse.js"></script>
+ <script src="../../docs/assets/js/bootstrap-carousel.js"></script>
+ <script src="../../docs/assets/js/bootstrap-typeahead.js"></script>
+
+ </body>
+</html>
diff --git a/src/fauxton/assets/less/bootstrap/tests/css-tests.css b/src/fauxton/assets/less/bootstrap/tests/css-tests.css
new file mode 100644
index 000000000..9edaf69bf
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/tests/css-tests.css
@@ -0,0 +1,139 @@
+/*!
+ * Bootstrap CSS Tests
+ */
+
+
+/* Remove background image */
+body {
+ background-image: none;
+}
+
+/* Space out subhead */
+.subhead {
+ margin-bottom: 36px;
+}
+/*h4 {
+ margin-bottom: 5px;
+}
+*/
+
+.type-test {
+ margin-bottom: 20px;
+ padding: 0 20px 20px;
+ background: url(../../docs/assets/img/grid-baseline-20px.png);
+}
+.type-test h1,
+.type-test h2,
+.type-test h3,
+.type-test h4,
+.type-test h5,
+.type-test h6 {
+ background-color: rgba(255,0,0,.2);
+}
+
+
+/* colgroup tests */
+.col1 {
+ background-color: rgba(255,0,0,.1);
+}
+.col2 {
+ background-color: rgba(0,255,0,.1);
+}
+.col3 {
+ background-color: rgba(0,0,255,.1);
+}
+
+
+/* Fluid row inputs */
+#rowInputs .row > [class*=span],
+#fluidRowInputs .row-fluid > [class*=span] {
+ background-color: rgba(255,0,0,.1);
+}
+
+
+/* Fluid grid */
+.fluid-grid {
+ margin-bottom: 45px;
+}
+.fluid-grid .row {
+ height: 40px;
+ padding-top: 10px;
+ margin-top: 10px;
+ color: #ddd;
+ text-align: center;
+}
+.fluid-grid .span1 {
+ background-color: #999;
+}
+
+
+/* Gradients */
+
+[class^="gradient-"] {
+ width: 100%;
+ height: 400px;
+ margin: 20px 0;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.gradient-horizontal {
+ background-color: #333333;
+ background-image: -moz-linear-gradient(left, #555555, #333333);
+ background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#555555), to(#333333));
+ background-image: -webkit-linear-gradient(left, #555555, #333333);
+ background-image: -o-linear-gradient(left, #555555, #333333);
+ background-image: linear-gradient(to right, #555555, #333333);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff555555', endColorstr='#ff333333', GradientType=1);
+}
+
+.gradient-vertical {
+ background-color: #474747;
+ background-image: -moz-linear-gradient(top, #555555, #333333);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#555555), to(#333333));
+ background-image: -webkit-linear-gradient(top, #555555, #333333);
+ background-image: -o-linear-gradient(top, #555555, #333333);
+ background-image: linear-gradient(to bottom, #555555, #333333);
+ background-repeat: repeat-x;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff555555', endColorstr='#ff333333', GradientType=0);
+}
+
+.gradient-directional {
+ background-color: #333333;
+ background-image: -moz-linear-gradient(45deg, #555555, #333333);
+ background-image: -webkit-linear-gradient(45deg, #555555, #333333);
+ background-image: -o-linear-gradient(45deg, #555555, #333333);
+ background-image: linear-gradient(45deg, #555555, #333333);
+ background-repeat: repeat-x;
+}
+
+.gradient-vertical-three {
+ background-color: #8940a5;
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#00b3ee), color-stop(50%, #7a43b6), to(#c3325f));
+ background-image: -webkit-linear-gradient(#00b3ee, #7a43b6 50%, #c3325f);
+ background-image: -moz-linear-gradient(top, #00b3ee, #7a43b6 50%, #c3325f);
+ background-image: -o-linear-gradient(#00b3ee, #7a43b6 50%, #c3325f);
+ background-image: linear-gradient(#00b3ee, #7a43b6 50%, #c3325f);
+ background-repeat: no-repeat;
+ filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ff00b3ee', endColorstr='#ffc3325f', GradientType=0);
+}
+
+.gradient-radial {
+ background-color: #333333;
+ background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(#555555), to(#333333));
+ background-image: -webkit-radial-gradient(circle, #555555, #333333);
+ background-image: -moz-radial-gradient(circle, #555555, #333333);
+ background-image: -o-radial-gradient(circle, #555555, #333333);
+ background-repeat: no-repeat;
+}
+
+.gradient-striped {
+ background-color: #555555;
+ 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);
+} \ No newline at end of file
diff --git a/src/fauxton/assets/less/bootstrap/tests/css-tests.html b/src/fauxton/assets/less/bootstrap/tests/css-tests.html
new file mode 100644
index 000000000..c0cb1485e
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/tests/css-tests.html
@@ -0,0 +1,1296 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>CSS Tests · Twitter Bootstrap</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le styles -->
+ <link href="../../docs/assets/css/bootstrap.css" rel="stylesheet">
+ <link href="../../docs/assets/css/bootstrap-responsive.css" rel="stylesheet">
+ <link href="../../docs/assets/css/docs.css" rel="stylesheet">
+ <link href="../../docs/assets/js/google-code-prettify/prettify.css" rel="stylesheet">
+
+ <!-- CSS just for the tests page -->
+ <link href="css-tests.css" rel="stylesheet">
+
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+
+ <!-- Le fav and touch icons -->
+ <link rel="shortcut icon" href="../../docs/assets/ico/favicon.ico">
+ <link rel="apple-touch-icon-precomposed" sizes="144x144" href="../../docs/assets/ico/apple-touch-icon-144-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="114x114" href="../../docs/assets/ico/apple-touch-icon-114-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="72x72" href="../../docs/assets/ico/apple-touch-icon-72-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" href="../../docs/assets/ico/apple-touch-icon-57-precomposed.png">
+ </head>
+
+ <body>
+
+
+ <!-- Navbar
+ ================================================== -->
+ <div class="navbar navbar-inverse navbar-fixed-top">
+ <div class="navbar-inner">
+ <div class="container">
+ <a class="brand" href="../../docs/index.html">Bootstrap</a>
+ </div>
+ </div>
+ </div>
+
+
+<!-- Masthead
+================================================== -->
+<header class="jumbotron subhead" id="overview">
+ <div class="container">
+ <h1>CSS Tests</h1>
+ <p class="lead">One stop shop for quick debugging and edge-case tests of CSS.</p>
+ </div>
+</header>
+
+
+<div class="bs-docs-canvas">
+
+ <div class="container">
+
+
+
+<!-- Typography
+================================================== -->
+
+<div class="page-header">
+ <h1>Typography</h1>
+</div>
+
+<div class="row">
+ <div class="span6">
+ <div class="type-test">
+ <h1>h1. Heading 1</h1>
+ <h2>h2. Heading 2</h2>
+ <h3>h3. Heading 3</h3>
+ <h4>h4. Heading 4</h4>
+ <h5>h5. Heading 5</h5>
+ <h6>h6. Heading 6</h6>
+ <p>Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
+ </div>
+ </div>
+ <div class="span6">
+ <div class="type-test">
+ <h1>h1. Heading 1</h1>
+ <p>Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
+ <h2>h2. Heading 2</h2>
+ <p>Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
+ <h3>h3. Heading 3</h3>
+ <p>Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
+ <h4>h4. Heading 4</h4>
+ <p>Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
+ <h5>h5. Heading 5</h5>
+ <p>Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
+ <h6>h6. Heading 6</h6>
+ <p>Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
+ </div>
+ </div>
+</div>
+
+
+
+<!-- Responsive images
+================================================== -->
+
+<div class="page-header">
+ <h1>Responsive images</h1>
+</div>
+
+<div class="row">
+ <div class="span4">
+ <img src="http://placehold.it/600x600" height="200">
+ </div>
+ <div class="span4">
+ <img src="http://placehold.it/600x600">
+ </div>
+ <div class="span4">
+ <img src="http://placehold.it/600x600">
+ </div>
+</div>
+
+<br>
+
+<div class="row">
+ <div class="span4">
+ <img src="http://placehold.it/600x900" style="width: 200px;">
+ </div>
+ <div class="span4">
+ <img src="http://placehold.it/200x300">
+ </div>
+ <div class="span4">
+ <img src="http://placehold.it/600x600">
+ </div>
+</div>
+
+<br><br>
+
+
+
+
+<!-- Fluid grid
+================================================== -->
+
+<div class="page-header">
+ <h1>Fluid grids</h1>
+</div>
+
+<div class="fluid-grid">
+ <div class="row">
+ <div class="span12">12
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="span11">11
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ <div class="span1">1
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="span10">10
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ <div class="span2">2
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="span9">9
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ <div class="span3">3
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="span8">8
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ <div class="span4">4
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="span7">7
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ <div class="span5">5
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="span6">6
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ <div class="span6">6
+ <div class="row-fluid">
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ <div class="span1">1</div>
+ </div>
+ </div>
+ </div>
+</div> <!-- fluid grids -->
+
+
+
+<!-- Tables
+================================================== -->
+
+<div class="page-header">
+ <h1>Tables</h1>
+</div>
+
+<div class="row">
+ <div class="span6">
+ <h4>Bordered without thead</h4>
+ <table class="table table-bordered">
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ </tbody>
+ </table>
+ <h4>Bordered without thead, with caption</h4>
+ <table class="table table-bordered">
+ <caption>Table caption</caption>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ </tbody>
+ </table>
+ <h4>Bordered without thead, with colgroup</h4>
+ <table class="table table-bordered">
+ <colgroup>
+ <col class="col1">
+ <col class="col2">
+ <col class="col3">
+ </colgroup>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td>3</td>
+ <td>6</td>
+ <td>9</td>
+ </tr>
+ </tfoot>
+ </table>
+ <h4>Bordered with thead, with colgroup</h4>
+ <table class="table table-bordered">
+ <colgroup>
+ <col class="col1">
+ <col class="col2">
+ <col class="col3">
+ </colgroup>
+ <thead>
+ <tr>
+ <th>A</th>
+ <th>B</th>
+ <th>C</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td>3</td>
+ <td>6</td>
+ <td>9</td>
+ </tr>
+ </tfoot>
+ </table>
+ </div><!--/span-->
+ <div class="span6">
+ <h4>Bordered with thead and caption</h4>
+ <table class="table table-bordered">
+ <caption>Table caption</caption>
+ <thead>
+ <tr>
+ <th>1</th>
+ <th>2</th>
+ <th>3</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td>2</td>
+ <td>3</td>
+ </tr>
+ </tbody>
+ <tfoot>
+ <tr>
+ <td>3</td>
+ <td>6</td>
+ <td>9</td>
+ </tr>
+ </tfoot>
+ </table>
+ <h4>Bordered with rowspan and colspan</h4>
+ <table class="table table-bordered">
+ <thead>
+ <tr>
+ <th>1</th>
+ <th>2</th>
+ <th>3</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td colspan="2">1 and 2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td rowspan="2">2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="2">1</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td colspan="2">2 and 3</td>
+ </tr>
+ </tbody>
+ </table>
+ </div><!--/span-->
+</div><!--/row-->
+
+
+<h4>Grid sizing</h4>
+<div class="row">
+ <div class="span12">
+ <table class="table table-bordered">
+ <thead>
+ <tr>
+ <th class="span3">1</th>
+ <th class="span4">2</th>
+ <th>3</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td colspan="2">1 and 2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td rowspan="2">2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="2">1</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td colspan="2">2 and 3</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div><!--/row-->
+
+<h4>Fluid grid sizing</h4>
+<div class="row-fluid">
+ <div class="span12">
+ <table class="table table-bordered">
+ <thead>
+ <tr>
+ <th class="span3">1</th>
+ <th class="span4">2</th>
+ <th>3</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td colspan="2">1 and 2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td>1</td>
+ <td rowspan="2">2</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td rowspan="2">1</td>
+ <td>3</td>
+ </tr>
+ <tr>
+ <td colspan="2">2 and 3</td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+</div><!--/row-->
+
+
+
+<!-- Forms
+================================================== -->
+
+<div class="page-header">
+ <h1>Forms</h1>
+</div>
+
+<h4>Buttons and button groups</h4>
+<form class="form-inline">
+ <button class="btn btn-success">Save</button>
+ <button class="btn btn-info">Add new</button>
+ <div class="btn-group">
+ <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
+ <i class="icon-user"></i> User
+ <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a href="#">Profile</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Sign Out</a></li>
+ </ul>
+ </div>
+</form>
+
+<h4>Horizontal form errors</h4>
+<form class="form-horizontal">
+ <div class="control-group error">
+ <label class="control-label" for="inputError">Radio with error</label>
+ <div class="controls">
+ <label class="radio">
+ <input type="radio" id="inputError"> Oh hai
+ </label>
+ <span class="help-inline">Please correct the error</span>
+ </div>
+ </div>
+</form>
+
+<div class="row">
+ <div class="span4">
+ <h4>Prepend and append on inputs</h4>
+ <form>
+ <div class="controls">
+ <div class="input-prepend">
+ <span class="add-on">@</span>
+ <input class="span2" id="prependedInput" size="16" type="text">
+ </div>
+ </div>
+ <div class="controls">
+ <div class="input-append">
+ <input class="span2" id="prependedInput" size="16" type="text">
+ <span class="add-on">@</span>
+ </div>
+ </div>
+ <div class="controls">
+ <div class="input-prepend input-append">
+ <span class="add-on">$</span>
+ <input class="span2" id="prependedInput" size="16" type="text">
+ <span class="add-on">.00</span>
+ </div>
+ </div>
+ </form>
+ </div><!--/span-->
+ <div class="span4">
+ <h4>Prepend and append with uneditable</h4>
+ <form>
+ <div class="input-prepend">
+ <span class="add-on">$</span>
+ <span class="span2 uneditable-input">Some value here</span>
+ </div>
+ <div class="input-append">
+ <span class="span2 uneditable-input">Some value here</span>
+ <span class="add-on">.00</span>
+ </div>
+ <div class="input-prepend input-append">
+ <span class="add-on">$</span>
+ <span class="span2 uneditable-input">Some value here</span>
+ <span class="add-on">.00</span>
+ </div>
+ </form>
+ </div><!--/span-->
+ <div class="span4">
+ <h4>Prepend with type="submit"</h4>
+ <form class="form-search">
+ <div class="input-append">
+ <input type="text" class="span2 search-query" value="" name="q">
+ <input type="submit" value="Search" class="btn">
+ </div>
+ </form>
+ <div class="input-append">
+ <input type="text" class="span2" value="" name="">
+ <input type="submit" value="Button" class="btn">
+ </div>
+ <div class="input-append">
+ <input type="text" size="16" id="appendedInputButtons" class="span2">
+ <input type="submit" value="Search" class="btn">
+ <button type="button" class="btn">Options</button>
+ </div>
+ </div><!--/span-->
+</div><!--/row-->
+
+<h4>Fluid prepended and appended inputs</h4>
+<div class="row-fluid">
+ <div class="span6">
+ <form>
+ <div class="controls">
+ <div class="input-prepend">
+ <span class="add-on">@</span><input class="span2" id="prependedInput" size="16" type="text">
+ </div>
+ </div>
+ <div class="controls">
+ <div class="input-append">
+ <input class="span2" id="prependedInput" size="16" type="text"><span class="add-on">@</span>
+ </div>
+ </div>
+ <div class="controls">
+ <div class="input-prepend input-append">
+ <span class="add-on">$</span><input class="span2" id="prependedInput" size="16" type="text"><span class="add-on">.00</span>
+ </div>
+ </div>
+ </form>
+ </div>
+</div>
+
+<h4>Fixed row with inputs</h4>
+<p>Inputs should not extend past the light red background, set on their parent, a <code>.span*</code> column.</p>
+
+<div class="rowInputs">
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span1" placeholder="span1">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span2" placeholder="span2">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span3" placeholder="span3">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span4" placeholder="span4">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span5" placeholder="span5">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span6" placeholder="span6">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span7" placeholder="span7">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span8" placeholder="span8">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span9" placeholder="span9">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span10" placeholder="span10">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span11" placeholder="span11">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row">
+ <div class="span12">
+ <input type="text" class="span12" placeholder="span12">
+ </div><!--/span-->
+ </div><!--/row-->
+</div>
+<br>
+
+<h4>Fluid row with inputs</h4>
+<p>Inputs should not extend past the light red background, set on their parent, a <code>.span*</code> column.</p>
+<div id="fluidRowInputs">
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span1" placeholder="span1">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span2" placeholder="span2">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span3" placeholder="span3">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span4" placeholder="span4">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span5" placeholder="span5">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span6" placeholder="span6">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span7" placeholder="span7">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span8" placeholder="span8">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span9" placeholder="span9">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span10" placeholder="span10">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span11" placeholder="span11">
+ </div><!--/span-->
+ </div><!--/row-->
+ <div class="row-fluid">
+ <div class="span12">
+ <input type="text" class="span12" placeholder="span12">
+ </div><!--/span-->
+ </div><!--/row-->
+</div>
+
+<br>
+
+<h4>Inline form in fluid row</h4>
+
+<div class="row-fluid">
+ <div class="span12">
+ <form class="form-inline">
+ <input type="text" class="span3" placeholder="Email">
+ <input type="password" class="span3" placeholder="Password">
+ <label class="checkbox">
+ <input type="checkbox"> Remember me
+ </label>
+ <button type="submit" class="btn">Sign in</button>
+ </form>
+ </div>
+</div>
+
+
+<br>
+
+
+<h4>Fluid textarea at .span12</h4>
+<div class="row-fluid">
+ <div class="span12">
+ <textarea class="span12"></textarea>
+ </div>
+</div>
+
+
+<br>
+
+
+<h4>Selects</h4>
+<form>
+ <select class="span4">
+ <option>Option</option>
+ </select>
+</form>
+
+
+<br>
+
+
+
+
+<!-- Dropdowns
+================================================== -->
+
+<div class="page-header">
+ <h1>Dropdowns</h1>
+</div>
+
+<h4>Dropdown link with hash URL</h4>
+<ul class="nav nav-pills">
+ <li class="active"><a href="#">Link</a></li>
+ <li><a href="#">Example link</a></li>
+ <li class="dropdown">
+ <a class="dropdown-toggle" data-toggle="dropdown" href="#">
+ Dropdown <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Separated link</a></li>
+ </ul>
+ </li>
+</ul>
+
+<h4>Dropdown link with custom URL and data-target</h4>
+<ul class="nav nav-pills">
+ <li class="active"><a href="#">Link</a></li>
+ <li><a href="#">Example link</a></li>
+ <li class="dropdown">
+ <a class="dropdown-toggle" data-toggle="dropdown" data-target="#" href="path/to/page.html">
+ Dropdown <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Separated link</a></li>
+ </ul>
+ </li>
+</ul>
+
+<h4>Dropdown on a button</h4>
+<div style="position: relative;">
+ <button class="btn" type="button" data-toggle="dropdown">Dropdown <span class="caret"></span></button>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li><a href="#">Separated link</a></li>
+ </ul>
+</div>
+
+<br>
+
+
+<!-- Thumbnails
+================================================== -->
+
+<div class="page-header">
+ <h1>Thumbnails</h1>
+</div>
+
+<h4>Default thumbnails (no grid sizing)</h4>
+<ul class="thumbnails">
+ <li class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </li>
+ <li class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </li>
+ <li class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </li>
+ <li class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </li>
+</ul>
+
+<!-- NOT CURRENTLY SUPPORTED
+<h4>Offset thumbnails</h4>
+<ul class="thumbnails">
+ <li class="span3 offset3">
+ <a href="#" class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </a>
+ </li>
+ <li class="span3">
+ <a href="#" class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </a>
+ </li>
+ <li class="span3">
+ <a href="#" class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </a>
+ </li>
+</ul>
+-->
+
+<h4>Standard grid sizing</h4>
+<ul class="thumbnails">
+ <li class="span3">
+ <a href="#" class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </a>
+ </li>
+ <li class="span3 offset3">
+ <a href="#" class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </a>
+ </li>
+ <li class="span3">
+ <a href="#" class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </a>
+ </li>
+</ul>
+
+<h4>Fluid thumbnails</h4>
+<div class="row-fluid">
+ <div class="span8">
+ <ul class="thumbnails">
+ <li class="span4">
+ <a href="#" class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </a>
+ </li>
+ <li class="span4">
+ <a href="#" class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </a>
+ </li>
+ <li class="span4">
+ <a href="#" class="thumbnail">
+ <img src="http://placehold.it/260x180" alt="">
+ </a>
+ </li>
+ </ul>
+ </div>
+</div>
+
+
+
+<!-- Tabs
+================================================== -->
+
+<div class="page-header">
+ <h1>Tabs</h1>
+</div>
+
+<div class="tabbable tabs-left" style="margin-bottom: 18px;">
+ <ul class="nav nav-tabs">
+ <li class="active"><a href="#tab1" data-toggle="tab">Section 1</a></li>
+ <li><a href="#tab2" data-toggle="tab">Section 2</a></li>
+ <li><a href="#tab3" data-toggle="tab">Section 3</a></li>
+ </ul>
+ <div class="tab-content" style="padding-bottom: 9px; border-bottom: 1px solid #ddd;">
+ <div class="tab-pane active" id="tab1">
+ <p>I'm in Section 1.</p>
+
+ <div class="tabbable" style="background: #f5f5f5; padding: 20px;">
+ <ul class="nav nav-tabs">
+ <li class="active"><a href="#tab1-1" data-toggle="tab">1.1</a></li>
+ <li><a href="#tab1-2" data-toggle="tab">1.2</a></li>
+ <li><a href="#tab1-3" data-toggle="tab">1.3</a></li>
+ </ul>
+ <div class="tab-content" style="padding-bottom: 9px; border-bottom: 1px solid #ddd;">
+ <div class="tab-pane active" id="tab1-1">
+ <p>I'm in Section 1.1.</p>
+ </div>
+ <div class="tab-pane" id="tab1-2">
+ <p>I'm in Section 1.2.</p>
+ </div>
+ <div class="tab-pane" id="tab1-3">
+ <p>I'm in Section 1.3.</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="tab-pane" id="tab2">
+ <p>Howdy, I'm in Section 2.</p>
+ </div>
+ <div class="tab-pane" id="tab3">
+ <p>What up girl, this is Section 3.</p>
+ </div>
+ </div>
+</div> <!-- /tabbable -->
+
+<br>
+
+
+<!-- Labels
+================================================== -->
+
+<div class="page-header">
+ <h1>Labels</h1>
+</div>
+
+<div class="row">
+ <div class="span4">
+ <h4>Inline label</h4>
+ <p>Cras justo odio, dapibus ac facilisis in, egestas eget quam. Maecenas sed diam <span class="label label-warning">Label name</span> eget risus varius blandit sit amet non magna. Fusce <code>.class-name</code> dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
+ </div><!--/span-->
+ <div class="span4">
+ <form class="form-horizontal">
+ <label>Example label</label>
+ <input type="text" placeholder="Input"> <span class="help-inline"><span class="label">Hey!</span> Read this.</span>
+ </form>
+ </div><!--/span-->
+ <div class="span4">
+ <button class="btn">Action <span class="badge">2</span></button>
+ <button class="btn">Action <span class="label">2</span></button>
+ </div><!--/span-->
+</div><!--/row-->
+
+<br>
+
+
+<!-- Button groups
+================================================== -->
+
+<div class="page-header">
+ <h1>Buttons</h1>
+</div>
+
+<table class="table table-bordered">
+ <tbody>
+ <tr>
+ <td>
+ Maecenas faucibus mollis interdum. Nulla vitae elit libero, a pharetra augue. Donec ullamcorper nulla non metus auctor fringilla. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.
+ </td>
+ <td>
+ <div class="btn-group">
+ <button class="btn">1</button>
+ <button class="btn">2</button>
+ <button class="btn">3</button>
+ <button class="btn">4</button>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<h4>Mini buttons: text and icon</h4>
+<div class="btn-group">
+ <button class="btn btn-mini">Button text</button>
+ <button class="btn btn-mini"><i class="icon-cog"></i></button>
+</div>
+
+<br>
+
+
+
+<!-- Responsive utility classes
+================================================== -->
+
+<div class="page-header">
+ <h1>Responsive utility classes</h1>
+</div>
+
+<h4>Visible on...</h4>
+<ul class="responsive-utilities-test visible-on">
+ <li>Phone<span class="visible-phone">✔ Phone</span></li>
+ <li>Tablet<span class="visible-tablet">✔ Tablet</span></li>
+ <li>Desktop<span class="visible-desktop">✔ Desktop</span></li>
+</ul>
+<ul class="responsive-utilities-test visible-on">
+ <li>Phone + Tablet<span class="visible-phone visible-tablet">✔ Phone + Tablet</span></li>
+ <li>Tablet + Desktop<span class="visible-tablet visible-desktop">✔ Tablet + Desktop</span></li>
+ <li>All<span class="visible-phone visible-tablet visible-desktop">✔ All</span></li>
+</ul>
+
+<h4>Hidden on...</h4>
+<ul class="responsive-utilities-test hidden-on">
+ <li>Phone<span class="hidden-phone">✔ Phone</span></li>
+ <li>Tablet<span class="hidden-tablet">✔ Tablet</span></li>
+ <li>Desktop<span class="hidden-desktop">✔ Desktop</span></li>
+</ul>
+<ul class="responsive-utilities-test hidden-on">
+ <li>Phone + Tablet<span class="hidden-phone hidden-tablet">✔ Phone + Tablet</span></li>
+ <li>Tablet + Desktop<span class="hidden-tablet hidden-desktop">✔ Tablet + Desktop</span></li>
+ <li>All<span class="hidden-phone hidden-tablet hidden-desktop">✔ All</span></li>
+</ul>
+
+
+
+<!-- Gradients
+================================================== -->
+
+<div class="page-header">
+ <h1>Gradients</h1>
+</div>
+
+<h4>Horizontal</h4>
+<div class="gradient-horizontal"></div>
+
+<h4>Vertical</h4>
+<div class="gradient-vertical"></div>
+
+<h4>Directional</h4>
+<div class="gradient-directional"></div>
+
+<h4>Three colors</h4>
+<div class="gradient-vertical-three"></div>
+
+<h4>Radial</h4>
+<div class="gradient-radial"></div>
+
+<h4>Striped</h4>
+<div class="gradient-striped"></div>
+
+
+
+
+
+
+
+ </div><!-- /container -->
+
+
+
+ <!-- Footer
+ ================================================== -->
+ <footer class="footer">
+ <div class="container">
+ <p class="pull-right"><a href="#">Back to top</a></p>
+ <p>Designed and built with all the love in the world <a href="http://twitter.com/twitter" target="_blank">@twitter</a> by <a href="http://twitter.com/mdo" target="_blank">@mdo</a> and <a href="http://twitter.com/fat" target="_blank">@fat</a>.</p>
+ <p>Code licensed under the <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank">Apache License v2.0</a>. Documentation licensed under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>.</p>
+ <p>Icons from <a href="http://glyphicons.com">Glyphicons Free</a>, licensed under <a href="http://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>.</p>
+ <ul class="footer-links">
+ <li><a href="http://blog.getbootstrap.com">Read the blog</a></li>
+ <li><a href="https://github.com/twitter/bootstrap/issues?state=open">Submit issues</a></li>
+ <li><a href="https://github.com/twitter/bootstrap/wiki">Roadmap and changelog</a></li>
+ </ul>
+ </div>
+ </footer>
+
+</div>
+
+
+ <!-- Le javascript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
+ <script src="../../docs/assets/js/jquery.js"></script>
+ <script src="../../docs/assets/js/google-code-prettify/prettify.js"></script>
+ <script src="../../docs/assets/js/bootstrap-transition.js"></script>
+ <script src="../../docs/assets/js/bootstrap-alert.js"></script>
+ <script src="../../docs/assets/js/bootstrap-modal.js"></script>
+ <script src="../../docs/assets/js/bootstrap-dropdown.js"></script>
+ <script src="../../docs/assets/js/bootstrap-scrollspy.js"></script>
+ <script src="../../docs/assets/js/bootstrap-tab.js"></script>
+ <script src="../../docs/assets/js/bootstrap-tooltip.js"></script>
+ <script src="../../docs/assets/js/bootstrap-popover.js"></script>
+ <script src="../../docs/assets/js/bootstrap-button.js"></script>
+ <script src="../../docs/assets/js/bootstrap-collapse.js"></script>
+ <script src="../../docs/assets/js/bootstrap-carousel.js"></script>
+ <script src="../../docs/assets/js/bootstrap-typeahead.js"></script>
+ <script src="../../docs/assets/js/application.js"></script>
+
+
+ </body>
+</html>
diff --git a/src/fauxton/assets/less/bootstrap/tests/forms-responsive.html b/src/fauxton/assets/less/bootstrap/tests/forms-responsive.html
new file mode 100644
index 000000000..846d5b43d
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/tests/forms-responsive.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bootstrap, from Twitter</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le styles -->
+ <link href="../../docs/assets/css/bootstrap.css" rel="stylesheet">
+ <link href="../../docs/assets/css/bootstrap-responsive.css" rel="stylesheet">
+ <style>
+ body {
+ padding-top: 30px;
+ padding-bottom: 30px;
+ }
+ </style>
+
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+
+ <!-- Le fav and touch icons -->
+ <link rel="shortcut icon" href="../../docs/assets/ico/favicon.ico">
+ <link rel="apple-touch-icon-precomposed" sizes="144x144" href="../../docs/assets/ico/apple-touch-icon-144-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="114x114" href="../../docs/assets/ico/apple-touch-icon-114-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="72x72" href="../../docs/assets/ico/apple-touch-icon-72-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" href="../../docs/assets/ico/apple-touch-icon-57-precomposed.png">
+ </head>
+
+ <body>
+
+ <form class="container">
+
+ <div class="page-header">
+ <h1>Fixed grid</h1>
+ </div>
+
+ <h3>Vertical alignment</h3>
+ <input type="text" class="span2" placeholder="span2">
+ <select class="span2"><option>span2</option></select>
+ <span class="uneditable-input span2">span1</span>
+
+ <h3>Width across elements</h3>
+ <div>
+ <input type="text" class="span2" placeholder="span2">
+ </div>
+ <div>
+ <select class="span2"><option>span2</option></select>
+ </div>
+ <div>
+ <span class="uneditable-input span2">span2</span>
+ </div>
+
+
+ <div class="page-header">
+ <h1>Fluid grid</h1>
+ </div>
+
+ <div class="row-fluid">
+ <input type="text" class="span2" placeholder="span2">
+ <select class="span2"><option>span2</option></select>
+ <span class="uneditable-input span2">span1</span>
+ </div>
+
+ </form> <!-- /container -->
+
+ </body>
+</html>
diff --git a/src/fauxton/assets/less/bootstrap/tests/forms.html b/src/fauxton/assets/less/bootstrap/tests/forms.html
new file mode 100644
index 000000000..a63d728a0
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/tests/forms.html
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bootstrap, from Twitter</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le styles -->
+ <link href="../../docs/assets/css/bootstrap.css" rel="stylesheet">
+ <link href="../../docs/assets/css/bootstrap-responsive.css" rel="stylesheet">
+ <style>
+ body {
+ padding-top: 30px;
+ padding-bottom: 30px;
+ }
+ </style>
+
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+
+ <!-- Le fav and touch icons -->
+ <link rel="shortcut icon" href="../../docs/assets/ico/favicon.ico">
+ <link rel="apple-touch-icon-precomposed" sizes="144x144" href="../../docs/assets/ico/apple-touch-icon-144-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="114x114" href="../../docs/assets/ico/apple-touch-icon-114-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="72x72" href="../../docs/assets/ico/apple-touch-icon-72-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" href="../../docs/assets/ico/apple-touch-icon-57-precomposed.png">
+ </head>
+
+ <body>
+
+ <form class="container">
+
+ <div class="page-header">
+ <h1>Form controls</h1>
+ </div>
+
+ <div class="row">
+ <div class="span4">
+
+ <label>Select</label>
+ <select>
+ <option>Select</option>
+ <option>Option 2</option>
+ <option>Option 3</option>
+ </select>
+
+ <hr>
+
+ <label>textarea</label>
+ <textarea>Textarea</textarea>
+
+ <hr>
+
+ <label>text</label>
+ <input type="text" value="Text input">
+
+ <hr>
+
+ <label>password</label>
+ <input type="password" value="Password input">
+
+ <hr>
+
+ <label>checkbox</label>
+ <input type="checkbox" value="">
+
+ <hr>
+
+ <label>radio</label>
+ <input type="radio" value="">
+
+ <hr>
+
+ <label>button</label>
+ <input type="button" value="Button">
+
+ <hr>
+
+ <label>submit</label>
+ <input type="submit" value="Submit">
+
+ <hr>
+
+ <label>reset</label>
+ <input type="reset" value="Reset">
+
+ </div><!-- /span4 -->
+ <div class="span4">
+
+ <label>file</label>
+ <input type="file" value="">
+
+ <hr>
+
+ <label>hidden</label>
+ <input type="hidden" value="hidden">
+
+ <hr>
+
+ <label>image</label>
+ <input type="image" value="">
+
+ <hr>
+
+ <label>datetime</label>
+ <input type="datetime" value="">
+
+ <hr>
+
+ <label>datetime-local</label>
+ <input type="datetime-local" value="">
+
+ <hr>
+
+ <label>date</label>
+ <input type="date" value="">
+
+ <hr>
+
+ <label>month</label>
+ <input type="month" value="">
+
+ <hr>
+
+ <label>time</label>
+ <input type="time" value="">
+
+ <hr>
+
+ <label>week</label>
+ <input type="week" value="">
+
+ </div><!-- /span4 -->
+ <div class="span4">
+
+ <label>number</label>
+ <input type="number" value="">
+
+ <hr>
+
+ <label>range</label>
+ <input type="range" value="">
+
+ <hr>
+
+ <label>email</label>
+ <input type="email" value="">
+
+ <hr>
+
+ <label>url</label>
+ <input type="url" value="">
+
+ <hr>
+
+ <label>search</label>
+ <input type="search" value="">
+
+ <hr>
+
+ <label>tel</label>
+ <input type="tel" value="">
+
+ <hr>
+
+ <label>color</label>
+ <input type="color" value="">
+
+ </div><!-- /span4 -->
+ </div><!-- /row -->
+
+ </form> <!-- /container -->
+
+ </body>
+</html>
diff --git a/src/fauxton/assets/less/bootstrap/tests/navbar-fixed-top.html b/src/fauxton/assets/less/bootstrap/tests/navbar-fixed-top.html
new file mode 100644
index 000000000..97b86fdef
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/tests/navbar-fixed-top.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bootstrap, from Twitter</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le styles -->
+ <link href="../../docs/assets/css/bootstrap.css" rel="stylesheet">
+ <style>
+ body {
+ padding-top: 60px;
+ padding-bottom: 30px;
+ }
+ </style>
+ <link href="../../docs/assets/css/bootstrap-responsive.css" rel="stylesheet">
+
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+
+ <!-- Le fav and touch icons -->
+ <link rel="shortcut icon" href="../../docs/assets/ico/favicon.ico">
+ <link rel="apple-touch-icon-precomposed" sizes="144x144" href="../../docs/assets/ico/apple-touch-icon-144-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="114x114" href="../../docs/assets/ico/apple-touch-icon-114-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="72x72" href="../../docs/assets/ico/apple-touch-icon-72-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" href="../../docs/assets/ico/apple-touch-icon-57-precomposed.png">
+ </head>
+
+ <body>
+
+ <!-- Fixed navbar -->
+ <div class="navbar navbar-fixed-top">
+ <div class="navbar-inner">
+ <div class="container">
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <a class="brand" href="#">Project name</a>
+ <div class="nav-collapse collapse">
+ <ul class="nav">
+ <li class="active"><a href="#">Home</a></li>
+ <li><a href="#about">About</a></li>
+ <li><a href="#contact">Contact</a></li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li class="nav-header">Nav header</li>
+ <li><a href="#">Separated link</a></li>
+ <li><a href="#">One more separated link</a></li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="nav pull-right">
+ <li><a href="./navbar.html">Default</a></li>
+ <li><a href="./navbar-static-top.html">Static top</a></li>
+ <li class="active"><a href="./navbar-fixed-top.html">Fixed top</a></li>
+ </ul>
+ </div><!--/.nav-collapse -->
+ </div>
+ </div>
+ </div>
+
+ <div class="container">
+
+ <!-- Main hero unit for a primary marketing message or call to action -->
+ <div class="hero-unit">
+ <h1>Navbar example</h1>
+ <p>This example is a quick exercise to illustrate how the default, static navbar and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.</p>
+ <p>
+ <a class="btn btn-large btn-primary" href="../components.html#navbar">View navbar docs &raquo;</a>
+ </p>
+ </div>
+
+ </div> <!-- /container -->
+
+ <!-- Le javascript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="../../docs/assets/js/jquery.js"></script>
+ <script src="../../docs/assets/js/bootstrap-transition.js"></script>
+ <script src="../../docs/assets/js/bootstrap-alert.js"></script>
+ <script src="../../docs/assets/js/bootstrap-modal.js"></script>
+ <script src="../../docs/assets/js/bootstrap-dropdown.js"></script>
+ <script src="../../docs/assets/js/bootstrap-scrollspy.js"></script>
+ <script src="../../docs/assets/js/bootstrap-tab.js"></script>
+ <script src="../../docs/assets/js/bootstrap-tooltip.js"></script>
+ <script src="../../docs/assets/js/bootstrap-popover.js"></script>
+ <script src="../../docs/assets/js/bootstrap-button.js"></script>
+ <script src="../../docs/assets/js/bootstrap-collapse.js"></script>
+ <script src="../../docs/assets/js/bootstrap-carousel.js"></script>
+ <script src="../../docs/assets/js/bootstrap-typeahead.js"></script>
+
+ </body>
+</html>
diff --git a/src/fauxton/assets/less/bootstrap/tests/navbar-static-top.html b/src/fauxton/assets/less/bootstrap/tests/navbar-static-top.html
new file mode 100644
index 000000000..505ecb608
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/tests/navbar-static-top.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bootstrap, from Twitter</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le styles -->
+ <link href="../../docs/assets/css/bootstrap.css" rel="stylesheet">
+ <style>
+ body {
+ padding-bottom: 30px;
+ }
+ .hero-unit {
+ margin-top: 20px;
+ }
+ </style>
+ <link href="../../docs/assets/css/bootstrap-responsive.css" rel="stylesheet">
+
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+
+ <!-- Le fav and touch icons -->
+ <link rel="shortcut icon" href="../../docs/assets/ico/favicon.ico">
+ <link rel="apple-touch-icon-precomposed" sizes="144x144" href="../../docs/assets/ico/apple-touch-icon-144-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="114x114" href="../../docs/assets/ico/apple-touch-icon-114-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="72x72" href="../../docs/assets/ico/apple-touch-icon-72-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" href="../../docs/assets/ico/apple-touch-icon-57-precomposed.png">
+ </head>
+
+ <body>
+
+ <!-- Static navbar -->
+ <div class="navbar navbar-static-top">
+ <div class="navbar-inner">
+ <div class="container">
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <a class="brand" href="#">Project name</a>
+ <div class="nav-collapse collapse">
+ <ul class="nav">
+ <li class="active"><a href="#">Home</a></li>
+ <li><a href="#about">About</a></li>
+ <li><a href="#contact">Contact</a></li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li class="nav-header">Nav header</li>
+ <li><a href="#">Separated link</a></li>
+ <li><a href="#">One more separated link</a></li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="nav pull-right">
+ <li><a href="./navbar.html">Default</a></li>
+ <li class="active"><a href="./navbar-static-top.html">Static top</a></li>
+ <li><a href="./navbar-fixed-top.html">Fixed top</a></li>
+ </ul>
+ </div><!--/.nav-collapse -->
+ </div>
+ </div>
+ </div>
+
+
+ <div class="container">
+
+ <!-- Main hero unit for a primary marketing message or call to action -->
+ <div class="hero-unit">
+ <h1>Navbar example</h1>
+ <p>This example is a quick exercise to illustrate how the default, static navbar and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.</p>
+ <p>
+ <a class="btn btn-large btn-primary" href="../components.html#navbar">View navbar docs &raquo;</a>
+ </p>
+ </div>
+
+ </div> <!-- /container -->
+
+ <!-- Le javascript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="../../docs/assets/js/jquery.js"></script>
+ <script src="../../docs/assets/js/bootstrap-transition.js"></script>
+ <script src="../../docs/assets/js/bootstrap-alert.js"></script>
+ <script src="../../docs/assets/js/bootstrap-modal.js"></script>
+ <script src="../../docs/assets/js/bootstrap-dropdown.js"></script>
+ <script src="../../docs/assets/js/bootstrap-scrollspy.js"></script>
+ <script src="../../docs/assets/js/bootstrap-tab.js"></script>
+ <script src="../../docs/assets/js/bootstrap-tooltip.js"></script>
+ <script src="../../docs/assets/js/bootstrap-popover.js"></script>
+ <script src="../../docs/assets/js/bootstrap-button.js"></script>
+ <script src="../../docs/assets/js/bootstrap-collapse.js"></script>
+ <script src="../../docs/assets/js/bootstrap-carousel.js"></script>
+ <script src="../../docs/assets/js/bootstrap-typeahead.js"></script>
+
+ </body>
+</html>
diff --git a/src/fauxton/assets/less/bootstrap/tests/navbar.html b/src/fauxton/assets/less/bootstrap/tests/navbar.html
new file mode 100644
index 000000000..c72da71a4
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/tests/navbar.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bootstrap, from Twitter</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <meta name="author" content="">
+
+ <!-- Le styles -->
+ <link href="../../docs/assets/css/bootstrap.css" rel="stylesheet">
+ <style>
+ body {
+ padding-top: 0;
+ padding-bottom: 30px;
+ }
+ .navbar {
+ margin-top: 20px;
+ }
+ </style>
+ <link href="../../docs/assets/css/bootstrap-responsive.css" rel="stylesheet">
+
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
+ <!--[if lt IE 9]>
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
+ <![endif]-->
+
+ <!-- Le fav and touch icons -->
+ <link rel="shortcut icon" href="../../docs/assets/ico/favicon.ico">
+ <link rel="apple-touch-icon-precomposed" sizes="144x144" href="../../docs/assets/ico/apple-touch-icon-144-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="114x114" href="../../docs/assets/ico/apple-touch-icon-114-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" sizes="72x72" href="../../docs/assets/ico/apple-touch-icon-72-precomposed.png">
+ <link rel="apple-touch-icon-precomposed" href="../../docs/assets/ico/apple-touch-icon-57-precomposed.png">
+ </head>
+
+ <body>
+
+ <div class="container">
+
+ <!-- Static navbar -->
+ <div class="navbar">
+ <div class="navbar-inner">
+ <div class="container">
+ <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </a>
+ <a class="brand" href="#">Project name</a>
+ <div class="nav-collapse collapse">
+ <ul class="nav">
+ <li class="active"><a href="#">Home</a></li>
+ <li><a href="#about">About</a></li>
+ <li><a href="#contact">Contact</a></li>
+ <li class="dropdown">
+ <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a>
+ <ul class="dropdown-menu">
+ <li><a href="#">Action</a></li>
+ <li><a href="#">Another action</a></li>
+ <li><a href="#">Something else here</a></li>
+ <li class="divider"></li>
+ <li class="nav-header">Nav header</li>
+ <li><a href="#">Separated link</a></li>
+ <li><a href="#">One more separated link</a></li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="nav pull-right">
+ <li class="active"><a href="./navbar.html">Default</a></li>
+ <li><a href="./navbar-static-top.html">Static top</a></li>
+ <li><a href="./navbar-fixed-top.html">Fixed top</a></li>
+ </ul>
+ </div><!--/.nav-collapse -->
+ </div>
+ </div>
+ </div>
+
+ <!-- Main hero unit for a primary marketing message or call to action -->
+ <div class="hero-unit">
+ <h1>Navbar example</h1>
+ <p>This example is a quick exercise to illustrate how the default, static navbar and fixed to top navbar work. It includes the responsive CSS and HTML, so it also adapts to your viewport and device.</p>
+ <p>
+ <a class="btn btn-large btn-primary" href="../components.html#navbar">View navbar docs &raquo;</a>
+ </p>
+ </div>
+
+ </div> <!-- /container -->
+
+ <!-- Le javascript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="../../docs/assets/js/jquery.js"></script>
+ <script src="../../docs/assets/js/bootstrap-transition.js"></script>
+ <script src="../../docs/assets/js/bootstrap-alert.js"></script>
+ <script src="../../docs/assets/js/bootstrap-modal.js"></script>
+ <script src="../../docs/assets/js/bootstrap-dropdown.js"></script>
+ <script src="../../docs/assets/js/bootstrap-scrollspy.js"></script>
+ <script src="../../docs/assets/js/bootstrap-tab.js"></script>
+ <script src="../../docs/assets/js/bootstrap-tooltip.js"></script>
+ <script src="../../docs/assets/js/bootstrap-popover.js"></script>
+ <script src="../../docs/assets/js/bootstrap-button.js"></script>
+ <script src="../../docs/assets/js/bootstrap-collapse.js"></script>
+ <script src="../../docs/assets/js/bootstrap-carousel.js"></script>
+ <script src="../../docs/assets/js/bootstrap-typeahead.js"></script>
+
+ </body>
+</html>
diff --git a/src/fauxton/assets/less/bootstrap/thumbnails.less b/src/fauxton/assets/less/bootstrap/thumbnails.less
new file mode 100644
index 000000000..a84a7d37d
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/thumbnails.less
@@ -0,0 +1,52 @@
+//
+// Thumbnails
+// --------------------------------------------------
+
+
+// Note: `.thumbnails` and `.thumbnails > li` are overriden in responsive files
+
+// Make wrapper ul behave like the grid
+.thumbnails {
+ margin-left: -@gridGutterWidth;
+ list-style: none;
+ .clearfix();
+}
+// Fluid rows have no left margin
+.row-fluid .thumbnails {
+ margin-left: 0;
+}
+
+// Float li to make thumbnails appear in a row
+.thumbnails > li {
+ float: left; // Explicity set the float since we don't require .span* classes
+ margin-bottom: @baseLineHeight;
+ margin-left: @gridGutterWidth;
+}
+
+// The actual thumbnail (can be `a` or `div`)
+.thumbnail {
+ display: block;
+ padding: 4px;
+ line-height: @baseLineHeight;
+ border: 1px solid #ddd;
+ .border-radius(@baseBorderRadius);
+ .box-shadow(0 1px 3px rgba(0,0,0,.055));
+ .transition(all .2s ease-in-out);
+}
+// Add a hover state for linked versions only
+a.thumbnail:hover {
+ border-color: @linkColor;
+ .box-shadow(0 1px 4px rgba(0,105,214,.25));
+}
+
+// Images and captions
+.thumbnail > img {
+ display: block;
+ max-width: 100%;
+ margin-left: auto;
+ margin-right: auto;
+}
+.thumbnail .caption {
+ padding: 9px;
+ color: @gray;
+}
diff --git a/src/fauxton/assets/less/bootstrap/tooltip.less b/src/fauxton/assets/less/bootstrap/tooltip.less
new file mode 100644
index 000000000..93fac8d6b
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/tooltip.less
@@ -0,0 +1,70 @@
+//
+// Tooltips
+// --------------------------------------------------
+
+
+// Base class
+.tooltip {
+ position: absolute;
+ z-index: @zindexTooltip;
+ display: block;
+ visibility: visible;
+ padding: 5px;
+ font-size: 11px;
+ .opacity(0);
+ &.in { .opacity(80); }
+ &.top { margin-top: -3px; }
+ &.right { margin-left: 3px; }
+ &.bottom { margin-top: 3px; }
+ &.left { margin-left: -3px; }
+}
+
+// Wrapper for the tooltip content
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: @tooltipColor;
+ text-align: center;
+ text-decoration: none;
+ background-color: @tooltipBackground;
+ .border-radius(@baseBorderRadius);
+}
+
+// Arrows
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+.tooltip {
+ &.top .tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -@tooltipArrowWidth;
+ border-width: @tooltipArrowWidth @tooltipArrowWidth 0;
+ border-top-color: @tooltipArrowColor;
+ }
+ &.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -@tooltipArrowWidth;
+ border-width: @tooltipArrowWidth @tooltipArrowWidth @tooltipArrowWidth 0;
+ border-right-color: @tooltipArrowColor;
+ }
+ &.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -@tooltipArrowWidth;
+ border-width: @tooltipArrowWidth 0 @tooltipArrowWidth @tooltipArrowWidth;
+ border-left-color: @tooltipArrowColor;
+ }
+ &.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -@tooltipArrowWidth;
+ border-width: 0 @tooltipArrowWidth @tooltipArrowWidth;
+ border-bottom-color: @tooltipArrowColor;
+ }
+}
diff --git a/src/fauxton/assets/less/bootstrap/type.less b/src/fauxton/assets/less/bootstrap/type.less
new file mode 100644
index 000000000..3b428e79d
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/type.less
@@ -0,0 +1,227 @@
+//
+// Typography
+// --------------------------------------------------
+
+
+// Body text
+// -------------------------
+
+p {
+ margin: 0 0 @baseLineHeight / 2;
+}
+.lead {
+ margin-bottom: @baseLineHeight;
+ font-size: @baseFontSize * 1.5;
+ font-weight: 200;
+ line-height: @baseLineHeight * 1.5;
+}
+
+
+// Emphasis & misc
+// -------------------------
+
+small {
+ font-size: 85%; // Ex: 14px base font * 85% = about 12px
+}
+strong {
+ font-weight: bold;
+}
+em {
+ font-style: italic;
+}
+cite {
+ font-style: normal;
+}
+
+// Utility classes
+.muted {
+ color: @grayLight;
+}
+.text-warning { color: @warningText; }
+a.text-warning:hover { color: darken(@warningText, 10%); }
+
+.text-error { color: @errorText; }
+a.text-error:hover { color: darken(@errorText, 10%); }
+
+.text-info { color: @infoText; }
+a.text-info:hover { color: darken(@infoText, 10%); }
+
+.text-success { color: @successText; }
+a.text-success:hover { color: darken(@successText, 10%); }
+
+
+// Headings
+// -------------------------
+
+h1, h2, h3, h4, h5, h6 {
+ margin: (@baseLineHeight / 2) 0;
+ font-family: @headingsFontFamily;
+ font-weight: @headingsFontWeight;
+ line-height: @baseLineHeight;
+ color: @headingsColor;
+ text-rendering: optimizelegibility; // Fix the character spacing for headings
+ small {
+ font-weight: normal;
+ line-height: 1;
+ color: @grayLight;
+ }
+}
+
+h1,
+h2,
+h3 { line-height: @baseLineHeight * 2; }
+
+h1 { font-size: @baseFontSize * 2.75; } // ~38px
+h2 { font-size: @baseFontSize * 2.25; } // ~32px
+h3 { font-size: @baseFontSize * 1.75; } // ~24px
+h4 { font-size: @baseFontSize * 1.25; } // ~18px
+h5 { font-size: @baseFontSize; }
+h6 { font-size: @baseFontSize * 0.85; } // ~12px
+
+h1 small { font-size: @baseFontSize * 1.75; } // ~24px
+h2 small { font-size: @baseFontSize * 1.25; } // ~18px
+h3 small { font-size: @baseFontSize; }
+h4 small { font-size: @baseFontSize; }
+
+
+// Page header
+// -------------------------
+
+.page-header {
+ padding-bottom: (@baseLineHeight / 2) - 1;
+ margin: @baseLineHeight 0 (@baseLineHeight * 1.5);
+ border-bottom: 1px solid @grayLighter;
+}
+
+
+
+// Lists
+// --------------------------------------------------
+
+// Unordered and Ordered lists
+ul, ol {
+ padding: 0;
+ margin: 0 0 @baseLineHeight / 2 25px;
+}
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+ margin-bottom: 0;
+}
+li {
+ line-height: @baseLineHeight;
+}
+ul.unstyled,
+ol.unstyled {
+ margin-left: 0;
+ list-style: none;
+}
+
+// Description Lists
+dl {
+ margin-bottom: @baseLineHeight;
+}
+dt,
+dd {
+ line-height: @baseLineHeight;
+}
+dt {
+ font-weight: bold;
+}
+dd {
+ margin-left: @baseLineHeight / 2;
+}
+// Horizontal layout (like forms)
+.dl-horizontal {
+ .clearfix(); // Ensure dl clears floats if empty dd elements present
+ dt {
+ float: left;
+ width: @horizontalComponentOffset - 20;
+ clear: left;
+ text-align: right;
+ .text-overflow();
+ }
+ dd {
+ margin-left: @horizontalComponentOffset;
+ }
+}
+
+// MISC
+// ----
+
+// Horizontal rules
+hr {
+ margin: @baseLineHeight 0;
+ border: 0;
+ border-top: 1px solid @hrBorder;
+ border-bottom: 1px solid @white;
+}
+
+// Abbreviations and acronyms
+abbr[title],
+// Added data-* attribute to help out our tooltip plugin, per https://github.com/twitter/bootstrap/issues/5257
+abbr[data-original-title] {
+ cursor: help;
+ border-bottom: 1px dotted @grayLight;
+}
+abbr.initialism {
+ font-size: 90%;
+ text-transform: uppercase;
+}
+
+// Blockquotes
+blockquote {
+ padding: 0 0 0 15px;
+ margin: 0 0 @baseLineHeight;
+ border-left: 5px solid @grayLighter;
+ p {
+ margin-bottom: 0;
+ #font > .shorthand(16px,300,@baseLineHeight * 1.25);
+ }
+ small {
+ display: block;
+ line-height: @baseLineHeight;
+ color: @grayLight;
+ &:before {
+ content: '\2014 \00A0';
+ }
+ }
+
+ // Float right with text-align: right
+ &.pull-right {
+ float: right;
+ padding-right: 15px;
+ padding-left: 0;
+ border-right: 5px solid @grayLighter;
+ border-left: 0;
+ p,
+ small {
+ text-align: right;
+ }
+ small {
+ &:before {
+ content: '';
+ }
+ &:after {
+ content: '\00A0 \2014';
+ }
+ }
+ }
+}
+
+// Quotes
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+ content: "";
+}
+
+// Addresses
+address {
+ display: block;
+ margin-bottom: @baseLineHeight;
+ font-style: normal;
+ line-height: @baseLineHeight;
+}
diff --git a/src/fauxton/assets/less/bootstrap/utilities.less b/src/fauxton/assets/less/bootstrap/utilities.less
new file mode 100644
index 000000000..314b4ffdb
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/utilities.less
@@ -0,0 +1,30 @@
+//
+// Utility classes
+// --------------------------------------------------
+
+
+// Quick floats
+.pull-right {
+ float: right;
+}
+.pull-left {
+ float: left;
+}
+
+// Toggling content
+.hide {
+ display: none;
+}
+.show {
+ display: block;
+}
+
+// Visibility
+.invisible {
+ visibility: hidden;
+}
+
+// For Affix plugin
+.affix {
+ position: fixed;
+}
diff --git a/src/fauxton/assets/less/bootstrap/variables.less b/src/fauxton/assets/less/bootstrap/variables.less
new file mode 100644
index 000000000..3fb5274c3
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/variables.less
@@ -0,0 +1,301 @@
+//
+// Variables
+// --------------------------------------------------
+
+
+// Global values
+// --------------------------------------------------
+
+
+// Grays
+// -------------------------
+@black: #000;
+@grayDarker: #222;
+@grayDark: #333;
+@gray: #555;
+@grayLight: #999;
+@grayLighter: #eee;
+@white: #fff;
+
+
+// Accent colors
+// -------------------------
+@blue: #049cdb;
+@blueDark: #0064cd;
+@green: #46a546;
+@red: #9d261d;
+@yellow: #ffc40d;
+@orange: #f89406;
+@pink: #c3325f;
+@purple: #7a43b6;
+
+
+// Scaffolding
+// -------------------------
+@bodyBackground: @white;
+@textColor: @grayDark;
+
+
+// Links
+// -------------------------
+@linkColor: #08c;
+@linkColorHover: darken(@linkColor, 15%);
+
+
+// Typography
+// -------------------------
+@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif;
+@serifFontFamily: Georgia, "Times New Roman", Times, serif;
+@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace;
+
+@baseFontSize: 14px;
+@baseFontFamily: @sansFontFamily;
+@baseLineHeight: 20px;
+@altFontFamily: @serifFontFamily;
+
+@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily
+@headingsFontWeight: bold; // instead of browser default, bold
+@headingsColor: inherit; // empty to use BS default, @textColor
+
+
+// Component sizing
+// -------------------------
+// Based on 14px font-size and 20px line-height
+
+@fontSizeLarge: @baseFontSize * 1.25; // ~18px
+@fontSizeSmall: @baseFontSize * 0.85; // ~12px
+@fontSizeMini: @baseFontSize * 0.75; // ~11px
+
+@paddingLarge: 11px 19px; // 44px
+@paddingSmall: 2px 10px; // 26px
+@paddingMini: 1px 6px; // 24px
+
+@baseBorderRadius: 4px;
+@borderRadiusLarge: 6px;
+@borderRadiusSmall: 3px;
+
+
+// Tables
+// -------------------------
+@tableBackground: transparent; // overall background-color
+@tableBackgroundAccent: #f9f9f9; // for striping
+@tableBackgroundHover: #f5f5f5; // for hover
+@tableBorder: #ddd; // table and cell border
+
+// Buttons
+// -------------------------
+@btnBackground: @white;
+@btnBackgroundHighlight: darken(@white, 10%);
+@btnBorder: #bbb;
+
+@btnPrimaryBackground: @linkColor;
+@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 20%);
+
+@btnInfoBackground: #5bc0de;
+@btnInfoBackgroundHighlight: #2f96b4;
+
+@btnSuccessBackground: #62c462;
+@btnSuccessBackgroundHighlight: #51a351;
+
+@btnWarningBackground: lighten(@orange, 15%);
+@btnWarningBackgroundHighlight: @orange;
+
+@btnDangerBackground: #ee5f5b;
+@btnDangerBackgroundHighlight: #bd362f;
+
+@btnInverseBackground: #444;
+@btnInverseBackgroundHighlight: @grayDarker;
+
+
+// Forms
+// -------------------------
+@inputBackground: @white;
+@inputBorder: #ccc;
+@inputBorderRadius: @baseBorderRadius;
+@inputDisabledBackground: @grayLighter;
+@formActionsBackground: #f5f5f5;
+@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border
+
+
+// Dropdowns
+// -------------------------
+@dropdownBackground: @white;
+@dropdownBorder: rgba(0,0,0,.2);
+@dropdownDividerTop: #e5e5e5;
+@dropdownDividerBottom: @white;
+
+@dropdownLinkColor: @grayDark;
+@dropdownLinkColorHover: @white;
+@dropdownLinkColorActive: @dropdownLinkColor;
+
+@dropdownLinkBackgroundActive: @linkColor;
+@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive;
+
+
+
+// COMPONENT VARIABLES
+// --------------------------------------------------
+
+
+// Z-index master list
+// -------------------------
+// Used for a bird's eye view of components dependent on the z-axis
+// Try to avoid customizing these :)
+@zindexDropdown: 1000;
+@zindexPopover: 1010;
+@zindexTooltip: 1030;
+@zindexFixedNavbar: 1030;
+@zindexModalBackdrop: 1040;
+@zindexModal: 1050;
+
+
+// Sprite icons path
+// -------------------------
+@iconSpritePath: "../img/glyphicons-halflings.png";
+@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png";
+
+
+// Input placeholder text color
+// -------------------------
+@placeholderText: @grayLight;
+
+
+// Hr border color
+// -------------------------
+@hrBorder: @grayLighter;
+
+
+// Horizontal forms & lists
+// -------------------------
+@horizontalComponentOffset: 180px;
+
+
+// Wells
+// -------------------------
+@wellBackground: #f5f5f5;
+
+
+// Navbar
+// -------------------------
+@navbarCollapseWidth: 979px;
+@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1;
+
+@navbarHeight: 40px;
+@navbarBackgroundHighlight: #ffffff;
+@navbarBackground: darken(@navbarBackgroundHighlight, 5%);
+@navbarBorder: darken(@navbarBackground, 12%);
+
+@navbarText: #777;
+@navbarLinkColor: #777;
+@navbarLinkColorHover: @grayDark;
+@navbarLinkColorActive: @gray;
+@navbarLinkBackgroundHover: transparent;
+@navbarLinkBackgroundActive: darken(@navbarBackground, 5%);
+
+@navbarBrandColor: @navbarLinkColor;
+
+// Inverted navbar
+@navbarInverseBackground: #111111;
+@navbarInverseBackgroundHighlight: #222222;
+@navbarInverseBorder: #252525;
+
+@navbarInverseText: @grayLight;
+@navbarInverseLinkColor: @grayLight;
+@navbarInverseLinkColorHover: @white;
+@navbarInverseLinkColorActive: @navbarInverseLinkColorHover;
+@navbarInverseLinkBackgroundHover: transparent;
+@navbarInverseLinkBackgroundActive: @navbarInverseBackground;
+
+@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%);
+@navbarInverseSearchBackgroundFocus: @white;
+@navbarInverseSearchBorder: @navbarInverseBackground;
+@navbarInverseSearchPlaceholderColor: #ccc;
+
+@navbarInverseBrandColor: @navbarInverseLinkColor;
+
+
+// Pagination
+// -------------------------
+@paginationBackground: #fff;
+@paginationBorder: #ddd;
+@paginationActiveBackground: #f5f5f5;
+
+
+// Hero unit
+// -------------------------
+@heroUnitBackground: @grayLighter;
+@heroUnitHeadingColor: inherit;
+@heroUnitLeadColor: inherit;
+
+
+// Form states and alerts
+// -------------------------
+@warningText: #c09853;
+@warningBackground: #fcf8e3;
+@warningBorder: darken(spin(@warningBackground, -10), 3%);
+
+@errorText: #b94a48;
+@errorBackground: #f2dede;
+@errorBorder: darken(spin(@errorBackground, -10), 3%);
+
+@successText: #468847;
+@successBackground: #dff0d8;
+@successBorder: darken(spin(@successBackground, -10), 5%);
+
+@infoText: #3a87ad;
+@infoBackground: #d9edf7;
+@infoBorder: darken(spin(@infoBackground, -10), 7%);
+
+
+// Tooltips and popovers
+// -------------------------
+@tooltipColor: #fff;
+@tooltipBackground: #000;
+@tooltipArrowWidth: 5px;
+@tooltipArrowColor: @tooltipBackground;
+
+@popoverBackground: #fff;
+@popoverArrowWidth: 10px;
+@popoverArrowColor: #fff;
+@popoverTitleBackground: darken(@popoverBackground, 3%);
+
+// Special enhancement for popovers
+@popoverArrowOuterWidth: @popoverArrowWidth + 1;
+@popoverArrowOuterColor: rgba(0,0,0,.25);
+
+
+
+// GRID
+// --------------------------------------------------
+
+
+// Default 940px grid
+// -------------------------
+@gridColumns: 12;
+@gridColumnWidth: 60px;
+@gridGutterWidth: 20px;
+@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1));
+
+// 1200px min
+@gridColumnWidth1200: 70px;
+@gridGutterWidth1200: 30px;
+@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1));
+
+// 768px-979px
+@gridColumnWidth768: 42px;
+@gridGutterWidth768: 20px;
+@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1));
+
+
+// Fluid grid
+// -------------------------
+@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth);
+@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth);
+
+// 1200px min
+@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200);
+@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200);
+
+// 768px-979px
+@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768);
+@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768);
diff --git a/src/fauxton/assets/less/bootstrap/wells.less b/src/fauxton/assets/less/bootstrap/wells.less
new file mode 100644
index 000000000..84a744b1c
--- /dev/null
+++ b/src/fauxton/assets/less/bootstrap/wells.less
@@ -0,0 +1,29 @@
+//
+// Wells
+// --------------------------------------------------
+
+
+// Base class
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: @wellBackground;
+ border: 1px solid darken(@wellBackground, 7%);
+ .border-radius(@baseBorderRadius);
+ .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
+ blockquote {
+ border-color: #ddd;
+ border-color: rgba(0,0,0,.15);
+ }
+}
+
+// Sizes
+.well-large {
+ padding: 24px;
+ .border-radius(@borderRadiusLarge);
+}
+.well-small {
+ padding: 9px;
+ .border-radius(@borderRadiusSmall);
+}
diff --git a/src/fauxton/assets/less/config.less b/src/fauxton/assets/less/config.less
new file mode 100644
index 000000000..fe0379628
--- /dev/null
+++ b/src/fauxton/assets/less/config.less
@@ -0,0 +1,46 @@
+/* Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+.config-item {
+ height: 41px;
+
+ .edit-button {
+ float: right;
+ .btn;
+ .btn-mini;
+ display:none;
+ }
+
+ td:hover .edit-button {
+ display: block;
+ }
+
+ .value-input {
+ width: 98%;
+ }
+
+ #delete-value {
+ cursor: pointer;
+ }
+}
+
+.button-margin {
+ margin-bottom: 15px;
+}
+
+#add-section-modal {
+ width: 400px;
+}
+
+
+
diff --git a/src/fauxton/assets/less/couchdb.less b/src/fauxton/assets/less/couchdb.less
new file mode 100644
index 000000000..20da4861d
--- /dev/null
+++ b/src/fauxton/assets/less/couchdb.less
@@ -0,0 +1,72 @@
+/* Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/*!
+ *
+ * CouchDB less style files
+ *
+ */
+@import "bootstrap/bootstrap.less";
+
+//
+// Variables
+// --------------------------------------------------
+
+
+// Global values
+// --------------------------------------------------
+
+// Links
+@linkColor: #CA2530;
+@linkColorHover: darken(@linkColor, 15%);
+
+// Grays
+@black: #0C0C0C;
+@grayDark: #5A5A5A;
+@grayDarker: darken(@grayDark, 10%);
+@gray: #F80507;
+@grayLight: #E27B81;
+@grayLighter: #B95D40;
+@white: #FDFBFB;
+
+// Accent colors
+// -------------------------
+@blue: #049cdb;
+@blueDark: #0064cd;
+@green: #46a546;
+@red: #9d261d;
+@yellow: #ffc40d;
+@orange: #f89406;
+@pink: #c3325f;
+@purple: #7a43b6;
+
+
+// Scaffolding
+// -------------------------
+@bodyBackground: @white;
+@textColor: @grayDark;
+
+// Typography
+// -------------------------
+@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif;
+@serifFontFamily: Georgia, "Times New Roman", Times, serif;
+@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace;
+
+@baseFontSize: 14px;
+@baseFontFamily: @sansFontFamily;
+@baseLineHeight: 20px;
+@altFontFamily: @serifFontFamily;
+
+@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily
+@headingsFontWeight: bold; // instead of browser default, bold
+@headingsColor: inherit; // empty to use BS default, @textColor
diff --git a/src/fauxton/assets/less/database.less b/src/fauxton/assets/less/database.less
new file mode 100644
index 000000000..74a990f7a
--- /dev/null
+++ b/src/fauxton/assets/less/database.less
@@ -0,0 +1,163 @@
+/* Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/* =database
+ ---------------------------------------------------------------------- */
+#db-tools {
+ position: absolute;
+ top: -7px;
+ right: 0;
+ width: 390px;
+
+ .btn-group {
+ position: absolute;
+ left: 0;
+ top: 6px;
+ }
+
+ form {
+ position: absolute;
+ right: 0;
+ top: 0;
+ }
+}
+
+.tools .nav {
+ margin-bottom: 10px;
+}
+
+#sidenav {
+ padding-top: 10px;
+
+ h3 {
+ margin: 10px 0;
+ }
+
+ li a span.divider {
+ background: none;
+ color: #ccc;
+ padding: 0 2px;
+ }
+
+ li.nav-header a {
+ display: inline
+ }
+
+ div.btn-group {
+ display: inline-block;
+ }
+
+ li.nav-header, #sidenav li a {
+ padding-left: 4px;
+ }
+
+ li.active a {
+ background-color: #ddd;
+ color: #333;
+ text-shadow: none;
+ }
+
+ li.active a i {
+ background-image: url("../img/glyphicons-halflings.png");
+ }
+}
+
+.edit {
+ display: none;
+
+ form {
+ margin-bottom: 0;
+ }
+
+ h3 {
+ border-bottom: 1px solid #ccc;
+ font-size: 100%;
+ line-height: 1;
+ margin-bottom: 18px;
+ }
+
+ textarea {
+ height: 100px;
+ width: 95%;
+ }
+
+ .btn-toolbar {
+ margin-bottom: 0;
+ }
+
+ .preview {
+ width: 100px;
+ }
+
+ .save {
+ }
+}
+
+#new-view-index {
+ .confirm {
+ display: none;
+ }
+
+ .confirm .progress {
+ display: none;
+ margin: 20px;
+ }
+
+ textarea {
+ height: 100px;
+ width: 95%;
+ }
+}
+
+.view {
+ display: none;
+
+ .result-tools {
+ float: left;
+ width: 100%;
+ margin-bottom: 10px;
+ }
+
+ table td div {
+ position: relative;
+ }
+
+ table td div div {
+ display: none;
+ line-height: 1;
+ position: absolute;
+ right: 4px;
+ top: 4px;
+ }
+
+ table td div:hover div a.edits {
+ padding-left: 16px;
+ padding-right: 16px;
+ }
+
+ table td div:hover div {
+ display: block;
+ }
+
+}
+.view.show {
+ display: block;
+}
+.view.show.hidden-by-params {
+ display: none;
+}
+#database .view table tr td {
+ padding: 0;
+}
+
+.loading {display: none;}
diff --git a/src/fauxton/assets/less/fauxton.less b/src/fauxton/assets/less/fauxton.less
new file mode 100644
index 000000000..0bbde28a1
--- /dev/null
+++ b/src/fauxton/assets/less/fauxton.less
@@ -0,0 +1,95 @@
+/* Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/*!
+ *
+ * Fauxton less style files
+ *
+ */
+@import "bootstrap/bootstrap.less";
+@import "config.less";
+@import "logs.less";
+@import "prettyprint.less";
+@import "database.less";
+
+/*!
+ *
+ * OVERRIDES
+ *
+ * Override styles from included less files here. Record where the style is
+ * originally defined.
+ *
+ */
+
+//
+// Breadcrumbs
+
+.breadcrumb {
+ background-color: transparent;
+ padding-left: 0px;
+ li {
+ color: @grayLight;
+ background-color: transparent;
+ a {
+ color: @grayLight;
+ }
+ }
+ .divider {
+ padding: 0 5px;
+ color: @grayLight; //ccc
+ }
+ .active {
+ color: @black;
+ font-weight: bold;
+ }
+}
+
+
+// Navs
+// ------
+
+.nav-tabs > li > a {
+ background-color: @grayLighter;
+}
+
+.dropdown-menu li > a {
+ font-size: 12px;
+}
+
+.nav-list .divider {
+ color: @grayLight;
+ // This function is defined in mixins
+ .nav-divider(transparent, @white);
+}
+
+
+// Misc
+// ------
+
+pre.view-code-error {
+ color: red !important; // yuck
+}
+
+.CodeMirror-scroll {
+ height: auto;
+ overflow-y: visible;
+}
+
+#define-view form textarea.js-editor {
+ width: 95%;
+}
+
+#define-view .CodeMirror-scroll {
+ height: auto;
+ min-height: 50px;
+}
diff --git a/src/fauxton/assets/less/logs.less b/src/fauxton/assets/less/logs.less
new file mode 100644
index 000000000..e50f903f5
--- /dev/null
+++ b/src/fauxton/assets/less/logs.less
@@ -0,0 +1,24 @@
+/* Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+#log-sidebar {
+
+ ul {
+ margin-left: 0px;
+ list-style: none;
+ }
+
+ .remove-filter {
+ opacity: 0.2;
+ }
+}
diff --git a/src/fauxton/assets/less/prettyprint.less b/src/fauxton/assets/less/prettyprint.less
new file mode 100644
index 000000000..8ed512c0c
--- /dev/null
+++ b/src/fauxton/assets/less/prettyprint.less
@@ -0,0 +1,38 @@
+/* Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+pre.prettyprint {
+ background: #000;
+ border-radius: 0;
+ font-size: 83%;
+ line-height: 1.4;
+ margin: 0;
+ padding: 16px;
+}
+.prettyprint {
+ .pln, .pun, .typ {
+ color: #f8f8f8;
+ }
+ .kwd {
+ color: #ff8300;
+ }
+ .str {
+ color: #ff8300;
+ }
+ .lit {
+ color: #00ff00;
+ }
+ .com {
+ color: #666;
+ }
+} \ No newline at end of file
diff --git a/src/fauxton/bin/grunt b/src/fauxton/bin/grunt
new file mode 100755
index 000000000..2c5239327
--- /dev/null
+++ b/src/fauxton/bin/grunt
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+REL_PATH="`dirname \"$0\"`"
+GRUNT_PATH="$REL_PATH/../node_modules/.bin/grunt"
+
+$GRUNT_PATH $*
diff --git a/src/fauxton/couchapp.js b/src/fauxton/couchapp.js
new file mode 100644
index 000000000..63ea29703
--- /dev/null
+++ b/src/fauxton/couchapp.js
@@ -0,0 +1,27 @@
+var couchapp = require('couchapp'),
+ path = require('path'),
+ ddoc;
+
+ddoc = {
+ _id: '_design/fauxton',
+ rewrites: [
+ { "from": "_db" , "to" : "../.." },
+ { "from": "_db/*" , "to" : "../../*" },
+ { "from": "_ddoc" , "to" : "" },
+ { "from": "_ddoc/*", "to" : "*"},
+ {from: '/', to: 'index.html'},
+ {from: '/*', to: '*'}
+ ],
+ views: {},
+ shows: {},
+ lists: {},
+ validate_doc_update: function(newDoc, oldDoc, userCtx) {
+ /*if (newDoc._deleted === true && userCtx.roles.indexOf('_admin') === -1) {
+ throw "Only admin can delete documents on this database.";
+ }*/
+ }
+};
+
+
+couchapp.loadAttachments(ddoc, path.join(__dirname, 'dist', 'release'));
+module.exports = ddoc;
diff --git a/src/fauxton/favicon.ico b/src/fauxton/favicon.ico
new file mode 100644
index 000000000..0baa6f344
--- /dev/null
+++ b/src/fauxton/favicon.ico
Binary files differ
diff --git a/src/fauxton/index.html b/src/fauxton/index.html
new file mode 100644
index 000000000..892f49919
--- /dev/null
+++ b/src/fauxton/index.html
@@ -0,0 +1,53 @@
+<!doctype html>
+
+<!--
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+-->
+
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+
+ <title>Project Fauxton</title>
+
+ <!-- Application styles. -->
+ <link rel="stylesheet" href="/css/index.css">
+ <style type="text/css">
+ body {
+ padding-top: 60px;
+ padding-bottom: 40px;
+ }
+ </style>
+ <base href="/"></base>
+</head>
+
+<body id="home">
+ <!-- Main container. -->
+ <div role="main" id="main">
+ <div id="global-notifications" class="container errors-container"></div>
+ <div id="app-container"></div>
+ <hr>
+
+ <footer>
+ <div id="footer-content" class="container">
+ <p>&copy; Project Fauxton 2012</p>
+ </div>
+ </footer>
+ </div>
+
+ <!-- Application source. -->
+ <script data-main="/app/config" src="/assets/js/libs/require.js"></script>
+</body>
+</html>
diff --git a/src/fauxton/package.json b/src/fauxton/package.json
new file mode 100644
index 000000000..75d7c639f
--- /dev/null
+++ b/src/fauxton/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "fauxton",
+ "version": "0.1.0",
+ "description": "Fauxton is a modular CouchDB dashboard and Futon replacement.",
+ "main": "grunt.js",
+ "directories": {
+ "test": "test"
+ },
+ "dependencies": {
+ "async": "~0.2.6",
+ "grunt": "~0.4.1",
+ "grunt-cli": "~0.1.6",
+ "couchapp": "~0.9.1",
+ "grunt-contrib": "~0.5.0",
+ "grunt-contrib-cssmin": "~0.5.0",
+ "grunt-contrib-uglify": "~0.2.0",
+ "grunt-couchapp": "~0.1.0",
+ "grunt-exec": "~0.4.0",
+ "grunt-jasmine-task": "~0.2.3",
+ "grunt-requirejs": "~0.3.3",
+ "underscore": "~1.4.2",
+ "url": "~0.7.9",
+ "urls": "~0.0.3",
+ "express": "~3.0.6",
+ "http-proxy": "~0.9.1"
+ },
+ "devDependencies": {},
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://git-wip-us.apache.org/repos/asf/couchdb.git"
+ },
+ "keywords": [
+ "couchdb",
+ "futon",
+ "fauxton"
+ ],
+ "author": "",
+ "license": "Apache V2"
+}
diff --git a/src/fauxton/readme.md b/src/fauxton/readme.md
new file mode 100644
index 000000000..85f1dce62
--- /dev/null
+++ b/src/fauxton/readme.md
@@ -0,0 +1,66 @@
+Fauxton
+=======
+
+This is the initial implementation of Fauxton, focused on fleshing out
+the various pieces of functionality and as a test bed for new ideas.
+Full functionality and design considerations will be added later.
+
+
+
+Current items of interest:
+
+ * Live JSON editor with dynamic JS Hinting and error popups
+ * Initial plugin system
+ * Minimal externally loadable plugin example
+ * Data popups for additional db info on \_all_dbs page
+ * CouchDB API compliant urls
+
+## Setup Fauxton ##
+
+A recent of [node.js](http://nodejs.org/) and npm is required.
+
+### CouchDB Setup ###
+
+ 1. Clone the Couchdb repo: https://github.com/apache/couchdb.git or http://git-wip-us.apache.org/repos/asf/couchdb.git
+ cd couchdb
+ git checkout fauxton
+
+### Fauxton Setup ###
+
+ cd src/fauxton
+
+ # Install all dependencies
+ npm install
+
+#### (Optional) To avoid a npm global install
+ # Add node_modules/.bin to your path
+ # export PATH=./node_modules/.bin:$PATH
+ # Or just use the wrappers in ./bin/
+
+ # Development mode, non minified files
+ ./bin/grunt couchdebug
+
+ # Or fully compiled install
+ # ./bin/grunt couchdb
+
+### Dev Server
+ Using the dev server is the easiest way to use fauxton, specially when developing for it.
+
+ grunt dev
+
+### To Deploy Fauxton
+
+ ./bin/grunt couchapp_deploy - to deploy to your local [Couchdb instance] (http://localhost:5984/fauxton/_design/fauxton/index.html)
+
+## Understang Fauxton Code layout
+
+Each bit of functionality is its own seperate module or addon. All core modules are stored under `app/module` and any addons that are optional are under `app/addons`.
+We use [backbone.js](http://backbonejs.org/) and [Backbone.layoutmanager](https://github.com/tbranyen/backbone.layoutmanager) quite heavily, so best to get an idea how they work.
+Its best at this point to read through a couple of the modules and addons to get an idea of how they work. Two good starting points are `app/addon/config` and `app/modules/databases`.
+Each module must have a `base.js` file, this is read and compile when Fauxton is deployed. A `resource.js` file is usually for your Backbone.Models and Backbone.Collections,
+`view.js` for your Backbone.Views. The `routes.js` is used to register a url path for your view along with what layout, data, breadcrumbs and api point is required for the view.
+
+## Todo items
+
+Checkout [Jira](https://issues.apache.org/jira/browse/COUCHDB/component/12320406) for a list of items to do.
+
diff --git a/src/fauxton/settings.json.default b/src/fauxton/settings.json.default
new file mode 100644
index 000000000..b4c0dbcfe
--- /dev/null
+++ b/src/fauxton/settings.json.default
@@ -0,0 +1,27 @@
+{
+ "deps": [
+ { "name": "config" },
+ { "name": "logs" },
+ { "name": "stats" },
+ { "name": "contribute" }
+ ],
+ "template": {
+ "src": "assets/index.underscore",
+ "dest": "dist/debug/index.html",
+ "variables": {
+ "assets_root": "./",
+ "requirejs": "require.js",
+ "base": null
+ }
+ },
+
+ "couch_config": {
+ "fauxton": {
+ "db": "http://localhost:5984/fauxton",
+ "app": "./couchapp.js",
+ "options": {
+ "okay_if_missing": true
+ }
+ }
+ }
+}
diff --git a/src/fauxton/settings.json.sample_external b/src/fauxton/settings.json.sample_external
new file mode 100644
index 000000000..71c45b4db
--- /dev/null
+++ b/src/fauxton/settings.json.sample_external
@@ -0,0 +1,10 @@
+{
+ "deps": [
+ {
+ "name": "fauxton-demo-plugin",
+ "url": "git://github.com/chewbranca/fauxton-demo-plugin.git"
+ },
+ { "name": "config" },
+ { "name": "logs" }
+ ]
+}
diff --git a/src/fauxton/tasks/couchserver.js b/src/fauxton/tasks/couchserver.js
new file mode 100644
index 000000000..a3f620ffc
--- /dev/null
+++ b/src/fauxton/tasks/couchserver.js
@@ -0,0 +1,72 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+module.exports = function (grunt) {
+ var log = grunt.log;
+
+ grunt.registerTask("couchserver", 'Run a couch dev proxy server', function () {
+ var fs = require("fs"),
+ path = require("path"),
+ httpProxy = require('http-proxy'),
+ express = require("express"),
+ options = grunt.config('couchserver'),
+ app = express();
+
+ // Options
+ var dist_dir = options.dist || './dist/debug/';
+ var port = options.port || 8000;
+
+ // Proxy options with default localhost
+ var proxy_target = options.proxy || {
+ host: 'localhost',
+ port: 5984,
+ https: false
+ };
+
+ // inform grunt that this task is async
+ var done = this.async();
+
+ // serve any javascript or css files from here
+ app.get(/\.css$|\.js$|img/, function (req, res) {
+ res.sendfile(path.join(dist_dir,req.url));
+ });
+
+ // serve main index file from here
+ app.get('/', function (req, res) {
+ res.sendfile(path.join(dist_dir, 'index.html'));
+ });
+
+ // create proxy to couch for all couch requests
+ var proxy = new httpProxy.HttpProxy({
+ target: proxy_target
+ });
+
+ app.all('*', function (req, res) {
+ proxy.proxyRequest(req, res);
+ });
+
+ // Fail this task if any errors have been logged
+ if (grunt.errors) {
+ return false;
+ }
+
+ var watch = grunt.util.spawn({cmd: 'bbb', grunt: true, args: ['watch']}, function (error, result, code) {/* log.writeln(String(result));*/ });
+
+ watch.stdout.pipe(process.stdout);
+ watch.stderr.pipe(process.stderr);
+
+ log.writeln('Listening on ' + port);
+ app.listen(port);
+
+ });
+
+};
diff --git a/src/fauxton/tasks/fauxton.js b/src/fauxton/tasks/fauxton.js
new file mode 100644
index 000000000..b718fea95
--- /dev/null
+++ b/src/fauxton/tasks/fauxton.js
@@ -0,0 +1,170 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// var prompt = require('prompt');
+
+module.exports = function(grunt) {
+ var _ = grunt.util._;
+ var prompts = [
+ {
+ name: "name",
+ message: "Add on Name",
+ validator: /^[\w\-\.]+$/,
+ default: "WickedCool"
+ },
+ {
+ name: "path",
+ message: "Location of add ons",
+ default: "app/addons"
+ },
+ {
+ name: "assets",
+ message: "Do you need an assets folder? (for .less)",
+ default: 'y/N'
+ }
+ ];
+
+ addonTemplates = [
+ {
+ name: 'base',
+ filename: 'base.js',
+ template: grunt.file.read('./tasks/templates/base.js.underscore')
+ },
+ {
+ name: 'resources',
+ filename: 'resources.js',
+ template: grunt.file.read('./tasks/templates/resources.js.underscore')
+ },
+ {
+ name: 'routes',
+ filename: 'routes.js',
+ template: grunt.file.read('./tasks/templates/route.js.underscore')
+ }
+ ];
+
+ // Create a new task.
+ grunt.registerInitTask('addon', 'Generate a skeleton for an addon"', function() {
+ var done = this.async();
+ grunt.helper('prompt', {}, prompts, function (err, result) {
+ if (err) { return onErr(err); }
+ grunt.log.writeln(result.assets);
+ var module = result.name,
+ assets = result.assets;
+ if (assets == 'y') {
+ //if you need an assets folder
+ filepath = result.path + '/' + module.toLowerCase() + '/assets/less';
+ grunt.file.mkdir(filepath);
+ lessfile = {
+ name: 'less',
+ filename: module.toLowerCase()+'.less',
+ template: '//<%= module %> styles'
+ };
+ lessfile.module = module.charAt(0).toUpperCase() + module.substr(1);
+ var content = grunt.template.process(lessfile.template, lessfile);
+ grunt.file.write(filepath + '/' + lessfile.filename, content);
+ }
+ filepath = result.path + '/' + module.toLowerCase() + '/templates';
+ grunt.file.mkdir(filepath);
+ filepath = result.path + '/' + module.toLowerCase();
+ _.each(addonTemplates, function(file){
+ file.module = module.charAt(0).toUpperCase() + module.substr(1);
+ var content = grunt.template.process(file.template, file);
+ grunt.file.write(filepath + '/' + file.filename, content);
+ });
+ grunt.log.writeln('Created addon ' + result.name + ' in ' + result.path);
+ grunt.log.writeln('\n\nAdd ' + result.name + ' to settings.json for it to be compiled and deployed');
+ done();
+ });
+ function onErr(err) {
+ grunt.log.writeln(err);
+ done();
+ return 1;
+ }
+ });
+
+ grunt.registerMultiTask('template', 'generates an html file from a specified template', function(){
+ var data = this.data;
+ var _ = grunt.util._;
+ var tmpl = _.template(grunt.file.read(data.src), null, data.variables);
+ grunt.file.write(data.dest, tmpl(data.variables));
+ });
+
+ grunt.registerMultiTask('get_deps', 'Fetch external dependencies', function() {
+ grunt.log.writeln("Fetching external dependencies");
+
+ var path = require('path');
+ var done = this.async();
+ var data = this.data;
+ var target = data.target || "app/addons/";
+ var settingsFile = path.existsSync(data.src) ? data.src : "settings.json.default";
+ var settings = grunt.file.readJSON(settingsFile);
+ var _ = grunt.util._;
+
+ // This should probably be a helper, though they seem to have been removed
+ var fetch = function(deps, command){
+ var child_process = require('child_process');
+ var async = require('async');
+ async.forEach(deps, function(dep, cb) {
+ var path = target + dep.name;
+ var location = dep.url || dep.path;
+ grunt.log.writeln("Fetching: " + dep.name + " (" + location + ")");
+
+ child_process.exec(command(dep, path), function(error, stdout, stderr) {
+ grunt.log.writeln(stderr);
+ grunt.log.writeln(stdout);
+ cb(error);
+ });
+ }, function(error) {
+ if (error) {
+ grunt.log.writeln("ERROR: " + error.message);
+ return false;
+ } else {
+ return true;
+ }
+ });
+ };
+
+ var remoteDeps = _.filter(settings.deps, function(dep) { return !! dep.url; });
+ grunt.log.writeln(remoteDeps.length + " remote dependencies");
+ var remote = fetch(remoteDeps, function(dep, destination){
+ return "git clone " + dep.url + " " + destination;
+ });
+
+ var localDeps = _.filter(settings.deps, function(dep) { return !! dep.path; });
+ grunt.log.writeln(localDeps.length + " local dependencies");
+ var local = fetch(localDeps, function(dep, destination){
+ // TODO: Windows
+ var command = "cp -r " + dep.path + " " + destination;
+ grunt.log.writeln(command);
+ return command;
+ });
+
+ done(remote && local);
+
+ });
+
+ grunt.registerMultiTask('gen_load_addons', 'Generate the load_addons.js file', function() {
+ var path = require('path');
+ var data = this.data;
+ var _ = grunt.util._;
+ var settingsFile = path.existsSync(data.src) ? data.src : "settings.json.default";
+ var settings = grunt.file.readJSON(settingsFile);
+ var template = "app/load_addons.js.underscore";
+ var dest = "app/load_addons.js";
+ var deps = _.map(settings.deps, function(dep) {
+ return "addons/" + dep.name + "/base";
+ });
+ var tmpl = _.template(grunt.file.read(template));
+ grunt.file.write(dest, tmpl({deps: deps}));
+ });
+
+};
diff --git a/src/fauxton/tasks/helper.js b/src/fauxton/tasks/helper.js
new file mode 100644
index 000000000..5178ee2e4
--- /dev/null
+++ b/src/fauxton/tasks/helper.js
@@ -0,0 +1,32 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+var fs = require('fs');
+
+exports.init = function(grunt) {
+
+ return {
+ readSettingsFile: function () {
+ if (fs.existsSync("settings.json")) {
+ return grunt.file.readJSON("settings.json");
+ } else if (fs.existsSync("settings.json.default")) {
+ return grunt.file.readJSON("settings.json.default");
+ } else {
+ return {deps: []};
+ }
+ },
+
+ processAddons: function (callback) {
+ this.readSettingsFile().deps.forEach(callback);
+ }
+ };
+};
diff --git a/src/fauxton/tasks/templates/base.js.underscore b/src/fauxton/tasks/templates/base.js.underscore
new file mode 100644
index 000000000..cf7afa7ee
--- /dev/null
+++ b/src/fauxton/tasks/templates/base.js.underscore
@@ -0,0 +1,21 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+ "api",
+ "addons/<%= module.toLowerCase() %>/routes"
+],
+
+function(app, FauxtonAPI, <%= module %>) {
+ return <%= module %>;
+});
diff --git a/src/fauxton/tasks/templates/resources.js.underscore b/src/fauxton/tasks/templates/resources.js.underscore
new file mode 100644
index 000000000..0aa37f8bc
--- /dev/null
+++ b/src/fauxton/tasks/templates/resources.js.underscore
@@ -0,0 +1,21 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+ "api"
+],
+
+function (app, FauxtonAPI) {
+ var <%= module %> = FauxtonAPI.addon();
+ return <%= module %>;
+});
diff --git a/src/fauxton/tasks/templates/route.js.underscore b/src/fauxton/tasks/templates/route.js.underscore
new file mode 100644
index 000000000..9256766d3
--- /dev/null
+++ b/src/fauxton/tasks/templates/route.js.underscore
@@ -0,0 +1,21 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+define([
+ "app",
+ "api",
+ "addons/<%= module.toLowerCase() %>/resources"
+],
+function(app, FauxtonAPI, <%= module %>) {
+ <%= module %>.Routes = {};
+ return <%= module %>;
+});
diff --git a/src/fauxton/test/jasmine/index.html b/src/fauxton/test/jasmine/index.html
new file mode 100644
index 000000000..8905580f4
--- /dev/null
+++ b/src/fauxton/test/jasmine/index.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+
+ <title>Backbone Boilerplate Jasmine Test Suite</title>
+
+ <!-- Jasmine styles -->
+ <link rel="stylesheet" href="vendor/jasmine.css">
+</head>
+
+<body>
+ <!-- Testing libs -->
+ <script src="vendor/jasmine.js"></script>
+ <script src="vendor/jasmine-html.js"></script>
+
+ <!-- Application libs -->
+ <script src="../../assets/js/libs/jquery.js"></script>
+ <script src="../../assets/js/libs/lodash.js"></script>
+ <script src="../../assets/js/libs/backbone.js"></script>
+
+ <!-- Load application -->
+ <script data-main="../../app/config"
+ src="../../assets/js/libs/require.js"></script>
+
+ <!-- Declare your spec files to be run here -->
+ <script>
+ // Ensure you point to where your spec folder is, base directory is app/,
+ // which is why ../test is necessary
+ require({ paths: { spec: "../test/jasmine/spec" } }, [
+
+ // Load the example spec, replace this and add your own spec
+ "spec/example"
+
+ ], function() {
+ // Set up the jasmine reporters once each spec has been loaded
+ jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
+ jasmine.getEnv().execute();
+ });
+ </script>
+</body>
+</html>
diff --git a/src/fauxton/test/jasmine/spec/example.js b/src/fauxton/test/jasmine/spec/example.js
new file mode 100644
index 000000000..bbba3e838
--- /dev/null
+++ b/src/fauxton/test/jasmine/spec/example.js
@@ -0,0 +1,73 @@
+describe("one tautology", function() {
+ it("is a tautology", function() {
+ expect(true).toBeTruthy();
+ });
+
+ describe("is awesome", function() {
+ it("is awesome", function() {
+ expect(1).toBe(1);
+ });
+ });
+});
+
+describe("simple tests", function() {
+ it("increments", function() {
+ var mike = 0;
+
+ expect(mike++ === 0).toBeTruthy();
+ expect(mike === 1).toBeTruthy();
+ });
+
+ it("increments (improved)", function() {
+ var mike = 0;
+
+ expect(mike++).toBe(0);
+ expect(mike).toBe(1);
+ });
+});
+
+describe("setUp/tearDown", function() {
+ beforeEach(function() {
+ // console.log("Before");
+ });
+
+ afterEach(function() {
+ // console.log("After");
+ });
+
+ it("example", function() {
+ // console.log("During");
+ });
+
+ describe("setUp/tearDown", function() {
+ beforeEach(function() {
+ // console.log("Before2");
+ });
+
+ afterEach(function() {
+ // console.log("After2");
+ });
+
+ it("example", function() {
+ // console.log("During Nested");
+ });
+ });
+});
+
+describe("async", function() {
+ it("multiple async", function() {
+ var semaphore = 2;
+
+ setTimeout(function() {
+ expect(true).toBeTruthy();
+ semaphore--;
+ }, 500);
+
+ setTimeout(function() {
+ expect(true).toBeTruthy();
+ semaphore--;
+ }, 500);
+
+ waitsFor(function() { return semaphore === 0 });
+ });
+});
diff --git a/src/fauxton/test/jasmine/vendor/MIT.LICENSE b/src/fauxton/test/jasmine/vendor/MIT.LICENSE
new file mode 100644
index 000000000..7c435baae
--- /dev/null
+++ b/src/fauxton/test/jasmine/vendor/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2011 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/fauxton/test/jasmine/vendor/jasmine-html.js b/src/fauxton/test/jasmine/vendor/jasmine-html.js
new file mode 100644
index 000000000..73834010f
--- /dev/null
+++ b/src/fauxton/test/jasmine/vendor/jasmine-html.js
@@ -0,0 +1,190 @@
+jasmine.TrivialReporter = function(doc) {
+ this.document = doc || document;
+ this.suiteDivs = {};
+ this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) { el.appendChild(child); }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+ var showPassed, showSkipped;
+
+ this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
+ this.createDom('div', { className: 'banner' },
+ this.createDom('div', { className: 'logo' },
+ this.createDom('span', { className: 'title' }, "Jasmine"),
+ this.createDom('span', { className: 'version' }, runner.env.versionString())),
+ this.createDom('div', { className: 'options' },
+ "Show ",
+ showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+ showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+ )
+ ),
+
+ this.runnerDiv = this.createDom('div', { className: 'runner running' },
+ this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+ this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+ this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+ );
+
+ this.document.body.appendChild(this.outerDiv);
+
+ var suites = runner.suites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ var suiteDiv = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+ this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+ this.suiteDivs[suite.id] = suiteDiv;
+ var parentDiv = this.outerDiv;
+ if (suite.parentSuite) {
+ parentDiv = this.suiteDivs[suite.parentSuite.id];
+ }
+ parentDiv.appendChild(suiteDiv);
+ }
+
+ this.startedAt = new Date();
+
+ var self = this;
+ showPassed.onclick = function(evt) {
+ if (showPassed.checked) {
+ self.outerDiv.className += ' show-passed';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+ }
+ };
+
+ showSkipped.onclick = function(evt) {
+ if (showSkipped.checked) {
+ self.outerDiv.className += ' show-skipped';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+ }
+ };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+ var results = runner.results();
+ var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+ this.runnerDiv.setAttribute("class", className);
+ //do it twice for IE
+ this.runnerDiv.setAttribute("className", className);
+ var specs = runner.specs();
+ var specCount = 0;
+ for (var i = 0; i < specs.length; i++) {
+ if (this.specFilter(specs[i])) {
+ specCount++;
+ }
+ }
+ var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+ message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+ this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+ this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+ var results = suite.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.totalCount === 0) { // todo: change this to check results.skipped
+ status = 'skipped';
+ }
+ this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+ if (this.logRunningSpecs) {
+ this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+ var results = spec.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+ var specDiv = this.createDom('div', { className: 'spec ' + status },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(spec.getFullName()),
+ title: spec.getFullName()
+ }, spec.description));
+
+
+ var resultItems = results.getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ specDiv.appendChild(messagesDiv);
+ }
+
+ this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+ return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+ var paramMap = {};
+ var params = this.getLocation().search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ if (!paramMap.spec) {
+ return true;
+ }
+ return spec.getFullName().indexOf(paramMap.spec) === 0;
+};
diff --git a/src/fauxton/test/jasmine/vendor/jasmine.css b/src/fauxton/test/jasmine/vendor/jasmine.css
new file mode 100644
index 000000000..6583fe7c6
--- /dev/null
+++ b/src/fauxton/test/jasmine/vendor/jasmine.css
@@ -0,0 +1,166 @@
+body {
+ font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
+}
+
+
+.jasmine_reporter a:visited, .jasmine_reporter a {
+ color: #303;
+}
+
+.jasmine_reporter a:hover, .jasmine_reporter a:active {
+ color: blue;
+}
+
+.run_spec {
+ float:right;
+ padding-right: 5px;
+ font-size: .8em;
+ text-decoration: none;
+}
+
+.jasmine_reporter {
+ margin: 0 5px;
+}
+
+.banner {
+ color: #303;
+ background-color: #fef;
+ padding: 5px;
+}
+
+.logo {
+ float: left;
+ font-size: 1.1em;
+ padding-left: 5px;
+}
+
+.logo .version {
+ font-size: .6em;
+ padding-left: 1em;
+}
+
+.runner.running {
+ background-color: yellow;
+}
+
+
+.options {
+ text-align: right;
+ font-size: .8em;
+}
+
+
+
+
+.suite {
+ border: 1px outset gray;
+ margin: 5px 0;
+ padding-left: 1em;
+}
+
+.suite .suite {
+ margin: 5px;
+}
+
+.suite.passed {
+ background-color: #dfd;
+}
+
+.suite.failed {
+ background-color: #fdd;
+}
+
+.spec {
+ margin: 5px;
+ padding-left: 1em;
+ clear: both;
+}
+
+.spec.failed, .spec.passed, .spec.skipped {
+ padding-bottom: 5px;
+ border: 1px solid gray;
+}
+
+.spec.failed {
+ background-color: #fbb;
+ border-color: red;
+}
+
+.spec.passed {
+ background-color: #bfb;
+ border-color: green;
+}
+
+.spec.skipped {
+ background-color: #bbb;
+}
+
+.messages {
+ border-left: 1px dashed gray;
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+.passed {
+ background-color: #cfc;
+ display: none;
+}
+
+.failed {
+ background-color: #fbb;
+}
+
+.skipped {
+ color: #777;
+ background-color: #eee;
+ display: none;
+}
+
+
+/*.resultMessage {*/
+ /*white-space: pre;*/
+/*}*/
+
+.resultMessage span.result {
+ display: block;
+ line-height: 2em;
+ color: black;
+}
+
+.resultMessage .mismatch {
+ color: black;
+}
+
+.stackTrace {
+ white-space: pre;
+ font-size: .8em;
+ margin-left: 10px;
+ max-height: 5em;
+ overflow: auto;
+ border: 1px inset red;
+ padding: 1em;
+ background: #eef;
+}
+
+.finished-at {
+ padding-left: 1em;
+ font-size: .6em;
+}
+
+.show-passed .passed,
+.show-skipped .skipped {
+ display: block;
+}
+
+
+#jasmine_content {
+ position:fixed;
+ right: 100%;
+}
+
+.runner {
+ border: 1px solid gray;
+ display: block;
+ margin: 5px 0;
+ padding: 2px 0 2px 10px;
+}
diff --git a/src/fauxton/test/jasmine/vendor/jasmine.js b/src/fauxton/test/jasmine/vendor/jasmine.js
new file mode 100644
index 000000000..c3d2dc7d2
--- /dev/null
+++ b/src/fauxton/test/jasmine/vendor/jasmine.js
@@ -0,0 +1,2476 @@
+var isCommonJS = typeof window == "undefined";
+
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+if (isCommonJS) exports.jasmine = jasmine;
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+ throw new Error("unimplemented method");
+};
+
+/**
+ * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Show diagnostic messages in the console if set to true
+ *
+ */
+jasmine.VERBOSE = false;
+
+/**
+ * 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.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+jasmine.getGlobal = function() {
+ function getGlobal() {
+ return this;
+ }
+
+ return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared. Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+ var original = base[name];
+ if (original.apply) {
+ return function() {
+ return original.apply(base, arguments);
+ };
+ } else {
+ // IE support
+ return jasmine.getGlobal()[name];
+ }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+ this.type = 'log';
+ this.values = values;
+ this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+ var text = "";
+ for (var i = 0; i < this.values.length; i++) {
+ if (i > 0) text += " ";
+ if (jasmine.isString_(this.values[i])) {
+ text += this.values[i];
+ } else {
+ text += jasmine.pp(this.values[i]);
+ }
+ }
+ return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+ this.type = 'expect';
+ this.matcherName = params.matcherName;
+ this.passed_ = params.passed;
+ this.expected = params.expected;
+ this.actual = params.actual;
+ this.message = this.passed_ ? 'Passed.' : params.message;
+
+ var trace = (params.trace || new Error(this.message));
+ this.trace = this.passed_ ? '' : trace;
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+ return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+ return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+ var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+ return env;
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+ return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+ return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+ return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+ var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+ return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).toHaveBeenCalled();
+ * expect(foo.not).toHaveBeenCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+ /**
+ * The name of the spy, if provided.
+ */
+ this.identity = name || 'unknown';
+ /**
+ * Is this Object a spy?
+ */
+ this.isSpy = true;
+ /**
+ * The actual function this spy stubs.
+ */
+ this.plan = function() {
+ };
+ /**
+ * Tracking of the most recent call to the spy.
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy.mostRecentCall.args = [1, 2];
+ */
+ this.mostRecentCall = {};
+
+ /**
+ * Holds arguments for each call to the spy, indexed by call count
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy(7, 8);
+ * mySpy.mostRecentCall.args = [7, 8];
+ * mySpy.argsForCall[0] = [1, 2];
+ * mySpy.argsForCall[1] = [7, 8];
+ */
+ this.argsForCall = [];
+ this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ * bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+ this.plan = this.originalValue;
+ return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+ this.plan = function() {
+ return value;
+ };
+ return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+ this.plan = function() {
+ throw exceptionMsg;
+ };
+ return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ * // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+ this.plan = fakeFunc;
+ return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+ this.wasCalled = false;
+ this.callCount = 0;
+ this.argsForCall = [];
+ this.calls = [];
+ this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+ var spyObj = function() {
+ spyObj.wasCalled = true;
+ spyObj.callCount++;
+ var args = jasmine.util.argsToArray(arguments);
+ spyObj.mostRecentCall.object = this;
+ spyObj.mostRecentCall.args = args;
+ spyObj.argsForCall.push(args);
+ spyObj.calls.push({object: this, args: args});
+ return spyObj.plan.apply(this, arguments);
+ };
+
+ var spy = new jasmine.Spy(name);
+
+ for (var prop in spy) {
+ spyObj[prop] = spy[prop];
+ }
+
+ spyObj.reset();
+
+ return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+ return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+ if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
+ throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> in production code.
+ */
+jasmine.log = function() {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.log.apply(spec, arguments);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @returns a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+ return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+if (isCommonJS) exports.spyOn = spyOn;
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ * expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+ return jasmine.getEnv().it(desc, func);
+};
+if (isCommonJS) exports.it = it;
+
+/**
+ * Creates a <em>disabled</em> Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+ return jasmine.getEnv().xit(desc, func);
+};
+if (isCommonJS) exports.xit = xit;
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ */
+var expect = function(actual) {
+ return jasmine.getEnv().currentSpec.expect(actual);
+};
+if (isCommonJS) exports.expect = expect;
+
+/**
+ * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+ jasmine.getEnv().currentSpec.runs(func);
+};
+if (isCommonJS) exports.runs = runs;
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+var waits = function(timeout) {
+ jasmine.getEnv().currentSpec.waits(timeout);
+};
+if (isCommonJS) exports.waits = waits;
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
+};
+if (isCommonJS) exports.waitsFor = waitsFor;
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+ jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+if (isCommonJS) exports.beforeEach = beforeEach;
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+ jasmine.getEnv().afterEach(afterEachFunction);
+};
+if (isCommonJS) exports.afterEach = afterEach;
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+ return jasmine.getEnv().describe(description, specDefinitions);
+};
+if (isCommonJS) exports.describe = describe;
+
+/**
+ * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+ return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+if (isCommonJS) exports.xdescribe = xdescribe;
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+ function tryIt(f) {
+ try {
+ return f();
+ } catch(e) {
+ }
+ return null;
+ }
+
+ var xhr = tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ });
+
+ if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
+
+ return xhr;
+} : XMLHttpRequest;
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+ /**
+ * @private
+ */
+ var subclass = function() {
+ };
+ subclass.prototype = parentClass.prototype;
+ childClass.prototype = new subclass();
+};
+
+jasmine.util.formatException = function(e) {
+ var lineNumber;
+ if (e.line) {
+ lineNumber = e.line;
+ }
+ else if (e.lineNumber) {
+ lineNumber = e.lineNumber;
+ }
+
+ var file;
+
+ if (e.sourceURL) {
+ file = e.sourceURL;
+ }
+ else if (e.fileName) {
+ file = e.fileName;
+ }
+
+ var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+ if (file && lineNumber) {
+ message += ' in ' + file + ' (line ' + lineNumber + ')';
+ }
+
+ return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+ if (!str) return str;
+ return str.replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+};
+
+jasmine.util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+ return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+ this.currentSpec = null;
+ this.currentSuite = null;
+ this.currentRunner_ = new jasmine.Runner(this);
+
+ this.reporter = new jasmine.MultiReporter();
+
+ this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+ this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+ this.lastUpdate = 0;
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.nextSpecId_ = 0;
+ this.nextSuiteId_ = 0;
+ this.equalityTesters_ = [];
+
+ // wrap matchers
+ this.matchersClass = function() {
+ jasmine.Matchers.apply(this, arguments);
+ };
+ jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+ jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+ if (jasmine.version_) {
+ return jasmine.version_;
+ } else {
+ throw new Error('Version not set');
+ }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+ if (!jasmine.version_) {
+ return "version unknown";
+ }
+
+ var version = this.version();
+ var versionString = version.major + "." + version.minor + "." + version.build;
+ if (version.release_candidate) {
+ versionString += ".rc" + version.release_candidate;
+ }
+ versionString += " revision " + version.revision;
+ return versionString;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+ return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+ return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+ this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+ this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+ var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+ var parentSuite = this.currentSuite;
+ if (parentSuite) {
+ parentSuite.add(suite);
+ } else {
+ this.currentRunner_.add(suite);
+ }
+
+ this.currentSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch(e) {
+ declarationError = e;
+ }
+
+ if (declarationError) {
+ this.it("encountered a declaration exception", function() {
+ throw declarationError;
+ });
+ }
+
+ this.currentSuite = parentSuite;
+
+ return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.beforeEach(beforeEachFunction);
+ } else {
+ this.currentRunner_.beforeEach(beforeEachFunction);
+ }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+ return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.afterEach(afterEachFunction);
+ } else {
+ this.currentRunner_.afterEach(afterEachFunction);
+ }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+ return {
+ execute: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+ var spec = new jasmine.Spec(this, this.currentSuite, description);
+ this.currentSuite.add(spec);
+ this.currentSpec = spec;
+
+ if (func) {
+ spec.runs(func);
+ }
+
+ return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+ return {
+ id: this.nextSpecId(),
+ runs: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+ return true;
+ }
+
+ a.__Jasmine_been_here_before__ = b;
+ b.__Jasmine_been_here_before__ = a;
+
+ var hasKey = function(obj, keyName) {
+ return obj !== null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in b) {
+ if (!hasKey(a, property) && hasKey(b, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ }
+ for (property in a) {
+ if (!hasKey(b, property) && hasKey(a, property)) {
+ mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+ }
+ }
+ for (property in b) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+ 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.");
+ }
+ }
+
+ if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+ mismatchValues.push("arrays were not the same length");
+ }
+
+ delete a.__Jasmine_been_here_before__;
+ delete b.__Jasmine_been_here_before__;
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ for (var i = 0; i < this.equalityTesters_.length; i++) {
+ var equalityTester = this.equalityTesters_[i];
+ var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+ if (result !== jasmine.undefined) return result;
+ }
+
+ if (a === b) return true;
+
+ if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+ return (a == jasmine.undefined && b == jasmine.undefined);
+ }
+
+ if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+ return a === b;
+ }
+
+ if (a instanceof Date && b instanceof Date) {
+ return a.getTime() == b.getTime();
+ }
+
+ if (a instanceof jasmine.Matchers.Any) {
+ return a.matches(b);
+ }
+
+ if (b instanceof jasmine.Matchers.Any) {
+ return b.matches(a);
+ }
+
+ if (jasmine.isString_(a) && jasmine.isString_(b)) {
+ return (a == b);
+ }
+
+ if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+ return (a == b);
+ }
+
+ if (typeof a === "object" && typeof b === "object") {
+ return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ //Straight check
+ return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+ if (jasmine.isArray_(haystack)) {
+ for (var i = 0; i < haystack.length; i++) {
+ if (this.equals_(haystack[i], needle)) return true;
+ }
+ return false;
+ }
+ return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+ this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+ this.env = env;
+ this.func = func;
+ this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {
+ try {
+ this.func.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ }
+ onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+ this.started = false;
+ this.finished = false;
+ this.suites_ = [];
+ this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+ this.started = true;
+ var suites = runner.topLevelSuites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ this.suites_.push(this.summarize_(suite));
+ }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+ var isSuite = suiteOrSpec instanceof jasmine.Suite;
+ var summary = {
+ id: suiteOrSpec.id,
+ name: suiteOrSpec.description,
+ type: isSuite ? 'suite' : 'spec',
+ children: []
+ };
+
+ if (isSuite) {
+ var children = suiteOrSpec.children();
+ for (var i = 0; i < children.length; i++) {
+ summary.children.push(this.summarize_(children[i]));
+ }
+ }
+ return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+ return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+ return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+ this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+ this.results_[spec.id] = {
+ messages: spec.results().getItems(),
+ result: spec.results().failedCount > 0 ? "failed" : "passed"
+ };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+ var results = {};
+ for (var i = 0; i < specIds.length; i++) {
+ var specId = specIds[i];
+ results[specId] = this.summarizeResult_(this.results_[specId]);
+ }
+ return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+ var summaryMessages = [];
+ var messagesLength = result.messages.length;
+ for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+ var resultMessage = result.messages[messageIndex];
+ summaryMessages.push({
+ text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
+ passed: resultMessage.passed ? resultMessage.passed() : true,
+ type: resultMessage.type,
+ message: resultMessage.message,
+ trace: {
+ stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+ }
+ });
+ }
+
+ return {
+ result : result.result,
+ messages : summaryMessages
+ };
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+ this.env = env;
+ this.actual = actual;
+ this.spec = spec;
+ this.isNot = opt_isNot || false;
+ this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+ throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+};
+
+// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+ throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+ for (var methodName in prototype) {
+ if (methodName == 'report') continue;
+ var orig = prototype[methodName];
+ matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+ }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+ return function() {
+ var matcherArgs = jasmine.util.argsToArray(arguments);
+ var result = matcherFunction.apply(this, arguments);
+
+ if (this.isNot) {
+ result = !result;
+ }
+
+ if (this.reportWasCalled_) return result;
+
+ var message;
+ if (!result) {
+ if (this.message) {
+ message = this.message.apply(this, arguments);
+ if (jasmine.isArray_(message)) {
+ message = message[this.isNot ? 1 : 0];
+ }
+ } else {
+ var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+ message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+ if (matcherArgs.length > 0) {
+ for (var i = 0; i < matcherArgs.length; i++) {
+ if (i > 0) message += ",";
+ message += " " + jasmine.pp(matcherArgs[i]);
+ }
+ }
+ message += ".";
+ }
+ }
+ var expectationResult = new jasmine.ExpectationResult({
+ matcherName: matcherName,
+ passed: result,
+ expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+ actual: this.actual,
+ message: message
+ });
+ this.spec.addMatcherResult(expectationResult);
+ return jasmine.undefined;
+ };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+ return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ * @deprecated as of 1.0. Use not.toBe() instead.
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+ return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+ return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ * @deprecated as of 1.0. Use not.toNotEqual() instead.
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+ return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+ return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ * @deprecated as of 1.0. Use not.toMatch() instead.
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+ return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+ return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+ return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+ return (this.actual === null);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+ return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+ return !this.actual;
+};
+
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.toHaveBeenCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called.",
+ "Expected spy " + this.actual.identity + " not to have been called."
+ ];
+ };
+
+ return this.actual.wasCalled;
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
+jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ *
+ * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('wasNotCalled does not take arguments');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to not have been called.",
+ "Expected spy " + this.actual.identity + " to have been called."
+ ];
+ };
+
+ return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ this.message = function() {
+ if (this.actual.callCount === 0) {
+ // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
+ return [
+ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
+ "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
+ ];
+ } else {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
+ "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
+ ];
+ }
+ };
+
+ return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
+
+/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
+ "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
+ ];
+ };
+
+ return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+ return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ * @deprecated as of 1.0. Use not.toNotContain() instead.
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+ return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+ return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+ return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected item is equal to the actual item
+ * up to a given level of decimal precision (default 2).
+ *
+ * @param {Number} expected
+ * @param {Number} precision
+ */
+jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
+ if (!(precision === 0)) {
+ precision = precision || 2;
+ }
+ var multiplier = Math.pow(10, precision);
+ var actual = Math.round(this.actual * multiplier);
+ expected = Math.round(expected * multiplier);
+ return expected == actual;
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} expected
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+ var result = false;
+ var exception;
+ if (typeof this.actual != 'function') {
+ throw new Error('Actual is not a function');
+ }
+ try {
+ this.actual();
+ } catch (e) {
+ exception = e;
+ }
+ if (exception) {
+ result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+ }
+
+ var not = this.isNot ? "not " : "";
+
+ this.message = function() {
+ if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+ return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
+ } else {
+ return "Expected function to throw an exception.";
+ }
+ };
+
+ return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+ this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.matches = function(other) {
+ if (this.expectedClass == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedClass == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedClass == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedClass == Object) {
+ return typeof other == 'object';
+ }
+
+ return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.toString = function() {
+ return '<jasmine.any(' + this.expectedClass + ')>';
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+ this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+ this.subReporters_.push(reporter);
+};
+
+(function() {
+ var functionNames = [
+ "reportRunnerStarting",
+ "reportRunnerResults",
+ "reportSuiteResults",
+ "reportSpecStarting",
+ "reportSpecResults",
+ "log"
+ ];
+ for (var i = 0; i < functionNames.length; i++) {
+ var functionName = functionNames[i];
+ jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+ return function() {
+ for (var j = 0; j < this.subReporters_.length; j++) {
+ var subReporter = this.subReporters_[j];
+ if (subReporter[functionName]) {
+ subReporter[functionName].apply(subReporter, arguments);
+ }
+ }
+ };
+ })(functionName);
+ }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+ /**
+ * The total count of results
+ */
+ this.totalCount = 0;
+ /**
+ * Number of passed results
+ */
+ this.passedCount = 0;
+ /**
+ * Number of failed results
+ */
+ this.failedCount = 0;
+ /**
+ * Was this suite/spec skipped?
+ */
+ this.skipped = false;
+ /**
+ * @ignore
+ */
+ this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+ this.totalCount += result.totalCount;
+ this.passedCount += result.passedCount;
+ this.failedCount += result.failedCount;
+};
+
+/**
+ * Adds a log message.
+ * @param values Array of message parts which will be concatenated later.
+ */
+jasmine.NestedResults.prototype.log = function(values) {
+ this.items_.push(new jasmine.MessageResult(values));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+ return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+ if (result.type != 'log') {
+ if (result.items_) {
+ this.rollupCounts(result);
+ } else {
+ this.totalCount++;
+ if (result.passed()) {
+ this.passedCount++;
+ } else {
+ this.failedCount++;
+ }
+ }
+ }
+ this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if <b>everything</b> below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+ return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+ this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+ if (this.ppNestLevel_ > 40) {
+ throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
+ }
+
+ this.ppNestLevel_++;
+ try {
+ if (value === jasmine.undefined) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value === jasmine.getGlobal()) {
+ this.emitScalar('<global>');
+ } else if (value instanceof jasmine.Matchers.Any) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (jasmine.isSpy(value)) {
+ this.emitScalar("spy on " + value.identity);
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (value.__Jasmine_been_here_before__) {
+ this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>');
+ } else if (jasmine.isArray_(value) || typeof value == 'object') {
+ value.__Jasmine_been_here_before__ = true;
+ if (jasmine.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ delete value.__Jasmine_been_here_before__;
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
+ obj.__lookupGetter__(property) !== null) : false);
+ }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+ jasmine.PrettyPrinter.call(this);
+
+ this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+ this.append('[ ');
+ for (var i = 0; i < array.length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+ var self = this;
+ this.append('{ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.append(property);
+ self.append(' : ');
+ if (isGetter) {
+ self.append('<getter>');
+ } else {
+ self.format(obj[property]);
+ }
+ });
+
+ this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+};
+jasmine.Queue = function(env) {
+ this.env = env;
+ this.blocks = [];
+ this.running = false;
+ this.index = 0;
+ this.offset = 0;
+ this.abort = false;
+};
+
+jasmine.Queue.prototype.addBefore = function(block) {
+ this.blocks.unshift(block);
+};
+
+jasmine.Queue.prototype.add = function(block) {
+ this.blocks.push(block);
+};
+
+jasmine.Queue.prototype.insertNext = function(block) {
+ this.blocks.splice((this.index + this.offset + 1), 0, block);
+ this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+ this.running = true;
+ this.onComplete = onComplete;
+ this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+ return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+ var self = this;
+ var goAgain = true;
+
+ while (goAgain) {
+ goAgain = false;
+
+ if (self.index < self.blocks.length && !this.abort) {
+ var calledSynchronously = true;
+ var completedSynchronously = false;
+
+ var onComplete = function () {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+ completedSynchronously = true;
+ return;
+ }
+
+ if (self.blocks[self.index].abort) {
+ self.abort = true;
+ }
+
+ self.offset = 0;
+ self.index++;
+
+ var now = new Date().getTime();
+ if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+ self.env.lastUpdate = now;
+ self.env.setTimeout(function() {
+ self.next_();
+ }, 0);
+ } else {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+ goAgain = true;
+ } else {
+ self.next_();
+ }
+ }
+ };
+ self.blocks[self.index].execute(onComplete);
+
+ calledSynchronously = false;
+ if (completedSynchronously) {
+ onComplete();
+ }
+
+ } else {
+ self.running = false;
+ if (self.onComplete) {
+ self.onComplete();
+ }
+ }
+ }
+};
+
+jasmine.Queue.prototype.results = function() {
+ var results = new jasmine.NestedResults();
+ for (var i = 0; i < this.blocks.length; i++) {
+ if (this.blocks[i].results) {
+ results.addResult(this.blocks[i].results());
+ }
+ }
+ return results;
+};
+
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+ var self = this;
+ self.env = env;
+ self.queue = new jasmine.Queue(env);
+ self.before_ = [];
+ self.after_ = [];
+ self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+ var self = this;
+ if (self.env.reporter.reportRunnerStarting) {
+ self.env.reporter.reportRunnerStarting(this);
+ }
+ self.queue.start(function () {
+ self.finishCallback();
+ });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.splice(0,0,beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.splice(0,0,afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+ this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+ this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+ if (block instanceof jasmine.Suite) {
+ this.addSuite(block);
+ }
+ this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+ var suites = this.suites();
+ var specs = [];
+ for (var i = 0; i < suites.length; i++) {
+ specs = specs.concat(suites[i].specs());
+ }
+ return specs;
+};
+
+jasmine.Runner.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Runner.prototype.topLevelSuites = function() {
+ var topLevelSuites = [];
+ for (var i = 0; i < this.suites_.length; i++) {
+ if (!this.suites_[i].parentSuite) {
+ topLevelSuites.push(this.suites_[i]);
+ }
+ }
+ return topLevelSuites;
+};
+
+jasmine.Runner.prototype.results = function() {
+ return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+ if (!env) {
+ throw new Error('jasmine.Env() required');
+ }
+ if (!suite) {
+ throw new Error('jasmine.Suite() required');
+ }
+ var spec = this;
+ spec.id = env.nextSpecId ? env.nextSpecId() : null;
+ spec.env = env;
+ spec.suite = suite;
+ spec.description = description;
+ spec.queue = new jasmine.Queue(env);
+
+ spec.afterCallbacks = [];
+ spec.spies_ = [];
+
+ spec.results_ = new jasmine.NestedResults();
+ spec.results_.description = description;
+ spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+ return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+ return this.results_;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the spec's output.
+ *
+ * Be careful not to leave calls to <code>jasmine.log</code> in production code.
+ */
+jasmine.Spec.prototype.log = function() {
+ return this.results_.log(arguments);
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+ var block = new jasmine.Block(this.env, func, this);
+ this.addToQueue(block);
+ return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+ if (this.queue.isRunning()) {
+ this.queue.insertNext(block);
+ } else {
+ this.queue.add(block);
+ }
+};
+
+/**
+ * @param {jasmine.ExpectationResult} result
+ */
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+ this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+ var positive = new (this.getMatchersClass_())(this.env, actual, this);
+ positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+ return positive;
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+jasmine.Spec.prototype.waits = function(timeout) {
+ var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+ this.addToQueue(waitsFunc);
+ return this;
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ var latchFunction_ = null;
+ var optional_timeoutMessage_ = null;
+ var optional_timeout_ = null;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ switch (typeof arg) {
+ case 'function':
+ latchFunction_ = arg;
+ break;
+ case 'string':
+ optional_timeoutMessage_ = arg;
+ break;
+ case 'number':
+ optional_timeout_ = arg;
+ break;
+ }
+ }
+
+ var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
+ this.addToQueue(waitsForFunc);
+ return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+ var expectationResult = new jasmine.ExpectationResult({
+ passed: false,
+ message: e ? jasmine.util.formatException(e) : 'Exception',
+ trace: { stack: e.stack }
+ });
+ this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+ return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+ var parent = this.getMatchersClass_();
+ var newMatchersClass = function() {
+ parent.apply(this, arguments);
+ };
+ jasmine.util.inherit(newMatchersClass, parent);
+ jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+ this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+ this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+ this.removeAllSpies();
+ this.finishCallback();
+ if (onComplete) {
+ onComplete();
+ }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+ if (this.queue.isRunning()) {
+ this.queue.add(new jasmine.Block(this.env, doAfter, this));
+ } else {
+ this.afterCallbacks.unshift(doAfter);
+ }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+ var spec = this;
+ if (!spec.env.specFilter(spec)) {
+ spec.results_.skipped = true;
+ spec.finish(onComplete);
+ return;
+ }
+
+ this.env.reporter.reportSpecStarting(this);
+
+ spec.env.currentSpec = spec;
+
+ spec.addBeforesAndAftersToQueue();
+
+ spec.queue.start(function () {
+ spec.finish(onComplete);
+ });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+ var runner = this.env.currentRunner();
+ var i;
+
+ for (var suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+ }
+ }
+ for (i = 0; i < runner.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+ }
+ for (i = 0; i < this.afterCallbacks.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
+ }
+ for (suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
+ }
+ }
+ for (i = 0; i < runner.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
+ }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+ throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+ if (obj == jasmine.undefined) {
+ throw "spyOn could not find an object to spy upon for " + methodName + "()";
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+ throw methodName + '() method does not exist';
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spyObj = jasmine.createSpy(methodName);
+
+ this.spies_.push(spyObj);
+ spyObj.baseObj = obj;
+ spyObj.methodName = methodName;
+ spyObj.originalValue = obj[methodName];
+
+ obj[methodName] = spyObj;
+
+ return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+ for (var i = 0; i < this.spies_.length; i++) {
+ var spy = this.spies_[i];
+ spy.baseObj[spy.methodName] = spy.originalValue;
+ }
+ this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+ var self = this;
+ self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+ self.description = description;
+ self.queue = new jasmine.Queue(env);
+ self.parentSuite = parentSuite;
+ self.env = env;
+ self.before_ = [];
+ self.after_ = [];
+ self.children_ = [];
+ self.suites_ = [];
+ self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+ this.env.reporter.reportSuiteResults(this);
+ this.finished = true;
+ if (typeof(onComplete) == 'function') {
+ onComplete();
+ }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.unshift(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.unshift(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+ return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(suiteOrSpec) {
+ this.children_.push(suiteOrSpec);
+ if (suiteOrSpec instanceof jasmine.Suite) {
+ this.suites_.push(suiteOrSpec);
+ this.env.currentRunner().addSuite(suiteOrSpec);
+ } else {
+ this.specs_.push(suiteOrSpec);
+ }
+ this.queue.add(suiteOrSpec);
+};
+
+jasmine.Suite.prototype.specs = function() {
+ return this.specs_;
+};
+
+jasmine.Suite.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Suite.prototype.children = function() {
+ return this.children_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+ var self = this;
+ this.queue.start(function () {
+ self.finish(onComplete);
+ });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+ this.timeout = timeout;
+ jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+ }
+ this.env.setTimeout(function () {
+ onComplete();
+ }, this.timeout);
+};
+/**
+ * A block which waits for some condition to become true, with timeout.
+ *
+ * @constructor
+ * @extends jasmine.Block
+ * @param {jasmine.Env} env The Jasmine environment.
+ * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
+ * @param {Function} latchFunction A function which returns true when the desired condition has been met.
+ * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
+ * @param {jasmine.Spec} spec The Jasmine spec.
+ */
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+ this.timeout = timeout || env.defaultTimeoutInterval;
+ this.latchFunction = latchFunction;
+ this.message = message;
+ this.totalTimeSpentWaitingForLatch = 0;
+ jasmine.Block.call(this, env, null, spec);
+};
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
+
+jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
+ }
+ var latchFunctionResult;
+ try {
+ latchFunctionResult = this.latchFunction.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ onComplete();
+ return;
+ }
+
+ if (latchFunctionResult) {
+ onComplete();
+ } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
+ var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
+ this.spec.fail({
+ name: 'timeout',
+ message: message
+ });
+
+ this.abort = true;
+ onComplete();
+ } else {
+ this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+ var self = this;
+ this.env.setTimeout(function() {
+ self.execute(onComplete);
+ }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+ }
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+ this.reset();
+
+ var self = this;
+ self.setTimeout = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+ return self.timeoutsMade;
+ };
+
+ self.setInterval = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+ return self.timeoutsMade;
+ };
+
+ self.clearTimeout = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+ self.clearInterval = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+ this.timeoutsMade = 0;
+ this.scheduledFunctions = {};
+ this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+ var oldMillis = this.nowMillis;
+ var newMillis = oldMillis + millis;
+ this.runFunctionsWithinRange(oldMillis, newMillis);
+ this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+ var scheduledFunc;
+ var funcsToRun = [];
+ for (var timeoutKey in this.scheduledFunctions) {
+ scheduledFunc = this.scheduledFunctions[timeoutKey];
+ if (scheduledFunc != jasmine.undefined &&
+ scheduledFunc.runAtMillis >= oldMillis &&
+ scheduledFunc.runAtMillis <= nowMillis) {
+ funcsToRun.push(scheduledFunc);
+ this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ }
+ }
+
+ if (funcsToRun.length > 0) {
+ funcsToRun.sort(function(a, b) {
+ return a.runAtMillis - b.runAtMillis;
+ });
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ try {
+ var funcToRun = funcsToRun[i];
+ this.nowMillis = funcToRun.runAtMillis;
+ funcToRun.funcToCall();
+ if (funcToRun.recurring) {
+ this.scheduleFunction(funcToRun.timeoutKey,
+ funcToRun.funcToCall,
+ funcToRun.millis,
+ true);
+ }
+ } catch(e) {
+ }
+ }
+ this.runFunctionsWithinRange(oldMillis, nowMillis);
+ }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+ this.scheduledFunctions[timeoutKey] = {
+ runAtMillis: this.nowMillis + millis,
+ funcToCall: funcToCall,
+ recurring: recurring,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+ defaultFakeTimer: new jasmine.FakeTimer(),
+
+ reset: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.reset();
+ },
+
+ tick: function(millis) {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.tick(millis);
+ },
+
+ runFunctionsWithinRange: function(oldMillis, nowMillis) {
+ jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+ },
+
+ scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+ jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+ },
+
+ useMock: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.after(jasmine.Clock.uninstallMock);
+
+ jasmine.Clock.installMock();
+ }
+ },
+
+ installMock: function() {
+ jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+ },
+
+ uninstallMock: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.installed = jasmine.Clock.real;
+ },
+
+ real: {
+ setTimeout: jasmine.getGlobal().setTimeout,
+ clearTimeout: jasmine.getGlobal().clearTimeout,
+ setInterval: jasmine.getGlobal().setInterval,
+ clearInterval: jasmine.getGlobal().clearInterval
+ },
+
+ assertInstalled: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+ }
+ },
+
+ isInstalled: function() {
+ return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
+ },
+
+ installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setTimeout.apply) {
+ return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().setInterval = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setInterval.apply) {
+ return jasmine.Clock.installed.setInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setInterval(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().clearTimeout = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearTimeout(timeoutKey);
+ }
+};
+
+jasmine.getGlobal().clearInterval = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearInterval(timeoutKey);
+ }
+};
+
+jasmine.version_= {
+ "major": 1,
+ "minor": 1,
+ "build": 0,
+ "revision": 1315677058
+};
diff --git a/src/fauxton/test/jasmine/vendor/jasmine_favicon.png b/src/fauxton/test/jasmine/vendor/jasmine_favicon.png
new file mode 100644
index 000000000..218f3b437
--- /dev/null
+++ b/src/fauxton/test/jasmine/vendor/jasmine_favicon.png
Binary files differ
diff --git a/src/fauxton/test/qunit/index.html b/src/fauxton/test/qunit/index.html
new file mode 100644
index 000000000..6b400a273
--- /dev/null
+++ b/src/fauxton/test/qunit/index.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width,initial-scale=1">
+
+ <title>Backbone Boilerplate QUnit Test Suite</title>
+
+ <!-- QUnit styles -->
+ <link rel="stylesheet" href="vendor/qunit.css">
+</head>
+
+<body>
+ <!-- QUnit stucture -->
+ <div class="dataset-test">
+ <h1 id="qunit-header">Backbone Boilerplate QUnit Test Suite</h1>
+ <h2 id="qunit-banner"></h2>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+ </div>
+
+ <!-- Testing libs -->
+ <script src="vendor/qunit.js"></script>
+
+ <!-- Application libs -->
+ <script src="../../assets/js/libs/jquery.js"></script>
+ <script src="../../assets/js/libs/lodash.js"></script>
+ <script src="../../assets/js/libs/backbone.js"></script>
+
+ <!-- Load application -->
+ <script data-main="../../app/config"
+ src="../../assets/js/libs/require.js"></script>
+
+ <!-- Declare your test files to be run here -->
+ <script>
+ // Ensure you point to where your tests are, base directory is app/, which
+ // is why ../test is necessary
+ require({ paths: { tests: "../test/qunit/tests" } }, [
+
+ // Load the example tests, replace this and add your own tests
+ "tests/example"
+
+ ]);
+ </script>
+</body>
+</html>
diff --git a/src/fauxton/test/qunit/tests/example.js b/src/fauxton/test/qunit/tests/example.js
new file mode 100644
index 000000000..4c1242f81
--- /dev/null
+++ b/src/fauxton/test/qunit/tests/example.js
@@ -0,0 +1,54 @@
+test("one tautology", function() {
+ ok(true);
+});
+
+module("simple tests");
+
+test("increments", function() {
+ var mike = 0;
+
+ ok(mike++ === 0);
+ ok(mike === 1);
+});
+
+test("increments (improved)", function() {
+ var mike = 0;
+
+ equal(mike++, 0);
+ equal(mike, 1);
+});
+
+
+module("setUp/tearDown", {
+ setup: function() {
+ //console.log("Before");
+ },
+
+ teardown: function() {
+ //console.log("After");
+ }
+});
+
+test("example", function() {
+ //console.log("During");
+});
+
+module("async");
+
+test("multiple async", function() {
+ expect(2);
+
+ stop();
+
+ setTimeout( function( ) {
+ ok(true, "async operation completed");
+ start();
+ }, 500);
+
+ stop();
+
+ setTimeout(function() {
+ ok(true, "async operation completed");
+ start();
+ }, 500);
+});
diff --git a/src/fauxton/test/qunit/vendor/qunit.css b/src/fauxton/test/qunit/vendor/qunit.css
new file mode 100644
index 000000000..861236512
--- /dev/null
+++ b/src/fauxton/test/qunit/vendor/qunit.css
@@ -0,0 +1,228 @@
+/**
+ * QUnit 1.2.0pre - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ * Pulled Live from Git Mon Oct 31 14:00:02 UTC 2011
+ * Last Commit: ee156923cdb01820e35e6bb579d5cf6bf55736d4
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+ font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
+ margin: 0;
+ padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+ padding: 0.5em 0 0.5em 1em;
+
+ color: #8699a4;
+ background-color: #0d3349;
+
+ font-size: 1.5em;
+ line-height: 1em;
+ font-weight: normal;
+
+ border-radius: 15px 15px 0 0;
+ -moz-border-radius: 15px 15px 0 0;
+ -webkit-border-top-right-radius: 15px;
+ -webkit-border-top-left-radius: 15px;
+}
+
+#qunit-header a {
+ text-decoration: none;
+ color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+ color: #fff;
+}
+
+#qunit-banner {
+ height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+ padding: 0.5em 0 0.5em 2em;
+ color: #5E740B;
+ background-color: #eee;
+}
+
+#qunit-userAgent {
+ padding: 0.5em 0 0.5em 2.5em;
+ background-color: #2b81af;
+ color: #fff;
+ text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+ list-style-position: inside;
+}
+
+#qunit-tests li {
+ padding: 0.4em 0.5em 0.4em 2.5em;
+ border-bottom: 1px solid #fff;
+ list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
+ display: none;
+}
+
+#qunit-tests li strong {
+ cursor: pointer;
+}
+
+#qunit-tests li a {
+ padding: 0.5em;
+ color: #c2ccd1;
+ text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+ color: #000;
+}
+
+#qunit-tests ol {
+ margin-top: 0.5em;
+ padding: 0.5em;
+
+ background-color: #fff;
+
+ border-radius: 15px;
+ -moz-border-radius: 15px;
+ -webkit-border-radius: 15px;
+
+ box-shadow: inset 0px 2px 13px #999;
+ -moz-box-shadow: inset 0px 2px 13px #999;
+ -webkit-box-shadow: inset 0px 2px 13px #999;
+}
+
+#qunit-tests table {
+ border-collapse: collapse;
+ margin-top: .2em;
+}
+
+#qunit-tests th {
+ text-align: right;
+ vertical-align: top;
+ padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+ vertical-align: top;
+}
+
+#qunit-tests pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+
+#qunit-tests del {
+ background-color: #e0f2be;
+ color: #374e0c;
+ text-decoration: none;
+}
+
+#qunit-tests ins {
+ background-color: #ffcaca;
+ color: #500;
+ text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts { color: black; }
+#qunit-tests b.passed { color: #5E740B; }
+#qunit-tests b.failed { color: #710909; }
+
+#qunit-tests li li {
+ margin: 0.5em;
+ padding: 0.4em 0.5em 0.4em 0.5em;
+ background-color: #fff;
+ border-bottom: none;
+ list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+ color: #5E740B;
+ background-color: #fff;
+ border-left: 26px solid #C6E746;
+}
+
+#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected { color: #999999; }
+
+#qunit-banner.qunit-pass { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+ color: #710909;
+ background-color: #fff;
+ border-left: 26px solid #EE5757;
+ white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+ border-radius: 0 0 15px 15px;
+ -moz-border-radius: 0 0 15px 15px;
+ -webkit-border-bottom-right-radius: 15px;
+ -webkit-border-bottom-left-radius: 15px;
+}
+
+#qunit-tests .fail { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name { color: #000000; }
+
+#qunit-tests .fail .test-actual { color: #EE5757; }
+#qunit-tests .fail .test-expected { color: green; }
+
+#qunit-banner.qunit-fail { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+ padding: 0.5em 0.5em 0.5em 2.5em;
+
+ color: #2b81af;
+ background-color: #D2E0E6;
+
+ border-bottom: 1px solid white;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+}
diff --git a/src/fauxton/test/qunit/vendor/qunit.js b/src/fauxton/test/qunit/vendor/qunit.js
new file mode 100644
index 000000000..a6339f473
--- /dev/null
+++ b/src/fauxton/test/qunit/vendor/qunit.js
@@ -0,0 +1,1589 @@
+/**
+ * QUnit 1.2.0pre - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ * Pulled Live from Git Mon Oct 31 14:00:02 UTC 2011
+ * Last Commit: ee156923cdb01820e35e6bb579d5cf6bf55736d4
+ */
+
+(function(window) {
+
+var defined = {
+ setTimeout: typeof window.setTimeout !== "undefined",
+ sessionStorage: (function() {
+ try {
+ return !!sessionStorage.getItem;
+ } catch(e) {
+ return false;
+ }
+ })()
+};
+
+var testId = 0,
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty;
+
+var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
+ this.name = name;
+ this.testName = testName;
+ this.expected = expected;
+ this.testEnvironmentArg = testEnvironmentArg;
+ this.async = async;
+ this.callback = callback;
+ this.assertions = [];
+};
+Test.prototype = {
+ init: function() {
+ var tests = id("qunit-tests");
+ if (tests) {
+ var b = document.createElement("strong");
+ b.innerHTML = "Running " + this.name;
+ var li = document.createElement("li");
+ li.appendChild( b );
+ li.className = "running";
+ li.id = this.id = "test-output" + testId++;
+ tests.appendChild( li );
+ }
+ },
+ setup: function() {
+ if (this.module != config.previousModule) {
+ if ( config.previousModule ) {
+ runLoggingCallbacks('moduleDone', QUnit, {
+ name: config.previousModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ } );
+ }
+ config.previousModule = this.module;
+ config.moduleStats = { all: 0, bad: 0 };
+ runLoggingCallbacks( 'moduleStart', QUnit, {
+ name: this.module
+ } );
+ }
+
+ config.current = this;
+ this.testEnvironment = extend({
+ setup: function() {},
+ teardown: function() {}
+ }, this.moduleTestEnvironment);
+ if (this.testEnvironmentArg) {
+ extend(this.testEnvironment, this.testEnvironmentArg);
+ }
+
+ runLoggingCallbacks( 'testStart', QUnit, {
+ name: this.testName,
+ module: this.module
+ });
+
+ // allow utility functions to access the current test environment
+ // TODO why??
+ QUnit.current_testEnvironment = this.testEnvironment;
+
+ try {
+ if ( !config.pollution ) {
+ saveGlobal();
+ }
+
+ this.testEnvironment.setup.call(this.testEnvironment);
+ } catch(e) {
+ QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
+ }
+ },
+ run: function() {
+ config.current = this;
+ if ( this.async ) {
+ QUnit.stop();
+ }
+
+ if ( config.notrycatch ) {
+ this.callback.call(this.testEnvironment);
+ return;
+ }
+ try {
+ this.callback.call(this.testEnvironment);
+ } catch(e) {
+ fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
+ QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
+ // else next test will carry the responsibility
+ saveGlobal();
+
+ // Restart the tests if they're blocking
+ if ( config.blocking ) {
+ QUnit.start();
+ }
+ }
+ },
+ teardown: function() {
+ config.current = this;
+ try {
+ this.testEnvironment.teardown.call(this.testEnvironment);
+ checkPollution();
+ } catch(e) {
+ QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
+ }
+ },
+ finish: function() {
+ config.current = this;
+ if ( this.expected != null && this.expected != this.assertions.length ) {
+ QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
+ }
+
+ var good = 0, bad = 0,
+ tests = id("qunit-tests");
+
+ config.stats.all += this.assertions.length;
+ config.moduleStats.all += this.assertions.length;
+
+ if ( tests ) {
+ var ol = document.createElement("ol");
+
+ for ( var i = 0; i < this.assertions.length; i++ ) {
+ var assertion = this.assertions[i];
+
+ var li = document.createElement("li");
+ li.className = assertion.result ? "pass" : "fail";
+ li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
+ ol.appendChild( li );
+
+ if ( assertion.result ) {
+ good++;
+ } else {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+
+ // store result when possible
+ if ( QUnit.config.reorder && defined.sessionStorage ) {
+ if (bad) {
+ sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
+ } else {
+ sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
+ }
+ }
+
+ if (bad == 0) {
+ ol.style.display = "none";
+ }
+
+ var b = document.createElement("strong");
+ b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
+
+ var a = document.createElement("a");
+ a.innerHTML = "Rerun";
+ a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
+
+ addEvent(b, "click", function() {
+ var next = b.nextSibling.nextSibling,
+ display = next.style.display;
+ next.style.display = display === "none" ? "block" : "none";
+ });
+
+ addEvent(b, "dblclick", function(e) {
+ var target = e && e.target ? e.target : window.event.srcElement;
+ if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
+ target = target.parentNode;
+ }
+ if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+ window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
+ }
+ });
+
+ var li = id(this.id);
+ li.className = bad ? "fail" : "pass";
+ li.removeChild( li.firstChild );
+ li.appendChild( b );
+ li.appendChild( a );
+ li.appendChild( ol );
+
+ } else {
+ for ( var i = 0; i < this.assertions.length; i++ ) {
+ if ( !this.assertions[i].result ) {
+ bad++;
+ config.stats.bad++;
+ config.moduleStats.bad++;
+ }
+ }
+ }
+
+ try {
+ QUnit.reset();
+ } catch(e) {
+ fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
+ }
+
+ runLoggingCallbacks( 'testDone', QUnit, {
+ name: this.testName,
+ module: this.module,
+ failed: bad,
+ passed: this.assertions.length - bad,
+ total: this.assertions.length
+ } );
+ },
+
+ queue: function() {
+ var test = this;
+ synchronize(function() {
+ test.init();
+ });
+ function run() {
+ // each of these can by async
+ synchronize(function() {
+ test.setup();
+ });
+ synchronize(function() {
+ test.run();
+ });
+ synchronize(function() {
+ test.teardown();
+ });
+ synchronize(function() {
+ test.finish();
+ });
+ }
+ // defer when previous test run passed, if storage is available
+ var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
+ if (bad) {
+ run();
+ } else {
+ synchronize(run, true);
+ };
+ }
+
+};
+
+var QUnit = {
+
+ // call on start of module test to prepend name to all tests
+ module: function(name, testEnvironment) {
+ config.currentModule = name;
+ config.currentModuleTestEnviroment = testEnvironment;
+ },
+
+ asyncTest: function(testName, expected, callback) {
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+
+ QUnit.test(testName, expected, callback, true);
+ },
+
+ test: function(testName, expected, callback, async) {
+ var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
+
+ if ( arguments.length === 2 ) {
+ callback = expected;
+ expected = null;
+ }
+ // is 2nd argument a testEnvironment?
+ if ( expected && typeof expected === 'object') {
+ testEnvironmentArg = expected;
+ expected = null;
+ }
+
+ if ( config.currentModule ) {
+ name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
+ }
+
+ if ( !validTest(config.currentModule + ": " + testName) ) {
+ return;
+ }
+
+ var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
+ test.module = config.currentModule;
+ test.moduleTestEnvironment = config.currentModuleTestEnviroment;
+ test.queue();
+ },
+
+ /**
+ * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
+ */
+ expect: function(asserts) {
+ config.current.expected = asserts;
+ },
+
+ /**
+ * Asserts true.
+ * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+ */
+ ok: function(a, msg) {
+ a = !!a;
+ var details = {
+ result: a,
+ message: msg
+ };
+ msg = escapeInnerText(msg);
+ runLoggingCallbacks( 'log', QUnit, details );
+ config.current.assertions.push({
+ result: a,
+ message: msg
+ });
+ },
+
+ /**
+ * Checks that the first two arguments are equal, with an optional message.
+ * Prints out both actual and expected values.
+ *
+ * Prefered to ok( actual == expected, message )
+ *
+ * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
+ *
+ * @param Object actual
+ * @param Object expected
+ * @param String message (optional)
+ */
+ equal: function(actual, expected, message) {
+ QUnit.push(expected == actual, actual, expected, message);
+ },
+
+ notEqual: function(actual, expected, message) {
+ QUnit.push(expected != actual, actual, expected, message);
+ },
+
+ deepEqual: function(actual, expected, message) {
+ QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
+ },
+
+ notDeepEqual: function(actual, expected, message) {
+ QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
+ },
+
+ strictEqual: function(actual, expected, message) {
+ QUnit.push(expected === actual, actual, expected, message);
+ },
+
+ notStrictEqual: function(actual, expected, message) {
+ QUnit.push(expected !== actual, actual, expected, message);
+ },
+
+ raises: function(block, expected, message) {
+ var actual, ok = false;
+
+ if (typeof expected === 'string') {
+ message = expected;
+ expected = null;
+ }
+
+ try {
+ block();
+ } catch (e) {
+ actual = e;
+ }
+
+ if (actual) {
+ // we don't want to validate thrown error
+ if (!expected) {
+ ok = true;
+ // expected is a regexp
+ } else if (QUnit.objectType(expected) === "regexp") {
+ ok = expected.test(actual);
+ // expected is a constructor
+ } else if (actual instanceof expected) {
+ ok = true;
+ // expected is a validation function which returns true is validation passed
+ } else if (expected.call({}, actual) === true) {
+ ok = true;
+ }
+ }
+
+ QUnit.ok(ok, message);
+ },
+
+ start: function(count) {
+ config.semaphore -= count || 1;
+ if (config.semaphore > 0) {
+ // don't start until equal number of stop-calls
+ return;
+ }
+ if (config.semaphore < 0) {
+ // ignore if start is called more often then stop
+ config.semaphore = 0;
+ }
+ // A slight delay, to avoid any current callbacks
+ if ( defined.setTimeout ) {
+ window.setTimeout(function() {
+ if (config.semaphore > 0) {
+ return;
+ }
+ if ( config.timeout ) {
+ clearTimeout(config.timeout);
+ }
+
+ config.blocking = false;
+ process(true);
+ }, 13);
+ } else {
+ config.blocking = false;
+ process(true);
+ }
+ },
+
+ stop: function(count) {
+ config.semaphore += count || 1;
+ config.blocking = true;
+
+ if ( config.testTimeout && defined.setTimeout ) {
+ clearTimeout(config.timeout);
+ config.timeout = window.setTimeout(function() {
+ QUnit.ok( false, "Test timed out" );
+ config.semaphore = 1;
+ QUnit.start();
+ }, config.testTimeout);
+ }
+ }
+};
+
+//We want access to the constructor's prototype
+(function() {
+ function F(){};
+ F.prototype = QUnit;
+ QUnit = new F();
+ //Make F QUnit's constructor so that we can add to the prototype later
+ QUnit.constructor = F;
+})();
+
+// Backwards compatibility, deprecated
+QUnit.equals = QUnit.equal;
+QUnit.same = QUnit.deepEqual;
+
+// Maintain internal state
+var config = {
+ // The queue of tests to run
+ queue: [],
+
+ // block until document ready
+ blocking: true,
+
+ // when enabled, show only failing tests
+ // gets persisted through sessionStorage and can be changed in UI via checkbox
+ hidepassed: false,
+
+ // by default, run previously failed tests first
+ // very useful in combination with "Hide passed tests" checked
+ reorder: true,
+
+ // by default, modify document.title when suite is done
+ altertitle: true,
+
+ urlConfig: ['noglobals', 'notrycatch'],
+
+ //logging callback queues
+ begin: [],
+ done: [],
+ log: [],
+ testStart: [],
+ testDone: [],
+ moduleStart: [],
+ moduleDone: []
+};
+
+// Load paramaters
+(function() {
+ var location = window.location || { search: "", protocol: "file:" },
+ params = location.search.slice( 1 ).split( "&" ),
+ length = params.length,
+ urlParams = {},
+ current;
+
+ if ( params[ 0 ] ) {
+ for ( var i = 0; i < length; i++ ) {
+ current = params[ i ].split( "=" );
+ current[ 0 ] = decodeURIComponent( current[ 0 ] );
+ // allow just a key to turn on a flag, e.g., test.html?noglobals
+ current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+ urlParams[ current[ 0 ] ] = current[ 1 ];
+ }
+ }
+
+ QUnit.urlParams = urlParams;
+ config.filter = urlParams.filter;
+
+ // Figure out if we're running the tests from a server or not
+ QUnit.isLocal = !!(location.protocol === 'file:');
+})();
+
+// Expose the API as global variables, unless an 'exports'
+// object exists, in that case we assume we're in CommonJS
+if ( typeof exports === "undefined" || typeof require === "undefined" ) {
+ extend(window, QUnit);
+ window.QUnit = QUnit;
+} else {
+ extend(exports, QUnit);
+ exports.QUnit = QUnit;
+}
+
+// define these after exposing globals to keep them in these QUnit namespace only
+extend(QUnit, {
+ config: config,
+
+ // Initialize the configuration options
+ init: function() {
+ extend(config, {
+ stats: { all: 0, bad: 0 },
+ moduleStats: { all: 0, bad: 0 },
+ started: +new Date,
+ updateRate: 1000,
+ blocking: false,
+ autostart: true,
+ autorun: false,
+ filter: "",
+ queue: [],
+ semaphore: 0
+ });
+
+ var tests = id( "qunit-tests" ),
+ banner = id( "qunit-banner" ),
+ result = id( "qunit-testresult" );
+
+ if ( tests ) {
+ tests.innerHTML = "";
+ }
+
+ if ( banner ) {
+ banner.className = "";
+ }
+
+ if ( result ) {
+ result.parentNode.removeChild( result );
+ }
+
+ if ( tests ) {
+ result = document.createElement( "p" );
+ result.id = "qunit-testresult";
+ result.className = "result";
+ tests.parentNode.insertBefore( result, tests );
+ result.innerHTML = 'Running...<br/>&nbsp;';
+ }
+ },
+
+ /**
+ * Resets the test setup. Useful for tests that modify the DOM.
+ *
+ * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
+ */
+ reset: function() {
+ if ( window.jQuery ) {
+ jQuery( "#qunit-fixture" ).html( config.fixture );
+ } else {
+ var main = id( 'qunit-fixture' );
+ if ( main ) {
+ main.innerHTML = config.fixture;
+ }
+ }
+ },
+
+ /**
+ * Trigger an event on an element.
+ *
+ * @example triggerEvent( document.body, "click" );
+ *
+ * @param DOMElement elem
+ * @param String type
+ */
+ triggerEvent: function( elem, type, event ) {
+ if ( document.createEvent ) {
+ event = document.createEvent("MouseEvents");
+ event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ elem.dispatchEvent( event );
+
+ } else if ( elem.fireEvent ) {
+ elem.fireEvent("on"+type);
+ }
+ },
+
+ // Safe object type checking
+ is: function( type, obj ) {
+ return QUnit.objectType( obj ) == type;
+ },
+
+ objectType: function( obj ) {
+ if (typeof obj === "undefined") {
+ return "undefined";
+
+ // consider: typeof null === object
+ }
+ if (obj === null) {
+ return "null";
+ }
+
+ var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || '';
+
+ switch (type) {
+ case 'Number':
+ if (isNaN(obj)) {
+ return "nan";
+ } else {
+ return "number";
+ }
+ case 'String':
+ case 'Boolean':
+ case 'Array':
+ case 'Date':
+ case 'RegExp':
+ case 'Function':
+ return type.toLowerCase();
+ }
+ if (typeof obj === "object") {
+ return "object";
+ }
+ return undefined;
+ },
+
+ push: function(result, actual, expected, message) {
+ var details = {
+ result: result,
+ message: message,
+ actual: actual,
+ expected: expected
+ };
+
+ message = escapeInnerText(message) || (result ? "okay" : "failed");
+ message = '<span class="test-message">' + message + "</span>";
+ expected = escapeInnerText(QUnit.jsDump.parse(expected));
+ actual = escapeInnerText(QUnit.jsDump.parse(actual));
+ var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
+ if (actual != expected) {
+ output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
+ output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
+ }
+ if (!result) {
+ var source = sourceFromStacktrace();
+ if (source) {
+ details.source = source;
+ output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>';
+ }
+ }
+ output += "</table>";
+
+ runLoggingCallbacks( 'log', QUnit, details );
+
+ config.current.assertions.push({
+ result: !!result,
+ message: output
+ });
+ },
+
+ url: function( params ) {
+ params = extend( extend( {}, QUnit.urlParams ), params );
+ var querystring = "?",
+ key;
+ for ( key in params ) {
+ if ( !hasOwn.call( params, key ) ) {
+ continue;
+ }
+ querystring += encodeURIComponent( key ) + "=" +
+ encodeURIComponent( params[ key ] ) + "&";
+ }
+ return window.location.pathname + querystring.slice( 0, -1 );
+ },
+
+ extend: extend,
+ id: id,
+ addEvent: addEvent
+});
+
+//QUnit.constructor is set to the empty F() above so that we can add to it's prototype later
+//Doing this allows us to tell if the following methods have been overwritten on the actual
+//QUnit object, which is a deprecated way of using the callbacks.
+extend(QUnit.constructor.prototype, {
+ // Logging callbacks; all receive a single argument with the listed properties
+ // run test/logs.html for any related changes
+ begin: registerLoggingCallback('begin'),
+ // done: { failed, passed, total, runtime }
+ done: registerLoggingCallback('done'),
+ // log: { result, actual, expected, message }
+ log: registerLoggingCallback('log'),
+ // testStart: { name }
+ testStart: registerLoggingCallback('testStart'),
+ // testDone: { name, failed, passed, total }
+ testDone: registerLoggingCallback('testDone'),
+ // moduleStart: { name }
+ moduleStart: registerLoggingCallback('moduleStart'),
+ // moduleDone: { name, failed, passed, total }
+ moduleDone: registerLoggingCallback('moduleDone')
+});
+
+if ( typeof document === "undefined" || document.readyState === "complete" ) {
+ config.autorun = true;
+}
+
+QUnit.load = function() {
+ runLoggingCallbacks( 'begin', QUnit, {} );
+
+ // Initialize the config, saving the execution queue
+ var oldconfig = extend({}, config);
+ QUnit.init();
+ extend(config, oldconfig);
+
+ config.blocking = false;
+
+ var urlConfigHtml = '', len = config.urlConfig.length;
+ for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) {
+ config[val] = QUnit.urlParams[val];
+ urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + ( config[val] ? ' checked="checked"' : '' ) + '>' + val + '</label>';
+ }
+
+ var userAgent = id("qunit-userAgent");
+ if ( userAgent ) {
+ userAgent.innerHTML = navigator.userAgent;
+ }
+ var banner = id("qunit-header");
+ if ( banner ) {
+ banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml;
+ addEvent( banner, "change", function( event ) {
+ var params = {};
+ params[ event.target.name ] = event.target.checked ? true : undefined;
+ window.location = QUnit.url( params );
+ });
+ }
+
+ var toolbar = id("qunit-testrunner-toolbar");
+ if ( toolbar ) {
+ var filter = document.createElement("input");
+ filter.type = "checkbox";
+ filter.id = "qunit-filter-pass";
+ addEvent( filter, "click", function() {
+ var ol = document.getElementById("qunit-tests");
+ if ( filter.checked ) {
+ ol.className = ol.className + " hidepass";
+ } else {
+ var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+ ol.className = tmp.replace(/ hidepass /, " ");
+ }
+ if ( defined.sessionStorage ) {
+ if (filter.checked) {
+ sessionStorage.setItem("qunit-filter-passed-tests", "true");
+ } else {
+ sessionStorage.removeItem("qunit-filter-passed-tests");
+ }
+ }
+ });
+ if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
+ filter.checked = true;
+ var ol = document.getElementById("qunit-tests");
+ ol.className = ol.className + " hidepass";
+ }
+ toolbar.appendChild( filter );
+
+ var label = document.createElement("label");
+ label.setAttribute("for", "qunit-filter-pass");
+ label.innerHTML = "Hide passed tests";
+ toolbar.appendChild( label );
+ }
+
+ var main = id('qunit-fixture');
+ if ( main ) {
+ config.fixture = main.innerHTML;
+ }
+
+ if (config.autostart) {
+ QUnit.start();
+ }
+};
+
+addEvent(window, "load", QUnit.load);
+
+// addEvent(window, "error") gives us a useless event object
+window.onerror = function( message, file, line ) {
+ if ( QUnit.config.current ) {
+ ok( false, message + ", " + file + ":" + line );
+ } else {
+ test( "global failure", function() {
+ ok( false, message + ", " + file + ":" + line );
+ });
+ }
+};
+
+function done() {
+ config.autorun = true;
+
+ // Log the last module results
+ if ( config.currentModule ) {
+ runLoggingCallbacks( 'moduleDone', QUnit, {
+ name: config.currentModule,
+ failed: config.moduleStats.bad,
+ passed: config.moduleStats.all - config.moduleStats.bad,
+ total: config.moduleStats.all
+ } );
+ }
+
+ var banner = id("qunit-banner"),
+ tests = id("qunit-tests"),
+ runtime = +new Date - config.started,
+ passed = config.stats.all - config.stats.bad,
+ html = [
+ 'Tests completed in ',
+ runtime,
+ ' milliseconds.<br/>',
+ '<span class="passed">',
+ passed,
+ '</span> tests of <span class="total">',
+ config.stats.all,
+ '</span> passed, <span class="failed">',
+ config.stats.bad,
+ '</span> failed.'
+ ].join('');
+
+ if ( banner ) {
+ banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
+ }
+
+ if ( tests ) {
+ id( "qunit-testresult" ).innerHTML = html;
+ }
+
+ if ( config.altertitle && typeof document !== "undefined" && document.title ) {
+ // show ✖ for good, ✔ for bad suite result in title
+ // use escape sequences in case file gets loaded with non-utf-8-charset
+ document.title = [
+ (config.stats.bad ? "\u2716" : "\u2714"),
+ document.title.replace(/^[\u2714\u2716] /i, "")
+ ].join(" ");
+ }
+
+ runLoggingCallbacks( 'done', QUnit, {
+ failed: config.stats.bad,
+ passed: passed,
+ total: config.stats.all,
+ runtime: runtime
+ } );
+}
+
+function validTest( name ) {
+ var filter = config.filter,
+ run = false;
+
+ if ( !filter ) {
+ return true;
+ }
+
+ var not = filter.charAt( 0 ) === "!";
+ if ( not ) {
+ filter = filter.slice( 1 );
+ }
+
+ if ( name.indexOf( filter ) !== -1 ) {
+ return !not;
+ }
+
+ if ( not ) {
+ run = true;
+ }
+
+ return run;
+}
+
+// so far supports only Firefox, Chrome and Opera (buggy)
+// could be extended in the future to use something like https://github.com/csnover/TraceKit
+function sourceFromStacktrace() {
+ try {
+ throw new Error();
+ } catch ( e ) {
+ if (e.stacktrace) {
+ // Opera
+ return e.stacktrace.split("\n")[6];
+ } else if (e.stack) {
+ // Firefox, Chrome
+ return e.stack.split("\n")[4];
+ } else if (e.sourceURL) {
+ // Safari, PhantomJS
+ // TODO sourceURL points at the 'throw new Error' line above, useless
+ //return e.sourceURL + ":" + e.line;
+ }
+ }
+}
+
+function escapeInnerText(s) {
+ if (!s) {
+ return "";
+ }
+ s = s + "";
+ return s.replace(/[\&<>]/g, function(s) {
+ switch(s) {
+ case "&": return "&amp;";
+ case "<": return "&lt;";
+ case ">": return "&gt;";
+ default: return s;
+ }
+ });
+}
+
+function synchronize( callback, last ) {
+ config.queue.push( callback );
+
+ if ( config.autorun && !config.blocking ) {
+ process(last);
+ }
+}
+
+function process( last ) {
+ var start = new Date().getTime();
+ config.depth = config.depth ? config.depth + 1 : 1;
+
+ while ( config.queue.length && !config.blocking ) {
+ if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
+ config.queue.shift()();
+ } else {
+ window.setTimeout( function(){
+ process( last );
+ }, 13 );
+ break;
+ }
+ }
+ config.depth--;
+ if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+ done();
+ }
+}
+
+function saveGlobal() {
+ config.pollution = [];
+
+ if ( config.noglobals ) {
+ for ( var key in window ) {
+ if ( !hasOwn.call( window, key ) ) {
+ continue;
+ }
+ config.pollution.push( key );
+ }
+ }
+}
+
+function checkPollution( name ) {
+ var old = config.pollution;
+ saveGlobal();
+
+ var newGlobals = diff( config.pollution, old );
+ if ( newGlobals.length > 0 ) {
+ ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
+ }
+
+ var deletedGlobals = diff( old, config.pollution );
+ if ( deletedGlobals.length > 0 ) {
+ ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
+ }
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+ var result = a.slice();
+ for ( var i = 0; i < result.length; i++ ) {
+ for ( var j = 0; j < b.length; j++ ) {
+ if ( result[i] === b[j] ) {
+ result.splice(i, 1);
+ i--;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+function fail(message, exception, callback) {
+ if ( typeof console !== "undefined" && console.error && console.warn ) {
+ console.error(message);
+ console.error(exception);
+ console.warn(callback.toString());
+
+ } else if ( window.opera && opera.postError ) {
+ opera.postError(message, exception, callback.toString);
+ }
+}
+
+function extend(a, b) {
+ for ( var prop in b ) {
+ if ( b[prop] === undefined ) {
+ delete a[prop];
+
+ // Avoid "Member not found" error in IE8 caused by setting window.constructor
+ } else if ( prop !== "constructor" || a !== window ) {
+ a[prop] = b[prop];
+ }
+ }
+
+ return a;
+}
+
+function addEvent(elem, type, fn) {
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, fn, false );
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, fn );
+ } else {
+ fn();
+ }
+}
+
+function id(name) {
+ return !!(typeof document !== "undefined" && document && document.getElementById) &&
+ document.getElementById( name );
+}
+
+function registerLoggingCallback(key){
+ return function(callback){
+ config[key].push( callback );
+ };
+}
+
+// Supports deprecated method of completely overwriting logging callbacks
+function runLoggingCallbacks(key, scope, args) {
+ //debugger;
+ var callbacks;
+ if ( QUnit.hasOwnProperty(key) ) {
+ QUnit[key].call(scope, args);
+ } else {
+ callbacks = config[key];
+ for( var i = 0; i < callbacks.length; i++ ) {
+ callbacks[i].call( scope, args );
+ }
+ }
+}
+
+// Test for equality any JavaScript type.
+// Author: Philippe Rathé <prathe@gmail.com>
+QUnit.equiv = function () {
+
+ var innerEquiv; // the real equiv function
+ var callers = []; // stack to decide between skip/abort functions
+ var parents = []; // stack to avoiding loops from circular referencing
+
+ // Call the o related callback with the given arguments.
+ function bindCallbacks(o, callbacks, args) {
+ var prop = QUnit.objectType(o);
+ if (prop) {
+ if (QUnit.objectType(callbacks[prop]) === "function") {
+ return callbacks[prop].apply(callbacks, args);
+ } else {
+ return callbacks[prop]; // or undefined
+ }
+ }
+ }
+
+ var callbacks = function () {
+
+ // for string, boolean, number and null
+ function useStrictEquality(b, a) {
+ if (b instanceof a.constructor || a instanceof b.constructor) {
+ // to catch short annotaion VS 'new' annotation of a
+ // declaration
+ // e.g. var i = 1;
+ // var j = new Number(1);
+ return a == b;
+ } else {
+ return a === b;
+ }
+ }
+
+ return {
+ "string" : useStrictEquality,
+ "boolean" : useStrictEquality,
+ "number" : useStrictEquality,
+ "null" : useStrictEquality,
+ "undefined" : useStrictEquality,
+
+ "nan" : function(b) {
+ return isNaN(b);
+ },
+
+ "date" : function(b, a) {
+ return QUnit.objectType(b) === "date"
+ && a.valueOf() === b.valueOf();
+ },
+
+ "regexp" : function(b, a) {
+ return QUnit.objectType(b) === "regexp"
+ && a.source === b.source && // the regex itself
+ a.global === b.global && // and its modifers
+ // (gmi) ...
+ a.ignoreCase === b.ignoreCase
+ && a.multiline === b.multiline;
+ },
+
+ // - skip when the property is a method of an instance (OOP)
+ // - abort otherwise,
+ // initial === would have catch identical references anyway
+ "function" : function() {
+ var caller = callers[callers.length - 1];
+ return caller !== Object && typeof caller !== "undefined";
+ },
+
+ "array" : function(b, a) {
+ var i, j, loop;
+ var len;
+
+ // b could be an object literal here
+ if (!(QUnit.objectType(b) === "array")) {
+ return false;
+ }
+
+ len = a.length;
+ if (len !== b.length) { // safe and faster
+ return false;
+ }
+
+ // track reference to avoid circular references
+ parents.push(a);
+ for (i = 0; i < len; i++) {
+ loop = false;
+ for (j = 0; j < parents.length; j++) {
+ if (parents[j] === a[i]) {
+ loop = true;// dont rewalk array
+ }
+ }
+ if (!loop && !innerEquiv(a[i], b[i])) {
+ parents.pop();
+ return false;
+ }
+ }
+ parents.pop();
+ return true;
+ },
+
+ "object" : function(b, a) {
+ var i, j, loop;
+ var eq = true; // unless we can proove it
+ var aProperties = [], bProperties = []; // collection of
+ // strings
+
+ // comparing constructors is more strict than using
+ // instanceof
+ if (a.constructor !== b.constructor) {
+ return false;
+ }
+
+ // stack constructor before traversing properties
+ callers.push(a.constructor);
+ // track reference to avoid circular references
+ parents.push(a);
+
+ for (i in a) { // be strict: don't ensures hasOwnProperty
+ // and go deep
+ loop = false;
+ for (j = 0; j < parents.length; j++) {
+ if (parents[j] === a[i])
+ loop = true; // don't go down the same path
+ // twice
+ }
+ aProperties.push(i); // collect a's properties
+
+ if (!loop && !innerEquiv(a[i], b[i])) {
+ eq = false;
+ break;
+ }
+ }
+
+ callers.pop(); // unstack, we are done
+ parents.pop();
+
+ for (i in b) {
+ bProperties.push(i); // collect b's properties
+ }
+
+ // Ensures identical properties name
+ return eq
+ && innerEquiv(aProperties.sort(), bProperties
+ .sort());
+ }
+ };
+ }();
+
+ innerEquiv = function() { // can take multiple arguments
+ var args = Array.prototype.slice.apply(arguments);
+ if (args.length < 2) {
+ return true; // end transition
+ }
+
+ return (function(a, b) {
+ if (a === b) {
+ return true; // catch the most you can
+ } else if (a === null || b === null || typeof a === "undefined"
+ || typeof b === "undefined"
+ || QUnit.objectType(a) !== QUnit.objectType(b)) {
+ return false; // don't lose time with error prone cases
+ } else {
+ return bindCallbacks(a, callbacks, [ b, a ]);
+ }
+
+ // apply transition with (1..n) arguments
+ })(args[0], args[1])
+ && arguments.callee.apply(this, args.splice(1,
+ args.length - 1));
+ };
+
+ return innerEquiv;
+
+}();
+
+/**
+ * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
+ * http://flesler.blogspot.com Licensed under BSD
+ * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
+ *
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+ function quote( str ) {
+ return '"' + str.toString().replace(/"/g, '\\"') + '"';
+ };
+ function literal( o ) {
+ return o + '';
+ };
+ function join( pre, arr, post ) {
+ var s = jsDump.separator(),
+ base = jsDump.indent(),
+ inner = jsDump.indent(1);
+ if ( arr.join )
+ arr = arr.join( ',' + s + inner );
+ if ( !arr )
+ return pre + post;
+ return [ pre, inner + arr, base + post ].join(s);
+ };
+ function array( arr, stack ) {
+ var i = arr.length, ret = Array(i);
+ this.up();
+ while ( i-- )
+ ret[i] = this.parse( arr[i] , undefined , stack);
+ this.down();
+ return join( '[', ret, ']' );
+ };
+
+ var reName = /^function (\w+)/;
+
+ var jsDump = {
+ parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
+ stack = stack || [ ];
+ var parser = this.parsers[ type || this.typeOf(obj) ];
+ type = typeof parser;
+ var inStack = inArray(obj, stack);
+ if (inStack != -1) {
+ return 'recursion('+(inStack - stack.length)+')';
+ }
+ //else
+ if (type == 'function') {
+ stack.push(obj);
+ var res = parser.call( this, obj, stack );
+ stack.pop();
+ return res;
+ }
+ // else
+ return (type == 'string') ? parser : this.parsers.error;
+ },
+ typeOf:function( obj ) {
+ var type;
+ if ( obj === null ) {
+ type = "null";
+ } else if (typeof obj === "undefined") {
+ type = "undefined";
+ } else if (QUnit.is("RegExp", obj)) {
+ type = "regexp";
+ } else if (QUnit.is("Date", obj)) {
+ type = "date";
+ } else if (QUnit.is("Function", obj)) {
+ type = "function";
+ } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
+ type = "window";
+ } else if (obj.nodeType === 9) {
+ type = "document";
+ } else if (obj.nodeType) {
+ type = "node";
+ } else if (
+ // native arrays
+ toString.call( obj ) === "[object Array]" ||
+ // NodeList objects
+ ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
+ ) {
+ type = "array";
+ } else {
+ type = typeof obj;
+ }
+ return type;
+ },
+ separator:function() {
+ return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
+ },
+ indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
+ if ( !this.multiline )
+ return '';
+ var chr = this.indentChar;
+ if ( this.HTML )
+ chr = chr.replace(/\t/g,' ').replace(/ /g,'&nbsp;');
+ return Array( this._depth_ + (extra||0) ).join(chr);
+ },
+ up:function( a ) {
+ this._depth_ += a || 1;
+ },
+ down:function( a ) {
+ this._depth_ -= a || 1;
+ },
+ setParser:function( name, parser ) {
+ this.parsers[name] = parser;
+ },
+ // The next 3 are exposed so you can use them
+ quote:quote,
+ literal:literal,
+ join:join,
+ //
+ _depth_: 1,
+ // This is the list of parsers, to modify them, use jsDump.setParser
+ parsers:{
+ window: '[Window]',
+ document: '[Document]',
+ error:'[ERROR]', //when no parser is found, shouldn't happen
+ unknown: '[Unknown]',
+ 'null':'null',
+ 'undefined':'undefined',
+ 'function':function( fn ) {
+ var ret = 'function',
+ name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
+ if ( name )
+ ret += ' ' + name;
+ ret += '(';
+
+ ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
+ return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
+ },
+ array: array,
+ nodelist: array,
+ arguments: array,
+ object:function( map, stack ) {
+ var ret = [ ];
+ QUnit.jsDump.up();
+ for ( var key in map ) {
+ var val = map[key];
+ ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack));
+ }
+ QUnit.jsDump.down();
+ return join( '{', ret, '}' );
+ },
+ node:function( node ) {
+ var open = QUnit.jsDump.HTML ? '&lt;' : '<',
+ close = QUnit.jsDump.HTML ? '&gt;' : '>';
+
+ var tag = node.nodeName.toLowerCase(),
+ ret = open + tag;
+
+ for ( var a in QUnit.jsDump.DOMAttrs ) {
+ var val = node[QUnit.jsDump.DOMAttrs[a]];
+ if ( val )
+ ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
+ }
+ return ret + close + open + '/' + tag + close;
+ },
+ functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
+ var l = fn.length;
+ if ( !l ) return '';
+
+ var args = Array(l);
+ while ( l-- )
+ args[l] = String.fromCharCode(97+l);//97 is 'a'
+ return ' ' + args.join(', ') + ' ';
+ },
+ key:quote, //object calls it internally, the key part of an item in a map
+ functionCode:'[code]', //function calls it internally, it's the content of the function
+ attribute:quote, //node calls it internally, it's an html attribute value
+ string:quote,
+ date:quote,
+ regexp:literal, //regex
+ number:literal,
+ 'boolean':literal
+ },
+ DOMAttrs:{//attributes to dump from nodes, name=>realName
+ id:'id',
+ name:'name',
+ 'class':'className'
+ },
+ HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
+ indentChar:' ',//indentation unit
+ multiline:true //if true, items in a collection, are separated by a \n, else just a space.
+ };
+
+ return jsDump;
+})();
+
+// from Sizzle.js
+function getText( elems ) {
+ var ret = "", elem;
+
+ for ( var i = 0; elems[i]; i++ ) {
+ elem = elems[i];
+
+ // Get the text from text nodes and CDATA nodes
+ if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+ ret += elem.nodeValue;
+
+ // Traverse everything else, except comment nodes
+ } else if ( elem.nodeType !== 8 ) {
+ ret += getText( elem.childNodes );
+ }
+ }
+
+ return ret;
+};
+
+//from jquery.js
+function inArray( elem, array ) {
+ if ( array.indexOf ) {
+ return array.indexOf( elem );
+ }
+
+ for ( var i = 0, length = array.length; i < length; i++ ) {
+ if ( array[ i ] === elem ) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * Javascript Diff Algorithm
+ * By John Resig (http://ejohn.org/)
+ * Modified by Chu Alan "sprite"
+ *
+ * Released under the MIT license.
+ *
+ * More Info:
+ * http://ejohn.org/projects/javascript-diff-algorithm/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
+ */
+QUnit.diff = (function() {
+ function diff(o, n) {
+ var ns = {};
+ var os = {};
+
+ for (var i = 0; i < n.length; i++) {
+ if (ns[n[i]] == null)
+ ns[n[i]] = {
+ rows: [],
+ o: null
+ };
+ ns[n[i]].rows.push(i);
+ }
+
+ for (var i = 0; i < o.length; i++) {
+ if (os[o[i]] == null)
+ os[o[i]] = {
+ rows: [],
+ n: null
+ };
+ os[o[i]].rows.push(i);
+ }
+
+ for (var i in ns) {
+ if ( !hasOwn.call( ns, i ) ) {
+ continue;
+ }
+ if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
+ n[ns[i].rows[0]] = {
+ text: n[ns[i].rows[0]],
+ row: os[i].rows[0]
+ };
+ o[os[i].rows[0]] = {
+ text: o[os[i].rows[0]],
+ row: ns[i].rows[0]
+ };
+ }
+ }
+
+ for (var i = 0; i < n.length - 1; i++) {
+ if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
+ n[i + 1] == o[n[i].row + 1]) {
+ n[i + 1] = {
+ text: n[i + 1],
+ row: n[i].row + 1
+ };
+ o[n[i].row + 1] = {
+ text: o[n[i].row + 1],
+ row: i + 1
+ };
+ }
+ }
+
+ for (var i = n.length - 1; i > 0; i--) {
+ if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
+ n[i - 1] == o[n[i].row - 1]) {
+ n[i - 1] = {
+ text: n[i - 1],
+ row: n[i].row - 1
+ };
+ o[n[i].row - 1] = {
+ text: o[n[i].row - 1],
+ row: i - 1
+ };
+ }
+ }
+
+ return {
+ o: o,
+ n: n
+ };
+ }
+
+ return function(o, n) {
+ o = o.replace(/\s+$/, '');
+ n = n.replace(/\s+$/, '');
+ var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
+
+ var str = "";
+
+ var oSpace = o.match(/\s+/g);
+ if (oSpace == null) {
+ oSpace = [" "];
+ }
+ else {
+ oSpace.push(" ");
+ }
+ var nSpace = n.match(/\s+/g);
+ if (nSpace == null) {
+ nSpace = [" "];
+ }
+ else {
+ nSpace.push(" ");
+ }
+
+ if (out.n.length == 0) {
+ for (var i = 0; i < out.o.length; i++) {
+ str += '<del>' + out.o[i] + oSpace[i] + "</del>";
+ }
+ }
+ else {
+ if (out.n[0].text == null) {
+ for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
+ str += '<del>' + out.o[n] + oSpace[n] + "</del>";
+ }
+ }
+
+ for (var i = 0; i < out.n.length; i++) {
+ if (out.n[i].text == null) {
+ str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
+ }
+ else {
+ var pre = "";
+
+ for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
+ pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
+ }
+ str += " " + out.n[i].text + nSpace[i] + pre;
+ }
+ }
+ }
+
+ return str;
+ };
+})();
+
+})(this);
diff --git a/src/fauxton/writing_addons.md b/src/fauxton/writing_addons.md
new file mode 100644
index 000000000..ccd09af3c
--- /dev/null
+++ b/src/fauxton/writing_addons.md
@@ -0,0 +1,163 @@
+# Addons
+Addons allow you to extend Fauxton for a specific use case. Addons will usually
+have the following structure:
+
+ * templates/
+ * my_addon.html - _underscore template fragments_
+ * base.js - _entry point to the addon_
+ * resources.js - _models and collections of the addon_
+ * routes.js - _URL routing for the addon_
+ * views.js - _views that the model provides_
+
+ [optional]
+ * assets/less
+ * my_addon.less
+
+## Generating an addon
+We have a grunt task that lets you create a skeleton addon, including all the
+boiler plate code. Run `bbb addon` and answer the questions it asks to create
+an addon:
+
+ ± bbb addon
+ path.existsSync is now called `fs.existsSync`.
+ Running "addon" task
+
+ Please answer the following:
+ [?] Add on Name (WickedCool) SuperAddon
+ [?] Location of add ons (app/addons)
+ [?] Do you need an assets folder?(for .less) (y/N)
+ [?] Do you need to make any changes to the above before continuing? (y/N)
+
+ Created addon SuperAddon in app/addons
+
+ Done, without errors.
+
+Once the addon is created add the name to the settings.json file to get it compiled
+and added on the next install.
+
+## Routes and hooks
+An addon can insert itself into fauxton in two ways; via a route or via a hook.
+
+### Routes
+An addon will override an existing route should one exist, but in all other
+ways is just a normal backbone route/view. This is how you would add a whole
+new feature.
+
+### Hooks
+Hooks let you modify/extend an existing feature. They modify a DOM element by
+selector for a named set of routes, for example:
+
+ var Search = new FauxtonAPI.addon();
+ Search.hooks = {
+ // Render additional content into the sidebar
+ "#sidebar-content": {
+ routes:[
+ "database/:database/_design/:ddoc/_search/:search",
+ "database/:database/_design/:ddoc/_view/:view",
+ "database/:database/_:handler"],
+ callback: searchSidebar
+ }
+ };
+ return Search;
+
+adds the `searchSidebar` callback to `#sidebar-content` for three routes.
+
+## Hello world addon
+First create the addon skeleton:
+
+ ± bbb addon
+ path.existsSync is now called `fs.existsSync`.
+ Running "addon" task
+
+ Please answer the following:
+ [?] Add on Name (WickedCool) Hello
+ [?] Location of add ons (app/addons)
+ [?] Do you need to make any changes to the above before continuing? (y/N)
+
+ Created addon Hello in app/addons
+
+ Done, without errors.
+
+In `app/addons/hello/templates/hello.html` place:
+
+ <h1>Hello!</h1>
+
+Next, we'll defined a simple view in `resources.js` (for more complex addons
+you may want to have a views.js) that renders that template:
+
+ define([
+ "app",
+ "api"
+ ],
+
+ function (app, FauxtonAPI) {
+ var Resources = {};
+
+ Resources.Hello = FauxtonAPI.View.extend({
+ template: "addons/hello/templates/hello"
+ });
+
+ return Resources;
+ });
+
+
+Then define a route in `routes.js` that the addon is accessible at:
+
+ define([
+ "app",
+ "api",
+ "addons/hello/resources"
+ ],
+
+ function(app, FauxtonAPI, Resources) {
+ var helloRoute = function () {
+ console.log('helloRoute callback yo');
+ return {
+ layout: "one_pane",
+ crumbs: [
+ {"name": "Hello","link": "_hello"}
+ ],
+ views: {
+ "#dashboard-content": new Resources.Hello({})
+ },
+ apiUrl: 'hello'
+ };
+ };
+
+ Routes = {
+ "_hello": helloRoute
+ };
+
+ return Routes;
+ });
+
+
+Then wire it all together in base.js:
+
+ define([
+ "app",
+ "api",
+ "addons/hello/routes"
+ ],
+
+ function(app, FauxtonAPI, HelloRoutes) {
+ var Hello = new FauxtonAPI.addon();
+ console.log('hello from hello');
+
+ Hello.initialize = function() {
+ FauxtonAPI.addHeaderLink({title: "Hello", href: "#_hello"});
+ };
+
+ Hello.Routes = HelloRoutes;
+ console.log(Hello);
+ return Hello;
+ });
+
+Once the code is in place include the add on in your `settings.json` so that it
+gets included by the `require` task. Your addon is included in one of three
+ways; a local path, a git URL or a name. Named plugins assume the plugin is in
+the fauxton base directory, addons with a git URL will be cloned into the
+application, local paths will be copied. Addons included from a local path will
+be cleaned out by the clean task, others are left alone.
+
+**TODO:** addons via npm module