diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..dd07fa6 --- /dev/null +++ b/Gemfile @@ -0,0 +1,10 @@ +source 'http://rubygems.org' + +gem 'github-pages' +gem 'html-proofer' +gem 'jekyll' +gem 'rake' +gem 'rouge' +gem 'webrick' +gem 'nokogiri', '>= 1.14.3' +gem "kramdown", ">= 2.3.1" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..80f7b08 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,299 @@ +GEM + remote: http://rubygems.org/ + specs: + Ascii85 (1.1.0) + activesupport (7.0.4.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + addressable (2.8.4) + public_suffix (>= 2.0.2, < 6.0) + afm (0.2.2) + async (2.5.0) + console (~> 1.10) + io-event (~> 1.1) + timers (~> 4.1) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.11.1) + colorator (1.1.0) + commonmarker (0.23.9) + concurrent-ruby (1.2.2) + console (1.16.2) + fiber-local + dnsruby (1.70.0) + simpleidn (~> 0.2.1) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + ethon (0.16.0) + ffi (>= 1.15.0) + eventmachine (1.2.7) + execjs (2.8.1) + faraday (2.7.4) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.2) + ffi (1.15.5) + fiber-local (1.0.0) + forwardable-extended (2.6.0) + gemoji (3.0.1) + github-pages (228) + github-pages-health-check (= 1.17.9) + jekyll (= 3.9.3) + jekyll-avatar (= 0.7.0) + jekyll-coffeescript (= 1.1.1) + jekyll-commonmark-ghpages (= 0.4.0) + jekyll-default-layout (= 0.1.4) + jekyll-feed (= 0.15.1) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.13.0) + jekyll-include-cache (= 0.2.1) + jekyll-mentions (= 1.6.0) + jekyll-optional-front-matter (= 0.3.2) + jekyll-paginate (= 1.1.0) + jekyll-readme-index (= 0.3.0) + jekyll-redirect-from (= 0.16.0) + jekyll-relative-links (= 0.6.1) + jekyll-remote-theme (= 0.4.3) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.8.0) + jekyll-sitemap (= 1.4.0) + jekyll-swiss (= 1.0.0) + jekyll-theme-architect (= 0.2.0) + jekyll-theme-cayman (= 0.2.0) + jekyll-theme-dinky (= 0.2.0) + jekyll-theme-hacker (= 0.2.0) + jekyll-theme-leap-day (= 0.2.0) + jekyll-theme-merlot (= 0.2.0) + jekyll-theme-midnight (= 0.2.0) + jekyll-theme-minimal (= 0.2.0) + jekyll-theme-modernist (= 0.2.0) + jekyll-theme-primer (= 0.6.0) + jekyll-theme-slate (= 0.2.0) + jekyll-theme-tactile (= 0.2.0) + jekyll-theme-time-machine (= 0.2.0) + jekyll-titles-from-headings (= 0.5.3) + jemoji (= 0.12.0) + kramdown (= 2.3.2) + kramdown-parser-gfm (= 1.1.0) + liquid (= 4.0.4) + mercenary (~> 0.3) + minima (= 2.5.1) + nokogiri (>= 1.13.6, < 2.0) + rouge (= 3.26.0) + terminal-table (~> 1.4) + github-pages-health-check (1.17.9) + addressable (~> 2.3) + dnsruby (~> 1.60) + octokit (~> 4.0) + public_suffix (>= 3.0, < 5.0) + typhoeus (~> 1.3) + hashery (2.1.2) + html-pipeline (2.14.3) + activesupport (>= 2) + nokogiri (>= 1.4) + html-proofer (5.0.7) + addressable (~> 2.3) + async (~> 2.1) + nokogiri (~> 1.13) + pdf-reader (~> 2.11) + rainbow (~> 3.0) + typhoeus (~> 1.3) + yell (~> 2.0) + zeitwerk (~> 2.5) + http_parser.rb (0.8.0) + i18n (1.13.0) + concurrent-ruby (~> 1.0) + io-event (1.1.7) + jekyll (3.9.3) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (>= 0.7, < 2) + jekyll-sass-converter (~> 1.0) + jekyll-watch (~> 2.0) + kramdown (>= 1.17, < 3) + liquid (~> 4.0) + mercenary (~> 0.3.3) + pathutil (~> 0.9) + rouge (>= 1.7, < 4) + safe_yaml (~> 1.0) + jekyll-avatar (0.7.0) + jekyll (>= 3.0, < 5.0) + jekyll-coffeescript (1.1.1) + coffee-script (~> 2.2) + coffee-script-source (~> 1.11.1) + jekyll-commonmark (1.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.4.0) + commonmarker (~> 0.23.7) + jekyll (~> 3.9.0) + jekyll-commonmark (~> 1.4.0) + rouge (>= 2.0, < 5.0) + jekyll-default-layout (0.1.4) + jekyll (~> 3.0) + jekyll-feed (0.15.1) + jekyll (>= 3.7, < 5.0) + jekyll-gist (1.5.0) + octokit (~> 4.2) + jekyll-github-metadata (2.13.0) + jekyll (>= 3.4, < 5.0) + octokit (~> 4.0, != 4.4.0) + jekyll-include-cache (0.2.1) + jekyll (>= 3.7, < 5.0) + jekyll-mentions (1.6.0) + html-pipeline (~> 2.3) + jekyll (>= 3.7, < 5.0) + jekyll-optional-front-matter (0.3.2) + jekyll (>= 3.0, < 5.0) + jekyll-paginate (1.1.0) + jekyll-readme-index (0.3.0) + jekyll (>= 3.0, < 5.0) + jekyll-redirect-from (0.16.0) + jekyll (>= 3.3, < 5.0) + jekyll-relative-links (0.6.1) + jekyll (>= 3.3, < 5.0) + jekyll-remote-theme (0.4.3) + addressable (~> 2.0) + jekyll (>= 3.5, < 5.0) + jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) + rubyzip (>= 1.3.0, < 3.0) + jekyll-sass-converter (1.5.2) + sass (~> 3.4) + jekyll-seo-tag (2.8.0) + jekyll (>= 3.8, < 5.0) + jekyll-sitemap (1.4.0) + jekyll (>= 3.7, < 5.0) + jekyll-swiss (1.0.0) + jekyll-theme-architect (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-cayman (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-dinky (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-hacker (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-leap-day (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-merlot (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-midnight (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-minimal (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-modernist (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-primer (0.6.0) + jekyll (> 3.5, < 5.0) + jekyll-github-metadata (~> 2.9) + jekyll-seo-tag (~> 2.0) + jekyll-theme-slate (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-tactile (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-time-machine (0.2.0) + jekyll (> 3.5, < 5.0) + jekyll-seo-tag (~> 2.0) + jekyll-titles-from-headings (0.5.3) + jekyll (>= 3.3, < 5.0) + jekyll-watch (2.2.1) + listen (~> 3.0) + jemoji (0.12.0) + gemoji (~> 3.0) + html-pipeline (~> 2.2) + jekyll (>= 3.0, < 5.0) + kramdown (2.3.2) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.3.6) + minima (2.5.1) + jekyll (>= 3.5, < 5.0) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.18.0) + nokogiri (1.14.3-x86_64-linux) + racc (~> 1.4) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + pdf-reader (2.11.0) + Ascii85 (~> 1.0) + afm (~> 0.2.1) + hashery (~> 2.0) + ruby-rc4 + ttfunk + public_suffix (4.0.7) + racc (1.6.2) + rainbow (3.1.1) + rake (13.0.6) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.5) + rouge (3.26.0) + ruby-rc4 (0.1.5) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + safe_yaml (1.0.5) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sawyer (0.9.2) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + simpleidn (0.2.1) + unf (~> 0.1.4) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + timers (4.3.5) + ttfunk (1.7.0) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8.2) + unicode-display_width (1.8.0) + webrick (1.8.1) + yell (2.2.2) + zeitwerk (2.6.8) + +PLATFORMS + x86_64-linux + +DEPENDENCIES + github-pages + html-proofer + jekyll + kramdown (>= 2.3.1) + nokogiri (>= 1.14.3) + rake + rouge + webrick + +BUNDLED WITH + 2.4.10 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..610cecd --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013-2015 Iron Summit Media Strategies, LLC + + 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. + diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..3257b1e --- /dev/null +++ b/Rakefile @@ -0,0 +1,52 @@ +require 'tmpdir' +require 'jekyll' +require 'html-proofer' + +# Change your GitHub reponame +GITHUB_REPONAME = 'tadp-utn-frba/tadp-utn-frba.github.io' + +desc 'Generate blog files' +task :generate do + Jekyll::Site.new(Jekyll.configuration(source: '.', destination: '_site')).process +end + +desc 'Check the generated page with html proofer' +task test: :generate do + begin + proofer = HTMLProofer.check_directory('./_site', + external_only: true, + enforce_https: false, + ignore_missing_alt: true, + parallel: { in_processes: 3 }, + url_ignore: [/rubymonk.com/, /tadp-utn-frba.github.io/], + typhoeus: { + :ssl_verifypeer => false, + :ssl_verifyhost => 0 }) + proofer.run + rescue => e + puts 'Task :test failed' + puts "#{e.class}: #{e.message}" + end + puts 'Finished running all tests' +end + +desc 'Generate and publish blog to gh-pages' +task publish: :generate do + Dir.mktmpdir do |tmp| + cp_r '_site/.', tmp + + pwd = Dir.pwd + Dir.chdir tmp + + system 'git init' + system 'git add .' + message = "Site updated at #{Time.now}" + system "git commit -m #{message.inspect}" + system "git remote add origin git@github.com:#{GITHUB_REPONAME}.git" + system 'git push origin master --force' + + Dir.chdir pwd + end +end + +task default: :generate diff --git a/administrativos/index.html b/administrativos/index.html new file mode 100644 index 0000000..25a96f5 --- /dev/null +++ b/administrativos/index.html @@ -0,0 +1,188 @@ + + + + + + + + + + + Temas Administrativos - TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

Temas Administrativos

+
+ +
+
+
+
+
+ + +
+
+
+

El programa

+ +

Estamos modificando el programa para adaptarlo mejor a la realidad de nuestra facultad y de los nuevos conceptos que surgen en la ciencia/industria. Por eso el mejor lugar para conocer los temas abarcados por la materia es la planificación. +El último programa estable de la materia pertenece al plan 2008, podés descargarlo desde acá.

+ +

Medios de comunicación

+ +

Discord

+ +

Medio de comunicación principal, para acompañar la cursada de la materia, permitiendo chatear con docentes y compañeros.

+ +

Para ingresar, primero aceptá esta invitación. Luego podrás entrar con tu usuario desde la página de Discord o bajándote la aplicación.

+ +

Aula virtual

+ +

Si estás cursando actualmente, ya deberías estar en el aula virtual del cuatrimestre en curso. Este medio de comunicación lo usaremos para establecer el contacto inicial. Para todo lo demás, usaremos Discord.

+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/archive/index.html b/archive/index.html new file mode 100644 index 0000000..78772e2 --- /dev/null +++ b/archive/index.html @@ -0,0 +1,175 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/blog/index.html b/blog/index.html new file mode 100644 index 0000000..3fc7de5 --- /dev/null +++ b/blog/index.html @@ -0,0 +1,176 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Entradas de la pagina +
+
+
+
+
+ + +
+
+
+ + + + + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..e5174c7 --- /dev/null +++ b/circle.yml @@ -0,0 +1,45 @@ +version: 2 +jobs: + build: + docker: + - image: cimg/ruby:3.2.2 + environment: + TZ: "/usr/share/zoneinfo/America/Argentina/Buenos_Aires" + paralelism: 2 + steps: + - add_ssh_keys: + fingerprints: + - "2b:d1:13:72:6c:a2:d1:4c:30:a5:a7:e5:b4:52:ee:75" + - run: git config --global user.email "tadp.bot@gmail.com" + - run: git config --global user.name "GreypineCI" + - checkout + - run: + name: APT Installs + command: | + sudo apt-get update + sudo apt-get install build-essential patch + sudo apt-get install ruby-dev zlib1g-dev liblzma-dev libgmp-dev cmake + - run: + name: Configure Bundler + command: | + echo 'export BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")' >> $BASH_ENV + source $BASH_ENV + gem install bundler + - run: + name: Install dependencies + command: bundle install --jobs=10 --retry=3 + - run: + name: Run the tests for backend + command: bundle exec rake test + - run: + name: Publishing + command: bundle exec rake publish + +workflows: + version: 2 + build: + jobs: + - build: + filters: + branches: + only: source diff --git a/contenidos/index.html b/contenidos/index.html new file mode 100644 index 0000000..e0ec4ac --- /dev/null +++ b/contenidos/index.html @@ -0,0 +1,211 @@ + + + + + + + + + + + Contenidos - TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

Contenidos

+
+ +
+
+
+
+
+ + +
+
+
+

El programa oficial de la materia puede ser descargado desde aquí.

+ +
    +
  • Unidad 1: Repaso y Mixins +
      +
    • Repaso de Objetos. Polimorfismo. Herencia.
    • +
    • Mixins. Introducción. Resolución de Conflictos.
    • +
    • Modelado con Bloques.
    • +
    +
  • +
  • Unidad 2: Metaprogramación +
      +
    • Reflection y Self-Modification en Ruby.
    • +
    • Open Classes. Autoclase.
    • +
    • Metamodelo.
    • +
    • Method missing, class e instance eval.
    • +
    +
  • +
  • Unidad 3: Tipado Estático +
      +
    • Chequeo estático de tipos.
    • +
    • Polimorfismo tipado. Comparación con polimorfismo no tipado. Polimorfismo con mixins.
    • +
    • Binding estático y dinámico. Sobrecarga. Inferencia de tipos.
    • +
    • Tipado estructural. Type arguments.
    • +
    • Varianza. Covarianza. Contravarianza.
    • +
    +
  • +
  • Unidad 4: Programación Funcional-Objetos en Scala +
      +
    • Pattern Matching vs Polimorfismo.
    • +
    • Inmutabilidad. Case classes.
    • +
    • Comportamiento vs Estructura.
    • +
    • Mónadas.
    • +
    • Objetos como función/funciones como objetos.
    • +
    • Funciones Parciales.
    • +
    • Deconstrucción.
    • +
    +
  • +
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/css/bootstrap.css b/css/bootstrap.css new file mode 100644 index 0000000..680e768 --- /dev/null +++ b/css/bootstrap.css @@ -0,0 +1,6800 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\2a"; +} +.glyphicon-plus:before { + content: "\2b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-jpy:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-rub:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +[role="button"] { + cursor: pointer; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + padding: .2em; + background-color: #fcf8e3; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777; +} +.text-primary { + color: #337ab7; +} +a.text-primary:hover, +a.text-primary:focus { + color: #286090; +} +.text-success { + color: #3c763d; +} +a.text-success:hover, +a.text-success:focus { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover, +a.text-info:focus { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover, +a.text-warning:focus { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover, +a.text-danger:focus { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #337ab7; +} +a.bg-primary:hover, +a.bg-primary:focus { + background-color: #286090; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover, +a.bg-success:focus { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover, +a.bg-info:focus { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover, +a.bg-warning:focus { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover, +a.bg-danger:focus { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +.table-responsive { + min-height: .01%; + overflow-x: auto; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); +} +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999; +} +.form-control::-webkit-input-placeholder { + color: #999; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + background-color: #eee; + opacity: 1; +} +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"].form-control, + input[type="time"].form-control, + input[type="datetime-local"].form-control, + input[type="month"].form-control { + line-height: 34px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + min-height: 34px; + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.form-group-sm select.form-control { + height: 30px; + line-height: 30px; +} +.form-group-sm textarea.form-control, +.form-group-sm select[multiple].form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + min-height: 32px; + padding: 6px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.form-group-lg select.form-control { + height: 46px; + line-height: 46px; +} +.form-group-lg textarea.form-control, +.form-group-lg select[multiple].form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 46px; + min-height: 38px; + padding: 11px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback, +.input-group-lg + .form-control-feedback, +.form-group-lg .form-control + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback, +.input-group-sm + .form-control-feedback, +.form-group-sm .form-control + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 14.333333px; + font-size: 18px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + font-size: 12px; + } +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:focus, +.btn-default.focus { + color: #333; + background-color: #e6e6e6; + border-color: #8c8c8c; +} +.btn-default:hover { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active:hover, +.btn-default.active:hover, +.open > .dropdown-toggle.btn-default:hover, +.btn-default:active:focus, +.btn-default.active:focus, +.open > .dropdown-toggle.btn-default:focus, +.btn-default:active.focus, +.btn-default.active.focus, +.open > .dropdown-toggle.btn-default.focus { + color: #333; + background-color: #d4d4d4; + border-color: #8c8c8c; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #fff; + border-color: #ccc; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary:focus, +.btn-primary.focus { + color: #fff; + background-color: #286090; + border-color: #122b40; +} +.btn-primary:hover { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active:hover, +.btn-primary.active:hover, +.open > .dropdown-toggle.btn-primary:hover, +.btn-primary:active:focus, +.btn-primary.active:focus, +.open > .dropdown-toggle.btn-primary:focus, +.btn-primary:active.focus, +.btn-primary.active.focus, +.open > .dropdown-toggle.btn-primary.focus { + color: #fff; + background-color: #204d74; + border-color: #122b40; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary .badge { + color: #337ab7; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:focus, +.btn-success.focus { + color: #fff; + background-color: #449d44; + border-color: #255625; +} +.btn-success:hover { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active:hover, +.btn-success.active:hover, +.open > .dropdown-toggle.btn-success:hover, +.btn-success:active:focus, +.btn-success.active:focus, +.open > .dropdown-toggle.btn-success:focus, +.btn-success:active.focus, +.btn-success.active.focus, +.open > .dropdown-toggle.btn-success.focus { + color: #fff; + background-color: #398439; + border-color: #255625; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:focus, +.btn-info.focus { + color: #fff; + background-color: #31b0d5; + border-color: #1b6d85; +} +.btn-info:hover { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active:hover, +.btn-info.active:hover, +.open > .dropdown-toggle.btn-info:hover, +.btn-info:active:focus, +.btn-info.active:focus, +.open > .dropdown-toggle.btn-info:focus, +.btn-info:active.focus, +.btn-info.active.focus, +.open > .dropdown-toggle.btn-info.focus { + color: #fff; + background-color: #269abc; + border-color: #1b6d85; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:focus, +.btn-warning.focus { + color: #fff; + background-color: #ec971f; + border-color: #985f0d; +} +.btn-warning:hover { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active:hover, +.btn-warning.active:hover, +.open > .dropdown-toggle.btn-warning:hover, +.btn-warning:active:focus, +.btn-warning.active:focus, +.open > .dropdown-toggle.btn-warning:focus, +.btn-warning:active.focus, +.btn-warning.active.focus, +.open > .dropdown-toggle.btn-warning.focus { + color: #fff; + background-color: #d58512; + border-color: #985f0d; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:focus, +.btn-danger.focus { + color: #fff; + background-color: #c9302c; + border-color: #761c19; +} +.btn-danger:hover { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active:hover, +.btn-danger.active:hover, +.open > .dropdown-toggle.btn-danger:hover, +.btn-danger:active:focus, +.btn-danger.active:focus, +.open > .dropdown-toggle.btn-danger:focus, +.btn-danger:active.focus, +.btn-danger.active.focus, +.open > .dropdown-toggle.btn-danger.focus { + color: #fff; + background-color: #ac2925; + border-color: #761c19; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: normal; + color: #337ab7; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-transition-duration: .35s; + -o-transition-duration: .35s; + transition-duration: .35s; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid \9; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #337ab7; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px dashed; + border-bottom: 4px solid \9; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn, +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + z-index: 2; + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eee; +} +.nav > li.disabled > a { + color: #777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eee; + border-color: #337ab7; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eee #eee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #337ab7; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + color: #777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555; + background-color: #e7e7e7; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-default .btn-link { + color: #777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #9d9d9d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #9d9d9d; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.navbar-inverse .btn-link { + color: #9d9d9d; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #337ab7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + z-index: 3; + color: #23527c; + background-color: #eee; + border-color: #ddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.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, +.pager li > a:focus { + text-decoration: none; + background-color: #eee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #777; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #337ab7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: middle; + background-color: #777; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge, +.btn-group-xs > .btn .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #337ab7; + background-color: #fff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding-top: 30px; + padding-bottom: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron, +.container-fluid .jumbotron { + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: border .2s ease-in-out; + -o-transition: border .2s ease-in-out; + transition: border .2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #337ab7; +} +.thumbnail .caption { + padding: 9px; + color: #333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); +} +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #337ab7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, 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(45deg, 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(45deg, 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); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, 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(45deg, 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(45deg, 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); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, 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(45deg, 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(45deg, 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); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, 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(45deg, 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(45deg, 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); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, 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(45deg, 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(45deg, 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); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-object.img-thumbnail { + max-width: none; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +a.list-group-item, +button.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading, +button.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +button.list-group-item:hover, +a.list-group-item:focus, +button.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5; +} +button.list-group-item { + width: 100%; + text-align: left; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #eee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success, +button.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading, +button.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +button.list-group-item-success:hover, +a.list-group-item-success:focus, +button.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +button.list-group-item-success.active, +a.list-group-item-success.active:hover, +button.list-group-item-success.active:hover, +a.list-group-item-success.active:focus, +button.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info, +button.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading, +button.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +button.list-group-item-info:hover, +a.list-group-item-info:focus, +button.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +button.list-group-item-info.active, +a.list-group-item-info.active:hover, +button.list-group-item-info.active:hover, +a.list-group-item-info.active:focus, +button.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning, +button.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading, +button.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +button.list-group-item-warning:hover, +a.list-group-item-warning:focus, +button.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +button.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +button.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus, +button.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger, +button.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading, +button.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +button.list-group-item-danger:hover, +a.list-group-item-danger:focus, +button.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +button.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +button.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus, +button.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-right: 15px; + padding-left: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #ddd; +} +.panel-default > .panel-heading { + color: #333; + background-color: #f5f5f5; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #337ab7; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #337ab7; +} +.panel-primary > .panel-heading .badge { + color: #337ab7; + background-color: #fff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #337ab7; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5; +} +.modal-header { + min-height: 16.42857143px; + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + filter: alpha(opacity=0); + opacity: 0; + + line-break: auto; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + right: 5px; + bottom: 0; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + word-wrap: normal; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + + line-break: auto; +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform .6s ease-in-out; + -o-transition: -o-transform .6s ease-in-out; + transition: transform .6s ease-in-out; + + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + left: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + left: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + left: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + filter: alpha(opacity=50); + opacity: .5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: 0; + opacity: .9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; + margin-top: -10px; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + font-family: serif; + line-height: 1; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -15px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -15px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table !important; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table !important; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table !important; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table !important; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table !important; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/css/bootstrap.min.css b/css/bootstrap.min.css new file mode 100644 index 0000000..d65c66b --- /dev/null +++ b/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:3;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.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:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,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(45deg,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(45deg,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);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,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(45deg,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(45deg,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)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,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(45deg,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(45deg,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)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,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(45deg,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(45deg,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)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,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(45deg,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(45deg,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)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/css/clean-blog.css b/css/clean-blog.css new file mode 100644 index 0000000..296841d --- /dev/null +++ b/css/clean-blog.css @@ -0,0 +1,407 @@ +body { + font-family: 'Lora', 'Times New Roman', serif; + font-size: 20px; + color: #404040; +} +p { + line-height: 1.5; + margin: 30px 0; +} +p a { + text-decoration: underline; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 800; +} +a { + color: #404040; +} +a:hover, +a:focus { + color: #0085a1; +} +a img:hover, +a img:focus { + cursor: zoom-in; +} +blockquote { + color: #808080; + font-style: italic; +} +hr.small { + max-width: 100px; + margin: 15px auto; + border-width: 4px; + border-color: white; +} +.navbar-custom { + background: white; + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 3; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; +} +.navbar-custom .navbar-brand { + font-weight: 800; +} +.navbar-custom .nav li a { + text-transform: uppercase; + font-size: 12px; + font-weight: 800; + letter-spacing: 1px; +} +@media only screen and (min-width: 768px) { + .navbar-custom { + background: transparent; + border-bottom: 1px solid transparent; + } + .navbar-custom .navbar-brand { + color: white; + padding: 20px; + } + .navbar-custom .navbar-brand:hover, + .navbar-custom .navbar-brand:focus { + color: rgba(255, 255, 255, 0.8); + } + .navbar-custom .nav li a { + color: white; + padding: 20px; + } + .navbar-custom .nav li a:hover, + .navbar-custom .nav li a:focus { + color: rgba(255, 255, 255, 0.8); + } +} +@media only screen and (min-width: 1170px) { + .navbar-custom { + -webkit-transition: background-color 0.3s; + -moz-transition: background-color 0.3s; + transition: background-color 0.3s; + /* Force Hardware Acceleration in WebKit */ + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); + -ms-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + } + .navbar-custom.is-fixed { + /* when the user scrolls down, we hide the header right above the viewport */ + position: fixed; + top: -61px; + background-color: rgba(255, 255, 255, 0.9); + border-bottom: 1px solid #f2f2f2; + -webkit-transition: -webkit-transform 0.3s; + -moz-transition: -moz-transform 0.3s; + transition: transform 0.3s; + } + .navbar-custom.is-fixed .navbar-brand { + color: #404040; + } + .navbar-custom.is-fixed .navbar-brand:hover, + .navbar-custom.is-fixed .navbar-brand:focus { + color: #0085a1; + } + .navbar-custom.is-fixed .nav li a { + color: #404040; + } + .navbar-custom.is-fixed .nav li a:hover, + .navbar-custom.is-fixed .nav li a:focus { + color: #0085a1; + } + .navbar-custom.is-visible { + /* if the user changes the scrolling direction, we show the header */ + -webkit-transform: translate3d(0, 100%, 0); + -moz-transform: translate3d(0, 100%, 0); + -ms-transform: translate3d(0, 100%, 0); + -o-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} +.intro-header { + background: no-repeat center center; + background-color: #808080; + background-attachment: scroll; + -webkit-background-size: cover; + -moz-background-size: cover; + background-size: cover; + -o-background-size: cover; + margin-bottom: 50px; +} +.intro-header .site-heading, +.intro-header .post-heading, +.intro-header .page-heading { + padding: 100px 0 50px; + color: white; +} +@media only screen and (min-width: 768px) { + .intro-header .site-heading, + .intro-header .post-heading, + .intro-header .page-heading { + padding: 150px 0; + } +} +.intro-header .site-heading, +.intro-header .page-heading { + text-align: center; +} +.intro-header .site-heading h1, +.intro-header .page-heading h1 { + margin-top: 0; + font-size: 50px; +} +.intro-header .site-heading .subheading, +.intro-header .page-heading .subheading { + font-size: 24px; + line-height: 1.1; + display: block; + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 300; + margin: 10px 0 0; +} +@media only screen and (min-width: 768px) { + .intro-header .site-heading h1, + .intro-header .page-heading h1 { + font-size: 80px; + } +} +.intro-header .post-heading h1 { + font-size: 35px; +} +.intro-header .post-heading .subheading, +.intro-header .post-heading .meta { + line-height: 1.1; + display: block; +} +.intro-header .post-heading .subheading { + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 24px; + margin: 10px 0 30px; + font-weight: 600; +} +.intro-header .post-heading .meta { + font-family: 'Lora', 'Times New Roman', serif; + font-style: italic; + font-weight: 300; + font-size: 20px; +} +.intro-header .post-heading .meta a { + color: white; +} +@media only screen and (min-width: 768px) { + .intro-header .post-heading h1 { + font-size: 55px; + } + .intro-header .post-heading .subheading { + font-size: 30px; + } +} +.post-preview > a { + color: #404040; +} +.post-preview > a:hover, +.post-preview > a:focus { + text-decoration: none; + color: #0085a1; +} +.post-preview > a > .post-title { + font-size: 30px; + margin-top: 30px; + margin-bottom: 10px; +} +.post-preview > a > .post-subtitle { + margin: 0; + font-weight: 300; + margin-bottom: 10px; +} +.post-preview > .post-meta { + color: #808080; + font-size: 18px; + font-style: italic; + margin-top: 0; +} +.post-preview > .post-meta > a { + text-decoration: none; + color: #404040; +} +.post-preview > .post-meta > a:hover, +.post-preview > .post-meta > a:focus { + color: #0085a1; + text-decoration: underline; +} +@media only screen and (min-width: 768px) { + .post-preview > a > .post-title { + font-size: 36px; + } +} +.section-heading { + font-size: 36px; + margin-top: 60px; + font-weight: 700; +} +.caption { + text-align: center; + font-size: 14px; + padding: 10px; + font-style: italic; + margin: 0; + display: block; + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} +footer { + padding: 50px 0 65px; +} +footer .list-inline { + margin: 0; + padding: 0; +} +footer .copyright { + font-size: 14px; + text-align: center; + margin-bottom: 0; +} +.floating-label-form-group { + font-size: 14px; + position: relative; + margin-bottom: 0; + padding-bottom: 0.5em; + border-bottom: 1px solid #eeeeee; +} +.floating-label-form-group input, +.floating-label-form-group textarea { + z-index: 1; + position: relative; + padding-right: 0; + padding-left: 0; + border: none; + border-radius: 0; + font-size: 1.5em; + background: none; + box-shadow: none !important; + resize: none; +} +.floating-label-form-group label { + display: block; + z-index: 0; + position: relative; + top: 2em; + margin: 0; + font-size: 0.85em; + line-height: 1.764705882em; + vertical-align: middle; + vertical-align: baseline; + opacity: 0; + -webkit-transition: top 0.3s ease,opacity 0.3s ease; + -moz-transition: top 0.3s ease,opacity 0.3s ease; + -ms-transition: top 0.3s ease,opacity 0.3s ease; + transition: top 0.3s ease,opacity 0.3s ease; +} +.floating-label-form-group::not(:first-child) { + padding-left: 14px; + border-left: 1px solid #eeeeee; +} +.floating-label-form-group-with-value label { + top: 0; + opacity: 1; +} +.floating-label-form-group-with-focus label { + color: #0085a1; +} +form .row:first-child .floating-label-form-group { + border-top: 1px solid #eeeeee; +} +.btn { + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + text-transform: uppercase; + font-size: 14px; + font-weight: 800; + letter-spacing: 1px; + border-radius: 0; + padding: 15px 25px; +} +.btn-lg { + font-size: 16px; + padding: 25px 35px; +} +.btn-default:hover, +.btn-default:focus { + background-color: #0085a1; + border: 1px solid #0085a1; + color: white; +} +.pager { + margin: 20px 0 0; +} +.pager li > a, +.pager li > span { + font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + text-transform: uppercase; + font-size: 14px; + font-weight: 800; + letter-spacing: 1px; + padding: 10px 5px; + background-color: white; + border-radius: 0; +} +@media only screen and (min-width: 768px) { + .pager li > a, + .pager li > span { + font-size: 14px; + padding: 15px 25px; + } +} +.pager li > a:hover, +.pager li > a:focus { + color: white; + background-color: #0085a1; + border: 1px solid #0085a1; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #808080; + background-color: #404040; + cursor: not-allowed; +} +::-moz-selection { + color: white; + text-shadow: none; + background: #0085a1; +} +::selection { + color: white; + text-shadow: none; + background: #0085a1; +} +img::selection { + color: white; + background: transparent; +} +img::-moz-selection { + color: white; + background: transparent; +} +body { + webkit-tap-highlight-color: #0085a1; +} + +h1 svg.octicon, +h2 svg.octicon { + padding-top: 10px; +} + +h3 svg.octicon, +h4 svg.octicon { + padding-top: 15px; +} diff --git a/css/clean-blog.min.css b/css/clean-blog.min.css new file mode 100644 index 0000000..d8ff1d3 --- /dev/null +++ b/css/clean-blog.min.css @@ -0,0 +1 @@ +body{font-family:Lora,'Times New Roman',serif;font-size:20px;color:#404040}p{line-height:1.5;margin:30px 0}p a{text-decoration:underline}h1,h2,h3,h4,h5,h6{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:800}a{color:#404040}a:hover,a:focus{color:#0085a1}a img:hover,a img:focus{cursor:zoom-in}blockquote{color:gray;font-style:italic}hr.small{max-width:100px;margin:15px auto;border-width:4px;border-color:#fff}.navbar-custom{background:#fff;position:absolute;top:0;left:0;width:100%;z-index:3;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif}.navbar-custom .navbar-brand{font-weight:800}.navbar-custom .nav li a{text-transform:uppercase;font-size:12px;font-weight:800;letter-spacing:1px}@media only screen and (min-width:768px){.navbar-custom{background:0 0;border-bottom:1px solid transparent}.navbar-custom .navbar-brand{color:#fff;padding:20px}.navbar-custom .navbar-brand:hover,.navbar-custom .navbar-brand:focus{color:rgba(255,255,255,.8)}.navbar-custom .nav li a{color:#fff;padding:20px}.navbar-custom .nav li a:hover,.navbar-custom .nav li a:focus{color:rgba(255,255,255,.8)}}@media only screen and (min-width:1170px){.navbar-custom{-webkit-transition:background-color .3s;-moz-transition:background-color .3s;transition:background-color .3s;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);transform:translate3d(0,0,0);-webkit-backface-visibility:hidden;backface-visibility:hidden}.navbar-custom.is-fixed{position:fixed;top:-61px;background-color:rgba(255,255,255,.9);border-bottom:1px solid #f2f2f2;-webkit-transition:-webkit-transform .3s;-moz-transition:-moz-transform .3s;transition:transform .3s}.navbar-custom.is-fixed .navbar-brand{color:#404040}.navbar-custom.is-fixed .navbar-brand:hover,.navbar-custom.is-fixed .navbar-brand:focus{color:#0085a1}.navbar-custom.is-fixed .nav li a{color:#404040}.navbar-custom.is-fixed .nav li a:hover,.navbar-custom.is-fixed .nav li a:focus{color:#0085a1}.navbar-custom.is-visible{-webkit-transform:translate3d(0,100%,0);-moz-transform:translate3d(0,100%,0);-ms-transform:translate3d(0,100%,0);-o-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.intro-header{background:no-repeat center center;background-color:gray;background-attachment:scroll;-webkit-background-size:cover;-moz-background-size:cover;background-size:cover;-o-background-size:cover;margin-bottom:50px}.intro-header .site-heading,.intro-header .post-heading,.intro-header .page-heading{padding:100px 0 50px;color:#fff}@media only screen and (min-width:768px){.intro-header .site-heading,.intro-header .post-heading,.intro-header .page-heading{padding:150px 0}}.intro-header .site-heading,.intro-header .page-heading{text-align:center}.intro-header .site-heading h1,.intro-header .page-heading h1{margin-top:0;font-size:50px}.intro-header .site-heading .subheading,.intro-header .page-heading .subheading{font-size:24px;line-height:1.1,display:block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-weight:300;margin:10px 0 0}@media only screen and (min-width:768px){.intro-header .site-heading h1,.intro-header .page-heading h1{font-size:80px}}.intro-header .post-heading h1{font-size:35px}.intro-header .post-heading .subheading,.intro-header .post-heading .meta{line-height:1.1;display:block}.intro-header .post-heading .subheading{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:24px;margin:10px 0 30px;font-weight:600}.intro-header .post-heading .meta{font-family:Lora,'Times New Roman',serif;font-style:italic;font-weight:300;font-size:20px}.intro-header .post-heading .meta a{color:#fff}@media only screen and (min-width:768px){.intro-header .post-heading h1{font-size:55px}.intro-header .post-heading .subheading{font-size:30px}}.post-preview>a{color:#404040}.post-preview>a:hover,.post-preview>a:focus{text-decoration:none;color:#0085a1}.post-preview>a>.post-title{font-size:30px;margin-top:30px;margin-bottom:10px}.post-preview>a>.post-subtitle{margin:0;font-weight:300;margin-bottom:10px}.post-preview>.post-meta{color:gray;font-size:18px;font-style:italic;margin-top:0}.post-preview>.post-meta>a{text-decoration:none;color:#404040}.post-preview>.post-meta>a:hover,.post-preview>.post-meta>a:focus{color:#0085a1;text-decoration:underline}@media only screen and (min-width:768px){.post-preview>a>.post-title{font-size:36px}}.section-heading{font-size:36px;margin-top:60px;font-weight:700}.caption{text-align:center;font-size:14px;padding:10px;font-style:italic;margin:0;display:block;border-bottom-right-radius:5px;border-bottom-left-radius:5px}footer{padding:50px 0 65px}footer .list-inline{margin:0;padding:0}footer .copyright{font-size:14px;text-align:center;margin-bottom:0}.floating-label-form-group{font-size:14px;position:relative;margin-bottom:0;padding-bottom:.5em;border-bottom:1px solid #eee}.floating-label-form-group input,.floating-label-form-group textarea{z-index:1;position:relative;padding-right:0;padding-left:0;border:none;border-radius:0;font-size:1.5em;background:0 0;box-shadow:none!important;resize:none}.floating-label-form-group label{display:block;z-index:0;position:relative;top:2em;margin:0;font-size:.85em;line-height:1.764705882em;vertical-align:middle;vertical-align:baseline;opacity:0;-webkit-transition:top .3s ease,opacity .3s ease;-moz-transition:top .3s ease,opacity .3s ease;-ms-transition:top .3s ease,opacity .3s ease;transition:top .3s ease,opacity .3s ease}.floating-label-form-group::not(:first-child){padding-left:14px;border-left:1px solid #eee}.floating-label-form-group-with-value label{top:0;opacity:1}.floating-label-form-group-with-focus label{color:#0085a1}form .row:first-child .floating-label-form-group{border-top:1px solid #eee}.btn{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;text-transform:uppercase;font-size:14px;font-weight:800;letter-spacing:1px;border-radius:0;padding:15px 25px}.btn-lg{font-size:16px;padding:25px 35px}.btn-default:hover,.btn-default:focus{background-color:#0085a1;border:1px solid #0085a1;color:#fff}.pager{margin:20px 0 0}.pager li>a,.pager li>span{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;text-transform:uppercase;font-size:14px;font-weight:800;letter-spacing:1px;padding:10px 5px;background-color:#fff;border-radius:0}@media only screen and (min-width:768px){.pager li>a,.pager li>span{font-size:14px;padding:15px 25px}}.pager li>a:hover,.pager li>a:focus{color:#fff;background-color:#0085a1;border:1px solid #0085a1}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:gray;background-color:#404040;cursor:not-allowed}::-moz-selection{color:#fff;text-shadow:none;background:#0085a1}::selection{color:#fff;text-shadow:none;background:#0085a1}img::selection{color:#fff;background:0 0}img::-moz-selection{color:#fff;background:0 0}body{webkit-tap-highlight-color:#0085a1} diff --git a/css/prism.css b/css/prism.css new file mode 100644 index 0000000..c4a956f --- /dev/null +++ b/css/prism.css @@ -0,0 +1,138 @@ +/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+abap+actionscript+ada+apacheconf+apl+applescript+asciidoc+aspnet+autoit+autohotkey+bash+basic+batch+c+brainfuck+bro+bison+csharp+cpp+coffeescript+ruby+css-extras+d+dart+diff+docker+eiffel+elixir+erlang+fsharp+fortran+gherkin+git+glsl+go+graphql+groovy+haml+handlebars+haskell+haxe+http+icon+inform7+ini+j+jade+java+json+julia+keyman+kotlin+latex+less+livescript+lolcode+lua+makefile+markdown+matlab+mel+mizar+monkey+nasm+nginx+nim+nix+nsis+objectivec+ocaml+oz+parigp+parser+pascal+perl+php+php-extras+powershell+processing+prolog+properties+protobuf+puppet+pure+python+q+qore+r+jsx+rest+rip+roboconf+crystal+rust+sas+sass+scss+scala+scheme+smalltalk+smarty+sql+stylus+swift+tcl+textile+twig+typescript+verilog+vhdl+vim+wiki+xojo+yaml */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/css/syntax.css b/css/syntax.css new file mode 100644 index 0000000..4919ebd --- /dev/null +++ b/css/syntax.css @@ -0,0 +1,84 @@ +/* to make lines scroll instead of wrap */ +/* from http://stackoverflow.com/a/23393920 */ + +.highlight pre code * { + white-space: nowrap; // this sets all children inside to nowrap +} + +.highlight pre { + overflow-x: auto; // this sets the scrolling in x +} + +.highlight pre code { + white-space: pre; // forces to respect
 formatting
+}
+
+
+/*
+ * GitHub style for Pygments syntax highlighter, for use with Jekyll
+ * Courtesy of GitHub.com
+ */
+
+.highlight pre, pre, .highlight .hll { background-color: #f8f8f8; border: 1px solid #ccc; padding: 6px 10px; border-radius: 3px; }
+.highlight .c { color: #999988; font-style: italic; }
+.highlight .err { color: #a61717; background-color: #e3d2d2; }
+.highlight .k { font-weight: bold; }
+.highlight .o { font-weight: bold; }
+.highlight .cm { color: #999988; font-style: italic; }
+.highlight .cp { color: #999999; font-weight: bold; }
+.highlight .c1 { color: #999988; font-style: italic; }
+.highlight .cs { color: #999999; font-weight: bold; font-style: italic; }
+.highlight .gd { color: #000000; background-color: #ffdddd; }
+.highlight .gd .x { color: #000000; background-color: #ffaaaa; }
+.highlight .ge { font-style: italic; }
+.highlight .gr { color: #aa0000; }
+.highlight .gh { color: #999999; }
+.highlight .gi { color: #000000; background-color: #ddffdd; }
+.highlight .gi .x { color: #000000; background-color: #aaffaa; }
+.highlight .go { color: #888888; }
+.highlight .gp { color: #555555; }
+.highlight .gs { font-weight: bold; }
+.highlight .gu { color: #800080; font-weight: bold; }
+.highlight .gt { color: #aa0000; }
+.highlight .kc { font-weight: bold; }
+.highlight .kd { font-weight: bold; }
+.highlight .kn { font-weight: bold; }
+.highlight .kp { font-weight: bold; }
+.highlight .kr { font-weight: bold; }
+.highlight .kt { color: #445588; font-weight: bold; }
+.highlight .m { color: #009999; }
+.highlight .s { color: #dd1144; }
+.highlight .n { color: #333333; }
+.highlight .na { color: teal; }
+.highlight .nb { color: #0086b3; }
+.highlight .nc { color: #445588; font-weight: bold; }
+.highlight .no { color: teal; }
+.highlight .ni { color: purple; }
+.highlight .ne { color: #990000; font-weight: bold; }
+.highlight .nf { color: #990000; font-weight: bold; }
+.highlight .nn { color: #555555; }
+.highlight .nt { color: navy; }
+.highlight .nv { color: teal; }
+.highlight .ow { font-weight: bold; }
+.highlight .w { color: #bbbbbb; }
+.highlight .mf { color: #009999; }
+.highlight .mh { color: #009999; }
+.highlight .mi { color: #009999; }
+.highlight .mo { color: #009999; }
+.highlight .sb { color: #dd1144; }
+.highlight .sc { color: #dd1144; }
+.highlight .sd { color: #dd1144; }
+.highlight .s2 { color: #dd1144; }
+.highlight .se { color: #dd1144; }
+.highlight .sh { color: #dd1144; }
+.highlight .si { color: #dd1144; }
+.highlight .sx { color: #dd1144; }
+.highlight .sr { color: #009926; }
+.highlight .s1 { color: #dd1144; }
+.highlight .ss { color: #990073; }
+.highlight .bp { color: #999999; }
+.highlight .vc { color: teal; }
+.highlight .vg { color: teal; }
+.highlight .vi { color: teal; }
+.highlight .il { color: #009999; }
+.highlight .gc { color: #999; background-color: #EAF2F5; }
diff --git a/cursada/index.html b/cursada/index.html
new file mode 100644
index 0000000..1b7a4df
--- /dev/null
+++ b/cursada/index.html
@@ -0,0 +1,179 @@
+
+
+
+
+
+    
+    
+    
+    
+
+    Cursada - TADP
+
+    
+
+    
+    
+
+    
+    
+
+    
+    
+
+    
+    
+
+    
+    
+    
+    
+
+    
+    
+    
+
+
+
+
+
+
+    
+
+
+
+    
+
+
+
+
+
+

Cursada

+
+ Grupos y Notas +
+
+
+
+
+ + +
+
+
+

Grupos y Notas

+ +

Alumnos

+ + + + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/cursadas/cursada_2015c2/index.html b/cursadas/cursada_2015c2/index.html new file mode 100644 index 0000000..b2db1f3 --- /dev/null +++ b/cursadas/cursada_2015c2/index.html @@ -0,0 +1,193 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Cursada 2015 2C +
+
+
+
+
+ + +
+
+
+

Grupos y Notas

+ +

##Alumnnos

+ + + +

##Grupos

+ + + +

#Enunciados

+ + + +

Trabajos Prácticos

+ + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..2c4756e Binary files /dev/null and b/favicon.ico differ diff --git a/feed.xml b/feed.xml new file mode 100644 index 0000000..f153913 --- /dev/null +++ b/feed.xml @@ -0,0 +1,13 @@ + + + + TADP + Pagina de TADP. Electiva de 3er anio de la UTN FRBA + http://tadp-utn-frba.github.io/ + + Sat, 21 Oct 2023 14:09:13 -0300 + Sat, 21 Oct 2023 14:09:13 -0300 + Jekyll v3.9.3 + + + diff --git a/fonts/glyphicons-halflings-regular.eot b/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..b93a495 Binary files /dev/null and b/fonts/glyphicons-halflings-regular.eot differ diff --git a/fonts/glyphicons-halflings-regular.svg b/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..94fb549 --- /dev/null +++ b/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/glyphicons-halflings-regular.ttf b/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..1413fc6 Binary files /dev/null and b/fonts/glyphicons-halflings-regular.ttf differ diff --git a/fonts/glyphicons-halflings-regular.woff b/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..9e61285 Binary files /dev/null and b/fonts/glyphicons-halflings-regular.woff differ diff --git a/fonts/glyphicons-halflings-regular.woff2 b/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000..64539b5 Binary files /dev/null and b/fonts/glyphicons-halflings-regular.woff2 differ diff --git a/generate_watches.bash b/generate_watches.bash new file mode 100755 index 0000000..f1ee42c --- /dev/null +++ b/generate_watches.bash @@ -0,0 +1,2 @@ +#!/bin/bash +echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p \ No newline at end of file diff --git a/guias/ruby/index.html b/guias/ruby/index.html new file mode 100644 index 0000000..c6a700b --- /dev/null +++ b/guias/ruby/index.html @@ -0,0 +1,191 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Instalación de IDE y otras guías +
+
+
+
+
+ + +
+
+
+

Los videos que pueden encontrar a continuación quedaron un poco viejos, pero deberían servir de todos modos.

+ +

La principal diferencia a tener en cuenta es la forma de registrar su instalación de Rubymine. La facultad les debería haber dado de un mail personal terminado en frba.utn.edu.ar, con ese mail se pueden crear una cuenta en https://account.jetbrains.com/login y acceder a una licencia educativa que les va a servir para todo el cuatrimestre.

+ +

Para más detalles, entrar acá.

+ +

Sobre la versión de Ruby, pueden usar la más actual que esté disponible, no debería impactar para lo que vamos a hacer en la materia.

+ +

Instalando Ruby + Rubymine en Linux

+ + + +

Instalando Rubymine en Windows

+ + + +

Creando un proyecto básico en Rubymine

+ + + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/guias/scala/index.html b/guias/scala/index.html new file mode 100644 index 0000000..e212ccf --- /dev/null +++ b/guias/scala/index.html @@ -0,0 +1,271 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Instalación de IDE y otras guías +
+
+
+
+
+ + +
+
+
+

Intellij

+

Instalación

+ +
    +
  1. Asegurate de tener instalada la JDK de Java 8 (o superior) +
      +
    • En la línea de comandos corré javac -version y asegurate de ver javac 1.8.___ (o superior).
    • +
    • Si no tenés la versión 1.8 o superior, instalá la JDK.
    • +
    +
  2. +
  3. Luego, descargá e instalá Intellij community edition
  4. +
  5. Luego de iniciar Intellij, podés descargar e instalar el plugin de Scala: +
      +
    • +

      File -> Settings -> Plugins -> buscar scala -> Install

      + +

      Plugins

      +
    • +
    +
  6. +
  7. Reiniciar el ide para que se apliquen los cambios
  8. +
+ +

Más detalles y opciones en la página de scala.

+ +

Importar el proyecto sbt

+
    +
  1. Clonar el proyecto de github correspondiente a tu grupo
  2. +
  3. +

    En intellij: File -> Open -> doble click en build.sbt

    + +

    Open project

    +
  4. +
  5. +

    Click en Open as Project

    + +

    Open as project

    +
  6. +
+ +

Para crear un proyecto desde cero, referirse a la página de scala (con sbt y sin sbt).

+ +

Escribiendo código scala

+
    +
  1. En el panel Project ubicado a la izquierda, expandí {tu proyecto} -> src -> main -> scala -> click derecho -> New -> Package
  2. +
  3. Nombrar el package domain
  4. +
  5. +

    Click derecho sobre domain -> New -> Scala class -> nombrarla Persona

    + +

    Open as project

    +
  6. +
  7. Cambiar el código a lo siguiente:
  8. +
+ +
package domain
+
+class Persona (var edad: Int) {
+  def cumpliAnio = {
+    edad += 1
+  }
+}
+
+ +

Tests con ScalaTest

+
    +
  1. Agregar (o verificar que exista) la dependencia de ScalaTest en build.sbt +
     libraryDependencies ++= Seq(
    +   "org.scalatest" %% "scalatest" % "3.2.9" % "test"
    + )
    +
    +
  2. +
  3. Si recibís la notificación “build.sbt was changed”, seleccionar auto-import.
  4. +
  5. Estas dos acciones van a producir que sbt descargue la biblioteca ScalaTest.
  6. +
  7. Esperá que la sincronización de sbt termine; de otra manera, intellij no va a reconocer el código de ScalaTest y el proyecto no va a compilar.
  8. +
  9. En el panel Project ubicado a la izquierda, expandí {tu proyecto} -> src -> test -> scala -> click derecho -> New -> Package
  10. +
  11. Nombrar el package domain
  12. +
  13. Click derecho sobre domain -> New -> Scala class -> nombrarla PersonaTest
  14. +
  15. Reemplazar el código por +
     package domain
    +    
    + import org.scalatest.freespec.AnyFreeSpec
    +    
    + class PersonaTest extends AnyFreeSpec {
    +   "cuando una persona cumple años la edad debería ser 2" in {
    +     val persona = new Persona(1)
    +     persona.cumpliAnio
    +     assert(persona.edad == 2)
    +   }
    + }
    +
    +
  16. +
  17. +

    En el código, click derecho en PersonaTest y seleccionar Run 'PersonaTest'. Podés verificar el resultado de los test en el panel de abajo:

    + +

    Open as project

    +
  18. +
+ +

Más detalles acá.

+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/img/UserImage.png b/img/UserImage.png new file mode 100644 index 0000000..2debb6c Binary files /dev/null and b/img/UserImage.png differ diff --git a/img/create_person_class.png b/img/create_person_class.png new file mode 100644 index 0000000..8b4ec50 Binary files /dev/null and b/img/create_person_class.png differ diff --git a/img/home-bg.jpg b/img/home-bg.jpg new file mode 100644 index 0000000..892411e Binary files /dev/null and b/img/home-bg.jpg differ diff --git a/img/intellij_scala_plugin.png b/img/intellij_scala_plugin.png new file mode 100644 index 0000000..80c015d Binary files /dev/null and b/img/intellij_scala_plugin.png differ diff --git a/img/open_as_project.png b/img/open_as_project.png new file mode 100644 index 0000000..5b5b20e Binary files /dev/null and b/img/open_as_project.png differ diff --git a/img/open_project.png b/img/open_project.png new file mode 100644 index 0000000..3c10191 Binary files /dev/null and b/img/open_project.png differ diff --git a/img/post-bg-01.jpg b/img/post-bg-01.jpg new file mode 100644 index 0000000..e7d6944 Binary files /dev/null and b/img/post-bg-01.jpg differ diff --git a/img/scala_test_results.png b/img/scala_test_results.png new file mode 100644 index 0000000..add43cf Binary files /dev/null and b/img/scala_test_results.png differ diff --git a/img/tadp_grupo_2.jpg b/img/tadp_grupo_2.jpg new file mode 100644 index 0000000..601189b Binary files /dev/null and b/img/tadp_grupo_2.jpg differ diff --git a/img/tadp_join_group.jpg b/img/tadp_join_group.jpg new file mode 100644 index 0000000..28f6391 Binary files /dev/null and b/img/tadp_join_group.jpg differ diff --git a/img/utn-logo.jpg b/img/utn-logo.jpg new file mode 100644 index 0000000..41f3236 Binary files /dev/null and b/img/utn-logo.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..c3c39ff --- /dev/null +++ b/index.html @@ -0,0 +1,187 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Pagina Principal +
+
+
+
+
+ + +
+
+
+
+

+ ¡Bienvenidos! +

+ +

Esta es la página del curso de Técnicas Avanzadas de Programación de la Universidad Tecnológica Nacional, Facultad Regional Buenos Aires.

+
+

Es muy importante leer los temas administrativos para estar suscripto a la lista de correo.

+
+
+ +
+
UTN-FRBA
+ +
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/js/bootstrap.js b/js/bootstrap.js new file mode 100644 index 0000000..5debfd7 --- /dev/null +++ b/js/bootstrap.js @@ -0,0 +1,2363 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under the MIT license + */ + +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} + ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher') + } +}(jQuery); + +/* ======================================================================== + * Bootstrap: transition.js v3.3.5 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) + } + } + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.3.5 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.5' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.3.5 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.5' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state += 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked')) changed = false + $parent.find('.active').removeClass('active') + this.$element.addClass('active') + } else if ($input.prop('type') == 'checkbox') { + if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false + this.$element.toggleClass('active') + } + $input.prop('checked', this.$element.hasClass('active')) + if (changed) $input.trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + this.$element.toggleClass('active') + } + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + Plugin.call($btn, 'toggle') + if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault() + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.3.5 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = null + this.sliding = null + this.interval = null + this.$active = null + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.3.5' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.3.5 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + + '[data-toggle="collapse"][data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.3.5' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.5 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.5' + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget) + }) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $(document.createElement('div')) + .addClass('dropdown-backdrop') + .insertAfter($(this)) + .on('click', clearMenus) + } + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger('shown.bs.dropdown', relatedTarget) + } + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if (!isActive && e.which != 27 || isActive && e.which == 27) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') + } + + var desc = ' li:not(.disabled):visible a' + var $items = $parent.find('.dropdown-menu' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.3.5 + * http://getbootstrap.com/javascript/#modals + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$dialog = this.$element.find('.modal-dialog') + this.$backdrop = null + this.isShown = null + this.originalBodyPad = null + this.scrollbarWidth = 0 + this.ignoreBackdropClick = false + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.3.5' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.$dialog.on('mousedown.dismiss.bs.modal', function () { + that.$element.one('mouseup.dismiss.bs.modal', function (e) { + if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true + }) + }) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$dialog // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .off('click.dismiss.bs.modal') + .off('mouseup.dismiss.bs.modal') + + this.$dialog.off('mousedown.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $(document.createElement('div')) + .addClass('modal-backdrop ' + animate) + .appendTo(this.$body) + + this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { + if (this.ignoreBackdropClick) { + this.ignoreBackdropClick = false + return + } + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus() + : this.hide() + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + this.adjustDialog() + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + var fullWindowWidth = window.innerWidth + if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 + var documentElementRect = document.documentElement.getBoundingClientRect() + fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) + } + this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + this.originalBodyPad = document.body.style.paddingRight || '' + if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', this.originalBodyPad) + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.5 + * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = null + this.options = null + this.enabled = null + this.timeout = null + this.hoverState = null + this.$element = null + this.inState = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.3.5' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 + } + } + + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) + this.inState = { click: false, hover: false, focus: false } + + if (this.$element[0] instanceof document.constructor && !this.options.selector) { + throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') + } + + var triggers = this.options.trigger.split(' ') + + for (var i = triggers.length; i--;) { + var trigger = triggers[i] + + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + + 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() + } + + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS + } + + Tooltip.prototype.getOptions = function (options) { + options = $.extend({}, this.getDefaults(), this.$element.data(), options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay + } + } + + return options + } + + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() + + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) + + return options + } + + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true + } + + if (self.tip().hasClass('in') || self.hoverState == 'in') { + self.hoverState = 'in' + return + } + + clearTimeout(self.timeout) + + self.hoverState = 'in' + + if (!self.options.delay || !self.options.delay.show) return self.show() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + Tooltip.prototype.isInStateTrue = function () { + for (var key in this.inState) { + if (this.inState[key]) return true + } + + return false + } + + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + if (obj instanceof $.Event) { + self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false + } + + if (self.isInStateTrue()) return + + clearTimeout(self.timeout) + + self.hoverState = 'out' + + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this + + var $tip = this.tip() + + var tipId = this.getUID(this.type) + + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + + if (this.options.animation) $tip.addClass('fade') + + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + this.$element.trigger('inserted.bs.' + this.type) + + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (autoPlace) { + var orgPlacement = placement + var viewportDim = this.getPosition(this.$viewport) + + placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : + placement + + $tip + .removeClass(orgPlacement) + .addClass(placement) + } + + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + + this.applyPlacement(calculatedOffset, placement) + + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) + } + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + } + } + + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top += marginTop + offset.left += marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) + } + }, offset), 0) + + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight + } + + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + + if (delta.left) offset.left += delta.left + else offset.top += delta.top + + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) + } + + Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { + this.arrow() + .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isVertical ? 'top' : 'left', '') + } + + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = $(this.$tip) + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) + callback && callback() + } + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + $.support.transition && $tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + + this.hoverState = null + + return this + } + + Tooltip.prototype.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') || '').attr('title', '') + } + } + + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } + + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element + + var el = $element[0] + var isBody = el.tagName == 'BODY' + + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) + } + var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null + + return $.extend({}, elRect, scroll, outerDims, elOffset) + } + + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } + + } + + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta + + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + } + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + } + } + + return delta + } + + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + } + + Tooltip.prototype.tip = function () { + if (!this.$tip) { + this.$tip = $(this.options.template) + if (this.$tip.length != 1) { + throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') + } + } + return this.$tip + } + + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } + + Tooltip.prototype.enable = function () { + this.enabled = true + } + + Tooltip.prototype.disable = function () { + this.enabled = false + } + + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled + } + + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) + } + } + + if (e) { + self.inState.click = !self.inState.click + if (self.isInStateTrue()) self.enter(self) + else self.leave(self) + } else { + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) + } + } + + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + if (that.$tip) { + that.$tip.detach() + } + that.$tip = null + that.$arrow = null + that.$viewport = null + }) + } + + + // TOOLTIP PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tooltip + + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip + + + // TOOLTIP NO CONFLICT + // =================== + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: popover.js v3.3.5 + * http://getbootstrap.com/javascript/#popovers + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // POPOVER PUBLIC CLASS DEFINITION + // =============================== + + var Popover = function (element, options) { + this.init('popover', element, options) + } + + if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') + + Popover.VERSION = '3.3.5' + + Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { + placement: 'right', + trigger: 'click', + content: '', + template: '' + }) + + + // NOTE: POPOVER EXTENDS tooltip.js + // ================================ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) + + Popover.prototype.constructor = Popover + + Popover.prototype.getDefaults = function () { + return Popover.DEFAULTS + } + + Popover.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + var content = this.getContent() + + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) + $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events + this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' + ](content) + + $tip.removeClass('fade top bottom left right in') + + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() + } + + Popover.prototype.hasContent = function () { + return this.getTitle() || this.getContent() + } + + Popover.prototype.getContent = function () { + var $e = this.$element + var o = this.options + + return $e.attr('data-content') + || (typeof o.content == 'function' ? + o.content.call($e[0]) : + o.content) + } + + Popover.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.arrow')) + } + + + // POPOVER PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + + if (!data && /destroy|hide/.test(option)) return + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.popover + + $.fn.popover = Plugin + $.fn.popover.Constructor = Popover + + + // POPOVER NO CONFLICT + // =================== + + $.fn.popover.noConflict = function () { + $.fn.popover = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: scrollspy.js v3.3.5 + * http://getbootstrap.com/javascript/#scrollspy + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // SCROLLSPY CLASS DEFINITION + // ========================== + + function ScrollSpy(element, options) { + this.$body = $(document.body) + this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) + this.options = $.extend({}, ScrollSpy.DEFAULTS, options) + this.selector = (this.options.target || '') + ' .nav li > a' + this.offsets = [] + this.targets = [] + this.activeTarget = null + this.scrollHeight = 0 + + this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) + this.refresh() + this.process() + } + + ScrollSpy.VERSION = '3.3.5' + + ScrollSpy.DEFAULTS = { + offset: 10 + } + + ScrollSpy.prototype.getScrollHeight = function () { + return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) + } + + ScrollSpy.prototype.refresh = function () { + var that = this + var offsetMethod = 'offset' + var offsetBase = 0 + + this.offsets = [] + this.targets = [] + this.scrollHeight = this.getScrollHeight() + + if (!$.isWindow(this.$scrollElement[0])) { + offsetMethod = 'position' + offsetBase = this.$scrollElement.scrollTop() + } + + this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + var href = $el.data('target') || $el.attr('href') + var $href = /^#./.test(href) && $(href) + + return ($href + && $href.length + && $href.is(':visible') + && [[$href[offsetMethod]().top + offsetBase, href]]) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + that.offsets.push(this[0]) + that.targets.push(this[1]) + }) + } + + ScrollSpy.prototype.process = function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + var scrollHeight = this.getScrollHeight() + var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() + var offsets = this.offsets + var targets = this.targets + var activeTarget = this.activeTarget + var i + + if (this.scrollHeight != scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) + } + + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() + } + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) + && this.activate(targets[i]) + } + } + + ScrollSpy.prototype.activate = function (target) { + this.activeTarget = target + + this.clear() + + var selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + var active = $(selector) + .parents('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active + .closest('li.dropdown') + .addClass('active') + } + + active.trigger('activate.bs.scrollspy') + } + + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + + + // SCROLLSPY PLUGIN DEFINITION + // =========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.scrollspy') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.scrollspy + + $.fn.scrollspy = Plugin + $.fn.scrollspy.Constructor = ScrollSpy + + + // SCROLLSPY NO CONFLICT + // ===================== + + $.fn.scrollspy.noConflict = function () { + $.fn.scrollspy = old + return this + } + + + // SCROLLSPY DATA-API + // ================== + + $(window).on('load.bs.scrollspy.data-api', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + Plugin.call($spy, $spy.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tab.js v3.3.5 + * http://getbootstrap.com/javascript/#tabs + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TAB CLASS DEFINITION + // ==================== + + var Tab = function (element) { + // jscs:disable requireDollarBeforejQueryAssignment + this.element = $(element) + // jscs:enable requireDollarBeforejQueryAssignment + } + + Tab.VERSION = '3.3.5' + + Tab.TRANSITION_DURATION = 150 + + Tab.prototype.show = function () { + var $this = this.element + var $ul = $this.closest('ul:not(.dropdown-menu)') + var selector = $this.data('target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + if ($this.parent('li').hasClass('active')) return + + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] + }) + + $previous.trigger(hideEvent) + $this.trigger(showEvent) + + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return + + var $target = $(selector) + + this.activate($this.closest('li'), $ul) + this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) + $this.trigger({ + type: 'shown.bs.tab', + relatedTarget: $previous[0] + }) + }) + } + + Tab.prototype.activate = function (element, container, callback) { + var $active = container.find('> .active') + var transition = callback + && $.support.transition + && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) + + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if (element.parent('.dropdown-menu').length) { + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + } + + callback && callback() + } + + $active.length && transition ? + $active + .one('bsTransitionEnd', next) + .emulateTransitionEnd(Tab.TRANSITION_DURATION) : + next() + + $active.removeClass('in') + } + + + // TAB PLUGIN DEFINITION + // ===================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tab') + + if (!data) $this.data('bs.tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tab + + $.fn.tab = Plugin + $.fn.tab.Constructor = Tab + + + // TAB NO CONFLICT + // =============== + + $.fn.tab.noConflict = function () { + $.fn.tab = old + return this + } + + + // TAB DATA-API + // ============ + + var clickHandler = function (e) { + e.preventDefault() + Plugin.call($(this), 'show') + } + + $(document) + .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) + .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: affix.js v3.3.5 + * http://getbootstrap.com/javascript/#affix + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // AFFIX CLASS DEFINITION + // ====================== + + var Affix = function (element, options) { + this.options = $.extend({}, Affix.DEFAULTS, options) + + this.$target = $(this.options.target) + .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) + .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) + + this.$element = $(element) + this.affixed = null + this.unpin = null + this.pinnedOffset = null + + this.checkPosition() + } + + Affix.VERSION = '3.3.5' + + Affix.RESET = 'affix affix-top affix-bottom' + + Affix.DEFAULTS = { + offset: 0, + target: window + } + + Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + var targetHeight = this.$target.height() + + if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false + + if (this.affixed == 'bottom') { + if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' + return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' + } + + var initializing = this.affixed == null + var colliderTop = initializing ? scrollTop : position.top + var colliderHeight = initializing ? targetHeight : height + + if (offsetTop != null && scrollTop <= offsetTop) return 'top' + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = Math.max($(document).height(), $(document.body).height()) + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); diff --git a/js/bootstrap.min.js b/js/bootstrap.min.js new file mode 100644 index 0000000..133aeec --- /dev/null +++ b/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.5 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/js/clean-blog.js b/js/clean-blog.js new file mode 100644 index 0000000..f4dc6ac --- /dev/null +++ b/js/clean-blog.js @@ -0,0 +1,1058 @@ +/*! + * Clean Blog v1.0.0 (http://startbootstrap.com) + * Copyright 2015 Start Bootstrap + * Licensed under Apache 2.0 (https://github.com/IronSummitMedia/startbootstrap/blob/gh-pages/LICENSE) + */ + +// Tooltip Init +$(function() { + $("[data-toggle='tooltip']").tooltip(); +}); + +// Contact Form Scripts + +$(function() { + + $("input,textarea").jqBootstrapValidation({ + preventSubmit: true, + submitError: function($form, event, errors) { + // additional error messages or events + }, + submitSuccess: function($form, event) { + event.preventDefault(); // prevent default submit behaviour + // get values from FORM + var name = $("input#name").val(); + var email = $("input#email").val(); + var phone = $("input#phone").val(); + var message = $("textarea#message").val(); + var firstName = name; // For Success/Failure Message + // Check for white space in name for Success/Fail message + if (firstName.indexOf(' ') >= 0) { + firstName = name.split(' ').slice(0, -1).join(' '); + } + $.ajax({ + url: "././mail/contact_me.php", + type: "POST", + data: { + name: name, + phone: phone, + email: email, + message: message + }, + cache: false, + success: function() { + // Success message + $('#success').html("
"); + $('#success > .alert-success').html(""); + $('#success > .alert-success') + .append("Your message has been sent. "); + $('#success > .alert-success') + .append('
'); + + //clear all fields + $('#contactForm').trigger("reset"); + }, + error: function() { + // Fail message + $('#success').html("
"); + $('#success > .alert-danger').html(""); + $('#success > .alert-danger').append("Sorry " + firstName + ", it seems that my mail server is not responding. Please try again later!"); + $('#success > .alert-danger').append('
'); + //clear all fields + $('#contactForm').trigger("reset"); + }, + }) + }, + filter: function() { + return $(this).is(":visible"); + }, + }); + + $("a[data-toggle=\"tab\"]").click(function(e) { + e.preventDefault(); + $(this).tab("show"); + }); +}); + + +/*When clicking on Full hide fail/success boxes */ +$('#name').focus(function() { + $('#success').html(''); +}); + +// jqBootstrapValidation +// * A plugin for automating validation on Twitter Bootstrap formatted forms. +// * +// * v1.3.6 +// * +// * License: MIT - see LICENSE file +// * +// * http://ReactiveRaven.github.com/jqBootstrapValidation/ + + +(function($) { + + var createdElements = []; + + var defaults = { + options: { + prependExistingHelpBlock: false, + sniffHtml: true, // sniff for 'required', 'maxlength', etc + preventSubmit: true, // stop the form submit event from firing if validation fails + submitError: false, // function called if there is an error when trying to submit + submitSuccess: false, // function called just before a successful submit event is sent to the server + semanticallyStrict: false, // set to true to tidy up generated HTML output + autoAdd: { + helpBlocks: true + }, + filter: function() { + // return $(this).is(":visible"); // only validate elements you can see + return true; // validate everything + } + }, + methods: { + init: function(options) { + + var settings = $.extend(true, {}, defaults); + + settings.options = $.extend(true, settings.options, options); + + var $siblingElements = this; + + var uniqueForms = $.unique( + $siblingElements.map(function() { + return $(this).parents("form")[0]; + }).toArray() + ); + + $(uniqueForms).bind("submit", function(e) { + var $form = $(this); + var warningsFound = 0; + var $inputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter); + $inputs.trigger("submit.validation").trigger("validationLostFocus.validation"); + + $inputs.each(function(i, el) { + var $this = $(el), + $controlGroup = $this.parents(".form-group").first(); + if ( + $controlGroup.hasClass("warning") + ) { + $controlGroup.removeClass("warning").addClass("error"); + warningsFound++; + } + }); + + $inputs.trigger("validationLostFocus.validation"); + + if (warningsFound) { + if (settings.options.preventSubmit) { + e.preventDefault(); + } + $form.addClass("error"); + if ($.isFunction(settings.options.submitError)) { + settings.options.submitError($form, e, $inputs.jqBootstrapValidation("collectErrors", true)); + } + } else { + $form.removeClass("error"); + if ($.isFunction(settings.options.submitSuccess)) { + settings.options.submitSuccess($form, e); + } + } + }); + + return this.each(function() { + + // Get references to everything we're interested in + var $this = $(this), + $controlGroup = $this.parents(".form-group").first(), + $helpBlock = $controlGroup.find(".help-block").first(), + $form = $this.parents("form").first(), + validatorNames = []; + + // create message container if not exists + if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) { + $helpBlock = $('
'); + $controlGroup.find('.controls').append($helpBlock); + createdElements.push($helpBlock[0]); + } + + // ============================================================= + // SNIFF HTML FOR VALIDATORS + // ============================================================= + + // *snort sniff snuffle* + + if (settings.options.sniffHtml) { + var message = ""; + // --------------------------------------------------------- + // PATTERN + // --------------------------------------------------------- + if ($this.attr("pattern") !== undefined) { + message = "Not in the expected format"; + if ($this.data("validationPatternMessage")) { + message = $this.data("validationPatternMessage"); + } + $this.data("validationPatternMessage", message); + $this.data("validationPatternRegex", $this.attr("pattern")); + } + // --------------------------------------------------------- + // MAX + // --------------------------------------------------------- + if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) { + var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax")); + message = "Too high: Maximum of '" + max + "'"; + if ($this.data("validationMaxMessage")) { + message = $this.data("validationMaxMessage"); + } + $this.data("validationMaxMessage", message); + $this.data("validationMaxMax", max); + } + // --------------------------------------------------------- + // MIN + // --------------------------------------------------------- + if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) { + var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin")); + message = "Too low: Minimum of '" + min + "'"; + if ($this.data("validationMinMessage")) { + message = $this.data("validationMinMessage"); + } + $this.data("validationMinMessage", message); + $this.data("validationMinMin", min); + } + // --------------------------------------------------------- + // MAXLENGTH + // --------------------------------------------------------- + if ($this.attr("maxlength") !== undefined) { + message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters"; + if ($this.data("validationMaxlengthMessage")) { + message = $this.data("validationMaxlengthMessage"); + } + $this.data("validationMaxlengthMessage", message); + $this.data("validationMaxlengthMaxlength", $this.attr("maxlength")); + } + // --------------------------------------------------------- + // MINLENGTH + // --------------------------------------------------------- + if ($this.attr("minlength") !== undefined) { + message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters"; + if ($this.data("validationMinlengthMessage")) { + message = $this.data("validationMinlengthMessage"); + } + $this.data("validationMinlengthMessage", message); + $this.data("validationMinlengthMinlength", $this.attr("minlength")); + } + // --------------------------------------------------------- + // REQUIRED + // --------------------------------------------------------- + if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) { + message = settings.builtInValidators.required.message; + if ($this.data("validationRequiredMessage")) { + message = $this.data("validationRequiredMessage"); + } + $this.data("validationRequiredMessage", message); + } + // --------------------------------------------------------- + // NUMBER + // --------------------------------------------------------- + if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") { + message = settings.builtInValidators.number.message; + if ($this.data("validationNumberMessage")) { + message = $this.data("validationNumberMessage"); + } + $this.data("validationNumberMessage", message); + } + // --------------------------------------------------------- + // EMAIL + // --------------------------------------------------------- + if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") { + message = "Not a valid email address"; + if ($this.data("validationValidemailMessage")) { + message = $this.data("validationValidemailMessage"); + } else if ($this.data("validationEmailMessage")) { + message = $this.data("validationEmailMessage"); + } + $this.data("validationValidemailMessage", message); + } + // --------------------------------------------------------- + // MINCHECKED + // --------------------------------------------------------- + if ($this.attr("minchecked") !== undefined) { + message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required"; + if ($this.data("validationMincheckedMessage")) { + message = $this.data("validationMincheckedMessage"); + } + $this.data("validationMincheckedMessage", message); + $this.data("validationMincheckedMinchecked", $this.attr("minchecked")); + } + // --------------------------------------------------------- + // MAXCHECKED + // --------------------------------------------------------- + if ($this.attr("maxchecked") !== undefined) { + message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required"; + if ($this.data("validationMaxcheckedMessage")) { + message = $this.data("validationMaxcheckedMessage"); + } + $this.data("validationMaxcheckedMessage", message); + $this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked")); + } + } + + // ============================================================= + // COLLECT VALIDATOR NAMES + // ============================================================= + + // Get named validators + if ($this.data("validation") !== undefined) { + validatorNames = $this.data("validation").split(","); + } + + // Get extra ones defined on the element's data attributes + $.each($this.data(), function(i, el) { + var parts = i.replace(/([A-Z])/g, ",$1").split(","); + if (parts[0] === "validation" && parts[1]) { + validatorNames.push(parts[1]); + } + }); + + // ============================================================= + // NORMALISE VALIDATOR NAMES + // ============================================================= + + var validatorNamesToInspect = validatorNames; + var newValidatorNamesToInspect = []; + + do // repeatedly expand 'shortcut' validators into their real validators + { + // Uppercase only the first letter of each name + $.each(validatorNames, function(i, el) { + validatorNames[i] = formatValidatorName(el); + }); + + // Remove duplicate validator names + validatorNames = $.unique(validatorNames); + + // Pull out the new validator names from each shortcut + newValidatorNamesToInspect = []; + $.each(validatorNamesToInspect, function(i, el) { + if ($this.data("validation" + el + "Shortcut") !== undefined) { + // Are these custom validators? + // Pull them out! + $.each($this.data("validation" + el + "Shortcut").split(","), function(i2, el2) { + newValidatorNamesToInspect.push(el2); + }); + } else if (settings.builtInValidators[el.toLowerCase()]) { + // Is this a recognised built-in? + // Pull it out! + var validator = settings.builtInValidators[el.toLowerCase()]; + if (validator.type.toLowerCase() === "shortcut") { + $.each(validator.shortcut.split(","), function(i, el) { + el = formatValidatorName(el); + newValidatorNamesToInspect.push(el); + validatorNames.push(el); + }); + } + } + }); + + validatorNamesToInspect = newValidatorNamesToInspect; + + } while (validatorNamesToInspect.length > 0) + + // ============================================================= + // SET UP VALIDATOR ARRAYS + // ============================================================= + + var validators = {}; + + $.each(validatorNames, function(i, el) { + // Set up the 'override' message + var message = $this.data("validation" + el + "Message"); + var hasOverrideMessage = (message !== undefined); + var foundValidator = false; + message = + ( + message ? message : "'" + el + "' validation failed " + ); + + $.each( + settings.validatorTypes, + function(validatorType, validatorTemplate) { + if (validators[validatorType] === undefined) { + validators[validatorType] = []; + } + if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) { + validators[validatorType].push( + $.extend( + true, { + name: formatValidatorName(validatorTemplate.name), + message: message + }, + validatorTemplate.init($this, el) + ) + ); + foundValidator = true; + } + } + ); + + if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) { + + var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]); + if (hasOverrideMessage) { + validator.message = message; + } + var validatorType = validator.type.toLowerCase(); + + if (validatorType === "shortcut") { + foundValidator = true; + } else { + $.each( + settings.validatorTypes, + function(validatorTemplateType, validatorTemplate) { + if (validators[validatorTemplateType] === undefined) { + validators[validatorTemplateType] = []; + } + if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) { + $this.data("validation" + el + formatValidatorName(validatorTemplate.name), validator[validatorTemplate.name.toLowerCase()]); + validators[validatorType].push( + $.extend( + validator, + validatorTemplate.init($this, el) + ) + ); + foundValidator = true; + } + } + ); + } + } + + if (!foundValidator) { + $.error("Cannot find validation info for '" + el + "'"); + } + }); + + // ============================================================= + // STORE FALLBACK VALUES + // ============================================================= + + $helpBlock.data( + "original-contents", ( + $helpBlock.data("original-contents") ? $helpBlock.data("original-contents") : $helpBlock.html() + ) + ); + + $helpBlock.data( + "original-role", ( + $helpBlock.data("original-role") ? $helpBlock.data("original-role") : $helpBlock.attr("role") + ) + ); + + $controlGroup.data( + "original-classes", ( + $controlGroup.data("original-clases") ? $controlGroup.data("original-classes") : $controlGroup.attr("class") + ) + ); + + $this.data( + "original-aria-invalid", ( + $this.data("original-aria-invalid") ? $this.data("original-aria-invalid") : $this.attr("aria-invalid") + ) + ); + + // ============================================================= + // VALIDATION + // ============================================================= + + $this.bind( + "validation.validation", + function(event, params) { + + var value = getValue($this); + + // Get a list of the errors to apply + var errorsFound = []; + + $.each(validators, function(validatorType, validatorTypeArray) { + if (value || value.length || (params && params.includeEmpty) || (!!settings.validatorTypes[validatorType].blockSubmit && params && !!params.submitting)) { + $.each(validatorTypeArray, function(i, validator) { + if (settings.validatorTypes[validatorType].validate($this, value, validator)) { + errorsFound.push(validator.message); + } + }); + } + }); + + return errorsFound; + } + ); + + $this.bind( + "getValidators.validation", + function() { + return validators; + } + ); + + // ============================================================= + // WATCH FOR CHANGES + // ============================================================= + $this.bind( + "submit.validation", + function() { + return $this.triggerHandler("change.validation", { + submitting: true + }); + } + ); + $this.bind( + [ + "keyup", + "focus", + "blur", + "click", + "keydown", + "keypress", + "change" + ].join(".validation ") + ".validation", + function(e, params) { + + var value = getValue($this); + + var errorsFound = []; + + $controlGroup.find("input,textarea,select").each(function(i, el) { + var oldCount = errorsFound.length; + $.each($(el).triggerHandler("validation.validation", params), function(j, message) { + errorsFound.push(message); + }); + if (errorsFound.length > oldCount) { + $(el).attr("aria-invalid", "true"); + } else { + var original = $this.data("original-aria-invalid"); + $(el).attr("aria-invalid", (original !== undefined ? original : false)); + } + }); + + $form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation"); + + errorsFound = $.unique(errorsFound.sort()); + + // Were there any errors? + if (errorsFound.length) { + // Better flag it up as a warning. + $controlGroup.removeClass("success error").addClass("warning"); + + // How many errors did we find? + if (settings.options.semanticallyStrict && errorsFound.length === 1) { + // Only one? Being strict? Just output it. + $helpBlock.html(errorsFound[0] + + (settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "")); + } else { + // Multiple? Being sloppy? Glue them together into an UL. + $helpBlock.html("
  • " + errorsFound.join("
  • ") + "
" + + (settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "")); + } + } else { + $controlGroup.removeClass("warning error success"); + if (value.length > 0) { + $controlGroup.addClass("success"); + } + $helpBlock.html($helpBlock.data("original-contents")); + } + + if (e.type === "blur") { + $controlGroup.removeClass("success"); + } + } + ); + $this.bind("validationLostFocus.validation", function() { + $controlGroup.removeClass("success"); + }); + }); + }, + destroy: function() { + + return this.each( + function() { + + var + $this = $(this), + $controlGroup = $this.parents(".form-group").first(), + $helpBlock = $controlGroup.find(".help-block").first(); + + // remove our events + $this.unbind('.validation'); // events are namespaced. + // reset help text + $helpBlock.html($helpBlock.data("original-contents")); + // reset classes + $controlGroup.attr("class", $controlGroup.data("original-classes")); + // reset aria + $this.attr("aria-invalid", $this.data("original-aria-invalid")); + // reset role + $helpBlock.attr("role", $this.data("original-role")); + // remove all elements we created + if (createdElements.indexOf($helpBlock[0]) > -1) { + $helpBlock.remove(); + } + + } + ); + + }, + collectErrors: function(includeEmpty) { + + var errorMessages = {}; + this.each(function(i, el) { + var $el = $(el); + var name = $el.attr("name"); + var errors = $el.triggerHandler("validation.validation", { + includeEmpty: true + }); + errorMessages[name] = $.extend(true, errors, errorMessages[name]); + }); + + $.each(errorMessages, function(i, el) { + if (el.length === 0) { + delete errorMessages[i]; + } + }); + + return errorMessages; + + }, + hasErrors: function() { + + var errorMessages = []; + + this.each(function(i, el) { + errorMessages = errorMessages.concat( + $(el).triggerHandler("getValidators.validation") ? $(el).triggerHandler("validation.validation", { + submitting: true + }) : [] + ); + }); + + return (errorMessages.length > 0); + }, + override: function(newDefaults) { + defaults = $.extend(true, defaults, newDefaults); + } + }, + validatorTypes: { + callback: { + name: "callback", + init: function($this, name) { + return { + validatorName: name, + callback: $this.data("validation" + name + "Callback"), + lastValue: $this.val(), + lastValid: true, + lastFinished: true + }; + }, + validate: function($this, value, validator) { + if (validator.lastValue === value && validator.lastFinished) { + return !validator.lastValid; + } + + if (validator.lastFinished === true) { + validator.lastValue = value; + validator.lastValid = true; + validator.lastFinished = false; + + var rrjqbvValidator = validator; + var rrjqbvThis = $this; + executeFunctionByName( + validator.callback, + window, + $this, + value, + function(data) { + if (rrjqbvValidator.lastValue === data.value) { + rrjqbvValidator.lastValid = data.valid; + if (data.message) { + rrjqbvValidator.message = data.message; + } + rrjqbvValidator.lastFinished = true; + rrjqbvThis.data("validation" + rrjqbvValidator.validatorName + "Message", rrjqbvValidator.message); + // Timeout is set to avoid problems with the events being considered 'already fired' + setTimeout(function() { + rrjqbvThis.trigger("change.validation"); + }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst + } + } + ); + } + + return false; + + } + }, + ajax: { + name: "ajax", + init: function($this, name) { + return { + validatorName: name, + url: $this.data("validation" + name + "Ajax"), + lastValue: $this.val(), + lastValid: true, + lastFinished: true + }; + }, + validate: function($this, value, validator) { + if ("" + validator.lastValue === "" + value && validator.lastFinished === true) { + return validator.lastValid === false; + } + + if (validator.lastFinished === true) { + validator.lastValue = value; + validator.lastValid = true; + validator.lastFinished = false; + $.ajax({ + url: validator.url, + data: "value=" + value + "&field=" + $this.attr("name"), + dataType: "json", + success: function(data) { + if ("" + validator.lastValue === "" + data.value) { + validator.lastValid = !!(data.valid); + if (data.message) { + validator.message = data.message; + } + validator.lastFinished = true; + $this.data("validation" + validator.validatorName + "Message", validator.message); + // Timeout is set to avoid problems with the events being considered 'already fired' + setTimeout(function() { + $this.trigger("change.validation"); + }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst + } + }, + failure: function() { + validator.lastValid = true; + validator.message = "ajax call failed"; + validator.lastFinished = true; + $this.data("validation" + validator.validatorName + "Message", validator.message); + // Timeout is set to avoid problems with the events being considered 'already fired' + setTimeout(function() { + $this.trigger("change.validation"); + }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst + } + }); + } + + return false; + + } + }, + regex: { + name: "regex", + init: function($this, name) { + return { + regex: regexFromString($this.data("validation" + name + "Regex")) + }; + }, + validate: function($this, value, validator) { + return (!validator.regex.test(value) && !validator.negative) || (validator.regex.test(value) && validator.negative); + } + }, + required: { + name: "required", + init: function($this, name) { + return {}; + }, + validate: function($this, value, validator) { + return !!(value.length === 0 && !validator.negative) || !!(value.length > 0 && validator.negative); + }, + blockSubmit: true + }, + match: { + name: "match", + init: function($this, name) { + var element = $this.parents("form").first().find("[name=\"" + $this.data("validation" + name + "Match") + "\"]").first(); + element.bind("validation.validation", function() { + $this.trigger("change.validation", { + submitting: true + }); + }); + return { + "element": element + }; + }, + validate: function($this, value, validator) { + return (value !== validator.element.val() && !validator.negative) || (value === validator.element.val() && validator.negative); + }, + blockSubmit: true + }, + max: { + name: "max", + init: function($this, name) { + return { + max: $this.data("validation" + name + "Max") + }; + }, + validate: function($this, value, validator) { + return (parseFloat(value, 10) > parseFloat(validator.max, 10) && !validator.negative) || (parseFloat(value, 10) <= parseFloat(validator.max, 10) && validator.negative); + } + }, + min: { + name: "min", + init: function($this, name) { + return { + min: $this.data("validation" + name + "Min") + }; + }, + validate: function($this, value, validator) { + return (parseFloat(value) < parseFloat(validator.min) && !validator.negative) || (parseFloat(value) >= parseFloat(validator.min) && validator.negative); + } + }, + maxlength: { + name: "maxlength", + init: function($this, name) { + return { + maxlength: $this.data("validation" + name + "Maxlength") + }; + }, + validate: function($this, value, validator) { + return ((value.length > validator.maxlength) && !validator.negative) || ((value.length <= validator.maxlength) && validator.negative); + } + }, + minlength: { + name: "minlength", + init: function($this, name) { + return { + minlength: $this.data("validation" + name + "Minlength") + }; + }, + validate: function($this, value, validator) { + return ((value.length < validator.minlength) && !validator.negative) || ((value.length >= validator.minlength) && validator.negative); + } + }, + maxchecked: { + name: "maxchecked", + init: function($this, name) { + var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]"); + elements.bind("click.validation", function() { + $this.trigger("change.validation", { + includeEmpty: true + }); + }); + return { + maxchecked: $this.data("validation" + name + "Maxchecked"), + elements: elements + }; + }, + validate: function($this, value, validator) { + return (validator.elements.filter(":checked").length > validator.maxchecked && !validator.negative) || (validator.elements.filter(":checked").length <= validator.maxchecked && validator.negative); + }, + blockSubmit: true + }, + minchecked: { + name: "minchecked", + init: function($this, name) { + var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]"); + elements.bind("click.validation", function() { + $this.trigger("change.validation", { + includeEmpty: true + }); + }); + return { + minchecked: $this.data("validation" + name + "Minchecked"), + elements: elements + }; + }, + validate: function($this, value, validator) { + return (validator.elements.filter(":checked").length < validator.minchecked && !validator.negative) || (validator.elements.filter(":checked").length >= validator.minchecked && validator.negative); + }, + blockSubmit: true + } + }, + builtInValidators: { + email: { + name: "Email", + type: "shortcut", + shortcut: "validemail" + }, + validemail: { + name: "Validemail", + type: "regex", + regex: "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\.[A-Za-z]{2,4}", + message: "Not a valid email address" + }, + passwordagain: { + name: "Passwordagain", + type: "match", + match: "password", + message: "Does not match the given password" + }, + positive: { + name: "Positive", + type: "shortcut", + shortcut: "number,positivenumber" + }, + negative: { + name: "Negative", + type: "shortcut", + shortcut: "number,negativenumber" + }, + number: { + name: "Number", + type: "regex", + regex: "([+-]?\\\d+(\\\.\\\d*)?([eE][+-]?[0-9]+)?)?", + message: "Must be a number" + }, + integer: { + name: "Integer", + type: "regex", + regex: "[+-]?\\\d+", + message: "No decimal places allowed" + }, + positivenumber: { + name: "Positivenumber", + type: "min", + min: 0, + message: "Must be a positive number" + }, + negativenumber: { + name: "Negativenumber", + type: "max", + max: 0, + message: "Must be a negative number" + }, + required: { + name: "Required", + type: "required", + message: "This is required" + }, + checkone: { + name: "Checkone", + type: "minchecked", + minchecked: 1, + message: "Check at least one option" + } + } + }; + + var formatValidatorName = function(name) { + return name + .toLowerCase() + .replace( + /(^|\s)([a-z])/g, + function(m, p1, p2) { + return p1 + p2.toUpperCase(); + } + ); + }; + + var getValue = function($this) { + // Extract the value we're talking about + var value = $this.val(); + var type = $this.attr("type"); + if (type === "checkbox") { + value = ($this.is(":checked") ? value : ""); + } + if (type === "radio") { + value = ($('input[name="' + $this.attr("name") + '"]:checked').length > 0 ? value : ""); + } + return value; + }; + + function regexFromString(inputstring) { + return new RegExp("^" + inputstring + "$"); + } + + /** + * Thanks to Jason Bunting via StackOverflow.com + * + * http://stackoverflow.com/questions/359788/how-to-execute-a-javascript-function-when-i-have-its-name-as-a-string#answer-359910 + * Short link: http://tinyurl.com/executeFunctionByName + **/ + function executeFunctionByName(functionName, context /*, args*/ ) { + var args = Array.prototype.slice.call(arguments).splice(2); + var namespaces = functionName.split("."); + var func = namespaces.pop(); + for (var i = 0; i < namespaces.length; i++) { + context = context[namespaces[i]]; + } + return context[func].apply(this, args); + } + + $.fn.jqBootstrapValidation = function(method) { + + if (defaults.methods[method]) { + return defaults.methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method) { + return defaults.methods.init.apply(this, arguments); + } else { + $.error('Method ' + method + ' does not exist on jQuery.jqBootstrapValidation'); + return null; + } + + }; + + $.jqBootstrapValidation = function(options) { + $(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this, arguments); + }; + +})(jQuery); + +// make all images responsive +$(function() { + $("img").addClass("img-responsive"); +}); + +// responsive tables +$(document).ready(function() { + $("table").wrap("
"); + $("table").addClass("table"); +}); + +// responsive embed videos +$(document).ready(function () { + $('iframe[src*="youtube.com"]').wrap('
'); + $('iframe[src*="youtube.com"]').addClass('embed-responsive-item'); + $('iframe[src*="vimeo.com"]').wrap('
'); + $('iframe[src*="vimeo.com"]').addClass('embed-responsive-item'); +}); + +// Floating label headings for the contact form +$(function() { + $("body").on("input propertychange", ".floating-label-form-group", function(e) { + $(this).toggleClass("floating-label-form-group-with-value", !!$(e.target).val()); + }).on("focus", ".floating-label-form-group", function() { + $(this).addClass("floating-label-form-group-with-focus"); + }).on("blur", ".floating-label-form-group", function() { + $(this).removeClass("floating-label-form-group-with-focus"); + }); +}); + +// Navigation Scripts to Show Header on Scroll-Up +jQuery(document).ready(function($) { + var MQL = 1170; + + //primary navigation slide-in effect + if ($(window).width() > MQL) { + var headerHeight = $('.navbar-custom').height(); + $(window).on('scroll', { + previousTop: 0 + }, + function() { + var currentTop = $(window).scrollTop(); + //check if user is scrolling up + if (currentTop < this.previousTop) { + //if scrolling up... + if (currentTop > 0 && $('.navbar-custom').hasClass('is-fixed')) { + $('.navbar-custom').addClass('is-visible'); + } else { + $('.navbar-custom').removeClass('is-visible is-fixed'); + } + } else { + //if scrolling down... + $('.navbar-custom').removeClass('is-visible'); + if (currentTop > headerHeight && !$('.navbar-custom').hasClass('is-fixed')) $('.navbar-custom').addClass('is-fixed'); + } + this.previousTop = currentTop; + }); + } +}); diff --git a/js/clean-blog.min.js b/js/clean-blog.min.js new file mode 100644 index 0000000..8e4bcad --- /dev/null +++ b/js/clean-blog.min.js @@ -0,0 +1,7 @@ +/*! + * Clean Blog v1.0.0 (http://startbootstrap.com) + * Copyright 2015 Start Bootstrap + * Licensed under Apache 2.0 (https://github.com/IronSummitMedia/startbootstrap/blob/gh-pages/LICENSE) + */ + +$(function(){$("[data-toggle='tooltip']").tooltip()}),$(function(){$("input,textarea").jqBootstrapValidation({preventSubmit:!0,submitError:function(){},submitSuccess:function(a,b){b.preventDefault();var c=$("input#name").val(),d=$("input#email").val(),e=$("input#phone").val(),f=$("textarea#message").val(),g=c;g.indexOf(" ")>=0&&(g=c.split(" ").slice(0,-1).join(" ")),$.ajax({url:"././mail/contact_me.php",type:"POST",data:{name:c,phone:e,email:d,message:f},cache:!1,success:function(){$("#success").html("
"),$("#success > .alert-success").html(""),$("#success > .alert-success").append("Your message has been sent. "),$("#success > .alert-success").append("
"),$("#contactForm").trigger("reset")},error:function(){$("#success").html("
"),$("#success > .alert-danger").html(""),$("#success > .alert-danger").append("Sorry "+g+", it seems that my mail server is not responding. Please try again later!"),$("#success > .alert-danger").append("
"),$("#contactForm").trigger("reset")}})},filter:function(){return $(this).is(":visible")}}),$('a[data-toggle="tab"]').click(function(a){a.preventDefault(),$(this).tab("show")})}),$("#name").focus(function(){$("#success").html("")}),function(a){function b(a){return new RegExp("^"+a+"$")}function c(a,b){for(var c=Array.prototype.slice.call(arguments).splice(2),d=a.split("."),e=d.pop(),f=0;f'),e.find(".controls").append(h),d.push(h[0])),c.options.sniffHtml){var k="";if(void 0!==b.attr("pattern")&&(k="Not in the expected format",b.data("validationPatternMessage")&&(k=b.data("validationPatternMessage")),b.data("validationPatternMessage",k),b.data("validationPatternRegex",b.attr("pattern"))),void 0!==b.attr("max")||void 0!==b.attr("aria-valuemax")){var l=b.attr(void 0!==b.attr("max")?"max":"aria-valuemax");k="Too high: Maximum of '"+l+"'",b.data("validationMaxMessage")&&(k=b.data("validationMaxMessage")),b.data("validationMaxMessage",k),b.data("validationMaxMax",l)}if(void 0!==b.attr("min")||void 0!==b.attr("aria-valuemin")){var m=b.attr(void 0!==b.attr("min")?"min":"aria-valuemin");k="Too low: Minimum of '"+m+"'",b.data("validationMinMessage")&&(k=b.data("validationMinMessage")),b.data("validationMinMessage",k),b.data("validationMinMin",m)}void 0!==b.attr("maxlength")&&(k="Too long: Maximum of '"+b.attr("maxlength")+"' characters",b.data("validationMaxlengthMessage")&&(k=b.data("validationMaxlengthMessage")),b.data("validationMaxlengthMessage",k),b.data("validationMaxlengthMaxlength",b.attr("maxlength"))),void 0!==b.attr("minlength")&&(k="Too short: Minimum of '"+b.attr("minlength")+"' characters",b.data("validationMinlengthMessage")&&(k=b.data("validationMinlengthMessage")),b.data("validationMinlengthMessage",k),b.data("validationMinlengthMinlength",b.attr("minlength"))),(void 0!==b.attr("required")||void 0!==b.attr("aria-required"))&&(k=c.builtInValidators.required.message,b.data("validationRequiredMessage")&&(k=b.data("validationRequiredMessage")),b.data("validationRequiredMessage",k)),void 0!==b.attr("type")&&"number"===b.attr("type").toLowerCase()&&(k=c.builtInValidators.number.message,b.data("validationNumberMessage")&&(k=b.data("validationNumberMessage")),b.data("validationNumberMessage",k)),void 0!==b.attr("type")&&"email"===b.attr("type").toLowerCase()&&(k="Not a valid email address",b.data("validationValidemailMessage")?k=b.data("validationValidemailMessage"):b.data("validationEmailMessage")&&(k=b.data("validationEmailMessage")),b.data("validationValidemailMessage",k)),void 0!==b.attr("minchecked")&&(k="Not enough options checked; Minimum of '"+b.attr("minchecked")+"' required",b.data("validationMincheckedMessage")&&(k=b.data("validationMincheckedMessage")),b.data("validationMincheckedMessage",k),b.data("validationMincheckedMinchecked",b.attr("minchecked"))),void 0!==b.attr("maxchecked")&&(k="Too many options checked; Maximum of '"+b.attr("maxchecked")+"' required",b.data("validationMaxcheckedMessage")&&(k=b.data("validationMaxcheckedMessage")),b.data("validationMaxcheckedMessage",k),b.data("validationMaxcheckedMaxchecked",b.attr("maxchecked")))}void 0!==b.data("validation")&&(j=b.data("validation").split(",")),a.each(b.data(),function(a){var b=a.replace(/([A-Z])/g,",$1").split(",");"validation"===b[0]&&b[1]&&j.push(b[1])});var n=j,o=[];do a.each(j,function(a,b){j[a]=f(b)}),j=a.unique(j),o=[],a.each(n,function(d,e){if(void 0!==b.data("validation"+e+"Shortcut"))a.each(b.data("validation"+e+"Shortcut").split(","),function(a,b){o.push(b)});else if(c.builtInValidators[e.toLowerCase()]){var g=c.builtInValidators[e.toLowerCase()];"shortcut"===g.type.toLowerCase()&&a.each(g.shortcut.split(","),function(a,b){b=f(b),o.push(b),j.push(b)})}}),n=o;while(n.length>0);var p={};a.each(j,function(d,e){var g=b.data("validation"+e+"Message"),h=void 0!==g,i=!1;if(g=g?g:"'"+e+"' validation failed ",a.each(c.validatorTypes,function(c,d){void 0===p[c]&&(p[c]=[]),i||void 0===b.data("validation"+e+f(d.name))||(p[c].push(a.extend(!0,{name:f(d.name),message:g},d.init(b,e))),i=!0)}),!i&&c.builtInValidators[e.toLowerCase()]){var j=a.extend(!0,{},c.builtInValidators[e.toLowerCase()]);h&&(j.message=g);var k=j.type.toLowerCase();"shortcut"===k?i=!0:a.each(c.validatorTypes,function(c,d){void 0===p[c]&&(p[c]=[]),i||k!==c.toLowerCase()||(b.data("validation"+e+f(d.name),j[d.name.toLowerCase()]),p[k].push(a.extend(j,d.init(b,e))),i=!0)})}i||a.error("Cannot find validation info for '"+e+"'")}),h.data("original-contents",h.data("original-contents")?h.data("original-contents"):h.html()),h.data("original-role",h.data("original-role")?h.data("original-role"):h.attr("role")),e.data("original-classes",e.data("original-clases")?e.data("original-classes"):e.attr("class")),b.data("original-aria-invalid",b.data("original-aria-invalid")?b.data("original-aria-invalid"):b.attr("aria-invalid")),b.bind("validation.validation",function(d,e){var f=g(b),h=[];return a.each(p,function(d,g){(f||f.length||e&&e.includeEmpty||c.validatorTypes[d].blockSubmit&&e&&e.submitting)&&a.each(g,function(a,e){c.validatorTypes[d].validate(b,f,e)&&h.push(e.message)})}),h}),b.bind("getValidators.validation",function(){return p}),b.bind("submit.validation",function(){return b.triggerHandler("change.validation",{submitting:!0})}),b.bind(["keyup","focus","blur","click","keydown","keypress","change"].join(".validation ")+".validation",function(d,f){var j=g(b),k=[];e.find("input,textarea,select").each(function(c,d){var e=k.length;if(a.each(a(d).triggerHandler("validation.validation",f),function(a,b){k.push(b)}),k.length>e)a(d).attr("aria-invalid","true");else{var g=b.data("original-aria-invalid");a(d).attr("aria-invalid",void 0!==g?g:!1)}}),i.find("input,select,textarea").not(b).not('[name="'+b.attr("name")+'"]').trigger("validationLostFocus.validation"),k=a.unique(k.sort()),k.length?(e.removeClass("success error").addClass("warning"),h.html(c.options.semanticallyStrict&&1===k.length?k[0]+(c.options.prependExistingHelpBlock?h.data("original-contents"):""):'
  • '+k.join("
  • ")+"
"+(c.options.prependExistingHelpBlock?h.data("original-contents"):""))):(e.removeClass("warning error success"),j.length>0&&e.addClass("success"),h.html(h.data("original-contents"))),"blur"===d.type&&e.removeClass("success")}),b.bind("validationLostFocus.validation",function(){e.removeClass("success")})})},destroy:function(){return this.each(function(){var b=a(this),c=b.parents(".form-group").first(),e=c.find(".help-block").first();b.unbind(".validation"),e.html(e.data("original-contents")),c.attr("class",c.data("original-classes")),b.attr("aria-invalid",b.data("original-aria-invalid")),e.attr("role",b.data("original-role")),d.indexOf(e[0])>-1&&e.remove()})},collectErrors:function(){var b={};return this.each(function(c,d){var e=a(d),f=e.attr("name"),g=e.triggerHandler("validation.validation",{includeEmpty:!0});b[f]=a.extend(!0,g,b[f])}),a.each(b,function(a,c){0===c.length&&delete b[a]}),b},hasErrors:function(){var b=[];return this.each(function(c,d){b=b.concat(a(d).triggerHandler("getValidators.validation")?a(d).triggerHandler("validation.validation",{submitting:!0}):[])}),b.length>0},override:function(b){e=a.extend(!0,e,b)}},validatorTypes:{callback:{name:"callback",init:function(a,b){return{validatorName:b,callback:a.data("validation"+b+"Callback"),lastValue:a.val(),lastValid:!0,lastFinished:!0}},validate:function(a,b,d){if(d.lastValue===b&&d.lastFinished)return!d.lastValid;if(d.lastFinished===!0){d.lastValue=b,d.lastValid=!0,d.lastFinished=!1;var e=d,f=a;c(d.callback,window,a,b,function(a){e.lastValue===a.value&&(e.lastValid=a.valid,a.message&&(e.message=a.message),e.lastFinished=!0,f.data("validation"+e.validatorName+"Message",e.message),setTimeout(function(){f.trigger("change.validation")},1))})}return!1}},ajax:{name:"ajax",init:function(a,b){return{validatorName:b,url:a.data("validation"+b+"Ajax"),lastValue:a.val(),lastValid:!0,lastFinished:!0}},validate:function(b,c,d){return""+d.lastValue==""+c&&d.lastFinished===!0?d.lastValid===!1:(d.lastFinished===!0&&(d.lastValue=c,d.lastValid=!0,d.lastFinished=!1,a.ajax({url:d.url,data:"value="+c+"&field="+b.attr("name"),dataType:"json",success:function(a){""+d.lastValue==""+a.value&&(d.lastValid=!!a.valid,a.message&&(d.message=a.message),d.lastFinished=!0,b.data("validation"+d.validatorName+"Message",d.message),setTimeout(function(){b.trigger("change.validation")},1))},failure:function(){d.lastValid=!0,d.message="ajax call failed",d.lastFinished=!0,b.data("validation"+d.validatorName+"Message",d.message),setTimeout(function(){b.trigger("change.validation")},1)}})),!1)}},regex:{name:"regex",init:function(a,c){return{regex:b(a.data("validation"+c+"Regex"))}},validate:function(a,b,c){return!c.regex.test(b)&&!c.negative||c.regex.test(b)&&c.negative}},required:{name:"required",init:function(){return{}},validate:function(a,b,c){return!(0!==b.length||c.negative)||!!(b.length>0&&c.negative)},blockSubmit:!0},match:{name:"match",init:function(a,b){var c=a.parents("form").first().find('[name="'+a.data("validation"+b+"Match")+'"]').first();return c.bind("validation.validation",function(){a.trigger("change.validation",{submitting:!0})}),{element:c}},validate:function(a,b,c){return b!==c.element.val()&&!c.negative||b===c.element.val()&&c.negative},blockSubmit:!0},max:{name:"max",init:function(a,b){return{max:a.data("validation"+b+"Max")}},validate:function(a,b,c){return parseFloat(b,10)>parseFloat(c.max,10)&&!c.negative||parseFloat(b,10)<=parseFloat(c.max,10)&&c.negative}},min:{name:"min",init:function(a,b){return{min:a.data("validation"+b+"Min")}},validate:function(a,b,c){return parseFloat(b)=parseFloat(c.min)&&c.negative}},maxlength:{name:"maxlength",init:function(a,b){return{maxlength:a.data("validation"+b+"Maxlength")}},validate:function(a,b,c){return b.length>c.maxlength&&!c.negative||b.length<=c.maxlength&&c.negative}},minlength:{name:"minlength",init:function(a,b){return{minlength:a.data("validation"+b+"Minlength")}},validate:function(a,b,c){return b.length=c.minlength&&c.negative}},maxchecked:{name:"maxchecked",init:function(a,b){var c=a.parents("form").first().find('[name="'+a.attr("name")+'"]');return c.bind("click.validation",function(){a.trigger("change.validation",{includeEmpty:!0})}),{maxchecked:a.data("validation"+b+"Maxchecked"),elements:c}},validate:function(a,b,c){return c.elements.filter(":checked").length>c.maxchecked&&!c.negative||c.elements.filter(":checked").length<=c.maxchecked&&c.negative},blockSubmit:!0},minchecked:{name:"minchecked",init:function(a,b){var c=a.parents("form").first().find('[name="'+a.attr("name")+'"]');return c.bind("click.validation",function(){a.trigger("change.validation",{includeEmpty:!0})}),{minchecked:a.data("validation"+b+"Minchecked"),elements:c}},validate:function(a,b,c){return c.elements.filter(":checked").length=c.minchecked&&c.negative},blockSubmit:!0}},builtInValidators:{email:{name:"Email",type:"shortcut",shortcut:"validemail"},validemail:{name:"Validemail",type:"regex",regex:"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}",message:"Not a valid email address"},passwordagain:{name:"Passwordagain",type:"match",match:"password",message:"Does not match the given password"},positive:{name:"Positive",type:"shortcut",shortcut:"number,positivenumber"},negative:{name:"Negative",type:"shortcut",shortcut:"number,negativenumber"},number:{name:"Number",type:"regex",regex:"([+-]?\\d+(\\.\\d*)?([eE][+-]?[0-9]+)?)?",message:"Must be a number"},integer:{name:"Integer",type:"regex",regex:"[+-]?\\d+",message:"No decimal places allowed"},positivenumber:{name:"Positivenumber",type:"min",min:0,message:"Must be a positive number"},negativenumber:{name:"Negativenumber",type:"max",max:0,message:"Must be a negative number"},required:{name:"Required",type:"required",message:"This is required"},checkone:{name:"Checkone",type:"minchecked",minchecked:1,message:"Check at least one option"}}},f=function(a){return a.toLowerCase().replace(/(^|\s)([a-z])/g,function(a,b,c){return b+c.toUpperCase()})},g=function(b){var c=b.val(),d=b.attr("type");return"checkbox"===d&&(c=b.is(":checked")?c:""),"radio"===d&&(c=a('input[name="'+b.attr("name")+'"]:checked').length>0?c:""),c};a.fn.jqBootstrapValidation=function(b){return e.methods[b]?e.methods[b].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof b&&b?(a.error("Method "+b+" does not exist on jQuery.jqBootstrapValidation"),null):e.methods.init.apply(this,arguments)},a.jqBootstrapValidation=function(){a(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this,arguments)}}(jQuery),$(function(){$("body").on("input propertychange",".floating-label-form-group",function(a){$(this).toggleClass("floating-label-form-group-with-value",!!$(a.target).val())}).on("focus",".floating-label-form-group",function(){$(this).addClass("floating-label-form-group-with-focus")}).on("blur",".floating-label-form-group",function(){$(this).removeClass("floating-label-form-group-with-focus")})}),jQuery(document).ready(function(a){var b=1170;if(a(window).width()>b){var c=a(".navbar-custom").height();a(window).on("scroll",{previousTop:0},function(){var b=a(window).scrollTop();b0&&a(".navbar-custom").hasClass("is-fixed")?a(".navbar-custom").addClass("is-visible"):a(".navbar-custom").removeClass("is-visible is-fixed"):(a(".navbar-custom").removeClass("is-visible"),b>c&&!a(".navbar-custom").hasClass("is-fixed")&&a(".navbar-custom").addClass("is-fixed")),this.previousTop=b})}});$(function(){$("img").addClass("img-responsive")});$(document).ready(function(){$("table").wrap("
");$("table").addClass("table table-hover")});$(document).ready(function(){$('iframe[src*="youtube.com"]').wrap('
');$('iframe[src*="youtube.com"]').addClass("embed-responsive-item");$('iframe[src*="vimeo.com"]').wrap('
');$('iframe[src*="vimeo.com"]').addClass("embed-responsive-item")}); diff --git a/js/jquery.js b/js/jquery.js new file mode 100644 index 0000000..79d631f --- /dev/null +++ b/js/jquery.js @@ -0,0 +1,9205 @@ +/*! + * jQuery JavaScript Library v2.1.3 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-12-18T15:11Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Support: Firefox 18+ +// Can't be in strict mode, several libs including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// + +var arr = []; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var support = {}; + + + +var + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + version = "2.1.3", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android<4.1 + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // 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 just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // 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 ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + 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: arr.sort, + splice: arr.splice +}; + +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; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // 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 ( i === length ) { + 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({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + // adding 1 corrects loss of precision from parseFloat (#15100) + return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; + }, + + isPlainObject: function( obj ) { + // Not plain objects: + // - Any object or value whose internal [[Class]] property is not "[object Object]" + // - DOM nodes + // - window + if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.constructor && + !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { + return false; + } + + // If the function hasn't returned already, we're confident that + // |obj| is a plain object, created by {} or constructed with new Object + return true; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + // Support: Android<4.0, iOS<6 (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + var script, + indirect = eval; + + code = jQuery.trim( code ); + + if ( code ) { + // If the code includes a valid, prologue position + // strict mode pragma, execute code by injecting a + // script tag into the document. + if ( code.indexOf("use strict") === 1 ) { + script = document.createElement("script"); + script.text = code; + document.head.appendChild( script ).parentNode.removeChild( script ); + } else { + // Otherwise, avoid the DOM node creation, insertion + // and removal by using an indirect global eval + indirect( code ); + } + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE9-11+ + // 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.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Support: Android<4.1 + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return 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 ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + 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 + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, 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 || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.2.0-pre + * http://sizzlejs.com/ + * + * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-12-16 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // http://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + characterEncoding + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + nodeType = context.nodeType; + + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + if ( !seed && documentIsHTML ) { + + // Try to shortcut find operations when possible (e.g., not under DocumentFragment) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType !== 1 && selector; + + // 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 + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = 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).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, parent, + doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + parent = doc.defaultView; + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", unloadHandler, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", unloadHandler ); + } + } + + /* Support tests + ---------------------------------------------------------------------- */ + documentIsHTML = !isXML( doc ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [ m ] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + docElem.appendChild( div ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( div.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+ + if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibing-combinator selector` fails + if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "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; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is no seed and only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + len = this.length, + ret = [], + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[0] === "<" && selector[ 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 = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Support: Blackberry 4.6 + // gEBID returns nodes no longer in the document (#6963) + if ( elem && elem.parentNode ) { + // 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: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; + }, + + sibling: function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter(function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} + return cur; +} + +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 sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "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 elem.contentDocument || jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.unique( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * 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( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // 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, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( 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 + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + 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 ) { + 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 ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( 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; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // Add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // If we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // 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 ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // 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.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + jQuery( document ).off( "ready" ); + } + } +}); + +/** + * The ready event handler and self cleanup method + */ +function completed() { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + jQuery.ready(); +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // We once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + } else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + } + } + return readyList.promise( obj ); +}; + +// Kick off the DOM ready check even if the user does not +jQuery.ready.promise(); + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + len ? fn( elems[0], key ) : emptyGet; +}; + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( owner ) { + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + /* jshint -W018 */ + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + +function Data() { + // Support: Android<4, + // Old WebKit does not have Object.preventExtensions/freeze method, + // return new empty object instead with no [[set]] accessor + Object.defineProperty( this.cache = {}, 0, { + get: function() { + return {}; + } + }); + + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; +Data.accepts = jQuery.acceptData; + +Data.prototype = { + key: function( owner ) { + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return the key for a frozen object. + if ( !Data.accepts( owner ) ) { + return 0; + } + + var descriptor = {}, + // Check if the owner object already has a cache key + unlock = owner[ this.expando ]; + + // If not, create one + if ( !unlock ) { + unlock = Data.uid++; + + // Secure it in a non-enumerable, non-writable property + try { + descriptor[ this.expando ] = { value: unlock }; + Object.defineProperties( owner, descriptor ); + + // Support: Android<4 + // Fallback to a less secure definition + } catch ( e ) { + descriptor[ this.expando ] = unlock; + jQuery.extend( owner, descriptor ); + } + } + + // Ensure the cache object + if ( !this.cache[ unlock ] ) { + this.cache[ unlock ] = {}; + } + + return unlock; + }, + set: function( owner, data, value ) { + var prop, + // There may be an unlock assigned to this node, + // if there is no entry for this "owner", create one inline + // and set the unlock as though an owner entry had always existed + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + // Handle: [ owner, key, value ] args + if ( typeof data === "string" ) { + cache[ data ] = value; + + // Handle: [ owner, { properties } ] args + } else { + // Fresh assignments by object are shallow copied + if ( jQuery.isEmptyObject( cache ) ) { + jQuery.extend( this.cache[ unlock ], data ); + // Otherwise, copy the properties one-by-one to the cache object + } else { + for ( prop in data ) { + cache[ prop ] = data[ prop ]; + } + } + } + return cache; + }, + get: function( owner, key ) { + // Either a valid cache is found, or will be created. + // New caches will be created and the unlock returned, + // allowing direct access to the newly created + // empty data object. A valid owner object must be provided. + var cache = this.cache[ this.key( owner ) ]; + + return key === undefined ? + cache : cache[ key ]; + }, + access: function( owner, key, value ) { + var stored; + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ((key && typeof key === "string") && value === undefined) ) { + + stored = this.get( owner, key ); + + return stored !== undefined ? + stored : this.get( owner, jQuery.camelCase(key) ); + } + + // [*]When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, name, camel, + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + if ( key === undefined ) { + this.cache[ unlock ] = {}; + + } else { + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = key.concat( key.map( jQuery.camelCase ) ); + } else { + camel = jQuery.camelCase( key ); + // Try the string as a key before any manipulation + if ( key in cache ) { + name = [ key, camel ]; + } else { + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + name = camel; + name = name in cache ? + [ name ] : ( name.match( rnotwhite ) || [] ); + } + } + + i = name.length; + while ( i-- ) { + delete cache[ name[ i ] ]; + } + } + }, + hasData: function( owner ) { + return !jQuery.isEmptyObject( + this.cache[ owner[ this.expando ] ] || {} + ); + }, + discard: function( owner ) { + if ( owner[ this.expando ] ) { + delete this.cache[ owner[ this.expando ] ]; + } + } +}; +var data_priv = new Data(); + +var data_user = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + 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 : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + data_user.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend({ + hasData: function( elem ) { + return data_user.hasData( elem ) || data_priv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return data_user.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + data_user.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to data_priv methods, these can be deprecated. + _data: function( elem, name, data ) { + return data_priv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + data_priv.remove( elem, name ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = data_user.get( elem ); + + if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE11+ + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + data_priv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + data_user.set( this, key ); + }); + } + + return access( this, function( value ) { + var data, + camelKey = jQuery.camelCase( key ); + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + // Attempt to get data from the cache + // with the key as-is + data = data_user.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to get data from the cache + // with the key camelized + data = data_user.get( elem, camelKey ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, camelKey, undefined ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each(function() { + // First, attempt to store a copy or reference of any + // data that might've been store with a camelCased key. + var data = data_user.get( this, camelKey ); + + // For HTML5 data-* attribute interop, we have to + // store property names with dashes in a camelCase form. + // This might not apply to all properties...* + data_user.set( this, camelKey, value ); + + // *... In the case of properties that might _actually_ + // have dashes, we need to also store a copy of that + // unchanged property. + if ( key.indexOf("-") !== -1 && data !== undefined ) { + data_user.set( this, key, value ); + } + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + data_user.remove( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = data_priv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = data_priv.access( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return data_priv.get( elem, key ) || data_priv.access( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + data_priv.remove( elem, [ type + "queue", key ] ); + }) + }); + } +}); + +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 ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + 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, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = data_priv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Safari<=5.1 + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Safari<=5.1, Android<4.2 + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<=11+ + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +})(); +var strundefined = typeof undefined; + + + +support.focusinBubbles = "onfocusin" in window; + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + 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 + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = 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 !== strundefined && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // 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: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + } + } + } + + 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; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.hasData( elem ) && data_priv.get( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // 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; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.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 ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + data_priv.remove( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // 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 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // 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 ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && 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) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === 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( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + 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 ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, j, ret, matched, handleObj, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // 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 + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = 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; + }, + + handlers: function( event, handlers ) { + var i, matches, sel, handleObj, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( 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 offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button; + + // 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 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, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: Cordova 2.5 (WebKit) (#13255) + // All events should have a target; Cordova deviceready doesn't + if ( !event.target ) { + event.target = document; + } + + // Support: Safari 6.0+, Chrome<28 + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + 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(); + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } +}; + +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.defaultPrevented === undefined && + // Support: Android<4.0 + src.returnValue === false ? + 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; +}; + +// 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 = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && e.preventDefault ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && e.stopPropagation ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && e.stopImmediatePropagation ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +// Support: Chrome 15+ +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // 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; + } + }; +}); + +// Support: Firefox, Chrome, Safari +// Create "bubbling" focus and blur events +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + data_priv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + data_priv.remove( doc, fix ); + + } else { + data_priv.access( doc, fix, attaches ); + } + } + }; + }); +} + +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" ) { + // ( 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 ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + 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 ( 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 ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +var + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style|link)/i, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + + // Support: IE9 + option: [ 1, "" ], + + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] + }; + +// Support: IE9 +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: 1.x compatibility +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute("type"); + } + + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + data_priv.set( + elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) + ); + } +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( data_priv.hasData( src ) ) { + pdataOld = data_priv.access( src ); + pdataCur = data_priv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( data_user.hasData( src ) ) { + udataOld = data_user.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + data_user.set( dest, udataCur ); + } +} + +function getAll( context, tag ) { + var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : + context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : + []; + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], ret ) : + ret; +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + // Support: QtWebKit, PhantomJS + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: QtWebKit, PhantomJS + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; + }, + + cleanData: function( elems ) { + var data, elem, type, key, + special = jQuery.event.special, + i = 0; + + for ( ; (elem = elems[ i ]) !== undefined; i++ ) { + if ( jQuery.acceptData( elem ) ) { + key = elem[ data_priv.expando ]; + + if ( key && (data = data_priv.cache[ key ]) ) { + if ( data.events ) { + for ( 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 ); + } + } + } + if ( data_priv.cache[ key ] ) { + // Discard any remaining `private` data + delete data_priv.cache[ key ]; + } + } + } + // Discard any remaining `user` data + delete data_user.cache[ elem[ data_user.expando ] ]; + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each(function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + }); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + 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 access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + 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() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); + } + } + } + } + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: QtWebKit + // .get() because push.apply(_, arraylike) throws + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var style, + elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? + + // Use of this method is a temporary fix (more like optimization) until something better comes along, + // since it was removed from specification and supported only in FF + style.display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( " + +

Cuando?

+ +

Sábados 9.15 - 12.30hs.

+ +

Donde?

+ +

Universidad Tecnológica Nacional, Buenos Aires regional, Av. Medrano 951.

+ +
+
+ +
+ +
+
+
+
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/quienessomos/index.html b/quienessomos/index.html new file mode 100644 index 0000000..cfa680e --- /dev/null +++ b/quienessomos/index.html @@ -0,0 +1,524 @@ + + + + + + + + + + + Quienes Somos - TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

Quienes Somos

+
+ +
+
+
+
+
+ + +
+
+
+

Docentes

+ + + +
+
+ +
+
+ +
+
+
+

Pablo de Haro

+ pablitar@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Mariana Matos

+ mmatos87@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Nicolas Scarcella

+ nscarcella@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Ernesto Bossi

+ bossi.ernestog@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Pablo Romanelli

+ pabloromanelli@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Axel Bergh

+ axelbergh@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Leandro Spir

+ leandrospir@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Micaela Martino

+ micaelafmartino@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Martin Loguancio (Lowy)

+ martin.loguancio@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Juan Manuel Fernandes dos Santos

+                juan9794@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+                

Juan Ignacio Cuiule

+                juanchicuiu@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Erwin Debusschere

+ erwincdl@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Fernando J. J. Petryszyn

+ fer.jjp@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Facundo Javier Gelatti

+ javiergelatti@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Matias Szlajen

+ matiasszlajen@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Hernan Peralta

+ hernanperalta3@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Alejandro Peralta Bazas

+ aleperaltabazas@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Brian Grajeda

+ brian.graj@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Erik Gervas

+ erikgervas@gmail.com +
+
+ +
+ +
+ +
+
+ +
+
+
+

Nicolás Rainhart

+ nm.rainhart@gmail.com +
+
+ +
+ +
+ +
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/Metamodelo_de_Ruby.jpg b/scripts/Metamodelo_de_Ruby.jpg new file mode 100644 index 0000000..9558d00 Binary files /dev/null and b/scripts/Metamodelo_de_Ruby.jpg differ diff --git a/scripts/clase_1/index.html b/scripts/clase_1/index.html new file mode 100644 index 0000000..ba6d20c --- /dev/null +++ b/scripts/clase_1/index.html @@ -0,0 +1,396 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 1 - Aspectos Administrativos / Modulos +
+
+
+
+
+ + +
+
+
+

+ + + Clase 1 TADP + + +

+ +

+ + + Aspectos administrativos: + + +

+ + +
    +
  • +

    Página de la materia: http://tadp-utn-frba.github.io/

    +
  • +
  • +

    Es muy importante que se metan en el servidor de Discord, como dice en la sección de temas administrativos.

    +
  • +
  • +

    Va a haber 2 TPs (uno de metaprogramación y el otro de híbrido objetos-funcional), cada uno con una entrega grupal y una individual. Les vamos a pedir una hoja por grupo con legajo, nombre, apellido y email.

    +
  • +
  • +

    Se van a usar 2 lenguajes (Ruby y Scala)

    +
  • +
  • +

    La idea es hacer una evaluación a lo largo de todo el año. El final se evalúa a lo largo de la cursada.

    +
  • +
+

+ + + Repaso Objetos + + +

+ + +

Vamos a comenzar pensando un juego de estrategia, similar al Age of Empires. En este juego existen guerreros que pueden atacarse entre sí. Cada Guerrero tiene un potencial ofensivo y un potencial defensivo y una cantidad de energía. Cuando un guerrero ataca a otro compara el potencial ofensivo suyo contra el potencial defensivo del otro. Si el potencial ofensivo del atacante es mayor o igual al del defensor, entonces el defensor resta la diferencia entre ambos en energía.

+ +

¿Cómo podemos programar esto con objetos?

+ +
    +
  • +

    Lo más obvio es modelar a los guerreros como objetos, que sean instancias de la clase Guerrero y que tengan como variables de instancia potencial_ofensivo, potencial_defensivo y energia.

    +
  • +
  • +

    Cada Guerrero entiende el mensaje atacar, que recibe por parámetro a otro guerrero y se implementa de la siguiente manera:

    +
  • +
+ +
class Guerrero
+    attr_accessor :energia, :potencial_ofensivo, :potencial_defensivo
+
+    def atacar(otro_guerrero)
+        if(self.potencial_ofensivo >= otro_guerrero.potencial_defensivo)
+            danio = self.potencial_ofensivo - otro_guerrero.potencial_defensivo
+            otro_guerrero.sufri_danio(danio)
+        end
+    end
+
+    def sufri_danio(danio)
+        self.energia= self.energia - danio
+    end
+end
+
+ +

Sobre los detalles de la implementación de Ruby no las trataremos con mucho detalle en clase, pero en este script podemos dedicarle algunas líneas a explicar brevemente un poco sobre su sintaxis.

+ +

En Ruby vemos que para declarar una clase se lo hace mediante class X …. end. En este lenguaje veremos que en lugar de que haya llaves o indentación para delimitar el contexto de una clase, método, etc., en Ruby se lo hace mediante end para finalizar el scope y ni bien se define una estructura su contexto empieza desde ahí.

+ +

Otro tema es la sentencia attr_accesor, por ahora solamente lo que nos interesa es que nos permite definir los accessors (getter y setter) para una variable de instancia. El nombre de las variables de instancia no se definen como strings sino con un : antes del identificador. Esto es porque lo que sea la sintaxis : se lo conoce como símbolo en Ruby, es un tipo de objeto que se usa como identificador porque tiene la propiedad de ser único el sistema. Por ejemplo: cuando se usa :bleh siempre va a ser el mismo.

+ +

Pero si se pueden hacer asignaciones o comparaciones usando símbolos.

+ +
	a = :bleh
+
+	a == :bleh #retorna true
+
+ +

Para más información sobre símbolos en Ruby ver: https://www.rubyguides.com/2018/02/ruby-symbols/

+ +

Volviendo al modelo y al problema del Age of Empires:

+ +
    +
  • +

    Marcamos una diferencia entre el mensaje sufri_danio y los demás, este es el único que tiene efecto.

    +
  • +
  • +

    Todo eso nos permitió repasar los conceptos de objeto, clase, instancia, mensaje, método, atributo, accessors, efecto.

    +
  • +
+ +

Ahora agregamos un caso especial de Guerrero: los espadachines, que se parecen mucho a los guerreros, pero su potencial ofensivo se ve incrementado por el uso de una espada. Para modelar esto agregamos dos clases Espadachin y Espada.

+ +
    +
  • +

    Espadachin es una subclase de Guerrero, que agrega la variable de instancia espada.

    +
  • +
  • +

    Espada es una nueva clase, que define el método potencial_ofensivo.

    +
  • +
+ +

En Espadachin redefinimos el método potencial_ofensivo de la siguiente manera:

+ +

+class Espada
+    attr_accessor :potencial_ofensivo
+end
+
+class Espadachin < Guerrero
+    attr_accessor :espada
+
+    def potencial_ofensivo
+        self.espada.potencial_ofensivo * super.potencial_ofensivo
+    end
+end
+
+ +

Luego agregamos Misiles, que pueden atacar pero no tiene sentido que se defiendan. Se nos ocurrió hacer una superclase Atacante, que sea superclase entre Misil y Guerrero. Movimos el atacar a la superclase. La clase Atacante no va a tener instancias, a este tipo de clases las denominamos clases abstractas.

+ +

Los misiles tienen un potencial ofensivo fijo de 1000, por lo tanto esa implementación no será compartida con los demás Atacantes. Si bien observamos que no es necesario, vemos que puede ser piola definir el método abstracto potencial_ofensivo en Atacante, para explicitar que las subclases deberían proveer una implementación de este método. A este tipo de métodos los llamamos métodos abstractos.

+ +

image alt text

+

+ + + Mixins + + +

+ + +

Finalmente, agregamos Murallas, que pueden defenderse, pero no atacar. Acá llegamos a un punto crítico en términos de diseño, porque la herencia simple no nos permite modelar esto de forma elegante. O sea no podemos extender una clase abstracta de otra, ya que si por ejemplo tenemos una clase que defina solamente el comportamiento y estado de defensor, que es energía y sufri_danio, y que esta extienda de atacante, la muralla aún tendrá el comportamiento heredado de atacante. Lo mismo sucede si invertimos el orden, en ese caso, los misiles podrán tener el comportamiento de defensor. Una solución es la de usar herencia múltiple, solo que muy pocos lenguajes lo soportan (C++ por ejemplo), e introduce varios problemas de resolución de conflictos como el problema del rombo o diamante (https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem). Entonces si solo disponemos de herencia simple en Ruby como resolvemos esto?

+ +

Para solucionar esto incorporamos una herramienta nueva: los Mixins.

+ +

Un mixin es similar a una clase, en el sentido de que permite definir un conjunto de métodos dentro de él, pero con la diferencia que, en lugar de ser instanciable, otras clases pueden incorporarlos como parte de sus métodos (otro enfoque sería pensar en los mixins como interfaces con código, o clases abstractas combinables). En este caso podemos definir Defensor como un mixin, que provee el método #sufri_danio:.

+ +

Para crear el Mixin debemos escribir, en el lugar donde normalmente definimos las clases:

+ +

+module Atacante
+    def atacar(un_defensor)
+        ...
+    end
+end
+
+
+ +

Ante la aparición de los mixins, podemos estar tentados de usarlos también para definir a los Atacantes. Entonces borramos la clase Atacante y creamos el Mixin, que define el método #atacar:.

+ +

Usando los mixins la clase Guerrero puede implementarse de la siguiente manera:

+ +

+class Guerrero
+    include Defensor
+    include Atacante
+end
+
+
+ +

image alt text

+

+ + + Más cuestiones sobre mixins + + +

+ + +
    +
  • +

    Los mixins permiten también definir estado.

    +
  • +
  • +

    ¿Qué pasa cuando uso dos mixins que definen el mismo método? Se produce un conflicto y, a diferencia de otras herramientas, la idea de mixins es que el lenguaje define una forma de resolverlos automáticamente y el programador debe acomodarse.

    +
  • +
  • +

    Otra característica esencial de los Mixins es el concepto de linearization.

    +
  • +
+ +

Sobre esto no hablamos mucho, para profundizar más conviene leer en el paper de traits de Bracha

+

+ + + ¿Conviene poner todo el código en los Mixins? + + +

+ + +

Hay gente que opina que sí, porque son unidades más reutilizables que las clases, pero ese enfoque hace que el rol de la clase sea solamente implementar mixins e instanciarse… Otra idea es usar siempre clases, hasta que por motivos de diseño similares a los presentados sea indispensable utilizar mixins. En general, el mejor enfoque suele estar en algún lugar del medio, aplicando una herramienta u otra aplicando criterio.

+ +

Código de la clase:

+ +

https://github.com/tadp-utn-frba/tadp-clases/blob/ruby-age/src/age.rb

+

+ + + Para la clase que viene: + + +

+ + +

Leer el paper donde se presenta la idea de Mixins:

+ +

https://bracha.org/oopsla90.pdf

+ +

Leer la idea de Traits:

+ +

http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf

+ +

http://scg.unibe.ch/archive/papers/Berg07aStatefulTraits.pdf

+ +

Hacer especial foco en cómo se resuelven los conflictos, cómo se implementan variables y en la diferencia entre flattening y linearization (y que implican para el programador)

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_10/index.html b/scripts/clase_10/index.html new file mode 100644 index 0000000..85dd027 --- /dev/null +++ b/scripts/clase_10/index.html @@ -0,0 +1,282 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 10 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + TADP - 2015 C2 - Clase 10 - Comportamiento vs Estructura (Orden Superior), Monadas y Monoides + + +

+ +

+ + + Orden Superior + + +

+ + +

Recordamos las Listas de Haskell, cómo estaban definidas, cómo podíamos trabajarlas por recursividad y que, de ser posible, resultaba mucho más práctico utilizar Operaciones de Orden Superior, que encapsulaban la idea de una operación y permitían que pensemos nuestros programas de forma más declarativa, con un nivel de abstracción más cercano al problema que teníamos que resolver.

+ +

Recordamos algunas operaciones como filter, map, flatMap y fold, qué tipo tenían, cómo se usaban y cuál es la intuición de las abstracciones que encapsulan. El objetivo es entender que estas operaciones pueden ser pensadas de forma independiente a los detalles de implementación de la lista; para eso tratamos de olvidarnos de las listas y pensar las operaciones en terminos de “cajas”:

+ +
    +
  • +

    filter: dada una caja[A] y una función A => Bool, abre la caja, el contenido que pasa la función criterio lo pone en una nueva caja[A].

    +
  • +
  • +

    map: dada una caja[A] y una función A => B, abre la caja, transforma el contenido usando la función y retorna el resultado en una nueva caja[B].

    +
  • +
  • +

    flatmap: dada una caja[A] y una función A => caja[B], abre la caja, transforma el contenido usando la función, abre estas nuevas cajas y retorna su contenido en una única caja[B].

    +
  • +
  • +

    El fold: dada una caja[A], y algún mecanismo que me permita convertirla en un valor B, aplica dicho mecanismo para abrir la caja.

    +
  • +
+ +

El fold va a ser un caso particular, dado que es la única de estas operaciones que no retorna una nueva caja. Todas las otras reciben y devuelven cajas, con lo que pueden componerse libremente, pero el fold abre la caja y rompe la cadena.

+ +

Señalamos también que, al usar estas operaciones, en ningún momento nos interesa si la caja realmente tiene o no contenido. Podemos pensar nuestro programa secuenciando operaciones y sólo preocuparnos por el caso de que no haya contenido cuando necesitamos abrir la caja. Por ejemplo, para el siguiente código:

+ +
  caja.filter(f).map(g).filter(h).flatMap(i).filter(j).size > 2
+
+ +

No tiene ninguna importancia si el resultado de alguno de los filter (o la caja inicial) es una caja sin contenido, porque nunca necesitamos acceder al contenido.

+ +

Ahora que ya tenemos la intuición sobre estas operaciones en término de contenedores, introducimos dos nuevos tipos de contenedores muy comunes que también pueden trabajarse con estas operaciones: Option, que modela la posibilidad de tener o no algo, y Try, que representa la posibilidad de obtener un valor o fallar en el intento. Pueden parecer similares, pero sus estructuras y finalidades son distintas.

+ +

Vemos entonces que el Option es un gran sustituto del null (y además es type-safe!) y el Try es excelente para reemplazar el manejo de excepciones con valores de retorno. También vemos que con el Try podemos desentendernos de cuando ocurre un error hasta que podamos manejarlo (similar a lo que pasaba con manejo de excepciones).

+ +

De las 4 operaciones de orden superior que vimos, la única que varía un poco entre List, Option y Try es el fold, justamente porque cada caja debe abrirse pensando en cada una de sus posibles formas: Distintas cajas se abren distinto.

+ +

La parte mágica de todo esto está en notar que es posible aprender a trabajar en un nivel de abstracción por encima de los datos. Podemos desentendernos de la forma de la caja estamos usando y simplemente pensar en termino de combinar filtrados, mapeos y otras operaciones. (Así como antes decíamos “quiero mapear, no me importa si la lista está vacía o tiene elementos”, ahora podemos decir “quiero mapear, no me importa ni siquiera si es una lista” :p ).

+

+ + + Monadas y Monoides + + +

+ + +

Podemos, de forma más bien laxa, llamar a estas cajas Monadas (o Monoide para el caso del Try), nombre que Scala toma del paradigma funcional y para el cual hace una implementación un tanto diferente a la de Haskell. No vamos a meternos a explicar en detalle qué es una Monoide o que propiedades tiene que cumplir para considerarse una Monada. Basta con entender que, así como la composición de funciones es un mecanismo para secuenciar operaciones, las Mónadas son un mecanismo para secuenciar tranformaciones de datos. Si quieren profundizar sobre el tema pueden encontrar un par de buenos artículos acá y acá.

+

+ + + For Comprehension + + +

+ + +

Como la anidación de operaciones de orden superior como flatmap, map y filter es muy común y enseguida se puede volver engorroso, hay una sintaxis más amigable que el compilador reescribe por atrás a operaciones monádicas. En Scala, esta sintaxis se llama for-comprehension y está inspirada en la do-comprehension de Haskell.

+ +

En precompilación se transforman las for-comprehensions en una combinación de maps, flatmaps, filters y/o foreachs. Esta sintaxis nos permite abstraernos de la cajita a la par de las operaciones que se utilizan por detrás, y termina resultando muy intuitivo.

+ +

Por ejemplo, una sentencia compleja como la siguiente:

+ +
caja.flatMap { e1 =>
+  tx1(e1).flatMap { e2 =>
+    tx2(e2).filter { e3 =>
+      crit(e3)
+    }.map { e4 =>
+      tx3(e4)
+    }
+  }
+}
+
+ +

Puede escribirse así:

+ +
for {
+  e1 <- caja
+  e2 <- tx1(e1)
+  e3 <- tx2(e2)
+  if crit(e3)
+} yield tx3(e3)
+
+ +

El compilador va a tomar la expresión del segundo bloque de código y a convertirla en lo que se ve en el primero.

+

+ + + Bajando a Tierra + + +

+ + +

Por último, en el siguiente ejemplo vamos a tratar de aprovechar estas nuevas ideas para mejorar la ejecución en nuestro Micro; primero convirtiendo los resultados de ejecución en Monoides y luego descartandolos en favor de utilizar un Try.

+ +

repo funcional-monadas

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_11/index.html b/scripts/clase_11/index.html new file mode 100644 index 0000000..1053dc1 --- /dev/null +++ b/scripts/clase_11/index.html @@ -0,0 +1,231 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 11 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + Clase 10 - Ejercicio en Funcional + + +

+ +

+ + + Objetivos de la clase: + + +

+ + +

Ver un poquito de diseño en funcional. La idea es partir de soluciones imperfectas, o lejanas del grado de abstracción y declaratividad que uno querría tener en un diseño funcionaloso (con mucho pattern matching, muchas estructuras muy expuestas) y empezar en cambio a introducir funciones como objetos y objetos como funciones para ir hacia algo que aplique algo más de orden superior, que use funciones para abstraer la lógica.

+

+ + + TP a utilizar: + + +

+ +

En principio, la intención es usar el TP de Pokemon.

+ +

Se utilizó esta versión reducida

+ +

Podemos basarnos en la implementación de referencia que hizo NicoS

+ +

Y también podemos basarnos en la implementación que hizo el grupo wololos

+ +
    +
  • Comenzar planteando el caso de uso de implementar algunas actividades sencillas y sin parámetros extra: Descansar y fingir intercambio.
  • +
  • Con estas dos actividades, debería surgir la idea de que quizás las actividades pueden ser funciones, pero de momento podemos dejarla de lado. Que quede colgada.
  • +
  • Agregar más actividades: Nadar y levantar pesas. Vemos que tienen un parámetro. Las agregamos como case classes.
  • +
  • Hacer funcionar las actividades con estructuras de datos y funciones, y mucho pattern matching. Hacer la función “realizarActividad”, quizás definida en pokemon o quizás definida afuera que use Pattern matching a full para determinar qué hacer en cada actividad.
  • +
  • Implementar la “Rutina” como lista de actividades, y el “realizar rutina” quizás como un fold, o algo así. O quizás empezar con una idea recursiva y después cambiar a fold. Quizás introducir el Try.
  • +
  • Preguntar si dado que cada actividad conoce lo que tiene que hacer con un pokemon, no sería conveniente encapsular lo que la actividad hace dentro de cada una. Hacer quizás un método polimórfico o algo así.
  • +
  • Reflexionar un poco acerca de Polimorfismo Ad-Hoc vs Pattern Matching. Ver que no son excluyentes, y que se puede usar uno o el otro donde convenga y combinarlos para lograr un diseño supuestamente flexible. Quizás hablar de la dicotomía agregar operaciones vs agregar tipos/estructuras.
  • +
  • Comentar que en Scala las funciones son objetos con un contrato en particular. Ver quizás los traits de function y cosas del estilo. Ver que para que un objeto se pueda comportar como función, la idea es definir el Apply. Definirlo en las actividades que ya existen. Cambiar el “type” de Actividad para que sea una función de Pokemon en Pokemon. Cambiar el realizar rutina para que foldee aplicando las funciones.
  • +
  • Agregar alguna actividad, más complicada, como realizar ataque, más como ejercitación que otra cosa
  • +
+

+ + + Segunda parte + + +

+ + +
    +
  • Arrancamos marcando que al subir un nivel no se están actualizando las características -> hacer el cambio de val a def
  • +
  • Implementar las rutinas(fold y try)
  • +
  • Agregamos las evoluciones. Modificar ganarExperiencia y agregar a la especie la condición evolutiva(option). Actividades usarPiedra e intercambiar
  • +
  • Ataques: validación APs suficientes, puntos de experiencia ganados, efectos posteriores
  • +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_12/index.html b/scripts/clase_12/index.html new file mode 100644 index 0000000..1dc2ea5 --- /dev/null +++ b/scripts/clase_12/index.html @@ -0,0 +1,424 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 12 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + Objetos + Funcional + + +

+ +

Vamos a construir una solucion funcional de un dominio y luego vamos a ir introduciendo y combinando conceptos de OOP.

+

+ + + Dominio + + +

+ +

Vamos a construir un programa para analizar las pociones que se enseñan a los alumnos en el colegio Hogwarts de Magia y Hechicería, y los efectos que pueden hacer sobre las personas.

+

+ + + Personas y Niveles + + +

+ +

Una persona es una tupla de nombre y niveles. +Los niveles definen el estado de cada caracteristica de una persona: suerte, convencimiento, fuerza.

+ +
type Niveles = (Int, Int, Int)
+type Persona = (String, Niveles)
+
+val personas = List(
+  ("Harry", (11, 5, 4)),
+  ("Ron", (6, 4, 6)),
+  ("Hermione", (8, 12, 2)),
+  ("Draco", (7, 9, 6))
+)
+
+

+ + + Efectos + + +

+ +

Los efectos son funciones que reciben niveles y devuelven el nuevo estado de los niveles.

+ +
type Efecto = Niveles => Niveles
+
+

+ + + Aplicación Parcial y Currificación + + +

+ + +

Definamos un efecto que duplique todos los niveles:

+ +
def duplica(niveles: Niveles) = niveles._1 * 2 + niveles._2 * 2 + niveles._3 * 2
+
+ +

Si ahora queremos definir otro efecto que se quede con el máximo valor entre cada nivel y 7, tendríamos que duplicar la lógica de la función anterior en gran parte.

+ +

Definamos entonces una función que dada una operación, la aplique a cada nivel:

+ +
def mapNiveles(f: Int => Int, niveles: Niveles) =
+  (f(niveles._1), f(niveles._2), f(niveles._3))
+
+val duplica: Efecto = mapNiveles(_ * 2, _)
+
+val alMenos7: Efecto = mapNiveles(_.max(7), _)
+
+

Estamos usando el “_” para aplicar parcialmente una función.

+ +

Aplicar parcialmente las funciones es muy importante porque me deja transformarlas en otras funciones más específicas, para componerlas o pasarlas por parámetro.

+ +

Una función en Scala puede recibir multiples grupos de parámetros. Dado que en Scala las funciones no están completamente currificadas, uno tiene que declarar “grupos de aplicación” para poder evitar el “_”.

+ +

Entonces podemos reescribir la función anterior como:

+ +
def mapNiveles(f: Int => Int)(niveles: Niveles) =
+  (f(niveles._1), f(niveles._2), f(niveles._3))
+
+val duplica: Efecto = mapNiveles(_ * 2)
+
+val alMenos7: Efecto = mapNiveles(_.max(7))
+
+

+ + + Composición de Funciones + + +

+ + +

En el paradigma funcional es importante componer porque las funciones son los ladrillitos con los que construiamos los programas.

+ +

En funcional las funciones son chiquitas y cohesivas. Las combinamos entre ellas para construir algoritmos más grandes.

+ +

Por ejemplo, en haskell es natural hacer algo como:

+ +
(length . filter aprobado . map parcial) alumnos
+
+ +

Tenemos las funciones length, filter y map (sin contar las que usamos cómo parámetro) y las combinamos en secuencia para hacer algoritmos más complejos.

+ +

En el paradigma orientado a objetos hacemos:

+ +
alumnos.map(_.parcial).filter(_.aprobado).length
+
+ +

Cual es la diferencia?

+ +

En funcional cada uno tiene que construir las operaciones por afuera de los datos, en objetos los mismos datos pueden proveer las funciones. No necesitamos componer, porque podemos mandarle mensajes al resultado de una operación.

+ +

Creemos las siguientes funciones y veamos como funciona la composición:

+ +
    +
  • Sumar el valor de todos los niveles
  • +
+ +
val toList: Niveles => List[Int] = niveles => List(niveles._1, niveles._2, niveles._3)
+val sumaNiveles: Niveles => Int = toList.andThen(_.sum)
+
+ +
    +
  • Calcular la diferencia entre el nivel más alto y el más bajo
  • +
+ +
val maxNivel: Niveles => Int = toList.andThen(_.max)
+val minNivel: Niveles => Int = toList.andThen(_.min)
+val diferenciaNiveles: Niveles => Int = niveles => maxNivel(niveles) - minNivel(niveles)
+
+ +
    +
  • Sumar los niveles de una persona
  • +
+ +
  def niveles(persona: Persona) = persona._2
+  val sumaNivelesPersona: Persona => Int = sumaNiveles.compose(niveles)
+
+ +

Podemos ver que las funciones pueden ser compuestas usando “andThen” y “compose” para crear nuevas funciones más complejas.

+

+ + + Pociones + + +

+ +

Una poción es una tupla de nombre y lista de ingredientes. +Los ingredientes son una 3-upla de nombre, cantidad del ingrediente y lista de efectos.

+ +
type Ingrediente = (String, Int, List[Efecto])
+type Pocion = (String, List[Ingrediente])
+
+// Pociones
+val multijugos = ("Multijugos", List(
+  ("Cuerno de Bicornio en Polvo", 10, List(invierte, suerteEsConvencimiento)),
+  ("Sanguijuela hormonal", 54, List(duplica, suerteEsConvencimiento))
+))
+
+val felixFelices = ("Felix Felices", List(
+  ("Escarabajos Machacados", 52, List(duplica, alMenos7)),
+  ("Ojo de Tigre Sucio", 2, List(suerteEsConvencimiento))
+))
+
+val floresDeBach = ("Flores de Bach", List(
+  ("Rosita", 8, List(duplica))
+))
+
+val pociones: List[Pocion] = List(felixFelices, multijugos, floresDeBach)
+
+

+ + + Funciones Parciales + + +

+ + +

Decimos que una poción es “heavy” cuando al menos tiene 2 efectos. Obtengamos una lista de todas las pociones heavies.

+ +
def efectos(ingrediente: Ingrediente) = ingrediente._3
+val todosLosEfectos: List[Ingrediente] => List[Efecto] = _.flatMap(efectos)
+val ingredientes: Pocion => List[Ingrediente] = _._2
+val efectosPocion: Pocion => List[Efecto] = todosLosEfectos.compose(ingredientes)
+val esHeavy: Pocion => Boolean = efectosPocion(_).size >= 2
+def nombre(pocion: Pocion) = pocion._1
+val pocionesHeavies: List[Pocion] => List[String] = _.filter(esHeavy).map(nombre)
+
+ +

Pero también podemos usar pattern matching y funciones parciales para conseguir lo mismo:

+ +
val pocionesHeaviesPartial: List[Pocion] => List[String] = _.collect {
+  case (nombre, ingredientes) if todosLosEfectos(ingredientes).size >= 2 => nombre
+}
+
+ +

Lo que acabamos de definir es una función parcial, una función que no está definida para todos los valores de su dominio.

+ +

Scala provee un azucar sintáctico para escribir funciones parciales con una sintaxis identica a la del patern matching sin iniciar con el “match”.

+ +

En caso de querer definir una funcion parcial con la misma sintaxis, estamos obligados a hacerlo en una variable o parámetro que esté tipado explicitamente.

+ +
val nombreDePocionHeavy: PartialFunction[Pocion, String] = {
+  case (nombre, ingredientes) if todosLosEfectos(ingredientes).size >= 2 => nombre
+}
+
+ +

Las funciones parciales son un tipo particular de “Function1[A, B]” o “A => B”. Ellas extienden la interfaz de las funciones y le agregan más comportamiento.

+ +

Que pasa si a una función parcial la evalúo con un valor para la cual no está definida? Lanza una excepción (MatchError).

+ +

Para evitar esto puedo utilizar los métodos que me provee, como por ejemplo:

+ +

Puedo preguntar si una funcion parcial está definida para un valor:

+ +
nombreDePocionHeavy.isDefinedAt(felixFelices) // true
+nombreDePocionHeavy.isDefinedAt(floresDeBach) // false
+
+ +

Puedo pasarle una función de fallback:

+ +
val nombreDePocionConFallback = nombreDePocionHeavy.orElse {
+  case (nombre, _) => s"$nombre no es heavy"
+}
+nombreDePocionConFallback(felixFelices) // Felix Felices
+nombreDePocionConFallback(floresDeBach) // Flores de Bach no es heavy
+
+ +

Y puedo transformarla en una función que si esté definida para todo su dominio (cambia el tipo de retorno a Optional usando None para los valores no definidos):

+ +
nombreDePocionHeavy.lift(felixFelices) // Some("Felix Felices")
+nombreDePocionHeavy.lift(floresDeBach) // None
+
+ +

Ya (abu)usamos funciones parciales anteriormente para utilizar la deconstrucción por patrones y hacer más facil la definición de una función. Veamos como escribir de diferentes maneras el efecto “invierte”:

+ +
val invierte1: Efecto = niveles => (niveles._3, niveles._2, niveles._1)
+val invierte2: Efecto = {
+  case (a, b, c) => (c, b, a)
+}
+
+ +

Como las funciones parciales también son funciones, la definición anterior es totalmente valida y dado que estamos incluyendo todos los valores del dominio, esas funciones son análogas.

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_13/index.html b/scripts/clase_13/index.html new file mode 100644 index 0000000..e20101a --- /dev/null +++ b/scripts/clase_13/index.html @@ -0,0 +1,1077 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 13 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + Intro + + +

+ + +

Para poder entender cómo trabaja el framework de reflection en Scala, primero hay que entender su metamodelo y cómo se representa en runtime. Scala no tiene un lenguaje de representación intermedia propio, sino que se compila a bytecode de Java para ejecutarse sobre la JVM (Java Virtual Machine). Eso significa que un programa Scala se traduce a un programa Java que debe funcionar utilizando sólo las representaciones internas pensadas para Java; por lo tanto durante la ejecución de un programa, las abstracciones propias del metamodelo de Scala no existen.

+ +

Pero el metamodelo de Scala no incluye al de Java? Y… Más o menos. Por ejemplo, tanto Scala como Java tienen clases, pero las de Java no pueden ser linearizadas con mixins, ni recibir parámetros de clase, lo cual las vuelve construcciones similares, pero no idénticas. Por otro lado Java tiene construcciones como las Interfaces que no son soportadas por Scala.

+ +

Entonces cómo puede funcionar? Lo que pasa es que Scala se las ingenia para modelar sus propias abstracciones usando las provistas por Java.

+ +

Siendo que Scala puede ejecutar nativamente código Java y que en runtime el programa en Scala es un programa Java, existen varias cosas que podemos realizar utilizando el framework de reflection de Java, sin embargo, la gran mayoría de las cosas van a requerir usar el framework de reflection de Scala, que provee una interfaz basada en mirrors que se adecúa al metamodelo propio de Scala y no requiere saber nada de cómo trabaja el compilador.

+

+ + + 3 Niveles de Abstracción + + +

+ + +

Podemos entender entonces que todo programa Scala trabaja en alguna medida con (al menos) 3 metamodelos: El provisto por Scala, el de Java y la representación en la JVM, los cuales soportan diferentes abstracciones:

+ +

NivelesDeAbstraccion

+ +

Jerarquía Básica de Tipos en Scala +JerarquiaDeTipos

+

+ + + Reflection en Java + + +

+ + +

ArquitecturaJVM +Arquitectura básica de la jvm

+

+ + + ClassLoaders y Linkers + + +

+ +

La JVM carga dinámicamente, linkea e inicializa clases e interfaces. La carga de clases es el proceso de encontrar la representación binaria de una clase o de una interfaz con un nombre y la creación dicha representación. Linkeo es el proceso de tomar una clase o interfaz y combinarlo en el estado de la JVM en tiempo de ejecución para que pueda ser ejecutado. La inicialización de una clase o interfaz consiste en ejecutar la inicialización de la clase o interfaz mediante el método . Este método es un método especial que provee el compilador y como no es un nombre válido en el lenguaje Java, no puede ser definido por el usuario directamente en un programa Java.

+

+ + + Pool de constantes en tiempo de ejecución + + +

+ +

La JVM inicia creando una clase inicial, utilizando el bootstrap class loader, veremos este paso en detalle después. La JVM luego linkea la clase inicial, la inicializa e invoca el método de la clase pública main. La invocación de este método permite la ejecución del programa. La ejecución de las instrucciones de la JVM que constituye el método principal puede causar el linkeo y la creación de clases e interfaces adicionales, así como su ejecución.

+ +

La JVM mantiene un pool de constantes por tipo, esto es una estructura creada en tiempo de ejecución que sirve para diferentes funcionalidades, similares a la tabla de símbolos de un lenguaje de programación.

+ +

Todas las representaciones binarias de clases o interfaces poseen una tabla de pool de constantes que se usa para construir el pool de constantes de tiempo de ejecución en el momento de creación de esa clase. Todas las referencias en este último pool son inicialmente simbólicas. Las referencias simbólicas en el pool de constantes de tiempo de ejecución son derivados de la representación de la tabla de constantes de la clases o interfaces de la siguiente manera.

+ +

Una referencia simbólica a una clase o interfaz es derivado de la estructura CONSTANT_Class_info en la representación binaria de una clase o interfaz.

+ +
CONSTANT_Class_info {
+    u1 tag;
+    u2 name_index;
+}
+
+ +

El name_index, siempre describe el tipo de estructura al que se está referenciando, incluso si la clase es formada de acuerdo a un array, que son objetos, la descripción en el name_index varía de acuerdo a estas estructuras más complejas también.

+ +

Dicha referencia tiene un nombre de la clase o interfaz en la forma retornado por el método Class.getName que es, de acuerdo al tipo:

+
    +
  • Para una clase o interfaz del tipo que no sea array, el nombre es el nombre binario de la clase o interfaz.
  • +
  • Para una clase del tipo array de n dimensiones, el nombre comienza con n ocurrencia de la letra ASCII “[” continuado con la representación del tipo de elemento.
  • +
+ +

Por ej.

+ +

int[][] -> [[I (Si es un tipo primitivo, sólo se lo describe bajo su nombre)

+ +

Thread[] -> [Ljava/lang/Thread; (Si es un objeto del tipo de dato referido, se le debe agregar un caracter L seguido del nombre del binario).

+ +

Referencias a un campo de una clase o interfaz es derivado de otra estructura, CONSTANT_Fieldref_info, en la representación binaria de una clase o interfaz. Tal referencia provee el nombre y descriptor de un campo, así como la referencia simbólica de clase o interfaz en la que se puede encontrar el campo.

+ +
CONSTANT_Fieldref_info {
+    u1 tag;
+    u2 class_index;
+    u2 name_and_type_index;
+}
+
+ +

Referencias a un método de una clase es derivado de la estructura CONSTANT_Methodref_info, en la representación binaria. Esta referencia posee el nombre y descriptor de los métodos así como las referencias simbólicas a la clase en la que se encuentra el método.

+ +
CONSTANT_Methodref_info {
+    u1 tag;
+    u2 class_index;
+    u2 name_and_type_index;
+}
+
+ +

Referencias a invocaciones en lugares puntuales de la clase o interfaz en formato binario, son derivados de la estructura CONSTANT_InvokeDynamic_info. Dicha estructura nos da la siguiente información

+ +
CONSTANT_InterfaceMethodref_info {
+    u1 tag;
+    u2 class_index;
+    u2 name_and_type_index;
+}
+
+ +
    +
  • Una referencia simbólica al handler de un método, que servirá como método inicial para la instrucción de invokedynamic.
  • +
  • Una secuencia de referencias simbólicas, string literas, y valores de constantes de tiempo de ejecución que servirán como argumentos estáticos a un método inicial.
  • +
  • Un nombre de método y un descriptor de método
  • +
  • Adicionalmente, ciertos valores de tiempo de ejecución que no son referencias simbólicas son derivados de elementos encontrados en la tabla constant_pool
  • +
  • Un string literal es una referencia a una instancia de la clase String, que es derivado de una estructura, del tipo CONSTANT_String_info. Esta estructura brinda la secuencia de caracteres en Unicode, que constituyen los string literals.
  • +
+ +
CONSTANT_String_info {
+    u1 tag;
+    u2 string_index;
+}
+
+ +

El lenguaje de programación Java requiere que los string literals idénticos, que poseen la misma secuencia de caracteres, deban referirse a la misma instancia de la clase String. Adicionalmente, en el método String.intern se llama a cualquier string, el resultado es una referencia a la misma instancia de la misma clase que hubiese retornado si el string aparecía como un literal, entonces la siguiente expresión es verdadera:

+ +
("a" + "b" + "c").intern() == "abc"
+
+ +
    +
  • Valores de constantes en tiempo de ejecución son derivados de las estructuras CONSTANT_Integer_info, CONSTANT_Float_info, CONSTANT_Long_info, or CONSTANT_Double_info. La estructura CONSTANT_Float_info representa valores de formato de acuerdo a IEEE 754 y CONSTANT_Double_info a los doubles del mismo estándar.
  • +
  • Las estructuras remanentes en la tabla de constant_pool, de la representación binaria de la clase/interfaz, las estructuras CONSTANT_NameAndType_info and CONSTANT_Utf8_info structures, son usadas solamente de manera indirecta cuando se derivan referencias simbólicas a clases, interfaces, métodos, campos, tipos de métodos y cuándo se derivan string literals y se hacen invocaciones dinámicas.
  • +
+ +

Sobre un ejemplo sencillo de esto, podemos mencionar que si tenemos una expresión sencilla del tipo +InicializacionJVM +*Inicialización de la JVM

+ +

Veremos que podemos proveer a la jvm de la clase inicial en la línea de comando.

+ +

por ej. si tenemos nuestro Programa de HolaMundo de la siguiente manera

+ +
public class HolaMundo {
+   public static void main(String argv[]) {
+      System.out.println("Hola Mundo!");
+   }
+}
+
+ +

y lo ejecutamos mediante la opción de -verbose:class, podremos decirle explícitamente a java cual es la clase inicial que se cargará

+ +
java -verbose:class HolaMundo
+
+ +

Veremos que hay otras maneras de inicializar el comienzo de la ejecución mediante Loaders, que pueden ser definidos por el usuarios y utilizados en la línea de comandos.

+

+ + + Creación y Carga de binarios + + +

+ +

La creación de una clase C con el nombre N consiste en la construcción en el área de métodos de la representación interna de C. La creación es activada y ejecutada por otra clase o interfaz D, que referencia a C en el pool de constantes de tiempo de ejecución. La creación también se puede disparar por D en métodos de invocación dinámicos, o sea, que D referencia a C por medio de reflection.

+ +

Si C no es una clase del tipo array, entonces es creado cargando la representación de C utilizando el class loader. Las clases que heredan de clases de Array no tienen una representación binaria externa, son creados por la JVM, más que por un classloader.

+ +

Hay dos clases de classloaders, los que son suministrados por la JVM, y los que define el usuario. Cada classloader definido por el usuario es una instancia de una subclase de la clase ClassLoader.

+ +

Hay dos clases de classloaders, los que son suministrados por la JVM, y los que define el usuario. Cada classloader definido por el usuario es una instancia de una subclase de la clase ClassLoader. Las aplicaciones que utilizan esta extensión de classloaders definidos por el usuario, se hacen con el objetivo de extender la manera en la que la JVM dinámicamente carga y crea las clases. Los classloaders creados por el usuario pueden ser utilizados para crear clases que se originan en otros lugares en la máquina local que no están definidos o que deben ser descargados a través de una red, generados al vuelo, o extraídos de algún comprimido o archivo encriptado.

+ +

Podemos mencionar un ejemplo de un classloader que baja de internet clases e interfaces:

+ +
import java.net.*;
+import java.io.*;
+public class MyNetwordLoader {
+   public static void main (String argv[]) throws Exception {
+
+      URLClassLoader loader = new URLClassLoader(new URL[] { new URL("http://www.pepito.com/classes/") });
+    
+      // Load class from class loader. argv[0] is the name of the class to be loaded
+      Class c = loader.loadClass (argv[0]);
+
+      // Create an instance of the class just loaded
+      Object o = c.newInstance();
+
+  }
+}
+
+ +

Entonces volviendo a nuestro ejemplo de un classloader con la clase C, un classloader L puede crear a C definiendo directamente o delegando su creación a otro classloader. Si lo hace L directamente, decimos que L define a C. Cuando una classloader delega la creación a otro classloader, el segundo que inicia la carga no es necesariamente el mismo loader que completa la carga y define la clase. Si L crea a C, definiéndolo directamente o delegando, diremos que L inicializa la carga de C.

+ +

En tiempo de ejecución, una clase o interfaz es determinada no por su nombre solamente, sino por una tupla, compuesta por su nombre binario y el classloader que lo define. Entonces tal clase pertenece a un paquete de tiempo de ejecución. Este paquete de tiempo de ejecución de una clase es determinado por el nombre del paquete, definiendo el classloader de la clase o interfaz.

+ +

La JVM usa uno de tres procedimientos para crear la clase C denotado por N:

+
    +
  • si N define una clase o interfaz que no es un array, uno de dos métodos siguientes es usados para crear a C
  • +
  • Si D fue definido por el classloader de la jvm, entonces este inicia con la carga de C
  • +
  • si D fue definido por un classloaders definido por un usuario, entonces este inicia la carga de C
  • +
  • De otra manera N denota una clase del tipo array o derivado de este. Una clase del tipo array es creado directamente por la JVM. Sin embargo el classloader que definió a D es usado en el proceso para crear la clase array C.
  • +
+ +

Si un error ocurre durante la carga de la clase, entonces una instancia de una subclase de LinkageError, debe ser lanzado en ese punto del programa. Si la JVM trata de cargar la clase C durante la parte de la verificación o resolución, y el classloader que es usado lanza una excepción ClassNotFoundException, entonces la JVM debe lanzar una instancia de la excepción de clase NoClassDefFoundError que hace referencia a la instancia de ClassNotFoundException.

+ +

Un classloader con un buen comportamiento, en contexto de que no fallará en cuanto a errores de tipo o a que no lanzará excepciones por el comportamiento que posee, posee las siguientes tres propiedades:

+ +
    +
  • Dado un mismo nombre, un classloader debería siempre devolver el mismo objeto de esa clase asociada.
  • +
  • Si un classloader L1 delega la carga de una clase C a otro loader L2, entonces cualquier tipo T que ocurre como la superclase o superinterface directa de C, o como el tipo de campo en C, o como el tipo del parámetro formal de un método o constructor en C, o como un tipo de retorno de un método en C, L1 y L2 deberían retornar el mismo objeto de clase.
  • +
  • Se un classloader definido por el usuario, busca la representación binaria de clases/interfaces, o carga un grupo de clases relacionadas entre si, entonces debe reflejar los errores de carga solo en los puntos del programa donde se pudieron haber producido sin búsqueda o carga de grupo de clases.
  • +
+ +

A veces representaremos una clase o interfaz utilizando la notación <N, Ld>, donde N es el nombre de la clase o interfaz y Ld el loader que carga a N. También representaremos una clase o interfaz utilizando la notación NLi, donde N es el nombre de la clase/interfaz, y Li, el classloader que lo inicializa.

+

+ + + Utilizando el Classloader de la JVM + + +

+ + +

Los siguientes pasos son usados para cargar y crear una clase C que no hereda de array y cuyo nombre lo llamaremos N utilizando el classloader de la JVM.

+ +

Primero, la JVM determina si el classloader por default, ya ha sido registrado como el inicializador de la clase/interfaz denotado por N. Si es así la clase o interfaz es C y no se necesita realizar creación alguna.

+ +

De otra manera, la JVM pasa el argumento N y llama al método de carga del classloader por defecto para buscar la representación de C en la plataforma, en el directorio del proyecto o el alguna dependencia definida. Típicamente, una clase o una interfaz estarán representadas utilizando un archivo en un sistema de archivos jerárquico, en el nombre de la clase o interfaz será codificado en la ruta del archivo. No hay garantía que una representación encontrada es la representación de C. Esta fase de carga debe detectar el siguiente error

+ +

Si no hay representación de C alguna en el sistema, se debe lanzar la excepción del tipo ClassNotFoundException.

+

+ + + Utilizando un Classloader definido por el usuario + + +

+ + +

Los pasos para cargar y crear una clase/interfaz C que no extiende de una clase del tipo array denotado N usando un classloader L definido por el usuario

+ +

Primero, la JVM, determina si L ha sido ya registrado como el loader que inicializa a la clase C, es decir N. Si es así la clase es C y no se necesita realizar creación alguna.

+ +

De otra manera la JVM, invoca loadClass(N) en L. El valor retornado por la invocación es la clase/interfaz C creada ya. La JVM entonces registra que L es el loader que inicializo a C.

+ +

En más detalle:

+ +

Cuando el método loadClass que está definido en L es invocado con el nombre N de una clase C, L debe realizar alguna de las dos operaciones para cargar C:

+ +
    +
  • L puede crear un array de bytes representando C como los bytes en una estructura ClassFile, entonces debe invocar el método defineClass de la clase ClassLoader. Invocando defineClass hace que JVM para derivar una clase o interfaz de nombre N utilizando L del array de bytes.
  • +
  • El classloader L puede delegar la carga de C en otro classloader como lo mencionamos anteriormente, L2. Esto se realiza solamente pasando el argumento N directamente o indirectamente a una invocación de L2. El resultado de esta invocación es C.
  • +
+ +

En cualquiera de los dos casos si L no puede cargar a C por medio de su nombre N, debe lanzar una excepción del tipo ClassNotFoundException.

+

+ + + Creando clases del tipo Array o subclases de este tipo + + +

+ + +

Para crear una clase del tipo array o que herede de array, llamado C, utilizando un nombre N, mediante un loader L, se realizarán los siguientes pasos. +Si L ya ha sido registrado como la clase que define a C, entonces no necesita realizarse creación alguna y se usa este loader. De otra manera:

+ +

Si el tipo del componente es una referencia, se realiza una búsqueda recursiva utilizando el class loader L, para crear el componente de tipo de C

+ +

La JVM crea una nueva clase, indicando el tipo de componente y la cantidad de dimensiones.

+ +

Si el tipo del componente una referencia, se lo marca como que ha sido definido por el classloader del tipo del componente.

+ +

Entonces L se lo registra que define a la clase C.

+ +

Si no se definió un tipo de accesibilidad en especial a la clase (public, protected, private), se lo setea por default como public.

+

+ + + Linkeo + + +

+ + +

La JVM permite que se pueda disponer del código para la ejecución a través de la carga, linkeo e inicialización. La carga como vimos es el proceso de traer una forma binaria para una clase a la JVM. Linkeo es el proceso de incorporar el tipo de dato binario en el estado de ejecución de la JVM. Dicho proceso está dividido en tres subpasos: verificación, preparación y resolución. La verificación permite que el tipo este propiamente formado y pueda ser ejecutado por la JVM. Preparación involucra la alocación de memoria necesario por el tipo, como memoria para cualquier variable de la clase. Resolución es el proceso de transformar referencias simbólicas en el pool de constantes en referencias directas. Estas implementaciones pueden demorar el proceso de resolución cada vez que una referencia simbólica es utilizado en el programa. Luego de la verificación, preparación, y opcionalmente la resolución se completaron, el tipo está listo para inicialización. Durante esta etapa las variables de clase se les da sus propios valores iniciales.

+ +

Link

+ +

Si bien la JVM, es flexible en cuando se realizan los procesos de carga, y linkeo, cuando se trata de la fase final de inicialización, es estricto. Todas las implementaciones deben inicializar cada clase en su primer uso activo. Cualquiera de estas acciones permiten que se inicialice dicho proceso:

+ +

Una nueva instancia de una clase es creado( en bytecodes o implícitamente, mediante reflection, clonado o deserialización).

+ +
    +
  • La invocación de un método estático declarado por una clase.
  • +
  • El uso o asignación de un campo estático declarado por una clase, excepto para campos estáticos que son finales e inicializados por una expresión constante en tiempo de ejecución.
  • +
  • La invocación de ciertos métodos reflectivos en la API de Java, como los métodos en la clase Class on clases en el paquete java.lang.reflect.
  • +
  • La designación de una clase como la inicial, cuando una JVM inicializa.
  • +
  • Todos los usos de otro tipo son pasivos, que no se disparan la inicialización del tipo.
  • +
+ +

Como se mencionó previamente, la inicialización de una clase requiere previa inicialización de sus superclases. Aplicado recursivamente, esta regla significa que todas las superclases de una clase deben ser inicializadas antes de la inicialización de esta clase. No es lo mismo con las interfaces, porque una subinterface o clase que implementa una interfaces. Entonces la inicialización de una clase requiere la inicialización previa de sus superclases pero no de sus superinterfaces.

+ +

Entonces un tipo como debe ser inicializado ante su primer uso activo, el mismo cuando deba ser inicializado si no fue linkeado deberá pasar por este proceso antes, y si no fue cargado, se ejecuta este paso. Entonces el proceso por el que pasa un tipo es solamente disparado por la inicialización y no por la carga.

+

+ + + Proceso de Linkeo + + +

+ + +

Como ya se describió previamente la carga de un tipo, sea una clase o una interfaz, se debe luego pasar por la fase de linkeo que se separa en tres partes, a continuación se describirán las fases del linkeo.

+

+ + + Verificación + + +

+ + +

El primer paso del linkeo implica la verificación, que es asegurar que el tipo cumple con las semánticas del lenguaje y no viola la integridad de la jvm. Este paso es otro en el que es flexible, solo debe ser ejecutado cuando se necesita y los diseñadores pueden decidir cuando y como verificar los tipos. La JVM lista todas las excepciones que deberían lanzar este proceso y en qué circunstancias hacerlo. Si bien la JVM indica cuales son las razones y en qué contexto deberían lanzarse los errores, no indica formalmente o estrictamente como llevar a cabo como y en qué orden debería hacerse la detección de errores.

+ +

Muchos de estos chequeos son probablemente realizados en ciertos tiempos de acuerdo a la implementación de la jvm. Por ejemplo, durante la fase de carga, la jvm debe parsear el stream del binario que representa el tipo de la clase/interfaz y construir las estructuras a partir del pool de constantes. En este punto solo se realizan algunos chequeos mínimos, que pueden involucrar que solo no se rompa la jvm cuando se parsea el binario y que se está esperando el formato indicado. Incluso aunque algunos de estos pasos se realizan previo a la fase de linkeo, aún siguen formando parte de este paso, y en caso de obtener algún error debería lanzarse en ese momento y sin embargo estos controles se agrupan dentro de una categoría llamada verificación. Otro chequeo realizado posteriormente es que todas las clases menos Object tengan una superclase, esto se puede hacer en la fase de carga, para asegurar que todas las superclases también se carguen.

+ +

Otro chequeo, que en general se hace después de la verificación oficial es la de chequear referencias simbólicas. Como se lo mencionó antes, esto involucra que se tengan que buscar referencias a clases, interfaces, variables y métodos a referencias simbólicas en el constant pool, y reemplazar las referencias simbólicas por referencias directas. Además en este tiempo también se hace el chequeo de permisos de los objetos que se están resolviendo de su referencia simbólica a la referencia concreta.

+ +

Entonces que se chequea en la fase propiamente dicha de verificación? Todo lo que no se mencionó hasta ahora, algunos ejemplos incluyen:

+ +

Chequear que la clase finales no se subclaseen +Chequear que los métodos finales no se sobrescriben +Asegurarse que no haya declaraciones de métodos incorrectos (colisión de nombres de métodos con la misma firma, tipos de parámetro, chequeo del tipo de retorno). También se verifica que la clases y todos sus superclases tengan aún su código binario compatible con la jvm y que puedan comunicarse entre sí.

+ +

Chequear que las entradas del constant pool sean consistentes unos con otros, de acuerdo a los tipos y sus valores asociados, que estén bien formados y tipados. También se hace el chequeo del bytecode si es válido. En este caso no hay nada estricto en cuanto a cómo se deben chequear, se puede hacer todo el chequeo del bytecode previa ejecución o a medida que se van ejecutando las instrucciones.

+

+ + + Cargando Constraints + + +

+ +

Asegurar el tipado seguro de referencias en la presencia de class loaders es importante. Es posible que cuando dos clases distintas de class loaders cargan una clase o interfaz llamada N, el nombre N pueda denotar una clase diferente uno del otro loader.

+ +

Cuando una clase o interfaz C = <N1, L1> crea una referencia simbólica a un campo o método de otra clase o interfaz D = <N2, L2>, la referencia simbólica incluye un descriptor especificando el tipo de campo, o el retorno y los tipos de los argumentos. Es esencial que cualquier tipo de nombre N mencionado en un campo, variable o descriptor de método, denotan la misma clase cuando se carga con L1 y/o L2.

+ +

Para asegurar esto, la JVM impone la carga de constrains de la forma NL1 = NL2, durante la fase de preparación y linkeo. Para asegurar estos constraints, la JVM, en ciertos tiempo, registrará que un loader en particular es el inicializador de una clase, después de registrar una clase, la JVM debe validar que no se esté violando la integridad de qué otro loader sea el inicializador de esa misma clase. Si sucede esto se lanza una excepción del tipo LinkageError, y la operación de registración falla. De esa manera cuando se carga un constraint, la JVM también chequea que dicho constraint no se haya ya definido, sino se lanza la misma excepción. Estos son los dos momentos en los que se realiza dicho chequeo.

+ +

Por ej.

+ +

Existe un loader en L en el que L fue registrado que es el inicializador de la clase C con nombre N, por otro lado existe un loader L2 que ha sido registrado como el inicializador de la clase C2, con nombre N. La equivalencia por transitividad dice que NL = NL2 pero C != C2.

+

+ + + Preparación + + +

+ +

Después de que la JVM cargó la clase y realizó las verificaciones de su correspondiente fase, entonces pasa a la fase preparación, en esta fase, la JVM aloca memoria para las variables de la clase y los setea a los valores iniciales declarados. Las variables de clase no son inicializados hasta la fase de inicialización. La alocación de memoria se hace de acuerdo al tipo de dato declarado, en la siguiente tabla:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TipoValor inicial
int0
long0L
short(short) 0
char‘\u0000’
byte(byte) 0
booleanfalse
referencenull
float0.0f
double0.0d
+ +

Aunque el booleano aparece en esta tabla, en realidad, la JVM tiene muy poco soporte para booleanos, y estos se traducen a tipo int, que es 0 para false y 1 para true….. Entonces en el caso de los booleanos, estos se traducen a ints y se inicializan a menos que explícitamente se ponga en True a 0.

+

+ + + Resolución + + +

+ + +

La fase final del linkeo es la resolución y es el proceso de localizar las clases, interfaces, variables y métodos referidos de manera simbólica del tipo de constant pool, y el reemplazo de estas referencias por referencias directas. Esta fase es opcional, a menos que cada referencia simbólica es primero usada por el programa.

+ +

Para más información sobre las etapas de esta fase referirse a y

+ +

Fase de inicialización +Si bien esta fase no forma parte del linkeo ya, solo mencionaremos brevemente que sucede, en esta etapa.

+ +

El paso final para que una clase/interfaz esté listo para ejecución es la inicialización, el proceso en el que se setean los valores iniciales finales. En este caso más puntualmente se setean los valores iniciales de las variables de clase, estos valores se designan de acuerdo al tipo de la variable. En esta fase también se precalculan los valores declarados como estáticos. por ej.

+ +
class Ejemplo1 {
+
+    // "= 3 * (int) (Math.random() * 5.0)" is the class variable
+    // initializer
+    static int size = 3 * (int) (Math.random() * 5.0);
+}
+
+ +

así como las declaraciones dentro de un bloque static

+ +
class Example1b {
+
+    static int size;
+
+    // This is the static initializer
+    static {
+
+        size = 3 * (int) (Math.random() * 5.0);
+    }
+}
+
+ +

Todas las variables de clase y estáticas iniciales de un tipo son colectados por el compilador Java y puestos en un método especial. Para las clases es el método de inicialización de clase y para interfaces el método de inicialización de interface. En las clases e interfaces este método es llamado “” (si en serio). Métodos regulares de una aplicación Java no pueden invocar este método, solo puede hacerse mediante la JVM.

+ +

Esta fase de inicialización consiste en dos pasos.

+ +

Inicializar la superclase directa de una clase, si no fue inicializado ya, ejecutar el método de inicialización, si se definió o necesita definirse y ejecutarse. Cuando se inicializa la superclase directa, estos dos pasos deben realizarse también. En el caso de las interfaces no se necesita inicializar su superinterfaz, solo consiste en una fase, que es la de ejecutar el método de inicialización de la interface, si necesita hacerse esto. También la jvm debe asegurarse de que el proceso este correspondientemente sincronizados, por ejemplo, si múltiples threads necesita inicializar una clase, solo uno podrá realizar esto, mientras que el resto espera. Cuando uno termina, este debe notificar del cambio al resto de los threads.

+

+ + + Classes + + +

+ +

La construcción principal alrededor de la cual gira Java (y, en consecuencia la JVM) es la Clase. A diferencia de otros lenguajes más dinámicos, las de Java son construcciones puramente estáticas, lo que hace que sea muy difícil alterarlas durante la ejecución; sin embargo, si lo que buscamos es realizar alguna tarea de introspection, Java provee los medios para obtener una descripción en runtime de las clases que permite realizar todo tipo de consultas.

+ +

Ojo! No hay que confundir a las clases (instancias de Class[T]) con las construcciones que usamos para acceder a los métodos estáticos: Class[T] != T

+ +

Para obtener una de estas representaciones de un tipo de Java, Scala ofrece la siguiente interfaz:

+ +
// obtener la clase a partir de su identificador
+classOf[MiClase] //en Java sería MiClase.class
+
+//obtener la clase a partir de una instancia
+instancia.getClass
+
+ +

Una vez adquirida una de estas representaciones de un tipo podemos usar su interfaz para realizar todo tipo de consultas:

+ +
trait T { def f: Int }
+class C extends T {
+  def m(x: Int) = x
+  var f = 5
+}
+
+classOf[Any] // returns Class[java.lang.Object]
+val classC = classOf[C]
+val c = new C
+
+classC.getName // returns "C" : String
+classC.getSuperclass // returns java.lang.Object : Class[_]
+classC.getInterfaces // returns Array(T) : Array[Class[_]]
+classC.isEnum // returns false
+classC.isAnnotationPresent(classOf[SomeAnnotation]) // returns false
+
+classC.getDeclaredFields // returns Array(C.f) : Array[Field]
+classC.getFields // returns Array() ~> fields públicos (con heredados)
+val fieldF = classC.getDeclaredField("f")
+fieldF.getType // returns int : Class[_]
+fieldF.getAnnotations // returns Array() : Array[Annotation]	fieldF.get(c) // Excepción! El campo es privado => no es accesible
+fieldF.setAccessible(true)
+fieldF.get(c) // returns 5
+fieldF.set(c, 8)
+fieldF.get(c) // returns 8
+
+classC.getDeclaredMethods // returns Array(C.f_$eq(int), C.f(), C.m(int)) : Array[Method]
+val methodM = classC.getMethod("m", classOf[Int])
+methodM.getName // returns "m" : String
+methodM.getParameters // returns Array(int arg0) : Array[Parameter]
+methodM.getParameterTypes // returns Array(int) : Array[Class[_]]
+methodM.getReturnType // returns int : Class[_]
+methodM.getTypeParameters // returns Array():Array[TypeVariable[Method]]
+methodM.isVarArgs // returns false
+methodM.invoke(c, new Integer(3)) // returns 3 : Object
+
+classC.getDeclaredConstructors // returns Array(C())
+val constructorC = classC.getConstructor()
+constructorC.newInstance() // returns a new C
+
+

+ + + Reflection en Scala + + +

+ +

+ + + Universes + + +

+ +

Los universos son el punto de entrada al framework de reflection. Existen dos tipos principales de universo, de runtime y de compilación, los cuales sirven para acceder a la estructura de tipos existente en tiempo de ejecución y en tiempo de compilación, respectivamente.

+ +

Para analizar el programa en ejecución, obtener atributos y métodos y ejecutar de forma dinámica necesitamos importar el universo de runtime, lo cual puede hacerse de la siguiente forma:

+ +
import scala.reflect.runtime.{universe => ru}
+
+ +

La API oficial de Universe provee información detallada de cómo utilizar los universos.

+

+ + + Types + + +

+ +

Los tipos encapsulan la información referente a muchos aspectos de clases y traits. Esto incluye un listado completo de sus miembros (métodos, campos, alias de tipo, definiciones anidadas, etc.) y la posibilidad de compararse entre ellos.

+ +

Para obtener un tipo puedo ejecutar el siguiente código:

+ +
import scala.reflect.runtime.universe._
+
+class A
+
+typeOf[A] //returns the Type A
+
+weakTypeOf[List[Int]] // for types with type arguments this is how you should do it
+
+ +

Los usos más comunes para un tipo son compararlo con otros tipos o realizar chequeos en cuanto a sus miembros definidos.

+ +
import scala.reflect.runtime.universe._
+
+class A{
+  def m = 5
+ val f = 3
+}
+
+class B extends A
+type A2 = A
+
+// Igualdad
+typeOf[A] =:= typeOf[A] // true ~> A es el mismo tipo que A
+typeOf[A] =:= typeOf[B] // false ~> A no es el mismo tipo que B
+typeOf[A] == typeOf[A2] // false ~> == no chequea alias
+typeOf[A] =:= typeOf[A2] // true ~> =:= se da cuenta que son el mismo tipo
+
+// Subtipado
+typeOf[A] <:< typeOf[A] // true ~> A es subtipo de A
+typeOf[B] <:< typeOf[A] // true ~> B es subtipo de A
+typeOf[A] <:< typeOf[B] // false ~> A no es subtipo de B
+typeOf[Int] <:< typeOf[Long] // false ~> Int no es realmente subtipo de Long
+typeOf[Int] weak_<:< typeOf[Long] // true ~> pero Sí hace algo parecido
+weakTypeOf[List[B]] <:< weakTypeOf[List[A]] // true
+weakTypeOf[List[A]] <:< weakTypeOf[List[B]] // false
+
+// Declaraciones
+typeOf[A].declarations // returns SynchronizedOps(constructor A, method m, value f, value f)
+typeOf[A].takesTypeArgs // returns false
+typeOf[A].typeParams // returns List()
+
+ +

La API oficial de Types provee información detallada de cómo utilizar los types.

+

+ + + TypeTags + + +

+ +

Como ya se mencionó antes, los tipos de Scala se borran al ser compilados para ejecutar en la JVM. Esto significa que, si inspeccionamos en runtime una instancia, no tenemos acceso a toda la información disponible previo a la compilación. Un ejemplo de esto son los Tipos Paramétricos que son eliminados al compilar.

+ +

Esto puede a veces producir comportamiento inesperado:

+ +
def detectorDeEnteros(list : List[Any]) = list match {
+  case list: List[Int] => true // WARNING! El [Int] no existe en runtime. Sólo List
+  case other => false
+}
+	
+detectorDeEnteros(List("no", "somos", "enteros")) // returns true
+
+ +

Los TypeTags son, a grandes razgos, objetos que pueden ser usados para preservar durante la ejecución toda la información de un tipo disponible al momento de compilar. Estas estructuras son generadas siempre por el compilador, y pueden obtenerse de varias formas:

+ +
import scala.reflect.runtime.universe._
+
+// Usando el método typeTag
+val tt1 = typeTag[List[Int]]
+
+// Usando un parámetro implicito. Si el compilador no encuentra un valor en contexto, lo genera.
+def obtenerTypeTag[T](implicit tt: TypeTag[T]) = tt
+val tt2 = obtenerTypeTag[List[Int]]
+
+// Usando un Context Bound en un Type Parameter
+def obtenerTypeTagDeOtraForma[T: TypeTag] = implicitly[TypeTag[T]]
+
+ +

Teniendo esto en cuenta, el código anterior podría reescribirse así:

+ +
import scala.reflect.runtime.universe._
+
+def detectorDeEnteros[T: TypeTag](list : List[T]) =
+  typeTag[T].tpe =:= typeOf[Int]
+
+detectorDeEnteros(List("no", "somos", "enteros")) // returns false
+
+ +

Los TypeTags pueden ser usados en conjunto con el extractor TypeRef para analizar un tipo:

+ +
import scala.reflect.runtime.universe._
+
+def explotar[T: TypeTag] = typeTag[T].tpe match {
+  case TypeRef(typePrefix, symbol, typeArguments) => (typePrefix, symbol, typeArguments)
+}
+
+val (typePrefix, symbol, typeArguments) = explotar[List[Int]]
+
+typePrefix // package object scala : Symbol
+symbol // type List : Symbol
+typeArguments // List(Int) : List[Type]
+
+ +

La API oficial de TypeTags provee información detallada de cómo utilizar los types.

+

+ + + Symbols + + +

+ +

Los símbolos vinculan nombres a los elementos del metamodelo. Todas las cosas a las que se les puede dar un nombre en Scala tienen un símbolo asociado.

+ +

Los símbolos contienen también toda la información asociada a las entidades y una amplia interfaz para consultarla, lo cual los convierte (junto con los Types) en la abstracción central para realizar introspection.

+ +

Los símbolos se organizan en una jerarquía que refleja la estructura básica de las entidades a las que están asociados (por ejemplo, el símbolo que representa un parámetro de un método es hijo del símbolo asociado a dicho método el cual, a su vez, es hijo del símbolo del trait, clase u objeto que lo define, etc.). Distintos tipos de símbolo existen para reflejar las distintas entidades, con sus distintas interfaces de consulta.

+ +

Algunos métodos de la API exponen un tipo de retorno más genérico que el que uno busca. Por ejemplo, si busco las declaraciones de una clase puedo obtener una lista con muchos tipos de símbolo (MethodSímbol, ModuleSímbol, etc). pero la lista va a ser de tipo List[TermSymbol]. Para estos casos existen varios métodos de conversión *as* para convertir un símbolo a su versión más específica.

+ +
import scala.reflect.runtime.universe._
+
+class C[T] { def test[U](x: T)(y: U): Int = ??? }
+
+// member retorna una instancia de Symbol
+val testMember: Symbol = typeOf[C[Int]].member(TermName("test"))
+
+// como sabemos que es un método, podemos obtener un MethodSymbol que tiene una interfaz más rica
+val testMethod: MethodSymbol = testMember.asMethod
+
+

+ + + TypeSymbols + + +

+ +

Representan un tipo, class o trait, así como también tipos paramétricos. Proveen más que nada información sobre la varianza.

+

+ + + ClassSymbols + + +

+ +

Son un caso particular de TypeSymbol. Proveen toda la información contenida en la declaración de una clase o trait.

+ +
import scala.reflect.runtime.universe._
+ 
+object C
+class C[T] {
+  
+}
+
+val classSymbol: ClassSymbol = ??? // Más adelante vemos cómo conseguirlo
+
+classSymbol.isCaseClass // returns false
+classSymbol.isModule  // returns false
+classSymbol.isTrait  // returns false
+classSymbol.companion // returns object C : Symbol
+classSymbol.isPublic // returns true
+classSymbol.typeParams // returns List(type T)
+classSymbol.toType // returns the Type
+classSymbol.name // returns C : TypeName
+classSymbol.primaryConstructor // returns constructor C : Symbol
+
+

+ + + TermSymbols + + +

+ +

Representan las declaraciones de val, var, def u object, así como también packages y value parameters

+

+ + + MethodSymbols + + +

+ +

Casos particulares de TermSymbol. Representan las declaraciones de def.

+ +
import scala.reflect.runtime.universe._
+ 
+class C[T] {
+  def m[U>:T](x: T)(y: U): Int = ???
+}
+
+val methodSymbol = typeOf[C[Int]].decl("m": TermName).asMethod
+
+methodSymbol.typeSignatureIn(typeOf[C[Int]]) // returns Type [U >: Int](x: Int)(y: U)Int
+methodSymbol.typeSignatureIn(typeOf[C[String]]) // returns Type [U >: String](x: Int)(y: U)Int
+methodSymbol.name // return m: methodSymbol.NameType
+methodSymbol.owner // returns C: ClassSymbol
+methodSymbol.isMethod // returns true
+methodSymbol.isConstructor // returns false
+methodSymbol.isPublic //returns true
+methodSymbol.paramLists // returns List(List(value x), List(value y)) : List[List[Symbol]]
+
+

+ + + ModuleSymbols + + +

+ +

Representan las declaraciones de object. Permiten obtener la clase implícitamente asociada con la declaración del objeto ( es decir, la clase que sabemos que tiene que existir para que exista el objeto, pero que Scala no nos muestra).

+ +

La API oficial de Symbols provee información detallada de cómo utilizar los symbols.

+

+ + + Mirrors + + +

+ +

Los mirrors son construcciones que centralizan el acceso a la información de reflection, desacoplandola de las clases del modelo. Existen varios tipos de mirrors. El mirror a utilizar se elige en función del tipo de operación que se busca realizar. Se los suele clasificar en 2 grupos:

+ +
    +
  • Classloader Mirrors: Sirven para obtener las representaciones de los tipos y sus miembros. A partir de ellos se puede obtener invoker mirrors. Su utilidad principal es convertir nombres en Symbols.
  • +
  • Invoker Mirrors: Mirrors especializados, que implementan las tareas más comunes (como invocar métodos y acceder a atributos).
  • +
+

+ + + ReflectiveMirror + + +

+ +

Se usan para cargar Symbols a partir de nombres y para obtener Invoker Mirrors.

+ +
val ru = scala.reflect.runtime.universe
+val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
+
+

+ + + InstanceMirror + + +

+ +

Se usan para crear Invoker Mirrors bindeados a la instancia para métodos y campos y para definiciones internas de clases y objetos. También pueden usarse para obtener el Symbol y Type asociados a la clase de la instance.

+ +
class C { def x = 2 }
+
+val ru = scala.reflect.runtime.universe
+val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
+val instanceMirror = runtimeMirror.reflect(new C)
+
+val classSymbol = instanceMirror.symbol.asClass
+val classType = classSymbol.toType
+val javaClass = runtimeMirror.runtimeClass(classSymbol) // The concrete runtime java class for the symbol
+
+

+ + + MethodMirror + + +

+ +

Se usan para invocar métodos de instancia (que son los únicos existentes en Scala) y constructores.

+ +
class C { def x = 2 }
+
+val ru = scala.reflect.runtime.universe
+val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
+val methodSymbol = ru.typeOf[C].declaration(ru.TermName("x")).asMethod
+val methodMirror = instanceMirror.reflectMethod(methodSymbol)
+methodMirror.apply() //returns 2
+
+

+ + + FieldMirror + + +

+ +

Se usan para leer/escribir los atributos que Scala usa internamente para los campos.

+ +
class C { val x = 2 }
+
+val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
+val instanceMirror = runtimeMirror.reflect(new C)
+
+val fieldSymbol = ru.typeOf[C].decl(ru.TermName("x")).asTerm
+fieldSymbol.isVal // returns false
+fieldSymbol.isMethod // returns true
+
+val backingFieldSymbol = fieldSymbol.accessed.asTerm
+backingFieldX.isVal // return true
+backingFieldX.isMethod // return false
+
+val fieldMirror = instanceMirror.reflectField(fieldSymbol)
+fieldMirror.get // returns 2
+fieldMirror.set(3)
+fieldMirror.get // returns 3
+
+// Los fields también son métodos, por lo tanto puedo hacer esto
+instanceMirror.reflectMethod(fieldSymbol.asMethod).apply() // returns 3
+
+

+ + + ClassMirror + + +

+ +

Se usan para crear Invoker Mirrors para los constructores.

+ +
case class C(x: Int)
+
+val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
+val classSymbol = ru.typeOf[C].typeSymbol.asClass
+val classMirror = runtimeMirror.reflectClass(classSymbol)
+val constructorSymbol = ru.typeOf[C].decl(ru.nme.CONSTRUCTOR).asMethod
+val constructorMirror: ru.MethodMirror = classMirror.reflectConstructor(constructorSymbol)
+constructorMirror.apply(2) // returns C(2)
+
+

+ + + ModuleMirror + + +

+ +

Se usan para acceder a las instancias de los singleton objects.

+ +
object C { def x = 2 }
+val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
+val moduleSymbol = ru.typeOf[C.type].termSymbol.asModule
+val moduleMirror = runtimeMirror.reflectModule(moduleSymbol)
+moduleMirror.instance // returns C
+
+ +

La API oficial de Mirrors provee información detallada de cómo utilizar los mirrors.

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_14/index.html b/scripts/clase_14/index.html new file mode 100644 index 0000000..17a14b7 --- /dev/null +++ b/scripts/clase_14/index.html @@ -0,0 +1,1001 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 14 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + El compilador y usted + + +

+ + +

Uno de los features más interesantes que ofrece un lenguaje es la capacidad de extenderlo. En mayor o menor +medida, siempre que escribo un programa “extiendo” de alguna manera lo que ya existe, pero algunas +tecnologías ofrecen además herramientas para, sin necesariamente cambiar su sintaxis, manipular la forma de +escribirlas.

+ +

Este tipo de herramientas permite eliminar gran cantidad de boilerplate y, a veces, crear una +manera completamente distinta de expresar la solución para un problema. Esto tiene un impacto muy grande en +la legibilidad y extensibilidad del código y permite crear desde mejoras menores para evitar repetición o +simplificar la lectura, hasta DSLs internos o sistemas de reescritura que aprovechan la metadata del código +para trabajar en mejores niveles de abstracción.

+ +

Vamos a contar algunos ejemplos de estás ideas implementadas en el lenguaje Scala. Cómo usarlas, qué pasa +abajo del capó y en qué situaciones podrían ser útiles.

+

+ + + Case Classes + + +

+ + +

Una Case Class es una abstracción pensada para proveer una forma rápida y sintácticamente agradable para +crear estructuras inmutables similares a los Tipos Algebraicos de los lenguajes funcionales. Si bien son +un concepto interesante, lo más importante es entender que son solamente un “atajo”. Scala no hace nada +especial con las case classes, simplemente cada definición “case” es pasada por un proceso de reescritura +en el compilador: Por cada Case Class se define una clase común y corriente, pero se implementan +automáticamente algunos métodos bien conocidos coyo código es predecible o repetitivo.

+ +

Entonces, una Case Class es simplemente una clase cualquiera al que el compilador le agrega:

+ +
    +
  • Properties de sólo lectura para cada parámetro de clase
  • +
  • Una forma sencilla de clonarla (método copy)
  • +
  • Una conversión a String clara, basada en su nombre y sus parámetros de clase
  • +
  • Métodos de comparación y hashing
  • +
  • Un companion object que puede usarse para construir instancias y como patrón para matching
  • +
  • Otros detalles (las case clases de aridad >1 extienden Product)
  • +
+ +

Sólo con este poquito las diferencias que se producen en el cógido son enormes. Acá hay dos +implementaciones de la misma(*) clase, una usando case classes y una sin ellas.

+ +

###Con Case Classes

+ +
// Properties de sólo lectura
+// Copy
+// Conversión a String lindo
+// Comparación / hashing
+// Constructor sin new, parcialmente aplicable
+// Unapply
+// Etc...
+case class Materia(nombre: String, ciclo: Int)(criterioDeAprobación: Alumno => Boolean) {
+	def aprobados(curso: Curso) = curso filter criterioDeAprobación
+}
+
+ +

###Sin Case Classes

+ +
class Materia {
+	// Properties de sólo lectura
+	private var _nombre: String = _
+	def nombre = _nombre
+	private var _ciclo: Int = _
+	def ciclo = _ciclo
+	private var _criterioDeAprobación: Alumno => Boolean = _
+	private def criterioDeAprobación = _criterioDeAprobación
+
+	private def this(nombre: String, ciclo: Int, criterioDeAprovación: Alumno => Boolean) {
+		this()
+		_nombre = nombre
+		_ciclo = ciclo
+		_criterioDeAprobación = criterioDeAprobación
+	}
+
+	// Copy
+	def copy(nombre: String = this.nombre, ciclo: Int = this.ciclo) = new Materia(nombre,ciclo,criterioDeAprobación)
+	
+	// Conversión a String lindo
+	override def toString = s"Materia($nombre, $ciclo)"
+
+	// Comparación / hashing
+	override def equals(other: Any) = other match {
+		case m: Materia => m.nombre == nombre && m.ciclo == ciclo
+		case _ => false
+	}
+	override def hashCode = ???
+
+	def aprobados(curso: Curso) = curso filter criterioDeAprobación
+}
+
+object Materia {
+	// Constructor sin new, parcialmente aplicable
+	def apply(nombre: String, ciclo: Int) = { criterioDeAprobación: (Alumno => Boolean) =>
+		new Materia(nombre, ciclo, criterioDeAprobación)
+	}
+
+	// Unapply
+	def unapply(materia: Materia): Option[(String, Int)] = ???
+}
+
+

+ + + String Interpolators + + +

+ + +

Muchos lenguajes ofrecen la posibilidad de interpolar Strings para evitar el boilerplate y la confusión +producto de las cadenas larguisimas de concatenación necesarias para poder construir un string a partir de +multiples objetos.

+ +

La forma de hacer esto en scala es precediendo el string con una s y envolviendo las expresiones a +interpolar con ${…} (las expresiones que sólo consisten en una variable pueden prescindir de las llaves).

+ +
val nombre = "Técnicas Avanzadas de Programación"
+val ciclo = 3
+val alumnos: List[Alumno] = ...
+
+val sinInterpolación = "La materia " + nombre + " del ciclo " + ciclo + " tiene " + alumnos.size + "alumnos"
+
+val conInterpolación = s"La materia $nombre del ciclo $ciclo tiene ${alumnos.size} alumnos"
+
+ +

La prueba de que esta forma de escritura es (al menos ligeramente) mejor que la concatenación directa está +en que la mayoría de la gente no nota a simple vista que al último string del ejemplo le falta un espacio ;)

+ +

Además de la “s” Scala ofrece otros interpoladores:

+ +
// f: Permite preceder las expresiones insertadas por un patron de formateo. Y es type safe!
+val formateado = f"El promedio en $nombre%s es ${alumnos.sum / alumnos.size}%2.2f"
+
+// raw: Trata a los caracteres especiales que modificarían el string como caracteres normales. 
+val procesado   = s"Estos\nSon\nSaltos\nDe\nLinea"
+val sinProcesar = raw"Estos\nNo\nSon\nSaltos\nDe\nLinea"
+
+ +

Sin embargo, el aspecto más interesante de la interpolación de strings en Scala es que no son palabras +reservadas, sino mensajes. Tanto s y f como raw son en realidad mensajes que el compilador envía a +una instancia de StringContext cuando ve el string literal. Esto permite que creemos nuestros propios +interpoladores extendiendo StringContext!

+ +

Digamos que tenemos una clase con la que representamos los mils y nos gustaría poder mostrar una lista de +mails mostrando sólo las primeras 4 letras y el dominio de cada uno (para evitar los crawlers). Una forma +posible para hacer esto es definir un interpolador que procese los parametros del tipo Email de forma +distinta a los demás:

+ +
class Email(val id: String, val dominio: String) {
+	override def toString = s"$id@$dominio"
+	def toEncriptedString = s"${id.take(4)}...@$dominio"
+}
+
+implicit class EmailsContext(val context: StringContext) {
+	def emails(arguments: Any*) = ("" /: context.parts.zip(arguments)) {
+		case (acum, (part, exp: Email)) => acum + part + exp.toEncriptedString
+		case (acum, (part, exp))        => acum + part + exp
+	}
+}
+
+val nico = new Email("nscarcella", "gmail.com")
+val ernesto = new Email("bossi.ernestog", "gmail.com")
+val ivan = new Email("ivanlocolosredoooo1964", "hotmail.com.es")
+
+emails"$nico, $ernesto, $ivan" // Esto va a mostrar los mails encriptados
+s"$nico, $ernesto, $ivan" // Esto va a mostrar los mails completos
+
+ +

Este es sólo un pequeño ejemplo de lo que se puede hacer con interpoladores. Noten que no hay ninguna +necesidad de que el método que se envía a un StringContext sea un String; esto quiere decir que podemos +usarlos para construir todo tipo de objetos a partir de Strings!

+ +
implicit class EmailContext(val context: StringContext) {
+	def mail(arguments: Any*): Email = {
+		val mergeado = context.s(arguments:_*)
+		val extractor = "(.*)@(.*)".r
+		val extractor(id,dominio) = mergeado
+		
+		new Email(id,dominio)			
+	}
+}
+
+mail"un-id@un-dominio.com" // retorna un Email
+mail"lalala" // falla por no cumplir el patrón
+
+ +

Todo muy lindo pero cómo llegan esos métodos a StringContext? y que es esa palabra “implicit”? Bueno, eso +nos lleva al siguiente tema…

+

+ + + Implicits + + +

+ + +

Los implicits son una de las herramientas más novedosas y mágicas de Scala. Vienen en varios sabores, pero +lo que todos ellos tienen en común es que permiten poner cosas en un contexto para luego poder usarlas sin +una referencia explicita en el código (o sea, implicitamente :P).

+

+ + + Implicit Class + + +

+ + +

Una Clase Implicita es una forma declarativa y no-invasiva de extender una clase.

+ +

Digamos que buscamos agregarle comportamiento a un objeto sin cambiar su implementación. Una forma +conocida de hacer esto es anteponiendo otro objeto que sepa hacer el nuevo comportamiento y tenga una +referencia al objeto viejo para poder usar el comportamiento ya existente.

+ +
class StringExtendido(unString: String) {
+	// Este método es demasiado específico para querer ponerlo en String
+	def esUnMail = unString.length > 10 && unString.contains("@") && unString.endsWith(".com") 
+}
+  
+new StringExtendido("foobar@gmail.com").esUnMail // Sí!
+new StringExtendido("Hola Mundo!").esUnMail      // No!
+"Chau Mundo...".esUnMail                         // Esto no compila. No cambié la clase String.
+
+ +

La ventaja de esta aproximación es que puedo agregar tantos métodos cómo quiero sin preocuparme de que +colisionen diferentes implementaciones, ya que cada uno puede instanciar el “wrapper” de String que +prefiere; lo malo es que ensuciar el código para envolver al String.

+ +

Pero qué tal si pudiera explicarle al compilador lo que estoy tratando de hacer? Qué tal si pudiera pedirle +que, cuando vea que estoy mandandole a un String un mensaje que no entiende, que se fije a ver si está +definido en esta clase wrapper y, si está, que en vez de fallar me lo envuelva él solito?

+ +

Bueno, eso es exactamente lo que son las Clases Implicitas: Una clase común y corriente que, cuando están +en contexto, el compilador usa para wrapear objetos que no hubieran entendido algún mensaje.

+ +

Para definir una clase implicita, alcanza con hacerle recibir un único parámetro de clase del tipo que +quiero wrappear y anteponer la palabra “implicit” para que el compilador sepa que puede usarla:

+ +
implicit class StringExtendido(unString: String) {
+	def esUnMail = unString.length > 10 && unString.contains("@") && unString.endsWith(".com") 
+}
+  
+"foobar@gmail.com".esUnMail // Ahora esto funciona! El compilador wrappea el string sin que yo lo vea.
+new StringExtendido("foobar@gmail.com").esUnMail // La linea de arriba se reescribe a esto. 
+
+ +

Es importante notar que el wrappeo automático sólo ocurre cuando mando un mensaje que el objeto no +entiende. Esto significa que las clases implicitas sirven para extender un tipo, pero no para redefinirle +implementaciones. Esto las convierte en una forma segura de extender una interfaz, sin preocuparse por +romper la implementación previa. Este es un buen momento para mirar el código que usamos para extender StringContext en +el tema anterior y asegurarse de entender que está pasando.

+ +

También hay que saber que el compilador no va a tratar de encadenar más de una aplicación de implicits por +expresión, así que hay que cuidar las firmas…

+

+ + + Implicit Methods (Implicit Conversions) + + +

+ + +

Las conversiones implicitas son similares a las clases implicitas pero, en lugar de definir una nueva clase para extender +una referencia, se utiliza para convertir algo de un tipo a otro ya existente.

+ +

Digamos, por ejemplo, que tenemos un sistema con las siguientes interfaces::

+ +
class Punto(x: Int, y: Int)
+object Mapa { def nombreDelLugar(lugar: Punto): String = ??? }
+object Input { def puntoPresionado: (Int, Int) = ??? }
+ 
+// Pedirle al mapa el nombre del punto presionado 
+val lugar = Input.puntoPresionado
+
+Mapa nombreDelLugar Input.puntoPresionado // Sería lindo poder hacerlo así, pero una tupla no es un punto...
+
+Mapa.nombreDelLugar(new Punto(lugar._1, lugar._2)) // Hay que hacer una conversión
+
+ +

Algo incomodo en este código es que, al pedir el punto presionado, recibo una Tupla2 pero lo que necesito es un Punto. +Semánticamente no es un problema, dado que tengo una forma concreta de convertir cualquier Tupla2 en un Punto. Podría +incluso evitar una posible repetición de esta lógica extrayendo esa conversión en una función:

+ +
def tuplaAPunto(lugar: (Int,Int)) = new Punto(lugar._1, lugar._2)
+
+Mapa.nombreDelLugar(tuplaAPunto(lugar)) // Ahora puedo usarlo así
+
+ +

Mejor? Sí. Pero si siempre que tengo una tupla y espero un punto tengo que aplicar esta función, sería lindo poder pedirle +al compilador que lo haga sin que yo lo tenga que escribir explicitamente; después de todo, mi función tuplaAPunto es +una transformación de Tuplas a Puntos.

+ +

Esto es exactamente para lo que las conversiones implicitas sirven. Puedo convertir una función que espera un único +parámetro en una conversión implicita anteponiendo la palabra implicit a su definición:

+ +
implicit def tuplaAPunto(lugar: (Int,Int)) = new Punto(lugar._1, lugar._2)
+
+ +

De ahora en adelante, si esta función (que es del tipo Tupla2 => Punto) está en contexto, el compilador va a aplicarla +automáticamente cada vez que usemos una Tupla2 en un lugar donde se esperaba un Punto. Eso permite reescribir nuestro uso +así:

+ +
Mapa.nombreDelLugar(Input.puntoPresionado)
+
+ +

Otro detalle interesante es que, por la forma en que Scala busca estos implicits, es posible, en lugar de importar la +función implicita en el contexto, definirla en el companion object de uno de los tipos en cuestión.

+ +
object Punto {
+	implicit def tuplaAPunto(lugar: (Int,Int)) = new Punto(lugar._1, lugar._2)
+}
+
+Mapa.nombreDelLugar(Input.puntoPresionado) // No necesito importar la función!
+
+

De esta manera estamos oficializando que un Punto puede ser obtenido a partir de una tupla en cualquier lugar.

+

+ + + Implicit parámeters + + +

+ + +

Los parámetros implícitos permiten establecer un valor por defecto para un parámetro, que puede ser configurado para cada +contexto. Si existe al momento de evaluar una función un valor implicito del tipo de uno de sus parámetros implicitos, +este valor se usa como parámetro automáticamente sin necesidad de escribirlo. Esto es especialmente útil cuando existen +una gran cantidad de llamadas a funciones que usan el mismo parámetro en un contexto:

+ +
	class Persona { def persistir(db: BaseDeDatos) = ??? }
+		
+	class Familia(padre: Persona, madre: Persona, hijos: List[Persona], abuelos: List[Persona]) {
+		def persistir(db: BaseDeDatos) {
+			padre.persistir(db)
+			madre.persistir(db)
+			hijos.foreach(_.persistir(db))
+			abuelos.foreach(_.persistir(db))
+		}
+	}
+	
+	\\ ...
+	
+	val db: BaseDeDatos = ???
+	unaFamilia.persistir(db)
+
+ +

En este ejemplo, la base de datos del método persistir puede declararse como un parámetro implicito anteponiendo la +palabra implicit al nombre del parámetro. Cabe aclarar que los parámetros implicitos deben ser los últimos parámetros +de la firma y deben estar en su propio grupo de aplicación.

+ +
	class Persona { def persistir(implicit db: BaseDeDatos) = ??? }
+		
+	class Familia(padre: Persona, madre: Persona, hijos: List[Persona], abuelos: List[Persona]) {
+		def persistir(implicit db: BaseDeDatos) {
+			padre.persistir
+			madre.persistir
+			hijos.foreach(_.persistir)
+			abuelos.foreach(_.persistir)
+		}
+	}
+	
+	\\ ...
+	
+	implicit val db: BaseDeDatos = ???
+	unaFamilia.persistir
+
+ +

Noten que, para poder evitar pasar el parámetro, es necesario que haya un valor implicito en el contexo. Los parámetros +implicitos son, a su vez, valores implicitos en el contexto del método.

+

+ + + Type Clases + + +

+ + +

Supongamos que tenemos un sistema que requiere poder persistir nuestros objetos de dominio utilizando multiples bases de +datos. Para mantener los ejemplos cortos, vamos a simplificar el problema suponiendo que solamente hace falta poder +guardar objetos (sin volver a leerlos o cambiarlos después).

+ +

Digamos que contamos con las siguientes interfaces para nuestras bases de datos:

+ +
	object SQL { def run(query: String) = ??? }
+	object Redis { def guardar(clave: String, valor: String) = ??? }
+
+

+ + + Aproximación Naíve + + +

+ + +

Una primera manera para integrar nuestro código a estas APIs es extendiendo la interfaz de nuestros objetos, agregando métodos para persistir en cada +tecnología.

+ +
// SQL --------------------------------------------------------------------------------------------
+
+trait PersistibleConSQL {
+	def tabla: String
+	def valores: List[String]
+}
+
+def persistirConSQL(obj: PersistibleConSQL) = {
+	SQL run s"INSERT INTO ${obj.tabla} VALUES ${obj.valores}"
+}
+
+// Redis ------------------------------------------------------------------------------------------
+
+trait PersistibleConRedis {
+	def clave: String
+	def valor: String
+}
+
+def persistirConRedis(obj: PersistibleConRedis) = {
+	Redis guardar (obj.clave, obj.valor)
+}
+
+// Dominio ----------------------------------------------------------------------------------------
+
+// Nuestra clase de dominio
+case class C(f1: String, f2: String) extends PersistibleConRedis with PersistibleConSQL {
+	def tabla = "C"
+	def valores = List(f1, f2)
+	def clave = "C"
+	def valor = s"{f1: $f1, f2: $f2}"
+}
+
+// Uso --------------------------------------------------------------------------------------------
+
+val c1 = new C("A", "1")
+val c2 = new C("B", "2")
+val c3 = new C("B", "3")
+
+persistirConSQL(c1)
+persistirConRedis(c2)
+persistirConSQL(c3)
+persistirConRedis(c3)
+
+ +

Esta aproximación tiene varios problemas: Es muy invasiva y requiere que todos las clases que se desean persistir puedan ser modificadas, ensucia la +interfaz de dominio y, si bien no es el caso en este ejemplo, podrían haber conflictos entre las interfaces requeridas por cada base de datos.

+

+ + + Aproximación Funcional + + +

+ + +

Lenguajes como Haskell proponen una aproximación alternativa: Type Classes.

+ +

Una Type Class define un conjunto de operaciones que tienen que ser soportadas por un tipo para pertenecer a ella. Cualquier tipo T puede pertenecer a la +type class C si alguien provee las operaciones que ella exige.

+ +

Guiandonos por esta idea, podemos definir una Type Class “PersistibleConSQL” que especifique todo lo que un Tipo debe poder hacer para ser considerado +persistible usando SQL:

+ +
trait PersistibleConSQL[T] {
+	def tabla(obj: T): String
+	def valores(obj: T): List[String]
+}
+
+ +

Es importante notar que este trait no pretende ser extendido por los tipos persistibles con SQL, simplemente dice qué debe ser posible hacer con sus +instancias (en este caso, obtener una tabla y una lista de valores). Usando este trait, podemos reescribir nuestra función de persisitencia de la +siguiente forma:

+ +
def persistirConSQL[T](obj: T)(persistible: PersistibleConSQL[T]) = {
+	SQL run s"INSERT INTO ${persistible.tabla(obj)} VALUES ${persistible.valores(obj)}"
+}
+
+ +

La función persistir ahora trabaja con dos parámetros: Uno de ellos es una instancia del tipo T, el cual queremos persistir. El otro parámetro debe ser +alguien que implemente las funciones tabla y valores sobre T. Este objeto es el que permite que T sea persistible con SQL, permitiendo desacoplar la idea +de persistencia de nuestro tipo de dominio.

+ +

Ahora podemos reescribir nuestro código sin ensuciarlo con lógica de persistencia. A cambio, debemos implementar un objeto aparte que lo vuelva +persistible:

+ +
case class C(f1: String, f2: String)
+
+object CSQL extends PersistibleConSQL[C] {
+	def tabla(obj: C) = "C"
+	def valores(obj: C) = List(obj.f1, obj.f2)
+}
+
+// uso
+
+persistirConSQL(new C("foo", "bar"))(CSQL)
+
+ +

Podemos pensar que el trait PersistibleConSQL[T] es una Type Class y nuestro tipo de dominio C la implementa a travéz del objeto CSQL.

+

+ + + Mejorando el uso con implicits + + +

+ + +

El problema de esta solución es que ahora tengo que preocuparme por tener en contexto al objeto CSQL y pasarlo por parámetro cada vez que quiero usarlo. +Ahí es donde los parámetros implicitos hacen lo suyo! Si cambiamos nuestra función de persistencia para que el parametro que provee las funciones sea +implicito podemos dejar que el compilador lo escriba por nosotros.

+ +
def persistirConSQL[T](obj: T)(implicit persistible: PersistibleConSQL[T]) = {
+	SQL run s"INSERT INTO ${persistible.tabla(obj)} VALUES ${persistible.valores(obj)}"
+}
+
+implicit object CSQL extends PersistibleConSQL[C] {
+	def tabla(obj: C) = "C"
+	def valores(obj: C) = List(obj.f1, obj.f2)
+}
+
+// uso
+
+persistirConSQL(new C("foo", "bar"))
+
+
+ +

Esta forma de “extender” tipos es tan común y flexible que Scala provee una notación especial para definir funciones con type clases:

+ +
def persistirConSQL[T](obj: T)(implicit persistible: PersistibleConSQL[T]) { ??? } // Esto mismo puede escribirse como está en la linea de abajo  
+def persistirConSQL[T : PersistibleConSQL](obj: T) { ??? } // Acá se ve más claro que esperamos un tipo T que pertenece a la typeclass PersistibleConSQL 
+
+ +

Uh… Pero ahora ya no está el parámetro “persistible”! Cómo consigo la instancia? Scala provee una función llamada implicitly para estas situaciones:

+ +
def persistirConSQL[T: PersistibleConSQL](obj: T) = {
+	val persistible - implicitly[PersistibleConSQL[T]]
+	SQL run s"INSERT INTO ${persistible.tabla(obj)} VALUES ${persistible.valores(obj)}"
+}
+
+

+ + + Macros + + +

+ +

+ + + Qué son? + + +

+ + +

Macros es una herramienta muy poderosa que permite definir reescrituras de AST (Abstract Syntax Tree) y está presente en +muchos lenguajes y tecnologías. A grandes razgos, la útilidad de las macros consiste en tomar una construcción sintáctica +válida y reemplazarla por otra en tiempo de compilación, permitiendo así que la sintaxis que normalmente construiría un +cierto programa construya otro totalmente diferente. +En Scala, la utilización de macros está definida en el paquete

+ +
scala.language.experimental.macros
+
+ +

el cual debe ser importado para poder trabajar.

+ +

Una macro de Scala se compone de dos partes: Una declaración y una implementación. Al momento de compilar, los usos de la +función declaración son procesados para reemplazarlos por el resultado de aplicar la función implementación. Definir la +declaración de una macro es muy similar a definir una función común pero, en lugar del cuerpo, se utiliza la palabra clave +macro seguida del nombre de la función implementación.

+ +
  // declaración
+  def miMacro(parametro1: String, parametro2: Int) = macro miMacro_impl
+  
+  // implementación
+  def miMacro_impl(c: Context)(parametro1: c.Expr[String], parametro2: c.Expr[Int]) = ???
+
+

+ + + Usos + + +

+ + +

Veremos que hay varias maneras de colgarse del proceso de compilador, por lo que tenemos distintos tipos de macros propuestos por scala, solo que en este caso nos estaremos enfocando en uno de los tipos de macros. Otra consideración a tener en cuenta es que la interfaz que tenemos de macros como la de reflection en scala puede ir variando en el tiempo, ya que son aún implementaciones experimentales y no se ha llegado a un estado final de como sería la implementación definitiva.

+ +

Las macros han sido utilizados durante la versión 2.10 de Scala, tanto para aplicaciones de investigación como industriales, y la conclusión según [1], es que las macros han sido útiles para aplicaciones tales como:

+ +
    +
  • Code Generation
  • +
  • Implementation of DSLs
  • +
  • Static checking among others
  • +
+

+ + + Tipos + + +

+ + +

Existen 2 tipos de macros, las llamadas “de caja blanca” o whitebox y las “de caja negra” o blackbox. La diferencia +entre los dos enfoques es que las macros de caja negra se usan cuando puedo definir claramente una firma para la función +que quiero implementar usando macros, mientras que las de caja blanca se usan cuando no puedo definir dicha firma. Las +macros de caja blanca son más flexibles pero menos seguras, ya que no pueden tiparse y van a ser discontinuadas en +versiones futuras de Scala, por esa razón vamos a concentrarnos en las definiciones de caja negra.

+ +

Para elegir uno de estos dos enfoques es necesario importar el paquete correspondiente

+ +
import scala.reflect.macros.whitebox
+
+ +

para las de caja blanca y

+ +
import scala.reflect.macros.blackbox
+
+ +

para las de caja negra.

+ +

La clase Context que se usó en el código anterior está definida en estos paquetes.

+ +

Mirando el ejemplo, se puede ver que hay una relación entre el tipo de la declaración de la macro y su implementación que, +además de recibir un parámetro Context, espera también un parámetro por cada parámetro de la declaración que debe tener +el mismo nombre y un tipo de expresión que coincida. creado a partir del contexto.

+

+ + + Un primer ejemplo + + +

+ + +

Empecemos por hacer una macros sencilla: la función identidad, que recibe un número y lo retorna:

+ +
def id(n: Int): Int = macro id_impl
+
+def id_impl(c: Context)(n: c.Expr[Int]): c.Expr[Int] = n
+
+ +

No fue tan difícil, no? Nuestra función id espera un Int y retorna un Int, por lo tanto, la macro con la que la +implementamos debe recibir (además del contexto) una expresión de tipo Int y retornar esa misma expresión. Es importante +notar que una expresión de tipo Int no es un Int, sino un fragmento de AST que, de ser evaluado, daría como resultado +un Int. Que sería el contexto en este caso y porque existe? Antes de eso vamos a explicar un poco algunos conceptos de lo que vimos recién. En el ejemplo que vimos el mismo se denominan def macros, y son métodos cuyas llamadas se expanden en tiempo de compilación, y con expansión se refiere en macros, a la transformación a código pero a nivel de compilación (no a al texto sintáctico ni al bytecode ejecutable, sino una representación intermedia de este) derivado del método al que se esta llamando con sus argumentos. El contexto se refiere al mismo en el cual se expone el código que será expandido y las rutinas que definimos que manipulan el código que deseemos transformar.

+ +

Otra parte del contexto de la macro es la funcion macroApplication, que nos permite proveer acceso al árbol de la expansión de la macro, y a pesar de que este arbol puede ser encontrado en argumentos de la implementación de la macro y en el método prefix, macroApplication nos permite dar un panorama más completo del contexto de la macro.

+ +

Veamos un ejemplo un poco más completo, por ejemplo implementemos un assert con macros…

+ +
  def assert(contidion: Boolean, msg: String): Unit = macro assert_impl
+
+  def assert_impl(c: Context)(contidion: c.Expr[Boolean], msg: c.Expr[String]) = {
+    import c.universe._
+  
+    val q"assert ($condition, $msg)" = c.macroApplication
+    q"if (!$condition) raise($msg)"
+  }
+
+ +

El ejemplo tiene un par de cosas nuevas, por un lado vemos un q seguido de un string con signos de $ refiriendose a variables, para empezar q es básicamente un método que nos permite, mediante un string interpolator al cual podemos referinos a parámetros o valores dentro de la implementación de la macro, crear y hacer pattern matching código que podemos generar o transformar. En este caso lo que se esta haciendo es pattern matchear todo el contexto que recibimos del assert a dos variables y luego genera un código condicional, en otras palabras si llamamos a

+ +
assert("1 == 1", "Uno no es igual a uno")
+
+ +

este código se reemplaza en tiempo de compilación cuando la macro se invoke y realice la expansión a

+ +
if(! 1 == 1) raise("Uno no es igual a uno")
+
+ +

en la próxima sección veremos un poco más de lo que es este método q que permite que podamos crear estructuras mediante string interpolation.

+

+ + + Quasiquotes + + +

+ + +

Implementar macros más complejas implica manipular el AST que conforma las expresiones. Esto es un trabajo muy pesado, +ya que requiere entender qué tipo de nodo se obtiene de cada posible expresión y como puede combinarse y deconstruirse +en base a otros.

+ +

Por suerte, las últimas versiones del framework de macros de Scala incluyen una herramienta muy poderosa para convertir +código a expresiones u obtener elementos a partir de un AST: Los Quasiquotes.

+ +

Quasiquotes son un tipo de StringContext que puede ser usado para aplicar y desaplicar valores desde/hacia un AST y +constituyen una interfaz relativamente accesible para manipular las expresiones.

+ +

El siguiente ejemplo usa quasiquotes para definir una macro que recibe una expresion por parámetro y loguea por consola +un aviso de que sentencia se va a ejecutar antes de evaluarla.

+ +
def debug(code: => Unit): Unit = macro debug_impl
+
+def debug_impl(c: Context)(code: c.Tree) = {
+	import c.universe._
+
+	val q"..$sentences" = code
+
+	val loggedSentences = (sentences :\ List[c.Tree]()){
+		case (sentence, acum) =>
+			val msg = "Se va a ejecutar: " + showCode(sentence)
+			val printSentence = q"println($msg)"
+
+			printSentence :: sentence :: acum
+	}
+
+	q"..$loggedSentences"
+}
+
+//uso
+
+val x = 10
+debug {
+  val x = 10         // Se va a ejecutar: val x = 10
+  val y = 15         // Se va a ejecutar: val y = 15
+  val z = x + y      // Se va a ejecutar: val z = x + y
+}
+
+
+

+ + + Validaciones y manejo de errores + + +

+ + +

El contexto de las macros permite realizar chequeos de tipos, validaciones sobre las expresiones e informar al compilador +de la necesidad de lanzar errores o warnings. Esto es un recurso excelente para validar construcciones estáticas en tiempo +de compilación.

+ +

El siguiente ejemplo retoma la idea de los mails con una macro que recibe un String y retorna un Email, pero falla si el +string no tiene el formato correcto. Este feedback puede verse en el mismo IDE, ya que se controla en tiempo de +compilación!

+ +
case class Email(id: String, domain: String)
+
+def email(str: String): Email = macro email_impl
+def email_impl(c: Context)(str: c.Expr[String]) = {
+	import c.universe._
+
+	val emailFormat = """(\w{4,})@([\w\.]+.com)""".r
+	
+	str match {
+		case Expr(Literal(Constant(emailFormat(id, domain)))) =>	q"""Email($id,$domain)"""
+		case _ => c.abort(c.enclosingPosition, "Formato inválido!!!")
+	}
+}
+
+//uso
+email("lalala@gmail.com") // Retorna Email("lalala", "gmail.com")
+email("lalalagmail.com") // Error de compilación! Formato inválido!
+
+

+ + + Poniendo todo junto + + +

+ + +

Podemos dar un último paso combinando un poco de todo lo que vimos. En el siguiente ejemplo se muestra cómo puede +extenderse StringContext mediante implicits para crear mails que son validados en tiempo de compilación. Si bien hay que +poner un poco de esfuerzo extra para adecuar las firmas y los usos, este es un gran ejemplo de lo poderosas que pueden +ser estas herramientas cuando se usan juntas.

+ +
case class Email(id: String, domain: String)
+
+implicit class EmailStringContext(strCtx: StringContext) {
+	def email(arguments: Any*): Email = macro email_impl
+}
+
+def email_impl(c: Context)(arguments: c.Expr[Any]*) = {
+	import c.universe._
+
+	val emailFormat = """(\w{4,})@([\w\.]+.com)""".r
+	
+	c.prefix.tree match {
+  		case Apply(_, List(Apply(_, rawParts))) =>
+		  	val parts = rawParts map { case Literal(Constant(const: String)) => const }
+		  	val args = arguments map { case Literal(Constant(const: String)) => const }
+		  	val mail = ("" /: parts.zipAll(args, "", "")) {	case (acum, (part, arg)) => acum + part + arg	}
+		
+		  	mail match {
+		  		case emailFormat(id,domain) => q"Email($id,$domain)"
+		  		case _ => c.abort(c.enclosingPosition, "Invalid mail!!!")
+		  	}
+		case _ => c.abort(c.enclosingPosition, "Invalid mail!!!")
+	}
+}
+
+ +

Noten que no podemos pasarle a la macro el parámetro extra que el StringContext necesita, así que tenemos que obtenerlo +en base al contexto.

+

+ + + Referencias + + +

+ + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_15/index.html b/scripts/clase_15/index.html new file mode 100644 index 0000000..0edd663 --- /dev/null +++ b/scripts/clase_15/index.html @@ -0,0 +1,1629 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase Bonus TADP 1C2018 +
+
+
+
+
+ + +
+
+
+

+ + + Objeto-Funcional en otros Lenguajes + + +

+ + +

La idea de esta clase es plantear cómo se están encarando los enfoques que presentamos en clase en otras técnologías y ver de paso algunas herramientas y nociones nuevas que giran alrededor de problemas similares. En particular vamos a presentar 2 lenguajes que son, a nuestro entender, los exponentes más interesantes de esta segunda generación de tecnologías Objeto-Funcionales: Kotlin, que tomó muchas ideas de Scala y las acomodó al mundo de Android y TypeScript que es hoy por hoy una de las mejores versiones tipadas de EcmaScript (el contrato sobre el que se definen los JavaScripts).

+ +

Estos lenguajes van a ser importantes para nosotros no sólo por las buenas ideas a las que llegaron, sino también por sus imperfecciones, los problemas a los que están sujetos, las limitaciones que sus diferentes contextos les imponen y la manera (mala o buena) como decidieron sobrellevarlas. Va a ser interesante también analizar que herramientas copiaron (aunque sea como un indicador de popularidad de ciertos conceptos) y el impacto de algunas aproximaciones innovadoras que presentan a problemas viejos.

+ +

En definitiva, estos lenguajes nos importan porque no son más de lo mismo, sino que tratan (en mayor o menor medida) de darle otra vuelta de rosca a la integración de paradigmas.


+

+ + + Tabla de Contenido + + +

+ + +
+

+ + + Tipado + + +

+ + +

Vamos a empezar planteando algunas variantes interesantes a los sistemas de tipos de los lenguajes que usamos durante la cursada. El tipado de Scala es de los más seguros, flexibles y, por lo tanto, complejos de los lenguajes orientados a objetos. Kotlin toma muchas de sus ideas y define un tipado algo más rígido y menos preciso pero mucho más simple, al mismo tiempo que agrega bastante boilerplate a su sintáxis para protejerse de (lo que algunos consideran) problemas comunes. Por otro lado, TypeScript abiertamente acepta su tipado como unsound y no ofrece una solución para las situaciones más complejas que otros lenguajes tratan de resolver pagando el costo de una mayor complejidad. Del sitio de TypeScript:

+ +
+

TypeScript’s type system allows certain operations that can’t be known at compile-time to be safe. […] The places where TypeScript allows unsound behavior were carefully considered, and throughout this document we’ll explain where these happen and the motivating scenarios behind them.

+
+ +

Basicamente el lenguaje aspira a que su tipado sea una mejor alternativa que el no-tipado de EcmaScript y está más preocupado por ser accesible que seguro. Ironicamente, esta laxedad en los chequeos permite luego tipar algunas construcciones complejas que en otros lenguajes más estrictos no serían posibles y tendrían que hacerse usando reflection u otros mecanismos inseguros.

+ +

Uno puede estar a favor o en contra de las decisiones particulares de estas tecnologías, pero hay una idea interesante escondida detrás que merece consideración: Los lenguajes (al igual que los problemas que buscan resolver) están atados a un tiempo, un público y un contexto. A veces puede ser buena idea alejarse del paradigma o implementar un concepto de forma menos (o más) rigurosa en pos de mejorar el uso cotidiano.

+ +

Vamos a mencionar entonces algunos de los aspectos más interesantes (para bien o para mal) de estos sistemas de tipos.

+

+ + + Typescript: Tipandolo con pinzas + + +

+ + +

En sí, la filosofía de TypeScript consiste en ser una versión más segura de EcmaScript, manteniendose fiel a sus principios y sin introducir “features” que no puedan mapearse directamente al lenguaje original. +Esto implica no requerir un cambio muy abrupto en la forma de programar y preservar la naturaleza “flexible” de ES, lo cual no es fácil…

+ +

Para esto, TypeScript basa sus chequeos en un Tipado Estructural:

+ +
let x: { a: number, b: string }
+
+// Esto funciona
+x = { a: 5, b: "foo" }
+
+// Pero esto no...
+x = { a: 7 }
+
+ +

Un detalle interesante es que, una vez que el lenguaje acepta que su tipado no es siempre seguro, se puede permitir hacer algunos chequeos que enfoques más estrictos descartarían por no ser consistentes:

+ +
let x: { a: number, b: string }
+
+// Esto, como es de esperarse, tipa.
+let y = { a: 7, b: "bar", c: true }
+x = y
+
+// Pero esto no!
+// TS asume que si harcodeas un literal acá, poner el "c" es probablemente un error.
+x = { a: 7, b: "bar", c: true }
+
+// Incluso se puede optar por incluir o excluir ciertas validaciones.
+x = null
+
+ +

Otra noción “impura”, del lenguajes es su aproximación al Tipado Nominal. Es posible, además de tipos estructurales definir Interfaces de forma muy similar a otros lenguajes, con la diferencia de que las estructuras no necesitan declarar que las implementan de forma explícita. Esto quiere decir que uno no puede confiar en que el objeto fué pensado para cubrir un rol (y no sólo expone la interfaz por casualidad).

+ +
interface T { a: number }
+
+let t: T
+
+// Esto, obviamente, tipa
+class C implements T {
+    a: number
+}
+t = new C()
+
+// Pero esto también!
+t = { a: 5 }
+
+ +

La consecuencia directa es que el aspecto nominal del tipado aporta más expresividad que otra cosa porque, en definitiva, los tipos son escencialmente estructurales. Esto no es ni bueno ni malo, aunque si tiene algunas limitaciones… Cuando alguien declara que quiere pertenecer al tipo T el compilador puede validar que implemente la estructura necesaria, pero cuando alguien espera recibir un parámetro de tipo T no hay manera de validar que no sea otra cosa con estructura similar. Por otro lado, esto permite que un objeto capaz de cumplir con un tipo no sea rechazado sólo porque no lo declara explicitamente.

+ +

¿Entonces, cuál es la moraleja? ¿Nos gusta o no nos gusta este “tipado laxo”? Y… Es distinto. Obviamente tenés menos garantías de que un programa que tipa funcione pero, una vez aceptado esto el lenguaje se puede permitir crecer más rápido o implementar conceptos más atrevidos.

+ +

Un ejemplo de esto es la Conjunción y Disjunción de Tipos que en Scala llevan varios años discutiendo y TypeScript implementa sin ningún tipo de reparo.

+ +
interface Alumno {
+  nombre: string
+}
+
+interface Docente {
+  legajo: string
+}
+
+let persona: Alumno | Docente  // Cualquiera de los dos!
+let ayudante: Alumno & Docente // Los dos al mismo tiempo!
+
+ +

Incluso nociones más controversiales, como mezclar valores en la definición de un tipo, no mueve mucho la vara:

+ +
interface Alumno {
+    nombre: string
+    nota: 2 | 6 | 8 | 9 | 10
+    condicion: "ingresante" | "regular" | "irregular"
+}
+
+

+ + + Tipos Paramétricos y Varianza + + +

+ + +

Los típos parámetricos (o, como algunos lenguajes los llaman, Generics) consisten básicamente en permitir parametrizar la construcción de un tipo, agregando información que puede ser usada por el chequeador para resolver situaciones complejas, donde la interfaz de un objeto depende de factores externos. Si bien la idea general es bastante sencilla, no todos los lenguajes utilizan estas herramientas del mismo modo. En clase cubrimos (casi todo) el uso que el sistema de tipos de Scala hace de estos parámetros y la manera en que decide cómo se relacionan los tipos en función a como se relacionan sus parámetros (Varianza), pero sería un error pensar que todos los lenguajes llegan así de lejos para mantener la consistencia de sus tipos. Sin ir más lejos Java, el punto de referencia para muchos lenguajes modernos, no maneja varianza de tipos sino que se conforma con cubrir a medias esas situaciones usando un mecanismo de wildcards.

+ +

Kotlin, pese a haber tomado gran parte de sus abstracciones de Scala y apuntarlas a usarios de Java, decidió que no le gustaba ni un enfoque ni el otro. Del sitio de Kotlin:

+ +
+

One of the most tricky parts of Java’s type system is wildcard types (see Java Generics FAQ). And Kotlin doesn’t have any.

+
+ +

Me gusta el detalle de que llama a los wildcards “tricky”, y no “difíciles”, porque abre la puerta a una discusión interesante. Varianza, como tantos otros conceptos con un contenido teórico fuerte, es un tema bastante complejo. Para usarlo bien, es necesario aprender los fundamentos y leer a los autores que estudiaron el tema. En ocasiones los lenguajes industriales optan por no seguir el camino que marca la academia (a veces porque creen tener una propuesta mejor, a veces porque no les interesa tanto un tema y prefieren ahorrar complejidad e invertirla en otra cosa y a veces porque abiertamente reniegan de la teoría). En estos casos, los mismos problemas pueden resolverse a los ponchazos, con construcciones simplificadas y especializadas para un uso particular, lo cual, a la larga, puede terminar en una pila de herramientas heterogéneas que se solapan o no terminan de cubrir todos los casos de uso. Lo gracioso es que estas herramientas simplificadas muchas veces terminan teniendo tantos casos especiales que resulta más complicado aprenderlos todos que leer la teoría que tratan de evitar.

+ +

En fin… Kotlin, que no quería los parches de Java ni quiso pagar la complejidad de Scala apostó por un mecanismo de tipado similar pero más sencillo, que tomó prestado de .NET.

+ +

Este enfoque permite definir Covarianza y Contravarianza similar al -T y +T de Scala, pero usando las palabras clave in y out, respectivamente.

+ +
class Caja<out T> { }
+
+fun main(args: Array<String>) {
+  var a: Caja<Any> = Caja<String>() // Esto funciona
+  var b: Caja<String> = Caja<Any>() // Esto no
+}
+
+ +

Como nota sobre la expresividad, las palabras clave in y out son más claras respecto a las restricciones que imponen sobre dónde es posible usar cada tipo (in => parámetros, out => retorno), mientras que los símbolos + y - resultan cómodos a la hora de pensar cómo se combinan las varianzas (“menos por menos es más” se puede mapear fácilmente a “contravariante de contravariante es covariante”). Esto da lugar a pensar qué va a tener el desarrollador en la cabeza al momento de escribir el código y dónde conviene ayudarlo…

+ +
class X<in T> {}
+
+fun main(args: Array<String>) {
+    var fx : (X<Any>)    -> Unit = {_ -> }
+    var gx : (X<String>) -> Unit = {_ -> }
+
+    // Qué tiene que ver ser "in" con todo de esto???
+    gx = fx // Falla!
+    fx = gx // Funciona: Contravariante de Contravariante.
+}
+
+ +

Kotlin complementa su sistema de generics con una sintaxis para definir Upper Bounds (Pero no Lower Bounds):

+ +
class Corral<T : Animal> {} // T debe ser subtipo de Animal
+
+ +

También, a diferencia de Scala, Kotlin permite restringir la interfaz de un tipo con tipo paramétrico invariante para forzarlo a restringir su interfaz como si fuera covariante/contravariante. A esto lo denomina Proyección de Tipo:

+ +
// Si queremos tener getter y setter de contenido, T debe ser invariante.
+class Caja<T>(var contenido: T) {
+    fun copiar1(otro: Caja<T>){ this.contenido = otro.contenido }
+    fun copiar2(otro: Caja<out T>){ this.contenido = otro.contenido }
+}
+
+fun main(args: Array<String>) {
+    val a: Caja<Any> = Caja(7)
+    val b: Caja<Int> = Caja(5)
+
+    a.copiar1(b) // Falla porque a y b tienen distinto tipo
+    a.copiar2(b) // Pero si forzamos el parámetro covariante funciona!
+}
+
+ +

En el otro extremo del espectro, si bien puede configurarse para hacer algunos controles básicos, TypeScript decide evitarse el problema y hacer todos los generics Bivariantes:

+ +
class Animal { }
+class Vaca extends Animal { }
+class VacaLoca extends Vaca { }
+
+class Corral<T extends Animal> { f(t: T) { return t } }
+
+let a: Corral<Animal> = new Corral()
+let b: Corral<Vaca> = new Corral()
+let c: Corral<VacaLoca> = new Corral()
+
+b = a // Sep.
+b = c // No veo porqué no...
+
+ +

De más está decir que esto no es lo más seguro, pero TypeScript elige poner la responsabilidad de evitar esos problemas en el usuario a cambio de permitirle permanecer ignorante sobre teoría de varianza y mantener el tipado suficientemente sencillo para implementar…

+

+ + + Features locos + + +

+ + +

El razonamiento es simple: Desde el punto de vista del usuario, el sistema de tipos es confiable o no (no importa porqué). Si ya sé que tengo que estar atento cuando uso ciertas construcciones y lo acepto como parte del uso cotidiano del lenguaje, entonces es posible agregar herramientas interesantes aunque no pueda hacerlas tipar de forma completamente consistente. Vamos a mencionar un par de ejemplos de esto presentes en TypeScript.

+

+ + + Tipos Condicionales + + +

+ + +

En typescript es posible definir un tipo en función de un chequeo de tipos:

+ +
type Nodo = NodoNum | NodoStr
+interface NodoNum { valor: number }
+interface NodoStr { valor: string }
+
+function valor<N extends Nodo>(nodo: N): N extends NodoStr ? string :
+                                         N extends NodoNum ? number :
+                                         never {
+    return nodo.valor as any
+}
+
+const x = valor({ valor: "foo" })
+const y = valor({ valor: 95 })
+
+

+ + + Index Types + + +

+ + +

With index types, you can get the compiler to check code that uses dynamic property names.

+ +

TypeScript permite el mismo uso de propiedades dinámicas que EcmaScript. Esto incluye referenciar el nombre de propiedades con construcciones no-estáticas (Ej.: obj[“propiedad”] en lugar de obj.propiedad). Los Index Types son la construcción sintáctica que permite que el compilador analice código que usa nombres dinámicos de propiedades.

+ +
class Alumno {
+    nombre: string
+    legajo: number
+}
+
+let campo: keyof Alumno // Esto tiene tipo "nombre" | "legajo"
+let tipoDeCampoNombre: Alumno["nombre"] // Tiene tipo "string"
+let tipoDeCualquierCampo: Alumno[keyof Alumno] // Tiene tipo "string" | "number"
+
+ +

Parece Reflection, no? Pero hay que notar que no usamos ninguna expresión de runtime; esa información está en los tipos y, por lo tanto, es accesible en tiempo de compilación.

+ +

Este tipo de sintáxis desdibujan la linea que separa el código cotidiano de la metaprogramación y permite hacer esta última de forma (un poco) más segura. Tomemos por ejemplo esta función:

+ +
function dameElCampo<T, K extends keyof T>(obj: T, key:K): T[K] {
+  return obj[key]
+}
+
+let pirulo: Alumno
+
+// Funciona y se da cuenta de que tiene tipo "string"
+dameElCampo(pirulo, "nombre")
+
+// Falla! El alumno no tiene el campo pedido.
+dameElCampo(pirulo, "qué pirulo?")
+
+ +

El código de este ejemplo es básicamente un wrapper del acceso con corchetes que recibe dos tipos paramétricos: uno es el tipo del objeto del cual queremos el campo y el otro es un subtipo de las claves presentes en dicho objeto. Al momento de aplicar la función, el compilador va a inferir dicho tipo en base al parámetro que pasamos, permitiendole identificar también el tipo más especifico para retornar (en lugar de retornar "string" | "number") y permite detectar en compilación un error tradicionalmente de runtime.

+

+ + + Mapped Types + + +

+ + +

Otra construcción interesante son los Mapped Types que, básicamente, permiten definir un tipo a partir de los campos de otro:

+ +
type Opcional<T> = {
+  [K in keyof T]: T[K] | null
+}
+
+let tipoDeNombre: Opcional<Alumno>["nombre"] // Tipa a "string" | "null"
+
+// Falla! legajo es de tipo number y no puede ser null
+let jose: Alumno = { nombre: "jose", legajo: null }
+
+// Ahora sí...
+let joseOpcional: Opcional<Alumno> = { nombre: "jose", legajo: null }
+
+ +

En este ejemplo se define el tipo Opcional<T>, que expone todos los campos de T, pero cambiando su tipo por la unión entre este y null.

+ +

El uso de mapped types es sorpresivamente recurrente, al punto en que ya vienen varios predefinidos (Ej: Readonly, Partial, Pick, Record, etc.) e incluso la misma comunidad suele proponer cosas locas.

+ +

Dando un paso atrás, es bastante evidente porqué estas construcciones son más raras en lenguajes con un tipado más estricto. De hecho, en Scala sólo sería posible modelar una abstracción como mapped types usando whitebox macros (macros cuyos tipo no puede establecerse en tiempo de compilación y que muchos proponen discontinuar justamente porque al tipador no le gustan) y aun así es difícil…

+ +

Combinando estas herramientas con los otros operadores de tipos es posible construir operadores muy avanzados:

+ +
// Miembros comunes a T y U.
+type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T]
+
+// Miembros de T que no están presentes en U.
+type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] }
+
+// Miembros de T y U donde las definiciones de U prevalecen.
+type Overwrite<T, U> = { [P in Diff<keyof T, keyof U>]: T[P] } & U
+
+

Acá hay un artículo lindo que explica un poco esto.

+

+ + + Antes y Después del Compilador + + +

+ + +

Tanto Kotlin como TypeScript permiten algún grado de Inferencia de Tipos. En general, la gran mayoría de los lenguajes con tipado explicito (incluso C++!) tratan de incorporar esto a sus sintáxis, para reducir el boilerplate y hacer más fácil la transición desde tecnologías más dinámicas. Es probable que lo único que previene que esto se convierta en el standard de la industria es que no terminamos de ponernos de acuerdo en si el tipo escrito mejora o empeora la lectura del código.

+ +

Sobre esto Kotlin es bastante opinionado: fuerza al usuario a explicitar algunos tipos que infiere, porque considera que el código resultante es más claro.

+ +
class X {
+  fun m() = "foo" // Obviamente retorna un String.
+  fun n() {
+    return "bar"  // Hey! Hey! Despacio cerebrito!
+  }
+}
+
+ +

De acuerdo o no, es interesante pensar que estás restricciones que parecen de menor importancia en la sintáxis de un lenguaje pueden empujar a la comunidad a adoptar ciertas prácticas (en este caso, usar más métodos definidos como expresiones y menos bloques largos).

+ +

Otro patrón recurrente, un poco menos feliz, es que ambos lenguajes también decidieron descartar la información de tipos en runtime. Kotlin, que originalmente se compilaba para la JVM, pasa por el mismo proceso de Erasure que Scala, donde los tipos paramétricos se descartan post compilación.

+ +

TypeScript va un paso más lejos y elimina toda información sobre los tipos, compuestos o no, para compilar al código ES más similar posible. Esto hace imposible usar esta información para hacer introspection en runtime…

+ +

Si bien existen lenguajes donde los tipos compuestos están reificados a nivel plataforma (como .NET) y otros (como Scala) que encontraron alguna forma de dibujarla, en general la postura suele ser que eliminar esta información es lo más rápido y fácil de hacer y evita que los programas “engorden” guardando metadata que no siempre necesitan.

+ +

De ahí que existan cosas como el Projecto Valhalla, que aspira a agregar soporte para este y otros features a la JVM, lo cual podría cambiar radicalmente el modo como otros lenguajes manejan los generics.

+

+ + + Definición de Objetos + + +

+ + +

Mencionamos en clase varias veces que el paradigma de objetos todavía no se decide sobre cuál es la mejor manera de definir, instanciar y asociar comportamiento a los objetos. Esto lleva a que la gran mayoría de los lenguajes prueben sus propias variantes de herramientas y metamodelos; con lo cual, si bien existen algunos patrones comunes, ningún enfoque superador se impuso todavía.

+

+ + + Kotlin: Entre la Scala y la pared + + +

+ + +

En muchos sentidos, Kotlin toma sobre estos temas un enfoque muy conservador, adoptando la Herencia Simple con Default Methods (interfaces con código) de Java 8 como su mecanismo principal de subtipado.

+ +
interface Humano {
+    fun cantar() = "Lalalala"
+}
+
+interface Caballo {
+    fun relinchar() = "Ihihihihi"
+}
+
+class Centauro : Humano, Caballo { }
+
+ +

Para el ojo poco entrenado de un tipo optimista, estas interfaces pueden parecer Mixins al estilo de Scala, pero cualquier alumno de TAdP sabe que hay que hacerse algunas preguntas antes de adelantar conclusiones:

+ +
    +
  • ¿Pueden definir estado? +No. Las properties de las interfaces son abstractas. +
    interface I {
    +  var x: String
    +}
    +
    +// Falla: Falta implementar x!
    +class C: I { }
    +
    +
  • +
  • ¿Los conflictos se resuelven automáticamente (por ejemplo, linearizando)? +Nop. Es necesario sobreescribir a mano cada método conflictivo. Las implementaciones heredadas están disponibles con una sintáxis especial. +
    interface I {
    +  fun m() = "foo"
    +}
    +interface J {
    +  fun m() = "bar"
    +}
    +
    +interface IJ: I, J {
    +  override fun m(): String {
    +      return super<J>.m()
    +  }
    +}
    +
    +
  • +
  • ¿El super es dinámico? +A efectos prácticos, no. Justamente porque las interfaces no se linearizan no puedo hacer un llamado a super que no refiera a un método concreto. +
    interface I {
    +  fun m(): String
    +}
    +
    +interface J: I {
    +  // Falla: m es abstracto.
    +  override fun m() = super<I>.m()
    +}
    +
    +
  • +
+ +

Vemos entonces que estas abstacciones están mucho más cerca de los Traits de Ducasse que a los Mixins de Bracha.

+ +

Es cierto que esta no es la opción más flexible o limpia pero, en defensa(?) de Kotlin, no es descabellado que un lenguaje que aspira a ser el sucesor de Java decida no introducir cambios abruptos en las herramientas de diseño Orientado a Objetos en pos de pelear otras batallas. Dicho esto, sí, duele un poco considerar que el mismo Java intrudujo estas abstracciones tarde y medio como una solución de compromiso, porque introducir algo más disruptivo comprometería la retrocompatibilidad, con lo cual Kotlin se ve limitado por decisiones que se tomaron para la primer JVM, hace más de 20 años. ¿Triste? Puede, ser… Pero este tipo de condicionamiento no es nada nuevo.

+ +

En un tono más positivo, Kotlin también toma de Scala varias de sus ideas más originales sobre cómo obtener objetos.

+ +

Un ejemplo de esto es que se pueden definir Singleton Objects,tanto anónimos como globales, para cuando sólo se necesita una única instancia.

+ +
object EstoEsUnObjeto {
+    val estoTambién = object {}
+}
+
+ +

Otra gran decisión fue reemplazar la idea de constructor por la de parametros de clase (a los que llaman “constructores” pero shh…).

+ +
// Los parámetros por defecto cubren la mayor parte de los casos de sobrecarga.
+class X(val a: Int, var b: String = "") {
+    var c: String
+
+    // Cualquier efecto que no sea inicializar un campo va en el bloque init (en lugar de estar desperdigado en el cuerpo de la clase).
+    init {
+         c = if (b == "foo") "bar" else "baz"
+    }
+
+    // En caso de necesitar otra firma, puedo hacer esto (o usar un object como en Scala).
+    constructor(d: Boolean) : this(0, "")
+}
+
+fun main(args: Array<String>) {
+    // Noten que no existe el "new"!
+    val xa = X(5).a
+}
+
+ +

También incorpora el concepto de Companion Object para lidiar con el aspecto estático de las abstracciones, aunque va un paso más lejos y hace que tenga que declararse dentro de la clase misma:

+ +
class X {
+  companion object {
+      fun m() = 5
+  }
+}
+
+fun main(args: Array<String>) {
+  val n = X.m()
+}
+
+ +

Por último, Kotlin también reñiega de la ambiguedad entre atributos y accessors, descartandolos en favor de properties, sin embargo, su aproximación a las mismas es mucho más similar a la de C# que a la de Scala.

+ +
class X {
+  // Puedo sobreescribir los accessors refiriendome al atributo con una palabra clave.
+  var p: Int = 0
+    get() {
+        println("Me están leyendo el campo")
+        return field
+    }
+    set(value: Int) {
+        println("Me cambiaron el campo de ${field} a ${value}")
+        field = value
+    }
+
+  // También puedo sólo inventar properties calculadas
+  val f
+    get() = 7
+}
+
+fun main(args: Array<String>) {
+    val x = X()
+    x.p += x.f
+}
+
+

+ + + Typescript/ES: Ahora con… Clases? + + +

+ + +

Vayamos ahora al otro extremo del espectro: ¿Qué ideas locas e inovadoras sobre cómo modelar objetos se introdujeron ultimamente en los lenguajes más dinámicos?

+ +

Clases.

+ +

+ +

Eso. Desde su versión 6, EcmaScript incorpora una reificación del concepto de Clases con Herencia Simple y, obviamente, TypeScript traslada esto a su propio modelo.

+ +

Que giles, no? Y… No. En realidad, la cosa es un poco más compleja…

+ +

La incorporación de Clases en ES es un cambio casi puramente cosmético. La sintáxis nueva incorpora una serie de azucares sintácticos para reducir el boilerplate, pero lo cierto es que el metamodelo de ES basado en Prototipos soporta perfectamente “simular” un árbol de clases y, de hecho, lo vienen haciendo desde hace tiempo.

+ +

La extensión basicamente implica poder escribir el código así:

+ +
class Persona {
+    nombre: string
+
+    constructor(nombre) {
+        this.nombre = nombre
+    }
+
+    id() { return this.nombre }
+}
+
+class Alumno extends Persona {
+    legajo: number
+
+    constructor(nombre, legajo) {
+        super(nombre)
+        this.legajo = legajo
+    }
+
+    id() { return this.legajo + "-" + super.id() }
+}
+
+let pirulo = new Alumno("pirulo", 148)
+
+ +

en lugar de así:

+ +
function Persona(nombre) {
+    this.nombre = nombre
+}
+Persona.prototype.id = function() { return this.nombre }
+
+function Alumno(nombre, legajo) {
+    Persona.call(this, nombre)
+    this.legajo = legajo
+}
+Alumno.prototype.prototype = Persona
+Alumno.prototype.id = function() {
+  return this.legajo + "-" + this.prototype.prototype.id.call(this)
+}
+
+let pirulo = new Alumno("pirulo", 148)
+
+ +

Una observación interesante sobre este último código es que ES trabaja con el concepto de Constructor Functions (tal vez un ancestro conceptual de los Parámetros de Clase) donde el new simplemente crea un nuevo objeto para cumplir el rol the this en una función cualquiera, que lo configura a criterio.

+ +

De forma similar podemos también modelar Mixines como se explica en este excelente artículo:

+ +
// Los mixines pueden modelarse como funciones!
+let M1 = (next) => class extends next {
+  m() { return super.m() * 2 }
+}
+
+let M2 = (next) => class extends next {
+    m() { return super.m() + 10 }
+}
+
+class S {
+    m() { return 5 }
+}
+
+// Al definir la clase, linearizamos de afuera hacia adentro.
+class C extends M1(M2(S)) {
+    m() { return super.m() + 1 }
+}
+
+new C().m() // 31
+
+// Incluso podemos instanciar los mixines!
+new (M1(M2(S)))().m() // 30
+
+ +

No se dejen engañar por la falta de tipos y multithreading, ES tiene desde hace años uno de los metamodelos más simples, poderosos y flexibles de la industria. Sí, su sintáxis tiene varias limitaciones y algunas decisiones del modelo base dejan bastante que desear, pero la mayor parte de estos inconvenientes son superficiales y facilmente vadeables, al punto que hoy en día existen cientos de lenguajes que trasladan su sintáxis a lo que ES tiene abajo del capot (incluyendo a Kotlin).

+

+ + + Inmutabilidad y Efecto + + +

+ + +

Trabajar sin efecto es una de las facetas de la programación funcional más abrazada por los lenguajes nuevos. Lamentablemente, muchas tecnologías se concentran en detalles que solamente rascan la superficie de la idea, o descartan la posibilidad de producir efectos por completo (y con ella, una parte escencial de la programación Orientada a Objetos). Vamos a marcar entonces algunas de las abstracciones más interesantes que los lenguajes modernos encontraron en su busqueda de un mejor control del efecto.

+

+ + + Constantes + + +

+ + +

Casi todos los lenguajes modernos (y muchos de los clásicos) tienen abstracciones que identifican a una referencia como constante (o final). Básicamente esto significa que la referencia en cuestión puede ser asignada sólo una vez, durante su inicialización, pero no puede ser reasignada luego.

+ +

El empuje que el enfoque funcional tuvo en los últimos años propició un cambio en el rol que estas referencias tienen en el código, pasando de ser usadas por muchos casi exclusivamente para evitar repetir el hardcodeo de valores bien conocidos a ser la manera estandard de separar resultados parciales en el código. Además, el soporte para properties inmutables a nivel sintáxis facilitó la popularización del trabajo sin efecto en la POO y abrió las puertas a nuevas preguntas.

+ +

Uno de estos planteos pasa por integrar la inmutabilidad al ciclo de vida de los objetos. Muchos lenguajes donde la sintáxis fuerza la separación entre la inicialización de un objeto y la definición de sus variables debieron adecuar su definición de constante para permitir que sean inicializadas en su definición o durante la inicialización del objeto.

+ +

Kotlin:

+
class Persona(edad: Int) {
+    val esAdulto: Boolean
+
+    init {
+      require(edad in 0..100){ "mala edad" }
+      esAdulto = edad > 18
+    }
+}
+
+ +

Typescript:

+
class Persona {
+    readonly edad: number
+    readonly esAdulto: boolean
+
+    constructor(edad: number) {
+        if (edad < 0 || edad > 100) throw new Error("mala edad")
+
+        this.edad = edad
+
+        // Estos chequeos de compilación no son triviales...
+        this.edad = edad
+
+        // Typescript usa const para las variables y el modificador de tipo readonly para las properties
+        const esAdulto = edad > 18
+        this.esAdulto = esAdulto
+    }
+}
+
+ +

Lo interesante es que, una vez abierta la puerta a la discusión de que tal vez hay más de una forma de variable, rapidamente aparecen propuestas nuevas. Kotlin, que fue originalmente pensado como un lenguaje para Android donde el espacio de almacenamiento tiene una importancia especial, define una sintáxis para un tipo de constantes estáticas que son embebidas en el lugar donde se referencian en tiempo de compilación (permitiendo potencialmente que la clase donde están definidas sea removida por el optimizador).

+ +
// Los const son embebidos en compilación y sólo pueden ser de tipos primitivos o strings.
+const val FOO = "BAR"
+
+// Estas dos funciones se compilan a lo mismo
+fun f() = FOO
+fun g() = "BAR"
+
+ +

También incorpora una sintáxis que permite postergar la inicialización de variables sin comprometer su tipo.

+ +
class Docente
+
+class Curso {
+    // Las referencias (vars o vals) deben ser inicializadas siempre...
+    var docente: Docente
+
+    // ...salvo los var marcados como lateinit, que pueden inicializarse después.
+    lateinit var jtp: Docente
+}
+
+fun main(args: Array<String>){
+    // Si un lateinit no se inicializa antes de leerse, rompe en runtime.
+    println(Curso().jtp)
+}
+
+ +

TypeScript también viene con abstracciones interesantes para las constantes, de la mano de los Mapped Types.

+ +
type Readonly<T> = {
+    readonly [P in keyof T]: T[P];
+}
+
+type DeepReadonly<T> = {
+    readonly [P in keyof T]: DeepReadonly<T[P]>;
+}
+
+class Docente {
+    nombre: string
+}
+
+class Curso {
+    docente: Docente
+    jtp: Docente
+
+    tieneDocente() { return this.docente != null }
+}
+
+// Como readonly es sólo un modificador de tipo no necesito cambiar código
+// puedo usar un curso cualquiera y sólo lo "veo" distinto.
+let curso: Curso
+let cursoRO: Readonly<Curso> = curso
+let cursoDRO: DeepReadonly<Curso> = curso
+
+
+curso.docente = new Docente()    // Que mal! Si retorno mi curso pueden cambiarmelo.
+cursoRO.docente = new Docente()  // Que bien! Puedo hacer sus campos readonly!
+cursoRO.docente.nombre = "Toto"  // Que mal! Sigue siendo mutable...
+cursoDRO.docente.nombre = "Toto" // Que bien! Cascadeo el readonly!
+cursoDRO.tieneDocente() // Pero el DeepReadonly no tiene la operación para evaluarse... Que mal!
+
+ +

Todo esto está muy bien pero, como se trató en clase, trabajar sin efecto requiere más que solamente tener variables que no pueden ser reasignadas, es necesario también contar con buenas herramientas para transformar estructuras, definir interfaces que no requieran de mantener un estado y provean alternativas limpias al lanzado de excepciones como mecanismo de control de flujo.

+

+ + + Expresiones Vs. Sentencias + + +

+ + +

Podemos pensar en las Expresiones como Sentencias que retornan un Valor, en contraposición a aquellas que sólo producen un efecto. Los lenguajes con fundamentos funcionales (como Kotlin) hacen hincapié en que todas (o al menos la mayoría) de sus sentencias son expresiones. Esto permite escribir cualquier sentencia donde se espera un valor, favoreciendo un estilo de escritura menos procedural.

+ +

TypeScript no tiene tanta suerte, ya que varias de sus construcciones y Clausulas de Control de Flujo no son expresiones:

+ +
let condicion: boolean
+
+// El if no retorna un valor, así que no puedo asignarlo.
+const n1 = if (condicion) 1; else 2;
+
+// Esto nos fuerza a separar la definición de la inicialización
+// y nos impide usar const...
+let n2
+if (condicion)
+    n2 = 1
+else
+    n2 = 2
+
+// Existe un operador ternario (sólo para el if) que SÍ es una expresión.
+const n3 = condicion ? 1 : 2
+
+// Sin embargo, sólo admite expresiones como parámetro.
+// El throw no es una expresión, así que NO puede usarse acá.
+const n4 = condicion ? 1 : throw "ufa"
+// Ni tampoco usar más de una sentencia.
+const n5 = condicion ? 1 : {
+    console.log("la condición fue falsa")
+    2
+}
+
+ +

Un truco recurrente para lidiar con esto es envolver las sentencias con Lambdas o Funciones (aunque la expresividad sufe un poco…):

+
let condicion: boolean
+
+// Noten que definimos si y no como funciones, para mantener la evaluación diferida.
+function ifThenElse(cond, si, no) {
+    if (cond) return si()
+    else return no()
+}
+
+const n1 = ifThenElse(condicion, () => 1, () => 2)
+
+const n2 = condicion ? 1 : (() => { throw "ufa" })()
+
+

+ + + Transformación de datos inmutables + + +

+ + +

Al trabajar sin efecto es habitual simular un cambio sobre una estructura creando una copia de la misma que cuente con la diferencia deseada. Lamentablemente, esto suele ser más fácil de decir que de hacer, ya que generar copias con cambios complejos y anidados tiende a ser una tarea incomoda y verbosa. +Como vimos en la cursada, Scala ofrece algunas facilidades superficiales para esto en sus Case Classes, las cuales cuentan con mecanismos para destructurarse y copiarse. Estos mecanismos, si bien útiles para cosas sencillas, carecen de una integración más profunda al metamodelo y no pueden en general ser heredados, extendidos o utilizados polimorficamente; lo cual hace que desde hace tiempo la comunidad los quiera mejorar.

+ +

Kotlin (un poco desperdiciando la oportunidad de plantear algo más interesante) tomó la idea de Case Class (renombrándola a Data Class) casi al pie de la letra, con la diferencia de que no basan sus mecanismos de deconstrucción en un contrato de mensajes como el de Scala, haciendolo un poco menos poderoso.

+ +
data class Alumno(val nombre: String, val nota: Int)
+
+fun main(args: Array<String>) {
+    val pepe = Alumno("Pepe", 7)
+    val (_, notaDePepe) = pepe
+    val pipo = pepe.copy(nombre = "pipo", nota = notaDePepe + 1)
+}
+
+ +

En contraste, no es raro que ES (y, por transición, TypeScript), que desde sus origines es utilizado de forma exhaustiva para consumir y manipular datos tontos, haya invertido mucho tiempo y esfuerzo a mejorar la sintaxis y herramientas con las que transforma estructuras.

+ +
interface Alumno {nombre: string, nota: number}
+interface Materia { nombre: string }
+interface Curso { materia: Materia, alumnos: Alumno[] }
+
+const unAlumno = { nombre: "pepe", nota: 7 }
+// Podemos definir variables siguiendo un patrón basado en la estructura.
+// A esto le llamamos destructurar un objeto.
+// En caso de que el campo no exista podemos darle un default.
+const {nombre, nota = 0} = unAlumno
+
+// Podemos extraer sólo los campos que nos interesan.
+const aprobo = ({ nota }: Alumno) => nota > 6
+
+// Podemos deconstruir multiples niveles (e incluso inferir el tipo estructural).
+const necesitaUnApodo = ({ nombre: { length } }) => length > 10
+
+// Podemos copiar un objeto "untandolo" en otra definición.
+// Noten que el campo alumnos es pisado... Este es nuestro copy.
+const desdoblar = (curso: Curso) => ({...curso, alumnos: []})
+
+function mejorNota(curso: Curso) {
+    // La destructuración no sólo funciona con hashes.
+    const [{ nota }, ...otros] =
+        // Podemos poner alias a las referencias obtenidas.
+        curso.alumnos.sort(({ nota: notaA }, { nota: notaB }) => notaA - notaB)
+
+    // Podemos insertar un campo que se llama igual que su variable sin usar el nombre.
+    return { curso, mejorNota: nota }
+}
+
+ +

Es importante notar cómo trabajar sobre estructuras mucho menos complejas y eficientes que otros lenguajes abre las puertas a una manipulación más dinámica y menos verbosa. También es interesante tomar esta interfaz como ejemplo de un lenguaje de propósito general evolucionando para adaptarse mejor al propósito específico para el que se lo usa. Si lo que vamos a hacer con ES es manipular JSON, porqué no darle las mejores herramientas posibles para eso (sacrificando otras cosas).

+ +

Por último, cabe mencionar que ninguno de los dos enfoques es especialmente bueno para realizar transformaciones anidadas o complejas, con lo que no es raro que existan librerías para ambos lenguajes que implementan Lenses (un patrón de diseño funcionaloso que apunta justamente a eso).

+ +

En esto, Typescript y sus Mapped Types son especialmente simpáticos ya que permiten tipar un contrato dinámico en lugar de tener que basarlo en strings como suele hacerse (aunque no pudimos encontrar una implementación que lo haga y así que tuvimos que hacer la nuestra…):

+ +
type Lens<T, U> = { [K in keyof U]: Lens<T, U[K]> } & { (t: T, u: U): T, (t: T): U }
+
+// Más adelante explicamos esta implementación. Por ahora no importa...
+function lens<T,U>(path:string[]): Lens<T, U> {
+    const f = ((t: T, u: U) => t) as Lens<T, U>
+
+    return new Proxy(f, {
+        get<K extends keyof U>(_, k: K) { return lens<T, U[K]>([k, ...path]) },
+        apply(_, self, args): T {
+            if (args[1]) {
+                const [k, ...ks] = path
+                const clone: T = { ...args[0] }
+                let current: any = clone
+                ks.reverse().forEach(k => {
+                    current[k] = { ...current[k] }
+                    current = current[k]
+                })
+                current[k] = args[1]
+                return clone
+
+            } else return path.reverse().reduce((e, k) => e[k], args[0]) as T
+        }
+    })
+}
+
+function $<T>() { return lens<T, T>([]) }
+
+
+const $curso = $<Curso>()
+// Noten que tipa bien y hasta funciona el autocompletar!
+const $nombreDeCurso = $curso.materia.nombre
+
+const unCurso: Curso = { materia: { nombre: "tadp"} }
+const otroCurso = $nombreDeCurso(unCurso, "taDEp")
+
+console.log($nombreDeCurso(unCurso))
+
+

+ + + Funciones como elementos de primer órden + + +

+ + +

Como ya deslizamos antes, ambos lenguajes permiten definir tanto Closures (Bloques, Lambdas, etc.) como Funciones Nombradas independientes a cualquier objeto. Esto abre las puertas para trabajar al estilo de funcional (separando los datos de la lógica) sin necesidad de boilerplate o estructuras auxiliares.

+ +

Kotlin

+
fun main(args: Array<String>) {
+// Las llaves son parte de la lambda...
+val siguiente: (Int)->Int = { x -> x + 1 }
+
+//...esto hace que la sintaxis tenga que manejar casos especiales
+//para evitar parentesis redundantes.
+(1..10).map{x -> x + 1}
+
+//Una lambda que espera un único parámetro puede referirlo con la palabra clave "it".
+val doble: (Int)->Int = { it * it }
+}
+
+ +

Typescript

+
// Puedo definir una lambda básica usando sólo el (=>).
+const siguiente = n => n + 1
+
+// La deconstrucción de parámetros funciona igual que en otras funciones.
+const nota = ({nota}) => nota
+
+// Esta sintáxis hace fácil currificar una función para aplicarla parcialmente.
+const sumar = x => y => x + y
+const sumatoria = ns => ns.map(sumar(1))
+
+// Pero esto no tiene soporte a nivel lenguaje como en Haskell... La firma cambia.
+const f: (x:number, y:number) => number = sumar // Falla!
+
+ +

Kotlin, habiendo sido pensado originalmente para correr en Android, define también algunas herramientas para usar orden superior de forma más eficiente.

+ +

Además de esto, en ambos lenguajes es posible referenciar métodos y properties definidos en objetos para utilizarlos como funciones (bindeadas o no):

+ +

Kotlin

+
class Alumno(val nota:Int) {
+    fun aprobo() = nota > 6
+}
+
+fun main(args: Array<String>) {
+	val jose = Alumno(7)
+	val tito = Alumno(2)
+
+    val aprobo = Alumno::aprobo
+    val aproboJose = jose::aprobo
+
+    aproboJose() // true
+    aprobo(tito) // false
+    Alumno::nota.get(jose) // 7
+}
+
+ +

Typescript

+
class Alumno {
+    nota: number
+    constructor(nota) { this.nota = nota }
+    aprobo() { return this.nota > 6 }
+}
+
+const jose = new Alumno(7)
+const tito = new Alumno(2)
+
+const aproboJose = jose.aprobo
+const aprobo = Alumno.prototype.aprobo
+
+aproboJose()             // true
+aprobo.call(tito)        // false
+aprobo.bind(tito).call() // false
+
+ +

Como se puede ver, en Kotlin el código es bastante directo (gracias al operador ::) mientras que en Typescript se hace con construcciones más artesanales y asociadas a reflection.

+ +

Pese a estas facilidades, las funciones en estos lenguajes suelen quedar relegadas a usarse mayormente para parametrizar operaciones de orden superior, ya que curiosamente, al momento de escribir este documento ninguno de los dos lenguajes tiene soporte a nivel sintaxis para componer o combinar funciones, lo cual dificulta hacer construcciones complejas con ellas. Por otro lado, es cierto que en Kotlin esto puede modelarse fácilmente con una sintáxis muy fluida gracias a la Sobrecarga de Operadores y que TypeScript tiene librerías que cubren esto y algunas propuestas abiertas para incorporar piping y binding.

+ +

Finalmente, la discusión no estaría completa sin analizar el polimorfismo entre objetos y funciones. En este terreno el modelo de Kotlin, que define la aplicación basandola en una interfaz, es ampliamente mejor que el de TypeScript, donde, si bien las funciones pueden tener propiedades como los objetos, son construcciones completamente diferentes y es imposible aplicar un objeto.

+ +

Kotlin

+
fun f(n: Int) = n + 1
+
+object g {
+    operator fun invoke(n: Int): Int = n + 1
+}
+
+fun main(args: Array<String>) {
+    var aplicable: (Int) -> Int
+
+    aplicable = ::f
+    aplicable = g    // Esto no funciona... g no es una función como en Scala.
+
+    // Pero puedo hacerlo así.
+    aplicable = object : (Int) -> Int {
+    	override operator fun invoke(n: Int): Int = n + 1
+	}
+
+    aplicable(5)
+}
+
+

+ + + Pattern Matching y Control de Flujo + + +

+ + +

Pattern Matching es una construcción central del Paradigma Funcional pero, como tratamos en clase, favorece enfoques de diseño que abiertamente se contradicen con aquellos preferidos por el Paradigma Orientado a Objetos. Siendo ese el caso, no es de extrañar que varios lenguajes que pretenden soportar ambos paradigmas optan por ahorrarse la complejidad de integrar esta herramienta y proponen un desarrollo puramente basado en polimorfismo.

+ +

Si bien en la cátedra somos amigos del Pattern Matching como herramienta de diseño, hay que reconocer que, en las técnologías con Objetos (donde los datos son capaces de exponer su propio contenido), el matcheo de patrones sólo ofrece una ventaja sintáctica, dado que es posible obtener resultados similares con un switch statement. ¿Va a ser más feo? Sí. Pero tengan en cuenta que no es nada fácil integrar de forma consistente y robusta un mecanismo de PM a una sintáxis basada en POO. Ni siquiera Scala, probablemente el mejor exponente de programación hibrida Objeto-Funcional a la fecha, tiene una integración perfecta, ya que los patrones no están representados como Entidades de Primer Orden, lo cual impide utilizarlos de muchas formas interesantes (pasarlos por parámetro, retornarlos como resultado de una función, etc.).

+ +

Independientemente de si las tecnologías lo incorporan o no, es interesante analizar la discusión que instala: Hay muchas situaciones en donde puede resultar conveniente tomar una decisión basandose en la forma de una estructura y el envio de mensajes puede no ser la mejor herramienta para esto. Vamos entonces a analizar algunas variantes de herramientas que proveen los lenguajes modernos que, sin proveer un Pattern Matching completo, facilitan el analisis estructural para ciertas situaciones especificas.

+

+ + + Decisiones basadas en el tipo + + +

+ + +

Uno de los usos más comunes de Pattern Matching está asociado a distinguir el tipo (en runtime) de un objeto. Este es uno de los aspectos más difíciles de emular en los lenguajes OO puros (especialmente los estáticamente tipados) dado que, sin soporte del lenguaje, incluso si averiguamos el tipo del objeto a mano, todavía tenemos que convencer al compilador de que conocemos el contenido de la variable utilizando alguna forma de casteo. Tomemos por ejemplo el siguiente código Scala, hecho sin utilizar PM:

+ +

Scala

+
trait Animal
+
+class Lobo() extends Animal {
+    def aullar() = "Auuuuuu"
+}
+
+class Vaca() extends Animal {
+    def muji() = "Muuuuuuuu"
+}
+
+def haceRuido(animal: Animal): String = {
+    if(animal.isInstanceOf[Lobo]) {
+        // Independientemente de mi chequeo, animal debe ser casteado
+        // return animal.aullar() // Esto no funciona
+        return animal.asInstanceOf[Lobo].aullar()
+    }
+
+    if(animal.isInstanceOf[Vaca]) {
+    	return animal.asInstanceOf[Vaca].muji()
+    }
+
+    throw new Error("No hace ruido")
+}
+
+ +

Algunos lenguajes modernos que reconocen la utilidad de trabajar con polimorfismo ad-hoc pero no soportan Pattern Matching optaron por refinar sus chequeadores de tipos para ser más sensibles al contexto. En Kotlin esta variante se denomina Smart-Cast, mientras que en Typescript se conocen como Type-Guards.

+ +

Kotlin

+
interface Animal
+
+class Lobo(): Animal {
+    fun aullar() = "Auuuuuu"
+}
+
+class Vaca(): Animal {
+    fun muji() = "Muuuuuuuu"
+}
+
+fun haceRuido(animal: Animal): String {
+    // Los chequeos de is y !is son considerados por el compilador.
+    if(animal is Lobo) {
+        // El bloque del if entiende que animal referencia algo de tipo Lobo.
+        return animal.aullar()
+    }
+
+    // No sólo funciona con el if...
+    animal is Vaca && return animal.muji()
+
+    // Ninguno de esos mensajes puede enviarse fuera del if
+    //animal.muji()
+
+    throw Error("No hace ruido")
+}
+
+ +

TypeScript

+
class Lobo {
+    aullar() { return "Auuuuuu" }
+}
+
+class Vaca {
+    muji() { return "Muuuuuuuu" }
+}
+
+// La disjunción de tipos va a funcionar mejor que una interfaz Animal
+function haceRuido(animal: Lobo | Vaca) {
+    // Dentro del if sabe que es un lobo
+    if (animal instanceof Lobo) return animal.aullar()
+
+    // No hace falta chequear, si no es Lobo es Vaca...
+    return animal.muji()
+
+    // Tampoco es necesario lanzar error, sabe que no puede llegar.
+    // throw new Error("No hace ruido")
+}
+
+ +

En TypeScript el chequeo inteligente no está limitado al instanceof. Es posible definir nuestras propias Type Guards utilizando una sintaxis especial:

+ +
function esLobo(animal: any): animal is Lobo {
+    return !!(<Lobo>animal).aullar
+}
+
+function haceRuido(animal: Lobo | Vaca) {
+    if (esLobo(animal)) return animal.aullar()
+    return animal.muji()
+}
+
+ +

Y eso no es todo; varios casos de uso comunes ya vienen soportados incluyendo lo que TypeScript llama Discriminated Union:

+ +
class Lobo {
+    especie: "lobo"
+    aullar() { return "Auuuuuu" }
+}
+
+class Vaca {
+    especie: "vaca"
+    muji() { return "Muuuuuuuu" }
+}
+
+function haceRuido(animal: Lobo | Vaca): String {
+    switch (animal.especie) {
+        case "lobo": return animal.aullar()
+            // Si comentamos este caso (y usamos chequeo de null estricto)
+            // el compilador va a avisarnos que no cubrimos todos los casos.
+        case "vaca": return animal.muji()
+}
+    }
+
+

+ + + Control de flujo basado en valores + + +

+ + +

Kotlin lleva estas herramientas un paso más lejos desarrollando una construcción sintáctica que, sin ser del todo Pattern Matching (ya que lo considera demasiado complejo), permite analizar no solamente tipos, sino también consultar por valores específicos.

+ +
interface Animal
+
+class Lobo(): Animal {
+    fun aulla() = "Auuuuuu"
+}
+
+class Vaca(): Animal {
+    fun muji() = "Muuuuuuuu"
+}
+
+class Camelus(val jorobas: Int): Animal
+
+fun haceRuido(animal: Animal) =
+	when(animal) {
+        is Lobo -> animal.aulla()
+        is Vaca -> animal.muji()
+        Camelus(1) -> "Tengo 1 joroba, soy un Dromedario"
+        Camelus(2) -> "Tengo 2 jorobas, soy un Camello"
+        // Ojo, esto no me permite más que chequear valores específicos.
+        // No tiene todo el poder del Pattern Matching!
+        // Camelus(n) -> "Tengo ${n} jorobas, soy un Monstruo!" // Esto no anda!
+        is Camelus -> "Tengo ${animal.jorobas} jorobas, soy un Monstruo!"
+        else -> throw Error("No hace ruido")
+    }
+
+ +

El when puede usarse también sin parámetros, como una alternativa idiomática de otras construcciones (como el if-then-else):

+ +
fun haceRuido(animal: Animal) =
+	when {
+        animal is Lobo -> animal.aulla()
+        animal is Vaca -> animal.muji()
+        // El when sin parámetro admite cualquier expresión booleana como clave
+        animal is Camelus && animal.jorobas > 2 -> "Tengo ${animal.jorobas} jorobas, soy un Monstruo!"
+        else -> "Soy otra cosa."
+    }
+
+

+ + + Mónadas y Secuenciamiento + + +

+ + +

Durante la cursada aprendimos qué son las Mónadas, para qué sirven, y cómo algunas de ellas (como List, Option y Error) contribuyen a resolver ciertos problemas recurrentes en POO de manera más funcional. Entonces… ¿Alguno de estos lenguajes tiene Mónadas?

+ +

No.

+ +

Bueno, más o menos… Si vamos a ser estrictos, una mónada no es más que un contrato que se cumple para cierta estructura y permite una forma genérica de trabajar. Desde este punto de vista, es posible implementar todas nuestras mónadas favoritas sin necesidad de soporte del lenguaje (y de hecho, ambos lenguajes tienen librerías de mónadas creadas por la comunidad).

+ +

Sin embargo, lo cierto es que si los contratos principales del lenguaje no utilizan estas mónadas de forma homogénea, es probable que su uso se haga cuesta arriba y conlleve una gran cantidad de boilerplate. Por ejemplo, el find en TypeScript retorna null si ningún elemento cumple la condición. Por supuesto, puedo envolver cada llamada a find… Pero no es lo mismo. :/

+ +

Además, hay una ventaja obvia en una sintáxis que reconoce la importancia de estas nociones (un ejemplo claro son herramientas como el For-Comprehension de Scala). El razonamiento es simple: vas a usar mucho esto, porqué no hacerlo lo más fácil posible? Esto es tán así que, en la práctica, es a veces más habitual encontrarse con situaciones donde es posible aprovechar los azucares sintácticos (aun sin entender del todo la teoría de fondo) que el trabajo mónadico genérico.

+ +

Y acá es dónde conviene entonces hacer una pausa y reflexionar lo siguiente: las mónadas pueden resultarnos interesantes por dos razones, la forma genérica de trabajar sobre ellas y las ventajas sintácticas y usos que se paran sobre esto para resolver problemas comúnes; pero no son la única forma de resolver estos problemas. Es más, pueden haber situaciones tan recurrentes o importantes que sería deseable que el lenguaje las maneje de forma especial en lugar de limitarse a aplicarles el contrato monádico.

+ +

Un buen ejemplo de esto es la posibilidad de que un valor no exista. A estas alturas de la cursada deberíamos tener este escenario asociado a la mónada Optional o Maybe), pero lo cierto es que hay otra construcción en la mayoría de los lenguajes OO que cumple este rol: el infame null. Incluso Scala, donde las mónadas son la propuesta por defecto para manejar esto, hace cosas raras con sus tipos para darle soporte a este concepto. ¿Porqué? Bueno, por muy útil que sean los Options, no pueden ser usados para representar que una variable todavía no fue inicializada. Además, sin null, escribir código compatible con Java sería imposible.

+ +

Y acá es donde Kotlin hace algo genial a lo que llama Null Safety.

+ +

Básicamente, cualquier tipo T en Kotlin puede marcarse con un ? para indicar que la referencia apunta, o bien a un T o bien a null.

+ +
    // val curso: Curso = null // Falla! null no puede asignarse a Curso
+    val curso: Curso? = null
+
+ +

En principio esto podría parecer similar a una conjunción de tipos T || nullde TypeScript, pero no! En el sistema de tipos de Kotlin el T? (o Nullable Type) es supertipo de T. Esto tiene implicaciones enormes, siendo la más obvia y directa que cualquier tipo puede ser usado donde se espera su versión nullable.

+ +
    class Curso(val docente: Docente)
+
+    fun ojalaMeDenUnDocente(docente: Docente?) { }
+
+    val curso: Curso = ...
+    ojalaMeDenUnDocente(null) // Esto vale...
+    ojalaMeDenUnDocente(curso.docente) // Esto también! No es necesario envolver al docente con un Some
+
+ +

Hasta acá todo muy lindo, pero no serviría de mucho si el null fuera la misma construcción tonta que en otros lenguajes. La parte más interesante de estos tipos es que vienen con su propia sintáxis para trabajarlos:

+ +
    // val curso: Curso = null // Falla! null no puede asignarse a Curso
+    val curso: Curso? = null
+
+    // El uso más habitual del map en un Option es enviarle un mensaje al contenido.
+    // La sintaxis ?. sirve básicamente para esto.
+   	curso?.docente
+
+    // Este tipo de acceso puede encadenarse sin problemas.
+    // Dado que no estamos envolviendo los valores en otras estructuras, T? === T??.
+    // Esto quiere decir que ?. puede usarse como equivalente de map Y flatMap.
+    val noEsDr: Boolean? = curso?.docente?.titulo?.startsWith("Dr")?.not()
+
+    // Si quiero hacer algo más que mandar un mensaje al posible null puedo usar ?.let.
+    // let es un mensaje que entienden todos los nullables, equivalente a map y flatMap.
+    val esIncreible: Boolean? = curso?.let{ it.docente.nombre == "Marcelo" }
+
+    // El ?: (operador elvis) funciona como un getOrElse.
+    val esIncreiblePosta: Boolean = esIncreible ?: false
+
+    // En el peor de los casos, los nullables también son considerados en el Smart-Cast.
+    if(curso == null) throw Error("es null")
+    // Si llega acá sabe que no puede ser null.
+    curso.docente
+
+ +

Recordemos que el problema principal del null era que me obligaba a preguntar por él a cada paso. Esta nueva sintáxis para operar con nullables es tan buena como el map monádico (e incluso un poco menos verboso).

+

+ + + Metaprogramación + + +

+ + +

No vamos a profundizar demasiado en los frameworks de Metaprogramación de estos lenguajes porque, en general, son bastante sencillos gracias a sus metamodelos simples y porque no distan demasiado de las formas de trabajar de otras herramientas que cubrimos en clase. Esto es especialmente cierto para el framework de Kotlin, que es muy similar en capacidad y diseño al de Java, ofreciendo una interfaz concisa y limplia de Reflection pero no dando casi ningún soporte para Self Modification. Por otro lado, en Typescript, como en casi todos los lenguajes dinámicos, la linea que separa el uso habitual de la Metaprogramación es borrosa ya que las entidades tienden a cambiar y redefinirse constantemente.

+ +

Dicho eso, podemos mencionar dos construcciones muy interesantes asociadas a la Metaprogramación presentes en estos lenguajes que no cubrimos durante la cursada.

+

+ + + Metadata + + +

+ + +

Conocidas como Decorators en TypeScript, Pragmas en Smalltalk, Attributes en .NET, Annotations en Kotlin y demas lenguajes de la JVM y quién sabe cuantos nombres más, estas herramientas llevan varios años siendo el standard para incorporar Metadata en el código (si se le puede llamar “standard” a un concepto sobre el que no nos ponemos de acuerdo ni en cómo se llama).

+ +

A grandes rasgos, podemos pensar en las Annotations como etiquetas estáticas, generalmente parametrizables, con las que podemos marcar las abstracciones del lenguaje (properties, métodos, clases, etc.). Estas etiquetas puede ser luego consultadas a través de una API de Reflection y son comunmente usadas para definir contratos que no dependan de una interfaz de mensajes.

+ +

Kotlin

+
annotation class Groso
+
+class MiClase(
+  @Groso val unCampoGroso: Int,
+  @Groso val otroCampoGroso: String,
+  val esteNo: Boolean
+)
+
+fun main(args: Array<String>){
+    val atributosGrosos = MiClase::class.members
+      .filter{it.annotations.any{a -> a is Groso}}
+      .map{it.name}
+}
+
+ +

Typescript

+
class MiClase {
+  @Groso unCampoGroso: any
+  @Groso otroCampoGroso: any
+  esteNo: boolean
+}
+
+function Groso<T>(target: T, key: keyof T) {
+    if(!target['camposGrosos']) target['camposGrosos'] = []
+    target['camposGrosos'].push(key)
+}
+
+const camposGrosos = MiClase.prototype['camposGrosos']
+
+ +

Como se ve en los ejemplos, las Annotations de Kotlin mantienen el enfoque declarativo de Java, mientras que los Decorators de TypeScript son básicamente funciones destructivas que se ejecutan durante la definición. Esto los hace mucho más poderosos, permitiendo que cambien completamente la definición en la que los incluyo, pero también hace que sean más peligrosos y requiere entender exactamente qué hace cada Decorator que uso.

+

+ + + Extensiones de Interfaz + + +

+ + +

Durante la cursada analizamos construcciones como los Implicits de Scala que permiten extender la interfaz de un objeto con nuevos mensajes sin modificarlo e incluso algunas como el method_missing de Ruby que permite que un objeto responda a mensajes cuyos nombres desconozco en tiempo estático.

+ +

Estas operaciones sólo permiten agregar métodos, pero no sobreescribirlos. Esto es así en la mayoría de las tecnologías orientadas a objetos, porque suele implementarse como un hook cuando falla el method lookup (tratar de capturar cada envío de mensaje, falle o no, es en general muy costoso).

+ +

EcmaScript propone una variante interesante para la extensión de los objetos: los Proxies. Estas abstracciones no sólo permiten interceptar cualquier acceso o envío de mensaje (definido o no), sino que lo hacen de forma no-destructiva.

+ +

La mécanica es simple. Cuando quiero extender un objeto lo envuelvo con una instancia de Proxy, que recibe al objeto en cuestión y una configuración que le indica cómo manejar los accesos (el proxy redireccionará cualquier acceso al objeto interno, salvo que su configuración diga lo contrario).

+ +
const loritoTonto = {
+  decimeChau() { return 'wraaaaak' },
+  cantidadDePatas: 2
+}
+
+const loritoInteligente: any = new Proxy(loritoTonto, {
+  get(target, key) {
+    return (key.toString().startsWith('decime'))
+      ? () => key.toString().slice(6)
+      : target[key]
+  }
+})
+
+loritoInteligente.cantidadDePatas // retorna 2
+loritoInteligente.decimeHola() // retorna Hola
+loritoInteligente.decimeChau() // retorna Chau! Esto no es un method_missing!
+
+ +

Este enfoque permite capturar cualquier envío de mensaje, ya que no afecta el method lookup de todos los objetos, solo de los pocos proxiados (?). +Nota: Ya podemos volver a ver el código de Lenses.

+ +

Del otro lado del espectro, en lo referente a extensiones de interfaces, Kotlin mantiene posiciones extrañas. Por un lado se presenta muy restrictivo, debido a la desafortunada decisión de hacer todas las abstracciones final por defecto que impide, entre otras cosas, extender clases que no hayan sido explicitamente marcadas como open; mientras que, por el otro, define unas herramientas a las que llama Extensions, muy similares a las Implicit Classes de Scala, que permiten extender (pero no sobreescribir) cualquier clase, abierta o no, con métodos y properties.

+ +
class Inutil(val nombre: String)
+
+// No puedo extender la clase, porque no es open
+class MenosInutil(nombre: String) : Inutil(nombre) {
+    fun saludar() = "Hola, soy ${this.nombre}"
+}
+
+// Pero puedo "agregarle" el método...
+fun Inutil.saludar() = "Hola, soy ${this.nombre}... Creo."
+
+fun main(args: Array<String>) {
+	Inutil("Ezequiel").saludar()
+}
+
+

+ + + Auto-Delegación + + +

+ + +

Si vamos a ser justos, esto no tiene mucho que ver con Reflection, pero queremos mencionarlo en esta sección porque la única forma de hacer algo parecido en la mayoría de los lenguajes requeriría de Metaprogramación de algún tipo.

+ +

Básicamente, Kotlin identifica dos situaciones comunes cuya solución suele ser trivial, pero engorrosa y plagada de boilerplate y las resuelve implementando unas construcciones sintácticas que hacen toda la mágia por atrás.

+

+ + + Class Delegation + + +

+ + +

Cualquiera que haya implementado un Strategy sabe lo tedioso que puede resultar delegar una y otra vez mensajes en una estratégia. No es para nada infrecuente tener interfaces que consisten casi exclusivamente en reenviar mensajes al objeto correcto simplemente para ganar la flexibilidad de la composición. La Delegación de Clases permite hacer esto mismo sin requerir de ningún tipo de boilerplate.

+ +
interface Saludador {
+    fun saludar(): String
+}
+
+object Formal: Saludador {
+    override fun saludar() = "Tenga usted un gran día, mi buen señor."
+}
+
+object Informal: Saludador {
+    override fun saludar() = "Qui hace', pa?"
+}
+
+// Persona implementa Saludador a travez de su estratégia.
+data class Persona(var estrategia: Saludador): Saludador by estrategia
+
+fun main(args: Array<String>) {
+	val pepe = Persona(Informal)
+    // Persona responde a la interfaz de Saludador delegando en su estratégia.
+    pepe.saludar() // "Qui hace', pa?"
+
+    // Ojo! El bindeo no es completamente dinámico!
+    // Cambiar el atributo no cambia la delegación.
+    pepe.estrategia = Formal
+    pepe.saludar()  // "Qui hace', pa?"
+
+    // Sin embargo, eso no es un problema si trabajamos de forma inmutable.
+    val lordPepe = pepe.copy(Formal)
+    lordPepe.saludar() // "Tenga usted un gran día, mi buen señor."
+}
+
+

+ + + Property Delegation + + +

+ + +

De forma similar, es posible delegar la implementación de una property en otro objeto. Esta mecánica no se basa en extender ninguna interface, sino en un contrato estructural que sólo pide implementar los métodos getValue y setValue.

+ +
import kotlin.reflect.KProperty
+
+class MiClase() {
+    val propiedadPeresoza : String by Peresozo { "foo" }
+}
+
+class Peresozo<T>(val getter:(()->T)) {
+    var valor: T? = null
+
+    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
+        if(valor == null) { valor = getter() }
+        return valor!!
+    }
+}
+
+fun main(args: Array<String>) {
+    MiClase().propiedadPeresoza // Retorna "foo"
+}
+
+ +

Esto tiene muchos usos prácticos, varios de los cuales ya vienen predefinidos (Ej.: inicialización diferida, propiedades observables, estado compartido).

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_2/index.html b/scripts/clase_2/index.html new file mode 100644 index 0000000..53297d8 --- /dev/null +++ b/scripts/clase_2/index.html @@ -0,0 +1,680 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 2 TADP +
+
+
+
+
+ + +
+
+
+

+ + + Traits vs Mixins + + +

+ +

Flattening: + - Se define qué traits se van a agregar y cómo en un momento determinado. Luego de eso, para el que usa la clase todo funciona como si el comportamiento traido de los traits fuese propio de la clase. +Linearización: + - Se agregan lugares donde buscar para el method lookup. Hay un orden definido en el cual se recorren todos esos lugares por lo que no es ambiguo la implementación del método que se va a usar. +Resolución de conflictos: + - Automática: hay una manera predeterminada que sabe como resolver el conflicto. Por ej si incluis dos mixines en ruby, el segundo va a tener más precedencia que el primero. + - Manual: cuando hay conflictos, el programador explícitamente tiene que decidir como se resuelve.

+

+ + + Continuación ejercicio age of empires + + +

+ +

Sobre el ejercicio de la clase anterior agregamos la posibilidad de que los atacantes y defensores puedan “descansar” y los siguientes requerimientos.

+ +

El descanso de la guerra

+ +

Todas las unidades pueden descansar. Cuando un atacante descansa, tiene el efecto de duplicar su potencial ofensivo en su próximo ataque. +Cuando un defensor descansa, suma siempre 10 de energía.

+ +

Banzai!

+ +

La característica del kamikaze es que se comporta como un atacante y un defensor, y su potencial ofensivo es 250, pero luego de atacar, su energía queda en 0. +Como la unidad va a morir luego de atacar, puede descansar como atacante, pero no debe descansar como defensor.

+ +

Atacando de la mano

+ +

Queremos que los guerreros formen parte de un pelotón. Cuando un guerrero es atacado, el pelotón puede tomar alguna acción. Ciertos pelotones se retiran cuando alguna de sus unidades es lastimada. Otros pelotones hacen que sus unidades descansen cuando alguno de sus integrantes es lastimado. Solo hará descansar aquellos guerreros que no están descansados. Un guerrero está descansado cuando su energía es mayor a 40.

+

+ + + Implementación + + +

+ + +

Arranquemos por el Defensor, cuando un defensor descansa, suma 10 de energía.

+ +
module Defensor
+
+    def descansar
+        self.energia += 10
+    end
+
+end
+
+ +

Por otro lado, cuando le decimos “descansar” al atacante, este ataca con el doble de energía en su próxima pelea.

+ +
module Atacante
+
+  def atacar(un_defensor)
+    if self.potencial_ofensivo > un_defensor.potencial_defensivo
+      danio = self.potencial_ofensivo - un_defensor.potencial_defensivo
+      un_defensor.sufri_danio(danio)
+    end
+    self.descansado = false
+  end
+
+  def potencial_ofensivo
+    self.descansado ? @potencial_ofensivo * 2 : @potencial_ofensivo
+  end
+
+  def descansar
+    self.descansado = true
+  end
+
+end
+
+ +

Hasta acá todo muy bien, pero ahora, surge el problema de que Guerrero, como usa el mixin Atacante, y el mixin Defensor, estaría trayendo dos métodos iguales, por lo que surge un conflicto. Entonces la pregunta es, ¿cómo se va a comportar el guerrero si le mandamos el mensaje descansar?

+ +

No se va a romper, porque los conflictos de mixins se resuelven automáticamente por medio de la linearización. En el caso de ruby, la segunda definición va a pisar la primera, entonces por cómo definimos Guerrero, al incluir último el Defensor, la definición del mixin Defensor se va a ejecutar y no la de Atacante.

+ +

Pero para los Guerreros vamos a querer que descanse como atacante y luego como defensor (o sea de ambas formas).

+ +

Para lograr esto, hay diferentes soluciones:

+

+ + + Hacer un ‘decorator’ con los mixines, en los que cada mixin llama a super y tienen un mixin padre con el caso base, que en este ejemplo simplemente no hace nada. + + +

+ + +
module Atacante
+  include Unidad
+  
+  def descansar
+    self.descansado = true
+    super
+  end
+end
+ 
+module Defensor
+  include Unidad
+  
+  def descansar
+    self.energia += 10
+    super
+  end
+end
+ 
+module Unidad
+  def descansar
+  end
+end
+ 
+class Guerrero
+  include Atacante
+  include Defensor
+end
+
+ +

En este caso, si Guerrero incluye tanto Atacante como Defensor, el method lookup seguiría el siguiente camino resultado de la linearización:

+ +

Guerrero ~> Defensor ~> Atacante ~> Unidad ~> Object

+ +

Entonces, Defensor y Atacante van a hacer lo suyo y luego van a delegar la responsabilidad de seguir descansando al siguiente de la lista. El objetivo de Unidad es terminar la cadena, si no estuviera ahí eventualmente se llegaría a un NoMethodError porque un Object no conoce el mensaje descansar.

+

+ + + Crear alias methods que nos provee ruby: + + +

+ + +
class Guerrero
+  include Atacante
+  
+  alias_method :descansar_atacante, :descansar
+  
+  include Defensor
+  
+  alias_method :descansar_defensor, :descansar
+
+  def descansar
+    self.descansar_atacante
+    self.descansar_defensor
+  end
+end
+
+ +

Nótese que no solucionamos el conflicto sólo con definir los alias, lo que vamos a tener son tres métodos: :descansar_atacante, :descansar_defensor y :descansar (cuya implementación va a coincidir con la de Defensor).

+ +

Es necesario sobreescribir el método :descansar para lograr el comportamiento que queríamos.

+

+ + + Kamikaze + + +

+ +

Ahora queremos agregar los Kamikaze, que son Atacantes y Defensores, pero descansan solo como Atacante porque van a morir de todas maneras:

+ +
class Kamikaze
+  include Defensor
+  include Atacante
+
+  def initialize(energia=100, potencial_defensivo=10)    
+    self.potencial_ofensivo = 250
+    self.energia = energia    
+    self.potencial_defensivo = potencial_defensivo  
+  end
+
+  def atacar(un_defensor)
+    super(un_defensor)
+    self.energia = 0
+  end
+  
+end
+
+

+ + + Pelotones + + +

+ +

A continuación vamos a agregar los Pelotones, que tienen un conjunto de Guerreros que lo integran. Queremos que entiendan descansar y que hagan descansar a todos los integrantes que estén cansados. Un integrante está cansado cuando su energía es menor o igual a 40. ¿Queremos que el Peloton sea un Defensor o un Atacante? ¿Qué aportan estos Mixins en cuanto a compartición de código? ¿Sería una buena idea desde el punto de vista de la naturaleza?

+ +
class Guerrero
+  attr_accessor :peloton
+end
+
+class Peloton
+  attr_accessor :guerreros
+
+  def initialize(integrantes)
+     self.integrantes = integrantes
+     self.integrantes.each { |integrante|integrante.peloton = self}
+  end.
+
+  def descansar    
+    cansados = self.integrantes.select { |integrante|
+      integrante.cansado    
+    }    
+    cansados.each { |integrante|
+      integrante.descansar
+    } 
+  end
+
+end
+
+ +

Cabe destacar que los bloques (tanto si los escribimos con llaves como con do y end) no son objetos y sólo se puede pasar un bloque como último parámetro del método.

+ +

Como necesitamos que los guerreros puedan avisarle a su pelotón que sufrieron daño para que el pelotón decida cómo reaccionar, tenemos que redefinir cómo reciben daño los Guerreros.

+ +
class Guerrero
+
+  def sufri_danio(danio)
+    super(danio)
+    self.lastimado if cansado
+  end
+  
+  def cansado    
+    self.energia <= 40
+  end
+
+end
+
+ +

Ahora, lo que vamos a hacer es modelar las estrategias que sigue el Pelotón, donde cada estrategia es una clase distinta (ver patrón Strategy):

+ +
class Peloton
+  attr_accessor :integrantes, :retirado, :estrategia
+
+  def initialize(integrantes, estrategia)
+    self.integrantes = integrantes
+    self.estrategia = estrategia
+    self.integrantes.each { |integrante|
+      integrante.peloton = self
+    }
+  end
+
+  def lastimado
+    self.estrategia.lastimado(self)
+  end
+
+  def retirate
+    self.retirado = true
+  end
+end
+
+class Descansador
+  def lastimado(peloton)
+    peloton.descansar
+  end
+end
+
+class Cobarde
+  def lastimado(peloton)
+   peloton.retirate
+  end
+end
+
+ +

El problema que tenemos aca es que cada vez que se quiera crear un nuevo tipo de estrategia, debe crearse una nueva clase. Esto en principio no es muy problemático, pero es algo que puede ser molesto si hay muchos tipos de estrategias para cuando se lastima el pelotón, tendría un montón de clases para que definan sólo un método.

+ +

Una alternativa para este problema sería que el Pelotón conozca un bloque de código en vez de una instancia de Descansador o Cobarde. Si bien los bloques de Ruby no son objetos, y necesitaríamos que lo sean para que el pelotón lo conozca y lo pueda ejecutar cuando sea necesario mandándole un mensaje, lo que podemos usar son procs o lambdas, que sí son objetos. Para evitar confusiones a los procs y lambdas vamos a decirles closures, después podemos ver en qué se diferencian pero no hace al ejemplo.

+ +

La parte simpática de tener una clase por cada estrategia era la facilidad de creación de un ejército descansador por ejemplo, ya que si tenemos que inicializarlo con el closure adecuado podría llevarnos a repetir lógica. Por eso definimos métodos de clase que devuelven el Peloton creado y configurado con el código de la acción a tomar.

+ +

Para definir estos métodos de clase vamos a esperar que nos pasen bloques y en el método convertirlo en un closure que pueda ser referenciado por el pelotón y usado más adelante con el mensaje call.

+ +
class Peloton
+  attr_accessor :integrantes, :retirado, :estrategia
+
+  def self.cobarde(integrantes)
+    self.new(integrantes) {|peloton|
+      peloton.retirate
+    }
+  end
+
+  def self.descansador(integrantes)
+    self.new( integrantes) { |peloton| 
+      peloton.descansar 
+    }
+  end
+
+  def initialize(integrantes, &estrategia)
+    self.integrantes = integrantes
+    self.estrategia = estrategia
+    self.integrantes.each { |integrante|
+      integrante.peloton = self
+    }
+  end
+
+  def lastimado(defensor)
+    self.estrategia.call(self)
+  end
+
+end
+
+ +

Retomando el tema de las diferencias entre procs y lambdas, los procs no checkean la cantidad de argumentos que reciben mientras que las lambdas sí. Por otro lado también hay una diferencia con el uso de return dentro de una lambda o de un proc, ya que en return en la lambda sólo finaliza la ejecución de ella misma sin afectar al contexto en el cual se encuentra, mientras que el proc hace que se retorne del método que lo contiene.

+ +

Tanto procs como lambdas son instancias de la clase Proc.

+ +
lam = lambda { |x| puts x }    # creates a lambda that takes 1 argument
+lam.call(2)                    # prints out 2
+lam.call                       # ArgumentError: wrong number of arguments (0 for 1)
+lam.call(1,2,3)                # ArgumentError: wrong number of arguments (3 for 1)
+
+proc = Proc.new { |x| puts x } # creates a proc that takes 1 argument
+proc.call(2)                   # prints out 2
+proc.call                      # returns nil
+proc.call(1,2,3)               # prints out 1 and forgets about the extra arguments
+---------------------------------------------------------------------------------------------
+def lambda_test
+  lam = lambda { return }
+  lam.call
+  puts "Hello world"
+end
+
+lambda_test                 # calling lambda_test prints 'Hello World'
+
+
+def proc_test
+  proc = Proc.new { return }
+  proc.call
+  puts "Hello world"
+end
+
+proc_test                 # calling proc_test prints nothing
+------------------------------
+a = 5
+p = proc {a = a + 1}
+p.call # 6
+p.call # 7
+
+a   # 7
+
+

+ + + Anexo + + +

+ +

+ + + MIXINS + + +

+ + +

Si lo que queremos es incluir comportamiento de n modules, pero no nos importa utilizar un método que comparten (en este caso descansar), podemos incluir a los modules de cualquier forma, y no va a influir en lo que hagamos después. Por el contrario, si lo que queremos es utilizar algun método que aparece en más de un module, tenemos que resolver los conflictos que se presenten.

+

+ + + MIXINS: Resolución de conflictos + + +

+ + +

En este ejemplo, vemos que un Atacante no descansa de la misma forma que un Defensor, por lo cual podemos tomar varios caminos

+ +
    +
  1. Si lo que queremos es incluir comportamiento de ambos, pero ademas nos interesa que descanse como uno o como el otro, entonces el que nos interesa es el que deberia estar incluido mas abajo, porque al ser su superclase más inmediata es del primero que toma el método cuando no lo encuentra en Guerrero. Esto conlleva a un grave problema de todas formas, ya que si ambos (aunque no es muy común) tuviesen n cantidad de métodos que se llaman igual pero por algun motivo hacen cosas diferentes, ya no seríamos libres de elegir cuál toma prioridad por sobre el otro.
  2. +
  3. Alias methods: Nos sirven para poder renombrar estos métodos compartidos con otro nombre y así poder utilizarlos, tanto si queremos separar su comportamiento como si queremos juntarlos. En el ejemplo, vemos que les cambia el nombre a ambos metodos para justamente poder usar a los dos. Ahora sí, una vez que ya tengo estos métodos separados puedo entonces si quiero definir un nuevo método descansar, que ejecute a ambos.
  4. +
  5. Tambien habíamos visto en clase una alternativa usando super, ya que cuando incluimos a un module por debajo del otro quedan como si fuesen superclases una de la otra (recordar que si hago ancestors de la clase que incluye a los modules, aparecen los modules y sus ancestors tambien), y entonces el module que se encuentre mas arriba va a ser la superclase mas lejana. +En el caso de descansar, podríamos haber definido solo el alias method para Atacante, y que cuando definimos el nuevo descansar ejecute super y luego a descansar_atacante.
  6. +
+ +

Hay que notar que si nosotros hubiésemos incluido más modules, podríamos hacer super (si todos tienen descansar en común) del último que incluimos, porque es la superclase inmediata, y del resto, definir alias methods.

+ +
[38] pry(main)> Guerrero.ancestors
+=> [Guerrero, Defensor, Atacante, Object, PP::ObjectMixin, Kernel, BasicObject]
+### Aca vemos que su primer Superclase es Defensor.
+
+
+class Guerrero
+    include Atacante
+    alias_method :descansar_atacante, :descansar
+    include Defensor
+    def descansar
+         self.descansar_atacante  super
+    end  
+end  
+
+[39] pry(main)> un_guerrero = Guerrero.new
+=> #<Guerrero2:0x0000000258f668>
+[40] pry(main)> un_guerrero.descansar
+soy atacante
+soy defensor
+
+

+ + + Lazy Initialization + + +

+ + +

Podemos usar ||= para inicializar una variable, pero hay que tomar algunas consideraciones con eso. Hay que tener en cuenta que Ruby tiene algunos valores que considera que son false o que son true. Por ejemplo, a nil lo considera false, y otros valores como numeros, letras, etc, los considera true. +Supongamos que quiero inicializar@a:

+ +
def inicializar
+ @a ||= @b
+end
+
+ +
### a. Si @variable es true o se considera true, toma el valor de  @a, sin importar el valor de @b
+[15] pry(main)> @a = 3
+=> 3
+[16] pry(main)> @b = false
+=> false
+[17] pry(main)> @a ||= @b
+=> 3
+[18] pry(main)> @b = 4
+=> 4
+[19] pry(main)> @a ||= @b
+=> 3
+
+### b. Si @a es false o se considera false, toma el valor de @b
+
+ pry(main)> @a = nil
+=> nil
+[2] pry(main)> @b = 3
+=> 3
+## Aca vemos que al hacer || devuelve el que considera true, o sea @b
+[3] pry(main)> @a || @b    
+=> 3
+[4] pry(main)> @a   ## @a sigue siendo nil
+=> nil
+[5] pry(main)> @a ||= @b    ##Lazy initialization
+=> 3
+[6] pry(main)> @a
+=> 3
+
+### c. Si ambos son false o se consideran false, siempre toma el segundo valor.
+
+[12] pry(main)> @a = nil
+=> nil
+[13] pry(main)> @b = false
+=> false
+[14] pry(main)> @a ||= @b
+=> false
+
+

+ + + Sobre bloques como objetos + + +

+ + +

Ademas de todo lo visto de bloques, puede ser muy útil guardar un bloque como un objeto (un proc) y ya vimos que lo único que hay que hacer es agregar la palabra proc antes del mismo. Esto permite que podamos guardarlo en una variable y que le podamos hacer:

+ +
@a_block.call(...args...)
+
+ +

Ahora, suponiendo que queremos revertir esto y convertir el objeto que era un bloque, en un bloque de nuevo, lo único que tenemos que hacer es agregar un &, por ejemplo: &@a_block, ya no es un objeto, sino un bloque, pero hay que entender que lo podemos hacer mientras un metodo lo vaya a recibir como argumento. +Un método puede recibir solo un bloque, pero varios argumentos. Por ejemplo:

+ +
[1] pry(main)> def un_metodo(a,b,&a_block)
+### Para poder mandarle un bloque hay que agregar &
+[1] pry(main)*   a_block.call
+[1] pry(main)* end  
+=> :un_metodo
+
+ +

Es un método que va a recibir dos argumentos y un bloque. Si yo tuviese un proc @a_proc, para pasárselo efectivamente debería hacer &@a_proc. +Probamos lo que pasa:

+ +
[2] pry(main)> @a_proc = proc do "hello!" end
+=> #<Proc:0x0000000205c600@(pry):101>
+
+### Miren lo que pasa si al metodo le paso un proc:
+[3] pry(main)> un_metodo 0, 0, @a_proc
+ArgumentError: wrong number of arguments (given 3, expected 2)
+from (pry):79:in `un_metodo'
+
+ +
### En cambio, si le paso un bloque:
+
+[5] pry(main)> un_metodo 0,0,&@a_proc
+hello!
+=> nil
+
+### o también
+
+[6] pry(main)> un_metodo(0,0) do puts "hello!" end
+hello!
+=> nil
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_3/index.html b/scripts/clase_3/index.html new file mode 100644 index 0000000..26bf669 --- /dev/null +++ b/scripts/clase_3/index.html @@ -0,0 +1,561 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 3 TADP +
+
+
+
+
+ + +
+
+
+

+ + + Clase 3 TADP + + +

+ +

+ + + METAPROGRAMACION + + +

+ +

Es el proceso o la práctica por la cual escribimos programas que generan, manipulan o utilizan otros programas.

+
+ + + Ejemplos + + +
+ +
    +
  • Un compilador se puede pensar como un programa que genera otro programa.
  • +
  • Un formateador de código es un programa que manipula otro programa.
  • +
  • Una herramienta como javadoc utiliza nuestro programa para generar su documentación.
  • +
+
+ + + Para qué se usa la metaprogramación? + + +
+ +

En general la metaprogramación se utiliza más fuertemente en el desarrollo de frameworks y herramientas.

+ +

Dado que los frameworks van a resolver cierta problemática de las aplicaciones, no van a estar diseñados para ninguna en particular. Es decir, la idea de framework es que se va a poder aplicar y utilizar en diferentes dominios desconocidos para el creador del framework.

+ +

Ejemplos:

+ +
    +
  • ORM’s (como hibernate): Que van a encargarse de persistir las instancias de nuestras clases sin siquiera conocerlas de antemano.
  • +
  • Frameworks de UI: Que deberán saber mostrar / bindear cualquier objeto.
  • +
  • Frameworks de Testing (como JUnit): suelen usar metaprogramación para analizar la clase de Test y encontrar los tests que se deben correr.
  • +
  • Documentadores de Código: Son herramientas que leen el código fuente y genera documentación (por ejemplo: JavaDoc).
  • +
  • Code Coverage: Herramientas que miden cuánto de nuestro código es realmente ejecutado al correr los tests, y cuales lineas no.
  • +
  • Analizadores de código: Que evalúan nuestro código y genera métricas o miden violaciones a reglas definidas. Como el estilo de código, complejidad ciclomática, etc.
  • +
+

+ + + Reflection + + +

+ +

Es un caso particular de metaprogramación, donde “metaprogramamos” en el mismo lenguaje en que están escritos (o vamos a escribir) los programas. Es decir, todo desde el mismo lenguaje.

+

+ + + Tipos de reflection + + +

+ +

Para esto, generalmente, es necesario contar con facilidades o herramientas específicas, digamos “soporte” del lenguaje. Entonces reflection, además, abarca los siguientes items que vamos a mencionar en esta lista:

+ +
    +
  • Introspection: Se refiere a la capacidad de un sistema, de analizarse a sí mismo. Algo así como la introspección humana, pero en términos de programa. Para eso, el lenguaje debe proveer ciertas herramientas, que le permitan al mismo programa, “ver” o “reflejar” cada uno de sus componentes.
  • +
  • Self-Modification: Es la capacidad de un programa de modificarse a sí mismo. Nuevamente esto requiere cierto soporte del lenguaje. Y las limitaciones van a depender de este soporte.
  • +
  • Intercession: Es la capacidad de modificar las características del lenguaje desde el mismo. Por ejemplo: agregarle orientación a objetos a Lisp (CLOS).
  • +
+

+ + + Modelos y metamodelos + + +

+ +

Así como todo programa construye un modelo para describir su dominio, los lenguajes pueden hacer lo mismo para describir sus abstracciones. El dominio de un metaprograma son los programas.

+ +

El programa describe las características de los elementos del dominio utilizando clases, métodos, atributos entre otros. Entonces, el modelo puede contener por ejemplo una clase Guerrero, que modela a los guerreros en el domino.

+ +

Un metaprograma usará el metamodelo que describe al programa base. Así como en el dominio hay guerreros, los elementos del “metadominio” serán las construcciones del lenguaje. Por ejemplo: clases, atributos, métodos.

+

+ + + Practica + + +

+ +

Vamos a usar el código de la clase anterior como ejemplo: https://github.com/tadp-utn-frba/tadp-clases/tree/ruby-age

+ +

Podemos comenzar con un poco de introspection y preguntarle a un objeto desde su clase hasta que metodos tiene.

+ +
require_relative 'age'
+atila = Guerrero.new
+atila.class  #=> Guerrero
+atila.class.superclass  #=> Object
+
+atila.methods  #=> [:sufri_danio, :descansar, :energia, :energia=,...]
+Guerrero.instance_methods #=> Idem a atila.methods
+Guerrero.instance_methods(false) #=> [:descansar_atacante, :descansar_defensor,...] (solo los de la clase guerrero)
+
+ +

Tambien podemos empezar a interactuar con los objetos de otra manera, como mandarle mensajes de otra manera.

+ +
atila.send(:potencial_ofensivo)  #=> 20
+atila.send(:descansar)  #=> 110
+
+# con send no existen los metodos privados, la seguridad es una sensacion
+class A
+    private
+    def metodo_privado
+        'cosa privada, no te metas'
+    end    
+end
+objeto = A.new
+objeto.metodo_privado #=> NoMethodError: private method `metodo_privado' called for #<A:direccion en memoria del objeto>
+objeto.send(:metodo_privado)  #=> "cosa privada, no te metas"
+
+

+ + + Method / Unbound Method + + +

+ +

Tambien podemos obtener un metodo e invocarlo

+ +
metodo = atila.method(:potencial_ofensivo)  #=> #<Method: Guerrero(Atacante)#potencial_ofensivo>
+metodo.call  #=> 20
+
+ +

Algo interesante que podemos hacer es pedirle un metodo de instancia a una clase. A este metodo se lo llama Unbound Method, ya que no esta asociado a ninguna instancia de esa clase. Podemos asociarlo a un objeto siempre y cuando este dentro de la jerarquia de clases.

+ +
class Padre
+    def correr
+        'correr como padre'
+    end
+end
+
+class Hijo < Padre
+    def correr
+        'correr como hijo'
+    end
+end
+metodo = Padre.instance_method(:correr)  #=>#<UnboundMethod: Padre#correr>
+metodo.bind(Hijo.new).call  #=> 'correr como padre'
+
+ +

Como vemos los Unbound Methods se escapan al metodo lookup. +Tambien podemos preguntarle cosas a los metodos.

+ +
metodo = atila.method(:atacar) #=> #<Method: Guerrero(Atacante)#atacar>
+metodo.arity  #=> 1
+metodo.parameters #=> [[:req, :un_defensor]]
+metodo.owner  #=> Atacante (donde esta definido)
+
+ +

Asi como ya estuvimos jugando con las clases, objetos y metodos, tambien podemos jugar con las variables.

+ +
atila.instance_variables #=> [:@potencial_ofensivo, :@energia, :@potencial_defensivo]
+atila.instance_variable_get(:@energia)  #=> 100
+atila.instance_variable_set(:@energia, 50) #=> 50
+atila.instance_variable_get(:@energia)  #=> 50
+atila #=> pretty print muestra el estado interno
+
+

+ + + Self-Modification + + +

+ +

+ + + Open classes + + +

+ + +

Nos permite definir métodos y atributos en una clase ya existente. +Es una forma de self modification con azucar sintáctica para no tener que hacerlo mediante mensajes.

+ +

Los métodos que se definan con el mismo nombre que otro ya existente, serán reemplazados (es destructivo).

+ +

Para Ruby, las firmas de los métodos están definidas solo por el nombre. Un método con el mismo nombre y diferente cantidad de parametros definen el mismo método.

+ +
class String
+  def importante
+    self + '!'
+  end
+end
+'aprobe'.importante  #=> "aprobe!"
+
+#Cambiar métodos
+class Fixnum
+  def +(x)
+    123
+  end
+end
+2+2  #=> 123
+
+ +

Otra manera de abrir las clases y definir metodos es usando en la clase el define_method pero este es privado y como ya vimos podemos pasarlo por arriba invocando el send.

+ +
Guerrero.send(:define_method, :saluda) {
+  'Hola'
+}
+Guerrero.new.saluda  #=> "Hola"
+
+ +

Aca podemos hacer referencia a dos practicas de programacion: Duck Typing y Monkey Patching.

+

+ + + Duck Typing + + +

+ +

Debido a que ruby es un lenguaje dinamicamente tipado, hacemos referencia a un tipo de dato por el comportamiento que tiene.

+
+

…if it walks like a duck and talks like a duck, it’s a duck, right?

+
+ +

Si tenemos un objeto que cuando hace ruido hace “cuak” y camina como un pato, probablemente lo sea, y deberia poder continuar usando este objeto como si fuera uno.

+

+ + + Monkey Patching + + +

+ +
+

…if it walks like a monkey and talks like a monkey, it’s a monkey, right? So if this monkey is not giving you the noise that you want, you’ve got to just punch that monkey until it returns what you expect.

+
+ +

Hace referencia a la posibilidad de practicamente modificar un tipo a gusto y piacere para que responda a nuestras necesidades y realizar otro tipo de operaciones como si fuera otro.

+

+ + + Singleton Class + + +

+ +

Tambien podemos empezar a hacer algunas cosas mas locas, como agregarle comportamiento a un unico objeto

+ +
atila.define_singleton_method(:saluda) {
+  'Hola soy Atila'
+}
+atila.saluda  #=> "Hola soy Atila"
+Guerrero.new.saluda # NoMethodError
+
+

+ + + METAMODELO + + +

+ +

Empecemos a descubrir el modelo de clases.

+ +

Vamos a jugar un poco más con el metamodelo, ya sabemos que existe el mensaje class que lo entienden todos los objetos, si queremos saber la superclase de una clase tenemos el mensaje superclass. Podríamos pensar en base a eso quiénes le proveen comportamiento a cada uno de nuestros objetos.

+ +
zorro = Espadachin.new(Espada.new(123))
+
+ +

Tenemos al zorro que es instancia de Espadachin, que hereda de Guerrero. Si le pedimos los métodos al zorro vemos que incluye a los instance_methods de Espadachin y de Guerrero, así como todos los que se definen para las instancias de Object.

+

+ + + Autoclases / Eigen Class + + +

+ +

Pero nosotros no le agregamos comportamiento sólo a las instancias, también teníamos un par de métodos de clase que habíamos definido para Peloton, como por ejemplo cobarde.

+ +
Peloton.cobarde([])
+
+ +

Quién provee ese comportamiento? La clase de Peloton es Class, y este comportamiento no se agregó para todas las clases así que no puede estar definido dentro de Class.

+ +
Peloton.methods.include? :cobarde  #=> true
+Peloton.class.instance_methods.include? :new  #=> true
+Peloton.class.instance_methods.include? :cobarde  #=> false
+
+ +

Esto sólo lo entiende la clase Peloton, o sea que está definido para un sólo objeto. El objeto que le provee el comportamiento a un sólo objeto es la autoclase. En Ruby podemos obtener la autoclase de un objeto mandándole singleton_class.

+ +
Peloton.singleton_class.instance_methods(false)  #=> [:cobarde, :descansador]
+
+ +

Todos los objetos tienen una singleton class, con lo cual podemos definirle comportamiento a atila y que sea el único guerrero con ese comportamiento.

+ +

Incluyanmos un mixin a una instancia, utilizando include en la singleton class o extend en la intancia:

+ +
module W
+  def m
+    123
+  end
+end
+
+a = Guerrero.new
+a.singleton_class.include W
+a.m  #=> 123
+b = Guerrero.new
+b.extend W
+b.m  #=>123
+
+ +

Agreguemos un test en el que atila cuando se lastima descansa y se come un pollo, incorporando comerse_un_pollo:

+ +
atila = Guerrero.new
+atila.singleton_class.send(:define_method, :comerse_un_pollo, proc { @energia += 20 })
+atila.energia
+atila.comerse_un_pollo
+atila.energia
+Guerrero.new.comerse_un_pollo  # NoMethodError
+
+ +

También vemos que se puede tener properties para un único objeto:

+ +
atila.singleton_class.send(:attr_accessor, :edad)
+atila.edad = 5
+atila.edad  #=> 5
+Guerrero.new.edad  #=> NoMethodError
+
+ +

Vemos que no aparecen los mixins que tiene la clase Guerrero si vamos preguntando las super clases. El mensaje superclass no muestra los mixins (porque no son clases), para poder ver la linearización tenemos que pedir quienes proveen comportamiento con el mensaje ancestors.

+ +
Guerrero.ancestors  #=> [Guerrero, Defensor, Atacante, Object, PP::ObjectMixin, Kernel, BasicObject]
+
+

+ + + Nota + + +

+ +

Las últimas versiones de ruby incluyen a la singleton class de Guerrero, las anteriores a 2.1.0 NO incluyen a las singleton classes:

+ +
Guerrero.new.singleton_class.ancestors
+
+[#<Class:#<Guerrero:0x00000001d6c8c8>>,
+Guerrero,
+Defensor,
+Atacante,
+Object,
+PP::ObjectMixin,
+Kernel,
+BasicObject]
+
+ +

Dibujar los ancestors de Guerrero en el pizarron. +Dibujar la singleton class de atila. +Agregamos un método de clase a Guerrero:

+ +
class Guerrero
+  def self.gritar
+    'haaaa'
+  end
+end
+
+atila.gritar  #=> NoMethodError
+Guerrero.gritar  #=> haaaa
+
+ +

Veamos en el diagrama, la singleton class de Guerrero (donde definimos gritar)

+ +
Espadachin.gritar  #=>haaaa
+
+ +

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_4/index.html b/scripts/clase_4/index.html new file mode 100644 index 0000000..c08a8e5 --- /dev/null +++ b/scripts/clase_4/index.html @@ -0,0 +1,653 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 4 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + TADP Clase 4: instance_eval, method_missing + + +

+ +

+ + + Contexto (scope) + + +

+ +

Consideremos el siguiente ejemplo

+
x = 10
+class A
+  puts x
+end
+# NameError: undefined local variable or method `x' for A:Class
+
+def m
+  puts x
+end
+m # NameError: undefined local variable or method `x' for main:Object
+
+ +

Esto pasa porque la variable ‘x’ solo puede ser accedida desde el contexto donde se creó. +El contexto cambia en tres casos (se los suele llamar scope gates):

+
    +
  • Cuando se define una clase con class
  • +
  • Cuando se define un módulo con module
  • +
  • Cuando se define un método con def
  • +
+

+ + + Flat scope + + +

+ + +

Una forma de saltear la restricción de los cambios de contexto, es usar closures. +Ruby tiene tres tipos de closures: los bloques, lambdas y procs. Su principal característica es que “recuerdan” el contexto donde fueron creados.

+
a = 5
+p = lambda {a = a + 1}
+p.call # 6
+p.call # 7
+
+a   # 7
+
+ +

Para hacer que una variable esté en el contexto de la definición de un método, podemos reemplazar def con define_method.

+
x = 10
+define_method(:m) do
+  x + 5
+end
+m # 15
+
+ +

De la misma manera, podemos superar el scope gate que define class reemplazandolo con Class.new

+
x = 10
+una_clase = Class.new do
+  x += 5
+end
+x # 15
+
+ +

A esta técnica que permite mantener el mismo contexto se la llama flat scope (contexto aplanado).

+

+ + + Bloques / lambdas / procs + + +

+ +

Las lambdas y los procs son objetos que reifican comportamiento y pueden ser ejecutados diferidamente. +Tienen dos diferencias fundamentales:

+
    +
  • Cómo manejan los parámetros
  • +
  • Cómo se comportan con el return
  • +
+ +
lam = lambda { |x| puts x }    # creates a lambda that takes 1 argument
+lam.call(2)                    # prints out 2
+lam.call                       # ArgumentError: wrong number of arguments (0 for 1)
+lam.call(1,2,3)                # ArgumentError: wrong number of arguments (3 for 1)
+
+proc = Proc.new { |x| puts x } # creates a proc that takes 1 argument
+proc.call(2)                   # prints out 2
+proc.call                      # returns nil
+proc.call(1,2,3)               # prints out 1 and forgets about the extra arguments
+---------------------------------------------------------------------------------------------
+def lambda_test
+  lam = lambda { return }
+  lam.call
+  puts "Hello world"
+end
+
+lambda_test                 # calling lambda_test prints 'Hello World'
+
+
+def proc_test
+  proc = Proc.new { return }
+  proc.call
+  puts "Hello world"
+end
+
+proc_test                 # calling proc_test prints nothing
+
+ +

Los bloques (tanto si los escribimos con llaves como con do y end) no son objetos y sólo se puede pasar un bloque como último parámetro del método.

+
def bloque_test
+  yield(3)
+end
+
+bloque_test do |x|
+  x + 2
+end
+
+ +

Cuando sea necesario, se puede pasar un proc en lugar de un bloque usando &

+
def bloque_proc_test(&bloque)
+  bloque.call(3)
+end
+
+bloque_test do |x|
+  x + 2
+end
+
+

+ + + Contexto y receptor implícito: instance_eval + + +

+ +

Estamos acostumbrados a que, dentro de un método de una clase, podemos mandar un mensaje al objeto actual sin definir ningún receptor. +Por ejemplo:

+
class Usuario
+  attr_accessor :edad
+
+  def initialize(edad)
+    @edad = edad
+  end
+
+  def mayor_de_edad?
+    edad >= 18
+  end
+end
+
+Usuario.new(19).mayor_de_edad?
+# true
+
+ +

Para mandar el mensaje edad en el método mayor_de_edad? no necesitamos poner self.edad ya que self es el contexto implícito.

+

+ + + Contexto y bloques + + +

+ +

¿Cuál es el contexto dentro de un bloque? Los mensajes dentro de un bloque tienen receptor implícito?

+
class Usuario
+  def lazy_edad
+    proc { edad }
+  end
+end
+
+Usuario.new(19).lazy_edad.call
+# 19
+
+ +

¿Y si ese bloque lo invoco dentro de otro usuario?

+
class Usuario
+  def con_bloque(bloque)
+    bloque.call
+  end
+end
+
+mayor = Usuario.new(19)
+menor = Usuario.new(15)
+menor.con_bloque(mayor.lazy_edad)
+# 19
+
+ +

El contexto implícito de un bloque es el mismo que el que existía al momento de ser creado. +Si necesito que el bloque mande mensajes a otro usuario, voy a tener que pasarlo por parámetro:

+
class Usuario
+  def edad_de
+    proc { |u| u.edad }
+  end
+end
+
+Usuario.new(19).edad_de.call(Usuario.new(15))
+# 15
+
+

+ + + Cambiar el contexto + + +

+ +

Si pudiera cambiar el receptor default de los bloques, podría evitar pasar por parámetros el destino de mis mensajes!

+ +
class Usuario
+  def edad_de
+    proc { edad }
+  end
+end
+
+bloque = menor.edad_de
+mayor.instance_eval(&bloque)
+# 19
+menor.instance_eval(&bloque)
+# 15
+
+
+ + + instance_eval vs instance_exec: + + +
+ +
    +
  • instance_eval: cambiar el contexto de un bloque sin parámetros
  • +
  • instance_exec: cambiar el contexto pudiendo pasar parámetros al bloque
  • +
+
+ + + instance_eval vs class_eval vs module_eval: + + +
+ +

Cuando creamos un método dentro del bloque usando def:

+
    +
  • instance_eval: lo define en la singleton class del objeto
  • +
  • module_eval, class_eval: lo agrega como parte de los instance methods de la clase o el modulo.
  • +
+

+ + + Ejercicio con el Age + + +

+ +
+ + + Bloques sin parámetros + + +
+ + +

Usando el código del Peloton, definamos estrategias con bloques sin pasarle ningún parámetro.

+ +
def self.cobarde(integrantes)
+ new(integrantes) { retirate }
+end
+
+def self.descansador(integrantes)
+ new(integrantes) { descansar }
+end
+
+def lastimado
+ instance_eval(&estrategia)
+end
+
+
+ + + Definir nuevos métodos + + +
+ + +

Ya tenemos dos factory methods para construir distintos tipos de Peloton: descansador y cobarde. Queremos tener una forma de definir dinámicamente métodos similares con distintas estrategias. Uso:

+ +
 Peloton.definir :descansador_cobarde do 
+          descansar
+          retirate
+ end
+
+ +

Tiene que definir un método de clase en Peloton:

+ +
  un_peloton = Peloton.descansador_cobarde integrantes
+
+  def self.definir(nombre, &estrategia)
+    self.define_singleton_method nombre do |integrantes|
+      self.new(integrantes, &estrategia)
+    end
+  end
+
+ +

En este caso self es la clase Peloton, por lo que define_singleton_method va a definir un método en la singleton class de Peloton(#Peloton).

+

+ + + Mensajes dinámicos: method_missing + + +

+ +

Por un momento recordemos el patrón decorator. Queremos construir un decorator que retorne retorne “Anon” para los nombres de las personas pero que siga pudiendo acceder al resto de su comportamiento.

+ +

La forma clásica de solucionar este problema es redefinir todos los mensajes que entiende la persona en el objeto decorador. +Aquellos que tienen que mantener la lógica del objeto de origen, solo se pasa el mensaje hacia él. En los mensajes que deben cambiar de comportamiento se agrega la nueva lógica.

+ +

Sin embargo, ésta lógica repite mucho código y es muy fragil (si la persona modifica los mensajes que entiende, el decorador debe ser adaptado también).

+ +

Si pudiéramos capturar todos los mensajes que se envían a un objeto podríamos generalizar esa lógica sin repetir código. +Ruby (y otros lenguajes) nos permite hacer esto mediante el mensaje “method_missing”.

+ +
class Persona
+  attr_accessor :nombre, :edad
+  def initialize(nombre, edad)
+    @nombre = nombre
+    @edad = edad
+  end
+end
+
+class Anonimo
+  def initialize(persona)
+    @persona = persona
+  end
+
+  def method_missing(symbol, *args, &block)
+    @persona.send(symbol, *args, &block)
+  end
+
+  def nombre
+    "Anon"
+  end
+end
+
+p = Persona.new("Pablo", 32)
+p.nombre
+# Pablo
+ap = Anonimo.new(p)
+ap.nombre
+# Anon
+ap.edad
+# 32
+
+

+ + + Ejercicio con el Age + + +

+ +

Ahora quiero poder tener estos métodos pero usando una convención.

+ +

Quiero que la clase Peloton entienda los mensajes con la siguiente forma: estrategia_<nombre de mensaje>. Al enviarlo, va a retornar un pelotón que se enviará a si mismo el mensaje <nombre de mensaje> al ser lastimado.

+
def self.method_missing(symbol, *args, &block)
+ if (symbol.to_s.start_with?('estrategia_') && args.length == 1)
+    # mejorar error si lo invoco con diferente cantidad de parámetros
+   message = symbol.to_s.gsub('estrategia_', '').to_sym
+   Peloton.new(args[0]) {
+    send(message)
+   }
+ else
+   super
+ end
+end
+
+ +

Redefinir method_missing tiene un efecto no deseado, Peloton ahora entiende mensajes pero respond_to? de los mismos retorna false. +Para mitigar este problema, el contrato cuando se redefine method_missing es que hay que redefinir respond_to_missing?

+ +
  def self.respond_to_missing?(sym, priv = false)
+    sym.to_s.start_with?('estrategia_')
+  end
+
+

+ + + Anexo + + +

+ +

Para definir constantes en ruby, se puede usar Class»const_set

+ +
class Guerrero
+
+end
+
+Object.const_set :Atila, Guerrero.new
+
+Atila.atacar(otro)
+
+ +

Sólo las clases pueden definir constantes y solo viven en el scope de la clase que la definió.

+ +
class Bla
+  def m
+    A
+  end
+end
+
+Bla.const_set :A, "Bla::A"
+Object.const_set :A, "Object::A"
+
+bla = Bla.new
+bla.m # "Bla::A"
+A # "Object::A"
+
+class Bla
+  A # "Bla::A"
+end
+
+Bla.class_eval do
+  A # "Object::A"
+end
+
+ +

También existe el método const_missing, que al igual que const_set, lo entienden sólo las clases. Cumple la misma función que method_missing, pero para cuando no se encuentra una constante.

+ +
class Bla
+  def self.const_missing const
+    "#{const} no encontrada"
+  end
+end
+
+Bla::T # "T no encontrada"
+
+

+ + + Anexo 2: method_added + + +

+ + +

Ruby provee un mecanismo para “avisar” cuando se agrega un método en una clase:

+
class A
+  def self.method_added(method_name)
+    puts "Se agregó el método #{method_name}"
+  end
+end
+
+class A
+  def un_metodo
+    # ...  
+  end
+end
+# 'Se agregó el método un_metodo'
+
+ +

También se puede saber si se agregó un singleton method:

+
class A
+  def singleton_method_added(name)
+    puts "Singleton added #{name}"
+  end
+end
+
+a = A.new
+
+def a.m
+  puts 'Soy un a'
+end
+# 'Singleton added m'
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_5/index.html b/scripts/clase_5/index.html new file mode 100644 index 0000000..9fa05ea --- /dev/null +++ b/scripts/clase_5/index.html @@ -0,0 +1,244 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 5 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + Clase 5: Ejercicio Integrador + + +

+ + +

En estas dos clases (clase 5 y 6) estaremos viendo y avanzando con un ejercicio integrador en el que vayamos aplicando los distintos conceptos de metaprogramación y aplicar también lo visto del metamodelo de Ruby. Para ello empezaremos viendo el enunciado del Prototype, que en algún momento fue un TP y ahora lo estamos dando como un ejercicio integrador.

+ +

La resolución a la que llegamos está en el siguiente repositorio (https://github.com/tadp-utn-frba/tadp-clases/tree/ruby-prototype)

+

+ + + Cuál fue la problemática en la resolución de este enunciado? + + +

+ + +

Qué sucede cuando se tiene una instancia de PrototypedObject guerrero al cual se le incorpora el comportamiento nuevo modificando su singleton class?

+ +
 @guerrero = PrototypedObject.new
+ @guerrero.set_property(:energia, 100)
+
+ +

Se desea que en algunos casos pueda existir una suerte de herencia con otros prototyped Objects, de modo que hereden el comportamiento que existe en la singleton class de la instancia de PrototypedObject. Se propuso la siguiente interfaz para obtener un nuevo PrototypedObject a partir del prototipo siguiendo la idea del clone de Ruby pero sin modificar el mismo para evitar problemas que podrían surgir por modificar la intención del clone, no sólo la implementación.

+ +
@otro_guerrero = @guerrero.clone_object
+
+ +

Vimos que usando clone, puedo obtener una nueva instancia de PrototypedObject con el mismo estado y comportamiento pero sin que se mantenga un vínculo con su prototipo (si el mismo cambiase se comportamiento más adelante, el clon no se va a ver afectado), ya que el clon se obtiene mediante un shallow copy del original que sólo copia en la singleton class del nuevo objeto lo que tenía la singleton class del original. +En el test tenemos que otro_guerrero es un clon de guerrero, que es un PrototypedObject. Lo que hicimos para el clone_object antes que nada fue setear el prototipo del nuevo objeto creado con el receptor.

+ +

Si no hacemos más nada cuando querramos utilizar un comportamiento que viene heredado de guerrero en otro_guerrero, nuesto method lookup no estará buscando por la jerarquía de guerrero.

+ +

Salieron distintas alternativas para resolver esto:

+ +
    +
  • Que tengamos nuestro propio method lookup y en caso de que no encontremos el método al que estemos llamando desde otro_guerrero, en nuestro lookup de prototypes, se siga por el method lookup de Ruby que tenemos. De esta manera, no es invasivo, o sea, que no estamos manoteando el method lookup original y permite que no pase el escenario en el que se pueda romper el entorno utilizando nuestro framework de prototypes con otras herramientas. Lo otro es que tenemos más control sobre lo que está sucediendo, aunque por otro lado empiezan a aparecer la inconsistencias, con las interfaces de reflection que tenemos en Ruby ya que es un method lookup independiente, y en ese caso para obtener comportamiento que está definido en nuestro method lookup necesitaremos exponer una interfaz aparte para conocer sobre ese mecanismo alternativo, por lo que implica mucho más trabajo adicional y manual.
  • +
  • Que el prototipo conozca a todos los que fueron clonados a partir de él y al momento de sufrir cambios avisarle a todos sus clones que tienen que cambiar. Esta alternativa tampoco es invasiva, pero tiene varios problemas asociados desde el punto de vista de performance cada vez que se quiere hacer un cambio y la complejidad algorítmica que puede surgir ya que nada nos evita que se generen loops en el grafo de objetos con los prototipos.
  • +
  • Modificar el método lookup de Ruby, haciendo que la eigenclass de la instancia de otro guerrero extienda de la eigenclass de guerrero, de esta manera no necesitamos hacer mucho trabajo y existe una consistencia, aunque por otra parte sucede que Ruby no nos permite de manera fácil hacer estos cambios en el metamodelo (no se permite crear una clase como subclase de una eigenclass).
  • +
  • Se puede hacer que los prototipos se incluyan por medio de módulos, y de esta manera estamos cambiando el método lookup de una manera que nos provee Ruby ya mediante la inclusión de módulos. Esta es probablemente la solución más simple y consistente de todas, ya que al ajustar nuestras ideas para que calcen a lo que Ruby ya soporta, todas las herramientas asociadas (como el respond_to?) ya van a funcionar como deberían sin que hagamos nada.
  • +
+ +

Continuamos la clase con la opción de hacer un method lookup paralelo redefiniendo method_missing, no porque fuera la mejor opción sino porque era interesante de analizar.

+ +
def respond_to?(sym)
+ super or self.prototype.respond_to? sym
+end
+
+def method(sym)
+ begin
+   super
+ rescue NameError
+   self.prototype.method sym
+ end
+end
+
+def method_missing(sym, *args)
+ super unless respond_to? sym
+ method = self.prototype.method(sym).unbind
+ method.bind(self).call *args
+end
+
+ +

De esta manera cuando querramos ejecutar un comportamiento de otro_guerrero que está definido en guerrero (su prototipo), después de buscar por el método lookup de Ruby, llega al method missing donde hacemos que busque en el prototipo. La forma en la cual podemos obtener el comportamiento correspondiente del mensaje no entendido es mandarle el mensaje method al prototipo, pero ese método va a estar bindeado al prototipo, motivo por el cual habría que desbindearlo y bindearlo al objeto otro_guerrero; pero al hacer esto nos va a tirar el error de que no se puede bindear un Unbound Method de una instancia de una jerarquía al de otra jerarquía, esto es por una limitación en la implementación del lenguaje. +Es por esto que se deberá optar por otro mecanismo aún si en teoría lo que propusimos puede funcionar perfectamente si no estuviese esta restricción.

+ +

El mismo problema hubiera surgido si en vez de pedírselo a la instancia y luego mandarle unbind se lo hubiéramos pedido a su eigenclass mandándole el mensaje instance_method, ya que nos retornaría ya un UnboundMethod pero tiraría un error al tratar de bindearlo con otro_guerrero.

+ +

La resolución final esta en el siguiente repositorio.

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_6/index.html b/scripts/clase_6/index.html new file mode 100644 index 0000000..9380900 --- /dev/null +++ b/scripts/clase_6/index.html @@ -0,0 +1,521 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 6 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + Clase 6: Ejercicio Integrador N° 2 + + +

+ + +

En esta clase veremos otro ejercicio integrador con el mismo fin que la clase pasada. Para ello realizaremos el ejercicio de Multimethods.

+

+ + + Que es multimethod? + + +

+ + +

Es un sistema que permite que una serie de métodos sean polimórficos en al menos uno o más de sus argumentos. Esta idea se puede ver en CLOS (Common Lisp Object System) y algunos lenguajes como Dylan. CLOS extiende a Common Lisp para agregar un sistema de objectos http://c2.com/cgi/wiki?TheArtOfTheMetaObjectProtocol extendiendo el lenguaje y para dar soporte a objetos.

+

+ + + Una explicación simple de Multimethods + + +

+ + +

En Ruby como en Python, ambos tienen sistemas de single dispatch, en el que la firma es esencialmente el nombre del método, sin importar en temas como la aridad, por lo que dos métodos como

+ +
def saraza(a)
+.
+end
+
+ +

y

+ +
def saraza(a, b, c)
+.
+end
+
+ +

no serán dos métodos sino que dependiendo de cómo estén declarados en el caso de Ruby, la segunda declaración pisa a la primera, en el caso de Python, en general sucede lo mismo en un intérprete o directamente nos lanzará un error en el código. Más allá de eso, nosotros queremos que saraza(a) y saraza(a, b) puedan coexistir y el método a ejecutar dependa de cuáles sean los parámetros recibidos. Por lo que deberemos chequear esto en tiempo de ejecución y dirigir el flujo al método con la firma adecuada. Esto se relaciona un poco con el concepto de polimorfismo del receptor, por ej. si tenemos algo como:

+ +
a.saraza(unParam)
+
+ +

a ejecutará un método u otro dependiendo de la clase a la que pertenece, queremos abrir la puerta a que también se decida en base al tipo de unParam. Vamos a hablar más sobre firmas de métodos y la búsqueda de la definición para un mensaje a partir de la misma en la segunda parte de la materia, cuando trabajemos con un lenguaje con tipado estático. De momento quédense con la idea de que lo que queremos implementar va a resolver qué definición usar en tiempo de ejecución, a partir del tipo de todos los objetos involucrados en el envío del mensaje, no sólo el receptor.

+

+ + + En donde entra la metaprogramación? + + +

+ + +

En este caso extenderemos a Ruby agregándole multimethods, de esta manera estamos un poco más del lado de intercession que es la capacidad de extender el lenguaje en el que estamos para agregarle más features interesantes (o locos?). Además como otros ejercicios usaremos reflection y self modification para llegar a resolver este problema.

+ +

Sobre el ejercicio

+ +

El enunciado del ejercicio esta acá

+ +

El código al que llegamos al final de la clase esta en este repo

+ +

En el primer punto implementamos algo del estilo

+ +
helloBlock = PartialBlock.new([String]) do |who|
+  "Hello #{who}"
+end
+
+helloBlock.matches("a") #true
+helloBlock.matches(1) #false
+helloBlock.matches("a", "b") #false
+
+ +

Acá lo que hicimos fue algo bastante simple que es crear la abstracción del PartialBlock que contenga los tipos de los parámetros esperados y el bloque que se espera poder ejecutar, y después le definimos el método matches devolviendo true o false dependiendo de si los tipos esperados coinciden con los de los argumentos que le pasamos al método. Este método debía definirse con varargs para poder recibir múltiples argumentos como se indicaba en el ejemplo de uso.

+ +
class PartialBlock
+  attr_accessor :block, :types
+
+  def initialize types, &block
+    self.types = types
+    self.block = block
+  end
+
+  def matches(*values)
+    unless values.length == types.length
+      return false
+    end
+
+    return true
+  end
+
+end
+
+ +

Además para poder evaluarlos como se pide a continuación usando call le agregamos:

+ +
class PartialBlock
+
+  def call(*args)
+    raise ArgumentError unless self.matches(*args)
+    self.block.call(*args)
+  end
+
+end
+
+ +
    +
  1. El segundo punto es el de poder crear multimethods de la mano de partial_def, que debemos poder utilizarlo en el contexto de una clase, de modo que luego las instancias de esa clase puedan responder al mensaje correspondiente.
  2. +
+ +
class A
+  partial_def :concat, [String, String] do |s1,s2|
+    s1 + s2
+  end
+
+  partial_def :concat, [String, Integer] do |s1,n|
+    s1 * n
+  end
+
+  partial_def :concat, [Array] do |a|
+    a.join
+  end
+end
+
+A.new.concat('hello', ' world') # devuelve 'helloworld'
+A.new.concat('hello', 3) # devuelve 'hellohellohello'
+A.new.concat(['hello', ' world', '!']) # devuelve 'hello world!'
+A.new.concat('hello', 'world', '!') # Lanza una excepción!
+
+ +

Ahora debemos definir el comportamiento del partial_def, abriendo una clase del metamodelo, pero cual? Podría pensarse de hacerlo sobre Object, al hacerlo sobre esta clase funcionaría para cualquier clase, pero a su vez le estaría dando este comportamiento también a cualquier objeto, lo cual no tendría sentido, sólo lo querríamos para las clases. Una opción válida sería Class aunque si queremos usar partial_def en un módulo no podremos, por lo que la otra opción es hacerlo sobre Module y permitirle tanto a clases como a módulos el de poder utilizar partial_def.

+ +

Otra cosa que tuvimos que decidir fue cómo íbamos a representar a los multimethods y cómo almacenar la información de cada definición que se haga usando partial_def. Una primer idea fue tener un atributo que guardara un diccionario donde el selector del mensaje a definir fuera la clave y se le asociara una lista con los partial blocks. Otra alternativa, por la que decidimos ir, era reificar la idea de Multimethod, de esa forma el atributo que terminamos llamando @actual_multimethods tendría directamente una lista de instancias de Multimethod (una por cada selector) de modo que se pudiera delegar también a estos objetos en vez de mantener toda la lógica en Module.

+ +

Para poder mandarle el mensaje definido usando partial_def a las instancias de la clase también surgieron ideas distintas. Una de ellas era definir un método en la clase/módulo que recibió partial_def que se llame igual que el símbolo recibido por parámetro de modo que triggeree la búsqueda de la implementación correspondiente en base a los parámetros que reciba, la otra era redefinir method_missing de modo que obtenga el multimethod con el símbolo correspondiente al mensaje no entendido y luego triggeree esa misma búsqueda en base a los parámetros recibidos.

+ +

Fuimos por la primer alternativa porque no hay una verdadera necesidad de caer en el method_missing, después de todo ya sabemos de antemano cuál es el mensaje que tiene que poder entender, y en general vamos a optar por no usar method_missing en esos casos ya que es más complejo (tenemos que asegurarnos de mantener consistente la interfaz de reflection también, cosa que si definimos el método usando define_method se da solo).

+ +

Además definimos la lógica necesaria para poder responder a los mensajes multimethod y multimethods:

+ +
A.multimethods() #[:concat]
+A.multimethod(:concat) #Representación del multimethod
+
+ +

… cuya implementación, al modelar al multimethod como un objeto, es trivial. Finalmente llegamos al siguiente código:

+ +
class Module
+
+  def partial_def(sym, types, &block)
+    partial_block = PartialBlock.new(types, &block)
+    multimethod = get_multimethod(sym)
+    multimethod.definitions << partial_block
+    self.send(:define_method, sym) do |*args|
+      multimethod.call(*args)
+    end
+  end
+
+  def actual_multimethods
+    @actual_multimethods ||= []
+  end
+
+  def multimethod(sym)
+    self.actual_multimethods.find { |mm| mm.selector.eql?(sym) }
+  end
+
+  def multimethods
+    self.actual_multimethods.map { |mm| mm.selector }
+  end
+
+  private
+
+  def has_multimethod?(multimethod)
+    self.actual_multimethods.include?(multimethod)
+  end
+
+  def get_multimethod(sym)
+    multimethod = self.multimethod(sym) || MultiMethod.new(sym)
+    actual_multimethods << multimethod unless has_multimethod?(multimethod)
+    multimethod
+  end
+
+end
+
+class MultiMethod
+
+  attr_accessor :selector, :definitions
+
+  def initialize(sym)
+    self.selector = sym
+    self.definitions = []
+  end
+
+  def call(*args)
+    definition = self.definitions
+                     .select { |definition| definition.matches(*args) }
+                     .min_by { |definition| definition.distance_to(*args) }
+    definition ? definition.call(*args) : raise(NoMethodError)
+  end
+
+end
+
+class PartialBlock
+  def distance_to(*args)
+    args.zip(types).each_with_index do |tuple, index|
+      case tuple[1]
+        when Array then
+          1 #because classroom-related reasons
+        else
+          tuple[0].class.ancestors.index(tuple[1]) * index
+      end
+    end
+  end
+end
+
+ +

Cabe destacar que esa solución de partial_def fue posible gracias a que el bloque conoce el contexto en el cual fue creado, por eso no es necesario buscar el multimethod en la lista.

+ +

A su vez, se pide extender la interfaz de reflection con métodos que indiquen si un objeto responde a un determinado mensaje con cierta firma:

+ +
A.new.respond_to?(:concat) # true, define el método como multimethod
+A.new.respond_to?(:to_s) # true, define el método normalmente
+A.new.respond_to?(:concat, false, [String,String]) # true, los tipos coinciden
+A.new.respond_to?(:concat, false, [Integer,A]) # true, matchea con [Object, Object]
+A.new.respond_to?(:to_s, false, [String]) # false, no es un multimethod
+A.new.respond_to?(:concat, false, [String,String,String]) # false, los tipos no coinciden
+
+ +

Entonces también debemos redefinir el respond_to?. Hay que tener siempre cuidado con este tipo de extensiones ya que debemos estar atentos de no modificar el comportamiento para aquellos métodos que no fueron definidos por medio de un partial_def. Las opciones propuestas fueron: +Definir respond_to? usando partial_def, de modo que que la definición original de respond_to? (la cual deberíamos asegurarnos de no perder mediante un alias o pidiendo el unbound method y guardándolo en una variable para poder invocarlo más adelante) se use si matchea con los tipos [Symbol] o [Symbol, Object], y una tercer definición para [Symbol, Object, Array] que haga lo que nosotros queremos. +Redefinir respond_to? como un método normal con un if, de modo que si nos pasan el tercer parámetro, se use la definición para multimethods y sino la original.

+ +

Tratamos de ir por la primera porque era más divertida, pero lamentablemente no funcionó por un loop infinito (respond_to? se usa en el core del method lookup, no fue por un error de la solución en sí, simplemente justo con en respond_to? no se puede, se las dejamos comentada de todos modos). Luego fuimos por la otra alternativa:

+ +
class Object
+
+  def respond_to?(sym, include_private = false, signature = nil)
+    signature.nil? ? super(sym, include_private) : self.class.actual_multimethods
+               .any? { |mm| mm.matches?(sym, signature) }
+  end
+
+=begin
+  partial_def :respond_to?, [Symbol] do |sym|
+    self.old_respond_to?(sym)
+  end
+  partial_def :respond_to?, [Symbol, Object] do |sym, bool|
+    self.old_respond_to?(sym, bool)
+  end
+  partial_def :respond_to?, [Symbol, Object, Array] do |sym, bool, types|
+    false unless self.class.multimethods.include?(sym)
+    multimethod = self.class.multimethod(sym)
+    multimethod.matches_signature?(types)
+  end
+=end
+
+end
+
+ +

Para saber si existe alguna definición para el multimethod cuya firma matchee con la lista de tipos recibida refactorizamos un poco PartialBlock para evitar la repetición de lógica.

+ +
class Multimethod
+
+  def matches?(sym, types)
+    self.selector.eql?(sym) && self.definitions
+                             .any? { |definition|definition.matches_signature?(types)}
+  end
+  
+end
+
+class PartialBlock
+  def matches(*args)
+    arg_types = args.map { |arg| arg.class }
+    matches_signature?(arg_types)
+  end
+
+  def matches_signature?(signature)
+    return false unless signature.size.eql?(self.types.size)
+    self.types.zip(signature).all? do |my_type, sign_type| sign_type <= my_type end
+  end
+end
+
+ +

Algo que quedó en el tintero para poder ir al siguiente punto es la aclaración de que se pueda usar self dentro de la definición de un método declarado mediante partial_def. Con el código actual eso no funcionará como querríamos, si te animás, c

+ +
    +
  1. Hay muchos más puntos en el enunciado de TP grupal original, pero por cuestiones de tiempo los dejamos afuera para resolver que pueda usarse duck typing (que fue el TP individual que se tomó), que es básicamente que pueda conocerse la usabilidad de un objeto de acuerdo a sus comportamientos en vez de su tipo estrictamente. Es decir que trato a dos objetos de distintas clases polimórficamente si entienden el mismo subconjunto de mensajes aún si son cosas totalmente diferentes como el siguiente ejemplo:
  2. +
+ +
class Duck:
+    def quack(self):
+        print("Quaaaaaack!")
+    def feathers(self):
+        print("The duck has white and gray feathers.")
+
+class Person:
+    def quack(self):
+        print("The person imitates a duck.")
+    def feathers(self):
+        print("The person takes a feather from the ground and shows it.")
+    def name(self):
+        print("John Smith")
+
+def in_the_forest(duck):
+    duck.quack()
+    duck.feathers()
+
+def game():
+    donald = Duck()
+    john = Person()
+    in_the_forest(donald)
+    in_the_forest(john)
+
+game()
+
+ +

Ahora volviendo al ejercicio, vamos a definir duck typing de la siguiente manera

+ +
class B
+    partial_def :concat, [String, [:m, :n], Integer] do |o1, o2, o3|
+    'Objetos Concatenados'
+    end
+end
+
+ +

Si el argumento que debería ser un tipo es un array de símbolos, representando el nombre de los selectores que debería entender, entonces se debe aplicar duck typing. Para implementar este agregado entonces debemos extender el matches del partial block de la siguiente manera.

+ +
class PartialBlock
+  def matches_signature?(signature)
+    return false unless signature.size.eql?(self.types.size)
+    self.types.zip(signature).all? do |my_type, sign_type|
+      case my_type
+        when Array then
+          my_type.all? { |method| sign_type.instance_methods.include?(method) }
+        else
+          sign_type <= my_type
+      end
+    end
+
+  end
+end
+
+ +

A esta altura debería volverse más evidente que el refactor que hicimos en el punto anterior para no repetir lógica era muy importante. Si no lo hacíamos antes, lo íbamos a tener que hacer ahora.

+ +

Algunas alternativas que no hubieran estado buenas para resolver este ejercicio: +Definir todo en términos de duck typing obteniendo todos los mensajes que definen las clases correspondientes. Eso rompería la funcionalidad porque el tipo en base a los mensajes que entiene puede abarcar objetos que no están en la jerarquía que se pedía inicialmente. +Resolver el if/switch con polimorfismo abriendo Array y Module. Esto es algo muy particular de nuestro framework, y ensuciar más la interfaz de Array y Module para evitar ese if no es una buena idea.

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_7/index.html b/scripts/clase_7/index.html new file mode 100644 index 0000000..e71ddb3 --- /dev/null +++ b/scripts/clase_7/index.html @@ -0,0 +1,436 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 7 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + Introducción a chequeo estático de tipos + + +

+ +
+ + + Primero vamos a comparar con lo que hacíamos anteriormente en Ruby + + +
+ + +
class Persona
+  :attr_accessor :name
+  
+  def initialize(name)
+    self.name = name
+  end
+end
+
+class Saludador
+  def saludar(a)
+    "asd" + a.nombre
+  end
+end
+
+Saludador.new().saludar(Persona.new("asd"))
+
+ +

Esto rompe en ejecución cuando intento mandar este mensaje, porque persona no entiende :nombre, sino que entiende :name.

+ +

Tendríamos que agregar algún tipo de validación para que esto no pase, o sea, un sistema de tipos.

+ +

Además, para poder ser usado con saludador, lo que le pasamos al método saludar tiene que ser una Persona +o alguien que entienda :nombre.

+ +

Cuando hablamos de tipos, hablamos de un conjunto de elementos o valores asociado a un conjunto de operaciones.

+ +

Si el saludador no puede interactuar con las operaciones que yo espero que interactúe, entonces tenemos un error de tipos.

+ +

En objetos, el tipo de uno de ellos (valores) está definido por el conjunto de métodos que entiende. Si hay dos elementos que entienden +un mismo mensaje, entonces comparten un tipo, pero no quiere decir que sean del mismo.

+

+ + + Tipado + + +

+ + +
    +
  • Operacional: aplica a todo lo que uno hace, sea computacional o no. Trato de llevar a cabo una operacion sobre un +elemento.
  • +
  • Representacional: solo aplica a lo computacional, llevar un tipo a un formato computable. Significa asumir una representación que no es correcta para ese tipo.
  • +
  • Estructural: Ej, si haskell permitiera hacer algo como: f [] = … f 1 = …
  • +
+ +

No pasa por qué tecnología tengo para extender el tipo o no, ni tampoco por si va a saltar cuando lo ejecute o antes, porque eso es parte del chequeo.

+

+ + + Chequeo + + +

+ + +
    +
  • Dinámico: Lo hace cuando yo le mando el mensaje al objeto. Ej: Ruby.
  • +
  • Estático: Decide si un programa es válido o no, analiza estáticamente el programa antes de que lo ejecutemos. +Esto permite detectar más errores que en el chequeo dinámico, por ejemplo, con el problema que tuvimos al principio +de la clase, donde persona no entiende :nombre. En este caso, no vamos a poder llegar a la instancia en la que ejecutemos +y rompa. El análisis es sobre la declaración. Ej: Haskell.
  • +
+
+ + + Ahora, en Scala: + + +
+ + +
object Saludador extends App {
+  case class Persona(name: String)
+  
+  class Saludador {
+    def saludar(alguien: Persona) =
+      "hola" + alguien.nombre
+  }
+  
+  new Saludador().saludar(new Persona("asdds"))
+}
+
+ +

Además, si la firma de saludar fuese diferente y recibiera algo de tipo Any, por más de que en todo el código nunca se +llame al metodo saludar con algo que no entienda el método nombre, rompe igual porque en el chequeo estático se fija +en el código, y cualquier cosa que le pase no entiende nombre.

+ +

!! Puedo tener polimorfismo sólo entre dos cosas que yo establecí explícitamente que comparten un tipo !!

+

+ + + Notación + + +

+ +
    +
  • Explícita: Se escribe el tipo. Puede existir un mecanismo de inferencia de tipos que facilite el trabajo de escribir los tipos explícitamente. +La inferencia sale de la notación explícita y el tipado estático y permite implicitar algo que de otra forma tendrías que haber explicitado. +Haskell tiene tipado explícito inferido. Scala tiene inferencia, pero no puede inferir todo.
  • +
  • Implícita
  • +
+

+ + + Conformación + + +

+ +
    +
  • Estructural (duck-typing, pattern matching): Un tipo se referencia por su forma. Ej: f (_,_,a) = a
  • +
  • Nominal: Un tipo se referencia por su nombre. Ej: String, Int
  • +
+

+ + + Errores + + +

+ +
    +
  • Errores de tipo: siempre está en el programa, esté en ejecución o no, no importa cómo hago el chequeo.
  • +
  • Errores detectados
  • +
+ +

equivalente a

+ +
    +
  • Programas que puedo hacer con un tipado estático: Un chequeo estático de tipos me permite detectar los programas válidos en compilación. El compilador es el que determina si funciona o no.
  • +
  • Programas que puedo hacer sólo con tipado dinámico
  • +
+
+ + + Errores que creemos que deberían ser de tipo pero en teoría no lo son + + +
+ + +
val x = 5
+val y = 0
+
+x/y // Los numeros entienden la división, por eso no sería un error de tipos en realidad
+
+val list = List()
+list.head // Las listas también entienden head, el problema es que está vacía
+
+ +

Acá aparece la operación parcial, que encapsula a un error de tipos, cuando puramente no lo son.

+

+ + + Desarrollo del ejercicio + + +

+ + +

Si a un parámetro le pongo val o var, lo expone como si fuera público. +Puedo recrear un constructor haciendo algo del estilo class A(variableA = valor) pero en realidad no es un constructor en sí mismo.

+ +

Necesitamos que un parámetro esté expuesto, si tenemos que accederlo después, por ejemplo

+ +
class Guerrero(potencialOfensivo = 10)
+
+def atacarA (unGuerrero : Guerrero) = {
+ .. .... unGuerrero.potencialOfensivo // si no estaba expuesto en la clase, no podía hacerlo.
+}
+
+ +

En este caso, era trivial poner ese =, de lo contrario me va a devolver Unit (void).

+ +

No es necesario poner las llaves si tengo una sola expresión adentro.

+ +

En cambio, si yo quiero hacer algo del estilo

+ +
atila.atacarA(unaMuralla)
+
+ +

No funciona, aunque podría ser perfectamente válido, porque entiende los mismos mensajes. Pero yo explicité el tipo de unGuerrero, y debe ser Guerrero.

+ +

En cambio, si en vez de definir el tipo de forma nominal como lo teníamos, y lo hacemos estructural:

+ +
type Atacable = {
+  def potencialDefensivo : Int  // metodo abstracto
+  def perderEnergia(a:Int) : Unit // metodo abstracto
+  }
+  
+def atacarA(unAtacable : Atacable) = {
+  ....
+  }
+
+ +

Lo que hicimos en este caso, fue decir que atacarA recibe a cualquier cosa que entienda esos dos métodos con esa firma. +Ahora, puede aceptar tanto Muralla como Guerrero.

+ +

Cambiemos la forma de hacer esto:

+ +
abstract class Defensor = {
+  def potencialDefensivo : Int  // metodo abstracto
+  def perderEnergia(a:Int) : Unit // metodo abstracto
+  var energia = 100
+  }
+
+class Muralla(altura: Int) extends Defensor {
+  def potencialDefensivo = altura  * 10
+  // val energia = 1000 no puedo hacer esto porque me falta un **override**
+  // override val energia = 1000 tampoco, porque energia tiene solo un getter y no un setter,
+  //y en la clase abstracta tenia un var, que tiene getter y setter.
+  // var energia = 1000 tampoco, porque estaría pisando la variable energia con otra variable energia.
+  energia = 1000
+  }
+
+ +

Al extender una clase, tengo que pasarle tambien los parametros a la clase de la cual está extendiendo. +La linearización va de derecha a izquierda, teniendo menos prioridad la superclase siempre porque esa clase podria tener superclases, muchos mixins.

+ +
extends Klass with Mixin1 with Mixin2 with Mixin3
+
+ +

En este caso, tiene más prioridad el Mixin3 y menor prioridad su superclase Klass.

+ +
  def leerDeLaBase(q: String): Any = {
+  //....
+  new Guerrero(3,13)
+  
+  }
+  // no podemos hacer  val atila = leerDeLaBase("asdasd"), sino:
+  val atila = leerDeLaBase("asdasd").asInstanceOf[Guerrero]
+
+ +

Aunque lo que estamos devolviendo sabemos que es un Guerrero, como no le explicitamos que era uno, sino que era un Any +esto no compila. Hay que castearlo. Ahora puedo tratar al guerrero como tal. En cambio, si:

+ +
  def leerDeLaBase(q: String): Any = {
+  //....
+  new Guerrero(3,13)
+  "asdasd"
+  }
+  // lo de abajo rompe
+  val atila = leerDeLaBase("asdasd").asInstanceOf[Guerrero]
+
+ +

Y está bien que rompa. Porque no hay ningún lugar en el cual compartan un tipo o se pueda convertir un String a un Guerrero.

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_8/index.html b/scripts/clase_8/index.html new file mode 100644 index 0000000..74a9da0 --- /dev/null +++ b/scripts/clase_8/index.html @@ -0,0 +1,535 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 8 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + Type Arguments y Varianza + + +

+ +

+ + + Introducción + + +

+ +

Arrancamos con un repaso básico de tipos.

+ +

+ +
var animal: Animal = ???
+var vaca: Vaca = ???
+
+animal.come
+vaca.ordeñate
+
+ +

Tengo eso. Con que se puede inicializar?

+ +
var animal: Animal = new Vaca // Ok! Una vaca es un animal
+var vaca: Vaca = new Animal   // No! Un animal no es necesariamente una vaca
+
+animal.come
+vaca.ordeñate
+
+

+ + + Type Arguments + + +

+ + +

Hasta acá el tipado cierra. Llevemoslo un paso más mostrando colecciones. Armemos un conjunto de animales y tratemos de filtrar los que están gordos.

+ +
var miColeccion: Set = Set(new Vaca, new Caballo, new Granero)
+
+miColeccion.filter{unElemento =>
+  unElemento.estaGordo // Cómo sé si los elementos entienden esto?
+}
+
+ +

Vemos que en este ejemplo no alcanza con decir que algo es una colección: Me va a importar también el tipo de las cosas que contiene.

+ +

De hecho, si recordamos el tipo de filter en Haskell era:

+ +
filter::[a]->(a->Bool)->[a]
+
+ +

Es la misma operación la que me pide saber que tipo de elementos tiene. De acá se deduce que cualquier tipo podría requerir de un “subtipado”, dependiendo de los mensajes que queremos que entienda.

+ +

Scala me deja definir tipos que llevan parámetros (otros tipos) para poder contemplar estos escenarios.

+ +

Agregando esto, mi código quedaría:

+ +
var animales: Set[Animal] = Set(new Vaca, new Oveja, new Granero) // no me deja pasar granero porque no es un animal
+
+animales.filter{ unElemento =>
+    unElemento.estaGordo
+}
+
+ +

El típo del mensaje filter va a ser muy parecido al de Haskell:

+ +
#Set[A] >> filter(criterio: A=>Bool):Set[A]
+
+

+ + + Ejercicio - Parte 1 + + +

+ +

Tomemos el modelo presentado y pensemos cómo hacer para agregar:

+ +
    +
  • Corral: Un corral es un lugar donde se acomodan varios animales de la misma especie.
  • +
  • Pastores: Un pastor puede, al recibir la orden, llevar a pastar a un conjunto de animales.
  • +
  • Lecheros: Los lecheros, cuando se les pide, ordeñan a todas las vacas de un corral.
  • +
+ +

Si programamos todo bien, el siguiente programa debería andar:

+ +
val corralito = ???
+val lechero = ???
+val pastor = ???
+
+pastor.pastorear(corralito.animales)
+lechero.ordeñar(corralito)
+
+ +

Una primer implementación posible podría ser la siguiente:

+ +
class Lechero {
+  def ordeñar(corral: Corral) = corral.animales.foreach(_.ordeñate)
+}
+	
+class Pastor {
+  def pastorear(animales: Set[Animal]) = animales.foreach(_.come)
+}
+
+class Corral(val animales: Set[Animal])
+
+val corralito = new Corral(Set(new Vaca, new Vaca, new Vaca))
+val lechero = new Lechero
+val pastor = new Pastor
+
+pastor.pastorear(corralito.animales) // Todo en orden!
+lechero.ordeñar(corralito)           // Nop. Animal no entiende ordeñate
+
+ +

Ufa… El pastor funciona, pero el lechero tiene un problema de tipos. Nada le asegura que los animales que hay en el corral sean vacas, así que no se anima a ordeñar. ¿Cómo se puede hacer para que esto tipe?

+ +

Después de laburarlo un poco y meterle type arguments podemos llegar a algo así:

+ +
class Lechero {
+  def ordeñar(corral: Corral[Vaca]) = corral.animales.foreach(_.ordeñate)
+}
+	
+class Pastor {
+  def pastorear(animales: Set[Animal]) = animales.foreach(_.come)
+}
+
+class Corral[T](val animales: Set[T])
+
+val corralito = new Corral(Set(new Vaca, new Vaca, new Vaca))
+val lechero = new Lechero
+val pastor = new Pastor
+
+pastor.pastorear(corralito.animales) // Ups… Esto se rompio??? PORQUÉ???
+lechero.ordeñar(corralito)           // Ahora sí! Esto anda!
+
+ +

Ok, el type argument en el Corral permite que el lechero sepa qué bicho está ordeñando! Incluso podemos hacer que el corral SOLO acepte animales haciendo:

+ +
class Corral[T <: Animal](val animales: Set[T])
+
+ +

Pero porqué ya no anda el pastor? Cómo que un Set de vacas no es un Set de animales???

+

+ + + Varianza + + +

+ +

Para entender el problema, simplifiquemos la situación y pensemos en los tipos…

+ +
var vacas: Set[Vaca] = ???
+var animales: Set[Animal] = ???
+
+animales.foreach{ animal => animal.come }
+vacas.foreach{ vaca => vaca.ordeñate }
+
+ +

Entonces, vale inicializarlo con esto?

+ +
var vacas: Set[Vaca] = new Set[Vaca]()
+var animales: Set[Animal] = new Set[Vaca]()
+
+animales.foreach{ animal => animal.come }
+vacas.foreach{ vaca => vaca.ordeñate }
+
+ +

En principio podría parecer que sí, pero vamos a ver que no es tan simple…

+ +

Qué pasa si cambio el código de esta forma:

+ +
var vacas: Set[Vaca] = new Set[Vaca]()
+var animales: Set[Animal] = vacas
+
+animales.add(new Caballo) // Opa! Un caballo es un animal, así que esto vale
+vacas.foreach{ vaca => vaca.ordeñate } //Eh… No.
+
+ +

Entonces la respuesta es NO. Un Set[Vaca] no es un Set[Animales].

+ +

La forma en la que varía el subtipado de un tipo compuesto en relación a sus parámetros de tipo se denomina Varianza.

+ +

En el caso de Set[T], el tipo no acepta nada que no sea el T exacto declarado. Esta situación se denomina Invarianza.


+ +

Veamos un ejemplo de algo parecido:

+ +
var f : Vaca => Vaca
+
+def g(vaca: Vaca): Vaca =  // Recibe una Vaca y devuelve una Vaca
+def h(vaca: Vaca): Animal =  // Devuelve un Animal
+def i(vaca: Vaca): VacaLoca =  // Devuelve una VacaLoca
+def j(vacaLoca: VacaLoca): Vaca =  // Recibe una VacaLoca
+def k(animal: Animal): Vaca =  // Recibe un Animal
+
+f = ???
+
+f(new Vaca).ordeñate
+
+ +

Cuales de las funciones definidas podrían guardarse en f ?

+ +
f = g  // Ok. Recibo una vaca y devuelvo una vaca. No problem.
+f = h  // No! Si h devuelve un animal no puedo garantizar que entienda muji!
+f = i  // Esto vale. La VacaLoca es una vaca y la puedo usar tal.
+f = j  // No! j espera una VacaLoca, no puedo decir que espera sólo una vaca.
+       // Si le paso una vaca a f y adentro le manda reite() se rompería!
+f = k  // Si! k sólo pide que su parámetro sea un Animal y le habla como tal.
+       // Entonces puedo pasarle una Vaca, que es un Animal.
+
+ +

En el caso de las funciones, el parámetro de tipo asociado al retorno varía en el mismo sentido que la jerarquía (o sea, admite casos más ESPECIFICOS del tipo que tiene declarado en el parámetro). A esto le decimos ser Covariante.

+ +

Por otro lado, los tipos de sus parámetros varían en el sentido opuesto (admite casos más GENÉRICOS). Son Contravariante.

+ +

En Scala eso se hace con una anotación en el tipo:

+ +
class Function1[-P,+R]{ // Clase de las funciones de un parámetro.
+                        // El - adelante de P indica que es CONTRAVARIANTE.
+                        // El + adelante de R indica que es COVARIANTE.
+
+}
+
+ +

También se puede decir que un parámetro sea covariante o contravariante a partir de cierto punto de la jerarquía de tipos.

+ +
class Foo[+T <: Vaca, -R >: Animal] {
+    // T es COVARIANTE para los subtipos de Vaca.
+    // Puedo pasarle una Vaca o una Vaca loca, pero no un Animal.
+    // R es CONTRAVARIANTE para los supertipos de Animal.
+    // Puedo pasarle, por ejemplo, un Object.
+
+}
+
+

+ + + Ejemplo de contravarianza + + +

+ +

Creemos una clase abstracta Printer que define un método print que imprime por la consola un objeto del tipo T:

+
abstract class Printer[-T] {
+  def print(t: T): Unit
+}
+
+// Sabe imprimir animales
+class AnimalPrinter extends Printer[Animal] {
+    override def print(t: Animal): Unit = {
+      println(s"Este animal pesa: ${t.peso}")
+    }
+}
+
+// Sabe imprimir vacas locas
+class VacaLocaPrinter extends Printer[VacaLoca] {
+    override def print(t: VacaLoca): Unit = {
+      println(s"Una vaca loca se ríe así: ${t.reite}")
+    }
+}
+
+ +

Veamos ejemplos de uso

+ +
var printer: Printer[VacaLoca] = new VacaLocaPrinter
+printer.print(new VacaLoca) // imprime: "Una vaca loca se ríe así: Muajajajjaajja"
+
+ +

Y como Printer es contravariante con respecto a su parámetro de tipo T, podemos guardar un objeto de tipo Printer[Animal] en una variable de tipo Printer[VacaLoca]

+
printer = new AnimalPrinter
+printer.print(new VacaLoca) // imprime: "Este animal pesa: 100"
+
+

Esto es correcto porque print de AnimalPrinter espera un parámetro de tipo Animal (puede imprimir cualquier animal), y la variable printer solo acepta VacaLoca, que entiende todos los mensajes de Animal.


+

+ + + Ejercicio - Parte 2 + + +

+ +

Volvamos a pensar el problema que teníamos con las herramientas nuevas que aprendimos.

+ +

El problema del pastor era que él sabía trabajar con una colección de Animales, pero el corral tenía una colección de Vacas. Ahora entendemos el hay un problema con la varianza.

+ +

Sin embargo, para la mayoría de los casos pareciera que una colección de vacas podría ser tratada como una colección de animales… Sería copado que las colecciones fueran COVARIANTES, no? Así el pastor podría trabajar sin problemas con las vacas del corral.

+ +

Por supuesto, como señalamos antes, el problema de las colecciones pasa por los mensajes que trata de exponer. Si fuera covariante corremos el riesgo de que alguien agregue un objeto que rompa su contrato. Uhm… Y si trabajaramos con colecciones que no pueden romperse? Bueno, entonces no habría problemas. Pero cómo creo una colección que no se pueda romper? Lo que hay que hacer es renunciar a todos los mensajes problemáticos!

+ +

Resulta que si le quito los métodos que reciben por parámetro el tipo paramétrico, la colección podría definirse como covariante. (En realidad es más complejo que eso y depende de en DONDE se está referenciando al tipo paramétrico, pero bleh… Si quieren el detalle lean.)

+ +

La clase List es una colección que está implementada para ser Covariante. Eso significa que List[Vaca] ES List[Animal].

+ +

List no tiene add(), hay que usarla como las listas de Haskell, construyendo otra. De hecho, no tiene ningún efecto de lado: Es Inmutable.

+ +

Entonces el código podría quedar así:

+ +
class Lechero {
+  def ordeñar(corral: Corral[Vaca]) = corral.animales.foreach(_.ordeñate)
+}
+	
+class Pastor {
+  def pastorear(animales: List[Animal]) = animales.foreach(_.come)
+}
+
+class Corral[T <: Animal](val animales: List[T])
+
+val corralito = new Corral(List(new Vaca, new Vaca, new Vaca))
+val lechero = new Lechero
+val pastor = new Pastor
+
+pastor.pastorear(corralito.animales) // Ahora sí! Esto anda!
+lechero.ordeñar(corralito)           // Esto también! Yupi!
+
+ +

Ojo! Que la lista sea inmutable no significa que el corral tenga que serlo. De hecho, alcanzaría con cambiar el val por un var.

+ +

Elegir en donde tener efecto colateral y en donde no es una decisión de diseño REEEE importante.

+

+ + + Covarianza + + +

+ +

Aprovechando las nuevas herramientas, podemos definir que los corrales de vacas sean subclases de los corrales de animales, usando covarianza:

+
class Corral[+T <: Animal](val animales: List[T])
+
+// Ahora podemos hacer esto:
+val corralDeVacas: Corral[Vaca] = new Corral(new Vaca, new Vaca, new VacaLoca)
+var corralDeAnimales: Corral[Animal] = corralDeVacas // Ahora es válido
+
+ +

La covarianza no viene gratis, una de las grandes limitantes es que no podemos definir un método que reciba T, porque T es covariante y el parámetro de una función es una posición contravariante:

+
class Corral[+T <: Animal](val animales: List[T]) {
+  def contiene(t: T): Boolean = ??? // covariant type T occurs in contravariant position in type T of value t
+}
+
+ +

Lo que podemos hacer para mitigar esto es usar lower bounds:

+ +
class Corral[+T <: Animal](val animales: List[T]) {
+  // [T1 >: T] significa "T1 tiene que ser una superclase de T"
+  def contiene[T1 >: T](t: T1): Boolean = ??? // covariant type T occurs in contravariant position in type T of value t
+}
+
+ +

Esto nos permite evitar en tiempo de compilación que se pueda llamar con cualquier objeto (que es imposible que sea contenido por el corral):

+ +
corralDeVacas.contiene[Int](123) // type arguments [Int] do not conform to class Corral's type parameter bounds [+T <: granja.Animal]
+
+// La desventaja es que también se puede
+corralDeVacas.contiene[AnyRef]("Vaca") // Funciona porque AnyRef es una superclase de Vaca
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_9/index.html b/scripts/clase_9/index.html new file mode 100644 index 0000000..44483b6 --- /dev/null +++ b/scripts/clase_9/index.html @@ -0,0 +1,279 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase 9 TADP 1C2016 +
+
+
+
+
+ + +
+
+
+

+ + + TADP - 2015 C2 - Clase 09 - Intro Hibrido Objeto/Funcional + + +

+ +

+ + + Intro + + +

+ + +

Empezamos recordando los conceptos que consideramos más importantes del paradigma de objetos: Encapsulamiento, Delegación y Polimorfismo (ad-hoc). Pero porqué son importantes? Qué nos dan? Hablamos de como la combinación de estas ideas hace que sea fácil agregar nuevas entidades para extender operaciones existentes, pero puede dificultar agregar nuevas operaciones (especialmente cuando no hay un único claro responsable de implementarla).

+ +

En el paradigma funcional donde las estructuras de datos están separadas de las operaciones estos conceptos parecen tener poco o ningún sentido, y son mayormente reemplazados por nociones como Transparencia Referencial (que implica falta de efecto colateral y, por lo tanto, inmutabilidad y conlleva la escencia de funcional: pensar los programas como transformaciones entre dominios), Polimorfismo Paramétrico y Orden Superior (termino que usamos de forma laxa, para referirnos al uso de las operaciones como individuos de primer orden).

+ +

Es fácil ver que estas ideas se contraponen: El Polimorfismo Paramétrico depende de conocer desde la operación la forma de los datos, lo cual choca con la idea de Encapsulamiento. Separar los datos de las operaciones favorece el orden superior, pero hace imposible delegar en los datos y compromete el polimorfismo ad-hoc; sin embargo también hace más sencillo agregar nuevas operaciones, aunque se complica agregar nuevas estructuras.

+ +

Visto así, no parece buena idea mezclar estos dos mundos, hasta que empezamos a entender que el Paradigma de Objetos no siempre es del todo feliz usando Encapsulamiento, Delegación y Polimorfismo (ad-hoc)

+

+ + + Visitor + + +

+ + +

Entra a la cancha el patrón visitor[1], el cual podemos leer del libro de Gamma. La idea es notar que este patrón, visto bajo la luz correcta, pareciera ir en contra de los conceptos principales de objetos.

+ +

Por supuesto el patrón no propone abiertamente romper estas 3 ideas, sino que las usa de tal modo que el resultado final es el opuesto al que normalmente estas buscan:

+ +

Los objetos “visitados”, en lugar de implementar su própia lógica, implementan una interfaz (más bien anémica) que permite navegarlos. Los objetos “visitantes” representan una operación que normalmente sería parte de la interfaz de los visitados, quedando fuertemente acoplados a estos, pero permitiendo agregar nuevas operaciones de forma sencilla. Eso hace que los objetos visitados no puedan ser tratados polimorficamente con otros objetos que implementen dichas operaciones, dado que no exponen en su interfaz mensajes para las mismas (-Polimorfismo-); la lógica de negocio no está definida en los objetos que representan la estructura, donde normalmente estarían (-Delegación-) y su estructura queda abierta a los visitadores, que dependen de esto para poder recorrerlos (-encapsulamiento-).

+ +

El resultado final tiende a exhibir las ventajas y facilidades de extensión de la aproximación funcional (en lugar de la de objetos), aunque con el costo de ser bastante burocrático (no es raro encontrar implementaciones con multiple dispatches y código difícil de seguir).

+ +

Esto no quiere decir que el patrón esté mal. Al contrario, la recurrencia del problema que inspiró el patrón es un indicio de que, a veces, Polimorfismo, Delegación y Encapsulamiento pueden no ser la mejor opción.

+ +

Para ilustrar este punto podemos ver el siguiente ejercicio, cuyo enunciado pueden encontrar acá, en el cual se presenta para el mismo problema una implementación “pura” y una basada en el patrón visitor:

+ +

repo objetos-puro

+ +

Se puede apreciar que la solución con el visitor desacopla la lógica (operaciones como ejecutar un programa, imprimirlo o simplificarlo) de la estructura (las instrucciones del lenguaje), con lo cual es posible agregar nuevas operaciones relativamente fácil sin tener que cambiar en absoluto las instrucciones. Esto es especialmente conveniente, dado que, por la naturaleza de este problema puntual, es más probable que haya que cambiar o agregar operaciones que instrucciones.

+ +

El problema de este enfoque es que se ve muy verboso, la navegación y delegación son considerablemente más complicados y hay muchas operaciones que se vuelven más complejas por tener que adecuarse al contrato de navegación propuesto por el visitor (ej: simplify). Es dificil mejorar estos aspectos estando atados a las reglas de Objetos, pero qué tal si en lugar de tratar de usar los conceptos de objetos para obtener las ventajas de funcional rompemos el paradigma e introducimos herramientas nuevas?

+ +

De esto se va a tratar lo que queda del cuatrimestre. Vamos a tratar de tomar el mundo de objetos y el de funcional y combinarlos de forma consistente, tratando de desarrollar un criterio sobre cuando conviene usar herramientas de uno o el otro y porqué.

+ +

Empezamos entonces por introducir de a poco conceptos de funcional para tratar de mejorar nuestra solución, a ver donde nos lleva…

+

+ + + Pattern Matching + + +

+ + +

Qué es pattern matching? Podemos quedarnos con la intuición de que se trata de elegir una pieza de código para ejecutar en base a la “forma” de un valor. Trasladado al paradigma de objetos, esto va a implicar decidir desde afuera del mismo qué hacer basandonos en su clase y estado interno, en lugar de delegar la decisión en él. En objetos esto es una mala práctica (de hecho el antipatrón tiene un nombre y todo: “Switch Statement”) pero en funcional es lo más natural del mundo.

+ +

Si prestamos atención, podemos ver que el Visitor termina haciendo esto también (sólo porque lo hace a través de un ida y vuelta de mensajes en lugar de un type check no cambia el hecho de que el resultado final termina produciendo las mismas consecuencias que el antipatrón) con el agregado de que el código está disperso y es más difícil de seguir.

+ +

Probamos entonces una nueva solución que reemplaza el double-dispatch del Visitor por Pattern Matching:

+ +

repo funcional-mutable

+ +

Aprovechamos para contar cómo trabaja Scala y sus case-classes.

+ +

En esta nueva implementación no sólo las instrucciones son más concisas sino que, al estar implementadas desde afuera de los objetos involucrados y no tener que preocuparse por encontrar un lugar en el algoritmo de recorrido predefinido, muchas de las operaciones terminan resolviendose mucho más fácil (ej. simplify).

+ +

Tiene sentido… Si lo que buscabamos para este caso eran las ventajas de extensión que del Polimorfismo Paramétrico, porqué usar un ida y vuelta de mensajes basado en Polimorfismo ad-hoc? Pare de sufrir!

+

+ + + Transparencia Referencial + + +

+ + +

Se dice que una expresión posee Transparencia Referencial cuando su evaluación puede ser reemplazada por su resultado sin que dicho cambio altere el programa. Esto, que a primera vista podría parecer una característica poco relevante, encierra en cierto modo la escencia de la filosofía que mueve al paradigma funcional. La construcción principal de este paradigma es, por supuesto, la Función: una relación unidireccional entre un dominio y una imagen. Las funciones no cambian estados ni producen efectos, solamente conectan puntos. Programar en funcional implica pensar en el programa como un viaje entre un dominio y una imagen: “Estoy acá y quiero llegar allá. Qué pasos doy?”.

+ +

Una vez que uno le toma la mano a este enfoque es sorprendentemente sencillo, no tanto porque aporte alguna construcción nueva, sino por todos los problemas que No tiene: No hay que preocuparse de mantener la consistencia de un estado, porque no hay estado. No hay que preocuparse por problemas de concurrencia porque todo lo que necesito para trabajar son los parámetros que recibo y no puedo modificarlos, solo analizarlos para llegar a la imagen que busco. Los algoritmos se vuelven más sencillos simplemente porque tengo menos componentes con los cuales construirlos y toda operación puede ser llamada en cualquier momento desde cualquier lugar con la certeza de que no puede cambiar al programa.

+ +

Si volvemos a mirar el código de nuestra solución actual y la comparamos con la versión anterior, podemos ver que algunas operaciones se simplificaron significativamente al reemplazar su implementación de un Visitor a una función (el simplify, por ejemplo, ya no necesita mantener un estado complejo, lo cual hace que el código sea mucho más fácil de mantener).

+ +

Sin embargo, especialmente en la operación run, todavía dependemos fuertemente de producir cambios de estados. Imaginemos que queremos debuguear el código del simplify: Podríamos empezar a ejecutarlo, poner breakpoints, evaluar la siguiente expresión o el siguiente paso para ver que retorna o volver a ejecutar un paso que ya dimos para entender porqué dio cierto resultado. Incluso podríamos “deshacer” la ejecución dropeando frames del stack de ejecución sin que eso comprometa la integridad del programa.

+ +

Podríamos hacer lo mismo con el run? Y… No. Cada paso de ejecución modifica el estado interno del Micro (e incluso algunos pasos podrían lanzar una excepción), con lo cual, si durante un debugue ejecutamos de nuevo el paso anterior estamos potencialmente comprometiendo el estado de ejecución, lo cual nos obliga a empezar todo de nuevo.

+ +

En el siguiente ejemplo nos paramos en el código anterior para ir gradualmente eliminando el uso de efecto colateral y mutabilidad y acercarnos de a poco a la Transparencia Referencial.

+ +

repo funcional-inmutable

+ +

Una consecuencia feliz de esta nueva aproximación es que ahora que nuestra ejecución es una función (o sea, va de un dominio a una imagen) su tipo es mucho más representativo de lo que hace:

+ +
  run(m: Micro, p: Program): Result // Describe muy claramente lo que pasa, porque pasa poco y nada. Solamente va de un micro y un programa a un resultado.
+  run(m: Micro, p: Program): Unit   // No termina de contar para qué sirve, ni qué cambia de cuál parámetro
+
+ +

La clase que viene vamos a partir de esta solución y, luego de aprender algunas cosas nuevas, vamos a tratar de mejorarla un poco más utilizando Orden Superior y abstracciones de más alto nivel.

+ +

[1] SourceMaking: Visitor

+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/clase_lenses_bonus/index.html b/scripts/clase_lenses_bonus/index.html new file mode 100644 index 0000000..6f0eb3e --- /dev/null +++ b/scripts/clase_lenses_bonus/index.html @@ -0,0 +1,837 @@ + + + + + + + + + + + TADP + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

TADP

+
+ Script Clase Bonus TADP 1C2020 +
+
+
+
+
+ + +
+
+
+

+ + + Lenses en Haskell y Scala + + +

+ +

+ + + Inmutabilidad + + +

+ + +

Uno de los rasgos más distintivos de la programación funcional es la inmutabilidad, aunque bien existen muchos lenguajes funcionales mutables (Clojure por ejemplo). La inmutabilidad está buena por varias razones:

+ +
    +
  • Código más fácil sobre el cual razonar
  • +
  • Como no tengo estado compartido, no tengo problemas de concurrencia
  • +
  • Permite realizar optimizaciones reutilizando operaciones y otras más.
  • +
+ +

Pero esta inmutabilidad es a su vez otro problema: los datos no cambian. Como los datos no cambian, para emular el efecto, tenemos que construir datos nuevos a partir de los que tenemos. Y esto puede rápidamente volverse un poco tedioso si tenemos que cambiar un valor en una estructura anidada (y ni hablar si esa estructura está en una lista).

+

+ + + Un caso práctico + + +

+ + +

Estamos en el mundo de terraria! Tenemos personajes, que tienen un nombre y obviamente llevan ropa. La ropa está conformada por la prenda superior, la prenda inferior, el calzado y un sombrero. Además, los personajes son re cancheros y usan gafas, ya sea unos lentes de sol para tirar facha o unos anteojos con aumento para ver mejor. +Las gafas tienen un armazón de oro, plata o hierro; un par de lentes y una decoración que puede ser de rubí, zafiro o esmeralda (quién te conoce pokemon). Los lentes a su vez se componen por su graduación y por tener (un número positivo) el material con el que están hecho: vidrio, cristal o polímero. +Queremos cambiar la graduación de los lentes de un personaje.

+ +
data Personaje = Personaje {
+  nombre :: String,
+  gafas :: Gafas,
+  ropa :: Ropa
+}
+
+data Ropa = Ropa {
+  superior :: String,
+  inferior :: String,
+  calzado :: String,
+  sombrero :: String
+}
+
+data Gafas = Gafas {
+  armazon :: Armazon,
+  lentes :: Lentes,
+  decoracion :: Decoracion
+}
+
+data Lentes = Lente {
+  material :: Material,
+  graduacion :: Double
+}
+
+data Armazon = Hierro | Oro | Plata
+data Material = Cristal | Vidrio | Polimero
+data Decoracion = Rubi | Zafiro | Esmeralda
+
+ +

Tenemos que ir de lo más específico a lo más general: empezamos generando un setter inmutable para la graduación de los lentes, luego un setter para los lentes de las gafas y luego un setter de gafas para los personajes:

+ +
setGraduacion :: Double -> Lentes -> Lentes
+setGraduacion graduacion l = l { graduacion = graduacion }
+
+setLentes :: Lentes -> Gafas -> Gafas
+setLentes lentes g = g { lentes = lentes }
+
+setGafas :: Gafas -> Personaje -> Personaje
+setGafas gafas p = p { gafas = gafas }
+
+setGraduacionGafas :: Double -> Gafas -> Gafas
+setGraduacionGafas graduacion g = g { lentes = setGraduacion graduacion . lentes $ g }
+
+cambiarGraduacion :: Double -> Personaje -> Personaje
+cambiarGraduacion graduacion p = p { gafas = setGraduacionGafas graduacion . gafas $ p }
+
+ +

Y… es bastante feo. Tuvimos que definir 5 funciones, las dos últimas con nombres bastantes verbosos y encima son un poco complejas de seguir. Y por cualquier otro campo que queramos actualizar, tenemos que hacer lo mismo. +Y para agregar sal en la herida: si no se dieron cuenta, estamos repitiendo lógica. en setGraduacionPersonaje y setGraduacionGafas estamos haciendo algo muy similar en ambas:

+ +
    +
  • Reciben el valor anidado a actualizar.
  • +
  • Reciben la estructura “base”.
  • +
  • Acceden un valor de la estructura.
  • +
  • Modifican este valor extraído.
  • +
  • Guardan el valor actualizado en la estructura original.
  • +
+ +

Entonces, con un poco de sopa, decidimos abstraer esta lógica repetida en una función:

+ +
type Setter a b = (b -> b) -> a -> a
+
+setter :: (a -> b) -> (a -> b -> a) -> Setter a b
+setter accessor modify update a = modify a . update . accessor $ a
+
+ +

Uff. Bueno, eso es duro de leer. Expliquemos por partes:

+
    +
  • accessor es la función que extrae el estado anidado de la estructura.
  • +
  • update es la función que, sobre el valor que retorna modify, lo aplica sobre la estructura inicial.
  • +
  • modify es la función que actualiza el valor extraído.
  • +
  • a es la estructura original.
  • +
+ +

Definamos cambiarGraduacion en base a esta nueva función:

+ +
setGraduacion :: Setter Lentes Double
+setGraduacion = 
+  setter graduacion (\lentes graduacion -> lentes { graduacion = graduacion })
+
+
+setLentes :: Setter Gafas Lentes
+setLentes = setter lentes (\gafas lentes -> gafas { lentes = lentes })
+
+setGafas :: Setter Personaje Gafas
+setGafas = setter gafas (\personaje gafas -> personaje { gafas = gafas })
+
+setGraduacionPersonaje :: Setter Personaje Double
+setGraduacionPersonaje = setGafas . setLentes . setGraduacion
+
+cambiarGraduacion :: Double -> Personaje -> Personaje
+cambiarGraduacion nuevaGraduacion = setGraduacionPersonaje (const nuevaGraduacion)
+
+ +

donde

+ +
const _ b = b
+
+ +

Tenemos definidos los tres setters en cada nivel de la estructura. Para definir un setter desde una capa más arriba, podemos componer ambos setters. Reemplacemos las firmas por el type original de Setter:

+ +
setGraduacion :: (Double -> Double) -> Lentes -> Lentes
+setLentes :: (Lentes -> Lentes) -> Gafas -> Gafas
+setGafas :: (Gafas -> Gafas) -> Personaje -> Personaje
+
+ +

El primer parámetro que recibe cambiarGraduacion es una función (Double -> Double), que nos lo provee const (nuevoValor). Y por currificación, las firmas anteriores las podemos reescribir de la siguiente manera:

+ +
setGraduacion :: (Double -> Double) -> (Lentes -> Lentes)
+setLentes :: (Lentes -> Lentes) -> (Gafas -> Gafas)
+setGafas :: (Gafas -> Gafas) -> (Personaje -> Personaje)
+
+ +

Ahora es mucho más evidente por qué puedo componer estas funciones.

+ +

Y podríamos terminar acá, regodearnos en la gloria de semejante descubrimiento, pero no inventamos nada, porque este concepto ya existe, conocido como lenses.

+

+ + + Lenses + + +

+ + +

No inventamos nada, este concepto es el de lenses. Existen varias bibliotecas de optics varios, pero nos vamos a centrar en la original: lens. El principal problema que solucionan las lenses es esto que acabamos de ver: setters anidados. En su mínima expresión, los lenses son getters y setters en funcional. El tipo de Lens se define de la siguiente manera:

+ +
type Lens a b = Monad m => (b -> m b) -> (a -> m a) 
+
+ +

Este es un tipo simplificado, no es la definición original, no es Haskell válido

+ +

Esto no es para nada trivial, y tampoco pretendemos que puedan escribir la definición de la función (ah, pero podríamos hacer un desafío de café con leche para esto). Es parecida a la firma de setter que armamos más arriba, pero es más genérica que nuestra definición. Para lo que nosotros queremos, podemos quedarnos con los mismos tipos que los que usamos para Setter:

+ +
type Lens a b = (b -> b) -> a -> a
+lens :: (a -> b) -> (a -> b -> a) -> (b -> b) -> a -> a --ey, esto es una Lens!
+lens :: (a -> b) -> (a -> b -> a) -> Lens a b
+
+

+ + + Setters + + +

+ + +

Por última vez, definamos cambiarGraduacion con las funciones de la biblioteca:

+ +
cambiarGraduacion :: Double -> Personaje -> Personaje
+cambiarGraduacion nuevaGraduacion = 
+  over (gafas . lentes . graduacion) (const nuevaGraduacion)
+
+over :: Lens a b -> (b -> b) -> a -> a
+over unaLente f a = unaLente f a
+
+ +

over recibe una Lens a b, un mapeo (b -> b) y una estructura a, y aplica el mapeo sobre el valor b dentro de a. De hecho, este mapeo de pisar el valor original por uno nuevo es muy común, por lo que hay una función ya definida que hace esto mismo: set

+ +
set :: Lens a b -> b -> a -> a
+
+cambiarGraduacion :: Double -> Personaje -> Personaje
+cambiarGraduacion = set (gafas . lentes . graduacion)
+
+

+ + + Getters + + +

+ + +

Si bien en Haskell no tenemos el mismo problema con los getters, es un poco anti-intuitivo, teniendo que leer de derecha a izquierda el orden de anidamiento. Por ejemplo, para obtener la graduación de un personaje, deberíamos escribirlo así:

+ +
graduacionPersonaje :: Personaje -> Double
+graduacionPersonaje = graduacion . lentes . gafas
+
+ +

Y bien podríamos acostumbrarnos, quedarnos con esto and call it a day. Pero lenses no hace eso. Como tenemos los lenses autogenerados, y se componen en el orden inverso, para obtener un valor de una Lens, existe la función view que hace esto mismo:

+ +
view :: Lens a b -> a -> b
+
+graduacionPersonaje :: Personaje -> Double
+graduacionPersonaje = view (gafas . lentes . graduacion)
+
+

+ + + Lenses en Scala + + +

+ + +

Bien, volvamos un poco al mismo caso del ejemplo anterior de Terraria, solamente que ahora queremos migrarlo a Scala. veamos como quedan alguna de las funciones de setear las lentes o las graduaciones de las gafas.

+ +
case class Personaje(nombre: String, gafas: Gafas, ropa: Ropa)
+case class Ropa(superior: String, inferior: String, calzado: String, sombrero: String)
+
+case class Gafas(armazon: Armazon, lentes: Lentes, decoracion: Decoracion) {
+  def setLentes(lente: Lentes): Gafas = this.copy(lentes= lente)
+  
+  def setGraduacionGafas(graduacion: Double) = this.copy(
+    lentes= lentes.copy(
+      graduacion= graduacion
+    )
+  )
+}
+
+case class Lentes(material: Material, graduacion: Double) {
+  def setGraduacion(graduacion: Double): Lentes = this.copy(graduacion= graduacion)
+}
+
+trait Armazon
+case object Hierro extends Armazon
+case object Oro extends Armazon
+case object Plata extends Armazon
+
+trait Material
+case object Cristal extends Material
+case object Vidrio extends Material
+case object Polimero extends Material
+
+trait Decoracion
+case object Rubi extends Decoracion
+case object Zafiro extends Decoracion
+case object Esmeralda extends Decoracion
+
+ +

setLentes y setGraduacion por ejemplo no parecen nada del otro mundo solamente un copy que nos devolverá una nueva instancia de lentes o de gafas.

+ +

Pero veamos que si empezamos a tener que definir funciones que cambien la graduación pero de las gafas, como por ejemplo

+ +
def setGraduacionDeLasGafas(gafas: Gafas,graduacion: Double): Gafas = gafas.copy(
+    lentes= gafas.lentes.copy(
+      graduacion= graduacion
+    )
+  )
+
+ +

vemos que no es tan problemático, pero si empezamos a tener mayores niveles de anidamiento, como el de la siguiente función

+ +
def setGraduacionDeLasGafasDeUnPersonaje(personaje: Personaje,graduacion: Double) = personaje.copy(
+    gafas= personaje.gafas.copy(
+      lentes= personaje.gafas.lentes.copy(
+        graduacion= graduacion
+      )
+    )
+  )
+
+ +

empezamos a ver que se empieza a perder un poco la expresividad y la claridad de la sintaxis.

+

+ + + Lentes + + +

+ + +

Podemos introducir una primera intuición de lentes de la siguiente manera cumpliendo siempre que

+ +
+

Una lente es una referencia de primera clase a una subparte de un data type una case class

+
+ +

Veamos de definir un poco a lo que necesitamos para definir mínimamente a un lens

+ +
case class Lens[O, V](
+  get: O => V,
+  set: (O, V) => O
+)
+
+ +

ok, esto parecería la mínima expresión a la que podríamos llegar, entonces veamos de aplicarlo sobre nuestras estructuras para cambiar u obtener valores

+ +

De tener Lentes como

+ +
case class Lentes(material: Material, graduacion: Double) {
+  def setGraduacion(graduacion: Double): Lentes = this.copy(graduacion= graduacion)
+}
+
+

vamos a crear Lenses para la graduación de un

+ +
trait TerrariaLenses {
+  protected val graduacionLens = Lens[Lentes, Double](
+    get = _.graduacion,
+    set = (o, v) => o.copy(graduacion = v)
+  )
+  
+  protected val lentesGafasLens = Lens[Gafas, Lentes](
+    get = _.lentes,
+    set = (o, v) => o.copy(lentes = v)
+  )}
+
+

ahora al utilizarlo:

+ +
 val l = Lentes(Cristal, 2.0)
+ val graduacion = graduacionLens.get(l)
+ val new_l: Lentes= graduacionLens.set(l, 3)
+
+ +

vemos que tenemos nuestro primer lens, no parece nada novedoso o superador a lo que teniamos con las case clases. Veamos el ejemplo de ajustar la graduación de las lentes de una gafa

+ +
  protected val graduacionGafasLens = Lens[Gafas, Double](
+    get = _.lentes.graduacion,
+    set = (o, v) => o.copy(lentes = o.lentes.copy(graduacion= v))
+  )
+
+ +

D’oh!… Otra vez el tema de los copy que empiezo a tener anidados… pero a no desesperarse, podemos tratar de usar algo como composición entre lenses y tratar de generalizarlo. Veamos..

+ +
object Lens {
+  def compose[Outer, Inner, Value](
+outer: Lens[Outer, Inner],
+inner: Lens[Inner, Value]) = Lens[Outer, Value](
+    get = outer.get andThen inner.get,
+    set = (obj, value) => outer.set(obj, inner.set(outer.get(obj), value))
+  )
+}
+
+

Bien una vez que tenemos la composicion podemos reutilizar las otras dos lenses que teniamos antes en realidad, graduacionLens y lentesGafasLens

+ +
  protected val graduacionGafasLens: Lens[Gafas, Double] =
+    Lens.compose(lentesGafasLens, graduacionLens)
+
+ +

Genial! ahora podemos componer medianamente facil y podemos imaginar a estos lenses como una especie de instancia de una funcion.

+ +

Entonces podemos decir que de alguna manera..

+ +
+

Lens[ A , B ] ~ A => B (aclaracion: no es una afirmación 100% exacta)

+
+ +

y si tenemos otro lens

+ +
+

Lens[ B , C ] ~ B => C

+
+ +

si estos se componen tenemos algo como

+ +
+

Lens[A, C] ~ A=>C.

+
+ +

Veamos que existen otras leyes que se cumplen además de la que vimos de transitividad.

+

+ + + Propiedades de los Lenses + + +

+ +

+ + + Identidad + + +

+ + +

Si hacemos un get y después seteamos el valor del get, el objeto que da igual (si lo se.. algo obvio)

+ +
  def identity[S, A](lens: Lens[S, A], s: S): Boolean =
+    lens.set(s, lens.get(s)) == s
+
+

+ + + Retención + + +

+ + +

Esto es un poco el caso contrario, si a un tipo S le seteamos el valor a, y después sobre esto hacemos un get debería retener el valor y devolvernos a

+ +
 def retention[S, A](lens: Lens[S, A], s: S, a: A): Boolean =
+    lens.get(lens.set(s, a)) == a
+
+

+ + + Doble set + + +

+ + +

si se setean dos veces un valor y después se hace un get, se obtiene el valor anterior

+ +
  def doubleSet[S, A](lens: Lens[S, A], s: S, a: A, b: A): Boolean =
+    lens.get(lens.set(lens.set(s, a), b)) == b
+
+ +

En suma nuestra mini implementación (algo naive) de lenses en Scala queda como

+ +
case class Lens[O, V](
+                       get: O => V,
+                       set: (O, V) => O
+                     )
+object Lens {
+  def compose[Outer, Inner, Value](
+            outer: Lens[Outer, Inner],
+            inner: Lens[Inner, Value]
+  ) = Lens[Outer, Value](
+    get = outer.get andThen inner.get,
+    set = (obj, value) => outer.set(obj, inner.set(outer.get(obj), value))
+  )
+
+  def identity[S, A](lens: Lens[S, A], s: S): Boolean =
+    lens.set(s, lens.get(s)) == s
+
+  def retention[S, A](lens: Lens[S, A], s: S, a: A): Boolean =
+    lens.get(lens.set(s, a)) == a
+
+  def doubleSet[S, A](lens: Lens[S, A], s: S, a: A, b: A): Boolean =
+    lens.get(lens.set(lens.set(s, a), b)) == b
+}
+
+ +

Los lenses son estructuras referenciales del paradigma funcional, pero no son la única estructura, existen una estructura generalizada de estos que se llaman optics. Y ahora estaremos utilizando una librería que ya los implementa de manera más seria llamada Monocle

+

+ + + Monocle en Scala + + +

+ + +

Tal como lo define la librería monocle, los optics son abstracciones y estructuras que nos permiten trabajar con objetos inmutables:

+ +
+

Optics are a group of purely functional abstractions to manipulate (get, set, modify, …) immutable objects.

+
+ +

Con monocle, podemos tambien definir lenses de manera bastante similar a nuestra intuicion

+ +
  import monocle.Lens
+  val gafas = Lens[Personaje, Gafas](_.gafas)(g => p => p.copy(gafas = g))
+
+ +

Aunque tambien podemos utilizar una macro GenLens para no tener que repetir a cada rato codigo que es bastante similar por cada lens que tenemos:

+ +
import monocle.Lens
+import monocle.macros.GenLens
+val gafas: Lens[Personaje, Gafas] = GenLens[Personaje] (_.gafas)
+
+

con lo cual podemos definir los lenses de una manera bastante simple ahora….

+ +
object TerrariaLenses {
+  val gafas: Lens[Personaje, Gafas] = GenLens[Personaje] (_.gafas)
+  val lentes: Lens[Gafas, Lentes] = GenLens[Gafas] (_.lentes)
+  val graduacion: Lens[Lentes, Double] = GenLens[Lentes] (_.graduacion)
+  val material: Lens[Lentes, Material] = GenLens[Lentes] (_.material)
+}
+
+ +

Otra cosa que podemos agregar ahora a los lenses es el de primero hacer get y después set con modify

+ +
val readingLens: Lentes = Lentes(Cristal, 2.0)
+
+val adjustedLens = graduacion.modify(_ + 1.1)(readingLens) # Lentes(Cristal, 3.1)
+
+ +

con lo cual ahora podemos incluso usar el composeLens si queremos acceder de manera anidada a las estructuras internas de un personaje

+ +
val readingLens: Lentes = Lentes(Cristal, 2.0)
+val gafasViejo = Gafas(Plata, readingLens, Rubi)
+val cloud = Personaje("Cloud", gafasViejo, Ropa("", "", "", ""))
+
+(gafas composeLens lentes).get(cloud) 
+
+(gafas composeLens lentes composeLens graduacion).get(cloud)
+
+ +

y si quiero modificar a los lentes de nuestro personaje y devolverlo podemos hacer algo como:

+ +
val cloud = Personaje("Cloud", gafasViejo, Ropa("", "", "", ""))
+  => Personaje(Cloud,Gafas(Plata,Lentes(Cristal,2.0),Rubi),Ropa(,,,))
+
+(gafas composeLens lentes composeLens graduacion).modify(_ + 1.1)(cloud) => Personaje(Cloud,Gafas(Plata,Lentes(Cristal,3.1),Rubi),Ropa(,,,))
+
+ +

incluso se puede mejorar esto con la sintaxis que tiene monocle de lens

+ +
import monocle.macros.syntax.lens._
+
+cloud.lens(_.gafas.lentes.graduacion).modify(_ + 1.1)  => Personaje(Cloud,Gafas(Plata,Lentes(Cristal,3.1),Rubi),Ropa(,,,))
+
+ +

Para la generacion incluso podemos ir un poco mas alla de lo que teniamos con GenLens… ahora podemos agregar la annotation @Lenses y nos generara los lenses para cada uno de los atributos de clase, entonces nuestro ejemplo seria ahora:

+ +
@Lenses case class Lentes(material: Material, graduacion: Double) {
+  def setGraduacion(graduacion: Double) = this.copy(graduacion= graduacion)
+}
+
+@Lenses case class Gafas(armazon: Armazon, lentes: Lentes, decoracion: Decoracion) {
+  def setLentes(lente: Lentes) = this.copy(lentes= lente)
+  def setGraduacionGafas(graduacion: Double) = this.copy(
+    lentes= lentes.copy(
+      graduacion= graduacion
+    )
+  )
+}
+
+@Lenses case class Personaje(nombre: String, gafas: Gafas, ropa: Ropa)
+
+ +

y ahora nisiquiera necesitaremos crear nosotros los lenses:

+ +
val readingLens: Lentes = Lentes(Cristal, 2.0)
+val adjustedLens = Lentes.graduacion.modify(_ + 1.1)(readingLens)
+
+(Personaje.gafas composeLens Gafas.lentes).get(cloud) => Lentes(Cristal,2.0)
+
+(Personaje.gafas composeLens Gafas.lentes composeLens Lentes.graduacion).modify(_ + 1.1)(cloud) => Personaje(Cloud,Gafas(Plata,Lentes(Cristal,3.1),Rubi),Ropa(,,,))
+
+ +

Con lo cual nuestro ejemplo de terraria quedaria finalmente como:

+ +
import monocle.macros.syntax.lens._
+import monocle.macros.Lenses
+
+trait Armazon
+case object Hierro extends Armazon
+case object Oro extends Armazon
+case object Plata extends Armazon
+
+trait Material
+case object Cristal extends Material
+case object Vidrio extends Material
+case object Polimero extends Material
+
+trait Decoracion
+case object Rubi extends Decoracion
+case object Zafiro extends Decoracion
+case object Esmeralda extends Decoracion
+
+case class Ropa(superior: String, inferior: String, calzado: String, sombrero: String)
+
+@Lenses case class Lentes(material: Material, graduacion: Double) {
+  def setGraduacion(graduacion: Double) = this.copy(graduacion= graduacion)
+}
+
+@Lenses case class Gafas(armazon: Armazon, lentes: Lentes, decoracion: Decoracion) {
+  def setLentes(lente: Lentes) = this.copy(lentes= lente)
+  def setGraduacionGafas(graduacion: Double) = this.copy(
+    lentes= lentes.copy(
+      graduacion= graduacion
+    )
+  )
+}
+
+@Lenses case class Personaje(nombre: String, gafas: Gafas, ropa: Ropa)
+
+ +

También monocle cumple con varias de las leyes de lenses que vimos antes y mas (shamelessly taken from https://github.com/optics-dev/Monocle/blob/385085a24ec2561d0892a99ef37a51ba2ea43402/core/shared/src/main/scala/monocle/law/LensLaws.scala)

+ +
import monocle.Lens
+import monocle.internal.IsEq
+
+import cats.data.Const
+import cats.Id
+
+case class LensLaws[S, A](lens: Lens[S, A]) {
+  import IsEq.syntax
+
+  def getSet(s: S): IsEq[S] =
+    lens.set(lens.get(s))(s) <==> s
+
+  def setGet(s: S, a: A): IsEq[A] =
+    lens.get(lens.set(a)(s)) <==> a
+
+  def setIdempotent(s: S, a: A): IsEq[S] =
+    lens.set(a)(lens.set(a)(s)) <==> lens.set(a)(s)
+
+  def modifyIdentity(s: S): IsEq[S] =
+    lens.modify(identity)(s) <==> s
+
+  def composeModify(s: S, f: A => A, g: A => A): IsEq[S] =
+    lens.modify(g)(lens.modify(f)(s)) <==> lens.modify(g compose f)(s)
+
+  def consistentSetModify(s: S, a: A): IsEq[S] =
+    lens.set(a)(s) <==> lens.modify(_ => a)(s)
+
+  def consistentModifyModifyId(s: S, f: A => A): IsEq[S] =
+    lens.modify(f)(s) <==> lens.modifyF[Id](f)(s)
+      def consistentGetModifyId(s: S): IsEq[A] =
+    lens.get(s) <==> lens.modifyF[Const[A, ?]](Const(_))(s).getConst
+}
+
+

+ + + Conclusión + + +

+ + +

Vimos que la inmutabilidad está buena. Pero también vimos que tiene algunos problemas no triviales de resolver. El concepto de lenses resuelva esta situación particular. No es necesario entender los tipos complejos que ofrecen las bibliotecas, alcanza con entender cómo crearlos y cómo aplicarlas. +Hay mucho más allá afuera sobre lenses, optics, prisms, antiparras y demás. Pero para esta clase no nos interesa mucho. Lo importante es saber que este problema existe, es muy común, y ya hay algo que resuelve este problema (y no es dejar de usar Haskell y pasar a un lenguaje mutable).

+

+ + + Lecturas recomendadas + + +

+ + + +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/granja.png b/scripts/granja.png new file mode 100644 index 0000000..00e8921 Binary files /dev/null and b/scripts/granja.png differ diff --git a/scripts/image_0.png b/scripts/image_0.png new file mode 100644 index 0000000..f349b13 Binary files /dev/null and b/scripts/image_0.png differ diff --git a/scripts/image_1.png b/scripts/image_1.png new file mode 100644 index 0000000..5a0d8aa Binary files /dev/null and b/scripts/image_1.png differ