diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index cd128fc..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index ba0accc..0000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "app/bower_components" -} diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index c2cdfb8..0000000 --- a/.editorconfig +++ /dev/null @@ -1,21 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# editorconfig.org - -root = true - - -[*] - -# Change these settings to your own preference -indent_style = space -indent_size = 2 - -# We recommend you to keep these unchanged -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 2125666..0000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7911b28..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -dist -.tmp -.sass-cache -app/bower_components diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 40377ba..0000000 --- a/.jshintrc +++ /dev/null @@ -1,24 +0,0 @@ -{ - "node": true, - "browser": true, - "esnext": true, - "bitwise": true, - "camelcase": true, - "curly": true, - "eqeqeq": true, - "immed": true, - "indent": 2, - "latedef": true, - "newcap": true, - "noarg": true, - "quotmark": "single", - "regexp": true, - "undef": true, - "unused": true, - "strict": true, - "trailing": true, - "smarttabs": true, - "globals": { - "angular": false - } -} diff --git a/404.html b/404.html new file mode 100644 index 0000000..ec98e3c --- /dev/null +++ b/404.html @@ -0,0 +1,157 @@ + + + + + Page Not Found :( + + + +
+

Not found :(

+

Sorry, but the page you were trying to view does not exist.

+

It looks like this was the result of either:

+ + + +
+ + diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index f3b6e79..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,368 +0,0 @@ -// Generated on 2014-01-09 using generator-angular 0.5.1 -'use strict'; - -// # Globbing -// for performance reasons we're only matching one level down: -// 'test/spec/{,*/}*.js' -// use this if you want to recursively match all subfolders: -// 'test/spec/**/*.js' - -module.exports = function (grunt) { - require('load-grunt-tasks')(grunt); - require('time-grunt')(grunt); - - grunt.initConfig({ - yeoman: { - // configurable paths - app: require('./bower.json').appPath || 'app', - dist: 'dist' - }, - watch: { - coffee: { - files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'], - tasks: ['coffee:dist'] - }, - coffeeTest: { - files: ['test/spec/{,*/}*.coffee'], - tasks: ['coffee:test'] - }, - compass: { - files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], - tasks: ['compass:server', 'autoprefixer'] - }, - styles: { - files: ['<%= yeoman.app %>/styles/{,*/}*.css'], - tasks: ['copy:styles', 'autoprefixer'] - }, - livereload: { - options: { - livereload: '<%= connect.options.livereload %>' - }, - files: [ - '<%= yeoman.app %>/{,*/}*.html', - '.tmp/styles/{,*/}*.css', - '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js', - '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' - ] - } - }, - autoprefixer: { - options: ['last 1 version'], - dist: { - files: [{ - expand: true, - cwd: '.tmp/styles/', - src: '{,*/}*.css', - dest: '.tmp/styles/' - }] - } - }, - connect: { - options: { - port: 9000, - // Change this to '0.0.0.0' to access the server from outside. - hostname: 'localhost', - livereload: 35729 - }, - livereload: { - options: { - open: true, - base: [ - '.tmp', - '<%= yeoman.app %>' - ] - } - }, - test: { - options: { - port: 9001, - base: [ - '.tmp', - 'test', - '<%= yeoman.app %>' - ] - } - }, - dist: { - options: { - base: '<%= yeoman.dist %>' - } - } - }, - clean: { - dist: { - files: [{ - dot: true, - src: [ - '.tmp', - '<%= yeoman.dist %>/*', - '!<%= yeoman.dist %>/.git*' - ] - }] - }, - server: '.tmp' - }, - jshint: { - options: { - jshintrc: '.jshintrc' - }, - all: [ - 'Gruntfile.js', - '<%= yeoman.app %>/scripts/{,*/}*.js' - ] - }, - coffee: { - options: { - sourceMap: true, - sourceRoot: '' - }, - dist: { - files: [{ - expand: true, - cwd: '<%= yeoman.app %>/scripts', - src: '{,*/}*.coffee', - dest: '.tmp/scripts', - ext: '.js' - }] - }, - test: { - files: [{ - expand: true, - cwd: 'test/spec', - src: '{,*/}*.coffee', - dest: '.tmp/spec', - ext: '.js' - }] - } - }, - compass: { - options: { - sassDir: '<%= yeoman.app %>/styles', - cssDir: '.tmp/styles', - generatedImagesDir: '.tmp/images/generated', - imagesDir: '<%= yeoman.app %>/images', - javascriptsDir: '<%= yeoman.app %>/scripts', - fontsDir: '<%= yeoman.app %>/styles/fonts', - importPath: '<%= yeoman.app %>/bower_components', - httpImagesPath: '/images', - httpGeneratedImagesPath: '/images/generated', - httpFontsPath: '/styles/fonts', - relativeAssets: false - }, - dist: {}, - server: { - options: { - debugInfo: true - } - } - }, - // not used since Uglify task does concat, - // but still available if needed - /*concat: { - dist: {} - },*/ - rev: { - dist: { - files: { - src: [ - '<%= yeoman.dist %>/scripts/{,*/}*.js', - '<%= yeoman.dist %>/styles/{,*/}*.css', - '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', - '<%= yeoman.dist %>/styles/fonts/*' - ] - } - } - }, - useminPrepare: { - html: '<%= yeoman.app %>/index.html', - options: { - dest: '<%= yeoman.dist %>' - } - }, - usemin: { - html: ['<%= yeoman.dist %>/{,*/}*.html'], - css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], - options: { - dirs: ['<%= yeoman.dist %>'] - } - }, - imagemin: { - dist: { - files: [{ - expand: true, - cwd: '<%= yeoman.app %>/images', - src: '{,*/}*.{png,jpg,jpeg}', - dest: '<%= yeoman.dist %>/images' - }] - } - }, - svgmin: { - dist: { - files: [{ - expand: true, - cwd: '<%= yeoman.app %>/images', - src: '{,*/}*.svg', - dest: '<%= yeoman.dist %>/images' - }] - } - }, - cssmin: { - // By default, your `index.html` will take care of - // minification. This option is pre-configured if you do not wish to use - // Usemin blocks. - // dist: { - // files: { - // '<%= yeoman.dist %>/styles/main.css': [ - // '.tmp/styles/{,*/}*.css', - // '<%= yeoman.app %>/styles/{,*/}*.css' - // ] - // } - // } - }, - htmlmin: { - dist: { - options: { - /*removeCommentsFromCDATA: true, - // https://github.com/yeoman/grunt-usemin/issues/44 - //collapseWhitespace: true, - collapseBooleanAttributes: true, - removeAttributeQuotes: true, - removeRedundantAttributes: true, - useShortDoctype: true, - removeEmptyAttributes: true, - removeOptionalTags: true*/ - }, - files: [{ - expand: true, - cwd: '<%= yeoman.app %>', - src: ['*.html', 'views/*.html', 'views/*'], - dest: '<%= yeoman.dist %>' - }] - } - }, - // Put files not handled in other tasks here - copy: { - dist: { - files: [{ - expand: true, - dot: true, - cwd: '<%= yeoman.app %>', - dest: '<%= yeoman.dist %>', - src: [ - '*.{ico,png,txt}', - '.htaccess', - 'bower_components/**/*', - 'images/{,*/}*.{gif,webp}', - 'styles/fonts/*' - ] - }, { - expand: true, - cwd: '.tmp/images', - dest: '<%= yeoman.dist %>/images', - src: [ - 'generated/*' - ] - }] - }, - styles: { - expand: true, - cwd: '<%= yeoman.app %>/styles', - dest: '.tmp/styles/', - src: '{,*/}*.css' - } - }, - concurrent: { - server: [ - 'coffee:dist', - 'compass:server', - 'copy:styles' - ], - test: [ - 'coffee', - 'compass', - 'copy:styles' - ], - dist: [ - 'coffee', - 'compass:dist', - 'copy:styles', - 'imagemin', - 'svgmin', - 'htmlmin' - ] - }, - karma: { - unit: { - configFile: 'karma.conf.js', - singleRun: true - } - }, - cdnify: { - dist: { - html: ['<%= yeoman.dist %>/*.html'] - } - }, - ngmin: { - dist: { - files: [{ - expand: true, - cwd: '<%= yeoman.dist %>/scripts', - src: '*.js', - dest: '<%= yeoman.dist %>/scripts' - }] - } - }, - uglify: { - dist: { - files: { - '<%= yeoman.dist %>/scripts/scripts.js': [ - '<%= yeoman.dist %>/scripts/scripts.js' - ] - } - } - } - }); - - grunt.registerTask('server', function (target) { - if (target === 'dist') { - return grunt.task.run(['build', 'connect:dist:keepalive']); - } - - grunt.task.run([ - 'clean:server', - 'concurrent:server', - 'autoprefixer', - 'connect:livereload', - 'watch' - ]); - }); - - grunt.registerTask('test', [ - 'clean:server', - 'concurrent:test', - 'autoprefixer', - 'connect:test', - 'karma' - ]); - - grunt.registerTask('build', [ - 'clean:dist', - 'useminPrepare', - 'concurrent:dist', - 'autoprefixer', - 'concat', - 'copy:dist', - 'cdnify', - 'ngmin', - 'cssmin', - 'uglify', - 'rev', - 'usemin' - ]); - - grunt.registerTask('default', [ - 'jshint', - 'test', - 'build' - ]); -}; diff --git a/LICENSE b/LICENSE deleted file mode 100644 index e06d208..0000000 --- a/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ -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 {yyyy} {name of copyright owner} - - 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/README.md b/README.md deleted file mode 100644 index dd74c7d..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -website -======= - -The official website not for teams diff --git a/app/.DS_Store b/app/.DS_Store deleted file mode 100644 index 1d96335..0000000 Binary files a/app/.DS_Store and /dev/null differ diff --git a/app/.buildignore b/app/.buildignore deleted file mode 100644 index fc98b8e..0000000 --- a/app/.buildignore +++ /dev/null @@ -1 +0,0 @@ -*.coffee \ No newline at end of file diff --git a/app/.htaccess b/app/.htaccess deleted file mode 100644 index cb84cb9..0000000 --- a/app/.htaccess +++ /dev/null @@ -1,543 +0,0 @@ -# Apache Configuration File - -# (!) Using `.htaccess` files slows down Apache, therefore, if you have access -# to the main server config file (usually called `httpd.conf`), you should add -# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. - -# ############################################################################## -# # CROSS-ORIGIN RESOURCE SHARING (CORS) # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Cross-domain AJAX requests | -# ------------------------------------------------------------------------------ - -# Enable cross-origin AJAX requests. -# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity -# http://enable-cors.org/ - -# -# Header set Access-Control-Allow-Origin "*" -# - -# ------------------------------------------------------------------------------ -# | CORS-enabled images | -# ------------------------------------------------------------------------------ - -# Send the CORS header for images when browsers request it. -# https://developer.mozilla.org/en/CORS_Enabled_Image -# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html -# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ - - - - - SetEnvIf Origin ":" IS_CORS - Header set Access-Control-Allow-Origin "*" env=IS_CORS - - - - -# ------------------------------------------------------------------------------ -# | Web fonts access | -# ------------------------------------------------------------------------------ - -# Allow access from all domains for web fonts - - - - Header set Access-Control-Allow-Origin "*" - - - - -# ############################################################################## -# # ERRORS # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | 404 error prevention for non-existing redirected folders | -# ------------------------------------------------------------------------------ - -# Prevent Apache from returning a 404 error for a rewrite if a directory -# with the same name does not exist. -# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews -# http://www.webmasterworld.com/apache/3808792.htm - -Options -MultiViews - -# ------------------------------------------------------------------------------ -# | Custom error messages / pages | -# ------------------------------------------------------------------------------ - -# You can customize what Apache returns to the client in case of an error (see -# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: - -ErrorDocument 404 /404.html - - -# ############################################################################## -# # INTERNET EXPLORER # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Better website experience | -# ------------------------------------------------------------------------------ - -# Force IE to render pages in the highest available mode in the various -# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. - - - Header set X-UA-Compatible "IE=edge" - # `mod_headers` can't match based on the content-type, however, we only - # want to send this header for HTML pages and not for the other resources - - Header unset X-UA-Compatible - - - -# ------------------------------------------------------------------------------ -# | Cookie setting from iframes | -# ------------------------------------------------------------------------------ - -# Allow cookies to be set from iframes in IE. - -# -# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" -# - -# ------------------------------------------------------------------------------ -# | Screen flicker | -# ------------------------------------------------------------------------------ - -# Stop screen flicker in IE on CSS rollovers (this only works in -# combination with the `ExpiresByType` directives for images from below). - -# BrowserMatch "MSIE" brokenvary=1 -# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 -# BrowserMatch "Opera" !brokenvary -# SetEnvIf brokenvary 1 force-no-vary - - -# ############################################################################## -# # MIME TYPES AND ENCODING # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Proper MIME types for all files | -# ------------------------------------------------------------------------------ - - - - # Audio - AddType audio/mp4 m4a f4a f4b - AddType audio/ogg oga ogg - - # JavaScript - # Normalize to standard type (it's sniffed in IE anyways): - # http://tools.ietf.org/html/rfc4329#section-7.2 - AddType application/javascript js jsonp - AddType application/json json - - # Video - AddType video/mp4 mp4 m4v f4v f4p - AddType video/ogg ogv - AddType video/webm webm - AddType video/x-flv flv - - # Web fonts - AddType application/font-woff woff - AddType application/vnd.ms-fontobject eot - - # Browsers usually ignore the font MIME types and sniff the content, - # however, Chrome shows a warning if other MIME types are used for the - # following fonts. - AddType application/x-font-ttf ttc ttf - AddType font/opentype otf - - # Make SVGZ fonts work on iPad: - # https://twitter.com/FontSquirrel/status/14855840545 - AddType image/svg+xml svg svgz - AddEncoding gzip svgz - - # Other - AddType application/octet-stream safariextz - AddType application/x-chrome-extension crx - AddType application/x-opera-extension oex - AddType application/x-shockwave-flash swf - AddType application/x-web-app-manifest+json webapp - AddType application/x-xpinstall xpi - AddType application/xml atom rdf rss xml - AddType image/webp webp - AddType image/x-icon ico - AddType text/cache-manifest appcache manifest - AddType text/vtt vtt - AddType text/x-component htc - AddType text/x-vcard vcf - - - -# ------------------------------------------------------------------------------ -# | UTF-8 encoding | -# ------------------------------------------------------------------------------ - -# Use UTF-8 encoding for anything served as `text/html` or `text/plain`. -AddDefaultCharset utf-8 - -# Force UTF-8 for certain file formats. - - AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml - - - -# ############################################################################## -# # URL REWRITES # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Rewrite engine | -# ------------------------------------------------------------------------------ - -# Turning on the rewrite engine and enabling the `FollowSymLinks` option is -# necessary for the following directives to work. - -# If your web host doesn't allow the `FollowSymlinks` option, you may need to -# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the -# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks - -# Also, some cloud hosting services require `RewriteBase` to be set: -# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site - - - Options +FollowSymlinks - # Options +SymLinksIfOwnerMatch - RewriteEngine On - # RewriteBase / - - -# ------------------------------------------------------------------------------ -# | Suppressing / Forcing the "www." at the beginning of URLs | -# ------------------------------------------------------------------------------ - -# The same content should never be available under two different URLs especially -# not with and without "www." at the beginning. This can cause SEO problems -# (duplicate content), therefore, you should choose one of the alternatives and -# redirect the other one. - -# By default option 1 (no "www.") is activated: -# http://no-www.org/faq.php?q=class_b - -# If you'd prefer to use option 2, just comment out all the lines from option 1 -# and uncomment the ones from option 2. - -# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Option 1: rewrite www.example.com → example.com - - - RewriteCond %{HTTPS} !=on - RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] - RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Option 2: rewrite example.com → www.example.com - -# Be aware that the following might not be a good idea if you use "real" -# subdomains for certain parts of your website. - -# -# RewriteCond %{HTTPS} !=on -# RewriteCond %{HTTP_HOST} !^www\..+$ [NC] -# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] -# - - -# ############################################################################## -# # SECURITY # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Content Security Policy (CSP) | -# ------------------------------------------------------------------------------ - -# You can mitigate the risk of cross-site scripting and other content-injection -# attacks by setting a Content Security Policy which whitelists trusted sources -# of content for your site. - -# The example header below allows ONLY scripts that are loaded from the current -# site's origin (no inline scripts, no CDN, etc). This almost certainly won't -# work as-is for your site! - -# To get all the details you'll need to craft a reasonable policy for your site, -# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or -# see the specification: http://w3.org/TR/CSP). - -# -# Header set Content-Security-Policy "script-src 'self'; object-src 'self'" -# -# Header unset Content-Security-Policy -# -# - -# ------------------------------------------------------------------------------ -# | File access | -# ------------------------------------------------------------------------------ - -# Block access to directories without a default document. -# Usually you should leave this uncommented because you shouldn't allow anyone -# to surf through every directory on your server (which may includes rather -# private places like the CMS's directories). - - - Options -Indexes - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Block access to hidden files and directories. -# This includes directories used by version control systems such as Git and SVN. - - - RewriteCond %{SCRIPT_FILENAME} -d [OR] - RewriteCond %{SCRIPT_FILENAME} -f - RewriteRule "(^|/)\." - [F] - - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Block access to backup and source files. -# These files may be left by some text editors and can pose a great security -# danger when anyone has access to them. - - - Order allow,deny - Deny from all - Satisfy All - - -# ------------------------------------------------------------------------------ -# | Secure Sockets Layer (SSL) | -# ------------------------------------------------------------------------------ - -# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: -# prevent `https://www.example.com` when your certificate only allows -# `https://secure.example.com`. - -# -# RewriteCond %{SERVER_PORT} !^443 -# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] -# - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Force client-side SSL redirection. - -# If a user types "example.com" in his browser, the above rule will redirect him -# to the secure version of the site. That still leaves a window of opportunity -# (the initial HTTP connection) for an attacker to downgrade or redirect the -# request. The following header ensures that browser will ONLY connect to your -# server via HTTPS, regardless of what the users type in the address bar. -# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ - -# -# Header set Strict-Transport-Security max-age=16070400; -# - -# ------------------------------------------------------------------------------ -# | Server software information | -# ------------------------------------------------------------------------------ - -# Avoid displaying the exact Apache version number, the description of the -# generic OS-type and the information about Apache's compiled-in modules. - -# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! - -# ServerTokens Prod - - -# ############################################################################## -# # WEB PERFORMANCE # -# ############################################################################## - -# ------------------------------------------------------------------------------ -# | Compression | -# ------------------------------------------------------------------------------ - - - - # Force compression for mangled headers. - # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping - - - SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding - RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding - - - - # Compress all output labeled with one of the following MIME-types - # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` - # and can remove the `` and `` lines - # as `AddOutputFilterByType` is still in the core directives). - - AddOutputFilterByType DEFLATE application/atom+xml \ - application/javascript \ - application/json \ - application/rss+xml \ - application/vnd.ms-fontobject \ - application/x-font-ttf \ - application/x-web-app-manifest+json \ - application/xhtml+xml \ - application/xml \ - font/opentype \ - image/svg+xml \ - image/x-icon \ - text/css \ - text/html \ - text/plain \ - text/x-component \ - text/xml - - - - -# ------------------------------------------------------------------------------ -# | Content transformations | -# ------------------------------------------------------------------------------ - -# Prevent some of the mobile network providers from modifying the content of -# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. - -# -# Header set Cache-Control "no-transform" -# - -# ------------------------------------------------------------------------------ -# | ETag removal | -# ------------------------------------------------------------------------------ - -# Since we're sending far-future expires headers (see below), ETags can -# be removed: http://developer.yahoo.com/performance/rules.html#etags. - -# `FileETag None` is not enough for every server. - - Header unset ETag - - -FileETag None - -# ------------------------------------------------------------------------------ -# | Expires headers (for better cache control) | -# ------------------------------------------------------------------------------ - -# The following expires headers are set pretty far in the future. If you don't -# control versioning with filename-based cache busting, consider lowering the -# cache time for resources like CSS and JS to something like 1 week. - - - - ExpiresActive on - ExpiresDefault "access plus 1 month" - - # CSS - ExpiresByType text/css "access plus 1 year" - - # Data interchange - ExpiresByType application/json "access plus 0 seconds" - ExpiresByType application/xml "access plus 0 seconds" - ExpiresByType text/xml "access plus 0 seconds" - - # Favicon (cannot be renamed!) - ExpiresByType image/x-icon "access plus 1 week" - - # HTML components (HTCs) - ExpiresByType text/x-component "access plus 1 month" - - # HTML - ExpiresByType text/html "access plus 0 seconds" - - # JavaScript - ExpiresByType application/javascript "access plus 1 year" - - # Manifest files - ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" - ExpiresByType text/cache-manifest "access plus 0 seconds" - - # Media - ExpiresByType audio/ogg "access plus 1 month" - ExpiresByType image/gif "access plus 1 month" - ExpiresByType image/jpeg "access plus 1 month" - ExpiresByType image/png "access plus 1 month" - ExpiresByType video/mp4 "access plus 1 month" - ExpiresByType video/ogg "access plus 1 month" - ExpiresByType video/webm "access plus 1 month" - - # Web feeds - ExpiresByType application/atom+xml "access plus 1 hour" - ExpiresByType application/rss+xml "access plus 1 hour" - - # Web fonts - ExpiresByType application/font-woff "access plus 1 month" - ExpiresByType application/vnd.ms-fontobject "access plus 1 month" - ExpiresByType application/x-font-ttf "access plus 1 month" - ExpiresByType font/opentype "access plus 1 month" - ExpiresByType image/svg+xml "access plus 1 month" - - - -# ------------------------------------------------------------------------------ -# | Filename-based cache busting | -# ------------------------------------------------------------------------------ - -# If you're not using a build process to manage your filename version revving, -# you might want to consider enabling the following directives to route all -# requests such as `/css/style.12345.css` to `/css/style.css`. - -# To understand why this is important and a better idea than `*.css?v231`, read: -# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring - -# -# RewriteCond %{REQUEST_FILENAME} !-f -# RewriteCond %{REQUEST_FILENAME} !-d -# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] -# - -# ------------------------------------------------------------------------------ -# | File concatenation | -# ------------------------------------------------------------------------------ - -# Allow concatenation from within specific CSS and JS files, e.g.: -# Inside of `script.combined.js` you could have -# -# -# and they would be included into this single file. - -# -# -# Options +Includes -# AddOutputFilterByType INCLUDES application/javascript application/json -# SetOutputFilter INCLUDES -# -# -# Options +Includes -# AddOutputFilterByType INCLUDES text/css -# SetOutputFilter INCLUDES -# -# - -# ------------------------------------------------------------------------------ -# | Persistent connections | -# ------------------------------------------------------------------------------ - -# Allow multiple requests to be sent over the same TCP connection: -# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. - -# Enable if you serve a lot of static content but, be aware of the -# possible disadvantages! - -# -# Header set Connection Keep-Alive -# diff --git a/app/404.html b/app/404.html deleted file mode 100644 index fdace4a..0000000 --- a/app/404.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - Page Not Found :( - - - -
-

Not found :(

-

Sorry, but the page you were trying to view does not exist.

-

It looks like this was the result of either:

- - - -
- - diff --git a/app/favicon.ico b/app/favicon.ico deleted file mode 100644 index d16285a..0000000 Binary files a/app/favicon.ico and /dev/null differ diff --git a/app/index.html b/app/index.html deleted file mode 100644 index 2807d9c..0000000 --- a/app/index.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - Tooski.ch - C'est too ski vous faut ! - - - - - - - - - - - - - -
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/robots.txt b/app/robots.txt deleted file mode 100644 index 9417495..0000000 --- a/app/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# robotstxt.org - -User-agent: * diff --git a/app/scripts/app.js b/app/scripts/app.js deleted file mode 100644 index c42da56..0000000 --- a/app/scripts/app.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -angular.module('websiteApp', [ - 'ngCookies', - 'ngResource', - 'ngSanitize', - 'ui.bootstrap', - 'ngDisqus', -]) - .config(function ($routeProvider, $locationProvider) { - $routeProvider - .when('/', { - templateUrl: 'views/index.html', - controller: 'MainCtrl' - }) - .when('/Mag', { - templateUrl: 'views/Mag.html', - controller: 'MagCtrl' - }) - .when('/News', { - templateUrl: 'views/news.html', - controller: 'NewsCtrl' - }) - .otherwise({ - redirectTo: '/' - }); - $locationProvider.html5Mode(false).hashPrefix('!'); - }); \ No newline at end of file diff --git a/app/scripts/controllers/Mag.js b/app/scripts/controllers/Mag.js deleted file mode 100644 index ac5b4b6..0000000 --- a/app/scripts/controllers/Mag.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .controller('MagCtrl', function ($scope) { - - }); diff --git a/app/scripts/controllers/main.js b/app/scripts/controllers/main.js deleted file mode 100644 index 13388b0..0000000 --- a/app/scripts/controllers/main.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .controller('MainCtrl', function($scope, News) { - function shuffle(o) { //v1.0 - for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); - return o; - }; - - var news = News.getNews(); - $scope.news = news; - $scope.randFirstNews = shuffle(news.slice(0, 5)); - }); \ No newline at end of file diff --git a/app/scripts/controllers/news.js b/app/scripts/controllers/news.js deleted file mode 100644 index e4a4eaf..0000000 --- a/app/scripts/controllers/news.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .controller('NewsCtrl', function($scope, $routeParams, News) { - var newsId = $routeParams.id; - $scope.news = News.getNews(newsId); - $scope.disqusId = 'News' + newsId; - }); \ No newline at end of file diff --git a/app/scripts/directives/tsComments.js b/app/scripts/directives/tsComments.js deleted file mode 100644 index b249b96..0000000 --- a/app/scripts/directives/tsComments.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .directive('tsComments', function() { - window.disqus_shortname = 'tooski'; - return { - templateUrl: 'views/directives/comments/comments.html', - restrict: 'EACM', - link: function postLink(scope, element, attrs) {} - }; - }); \ No newline at end of file diff --git a/app/scripts/directives/tsNavbar.js b/app/scripts/directives/tsNavbar.js deleted file mode 100644 index 10d7758..0000000 --- a/app/scripts/directives/tsNavbar.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .directive('tsNavbar', function () { - return { - templateUrl: 'views/directives/core/navbar.html', - restrict: 'EACM', - replace: true, - link: function postLink(scope, element, attrs) { - // TODO: When person clicks on button, then add the active class - } - }; - }); diff --git a/app/scripts/directives/tsNewsCarousel.js b/app/scripts/directives/tsNewsCarousel.js deleted file mode 100644 index 3bee505..0000000 --- a/app/scripts/directives/tsNewsCarousel.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .directive('tsNewsCarousel', function() { - return { - templateUrl: 'views/directives/news/carousel.html', - restrict: 'EACM', - scope: { - news: '=', - }, - link: function postLink(scope, element, attrs) { - scope.interval = 5000; - } - }; - }); \ No newline at end of file diff --git a/app/scripts/directives/tsNewsList.js b/app/scripts/directives/tsNewsList.js deleted file mode 100644 index a5bb382..0000000 --- a/app/scripts/directives/tsNewsList.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .directive('tsNewsList', function() { - return { - templateUrl: 'views/directives/news/newslist.html', - restrict: 'EACM', - replace: true, - scope: { - news: '=news', - }, - link: function postLink(scope, element, attrs) { - }, - }; - }); \ No newline at end of file diff --git a/app/scripts/directives/tsPubSide.js b/app/scripts/directives/tsPubSide.js deleted file mode 100644 index 58d07bf..0000000 --- a/app/scripts/directives/tsPubSide.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .directive('tsPubSide', function(Pub) { - return { - templateUrl: 'views/directives/pub/side.html', - restrict: 'EACM', - link: function postLink(scope, element, attrs) { - var verticals = Pub.getVerticalBanner(), - squares = Pub.getSquareBanner(); - scope.vertical = verticals[0]; - scope.square1 = squares[0]; - scope.square2 = squares.length >1 ? squares[1] : squares[0]; - } - }; - }); \ No newline at end of file diff --git a/app/scripts/filters/findImage.js b/app/scripts/filters/findImage.js deleted file mode 100644 index d4d497a..0000000 --- a/app/scripts/filters/findImage.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .filter('findImage', function() { - return function(input) { - var re = /(https?:\/\/.*\.(?:png|jpg|svg|jpeg))(?![>|<|'|"])/gi; - input = re.exec(input); - input = !! input ? input[0] : '/images/Sflocon_vecteur_Transparent.png'; - return input; - }; - }); \ No newline at end of file diff --git a/app/scripts/filters/truncate.js b/app/scripts/filters/truncate.js deleted file mode 100644 index e715f80..0000000 --- a/app/scripts/filters/truncate.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -angular.module('websiteApp'). - filter('truncate', function() { - return function(text, length, end) { - if (isNaN(length)) { - length = 10; - } - if (end === undefined) { - end = '...'; - } - if (text.length <= length || text.length - end.length <= length) { - return text; - } else { - return String(text).substring(0, length - end.length) + end; - } - }; -}); \ No newline at end of file diff --git a/app/scripts/services/Pub.js b/app/scripts/services/Pub.js deleted file mode 100644 index 4f1ec6a..0000000 --- a/app/scripts/services/Pub.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .factory('Pub', function() { - var banners = [{ - id: 0, - horizontal: 0, - vertical: 1, - square: 0, - img: 'http://placehold.it/350x750', - }, { - id: 0, - horizontal: 0, - vertical: 0, - square: 1, - img: 'http://placehold.it/350x350', - }, { - id: 0, - horizontal: 0, - vertical: 0, - square: 1, - img: 'http://placehold.it/750x750', - }, { - id: 0, - horizontal: 1, - vertical: 0, - square: 0, - img: 'http://placehold.it/750x350', - }, ]; - - - var shuffleArray = function shuffle(o) { //v1.0 - for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); - return o; - }; - - banners = shuffleArray(banners); - - return { - getVerticalBanner: function() { - return banners.filter(function(el) { - return el.vertical === 1; - }).reduce(function(prev, cur) { - prev.push(cur.img) - return prev; - }, []); - }, - - getSquareBanner: function() { - return banners.filter(function(el) { - return el.square === 1; - }).reduce(function(prev, cur) { - prev.push(cur.img); - return prev; - }, []); - }, - }; - }); \ No newline at end of file diff --git a/app/scripts/services/news.js b/app/scripts/services/news.js deleted file mode 100644 index 78fdcc5..0000000 --- a/app/scripts/services/news.js +++ /dev/null @@ -1,84 +0,0 @@ -'use strict'; - -angular.module('websiteApp') - .factory('News', function() { - var news = [ - { - id: 0, - author: 'Séb', - title: 'News n°0', - content: 'Mon super text, avec du html: ', - date: 1389390694, - }, - { - id: 1, - author: 'Séb', - title: 'News n°1', - content: 'Mon super text, avec du html: ', - date: 1389390695, - }, - { - id: 2, - author: 'Séb', - title: 'News n°2', - content: 'Mon super text, avec du html: ', - date: 1389390696, - }, - { - id: 0, - author: 'Séb', - title: 'News n°0', - content: 'Mon super text, avec du html: ', - date: 1389390694, - }, - { - id: 1, - author: 'Séb', - title: 'News n°1', - content: 'Mon super text, avec du html: ', - date: 1389390695, - }, - { - id: 2, - author: 'Séb', - title: 'News n°2', - content: 'Mon super text, avec du html: ', - date: 1389390696, - }, - { - id: 0, - author: 'Séb', - title: 'News n°0', - content: 'Mon super text, avec du html: ', - date: 1389390694, - }, - { - id: 1, - author: 'Séb', - title: 'News n°1', - content: 'Mon super text, avec du html: ', - date: 1389390695, - }, - { - id: 2, - author: 'Séb', - title: 'News n°2', - content: 'Mon super text, avec du html: ', - date: 1389390696, - }, - ]; - - var meaningOfLife = 42; - - // Public API here - return { - getNews: function(id) { - if (id) { - return news[id]; - } - else { - return news; - } - }, - }; - }); \ No newline at end of file diff --git a/app/styles/main.scss b/app/styles/main.scss deleted file mode 100644 index eb6446f..0000000 --- a/app/styles/main.scss +++ /dev/null @@ -1,13 +0,0 @@ -/* For testing purposes only*/ -.bordered { - border:solid black 1px; -} -/* End of testing*/ - -.white { - color:white; - text-shadow: 2px 2px #000000; -} - -/*! normalize.css v2.1.3 | MIT License | git.io/normalize */ -article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a{background:transparent}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{margin:.67em 0;font-size:2em}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}hr{height:0;-moz-box-sizing:content-box;box-sizing:content-box}mark{color:#000;background:#ff0}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid #c0c0c0}legend{padding:0;border:0}button,input,select,textarea{margin:0;font-family:inherit;font-size:100%}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{padding:0;box-sizing:border-box}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}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:2cm .5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*,*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#555;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#2fa4e7;text-decoration:none}a:hover,a:focus{color:#157ab5;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}img{vertical-align:middle}.img-responsive{display:block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-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}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1;color:#317eac}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:#999}h1,h2,h3{margin-top:20px;margin-bottom:10px}h1 small,h2 small,h3 small,h1 .small,h2 .small,h3 .small{font-size:65%}h4,h5,h6{margin-top:10px;margin-bottom:10px}h4 small,h5 small,h6 small,h4 .small,h5 .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:200;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#2fa4e7}.text-primary:hover{color:#178acc}.text-warning{color:#c09853}.text-warning:hover{color:#a47e3c}.text-danger{color:#b94a48}.text-danger:hover{color:#953b39}.text-success{color:#468847}.text-success:hover{color:#356635}.text-info{color:#3a87ad}.text-info:hover{color:#2d6987}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.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;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}.list-inline>li:first-child{padding-left:0}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.428571429}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}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}.dl-horizontal dd:before,.dl-horizontal dd:after{display:table;content:" "}.dl-horizontal dd:after{clear:both}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small,blockquote .small{display:block;line-height:1.428571429;color:#999}blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small,blockquote.pull-right .small{text-align:right}blockquote.pull-right small:before,blockquote.pull-right .small:before{content:''}blockquote.pull-right small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.428571429}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;white-space:nowrap;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;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}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}.container:before,.container:after{display:table;content:" "}.container:after{clear:both}@media(min-width:768px){.container{width:750px}}@media(min-width:992px){.container{width:970px}}@media(min-width:1200px){.container{width:1170px}}.row{margin-right:-15px;margin-left:-15px}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.row:before,.row:after{display:table;content:" "}.row:after{clear:both}.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.66666666666666%}.col-xs-10{width:83.33333333333334%}.col-xs-9{width:75%}.col-xs-8{width:66.66666666666666%}.col-xs-7{width:58.333333333333336%}.col-xs-6{width:50%}.col-xs-5{width:41.66666666666667%}.col-xs-4{width:33.33333333333333%}.col-xs-3{width:25%}.col-xs-2{width:16.666666666666664%}.col-xs-1{width:8.333333333333332%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666666666666%}.col-xs-pull-10{right:83.33333333333334%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666666666666%}.col-xs-pull-7{right:58.333333333333336%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666666666667%}.col-xs-pull-4{right:33.33333333333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.666666666666664%}.col-xs-pull-1{right:8.333333333333332%}.col-xs-pull-0{right:0}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666666666666%}.col-xs-push-10{left:83.33333333333334%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666666666666%}.col-xs-push-7{left:58.333333333333336%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666666666667%}.col-xs-push-4{left:33.33333333333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.666666666666664%}.col-xs-push-1{left:8.333333333333332%}.col-xs-push-0{left:0}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666666666666%}.col-xs-offset-10{margin-left:83.33333333333334%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666666666666%}.col-xs-offset-7{margin-left:58.333333333333336%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666666666667%}.col-xs-offset-4{margin-left:33.33333333333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.666666666666664%}.col-xs-offset-1{margin-left:8.333333333333332%}.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.66666666666666%}.col-sm-10{width:83.33333333333334%}.col-sm-9{width:75%}.col-sm-8{width:66.66666666666666%}.col-sm-7{width:58.333333333333336%}.col-sm-6{width:50%}.col-sm-5{width:41.66666666666667%}.col-sm-4{width:33.33333333333333%}.col-sm-3{width:25%}.col-sm-2{width:16.666666666666664%}.col-sm-1{width:8.333333333333332%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-0{right:0}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666666666666%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-0{left:0}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666666666666%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-1{margin-left:8.333333333333332%}.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.66666666666666%}.col-md-10{width:83.33333333333334%}.col-md-9{width:75%}.col-md-8{width:66.66666666666666%}.col-md-7{width:58.333333333333336%}.col-md-6{width:50%}.col-md-5{width:41.66666666666667%}.col-md-4{width:33.33333333333333%}.col-md-3{width:25%}.col-md-2{width:16.666666666666664%}.col-md-1{width:8.333333333333332%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666666666666%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-0{right:0}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666666666666%}.col-md-push-10{left:83.33333333333334%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666666666666%}.col-md-push-7{left:58.333333333333336%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666666666667%}.col-md-push-4{left:33.33333333333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.666666666666664%}.col-md-push-1{left:8.333333333333332%}.col-md-push-0{left:0}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666666666666%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-1{margin-left:8.333333333333332%}.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.66666666666666%}.col-lg-10{width:83.33333333333334%}.col-lg-9{width:75%}.col-lg-8{width:66.66666666666666%}.col-lg-7{width:58.333333333333336%}.col-lg-6{width:50%}.col-lg-5{width:41.66666666666667%}.col-lg-4{width:33.33333333333333%}.col-lg-3{width:25%}.col-lg-2{width:16.666666666666664%}.col-lg-1{width:8.333333333333332%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-0{right:0}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666666666666%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-0{left:0}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666666666666%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-0{margin-left:0}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{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.428571429;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-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*="col-"]{position:static;display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{display:table-cell;float:none}.table>thead>tr>.active,.table>tbody>tr>.active,.table>tfoot>tr>.active,.table>thead>.active>td,.table>tbody>.active>td,.table>tfoot>.active>td,.table>thead>.active>th,.table>tbody>.active>th,.table>tfoot>.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>.active:hover,.table-hover>tbody>.active:hover>td,.table-hover>tbody>.active:hover>th{background-color:#e8e8e8}.table>thead>tr>.success,.table>tbody>tr>.success,.table>tfoot>tr>.success,.table>thead>.success>td,.table>tbody>.success>td,.table>tfoot>.success>td,.table>thead>.success>th,.table>tbody>.success>th,.table>tfoot>.success>th{background-color:#dff0d8}.table-hover>tbody>tr>.success:hover,.table-hover>tbody>.success:hover>td,.table-hover>tbody>.success:hover>th{background-color:#d0e9c6}.table>thead>tr>.danger,.table>tbody>tr>.danger,.table>tfoot>tr>.danger,.table>thead>.danger>td,.table>tbody>.danger>td,.table>tfoot>.danger>td,.table>thead>.danger>th,.table>tbody>.danger>th,.table>tfoot>.danger>th{background-color:#f2dede}.table-hover>tbody>tr>.danger:hover,.table-hover>tbody>.danger:hover>td,.table-hover>tbody>.danger:hover>th{background-color:#ebcccc}.table>thead>tr>.warning,.table>tbody>tr>.warning,.table>tfoot>tr>.warning,.table>thead>.warning>td,.table>tbody>.warning>td,.table>tfoot>.warning>td,.table>thead>.warning>th,.table>tbody>.warning>th,.table>tfoot>.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>.warning:hover,.table-hover>tbody>.warning:hover>td,.table-hover>tbody>.warning:hover>th{background-color:#faf2cc}@media(max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:scroll;overflow-y:hidden;border:1px solid #ddd;-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-overflow-scrolling:touch}.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{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#555;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;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}select[multiple],select[size]{height:auto}select optgroup{font-family:inherit;font-size:inherit;font-style:inherit}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}input[type="number"]::-webkit-outer-spin-button,input[type="number"]::-webkit-inner-spin-button{height:auto}output{display:block;padding-top:9px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle}.form-control{display:block;width:100%;height:38px;padding:8px 12px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-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,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control:-moz-placeholder{color:#999}.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{cursor:not-allowed;background-color:#eee}textarea.form-control{height:auto}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;padding-left:20px;margin-top:10px;margin-bottom:10px;vertical-align:middle}.radio label,.checkbox label{display:inline;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"]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{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],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.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{height:auto}.input-lg{height:54px;padding:14px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:54px;line-height:54px}textarea.input-lg{height:auto}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#c09853}.has-warning .form-control{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.has-warning .input-group-addon{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#b94a48}.has-error .form-control{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.has-error .input-group-addon{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#468847}.has-success .form-control{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.has-success .input-group-addon{color:#468847;background-color:#dff0d8;border-color:#468847}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#959595}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block}.form-inline select.form-control{width:auto}.form-inline .radio,.form-inline .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:none;margin-left:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:9px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:29px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-group:before,.form-horizontal .form-group:after{display:table;content:" "}.form-horizontal .form-group:after{clear:both}.form-horizontal .form-control-static{padding-top:9px}@media(min-width:768px){.form-horizontal .control-label{text-align:right}}.btn{display:inline-block;padding:8px 12px;margin-bottom:0;font-size:14px;font-weight:normal;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#555;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#555;background-color:#fff;border-color:rgba(0,0,0,0.1)}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#555;background-color:#ebebeb;border-color:rgba(0,0,0,0.1)}.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: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:rgba(0,0,0,0.1)}.btn-default .badge{color:#fff;background-color:#fff}.btn-primary{color:#fff;background-color:#2fa4e7;border-color:#2fa4e7}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#1990d5;border-color:#1684c2}.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: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:#2fa4e7;border-color:#2fa4e7}.btn-primary .badge{color:#2fa4e7;background-color:#fff}.btn-warning{color:#fff;background-color:#dd5600;border-color:#dd5600}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#b44600;border-color:#a03e00}.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: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:#dd5600;border-color:#dd5600}.btn-warning .badge{color:#dd5600;background-color:#fff}.btn-danger{color:#fff;background-color:#c71c22;border-color:#c71c22}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#a3171c;border-color:#911419}.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: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:#c71c22;border-color:#c71c22}.btn-danger .badge{color:#c71c22;background-color:#fff}.btn-success{color:#fff;background-color:#73a839;border-color:#73a839}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#5e8a2f;border-color:#547a29}.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: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:#73a839;border-color:#73a839}.btn-success .badge{color:#73a839;background-color:#fff}.btn-info{color:#fff;background-color:#033c73;border-color:#033c73}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#02274b;border-color:#011d37}.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: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:#033c73;border-color:#033c73}.btn-info .badge{color:#033c73;background-color:#fff}.btn-link{font-weight:normal;color:#2fa4e7;cursor:pointer;border-radius:0}.btn-link,.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:#157ab5;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:#999;text-decoration:none}.btn-lg{padding:14px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0}.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;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@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.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';-webkit-font-smoothing:antialiased;font-style:normal;font-weight:normal;line-height:1;-moz-osx-font-smoothing:grayscale}.glyphicon:empty{width:1em}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.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"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.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;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);background-clip:padding-box}.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.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{color:#fff;text-decoration:none;background-color:#2fa4e7}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#2fa4e7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor: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-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.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{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}}.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:focus,.btn-group-vertical>.btn:focus{outline:0}.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:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar:before,.btn-toolbar:after{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group{float:left}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group,.btn-toolbar>.btn-group+.btn-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-bottom-left-radius:0;border-top-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>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-lg>.btn{padding:14px 16px;font-size:18px;line-height:1.33;border-radius:6px}.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,0.125);box-shadow:inset 0 3px 5px rgba(0,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:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.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-right-radius:0;border-bottom-left-radius:4px;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child>.btn:last-child,.btn-group-vertical>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;border-collapse:separate;table-layout:fixed}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}[data-toggle="buttons"]>.btn>input[type="radio"],[data-toggle="buttons"]>.btn>input[type="checkbox"]{display: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{width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:54px;padding:14px 16px;font-size:18px;line-height:1.33;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:54px;line-height:54px}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}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-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:8px 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:14px 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>.dropdown-toggle,.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:last-child>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;white-space:nowrap}.input-group-btn:first-child>.btn{margin-right:-1px}.input-group-btn:last-child>.btn{margin-left:-1px}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-4px}.input-group-btn>.btn:hover,.input-group-btn>.btn:active{z-index:2}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.nav:before,.nav:after{display:table;content:" "}.nav:after{clear:both}.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:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;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:#2fa4e7}.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.428571429;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:#2fa4e7}.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-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}.navbar:before,.navbar:after{display:table;content:" "}.navbar:after{clear:both}@media(min-width:768px){.navbar{border-radius:4px}}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}.navbar-header:before,.navbar-header:after{display:table;content:" "}.navbar-header:after{clear:both}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse:before,.navbar-collapse:after{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;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}}.container>.navbar-header,.container>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.container>.navbar-header,.container>.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;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media(min-width:768px){.navbar>.container .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 .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;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-nav.navbar-right:last-child{margin-right:-15px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:6px;margin-right:-15px;margin-bottom:6px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.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}.navbar-form select.form-control{width:auto}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{float:none;margin-left:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@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-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-nav.pull-right>li>.dropdown-menu,.navbar-nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar-btn{margin-top:6px;margin-bottom:6px}.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}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#2fa4e7;border-color:#1995dc}.navbar-default .navbar-brand{color:#fff}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#fff;background-color:none}.navbar-default .navbar-text{color:#ddd}.navbar-default .navbar-nav>li>a{color:#fff}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#fff;background-color:#178acc}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#fff;background-color:#178acc}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ddd;background-color:transparent}.navbar-default .navbar-toggle{border-color:#178acc}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#178acc}.navbar-default .navbar-toggle .icon-bar{background-color:#fff}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#1995dc}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#fff;background-color:#178acc}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#fff}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:#178acc}.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:#fff;background-color:#178acc}.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:#ddd;background-color:transparent}}.navbar-default .navbar-link{color:#fff}.navbar-default .navbar-link:hover{color:#fff}.navbar-inverse{background-color:#033c73;border-color:#022f5a}.navbar-inverse .navbar-brand{color:#fff}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:none}.navbar-inverse .navbar-text{color:#fff}.navbar-inverse .navbar-nav>li>a{color:#fff}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:#022f5a}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#022f5a}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#022f5a}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#022f5a}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#022a50}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#022f5a}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#022f5a}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#022f5a}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#fff}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:#022f5a}.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:#022f5a}.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:#ccc;background-color:transparent}}.navbar-inverse .navbar-link{color:#fff}.navbar-inverse .navbar-link:hover{color:#fff}.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:#999}.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:8px 12px;margin-left:-1px;line-height:1.428571429;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-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{background-color:#eee}.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:#999;cursor:default;background-color:#f5f5f5;border-color:#f5f5f5}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:14px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-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}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-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:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager:before,.pager:after{display:table;content:" "}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;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:#999;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}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#2fa4e7}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#178acc}.label-success{background-color:#73a839}.label-success[href]:hover,.label-success[href]:focus{background-color:#59822c}.label-info{background-color:#033c73}.label-info[href]:hover,.label-info[href]:focus{background-color:#022241}.label-warning{background-color:#dd5600}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#aa4200}.label-danger{background-color:#c71c22}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#9a161a}.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:baseline;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#2fa4e7;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;font-size:21px;font-weight:200;line-height:2.1428571435;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{line-height:1;color:inherit}.jumbotron p{line-height:1.4}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .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.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;height:auto;max-width:100%;margin-right:auto;margin-left:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#2fa4e7}.thumbnail .caption{padding:9px;color:#555}.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{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#356635}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#2d6987}.alert-warning{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.alert-warning hr{border-top-color:#f8e5be}.alert-warning .alert-link{color:#a47e3c}.alert-danger{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger hr{border-top-color:#e6c1c7}.alert-danger .alert-link{color:#953b39}@-webkit-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,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#2fa4e7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#73a839}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#033c73}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#dd5600}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#c71c22}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{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-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#fff;background-color:#2fa4e7;border-color:#2fa4e7}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#e6f4fc}.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,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel-body:before,.panel-body:after{display:table;content:" "}.panel-body:after{clear:both}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0}.panel>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.list-group .list-group-item:last-child{border-bottom:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child th,.panel>.table>tbody: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:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:last-child>th,.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,.panel>.table-bordered>thead>tr:last-child>td,.panel>.table-responsive>.table-bordered>thead>tr:last-child>td,.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{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-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{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-group .panel{margin-bottom:0;overflow:hidden;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{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:#555;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#ddd}.panel-primary>.panel-heading{color:#fff;background-color:#2fa4e7;border-color:#ddd}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-success{border-color:#ddd}.panel-success>.panel-heading{color:#468847;background-color:#73a839;border-color:#ddd}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-warning{border-color:#ddd}.panel-warning>.panel-heading{color:#c09853;background-color:#dd5600;border-color:#ddd}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-danger{border-color:#ddd}.panel-danger>.panel-heading{color:#b94a48;background-color:#c71c22;border-color:#ddd}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-info{border-color:#ddd}.panel-info>.panel-heading{color:#3a87ad;background-color:#033c73;border-color:#ddd}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.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,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-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;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:auto;overflow-y:scroll}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{position:relative;z-index:1050;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1030;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{min-height:16.428571429px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{padding:19px 20px 20px;margin-top:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer:before,.modal-footer:after{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media screen and (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}}.tooltip{position:absolute;z-index:1030;display:block;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.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;text-decoration:none;background-color:rgba(0,0,0,0.9);border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:rgba(0,0,0,0.9);border-width:5px 5px 0}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-top-color:rgba(0,0,0,0.9);border-width:5px 5px 0}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-top-color:rgba(0,0,0,0.9);border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:rgba(0,0,0,0.9);border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:rgba(0,0,0,0.9);border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:rgba(0,0,0,0.9);border-width:0 5px 5px}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-bottom-color:rgba(0,0,0,0.9);border-width:0 5px 5px}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-bottom-color:rgba(0,0,0,0.9);border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;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{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0;content:" "}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0;content:" "}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0;content:" "}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0;content:" "}.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;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;height:auto;max-width:100%;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);opacity:.5;filter:alpha(opacity=50)}.carousel-control.left{background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.5) 0),color-stop(rgba(0,0,0,0.0001) 100%));background-image:linear-gradient(to right,rgba(0,0,0,0.5) 0,rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000',endColorstr='#00000000',GradientType=1)}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,0.0001) 0),color-stop(rgba(0,0,0,0.5) 100%));background-image:linear-gradient(to right,rgba(0,0,0,0.0001) 0,rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000',endColorstr='#80000000',GradientType=1)}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;outline:0;opacity:.9;filter:alpha(opacity=90)}.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}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.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,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicons-chevron-left,.carousel-control .glyphicons-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix:after{clear:both}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix: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;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,tr.visible-xs,th.visible-xs,td.visible-xs{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-xs.visible-sm{display:block!important}table.visible-xs.visible-sm{display:table}tr.visible-xs.visible-sm{display:table-row!important}th.visible-xs.visible-sm,td.visible-xs.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-xs.visible-md{display:block!important}table.visible-xs.visible-md{display:table}tr.visible-xs.visible-md{display:table-row!important}th.visible-xs.visible-md,td.visible-xs.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-xs.visible-lg{display:block!important}table.visible-xs.visible-lg{display:table}tr.visible-xs.visible-lg{display:table-row!important}th.visible-xs.visible-lg,td.visible-xs.visible-lg{display:table-cell!important}}.visible-sm,tr.visible-sm,th.visible-sm,td.visible-sm{display:none!important}@media(max-width:767px){.visible-sm.visible-xs{display:block!important}table.visible-sm.visible-xs{display:table}tr.visible-sm.visible-xs{display:table-row!important}th.visible-sm.visible-xs,td.visible-sm.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-sm.visible-md{display:block!important}table.visible-sm.visible-md{display:table}tr.visible-sm.visible-md{display:table-row!important}th.visible-sm.visible-md,td.visible-sm.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-sm.visible-lg{display:block!important}table.visible-sm.visible-lg{display:table}tr.visible-sm.visible-lg{display:table-row!important}th.visible-sm.visible-lg,td.visible-sm.visible-lg{display:table-cell!important}}.visible-md,tr.visible-md,th.visible-md,td.visible-md{display:none!important}@media(max-width:767px){.visible-md.visible-xs{display:block!important}table.visible-md.visible-xs{display:table}tr.visible-md.visible-xs{display:table-row!important}th.visible-md.visible-xs,td.visible-md.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-md.visible-sm{display:block!important}table.visible-md.visible-sm{display:table}tr.visible-md.visible-sm{display:table-row!important}th.visible-md.visible-sm,td.visible-md.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-md.visible-lg{display:block!important}table.visible-md.visible-lg{display:table}tr.visible-md.visible-lg{display:table-row!important}th.visible-md.visible-lg,td.visible-md.visible-lg{display:table-cell!important}}.visible-lg,tr.visible-lg,th.visible-lg,td.visible-lg{display:none!important}@media(max-width:767px){.visible-lg.visible-xs{display:block!important}table.visible-lg.visible-xs{display:table}tr.visible-lg.visible-xs{display:table-row!important}th.visible-lg.visible-xs,td.visible-lg.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-lg.visible-sm{display:block!important}table.visible-lg.visible-sm{display:table}tr.visible-lg.visible-sm{display:table-row!important}th.visible-lg.visible-sm,td.visible-lg.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-lg.visible-md{display:block!important}table.visible-lg.visible-md{display:table}tr.visible-lg.visible-md{display:table-row!important}th.visible-lg.visible-md,td.visible-lg.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}.hidden-xs{display:block!important}table.hidden-xs{display:table}tr.hidden-xs{display:table-row!important}th.hidden-xs,td.hidden-xs{display:table-cell!important}@media(max-width:767px){.hidden-xs,tr.hidden-xs,th.hidden-xs,td.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-xs.hidden-sm,tr.hidden-xs.hidden-sm,th.hidden-xs.hidden-sm,td.hidden-xs.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-xs.hidden-md,tr.hidden-xs.hidden-md,th.hidden-xs.hidden-md,td.hidden-xs.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-xs.hidden-lg,tr.hidden-xs.hidden-lg,th.hidden-xs.hidden-lg,td.hidden-xs.hidden-lg{display:none!important}}.hidden-sm{display:block!important}table.hidden-sm{display:table}tr.hidden-sm{display:table-row!important}th.hidden-sm,td.hidden-sm{display:table-cell!important}@media(max-width:767px){.hidden-sm.hidden-xs,tr.hidden-sm.hidden-xs,th.hidden-sm.hidden-xs,td.hidden-sm.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm,tr.hidden-sm,th.hidden-sm,td.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-sm.hidden-md,tr.hidden-sm.hidden-md,th.hidden-sm.hidden-md,td.hidden-sm.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-sm.hidden-lg,tr.hidden-sm.hidden-lg,th.hidden-sm.hidden-lg,td.hidden-sm.hidden-lg{display:none!important}}.hidden-md{display:block!important}table.hidden-md{display:table}tr.hidden-md{display:table-row!important}th.hidden-md,td.hidden-md{display:table-cell!important}@media(max-width:767px){.hidden-md.hidden-xs,tr.hidden-md.hidden-xs,th.hidden-md.hidden-xs,td.hidden-md.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-md.hidden-sm,tr.hidden-md.hidden-sm,th.hidden-md.hidden-sm,td.hidden-md.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md,tr.hidden-md,th.hidden-md,td.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-md.hidden-lg,tr.hidden-md.hidden-lg,th.hidden-md.hidden-lg,td.hidden-md.hidden-lg{display:none!important}}.hidden-lg{display:block!important}table.hidden-lg{display:table}tr.hidden-lg{display:table-row!important}th.hidden-lg,td.hidden-lg{display:table-cell!important}@media(max-width:767px){.hidden-lg.hidden-xs,tr.hidden-lg.hidden-xs,th.hidden-lg.hidden-xs,td.hidden-lg.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-lg.hidden-sm,tr.hidden-lg.hidden-sm,th.hidden-lg.hidden-sm,td.hidden-lg.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-lg.hidden-md,tr.hidden-lg.hidden-md,th.hidden-lg.hidden-md,td.hidden-lg.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg,tr.hidden-lg,th.hidden-lg,td.hidden-lg{display:none!important}}.visible-print,tr.visible-print,th.visible-print,td.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}.hidden-print,tr.hidden-print,th.hidden-print,td.hidden-print{display:none!important}}.navbar{background-image:-webkit-linear-gradient(#54b4eb,#2fa4e7 60%,#1d9ce5);background-image:linear-gradient(#54b4eb,#2fa4e7 60%,#1d9ce5);background-repeat:no-repeat;border-bottom:1px solid #178acc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff54b4eb',endColorstr='#ff1d9ce5',GradientType=0);filter:none;-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar .navbar-nav>li>a,.navbar-brand{text-shadow:0 1px 0 rgba(0,0,0,0.1)}.navbar-inverse{background-image:-webkit-linear-gradient(#04519b,#044687 60%,#033769);background-image:linear-gradient(#04519b,#044687 60%,#033769);background-repeat:no-repeat;border-bottom:1px solid #022241;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff04519b',endColorstr='#ff033769',GradientType=0);filter:none}.btn{text-shadow:0 1px 0 rgba(0,0,0,0.1)}.btn .caret{border-top-color:#fff}.btn-default{background-image:-webkit-linear-gradient(#fff,#fff 60%,#f5f5f5);background-image:linear-gradient(#fff,#fff 60%,#f5f5f5);background-repeat:no-repeat;border-bottom:1px solid #e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff5f5f5',GradientType=0);filter:none}.btn-default:hover{color:#555}.btn-default .caret{border-top-color:#555}.btn-default{background-image:-webkit-linear-gradient(#fff,#fff 60%,#f5f5f5);background-image:linear-gradient(#fff,#fff 60%,#f5f5f5);background-repeat:no-repeat;border-bottom:1px solid #e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff5f5f5',GradientType=0);filter:none}.btn-primary{background-image:-webkit-linear-gradient(#54b4eb,#2fa4e7 60%,#1d9ce5);background-image:linear-gradient(#54b4eb,#2fa4e7 60%,#1d9ce5);background-repeat:no-repeat;border-bottom:1px solid #178acc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff54b4eb',endColorstr='#ff1d9ce5',GradientType=0);filter:none}.btn-success{background-image:-webkit-linear-gradient(#88c149,#73a839 60%,#699934);background-image:linear-gradient(#88c149,#73a839 60%,#699934);background-repeat:no-repeat;border-bottom:1px solid #59822c;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff88c149',endColorstr='#ff699934',GradientType=0);filter:none}.btn-info{background-image:-webkit-linear-gradient(#04519b,#033c73 60%,#02325f);background-image:linear-gradient(#04519b,#033c73 60%,#02325f);background-repeat:no-repeat;border-bottom:1px solid #022241;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff04519b',endColorstr='#ff02325f',GradientType=0);filter:none}.btn-warning{background-image:-webkit-linear-gradient(#ff6707,#dd5600 60%,#c94e00);background-image:linear-gradient(#ff6707,#dd5600 60%,#c94e00);background-repeat:no-repeat;border-bottom:1px solid #aa4200;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff6707',endColorstr='#ffc94e00',GradientType=0);filter:none}.btn-danger{background-image:-webkit-linear-gradient(#e12b31,#c71c22 60%,#b5191f);background-image:linear-gradient(#e12b31,#c71c22 60%,#b5191f);background-repeat:no-repeat;border-bottom:1px solid #9a161a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe12b31',endColorstr='#ffb5191f',GradientType=0);filter:none}.pagination .active>a,.pagination .active>a:hover{border-color:#ddd}.panel-primary .panel-heading,.panel-success .panel-heading,.panel-warning .panel-heading,.panel-danger .panel-heading,.panel-info .panel-heading,.panel-primary .panel-title,.panel-success .panel-title,.panel-warning .panel-title,.panel-danger .panel-title,.panel-info .panel-title{color:#fff}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix:after{clear:both}.clearfix:before,.clearfix:after{display:table;content:" "}.clearfix: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;visibility:hidden!important}.affix{position:fixed} diff --git a/app/views/directives/core/navbar.html b/app/views/directives/core/navbar.html deleted file mode 100644 index f0f93c4..0000000 --- a/app/views/directives/core/navbar.html +++ /dev/null @@ -1,23 +0,0 @@ - \ No newline at end of file diff --git a/app/views/directives/news/carousel.html b/app/views/directives/news/carousel.html deleted file mode 100644 index f2981a6..0000000 --- a/app/views/directives/news/carousel.html +++ /dev/null @@ -1,9 +0,0 @@ - - - {{n.title}} - - - -
\ No newline at end of file diff --git a/app/views/directives/news/newslist.html b/app/views/directives/news/newslist.html deleted file mode 100755 index 1a93b86..0000000 --- a/app/views/directives/news/newslist.html +++ /dev/null @@ -1,9 +0,0 @@ -
- - {{news.title}} - -
-

{{news.title}}

-
{{news.date*1000|date:'dd/MM/yyyy'}}
-
-
\ No newline at end of file diff --git a/app/views/index.html b/app/views/index.html deleted file mode 100644 index 67b8fc5..0000000 --- a/app/views/index.html +++ /dev/null @@ -1,37 +0,0 @@ -
-
-
- -
- -
-
-
-
-
- - - - - -
-
\ No newline at end of file diff --git a/app/views/news.html b/app/views/news.html deleted file mode 100644 index 77faa7d..0000000 --- a/app/views/news.html +++ /dev/null @@ -1,15 +0,0 @@ -
-
-
-

{{news.title}}

-

-

- Par {{news.author}}, le {{news.date*1000 | date:'dd/mm/yyyy'}} à {{news.date*1000 | date:'hh:mm'}} -

-
-
-
- - -
-
diff --git a/bower.json b/bower.json deleted file mode 100644 index 41da0b5..0000000 --- a/bower.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "website", - "version": "0.0.0", - "dependencies": { - "angular": "~1.0.7", - "json3": "~3.2.4", - "jquery": "~1.9.1", - "bootstrap-sass": "~2.3.1", - "es5-shim": "~2.0.8", - "angular-resource": "~1.0.7", - "angular-cookies": "~1.0.7", - "angular-sanitize": "~1.0.7" - }, - "devDependencies": { - "angular-mocks": "~1.0.7", - "angular-scenario": "~1.0.7" - } -} diff --git a/bower_components/angular-bootstrap-lightbox/LICENSE.txt b/bower_components/angular-bootstrap-lightbox/LICENSE.txt new file mode 100644 index 0000000..8244556 --- /dev/null +++ b/bower_components/angular-bootstrap-lightbox/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bower_components/angular-bootstrap-lightbox/README.md b/bower_components/angular-bootstrap-lightbox/README.md new file mode 100644 index 0000000..c90e743 --- /dev/null +++ b/bower_components/angular-bootstrap-lightbox/README.md @@ -0,0 +1,132 @@ +# angular-bootstrap-lightbox + +This lightbox displays images using an [AngularUI Bootstrap](http://angular-ui.github.io/bootstrap/) Modal. + +When the lightbox is opened, navigating to the previous/next image can be achieved by clicking buttons above the image, clicking the left/right arrow keys, or swiping to the left/right (using [ngTouch directives](http://docs.angularjs.org/api/ngTouch/directive)). The escape key for closing the lightbox modal is automatically binded by AngularUI Bootstrap. + +Each image is scaled to fit inside the window. An optional image caption overlays the top left corner of the image. + +[angular-loading-bar](https://github.com/chieffancypants/angular-loading-bar) is used to show the loading progress of the current image. + +## Demo + +[Demo](http://compact.github.io/angular-bootstrap-lightbox/) + +## Install + +``` +bower install angular-bootstrap-lightbox --save +``` + +Stylesheet with dependencies: + +```html + + + +``` + +Script with dependencies: + +```html + + + + + +``` + +The Angular module is named `bootstrapLightbox`. Add it as a dependency: + +```js +angular.module('app', ['bootstrapLightbox']); +``` + +## Example + +Gallery: + +```html + +``` + +Controller: + +```js +angular.module('app').controller('GalleryCtrl', function ($scope, Lightbox) { + $scope.images = [ + { + 'url': '1.jpg', // required + 'caption': 'Optional caption', // optional + 'thumbUrl': 'thumb1.jpg' // used only for this example + }, + { + 'url': '2.gif', + 'thumbUrl': 'thumb2.jpg' + }, + { + 'url': '3.png', + 'thumbUrl': 'thumb3.png' + } + ]; + + $scope.openLightboxModal = function (index) { + Lightbox.openModal($scope.images, index); + }; +}); +``` + +## Configuration + +The keyboard navigation in the lightbox can be enabled or disabled at any time by changing the value of the boolean `Lightbox.keyboardNavEnabled` (`Lightbox` is a service). + +The look of the lightbox may be edited by changing the `templateUrl` or by adding CSS rules for the elements in the default view [lightbox.html](src/lightbox.html) (for example, use the selector `.lightbox-image-caption` to style the caption). + +The provider may be configured as follows. + +```js +angular.module('app').config(function (LightboxProvider) { + // set a custom template + LightboxProvider.templateUrl = 'lightbox.html'; + + /** + * Calculate the max and min limits to the width and height of the displayed + * image (all are optional). The max dimensions override the min + * dimensions if they conflict. + * @param {Object} dimensions Contains the properties windowWidth, + * windowHeight, imageWidth, imageHeight. + * @return {Object} May optionally contain the properties minWidth, + * minHeight, maxWidth, maxHeight. + */ + LightboxProvider.calculateImageDimensionLimits = function (dimensions) { + return { + 'minWidth': 100, + 'minHeight': 100, + 'maxWidth': dimensions.windowWidth - 102, + 'maxHeight': dimensions.windowHeight - 136 + }; + }; + + /** + * Calculate the width and height of the modal. This method gets called + * after the width and height of the image, as displayed inside the modal, + * are calculated. See the default method for cases where the width or + * height are 'auto'. + * @param {Object} dimensions Contains the properties windowWidth, + * windowHeight, imageDisplayWidth, imageDisplayHeight. + * @return {Object} Must contain the properties width and height. + */ + LightboxProvider.calculateModalDimensions = function (dimensions) { + return { + 'width': Math.max(500, dimensions.imageDisplayWidth + 42), + 'height': Math.max(500, dimensions.imageDisplayHeight + 76) + }; + }; +}); +``` diff --git a/bower_components/angular-bootstrap-lightbox/bower.json b/bower_components/angular-bootstrap-lightbox/bower.json new file mode 100644 index 0000000..b1cece5 --- /dev/null +++ b/bower_components/angular-bootstrap-lightbox/bower.json @@ -0,0 +1,21 @@ +{ + "name": "angular-bootstrap-lightbox", + "version": "0.2.3", + "main": [ + "dist/angular-bootstrap-lightbox.js", + "dist/angular-bootstrap-lightbox.css" + ], + "ignore": [ + "src", + ".*", + "Gruntfile.js", + "package.json" + ], + "dependencies": { + "angular": "^1.2.22", + "angular-bootstrap": "^0.11.0", + "angular-loading-bar": "^0.5.0", + "angular-touch": "^1.2.22", + "bootstrap": "^3.2.0" + } +} diff --git a/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.css b/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.css new file mode 100644 index 0000000..bf44f18 --- /dev/null +++ b/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.css @@ -0,0 +1,44 @@ +.lightbox-nav { + position: relative; + margin-bottom: 12px; /* the font-size of .btn-xs */ + text-align: center; + font-size: 0; /* prevent the otherwise inherited font-size and line-height from adding extra space to the bottom of this div */ +} + +.lightbox-nav .btn-group { + vertical-align: top; +} + +.lightbox-nav .close { + /* absolutely position this in order to center the nav buttons */ + position: absolute; + top: 0; + right: 0; +} + +.lightbox-image-container { + position: relative; + text-align: center; /* center the image */ +} + +/* the caption overlays the top left corner of the image */ +.lightbox-image-caption { + position: absolute; + top: 0; + left: 0; + margin: 0.5em 0.9em; /* the left and right margins are offset by 0.4em for the span box-shadow */ + color: #000; + font-size: 1.5em; + font-weight: bold; + text-align: left; + text-shadow: 0.1em 0.1em 0.2em rgba(255, 255, 255, 0.5); +} + +.lightbox-image-caption span { + padding-top: 0.1em; + padding-bottom: 0.1em; + background-color: rgba(255, 255, 255, 0.75); + /* pad the left and right of each line of text */ + box-shadow: 0.4em 0 0 rgba(255, 255, 255, 0.75), + -0.4em 0 0 rgba(255, 255, 255, 0.75); +} diff --git a/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.js b/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.js new file mode 100644 index 0000000..ccaa436 --- /dev/null +++ b/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.js @@ -0,0 +1,397 @@ +angular.module('bootstrapLightbox', [ + 'ngTouch', + 'ui.bootstrap', + 'chieffancypants.loadingBar', +]); +angular.module('bootstrapLightbox').run(['$templateCache', function($templateCache) { + 'use strict'; + + $templateCache.put('lightbox.html', + "" + ); + +}]); +angular.module('bootstrapLightbox').service('ImageLoader', function ($q) { + this.load = function (url) { + var deferred = $q.defer(); + + var image = new Image(); + + // when the image has loaded + image.onload = function () { + // check image properties for possible errors + if ((typeof this.complete === 'boolean' && this.complete === false) || + (typeof this.naturalWidth === 'number' && this.naturalWidth === 0)) { + deferred.reject(); + } + + deferred.resolve(); + }; + + // when the image fails to load + image.onerror = function () { + deferred.reject(); + }; + + // start loading the image + image.src = url; + + return deferred.promise; + }; +}); +angular.module('bootstrapLightbox').provider('Lightbox', function () { + this.templateUrl = 'lightbox.html'; + + /** + * Calculate the max and min limits to the width and height of the displayed + * image (all are optional). The max dimensions override the min + * dimensions if they conflict. + * @param {Object} dimensions Contains the properties windowWidth, + * windowHeight, imageWidth, imageHeight. + * @return {Object} May optionally contain the properties minWidth, + * minHeight, maxWidth, maxHeight. + */ + this.calculateImageDimensionLimits = function (dimensions) { + return { + // 102px = 2 * (30px margin of .modal-dialog + // + 1px border of .modal-content + // + 20px padding of .modal-body) + // with the goal of 30px side margins; however, the actual side margins + // will be slightly less (at 22.5px) due to the vertical scrollbar + 'maxWidth': dimensions.windowWidth - 102, + // 136px = 102px as above + // + 34px outer height of .lightbox-nav + 'maxHeight': dimensions.windowHeight - 136 + }; + }; + + /** + * Calculate the width and height of the modal. This method gets called + * after the width and height of the image, as displayed inside the modal, + * are calculated. + * @param {Object} dimensions Contains the properties windowWidth, + * windowHeight, imageDisplayWidth, imageDisplayHeight. + * @return {Object} Must contain the properties width and height. + */ + this.calculateModalDimensions = function (dimensions) { + // 400px = arbitrary min width + // 42px = 2 * (1px border of .modal-content + // + 20px padding of .modal-body) + var width = Math.max(400, dimensions.imageDisplayWidth + 42); + + // 200px = arbitrary min height + // 76px = 42px as above + // + 34px outer height of .lightbox-nav + var height = Math.max(200, dimensions.imageDisplayHeight + 76); + + // first case: the modal width cannot be larger than the window width + // 20px = arbitrary value larger than the vertical scrollbar + // width in order to avoid having a horizontal scrollbar + // second case: Bootstrap modals are not centered below 768px + if (width >= dimensions.windowWidth - 20 || dimensions.windowWidth < 768) { + width = 'auto'; + } + + // the modal height cannot be larger than the window height + if (height >= dimensions.windowHeight) { + height = 'auto'; + } + + return { + 'width': width, + 'height': height + }; + }; + + this.$get = function ($document, $modal, $timeout, cfpLoadingBar, + ImageLoader) { + // array of all images to be shown in the lightbox (not Image objects) + var images = []; + + // the index of the image currently shown (Lightbox.image) + var index = -1; + + // the service object + var Lightbox = {}; + + // configurable properties + Lightbox.templateUrl = this.templateUrl; + Lightbox.calculateImageDimensionLimits = this.calculateImageDimensionLimits; + Lightbox.calculateModalDimensions = this.calculateModalDimensions; + + // whether keyboard navigation is currently enabled for navigating through + // images in the lightbox + Lightbox.keyboardNavEnabled = false; + + // the current image + Lightbox.image = {}; + + // open the lightbox modal + Lightbox.openModal = function (newImages, newIndex) { + images = newImages; + Lightbox.setImage(newIndex); + + $modal.open({ + 'templateUrl': Lightbox.templateUrl, + 'controller': ['$scope', function ($scope) { + // $scope is the modal scope, a child of $rootScope + $scope.Lightbox = Lightbox; + + Lightbox.keyboardNavEnabled = true; + }], + 'windowClass': 'lightbox-modal' + }).result.finally(function () { // close + // prevent the lightbox from flickering from the old image when it gets + // opened again + Lightbox.image = {}; + + Lightbox.keyboardNavEnabled = false; + + // complete any lingering loading bar progress + cfpLoadingBar.complete(); + }); + }; + + Lightbox.setImage = function (newIndex) { + if (!(newIndex in images) || !('url' in images[newIndex])) { + throw 'Invalid image.'; + } + + cfpLoadingBar.start(); + + var success = function () { + index = newIndex; + Lightbox.image = images[index]; + + cfpLoadingBar.complete(); + }; + + // load the image before setting it, so everything in the view is updated + // at the same time; otherwise, the previous image remains while the + // current image is loading + ImageLoader.load(images[newIndex].url).then(success, function () { + success(); + + // blank image + Lightbox.image.url = '//:0'; + // use the caption to show the user an error + Lightbox.image.caption = 'Failed to load image'; + }); + }; + + // methods for navigation + Lightbox.firstImage = function () { + Lightbox.setImage(0); + }; + Lightbox.prevImage = function () { + Lightbox.setImage((index - 1 + images.length) % images.length); + }; + Lightbox.nextImage = function () { + Lightbox.setImage((index + 1) % images.length); + }; + Lightbox.lastImage = function () { + Lightbox.setImage(images.length - 1); + }; + + /** + * Call this method to set both the images array and the image object + * (based on the current index). A use case is when the images get + * changed dynamically in some way. + */ + Lightbox.setImages = function (newImages) { + images = newImages; + Lightbox.setImage(index); + }; + + /** + * Bind the left and right arrow keys for image navigation. This event + * handler never gets unbinded. Disable this using the + * keyboardNavEnabled flag. It is automatically disabled when + * the target is an input and or a textarea. + */ + $document.bind('keydown', function (event) { + if (!Lightbox.keyboardNavEnabled) { + return; + } + + // method of Lightbox to call + var method = null; + + switch (event.which) { + case 39: // right arrow key + method = 'nextImage'; + break; + case 37: // left arrow key + method = 'prevImage'; + break; + } + + if (method !== null && + ['input', 'textarea'].indexOf(event.tagName) === -1) { + // the view doesn't update without a manual digest + $timeout(function () { + Lightbox[method](); + }); + + event.preventDefault(); + } + }); + + return Lightbox; + }; +}); +angular.module('bootstrapLightbox').directive('lightboxSrc', function ($window, + Lightbox) { + /** + * Calculate the dimensions to display the image. The max dimensions + * override the min dimensions if they conflict. + */ + var calculateImageDisplayDimensions = function (dimensions) { + var w = dimensions.width; + var h = dimensions.height; + var minW = dimensions.minWidth; + var minH = dimensions.minHeight; + var maxW = dimensions.maxWidth; + var maxH = dimensions.maxHeight; + + var displayW = w; + var displayH = h; + + // resize the image if it is too small + if (w < minW && h < minH) { + // the image is both too thin and short, so compare the aspect ratios to + // determine whether to min the width or height + if (w / h > maxW / maxH) { + displayH = minH; + displayW = Math.round(w * minH / h); + } else { + displayW = minW; + displayH = Math.round(h * minW / w); + } + } else if (w < minW) { + // the image is too thin + displayW = minW; + displayH = Math.round(h * minW / w); + } else if (h < minH) { + // the image is too short + displayH = minH; + displayW = Math.round(w * minH / h); + } + + // resize the image if it is too large + if (w > maxW && h > maxH) { + // the image is both too tall and wide, so compare the aspect ratios + // to determine whether to max the width or height + if (w / h > maxW / maxH) { + displayW = maxW; + displayH = Math.round(h * maxW / w); + } else { + displayH = maxH; + displayW = Math.round(w * maxH / h); + } + } else if (w > maxW) { + // the image is too wide + displayW = maxW; + displayH = Math.round(h * maxW / w); + } else if (h > maxH) { + // the image is too tall + displayH = maxH; + displayW = Math.round(w * maxH / h); + } + + return { + 'width': displayW || 0, + 'height': displayH || 0 // NaN is possible when dimensions.width is 0 + }; + }; + + // the dimensions of the image + var imageWidth = 0; + var imageHeight = 0; + + return { + 'link': function (scope, element, attrs) { + // resize the image and the containing modal + var resize = function () { + // get the window dimensions + var windowWidth = $window.innerWidth; + var windowHeight = $window.innerHeight; + + // calculate the max/min dimensions for the image + var imageDimensionLimits = Lightbox.calculateImageDimensionLimits({ + 'windowWidth': windowWidth, + 'windowHeight': windowHeight, + 'imageWidth': imageWidth, + 'imageHeight': imageHeight + }); + + // calculate the dimensions to display the image + var imageDisplayDimensions = calculateImageDisplayDimensions( + angular.extend({ + 'width': imageWidth, + 'height': imageHeight, + 'minWidth': 1, + 'minHeight': 1, + 'maxWidth': 3000, + 'maxHeight': 3000, + }, imageDimensionLimits) + ); + + // calculate the dimensions of the modal container + var modalDimensions = Lightbox.calculateModalDimensions({ + 'windowWidth': windowWidth, + 'windowHeight': windowHeight, + 'imageDisplayWidth': imageDisplayDimensions.width, + 'imageDisplayHeight': imageDisplayDimensions.height + }); + + // resize the image + element.css({ + 'width': imageDisplayDimensions.width + 'px', + 'height': imageDisplayDimensions.height + 'px' + }); + + // setting the height on .modal-dialog does not expand the div with the + // background, which is .modal-content + angular.element( + document.querySelector('.lightbox-modal .modal-dialog') + ).css({ + 'width': modalDimensions.width + 'px' + }); + + // .modal-content has no width specified; if we set the width on + // .modal-content and not on .modal-dialog, .modal-dialog retains its + // default width of 600px and that places .modal-content off center + angular.element( + document.querySelector('.lightbox-modal .modal-content') + ).css({ + 'height': modalDimensions.height + 'px' + }); + }; + + // load the new image whenever the attr changes + scope.$watch(function () { + return attrs.lightboxSrc; + }, function (src) { + // blank the image before resizing the element; see + // http://stackoverflow.com/questions/5775469/whats-the-valid-way-to-include-an-image-with-no-src + element[0].src = '//:0'; + + var image = new Image(); + image.src = src; + + // these variables must be set before resize(), as they are used in it + imageWidth = image.naturalWidth; + imageHeight = image.naturalHeight; + + resize(); + + // show the image + element[0].src = src; + }); + + // resize the image and modal whenever the window gets resized + angular.element($window).on('resize', resize); + } + }; +}); diff --git a/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.min.css b/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.min.css new file mode 100644 index 0000000..d70e3d8 --- /dev/null +++ b/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.min.css @@ -0,0 +1 @@ +.lightbox-nav{position:relative;margin-bottom:12px;text-align:center;font-size:0}.lightbox-nav .btn-group{vertical-align:top}.lightbox-nav .close{position:absolute;top:0;right:0}.lightbox-image-container{position:relative;text-align:center}.lightbox-image-caption{position:absolute;top:0;left:0;margin:.5em .9em;color:#000;font-size:1.5em;font-weight:700;text-align:left;text-shadow:.1em .1em .2em rgba(255,255,255,.5)}.lightbox-image-caption span{padding-top:.1em;padding-bottom:.1em;background-color:rgba(255,255,255,.75);box-shadow:.4em 0 0 rgba(255,255,255,.75),-.4em 0 0 rgba(255,255,255,.75)} \ No newline at end of file diff --git a/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.min.js b/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.min.js new file mode 100644 index 0000000..99488f0 --- /dev/null +++ b/bower_components/angular-bootstrap-lightbox/dist/angular-bootstrap-lightbox.min.js @@ -0,0 +1,2 @@ +/*! angular-bootstrap-lightbox */ +angular.module("bootstrapLightbox",["ngTouch","ui.bootstrap","chieffancypants.loadingBar"]),angular.module("bootstrapLightbox").run(["$templateCache",function(a){"use strict";a.put("lightbox.html",'')}]),angular.module("bootstrapLightbox").service("ImageLoader",["$q",function(a){this.load=function(b){var c=a.defer(),d=new Image;return d.onload=function(){("boolean"==typeof this.complete&&this.complete===!1||"number"==typeof this.naturalWidth&&0===this.naturalWidth)&&c.reject(),c.resolve()},d.onerror=function(){c.reject()},d.src=b,c.promise}}]),angular.module("bootstrapLightbox").provider("Lightbox",function(){this.templateUrl="lightbox.html",this.calculateImageDimensionLimits=function(a){return{maxWidth:a.windowWidth-102,maxHeight:a.windowHeight-136}},this.calculateModalDimensions=function(a){var b=Math.max(400,a.imageDisplayWidth+42),c=Math.max(200,a.imageDisplayHeight+76);return(b>=a.windowWidth-20||a.windowWidth<768)&&(b="auto"),c>=a.windowHeight&&(c="auto"),{width:b,height:c}},this.$get=["$document","$modal","$timeout","cfpLoadingBar","ImageLoader",function(a,b,c,d,e){var f=[],g=-1,h={};return h.templateUrl=this.templateUrl,h.calculateImageDimensionLimits=this.calculateImageDimensionLimits,h.calculateModalDimensions=this.calculateModalDimensions,h.keyboardNavEnabled=!1,h.image={},h.openModal=function(a,c){f=a,h.setImage(c),b.open({templateUrl:h.templateUrl,controller:["$scope",function(a){a.Lightbox=h,h.keyboardNavEnabled=!0}],windowClass:"lightbox-modal"}).result.finally(function(){h.image={},h.keyboardNavEnabled=!1,d.complete()})},h.setImage=function(a){if(!(a in f&&"url"in f[a]))throw"Invalid image.";d.start();var b=function(){g=a,h.image=f[g],d.complete()};e.load(f[a].url).then(b,function(){b(),h.image.url="//:0",h.image.caption="Failed to load image"})},h.firstImage=function(){h.setImage(0)},h.prevImage=function(){h.setImage((g-1+f.length)%f.length)},h.nextImage=function(){h.setImage((g+1)%f.length)},h.lastImage=function(){h.setImage(f.length-1)},h.setImages=function(a){f=a,h.setImage(g)},a.bind("keydown",function(a){if(h.keyboardNavEnabled){var b=null;switch(a.which){case 39:b="nextImage";break;case 37:b="prevImage"}null!==b&&-1===["input","textarea"].indexOf(a.tagName)&&(c(function(){h[b]()}),a.preventDefault())}}),h}]}),angular.module("bootstrapLightbox").directive("lightboxSrc",["$window","Lightbox",function(a,b){var c=function(a){var b=a.width,c=a.height,d=a.minWidth,e=a.minHeight,f=a.maxWidth,g=a.maxHeight,h=b,i=c;return d>b&&e>c?b/c>f/g?(i=e,h=Math.round(b*e/c)):(h=d,i=Math.round(c*d/b)):d>b?(h=d,i=Math.round(c*d/b)):e>c&&(i=e,h=Math.round(b*e/c)),b>f&&c>g?b/c>f/g?(h=f,i=Math.round(c*f/b)):(i=g,h=Math.round(b*g/c)):b>f?(h=f,i=Math.round(c*f/b)):c>g&&(i=g,h=Math.round(b*g/c)),{width:h||0,height:i||0}},d=0,e=0;return{link:function(f,g,h){var i=function(){var f=a.innerWidth,h=a.innerHeight,i=b.calculateImageDimensionLimits({windowWidth:f,windowHeight:h,imageWidth:d,imageHeight:e}),j=c(angular.extend({width:d,height:e,minWidth:1,minHeight:1,maxWidth:3e3,maxHeight:3e3},i)),k=b.calculateModalDimensions({windowWidth:f,windowHeight:h,imageDisplayWidth:j.width,imageDisplayHeight:j.height});g.css({width:j.width+"px",height:j.height+"px"}),angular.element(document.querySelector(".lightbox-modal .modal-dialog")).css({width:k.width+"px"}),angular.element(document.querySelector(".lightbox-modal .modal-content")).css({height:k.height+"px"})};f.$watch(function(){return h.lightboxSrc},function(a){g[0].src="//:0";var b=new Image;b.src=a,d=b.naturalWidth,e=b.naturalHeight,i(),g[0].src=a}),angular.element(a).on("resize",i)}}}]); \ No newline at end of file diff --git a/bower_components/angular-bootstrap/bower.json b/bower_components/angular-bootstrap/bower.json new file mode 100644 index 0000000..eb32ff3 --- /dev/null +++ b/bower_components/angular-bootstrap/bower.json @@ -0,0 +1,11 @@ +{ + "author": { + "name": "https://github.com/angular-ui/bootstrap/graphs/contributors" + }, + "name": "angular-bootstrap", + "version": "0.11.2", + "main": ["./ui-bootstrap-tpls.js"], + "dependencies": { + "angular": ">=1" + } +} diff --git a/bower_components/angular-bootstrap/ui-bootstrap-193451f6b2.js b/bower_components/angular-bootstrap/ui-bootstrap-193451f6b2.js new file mode 100644 index 0000000..9d36932 --- /dev/null +++ b/bower_components/angular-bootstrap/ui-bootstrap-193451f6b2.js @@ -0,0 +1,3857 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.2 - 2014-09-26 + * License: MIT + */ +angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) + + .directive('collapse', ['$transition', function ($transition) { + + return { + link: function (scope, element, attrs) { + + var initialAnimSkip = true; + var currentTransition; + + function doTransition(change) { + var newTransition = $transition(element, change); + if (currentTransition) { + currentTransition.cancel(); + } + currentTransition = newTransition; + newTransition.then(newTransitionDone, newTransitionDone); + return newTransition; + + function newTransitionDone() { + // Make sure it's this transition, otherwise, leave it alone. + if (currentTransition === newTransition) { + currentTransition = undefined; + } + } + } + + function expand() { + if (initialAnimSkip) { + initialAnimSkip = false; + expandDone(); + } else { + element.removeClass('collapse').addClass('collapsing'); + doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); + } + } + + function expandDone() { + element.removeClass('collapsing'); + element.addClass('collapse in'); + element.css({height: 'auto'}); + } + + function collapse() { + if (initialAnimSkip) { + initialAnimSkip = false; + collapseDone(); + element.css({height: 0}); + } else { + // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value + element.css({ height: element[0].scrollHeight + 'px' }); + //trigger reflow so a browser realizes that height was updated from auto to a specific value + var x = element[0].offsetWidth; + + element.removeClass('collapse in').addClass('collapsing'); + + doTransition({ height: 0 }).then(collapseDone); + } + } + + function collapseDone() { + element.removeClass('collapsing'); + element.addClass('collapse'); + } + + scope.$watch(attrs.collapse, function (shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) + +.constant('accordionConfig', { + closeOthers: true +}) + +.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { + + // This array keeps track of the accordion groups + this.groups = []; + + // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to + this.closeOthers = function(openGroup) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if ( closeOthers ) { + angular.forEach(this.groups, function (group) { + if ( group !== openGroup ) { + group.isOpen = false; + } + }); + } + }; + + // This is called from the accordion-group directive to add itself to the accordion + this.addGroup = function(groupScope) { + var that = this; + this.groups.push(groupScope); + + groupScope.$on('$destroy', function (event) { + that.removeGroup(groupScope); + }); + }; + + // This is called from the accordion-group directive when to remove itself + this.removeGroup = function(group) { + var index = this.groups.indexOf(group); + if ( index !== -1 ) { + this.groups.splice(index, 1); + } + }; + +}]) + +// The accordion directive simply sets up the directive controller +// and adds an accordion CSS class to itself element. +.directive('accordion', function () { + return { + restrict:'EA', + controller:'AccordionController', + transclude: true, + replace: false, + templateUrl: 'template/accordion/accordion.html' + }; +}) + +// The accordion-group directive indicates a block of html that will expand and collapse in an accordion +.directive('accordionGroup', function() { + return { + require:'^accordion', // We need this directive to be inside an accordion + restrict:'EA', + transclude:true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl:'template/accordion/accordion-group.html', + scope: { + heading: '@', // Interpolate the heading attribute onto this scope + isOpen: '=?', + isDisabled: '=?' + }, + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + accordionCtrl.addGroup(scope); + + scope.$watch('isOpen', function(value) { + if ( value ) { + accordionCtrl.closeOthers(scope); + } + }); + + scope.toggleOpen = function() { + if ( !scope.isDisabled ) { + scope.isOpen = !scope.isOpen; + } + }; + } + }; +}) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +// +// Heading containing HTML - +// +.directive('accordionHeading', function() { + return { + restrict: 'EA', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + link: function(scope, element, attr, accordionGroupCtrl, transclude) { + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, function() {})); + } + }; +}) + +// Use in the accordion-group template to indicate where you want the heading to be transcluded +// You must provide the property on the accordion-group controller that will hold the transcluded element +//
+//
...
+// ... +//
+.directive('accordionTransclude', function() { + return { + require: '^accordionGroup', + link: function(scope, element, attr, controller) { + scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { + if ( heading ) { + element.html(''); + element.append(heading); + } + }); + } + }; +}); + +angular.module('ui.bootstrap.alert', []) + +.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { + $scope.closeable = 'close' in $attrs; +}]) + +.directive('alert', function () { + return { + restrict:'EA', + controller:'AlertController', + templateUrl:'template/alert/alert.html', + transclude:true, + replace:true, + scope: { + type: '@', + close: '&' + } + }; +}); + +angular.module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function () { + return function (scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +angular.module('ui.bootstrap.buttons', []) + +.constant('buttonConfig', { + activeClass: 'active', + toggleEvent: 'click' +}) + +.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { + this.activeClass = buttonConfig.activeClass || 'active'; + this.toggleEvent = buttonConfig.toggleEvent || 'click'; +}]) + +.directive('btnRadio', function () { + return { + require: ['btnRadio', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + var isActive = element.hasClass(buttonsCtrl.activeClass); + + if (!isActive || angular.isDefined(attrs.uncheckable)) { + scope.$apply(function () { + ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); + ngModelCtrl.$render(); + }); + } + }); + } + }; +}) + +.directive('btnCheckbox', function () { + return { + require: ['btnCheckbox', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + function getTrueValue() { + return getCheckboxValue(attrs.btnCheckboxTrue, true); + } + + function getFalseValue() { + return getCheckboxValue(attrs.btnCheckboxFalse, false); + } + + function getCheckboxValue(attributeValue, defaultValue) { + var val = scope.$eval(attributeValue); + return angular.isDefined(val) ? val : defaultValue; + } + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + scope.$apply(function () { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + } + }; +}); + +/** +* @ngdoc overview +* @name ui.bootstrap.carousel +* +* @description +* AngularJS version of an image carousel. +* +*/ +angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) +.controller('CarouselController', ['$scope', '$timeout', '$transition', function ($scope, $timeout, $transition) { + var self = this, + slides = self.slides = $scope.slides = [], + currentIndex = -1, + currentTimeout, isPlaying; + self.currentSlide = null; + + var destroyed = false; + /* direction: "prev" or "next" */ + self.select = $scope.select = function(nextSlide, direction) { + var nextIndex = slides.indexOf(nextSlide); + //Decide direction if it's not given + if (direction === undefined) { + direction = nextIndex > currentIndex ? 'next' : 'prev'; + } + if (nextSlide && nextSlide !== self.currentSlide) { + if ($scope.$currentTransition) { + $scope.$currentTransition.cancel(); + //Timeout so ng-class in template has time to fix classes for finished slide + $timeout(goNext); + } else { + goNext(); + } + } + function goNext() { + // Scope has been destroyed, stop here. + if (destroyed) { return; } + //If we have a slide to transition from and we have a transition type and we're allowed, go + if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { + //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime + nextSlide.$element.addClass(direction); + var reflow = nextSlide.$element[0].offsetWidth; //force reflow + + //Set all other slides to stop doing their stuff for the new transition + angular.forEach(slides, function(slide) { + angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); + }); + angular.extend(nextSlide, {direction: direction, active: true, entering: true}); + angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); + + $scope.$currentTransition = $transition(nextSlide.$element, {}); + //We have to create new pointers inside a closure since next & current will change + (function(next,current) { + $scope.$currentTransition.then( + function(){ transitionDone(next, current); }, + function(){ transitionDone(next, current); } + ); + }(nextSlide, self.currentSlide)); + } else { + transitionDone(nextSlide, self.currentSlide); + } + self.currentSlide = nextSlide; + currentIndex = nextIndex; + //every time you change slides, reset the timer + restartTimer(); + } + function transitionDone(next, current) { + angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); + angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); + $scope.$currentTransition = null; + } + }; + $scope.$on('$destroy', function () { + destroyed = true; + }); + + /* Allow outside people to call indexOf on slides array */ + self.indexOfSlide = function(slide) { + return slides.indexOf(slide); + }; + + $scope.next = function() { + var newIndex = (currentIndex + 1) % slides.length; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'next'); + } + }; + + $scope.prev = function() { + var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'prev'); + } + }; + + $scope.isActive = function(slide) { + return self.currentSlide === slide; + }; + + $scope.$watch('interval', restartTimer); + $scope.$on('$destroy', resetTimer); + + function restartTimer() { + resetTimer(); + var interval = +$scope.interval; + if (!isNaN(interval) && interval>=0) { + currentTimeout = $timeout(timerFn, interval); + } + } + + function resetTimer() { + if (currentTimeout) { + $timeout.cancel(currentTimeout); + currentTimeout = null; + } + } + + function timerFn() { + if (isPlaying) { + $scope.next(); + restartTimer(); + } else { + $scope.pause(); + } + } + + $scope.play = function() { + if (!isPlaying) { + isPlaying = true; + restartTimer(); + } + }; + $scope.pause = function() { + if (!$scope.noPause) { + isPlaying = false; + resetTimer(); + } + }; + + self.addSlide = function(slide, element) { + slide.$element = element; + slides.push(slide); + //if this is the first slide or the slide is set to active, select it + if(slides.length === 1 || slide.active) { + self.select(slides[slides.length-1]); + if (slides.length == 1) { + $scope.play(); + } + } else { + slide.active = false; + } + }; + + self.removeSlide = function(slide) { + //get the index of the slide inside the carousel + var index = slides.indexOf(slide); + slides.splice(index, 1); + if (slides.length > 0 && slide.active) { + if (index >= slides.length) { + self.select(slides[index-1]); + } else { + self.select(slides[index]); + } + } else if (currentIndex > index) { + currentIndex--; + } + }; + +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:carousel + * @restrict EA + * + * @description + * Carousel is the outer container for a set of image 'slides' to showcase. + * + * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. + * @param {boolean=} noTransition Whether to disable transitions on the carousel. + * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). + * + * @example + + + + + + + + + + + + + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + + + */ +.directive('carousel', [function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + controller: 'CarouselController', + require: 'carousel', + templateUrl: 'template/carousel/carousel.html', + scope: { + interval: '=', + noTransition: '=', + noPause: '=' + } + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:slide + * @restrict EA + * + * @description + * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. + * + * @param {boolean=} active Model binding, whether or not this slide is currently active. + * + * @example + + +
+ + + + + + + Interval, in milliseconds: +
Enter a negative number to stop the interval. +
+
+ +function CarouselDemoCtrl($scope) { + $scope.myInterval = 5000; +} + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + +
+*/ + +.directive('slide', function() { + return { + require: '^carousel', + restrict: 'EA', + transclude: true, + replace: true, + templateUrl: 'template/carousel/slide.html', + scope: { + active: '=?' + }, + link: function (scope, element, attrs, carouselCtrl) { + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}); + +angular.module('ui.bootstrap.dateparser', []) + +.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) { + + this.parsers = {}; + + var formatCodeToRegex = { + 'yyyy': { + regex: '\\d{4}', + apply: function(value) { this.year = +value; } + }, + 'yy': { + regex: '\\d{2}', + apply: function(value) { this.year = +value + 2000; } + }, + 'y': { + regex: '\\d{1,4}', + apply: function(value) { this.year = +value; } + }, + 'MMMM': { + regex: $locale.DATETIME_FORMATS.MONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } + }, + 'MMM': { + regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } + }, + 'MM': { + regex: '0[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'M': { + regex: '[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'dd': { + regex: '[0-2][0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'd': { + regex: '[1-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'EEEE': { + regex: $locale.DATETIME_FORMATS.DAY.join('|') + }, + 'EEE': { + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') + } + }; + + function createParser(format) { + var map = [], regex = format.split(''); + + angular.forEach(formatCodeToRegex, function(data, code) { + var index = format.indexOf(code); + + if (index > -1) { + format = format.split(''); + + regex[index] = '(' + data.regex + ')'; + format[index] = '$'; // Custom symbol to define consumed part of format + for (var i = index + 1, n = index + code.length; i < n; i++) { + regex[i] = ''; + format[i] = '$'; + } + format = format.join(''); + + map.push({ index: index, apply: data.apply }); + } + }); + + return { + regex: new RegExp('^' + regex.join('') + '$'), + map: orderByFilter(map, 'index') + }; + } + + this.parse = function(input, format) { + if ( !angular.isString(input) || !format ) { + return input; + } + + format = $locale.DATETIME_FORMATS[format] || format; + + if ( !this.parsers[format] ) { + this.parsers[format] = createParser(format); + } + + var parser = this.parsers[format], + regex = parser.regex, + map = parser.map, + results = input.match(regex); + + if ( results && results.length ) { + var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt; + + for( var i = 1, n = results.length; i < n; i++ ) { + var mapper = map[i-1]; + if ( mapper.apply ) { + mapper.apply.call(fields, results[i]); + } + } + + if ( isValid(fields.year, fields.month, fields.date) ) { + dt = new Date( fields.year, fields.month, fields.date, fields.hours); + } + + return dt; + } + }; + + // Check if date is valid for specific month (and year for February). + // Month: 0 = Jan, 1 = Feb, etc + function isValid(year, month, date) { + if ( month === 1 && date > 28) { + return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); + } + + if ( month === 3 || month === 5 || month === 8 || month === 10) { + return date < 31; + } + + return true; + } +}]); + +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, 'position') || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) + }; + }, + + /** + * Provides coordinates for the targetEl in relation to hostEl + */ + positionElements: function (hostEl, targetEl, positionStr, appendToBody) { + + var positionStrParts = positionStr.split('-'); + var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; + + var hostElPos, + targetElWidth, + targetElHeight, + targetElPos; + + hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + + targetElWidth = targetEl.prop('offsetWidth'); + targetElHeight = targetEl.prop('offsetHeight'); + + var shiftWidth = { + center: function () { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function () { + return hostElPos.left; + }, + right: function () { + return hostElPos.left + hostElPos.width; + } + }; + + var shiftHeight = { + center: function () { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function () { + return hostElPos.top; + }, + bottom: function () { + return hostElPos.top + hostElPos.height; + } + }; + + switch (pos0) { + case 'right': + targetElPos = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0]() + }; + break; + case 'left': + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth + }; + break; + case 'bottom': + targetElPos = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1]() + }; + break; + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElPos; + } + }; + }]); + +angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) + +.constant('datepickerConfig', { + formatDay: 'dd', + formatMonth: 'MMMM', + formatYear: 'yyyy', + formatDayHeader: 'EEE', + formatDayTitle: 'MMMM yyyy', + formatMonthTitle: 'yyyy', + datepickerMode: 'day', + minMode: 'day', + maxMode: 'year', + showWeeks: true, + startingDay: 0, + yearRange: 20, + minDate: null, + maxDate: null +}) + +.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; + + // Modes chain + this.modes = ['day', 'month', 'year']; + + // Configuration attributes + angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', + 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) { + self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; + }); + + // Watchable date attributes + angular.forEach(['minDate', 'maxDate'], function( key ) { + if ( $attrs[key] ) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = value ? new Date(value) : null; + self.refreshView(); + }); + } else { + self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; + } + }); + + $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; + $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); + this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date(); + + $scope.isActive = function(dateObject) { + if (self.compare(dateObject.date, self.activeDate) === 0) { + $scope.activeDateId = dateObject.uid; + return true; + } + return false; + }; + + this.init = function( ngModelCtrl_ ) { + ngModelCtrl = ngModelCtrl_; + + ngModelCtrl.$render = function() { + self.render(); + }; + }; + + this.render = function() { + if ( ngModelCtrl.$modelValue ) { + var date = new Date( ngModelCtrl.$modelValue ), + isValid = !isNaN(date); + + if ( isValid ) { + this.activeDate = date; + } else { + $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } + ngModelCtrl.$setValidity('date', isValid); + } + this.refreshView(); + }; + + this.refreshView = function() { + if ( this.element ) { + this._refreshView(); + + var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); + } + }; + + this.createDateObject = function(date, format) { + var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + return { + date: date, + label: dateFilter(date, format), + selected: model && this.compare(date, model) === 0, + disabled: this.isDisabled(date), + current: this.compare(date, new Date()) === 0 + }; + }; + + this.isDisabled = function( date ) { + return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); + }; + + // Split array into smaller arrays + this.split = function(arr, size) { + var arrays = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + }; + + $scope.select = function( date ) { + if ( $scope.datepickerMode === self.minMode ) { + var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); + ngModelCtrl.$setViewValue( dt ); + ngModelCtrl.$render(); + } else { + self.activeDate = date; + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ]; + } + }; + + $scope.move = function( direction ) { + var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), + month = self.activeDate.getMonth() + direction * (self.step.months || 0); + self.activeDate.setFullYear(year, month, 1); + self.refreshView(); + }; + + $scope.toggleMode = function( direction ) { + direction = direction || 1; + + if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { + return; + } + + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ]; + }; + + // Key event mapper + $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' }; + + var focusElement = function() { + $timeout(function() { + self.element[0].focus(); + }, 0 , false); + }; + + // Listen for focus requests from popup directive + $scope.$on('datepicker.focus', focusElement); + + $scope.keydown = function( evt ) { + var key = $scope.keys[evt.which]; + + if ( !key || evt.shiftKey || evt.altKey ) { + return; + } + + evt.preventDefault(); + evt.stopPropagation(); + + if (key === 'enter' || key === 'space') { + if ( self.isDisabled(self.activeDate)) { + return; // do nothing + } + $scope.select(self.activeDate); + focusElement(); + } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { + $scope.toggleMode(key === 'up' ? 1 : -1); + focusElement(); + } else { + self.handleKeyDown(key, evt); + self.refreshView(); + } + }; +}]) + +.directive( 'datepicker', function () { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/datepicker.html', + scope: { + datepickerMode: '=?', + dateDisabled: '&' + }, + require: ['datepicker', '?^ngModel'], + controller: 'DatepickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + datepickerCtrl.init( ngModelCtrl ); + } + } + }; +}) + +.directive('daypicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/day.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + scope.showWeeks = ctrl.showWeeks; + + ctrl.step = { months: 1 }; + ctrl.element = element; + + var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + function getDaysInMonth( year, month ) { + return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; + } + + function getDates(startDate, n) { + var dates = new Array(n), current = new Date(startDate), i = 0; + current.setHours(12); // Prevent repeated dates because of timezone bug + while ( i < n ) { + dates[i++] = new Date(current); + current.setDate( current.getDate() + 1 ); + } + return dates; + } + + ctrl._refreshView = function() { + var year = ctrl.activeDate.getFullYear(), + month = ctrl.activeDate.getMonth(), + firstDayOfMonth = new Date(year, month, 1), + difference = ctrl.startingDay - firstDayOfMonth.getDay(), + numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, + firstDate = new Date(firstDayOfMonth); + + if ( numDisplayedFromPreviousMonth > 0 ) { + firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); + } + + // 42 is the number of days on a six-month calendar + var days = getDates(firstDate, 42); + for (var i = 0; i < 42; i ++) { + days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), { + secondary: days[i].getMonth() !== month, + uid: scope.uniqueId + '-' + i + }); + } + + scope.labels = new Array(7); + for (var j = 0; j < 7; j++) { + scope.labels[j] = { + abbr: dateFilter(days[j].date, ctrl.formatDayHeader), + full: dateFilter(days[j].date, 'EEEE') + }; + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); + scope.rows = ctrl.split(days, 7); + + if ( scope.showWeeks ) { + scope.weekNumbers = []; + var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ), + numWeeks = scope.rows.length; + while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {} + } + }; + + ctrl.compare = function(date1, date2) { + return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); + }; + + function getISO8601WeekNumber(date) { + var checkDate = new Date(date); + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + } + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getDate(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 7; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 7; + } else if (key === 'pageup' || key === 'pagedown') { + var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setMonth(month, 1); + date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); + } else if (key === 'home') { + date = 1; + } else if (key === 'end') { + date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); + } + ctrl.activeDate.setDate(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('monthpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/month.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + ctrl.step = { years: 1 }; + ctrl.element = element; + + ctrl._refreshView = function() { + var months = new Array(12), + year = ctrl.activeDate.getFullYear(); + + for ( var i = 0; i < 12; i++ ) { + months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); + scope.rows = ctrl.split(months, 3); + }; + + ctrl.compare = function(date1, date2) { + return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getMonth(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 3; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 3; + } else if (key === 'pageup' || key === 'pagedown') { + var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setFullYear(year); + } else if (key === 'home') { + date = 0; + } else if (key === 'end') { + date = 11; + } + ctrl.activeDate.setMonth(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('yearpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/year.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + var range = ctrl.yearRange; + + ctrl.step = { years: range }; + ctrl.element = element; + + function getStartingYear( year ) { + return parseInt((year - 1) / range, 10) * range + 1; + } + + ctrl._refreshView = function() { + var years = new Array(range); + + for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) { + years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = [years[0].label, years[range - 1].label].join(' - '); + scope.rows = ctrl.split(years, 5); + }; + + ctrl.compare = function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getFullYear(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 5; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 5; + } else if (key === 'pageup' || key === 'pagedown') { + date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; + } else if (key === 'home') { + date = getStartingYear( ctrl.activeDate.getFullYear() ); + } else if (key === 'end') { + date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1; + } + ctrl.activeDate.setFullYear(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.constant('datepickerPopupConfig', { + datepickerPopup: 'yyyy-MM-dd', + currentText: 'Today', + clearText: 'Clear', + closeText: 'Done', + closeOnDateSelection: true, + appendToBody: false, + showButtonBar: true +}) + +.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', +function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { + return { + restrict: 'EA', + require: 'ngModel', + scope: { + isOpen: '=?', + currentText: '@', + clearText: '@', + closeText: '@', + dateDisabled: '&' + }, + link: function(scope, element, attrs, ngModel) { + var dateFormat, + closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, + appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + + scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; + + scope.getText = function( key ) { + return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; + }; + + attrs.$observe('datepickerPopup', function(value) { + dateFormat = value || datepickerPopupConfig.datepickerPopup; + ngModel.$render(); + }); + + // popup element used to display calendar + var popupEl = angular.element('
'); + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection()' + }); + + function cameltoDash( string ){ + return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); + } + + // datepicker element + var datepickerEl = angular.element(popupEl.children()[0]); + if ( attrs.datepickerOptions ) { + angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) { + datepickerEl.attr( cameltoDash(option), value ); + }); + } + + scope.watchData = {}; + angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function( key ) { + if ( attrs[key] ) { + var getAttribute = $parse(attrs[key]); + scope.$parent.$watch(getAttribute, function(value){ + scope.watchData[key] = value; + }); + datepickerEl.attr(cameltoDash(key), 'watchData.' + key); + + // Propagate changes from datepicker to outside + if ( key === 'datepickerMode' ) { + var setAttribute = getAttribute.assign; + scope.$watch('watchData.' + key, function(value, oldvalue) { + if ( value !== oldvalue ) { + setAttribute(scope.$parent, value); + } + }); + } + } + }); + if (attrs.dateDisabled) { + datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); + } + + function parseDate(viewValue) { + if (!viewValue) { + ngModel.$setValidity('date', true); + return null; + } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { + ngModel.$setValidity('date', true); + return viewValue; + } else if (angular.isString(viewValue)) { + var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue); + if (isNaN(date)) { + ngModel.$setValidity('date', false); + return undefined; + } else { + ngModel.$setValidity('date', true); + return date; + } + } else { + ngModel.$setValidity('date', false); + return undefined; + } + } + ngModel.$parsers.unshift(parseDate); + + // Inner change + scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + scope.date = dt; + } + ngModel.$setViewValue(scope.date); + ngModel.$render(); + + if ( closeOnDateSelection ) { + scope.isOpen = false; + element[0].focus(); + } + }; + + element.bind('input change keyup', function() { + scope.$apply(function() { + scope.date = ngModel.$modelValue; + }); + }); + + // Outter change + ngModel.$render = function() { + var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; + element.val(date); + scope.date = parseDate( ngModel.$modelValue ); + }; + + var documentClickBind = function(event) { + if (scope.isOpen && event.target !== element[0]) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + }; + + var keydown = function(evt, noApply) { + scope.keydown(evt); + }; + element.bind('keydown', keydown); + + scope.keydown = function(evt) { + if (evt.which === 27) { + evt.preventDefault(); + evt.stopPropagation(); + scope.close(); + } else if (evt.which === 40 && !scope.isOpen) { + scope.isOpen = true; + } + }; + + scope.$watch('isOpen', function(value) { + if (value) { + scope.$broadcast('datepicker.focus'); + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); + + $document.bind('click', documentClickBind); + } else { + $document.unbind('click', documentClickBind); + } + }); + + scope.select = function( date ) { + if (date === 'today') { + var today = new Date(); + if (angular.isDate(ngModel.$modelValue)) { + date = new Date(ngModel.$modelValue); + date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); + } else { + date = new Date(today.setHours(0, 0, 0, 0)); + } + } + scope.dateSelection( date ); + }; + + scope.close = function() { + scope.isOpen = false; + element[0].focus(); + }; + + var $popup = $compile(popupEl)(scope); + // Prevent jQuery cache memory leak (template is now redundant after linking) + popupEl.remove(); + + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + + scope.$on('$destroy', function() { + $popup.remove(); + element.unbind('keydown', keydown); + $document.unbind('click', documentClickBind); + }); + } + }; +}]) + +.directive('datepickerPopupWrap', function() { + return { + restrict:'EA', + replace: true, + transclude: true, + templateUrl: 'template/datepicker/popup.html', + link:function (scope, element, attrs) { + element.bind('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + }); + } + }; +}); + +angular.module('ui.bootstrap.dropdown', []) + +.constant('dropdownConfig', { + openClass: 'open' +}) + +.service('dropdownService', ['$document', function($document) { + var openScope = null; + + this.open = function( dropdownScope ) { + if ( !openScope ) { + $document.bind('click', closeDropdown); + $document.bind('keydown', escapeKeyBind); + } + + if ( openScope && openScope !== dropdownScope ) { + openScope.isOpen = false; + } + + openScope = dropdownScope; + }; + + this.close = function( dropdownScope ) { + if ( openScope === dropdownScope ) { + openScope = null; + $document.unbind('click', closeDropdown); + $document.unbind('keydown', escapeKeyBind); + } + }; + + var closeDropdown = function( evt ) { + var toggleElement = openScope.getToggleElement(); + if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { + return; + } + + openScope.$apply(function() { + openScope.isOpen = false; + }); + }; + + var escapeKeyBind = function( evt ) { + if ( evt.which === 27 ) { + openScope.focusToggleElement(); + closeDropdown(); + } + }; +}]) + +.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) { + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop; + + this.init = function( element ) { + self.$element = element; + + if ( $attrs.isOpen ) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + }; + + this.toggle = function( open ) { + return scope.isOpen = arguments.length ? !!open : !scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.getToggleElement = function() { + return self.toggleElement; + }; + + scope.focusToggleElement = function() { + if ( self.toggleElement ) { + self.toggleElement[0].focus(); + } + }; + + scope.$watch('isOpen', function( isOpen, wasOpen ) { + $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); + + if ( isOpen ) { + scope.focusToggleElement(); + dropdownService.open( scope ); + } else { + dropdownService.close( scope ); + } + + setIsOpen($scope, isOpen); + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + + $scope.$on('$locationChangeSuccess', function() { + scope.isOpen = false; + }); + + $scope.$on('$destroy', function() { + scope.$destroy(); + }); +}]) + +.directive('dropdown', function() { + return { + restrict: 'CA', + controller: 'DropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + dropdownCtrl.init( element ); + } + }; +}) + +.directive('dropdownToggle', function() { + return { + restrict: 'CA', + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if ( !dropdownCtrl ) { + return; + } + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if ( !element.hasClass('disabled') && !attrs.disabled ) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } + }; +}); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) + +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function () { + return { + createNew: function () { + var stack = []; + + return { + add: function (key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function (key) { + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function () { + return stack[stack.length - 1]; + }, + remove: function (key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function () { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function () { + return stack.length; + } + }; + } + }; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', ['$timeout', function ($timeout) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + link: function (scope, element, attrs) { + scope.backdropClass = attrs.backdropClass || ''; + + scope.animate = false; + + //trigger CSS transitions + $timeout(function () { + scope.animate = true; + }); + } + }; + }]) + + .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + return { + restrict: 'EA', + scope: { + index: '@', + animate: '=' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'template/modal/window.html'; + }, + link: function (scope, element, attrs) { + element.addClass(attrs.windowClass || ''); + scope.size = attrs.size; + + $timeout(function () { + // trigger CSS transitions + scope.animate = true; + + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to loose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (!element[0].querySelectorAll('[autofocus]').length) { + element[0].focus(); + } + }); + + scope.close = function (evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + } + }; + }]) + + .directive('modalTransclude', function () { + return { + link: function($scope, $element, $attrs, controller, $transclude) { + $transclude($scope.$parent, function(clone) { + $element.empty(); + $element.append(clone); + }); + } + }; + }) + + .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', + function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var $modalStack = {}; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance) { + + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + //remove window DOM element + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() { + modalWindow.modalScope.$destroy(); + body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); + checkRemoveBackdrop(); + }); + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { + backdropScopeRef.$destroy(); + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, emulateTime, done) { + // Closing animation + scope.animate = false; + + var transitionEndEventName = $transition.transitionEndEventName; + if (transitionEndEventName) { + // transition out + var timeout = $timeout(afterAnimating, emulateTime); + + domEl.bind(transitionEndEventName, function () { + $timeout.cancel(timeout); + afterAnimating(); + scope.$apply(); + }); + } else { + // Ensure this call is async + $timeout(afterAnimating); + } + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + domEl.remove(); + if (done) { + done(); + } + } + } + + $document.bind('keydown', function (evt) { + var modal; + + if (evt.which === 27) { + modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + evt.preventDefault(); + $rootScope.$apply(function () { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + } + } + }); + + $modalStack.open = function (modalInstance, modal) { + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard + }); + + var body = $document.find('body').eq(0), + currBackdropIndex = backdropIndex(); + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.index = currBackdropIndex; + var angularBackgroundDomEl = angular.element('
'); + angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); + backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
'); + angularDomEl.attr({ + 'template-url': modal.windowTemplateUrl, + 'window-class': modal.windowClass, + 'size': modal.size, + 'index': openedWindows.length() - 1, + 'animate': 'animate' + }).html(modal.content); + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + body.append(modalDomEl); + body.addClass(OPENED_MODAL_CLASS); + }; + + $modalStack.close = function (modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.deferred.resolve(result); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismiss = function (modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.deferred.reject(reason); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismissAll = function (reason) { + var topModal = this.getTop(); + while (topModal) { + this.dismiss(topModal.key, reason); + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function () { + return openedWindows.top(); + }; + + return $modalStack; + }]) + + .provider('$modal', function () { + + var $modalProvider = { + options: { + backdrop: true, //can be also false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', + function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { + + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl, + {cache: $templateCache}).then(function (result) { + return result.data; + }); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function (value) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } + }); + return promisesArr; + } + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + close: function (result) { + $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + + templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function (value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + if (modalOptions.controllerAs) { + modalScope[modalOptions.controllerAs] = ctrlInstance; + } + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + content: tplAndVars[0], + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + backdropClass: modalOptions.backdropClass, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size + }); + + }, function resolveError(reason) { + modalResultDeferred.reject(reason); + }); + + templateAndResolvePromise.then(function () { + modalOpenedDeferred.resolve(true); + }, function () { + modalOpenedDeferred.reject(false); + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.pagination', []) + +.controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; + + this.init = function(ngModelCtrl_, config) { + ngModelCtrl = ngModelCtrl_; + this.config = config; + + ngModelCtrl.$render = function() { + self.render(); + }; + + if ($attrs.itemsPerPage) { + $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { + self.itemsPerPage = parseInt(value, 10); + $scope.totalPages = self.calculateTotalPages(); + }); + } else { + this.itemsPerPage = config.itemsPerPage; + } + }; + + this.calculateTotalPages = function() { + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); + }; + + this.render = function() { + $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; + }; + + $scope.selectPage = function(page) { + if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) { + ngModelCtrl.$setViewValue(page); + ngModelCtrl.$render(); + } + }; + + $scope.getText = function( key ) { + return $scope[key + 'Text'] || self.config[key + 'Text']; + }; + $scope.noPrevious = function() { + return $scope.page === 1; + }; + $scope.noNext = function() { + return $scope.page === $scope.totalPages; + }; + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( $scope.page > value ) { + $scope.selectPage(value); + } else { + ngModelCtrl.$render(); + } + }); +}]) + +.constant('paginationConfig', { + itemsPerPage: 10, + boundaryLinks: false, + directionLinks: true, + firstText: 'First', + previousText: 'Previous', + nextText: 'Next', + lastText: 'Last', + rotate: true +}) + +.directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + firstText: '@', + previousText: '@', + nextText: '@', + lastText: '@' + }, + require: ['pagination', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pagination.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + // Setup configuration parameters + var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize, + rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate; + scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; + scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks; + + paginationCtrl.init(ngModelCtrl, paginationConfig); + + if (attrs.maxSize) { + scope.$parent.$watch($parse(attrs.maxSize), function(value) { + maxSize = parseInt(value, 10); + paginationCtrl.render(); + }); + } + + // Create page object used in template + function makePage(number, text, isActive) { + return { + number: number, + text: text, + active: isActive + }; + } + + function getPages(currentPage, totalPages) { + var pages = []; + + // Default page limits + var startPage = 1, endPage = totalPages; + var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); + + // recompute if maxSize + if ( isMaxSized ) { + if ( rotate ) { + // Current page is displayed in the middle of the visible ones + startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); + endPage = startPage + maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > totalPages) { + endPage = totalPages; + startPage = endPage - maxSize + 1; + } + } else { + // Visible pages are paginated with maxSize + startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; + + // Adjust last page if limit is exceeded + endPage = Math.min(startPage + maxSize - 1, totalPages); + } + } + + // Add page number links + for (var number = startPage; number <= endPage; number++) { + var page = makePage(number, number, number === currentPage); + pages.push(page); + } + + // Add links to move between page sets + if ( isMaxSized && ! rotate ) { + if ( startPage > 1 ) { + var previousPageSet = makePage(startPage - 1, '...', false); + pages.unshift(previousPageSet); + } + + if ( endPage < totalPages ) { + var nextPageSet = makePage(endPage + 1, '...', false); + pages.push(nextPageSet); + } + } + + return pages; + } + + var originalRender = paginationCtrl.render; + paginationCtrl.render = function() { + originalRender(); + if (scope.page > 0 && scope.page <= scope.totalPages) { + scope.pages = getPages(scope.page, scope.totalPages); + } + }; + } + }; +}]) + +.constant('pagerConfig', { + itemsPerPage: 10, + previousText: '« Previous', + nextText: 'Next »', + align: true +}) + +.directive('pager', ['pagerConfig', function(pagerConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + previousText: '@', + nextText: '@' + }, + require: ['pager', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pager.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align; + paginationCtrl.init(ngModelCtrl, pagerConfig); + } + }; +}]); + +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0 + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers ( triggers ) { + angular.extend( triggerMap, triggers ); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers ( trigger ) { + var show = trigger || options.trigger || defaultTriggerShow; + var hide = triggerMap[show] || show; + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case( type ); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
'+ + '
'; + + return { + restrict: 'EA', + scope: true, + compile: function (tElem, tAttrs) { + var tooltipLinker = $compile( template ); + + return function link ( scope, element, attrs ) { + var tooltip; + var transitionTimeout; + var popupTimeout; + var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; + var triggers = getTriggers( undefined ); + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + + var positionTooltip = function () { + + var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody); + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; + + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } + if ( scope.tt_popupDelay ) { + // Do nothing if the tooltip was already scheduled to pop-up. + // This happens if show is triggered multiple times before any hide is triggered. + if (!popupTimeout) { + popupTimeout = $timeout( show, scope.tt_popupDelay, false ); + popupTimeout.then(function(reposition){reposition();}); + } + } else { + show()(); + } + } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + + popupTimeout = null; + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + transitionTimeout = null; + } + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return angular.noop; + } + + createTooltip(); + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + positionTooltip(); + + // And show the tooltip. + scope.tt_isOpen = true; + scope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); + popupTimeout = null; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( scope.tt_animation ) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 500); + } + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltip = tooltipLinker(scope, function () {}); + + // Get contents rendered into the tooltip + scope.$digest(); + } + + function removeTooltip() { + transitionTimeout = null; + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + + if (!val && scope.tt_isOpen ) { + hide(); + } + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + var unregisterTriggers = function () { + element.unbind(triggers.show, showTooltipBind); + element.unbind(triggers.hide, hideTooltipBind); + }; + + attrs.$observe( prefix+'Trigger', function ( val ) { + unregisterTriggers(); + + triggers = getTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + }); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + + attrs.$observe( prefix+'AppendToBody', function ( val ) { + appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; + }); + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if ( appendToBody ) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { + if ( scope.tt_isOpen ) { + hide(); + } + }); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel( transitionTimeout ); + $timeout.cancel( popupTimeout ); + unregisterTriggers(); + removeTooltip(); + }); + }; + } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +}]); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html popovers, and selector delegatation. + */ +angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) + +.directive( 'popoverPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html' + }; +}) + +.directive( 'popover', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'popover', 'popover', 'click' ); +}]); + +angular.module('ui.bootstrap.progressbar', []) + +.constant('progressConfig', { + animate: true, + max: 100 +}) + +.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { + var self = this, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; + + this.bars = []; + $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max; + + this.addBar = function(bar, element) { + if ( !animate ) { + element.css({'transition': 'none'}); + } + + this.bars.push(bar); + + bar.$watch('value', function( value ) { + bar.percent = +(100 * value / $scope.max).toFixed(2); + }); + + bar.$on('$destroy', function() { + element = null; + self.removeBar(bar); + }); + }; + + this.removeBar = function(bar) { + this.bars.splice(this.bars.indexOf(bar), 1); + }; +}]) + +.directive('progress', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + require: 'progress', + scope: {}, + templateUrl: 'template/progressbar/progress.html' + }; +}) + +.directive('bar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + require: '^progress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, element); + } + }; +}) + +.directive('progressbar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, angular.element(element.children()[0])); + } + }; +}); +angular.module('ui.bootstrap.rating', []) + +.constant('ratingConfig', { + max: 5, + stateOn: null, + stateOff: null +}) + +.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) { + var ngModelCtrl = { $setViewValue: angular.noop }; + + this.init = function(ngModelCtrl_) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; + this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; + + var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : + new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max ); + $scope.range = this.buildTemplateObjects(ratingStates); + }; + + this.buildTemplateObjects = function(states) { + for (var i = 0, n = states.length; i < n; i++) { + states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]); + } + return states; + }; + + $scope.rate = function(value) { + if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) { + ngModelCtrl.$setViewValue(value); + ngModelCtrl.$render(); + } + }; + + $scope.enter = function(value) { + if ( !$scope.readonly ) { + $scope.value = value; + } + $scope.onHover({value: value}); + }; + + $scope.reset = function() { + $scope.value = ngModelCtrl.$viewValue; + $scope.onLeave(); + }; + + $scope.onKeydown = function(evt) { + if (/(37|38|39|40)/.test(evt.which)) { + evt.preventDefault(); + evt.stopPropagation(); + $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) ); + } + }; + + this.render = function() { + $scope.value = ngModelCtrl.$viewValue; + }; +}]) + +.directive('rating', function() { + return { + restrict: 'EA', + require: ['rating', 'ngModel'], + scope: { + readonly: '=?', + onHover: '&', + onLeave: '&' + }, + controller: 'RatingController', + templateUrl: 'template/rating/rating.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + ratingCtrl.init( ngModelCtrl ); + } + } + }; +}); + +/** + * @ngdoc overview + * @name ui.bootstrap.tabs + * + * @description + * AngularJS version of the tabs directive. + */ + +angular.module('ui.bootstrap.tabs', []) + +.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { + var ctrl = this, + tabs = ctrl.tabs = $scope.tabs = []; + + ctrl.select = function(selectedTab) { + angular.forEach(tabs, function(tab) { + if (tab.active && tab !== selectedTab) { + tab.active = false; + tab.onDeselect(); + } + }); + selectedTab.active = true; + selectedTab.onSelect(); + }; + + ctrl.addTab = function addTab(tab) { + tabs.push(tab); + // we can't run the select function on the first tab + // since that would select it twice + if (tabs.length === 1) { + tab.active = true; + } else if (tab.active) { + ctrl.select(tab); + } + }; + + ctrl.removeTab = function removeTab(tab) { + var index = tabs.indexOf(tab); + //Select a new tab if the tab to be removed is selected + if (tab.active && tabs.length > 1) { + //If this is the last tab, select the previous tab. else, the next tab. + var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; + ctrl.select(tabs[newActiveIndex]); + } + tabs.splice(index, 1); + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabset + * @restrict EA + * + * @description + * Tabset is the outer container for the tabs directive + * + * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. + * @param {boolean=} justified Whether or not to use justified styling for the tabs. + * + * @example + + + + First Content! + Second Content! + +
+ + First Vertical Content! + Second Vertical Content! + + + First Justified Content! + Second Justified Content! + +
+
+ */ +.directive('tabset', function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: { + type: '@' + }, + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; + } + }; +}) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tab + * @restrict EA + * + * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. + * @param {string=} select An expression to evaluate when the tab is selected. + * @param {boolean=} active A binding, telling whether or not this tab is selected. + * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. + * + * @description + * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. + * + * @example + + +
+ + +
+ + First Tab + + Alert me! + Second Tab, with alert callback and html heading! + + + {{item.content}} + + +
+
+ + function TabsDemoCtrl($scope) { + $scope.items = [ + { title:"Dynamic Title 1", content:"Dynamic Item 0" }, + { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } + ]; + + $scope.alertMe = function() { + setTimeout(function() { + alert("You've selected the alert tab!"); + }); + }; + }; + +
+ */ + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabHeading + * @restrict EA + * + * @description + * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. + * + * @example + + + + + HTML in my titles?! + And some content, too! + + + Icon heading?!? + That's right. + + + + + */ +.directive('tab', ['$parse', function($parse) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + active: '=?', + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + compile: function(elm, attrs, transclude) { + return function postLink(scope, elm, attrs, tabsetCtrl) { + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); + } + }); + + scope.disabled = false; + if ( attrs.disabled ) { + scope.$parent.$watch($parse(attrs.disabled), function(value) { + scope.disabled = !! value; + }); + } + + scope.select = function() { + if ( !scope.disabled ) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + }; + } + }; +}]) + +.directive('tabHeadingTransclude', [function() { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm, attrs, tabCtrl) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; +}]) + +.directive('tabContentTransclude', function() { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } else { + elm.append(node); + } + }); + }); + } + }; + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' + ); + } +}) + +; + +angular.module('ui.bootstrap.timepicker', []) + +.constant('timepickerConfig', { + hourStep: 1, + minuteStep: 1, + showMeridian: true, + meridians: null, + readonlyInput: false, + mousewheel: true +}) + +.controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) { + var selected = new Date(), + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; + + this.init = function( ngModelCtrl_, inputs ) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + var hoursInputEl = inputs.eq(0), + minutesInputEl = inputs.eq(1); + + var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; + if ( mousewheel ) { + this.setupMousewheelEvents( hoursInputEl, minutesInputEl ); + } + + $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; + this.setupInputEvents( hoursInputEl, minutesInputEl ); + }; + + var hourStep = timepickerConfig.hourStep; + if ($attrs.hourStep) { + $scope.$parent.$watch($parse($attrs.hourStep), function(value) { + hourStep = parseInt(value, 10); + }); + } + + var minuteStep = timepickerConfig.minuteStep; + if ($attrs.minuteStep) { + $scope.$parent.$watch($parse($attrs.minuteStep), function(value) { + minuteStep = parseInt(value, 10); + }); + } + + // 12H / 24H mode + $scope.showMeridian = timepickerConfig.showMeridian; + if ($attrs.showMeridian) { + $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { + $scope.showMeridian = !!value; + + if ( ngModelCtrl.$error.time ) { + // Evaluate from template + var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); + if (angular.isDefined( hours ) && angular.isDefined( minutes )) { + selected.setHours( hours ); + refresh(); + } + } else { + updateTemplate(); + } + }); + } + + // Get $scope.hours in 24H mode if valid + function getHoursFromTemplate ( ) { + var hours = parseInt( $scope.hours, 10 ); + var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); + if ( !valid ) { + return undefined; + } + + if ( $scope.showMeridian ) { + if ( hours === 12 ) { + hours = 0; + } + if ( $scope.meridian === meridians[1] ) { + hours = hours + 12; + } + } + return hours; + } + + function getMinutesFromTemplate() { + var minutes = parseInt($scope.minutes, 10); + return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; + } + + function pad( value ) { + return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; + } + + // Respond on mousewheel spin + this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) { + var isScrollingUp = function(e) { + if (e.originalEvent) { + e = e.originalEvent; + } + //pick correct delta variable depending on event + var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; + return (e.detail || delta > 0); + }; + + hoursInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() ); + e.preventDefault(); + }); + + minutesInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() ); + e.preventDefault(); + }); + + }; + + this.setupInputEvents = function( hoursInputEl, minutesInputEl ) { + if ( $scope.readonlyInput ) { + $scope.updateHours = angular.noop; + $scope.updateMinutes = angular.noop; + return; + } + + var invalidate = function(invalidHours, invalidMinutes) { + ngModelCtrl.$setViewValue( null ); + ngModelCtrl.$setValidity('time', false); + if (angular.isDefined(invalidHours)) { + $scope.invalidHours = invalidHours; + } + if (angular.isDefined(invalidMinutes)) { + $scope.invalidMinutes = invalidMinutes; + } + }; + + $scope.updateHours = function() { + var hours = getHoursFromTemplate(); + + if ( angular.isDefined(hours) ) { + selected.setHours( hours ); + refresh( 'h' ); + } else { + invalidate(true); + } + }; + + hoursInputEl.bind('blur', function(e) { + if ( !$scope.invalidHours && $scope.hours < 10) { + $scope.$apply( function() { + $scope.hours = pad( $scope.hours ); + }); + } + }); + + $scope.updateMinutes = function() { + var minutes = getMinutesFromTemplate(); + + if ( angular.isDefined(minutes) ) { + selected.setMinutes( minutes ); + refresh( 'm' ); + } else { + invalidate(undefined, true); + } + }; + + minutesInputEl.bind('blur', function(e) { + if ( !$scope.invalidMinutes && $scope.minutes < 10 ) { + $scope.$apply( function() { + $scope.minutes = pad( $scope.minutes ); + }); + } + }); + + }; + + this.render = function() { + var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null; + + if ( isNaN(date) ) { + ngModelCtrl.$setValidity('time', false); + $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } else { + if ( date ) { + selected = date; + } + makeValid(); + updateTemplate(); + } + }; + + // Call internally when we know that model is valid. + function refresh( keyboardChange ) { + makeValid(); + ngModelCtrl.$setViewValue( new Date(selected) ); + updateTemplate( keyboardChange ); + } + + function makeValid() { + ngModelCtrl.$setValidity('time', true); + $scope.invalidHours = false; + $scope.invalidMinutes = false; + } + + function updateTemplate( keyboardChange ) { + var hours = selected.getHours(), minutes = selected.getMinutes(); + + if ( $scope.showMeridian ) { + hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system + } + + $scope.hours = keyboardChange === 'h' ? hours : pad(hours); + $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); + $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + } + + function addMinutes( minutes ) { + var dt = new Date( selected.getTime() + minutes * 60000 ); + selected.setHours( dt.getHours(), dt.getMinutes() ); + refresh(); + } + + $scope.incrementHours = function() { + addMinutes( hourStep * 60 ); + }; + $scope.decrementHours = function() { + addMinutes( - hourStep * 60 ); + }; + $scope.incrementMinutes = function() { + addMinutes( minuteStep ); + }; + $scope.decrementMinutes = function() { + addMinutes( - minuteStep ); + }; + $scope.toggleMeridian = function() { + addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); + }; +}]) + +.directive('timepicker', function () { + return { + restrict: 'EA', + require: ['timepicker', '?^ngModel'], + controller:'TimepickerController', + replace: true, + scope: {}, + templateUrl: 'template/timepicker/timepicker.html', + link: function(scope, element, attrs, ctrls) { + var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + timepickerCtrl.init( ngModelCtrl, element.find('input') ); + } + } + }; +}); + +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) + +/** + * A helper service that can parse typeahead's syntax (string provided by users) + * Extracted to a separate service for ease of unit testing + */ + .factory('typeaheadParser', ['$parse', function ($parse) { + + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; + + return { + parse:function (input) { + + var match = input.match(TYPEAHEAD_REGEXP); + if (!match) { + throw new Error( + 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + + ' but got "' + input + '".'); + } + + return { + itemName:match[3], + source:$parse(match[4]), + viewMapper:$parse(match[2] || match[1]), + modelMapper:$parse(match[1]) + }; + } + }; +}]) + + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', + function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { + + var HOT_KEYS = [9, 13, 27, 38, 40]; + + return { + require:'ngModel', + link:function (originalScope, element, attrs, modelCtrl) { + + //SUPPORTED ATTRIBUTES (OPTIONS) + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; + + //minimal wait time after last character typed before typehead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); + + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + + var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; + + //INTERNAL VARIABLES + + //model setter executed upon match selection + var $setModelValue = $parse(attrs.ngModel).assign; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + var hasFocus; + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function(){ + scope.$destroy(); + }); + + // WAI-ARIA + var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + element.attr({ + 'aria-autocomplete': 'list', + 'aria-expanded': false, + 'aria-owns': popupId + }); + + //pop-up element used to display matches + var popUpEl = angular.element('
'); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; + + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + + var getMatchesAsync = function(inputValue) { + + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches.length > 0) { + + scope.activeIdx = 0; + scope.matches.length = 0; + + //transform labels + for(var i=0; i= minSearch) { + if (waitTime > 0) { + cancelPreviousTimeout(); + scheduleSearchWithTimeout(inputValue); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); + cancelPreviousTimeout(); + resetMatches(); + } + + if (isEditable) { + return inputValue; + } else { + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return inputValue; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } + } + }); + + modelCtrl.$formatters.push(function (modelValue) { + + var candidateViewValue, emptyViewValue; + var locals = {}; + + if (inputFormatter) { + + locals['$model'] = modelValue; + return inputFormatter(originalScope, locals); + + } else { + + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + } + }); + + scope.select = function (activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); + + resetMatches(); + + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + $timeout(function() { element[0].focus(); }, 0, false); + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function (evt) { + + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function (evt) { + hasFocus = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function (evt) { + if (element[0] !== evt.target) { + resetMatches(); + scope.$digest(); + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function(){ + $document.unbind('click', dismissClickHandler); + }); + + var $popup = $compile(popUpEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + } + }; + +}]) + + .directive('typeaheadPopup', function () { + return { + restrict:'EA', + scope:{ + matches:'=', + query:'=', + active:'=', + position:'=', + select:'&' + }, + replace:true, + templateUrl:'template/typeahead/typeahead-popup.html', + link:function (scope, element, attrs) { + + scope.templateUrl = attrs.templateUrl; + + scope.isOpen = function () { + return scope.matches.length > 0; + }; + + scope.isActive = function (matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function (matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function (activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }) + + .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { + return { + restrict:'EA', + scope:{ + index:'=', + match:'=', + query:'=' + }, + link:function (scope, element, attrs) { + var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; + $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ + element.replaceWith($compile(tplContent.trim())(scope)); + }); + } + }; + }]) + + .filter('typeaheadHighlight', function() { + + function escapeRegexp(queryToEscape) { + return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + } + + return function(matchItem, query) { + return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + }; + }); diff --git a/bower_components/angular-bootstrap/ui-bootstrap-2a9b8bdceb.min.js b/bower_components/angular-bootstrap/ui-bootstrap-2a9b8bdceb.min.js new file mode 100644 index 0000000..ffd1a9e --- /dev/null +++ b/bower_components/angular-bootstrap/ui-bootstrap-2a9b8bdceb.min.js @@ -0,0 +1,9 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.2 - 2014-09-26 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(a){return{link:function(b,c,d){function e(b){function d(){j===e&&(j=void 0)}var e=a(c,b);return j&&j.cancel(),j=e,e.then(d,d),e}function f(){k?(k=!1,g()):(c.removeClass("collapse").addClass("collapsing"),e({height:c[0].scrollHeight+"px"}).then(g))}function g(){c.removeClass("collapsing"),c.addClass("collapse in"),c.css({height:"auto"})}function h(){if(k)k=!1,i(),c.css({height:0});else{c.css({height:c[0].scrollHeight+"px"});{c[0].offsetWidth}c.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(i)}}function i(){c.removeClass("collapsing"),c.addClass("collapse")}var j,k=!0;b.$watch(d.collapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.$watch("isOpen",function(b){b&&d.closeOthers(a)}),a.toggleOpen=function(){a.isDisabled||(a.isOpen=!a.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$transition",function(a,b,c){function d(){e();var c=+a.interval;!isNaN(c)&&c>=0&&(g=b(f,c))}function e(){g&&(b.cancel(g),g=null)}function f(){h?(a.next(),d()):a.pause()}var g,h,i=this,j=i.slides=a.slides=[],k=-1;i.currentSlide=null;var l=!1;i.select=a.select=function(e,f){function g(){if(!l){if(i.currentSlide&&angular.isString(f)&&!a.noTransition&&e.$element){e.$element.addClass(f);{e.$element[0].offsetWidth}angular.forEach(j,function(a){angular.extend(a,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(e,{direction:f,active:!0,entering:!0}),angular.extend(i.currentSlide||{},{direction:f,leaving:!0}),a.$currentTransition=c(e.$element,{}),function(b,c){a.$currentTransition.then(function(){h(b,c)},function(){h(b,c)})}(e,i.currentSlide)}else h(e,i.currentSlide);i.currentSlide=e,k=m,d()}}function h(b,c){angular.extend(b,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(c||{},{direction:"",active:!1,leaving:!1,entering:!1}),a.$currentTransition=null}var m=j.indexOf(e);void 0===f&&(f=m>k?"next":"prev"),e&&e!==i.currentSlide&&(a.$currentTransition?(a.$currentTransition.cancel(),b(g)):g())},a.$on("$destroy",function(){l=!0}),i.indexOfSlide=function(a){return j.indexOf(a)},a.next=function(){var b=(k+1)%j.length;return a.$currentTransition?void 0:i.select(j[b],"next")},a.prev=function(){var b=0>k-1?j.length-1:k-1;return a.$currentTransition?void 0:i.select(j[b],"prev")},a.isActive=function(a){return i.currentSlide===a},a.$watch("interval",d),a.$on("$destroy",e),a.play=function(){h||(h=!0,d())},a.pause=function(){a.noPause||(h=!1,e())},i.addSlide=function(b,c){b.$element=c,j.push(b),1===j.length||b.active?(i.select(j[j.length-1]),1==j.length&&a.play()):b.active=!1},i.removeSlide=function(a){var b=j.indexOf(a);j.splice(b,1),j.length>0&&a.active?i.select(b>=j.length?j[b-1]:j[b]):k>b&&k--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$locale","orderByFilter",function(a,b){function c(a){var c=[],d=a.split("");return angular.forEach(e,function(b,e){var f=a.indexOf(e);if(f>-1){a=a.split(""),d[f]="("+b.regex+")",a[f]="$";for(var g=f+1,h=f+e.length;h>g;g++)d[g]="",a[g]="$";a=a.join(""),c.push({index:f,apply:b.apply})}}),{regex:new RegExp("^"+d.join("")+"$"),map:b(c,"index")}}function d(a,b,c){return 1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}this.parsers={};var e={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:a.DATETIME_FORMATS.MONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.MONTH.indexOf(b)}},MMM:{regex:a.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.SHORTMONTH.indexOf(b)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:a.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:a.DATETIME_FORMATS.SHORTDAY.join("|")}};this.parse=function(b,e){if(!angular.isString(b)||!e)return b;e=a.DATETIME_FORMATS[e]||e,this.parsers[e]||(this.parsers[e]=c(e));var f=this.parsers[e],g=f.regex,h=f.map,i=b.match(g);if(i&&i.length){for(var j,k={year:1900,month:0,date:1,hours:0},l=1,m=i.length;m>l;l++){var n=h[l-1];n.apply&&n.apply.call(k,i[l])}return d(k.year,k.month,k.date)&&(j=new Date(k.year,k.month,k.date,k.hours)),j}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$timeout","$log","dateFilter","datepickerConfig",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","minMode","maxMode","showWeeks","startingDay","yearRange"],function(c,e){i[c]=angular.isDefined(b[c])?8>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):h[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=h[d]?new Date(h[d]):null}),a.datepickerMode=a.datepickerMode||h.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),this.activeDate=angular.isDefined(b.initDate)?a.$parent.$eval(b.initDate):new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$modelValue){var a=new Date(j.$modelValue),b=!isNaN(a);b?this.activeDate=a:f.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'),j.$setValidity("date",b)}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$modelValue?new Date(j.$modelValue):null;j.$setValidity("date-disabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$modelValue?new Date(j.$modelValue):null;return{date:a,label:g(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$modelValue?new Date(j.$modelValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){e(function(){i.element[0].focus()},0,!1)};a.$on("datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate),k()}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):(a.toggleMode("up"===c?1:-1),k())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{datepickerMode:"=?",dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}).directive("daypicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(b,c,d,e){function f(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?i[b]:29}function g(a,b){var c=new Array(b),d=new Date(a),e=0;for(d.setHours(12);b>e;)c[e++]=new Date(d),d.setDate(d.getDate()+1);return c}function h(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}b.showWeeks=e.showWeeks,e.step={months:1},e.element=c;var i=[31,28,31,30,31,30,31,31,30,31,30,31];e._refreshView=function(){var c=e.activeDate.getFullYear(),d=e.activeDate.getMonth(),f=new Date(c,d,1),i=e.startingDay-f.getDay(),j=i>0?7-i:-i,k=new Date(f);j>0&&k.setDate(-j+1);for(var l=g(k,42),m=0;42>m;m++)l[m]=angular.extend(e.createDateObject(l[m],e.formatDay),{secondary:l[m].getMonth()!==d,uid:b.uniqueId+"-"+m});b.labels=new Array(7);for(var n=0;7>n;n++)b.labels[n]={abbr:a(l[n].date,e.formatDayHeader),full:a(l[n].date,"EEEE")};if(b.title=a(e.activeDate,e.formatDayTitle),b.rows=e.split(l,7),b.showWeeks){b.weekNumbers=[];for(var o=h(b.rows[0][0].date),p=b.rows.length;b.weekNumbers.push(o++)f;f++)c[f]=angular.extend(e.createDateObject(new Date(d,f,1),e.formatMonth),{uid:b.uniqueId+"-"+f});b.title=a(e.activeDate,e.formatMonthTitle),b.rows=e.split(c,3)},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},e.handleKeyDown=function(a){var b=e.activeDate.getMonth();if("left"===a)b-=1;else if("up"===a)b-=3;else if("right"===a)b+=1;else if("down"===a)b+=3;else if("pageup"===a||"pagedown"===a){var c=e.activeDate.getFullYear()+("pageup"===a?-1:1);e.activeDate.setFullYear(c)}else"home"===a?b=0:"end"===a&&(b=11);e.activeDate.setMonth(b)},e.refreshView()}}}]).directive("yearpicker",["dateFilter",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(a,b,c,d){function e(a){return parseInt((a-1)/f,10)*f+1}var f=d.yearRange;d.step={years:f},d.element=b,d._refreshView=function(){for(var b=new Array(f),c=0,g=e(d.activeDate.getFullYear());f>c;c++)b[c]=angular.extend(d.createDateObject(new Date(g+c,0,1),d.formatYear),{uid:a.uniqueId+"-"+c});a.title=[b[0].label,b[f-1].label].join(" - "),a.rows=d.split(b,5)},d.compare=function(a,b){return a.getFullYear()-b.getFullYear()},d.handleKeyDown=function(a){var b=d.activeDate.getFullYear();"left"===a?b-=1:"up"===a?b-=5:"right"===a?b+=1:"down"===a?b+=5:"pageup"===a||"pagedown"===a?b+=("pageup"===a?-1:1)*d.step.years:"home"===a?b=e(d.activeDate.getFullYear()):"end"===a&&(b=e(d.activeDate.getFullYear())+f-1),d.activeDate.setFullYear(b)},d.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","dateParser","datepickerPopupConfig",function(a,b,c,d,e,f,g){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&"},link:function(h,i,j,k){function l(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function m(a){if(a){if(angular.isDate(a)&&!isNaN(a))return k.$setValidity("date",!0),a;if(angular.isString(a)){var b=f.parse(a,n)||new Date(a);return isNaN(b)?void k.$setValidity("date",!1):(k.$setValidity("date",!0),b)}return void k.$setValidity("date",!1)}return k.$setValidity("date",!0),null}var n,o=angular.isDefined(j.closeOnDateSelection)?h.$parent.$eval(j.closeOnDateSelection):g.closeOnDateSelection,p=angular.isDefined(j.datepickerAppendToBody)?h.$parent.$eval(j.datepickerAppendToBody):g.appendToBody;h.showButtonBar=angular.isDefined(j.showButtonBar)?h.$parent.$eval(j.showButtonBar):g.showButtonBar,h.getText=function(a){return h[a+"Text"]||g[a+"Text"]},j.$observe("datepickerPopup",function(a){n=a||g.datepickerPopup,k.$render()});var q=angular.element("
");q.attr({"ng-model":"date","ng-change":"dateSelection()"});var r=angular.element(q.children()[0]);j.datepickerOptions&&angular.forEach(h.$parent.$eval(j.datepickerOptions),function(a,b){r.attr(l(b),a)}),h.watchData={},angular.forEach(["minDate","maxDate","datepickerMode"],function(a){if(j[a]){var c=b(j[a]);if(h.$parent.$watch(c,function(b){h.watchData[a]=b}),r.attr(l(a),"watchData."+a),"datepickerMode"===a){var d=c.assign;h.$watch("watchData."+a,function(a,b){a!==b&&d(h.$parent,a)})}}}),j.dateDisabled&&r.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),k.$parsers.unshift(m),h.dateSelection=function(a){angular.isDefined(a)&&(h.date=a),k.$setViewValue(h.date),k.$render(),o&&(h.isOpen=!1,i[0].focus())},i.bind("input change keyup",function(){h.$apply(function(){h.date=k.$modelValue})}),k.$render=function(){var a=k.$viewValue?e(k.$viewValue,n):"";i.val(a),h.date=m(k.$modelValue)};var s=function(a){h.isOpen&&a.target!==i[0]&&h.$apply(function(){h.isOpen=!1})},t=function(a){h.keydown(a)};i.bind("keydown",t),h.keydown=function(a){27===a.which?(a.preventDefault(),a.stopPropagation(),h.close()):40!==a.which||h.isOpen||(h.isOpen=!0)},h.$watch("isOpen",function(a){a?(h.$broadcast("datepicker.focus"),h.position=p?d.offset(i):d.position(i),h.position.top=h.position.top+i.prop("offsetHeight"),c.bind("click",s)):c.unbind("click",s)}),h.select=function(a){if("today"===a){var b=new Date;angular.isDate(k.$modelValue)?(a=new Date(k.$modelValue),a.setFullYear(b.getFullYear(),b.getMonth(),b.getDate())):a=new Date(b.setHours(0,0,0,0))}h.dateSelection(a)},h.close=function(){h.isOpen=!1,i[0].focus()};var u=a(q)(h);q.remove(),p?c.find("body").append(u):i.after(u),h.$on("$destroy",function(){u.remove(),i.unbind("keydown",t),c.unbind("click",s)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(a,b){b.bind("click",function(a){a.preventDefault(),a.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function(a){var b=null;this.open=function(e){b||(a.bind("click",c),a.bind("keydown",d)),b&&b!==e&&(b.isOpen=!1),b=e},this.close=function(e){b===e&&(b=null,a.unbind("click",c),a.unbind("keydown",d))};var c=function(a){var c=b.getToggleElement();a&&c&&c[0].contains(a.target)||b.$apply(function(){b.isOpen=!1})},d=function(a){27===a.which&&(b.focusToggleElement(),c())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function(a,b,c,d,e,f){var g,h=this,i=a.$new(),j=d.openClass,k=angular.noop,l=b.onToggle?c(b.onToggle):angular.noop;this.init=function(d){h.$element=d,b.isOpen&&(g=c(b.isOpen),k=g.assign,a.$watch(g,function(a){i.isOpen=!!a}))},this.toggle=function(a){return i.isOpen=arguments.length?!!a:!i.isOpen},this.isOpen=function(){return i.isOpen},i.getToggleElement=function(){return h.toggleElement},i.focusToggleElement=function(){h.toggleElement&&h.toggleElement[0].focus()},i.$watch("isOpen",function(b,c){f[b?"addClass":"removeClass"](h.$element,j),b?(i.focusToggleElement(),e.open(i)):e.close(i),k(a,b),angular.isDefined(b)&&b!==c&&l(a,{open:!!b})}),a.$on("$locationChangeSuccess",function(){i.isOpen=!1}),a.$on("$destroy",function(){i.$destroy()})}]).directive("dropdown",function(){return{restrict:"CA",controller:"DropdownController",link:function(a,b,c,d){d.init(b)}}}).directive("dropdownToggle",function(){return{restrict:"CA",require:"?^dropdown",link:function(a,b,c,d){if(d){d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0),i()})}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&(a.preventDefault(),e.$apply(function(){o.dismiss(b.key,"escape key press")})))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();if(h>=0&&!k){l=e.$new(!0),l.index=h;var i=angular.element("
");i.attr("backdrop-class",b.backdropClass),k=d(i)(l),f.append(k)}var j=angular.element("
");j.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,size:b.size,index:n.length()-1,animate:"animate"}).html(b.content);var o=d(j)(b.scope);n.top().value.modalDomEl=o,f.append(o),f.addClass(m)},o.close=function(a,b){var c=n.get(a);c&&(c.value.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a);c&&(c.value.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,backdropClass:b.backdropClass,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(f,g){e=f,this.config=g,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=g.itemsPerPage},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b){a.page!==b&&b>0&&b<=a.totalPages&&(e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages},a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render()});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@"},require:["pager","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a) +},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!y||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?v||(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return v=null,u&&(g.cancel(u),u=null),b.tt_content?(r(),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),z(),b.tt_isOpen=!0,b.$digest(),z):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),v=null,b.tt_animation?u||(u=g(s,500)):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){u=null,t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=angular.isDefined(d[l+"Enable"]),z=function(){var a=j.positionElements(c,t,b.tt_placement,w);a.top+="px",a.left+="px",t.css(a)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var A=function(){c.unbind(x.show,k),c.unbind(x.hide,m)};d.$observe(l+"Trigger",function(a){A(),x=n(a),x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m))});var B=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(B)?!!B:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),A(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,this.addBar=function(b,c){e||c.css({transition:"none"}),this.bars.push(b),b.$watch("value",function(c){b.percent=+(100*c/a.max).toFixed(2)}),b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(f)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff},a[b]);return a},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect())}),a.active=!0,a.onSelect()},b.addTab=function(a){c.push(a),1===c.length?a.active=!0:a.active&&b.select(a)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){b.$watch("active",function(a){a&&f.select(b)}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function(a,b,c,d,e,f){function g(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===p[1]&&(b+=12)),b):void 0}function h(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function i(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a}function j(a){k(),o.$setViewValue(new Date(n)),l(a)}function k(){o.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function l(b){var c=n.getHours(),d=n.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:i(c),a.minutes="m"===b?d:i(d),a.meridian=n.getHours()<12?p[0]:p[1]}function m(a){var b=new Date(n.getTime()+6e4*a);n.setHours(b.getHours(),b.getMinutes()),j()}var n=new Date,o={$setViewValue:angular.noop},p=angular.isDefined(b.meridians)?a.$parent.$eval(b.meridians):f.meridians||e.DATETIME_FORMATS.AMPMS;this.init=function(c,d){o=c,o.$render=this.render;var e=d.eq(0),g=d.eq(1),h=angular.isDefined(b.mousewheel)?a.$parent.$eval(b.mousewheel):f.mousewheel;h&&this.setupMousewheelEvents(e,g),a.readonlyInput=angular.isDefined(b.readonlyInput)?a.$parent.$eval(b.readonlyInput):f.readonlyInput,this.setupInputEvents(e,g)};var q=f.hourStep;b.hourStep&&a.$parent.$watch(c(b.hourStep),function(a){q=parseInt(a,10)});var r=f.minuteStep;b.minuteStep&&a.$parent.$watch(c(b.minuteStep),function(a){r=parseInt(a,10)}),a.showMeridian=f.showMeridian,b.showMeridian&&a.$parent.$watch(c(b.showMeridian),function(b){if(a.showMeridian=!!b,o.$error.time){var c=g(),d=h();angular.isDefined(c)&&angular.isDefined(d)&&(n.setHours(c),j())}else l()}),this.setupMousewheelEvents=function(b,c){var d=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){o.$setViewValue(null),o.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=g();angular.isDefined(a)?(n.setHours(a),j("h")):d(!0)},b.bind("blur",function(){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=i(a.hours)})}),a.updateMinutes=function(){var a=h();angular.isDefined(a)?(n.setMinutes(a),j("m")):d(void 0,!0)},c.bind("blur",function(){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=i(a.minutes)})})},this.render=function(){var a=o.$modelValue?new Date(o.$modelValue):null;isNaN(a)?(o.$setValidity("time",!1),d.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(a&&(n=a),k(),l())},a.incrementHours=function(){m(60*q)},a.decrementHours=function(){m(60*-q)},a.incrementMinutes=function(){m(r)},a.decrementMinutes=function(){m(-r)},a.toggleMeridian=function(){m(720*(n.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",replace:!0,scope:{},templateUrl:"template/timepicker/timepicker.html",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?i.$eval(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=i.$new();i.$on("$destroy",function(){w.$destroy()});var x="typeahead-"+w.$id+"-"+Math.floor(1e4*Math.random());j.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":x});var y=angular.element("
");y.attr({id:x,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&y.attr("template-url",k.typeaheadTemplateUrl);var z=function(){w.matches=[],w.activeIdx=-1,j.attr("aria-expanded",!1)},A=function(a){return x+"-option-"+a};w.$watch("activeIdx",function(a){0>a?j.removeAttr("aria-activedescendant"):j.attr("aria-activedescendant",A(a))});var B=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){var d=a===l.$viewValue;if(d&&m)if(c.length>0){w.activeIdx=0,w.matches.length=0;for(var e=0;e=n?o>0?(E(),D(a)):B(a):(q(i,!1),E(),z()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),w.select=function(a){var b,c,e={};e[v.itemName]=c=w.matches[a].model,b=v.modelMapper(i,e),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,e)}),z(),d(function(){j[0].focus()},0,!1)},j.bind("keydown",function(a){0!==w.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(w.activeIdx=(w.activeIdx+1)%w.matches.length,w.$digest()):38===a.which?(w.activeIdx=(w.activeIdx?w.activeIdx:w.matches.length)-1,w.$digest()):13===a.which||9===a.which?w.$apply(function(){w.select(w.activeIdx)}):27===a.which&&(a.stopPropagation(),z(),w.$digest()))}),j.bind("blur",function(){m=!1});var F=function(a){j[0]!==a.target&&(z(),w.$digest())};e.bind("click",F),i.$on("$destroy",function(){e.unbind("click",F)});var G=a(y)(w);t?e.find("body").append(G):j.after(G)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?(""+b).replace(new RegExp(a(c),"gi"),"$&"):b}}); \ No newline at end of file diff --git a/bower_components/angular-bootstrap/ui-bootstrap-tpls-cb73122a95.js b/bower_components/angular-bootstrap/ui-bootstrap-tpls-cb73122a95.js new file mode 100644 index 0000000..260c276 --- /dev/null +++ b/bower_components/angular-bootstrap/ui-bootstrap-tpls-cb73122a95.js @@ -0,0 +1,4167 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.2 - 2014-09-26 + * License: MIT + */ +angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); +angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]); +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) + + .directive('collapse', ['$transition', function ($transition) { + + return { + link: function (scope, element, attrs) { + + var initialAnimSkip = true; + var currentTransition; + + function doTransition(change) { + var newTransition = $transition(element, change); + if (currentTransition) { + currentTransition.cancel(); + } + currentTransition = newTransition; + newTransition.then(newTransitionDone, newTransitionDone); + return newTransition; + + function newTransitionDone() { + // Make sure it's this transition, otherwise, leave it alone. + if (currentTransition === newTransition) { + currentTransition = undefined; + } + } + } + + function expand() { + if (initialAnimSkip) { + initialAnimSkip = false; + expandDone(); + } else { + element.removeClass('collapse').addClass('collapsing'); + doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); + } + } + + function expandDone() { + element.removeClass('collapsing'); + element.addClass('collapse in'); + element.css({height: 'auto'}); + } + + function collapse() { + if (initialAnimSkip) { + initialAnimSkip = false; + collapseDone(); + element.css({height: 0}); + } else { + // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value + element.css({ height: element[0].scrollHeight + 'px' }); + //trigger reflow so a browser realizes that height was updated from auto to a specific value + var x = element[0].offsetWidth; + + element.removeClass('collapse in').addClass('collapsing'); + + doTransition({ height: 0 }).then(collapseDone); + } + } + + function collapseDone() { + element.removeClass('collapsing'); + element.addClass('collapse'); + } + + scope.$watch(attrs.collapse, function (shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) + +.constant('accordionConfig', { + closeOthers: true +}) + +.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { + + // This array keeps track of the accordion groups + this.groups = []; + + // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to + this.closeOthers = function(openGroup) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if ( closeOthers ) { + angular.forEach(this.groups, function (group) { + if ( group !== openGroup ) { + group.isOpen = false; + } + }); + } + }; + + // This is called from the accordion-group directive to add itself to the accordion + this.addGroup = function(groupScope) { + var that = this; + this.groups.push(groupScope); + + groupScope.$on('$destroy', function (event) { + that.removeGroup(groupScope); + }); + }; + + // This is called from the accordion-group directive when to remove itself + this.removeGroup = function(group) { + var index = this.groups.indexOf(group); + if ( index !== -1 ) { + this.groups.splice(index, 1); + } + }; + +}]) + +// The accordion directive simply sets up the directive controller +// and adds an accordion CSS class to itself element. +.directive('accordion', function () { + return { + restrict:'EA', + controller:'AccordionController', + transclude: true, + replace: false, + templateUrl: 'template/accordion/accordion.html' + }; +}) + +// The accordion-group directive indicates a block of html that will expand and collapse in an accordion +.directive('accordionGroup', function() { + return { + require:'^accordion', // We need this directive to be inside an accordion + restrict:'EA', + transclude:true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl:'template/accordion/accordion-group.html', + scope: { + heading: '@', // Interpolate the heading attribute onto this scope + isOpen: '=?', + isDisabled: '=?' + }, + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + accordionCtrl.addGroup(scope); + + scope.$watch('isOpen', function(value) { + if ( value ) { + accordionCtrl.closeOthers(scope); + } + }); + + scope.toggleOpen = function() { + if ( !scope.isDisabled ) { + scope.isOpen = !scope.isOpen; + } + }; + } + }; +}) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +// +// Heading containing HTML - +// +.directive('accordionHeading', function() { + return { + restrict: 'EA', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + link: function(scope, element, attr, accordionGroupCtrl, transclude) { + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, function() {})); + } + }; +}) + +// Use in the accordion-group template to indicate where you want the heading to be transcluded +// You must provide the property on the accordion-group controller that will hold the transcluded element +//
+// +// ... +//
+.directive('accordionTransclude', function() { + return { + require: '^accordionGroup', + link: function(scope, element, attr, controller) { + scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { + if ( heading ) { + element.html(''); + element.append(heading); + } + }); + } + }; +}); + +angular.module('ui.bootstrap.alert', []) + +.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { + $scope.closeable = 'close' in $attrs; +}]) + +.directive('alert', function () { + return { + restrict:'EA', + controller:'AlertController', + templateUrl:'template/alert/alert.html', + transclude:true, + replace:true, + scope: { + type: '@', + close: '&' + } + }; +}); + +angular.module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function () { + return function (scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +angular.module('ui.bootstrap.buttons', []) + +.constant('buttonConfig', { + activeClass: 'active', + toggleEvent: 'click' +}) + +.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { + this.activeClass = buttonConfig.activeClass || 'active'; + this.toggleEvent = buttonConfig.toggleEvent || 'click'; +}]) + +.directive('btnRadio', function () { + return { + require: ['btnRadio', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + var isActive = element.hasClass(buttonsCtrl.activeClass); + + if (!isActive || angular.isDefined(attrs.uncheckable)) { + scope.$apply(function () { + ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); + ngModelCtrl.$render(); + }); + } + }); + } + }; +}) + +.directive('btnCheckbox', function () { + return { + require: ['btnCheckbox', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + function getTrueValue() { + return getCheckboxValue(attrs.btnCheckboxTrue, true); + } + + function getFalseValue() { + return getCheckboxValue(attrs.btnCheckboxFalse, false); + } + + function getCheckboxValue(attributeValue, defaultValue) { + var val = scope.$eval(attributeValue); + return angular.isDefined(val) ? val : defaultValue; + } + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + scope.$apply(function () { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + } + }; +}); + +/** +* @ngdoc overview +* @name ui.bootstrap.carousel +* +* @description +* AngularJS version of an image carousel. +* +*/ +angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) +.controller('CarouselController', ['$scope', '$timeout', '$transition', function ($scope, $timeout, $transition) { + var self = this, + slides = self.slides = $scope.slides = [], + currentIndex = -1, + currentTimeout, isPlaying; + self.currentSlide = null; + + var destroyed = false; + /* direction: "prev" or "next" */ + self.select = $scope.select = function(nextSlide, direction) { + var nextIndex = slides.indexOf(nextSlide); + //Decide direction if it's not given + if (direction === undefined) { + direction = nextIndex > currentIndex ? 'next' : 'prev'; + } + if (nextSlide && nextSlide !== self.currentSlide) { + if ($scope.$currentTransition) { + $scope.$currentTransition.cancel(); + //Timeout so ng-class in template has time to fix classes for finished slide + $timeout(goNext); + } else { + goNext(); + } + } + function goNext() { + // Scope has been destroyed, stop here. + if (destroyed) { return; } + //If we have a slide to transition from and we have a transition type and we're allowed, go + if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { + //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime + nextSlide.$element.addClass(direction); + var reflow = nextSlide.$element[0].offsetWidth; //force reflow + + //Set all other slides to stop doing their stuff for the new transition + angular.forEach(slides, function(slide) { + angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); + }); + angular.extend(nextSlide, {direction: direction, active: true, entering: true}); + angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); + + $scope.$currentTransition = $transition(nextSlide.$element, {}); + //We have to create new pointers inside a closure since next & current will change + (function(next,current) { + $scope.$currentTransition.then( + function(){ transitionDone(next, current); }, + function(){ transitionDone(next, current); } + ); + }(nextSlide, self.currentSlide)); + } else { + transitionDone(nextSlide, self.currentSlide); + } + self.currentSlide = nextSlide; + currentIndex = nextIndex; + //every time you change slides, reset the timer + restartTimer(); + } + function transitionDone(next, current) { + angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); + angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); + $scope.$currentTransition = null; + } + }; + $scope.$on('$destroy', function () { + destroyed = true; + }); + + /* Allow outside people to call indexOf on slides array */ + self.indexOfSlide = function(slide) { + return slides.indexOf(slide); + }; + + $scope.next = function() { + var newIndex = (currentIndex + 1) % slides.length; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'next'); + } + }; + + $scope.prev = function() { + var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'prev'); + } + }; + + $scope.isActive = function(slide) { + return self.currentSlide === slide; + }; + + $scope.$watch('interval', restartTimer); + $scope.$on('$destroy', resetTimer); + + function restartTimer() { + resetTimer(); + var interval = +$scope.interval; + if (!isNaN(interval) && interval>=0) { + currentTimeout = $timeout(timerFn, interval); + } + } + + function resetTimer() { + if (currentTimeout) { + $timeout.cancel(currentTimeout); + currentTimeout = null; + } + } + + function timerFn() { + if (isPlaying) { + $scope.next(); + restartTimer(); + } else { + $scope.pause(); + } + } + + $scope.play = function() { + if (!isPlaying) { + isPlaying = true; + restartTimer(); + } + }; + $scope.pause = function() { + if (!$scope.noPause) { + isPlaying = false; + resetTimer(); + } + }; + + self.addSlide = function(slide, element) { + slide.$element = element; + slides.push(slide); + //if this is the first slide or the slide is set to active, select it + if(slides.length === 1 || slide.active) { + self.select(slides[slides.length-1]); + if (slides.length == 1) { + $scope.play(); + } + } else { + slide.active = false; + } + }; + + self.removeSlide = function(slide) { + //get the index of the slide inside the carousel + var index = slides.indexOf(slide); + slides.splice(index, 1); + if (slides.length > 0 && slide.active) { + if (index >= slides.length) { + self.select(slides[index-1]); + } else { + self.select(slides[index]); + } + } else if (currentIndex > index) { + currentIndex--; + } + }; + +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:carousel + * @restrict EA + * + * @description + * Carousel is the outer container for a set of image 'slides' to showcase. + * + * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. + * @param {boolean=} noTransition Whether to disable transitions on the carousel. + * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). + * + * @example + + + + + + + + + + + + + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + + + */ +.directive('carousel', [function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + controller: 'CarouselController', + require: 'carousel', + templateUrl: 'template/carousel/carousel.html', + scope: { + interval: '=', + noTransition: '=', + noPause: '=' + } + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:slide + * @restrict EA + * + * @description + * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. + * + * @param {boolean=} active Model binding, whether or not this slide is currently active. + * + * @example + + +
+ + + + + + + Interval, in milliseconds: +
Enter a negative number to stop the interval. +
+
+ +function CarouselDemoCtrl($scope) { + $scope.myInterval = 5000; +} + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + +
+*/ + +.directive('slide', function() { + return { + require: '^carousel', + restrict: 'EA', + transclude: true, + replace: true, + templateUrl: 'template/carousel/slide.html', + scope: { + active: '=?' + }, + link: function (scope, element, attrs, carouselCtrl) { + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}); + +angular.module('ui.bootstrap.dateparser', []) + +.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) { + + this.parsers = {}; + + var formatCodeToRegex = { + 'yyyy': { + regex: '\\d{4}', + apply: function(value) { this.year = +value; } + }, + 'yy': { + regex: '\\d{2}', + apply: function(value) { this.year = +value + 2000; } + }, + 'y': { + regex: '\\d{1,4}', + apply: function(value) { this.year = +value; } + }, + 'MMMM': { + regex: $locale.DATETIME_FORMATS.MONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } + }, + 'MMM': { + regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } + }, + 'MM': { + regex: '0[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'M': { + regex: '[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'dd': { + regex: '[0-2][0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'd': { + regex: '[1-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'EEEE': { + regex: $locale.DATETIME_FORMATS.DAY.join('|') + }, + 'EEE': { + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') + } + }; + + function createParser(format) { + var map = [], regex = format.split(''); + + angular.forEach(formatCodeToRegex, function(data, code) { + var index = format.indexOf(code); + + if (index > -1) { + format = format.split(''); + + regex[index] = '(' + data.regex + ')'; + format[index] = '$'; // Custom symbol to define consumed part of format + for (var i = index + 1, n = index + code.length; i < n; i++) { + regex[i] = ''; + format[i] = '$'; + } + format = format.join(''); + + map.push({ index: index, apply: data.apply }); + } + }); + + return { + regex: new RegExp('^' + regex.join('') + '$'), + map: orderByFilter(map, 'index') + }; + } + + this.parse = function(input, format) { + if ( !angular.isString(input) || !format ) { + return input; + } + + format = $locale.DATETIME_FORMATS[format] || format; + + if ( !this.parsers[format] ) { + this.parsers[format] = createParser(format); + } + + var parser = this.parsers[format], + regex = parser.regex, + map = parser.map, + results = input.match(regex); + + if ( results && results.length ) { + var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt; + + for( var i = 1, n = results.length; i < n; i++ ) { + var mapper = map[i-1]; + if ( mapper.apply ) { + mapper.apply.call(fields, results[i]); + } + } + + if ( isValid(fields.year, fields.month, fields.date) ) { + dt = new Date( fields.year, fields.month, fields.date, fields.hours); + } + + return dt; + } + }; + + // Check if date is valid for specific month (and year for February). + // Month: 0 = Jan, 1 = Feb, etc + function isValid(year, month, date) { + if ( month === 1 && date > 28) { + return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); + } + + if ( month === 3 || month === 5 || month === 8 || month === 10) { + return date < 31; + } + + return true; + } +}]); + +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, 'position') || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) + }; + }, + + /** + * Provides coordinates for the targetEl in relation to hostEl + */ + positionElements: function (hostEl, targetEl, positionStr, appendToBody) { + + var positionStrParts = positionStr.split('-'); + var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; + + var hostElPos, + targetElWidth, + targetElHeight, + targetElPos; + + hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + + targetElWidth = targetEl.prop('offsetWidth'); + targetElHeight = targetEl.prop('offsetHeight'); + + var shiftWidth = { + center: function () { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function () { + return hostElPos.left; + }, + right: function () { + return hostElPos.left + hostElPos.width; + } + }; + + var shiftHeight = { + center: function () { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function () { + return hostElPos.top; + }, + bottom: function () { + return hostElPos.top + hostElPos.height; + } + }; + + switch (pos0) { + case 'right': + targetElPos = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0]() + }; + break; + case 'left': + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth + }; + break; + case 'bottom': + targetElPos = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1]() + }; + break; + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElPos; + } + }; + }]); + +angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) + +.constant('datepickerConfig', { + formatDay: 'dd', + formatMonth: 'MMMM', + formatYear: 'yyyy', + formatDayHeader: 'EEE', + formatDayTitle: 'MMMM yyyy', + formatMonthTitle: 'yyyy', + datepickerMode: 'day', + minMode: 'day', + maxMode: 'year', + showWeeks: true, + startingDay: 0, + yearRange: 20, + minDate: null, + maxDate: null +}) + +.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; + + // Modes chain + this.modes = ['day', 'month', 'year']; + + // Configuration attributes + angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', + 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) { + self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; + }); + + // Watchable date attributes + angular.forEach(['minDate', 'maxDate'], function( key ) { + if ( $attrs[key] ) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = value ? new Date(value) : null; + self.refreshView(); + }); + } else { + self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; + } + }); + + $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; + $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); + this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date(); + + $scope.isActive = function(dateObject) { + if (self.compare(dateObject.date, self.activeDate) === 0) { + $scope.activeDateId = dateObject.uid; + return true; + } + return false; + }; + + this.init = function( ngModelCtrl_ ) { + ngModelCtrl = ngModelCtrl_; + + ngModelCtrl.$render = function() { + self.render(); + }; + }; + + this.render = function() { + if ( ngModelCtrl.$modelValue ) { + var date = new Date( ngModelCtrl.$modelValue ), + isValid = !isNaN(date); + + if ( isValid ) { + this.activeDate = date; + } else { + $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } + ngModelCtrl.$setValidity('date', isValid); + } + this.refreshView(); + }; + + this.refreshView = function() { + if ( this.element ) { + this._refreshView(); + + var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); + } + }; + + this.createDateObject = function(date, format) { + var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + return { + date: date, + label: dateFilter(date, format), + selected: model && this.compare(date, model) === 0, + disabled: this.isDisabled(date), + current: this.compare(date, new Date()) === 0 + }; + }; + + this.isDisabled = function( date ) { + return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); + }; + + // Split array into smaller arrays + this.split = function(arr, size) { + var arrays = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + }; + + $scope.select = function( date ) { + if ( $scope.datepickerMode === self.minMode ) { + var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); + ngModelCtrl.$setViewValue( dt ); + ngModelCtrl.$render(); + } else { + self.activeDate = date; + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ]; + } + }; + + $scope.move = function( direction ) { + var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), + month = self.activeDate.getMonth() + direction * (self.step.months || 0); + self.activeDate.setFullYear(year, month, 1); + self.refreshView(); + }; + + $scope.toggleMode = function( direction ) { + direction = direction || 1; + + if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { + return; + } + + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ]; + }; + + // Key event mapper + $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' }; + + var focusElement = function() { + $timeout(function() { + self.element[0].focus(); + }, 0 , false); + }; + + // Listen for focus requests from popup directive + $scope.$on('datepicker.focus', focusElement); + + $scope.keydown = function( evt ) { + var key = $scope.keys[evt.which]; + + if ( !key || evt.shiftKey || evt.altKey ) { + return; + } + + evt.preventDefault(); + evt.stopPropagation(); + + if (key === 'enter' || key === 'space') { + if ( self.isDisabled(self.activeDate)) { + return; // do nothing + } + $scope.select(self.activeDate); + focusElement(); + } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { + $scope.toggleMode(key === 'up' ? 1 : -1); + focusElement(); + } else { + self.handleKeyDown(key, evt); + self.refreshView(); + } + }; +}]) + +.directive( 'datepicker', function () { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/datepicker.html', + scope: { + datepickerMode: '=?', + dateDisabled: '&' + }, + require: ['datepicker', '?^ngModel'], + controller: 'DatepickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + datepickerCtrl.init( ngModelCtrl ); + } + } + }; +}) + +.directive('daypicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/day.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + scope.showWeeks = ctrl.showWeeks; + + ctrl.step = { months: 1 }; + ctrl.element = element; + + var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + function getDaysInMonth( year, month ) { + return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; + } + + function getDates(startDate, n) { + var dates = new Array(n), current = new Date(startDate), i = 0; + current.setHours(12); // Prevent repeated dates because of timezone bug + while ( i < n ) { + dates[i++] = new Date(current); + current.setDate( current.getDate() + 1 ); + } + return dates; + } + + ctrl._refreshView = function() { + var year = ctrl.activeDate.getFullYear(), + month = ctrl.activeDate.getMonth(), + firstDayOfMonth = new Date(year, month, 1), + difference = ctrl.startingDay - firstDayOfMonth.getDay(), + numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, + firstDate = new Date(firstDayOfMonth); + + if ( numDisplayedFromPreviousMonth > 0 ) { + firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); + } + + // 42 is the number of days on a six-month calendar + var days = getDates(firstDate, 42); + for (var i = 0; i < 42; i ++) { + days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), { + secondary: days[i].getMonth() !== month, + uid: scope.uniqueId + '-' + i + }); + } + + scope.labels = new Array(7); + for (var j = 0; j < 7; j++) { + scope.labels[j] = { + abbr: dateFilter(days[j].date, ctrl.formatDayHeader), + full: dateFilter(days[j].date, 'EEEE') + }; + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); + scope.rows = ctrl.split(days, 7); + + if ( scope.showWeeks ) { + scope.weekNumbers = []; + var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ), + numWeeks = scope.rows.length; + while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {} + } + }; + + ctrl.compare = function(date1, date2) { + return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); + }; + + function getISO8601WeekNumber(date) { + var checkDate = new Date(date); + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + } + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getDate(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 7; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 7; + } else if (key === 'pageup' || key === 'pagedown') { + var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setMonth(month, 1); + date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); + } else if (key === 'home') { + date = 1; + } else if (key === 'end') { + date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); + } + ctrl.activeDate.setDate(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('monthpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/month.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + ctrl.step = { years: 1 }; + ctrl.element = element; + + ctrl._refreshView = function() { + var months = new Array(12), + year = ctrl.activeDate.getFullYear(); + + for ( var i = 0; i < 12; i++ ) { + months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); + scope.rows = ctrl.split(months, 3); + }; + + ctrl.compare = function(date1, date2) { + return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getMonth(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 3; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 3; + } else if (key === 'pageup' || key === 'pagedown') { + var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setFullYear(year); + } else if (key === 'home') { + date = 0; + } else if (key === 'end') { + date = 11; + } + ctrl.activeDate.setMonth(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('yearpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/year.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + var range = ctrl.yearRange; + + ctrl.step = { years: range }; + ctrl.element = element; + + function getStartingYear( year ) { + return parseInt((year - 1) / range, 10) * range + 1; + } + + ctrl._refreshView = function() { + var years = new Array(range); + + for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) { + years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = [years[0].label, years[range - 1].label].join(' - '); + scope.rows = ctrl.split(years, 5); + }; + + ctrl.compare = function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getFullYear(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 5; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 5; + } else if (key === 'pageup' || key === 'pagedown') { + date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; + } else if (key === 'home') { + date = getStartingYear( ctrl.activeDate.getFullYear() ); + } else if (key === 'end') { + date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1; + } + ctrl.activeDate.setFullYear(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.constant('datepickerPopupConfig', { + datepickerPopup: 'yyyy-MM-dd', + currentText: 'Today', + clearText: 'Clear', + closeText: 'Done', + closeOnDateSelection: true, + appendToBody: false, + showButtonBar: true +}) + +.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', +function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { + return { + restrict: 'EA', + require: 'ngModel', + scope: { + isOpen: '=?', + currentText: '@', + clearText: '@', + closeText: '@', + dateDisabled: '&' + }, + link: function(scope, element, attrs, ngModel) { + var dateFormat, + closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, + appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + + scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; + + scope.getText = function( key ) { + return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; + }; + + attrs.$observe('datepickerPopup', function(value) { + dateFormat = value || datepickerPopupConfig.datepickerPopup; + ngModel.$render(); + }); + + // popup element used to display calendar + var popupEl = angular.element('
'); + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection()' + }); + + function cameltoDash( string ){ + return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); + } + + // datepicker element + var datepickerEl = angular.element(popupEl.children()[0]); + if ( attrs.datepickerOptions ) { + angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) { + datepickerEl.attr( cameltoDash(option), value ); + }); + } + + scope.watchData = {}; + angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function( key ) { + if ( attrs[key] ) { + var getAttribute = $parse(attrs[key]); + scope.$parent.$watch(getAttribute, function(value){ + scope.watchData[key] = value; + }); + datepickerEl.attr(cameltoDash(key), 'watchData.' + key); + + // Propagate changes from datepicker to outside + if ( key === 'datepickerMode' ) { + var setAttribute = getAttribute.assign; + scope.$watch('watchData.' + key, function(value, oldvalue) { + if ( value !== oldvalue ) { + setAttribute(scope.$parent, value); + } + }); + } + } + }); + if (attrs.dateDisabled) { + datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); + } + + function parseDate(viewValue) { + if (!viewValue) { + ngModel.$setValidity('date', true); + return null; + } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { + ngModel.$setValidity('date', true); + return viewValue; + } else if (angular.isString(viewValue)) { + var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue); + if (isNaN(date)) { + ngModel.$setValidity('date', false); + return undefined; + } else { + ngModel.$setValidity('date', true); + return date; + } + } else { + ngModel.$setValidity('date', false); + return undefined; + } + } + ngModel.$parsers.unshift(parseDate); + + // Inner change + scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + scope.date = dt; + } + ngModel.$setViewValue(scope.date); + ngModel.$render(); + + if ( closeOnDateSelection ) { + scope.isOpen = false; + element[0].focus(); + } + }; + + element.bind('input change keyup', function() { + scope.$apply(function() { + scope.date = ngModel.$modelValue; + }); + }); + + // Outter change + ngModel.$render = function() { + var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; + element.val(date); + scope.date = parseDate( ngModel.$modelValue ); + }; + + var documentClickBind = function(event) { + if (scope.isOpen && event.target !== element[0]) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + }; + + var keydown = function(evt, noApply) { + scope.keydown(evt); + }; + element.bind('keydown', keydown); + + scope.keydown = function(evt) { + if (evt.which === 27) { + evt.preventDefault(); + evt.stopPropagation(); + scope.close(); + } else if (evt.which === 40 && !scope.isOpen) { + scope.isOpen = true; + } + }; + + scope.$watch('isOpen', function(value) { + if (value) { + scope.$broadcast('datepicker.focus'); + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); + + $document.bind('click', documentClickBind); + } else { + $document.unbind('click', documentClickBind); + } + }); + + scope.select = function( date ) { + if (date === 'today') { + var today = new Date(); + if (angular.isDate(ngModel.$modelValue)) { + date = new Date(ngModel.$modelValue); + date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); + } else { + date = new Date(today.setHours(0, 0, 0, 0)); + } + } + scope.dateSelection( date ); + }; + + scope.close = function() { + scope.isOpen = false; + element[0].focus(); + }; + + var $popup = $compile(popupEl)(scope); + // Prevent jQuery cache memory leak (template is now redundant after linking) + popupEl.remove(); + + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + + scope.$on('$destroy', function() { + $popup.remove(); + element.unbind('keydown', keydown); + $document.unbind('click', documentClickBind); + }); + } + }; +}]) + +.directive('datepickerPopupWrap', function() { + return { + restrict:'EA', + replace: true, + transclude: true, + templateUrl: 'template/datepicker/popup.html', + link:function (scope, element, attrs) { + element.bind('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + }); + } + }; +}); + +angular.module('ui.bootstrap.dropdown', []) + +.constant('dropdownConfig', { + openClass: 'open' +}) + +.service('dropdownService', ['$document', function($document) { + var openScope = null; + + this.open = function( dropdownScope ) { + if ( !openScope ) { + $document.bind('click', closeDropdown); + $document.bind('keydown', escapeKeyBind); + } + + if ( openScope && openScope !== dropdownScope ) { + openScope.isOpen = false; + } + + openScope = dropdownScope; + }; + + this.close = function( dropdownScope ) { + if ( openScope === dropdownScope ) { + openScope = null; + $document.unbind('click', closeDropdown); + $document.unbind('keydown', escapeKeyBind); + } + }; + + var closeDropdown = function( evt ) { + var toggleElement = openScope.getToggleElement(); + if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { + return; + } + + openScope.$apply(function() { + openScope.isOpen = false; + }); + }; + + var escapeKeyBind = function( evt ) { + if ( evt.which === 27 ) { + openScope.focusToggleElement(); + closeDropdown(); + } + }; +}]) + +.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) { + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop; + + this.init = function( element ) { + self.$element = element; + + if ( $attrs.isOpen ) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + }; + + this.toggle = function( open ) { + return scope.isOpen = arguments.length ? !!open : !scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.getToggleElement = function() { + return self.toggleElement; + }; + + scope.focusToggleElement = function() { + if ( self.toggleElement ) { + self.toggleElement[0].focus(); + } + }; + + scope.$watch('isOpen', function( isOpen, wasOpen ) { + $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); + + if ( isOpen ) { + scope.focusToggleElement(); + dropdownService.open( scope ); + } else { + dropdownService.close( scope ); + } + + setIsOpen($scope, isOpen); + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + + $scope.$on('$locationChangeSuccess', function() { + scope.isOpen = false; + }); + + $scope.$on('$destroy', function() { + scope.$destroy(); + }); +}]) + +.directive('dropdown', function() { + return { + restrict: 'CA', + controller: 'DropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + dropdownCtrl.init( element ); + } + }; +}) + +.directive('dropdownToggle', function() { + return { + restrict: 'CA', + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if ( !dropdownCtrl ) { + return; + } + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if ( !element.hasClass('disabled') && !attrs.disabled ) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } + }; +}); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) + +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function () { + return { + createNew: function () { + var stack = []; + + return { + add: function (key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function (key) { + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function () { + return stack[stack.length - 1]; + }, + remove: function (key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function () { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function () { + return stack.length; + } + }; + } + }; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', ['$timeout', function ($timeout) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + link: function (scope, element, attrs) { + scope.backdropClass = attrs.backdropClass || ''; + + scope.animate = false; + + //trigger CSS transitions + $timeout(function () { + scope.animate = true; + }); + } + }; + }]) + + .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + return { + restrict: 'EA', + scope: { + index: '@', + animate: '=' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'template/modal/window.html'; + }, + link: function (scope, element, attrs) { + element.addClass(attrs.windowClass || ''); + scope.size = attrs.size; + + $timeout(function () { + // trigger CSS transitions + scope.animate = true; + + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to loose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (!element[0].querySelectorAll('[autofocus]').length) { + element[0].focus(); + } + }); + + scope.close = function (evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + } + }; + }]) + + .directive('modalTransclude', function () { + return { + link: function($scope, $element, $attrs, controller, $transclude) { + $transclude($scope.$parent, function(clone) { + $element.empty(); + $element.append(clone); + }); + } + }; + }) + + .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', + function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var $modalStack = {}; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance) { + + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + //remove window DOM element + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() { + modalWindow.modalScope.$destroy(); + body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); + checkRemoveBackdrop(); + }); + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { + backdropScopeRef.$destroy(); + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, emulateTime, done) { + // Closing animation + scope.animate = false; + + var transitionEndEventName = $transition.transitionEndEventName; + if (transitionEndEventName) { + // transition out + var timeout = $timeout(afterAnimating, emulateTime); + + domEl.bind(transitionEndEventName, function () { + $timeout.cancel(timeout); + afterAnimating(); + scope.$apply(); + }); + } else { + // Ensure this call is async + $timeout(afterAnimating); + } + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + domEl.remove(); + if (done) { + done(); + } + } + } + + $document.bind('keydown', function (evt) { + var modal; + + if (evt.which === 27) { + modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + evt.preventDefault(); + $rootScope.$apply(function () { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + } + } + }); + + $modalStack.open = function (modalInstance, modal) { + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard + }); + + var body = $document.find('body').eq(0), + currBackdropIndex = backdropIndex(); + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.index = currBackdropIndex; + var angularBackgroundDomEl = angular.element('
'); + angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); + backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
'); + angularDomEl.attr({ + 'template-url': modal.windowTemplateUrl, + 'window-class': modal.windowClass, + 'size': modal.size, + 'index': openedWindows.length() - 1, + 'animate': 'animate' + }).html(modal.content); + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + body.append(modalDomEl); + body.addClass(OPENED_MODAL_CLASS); + }; + + $modalStack.close = function (modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.deferred.resolve(result); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismiss = function (modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.deferred.reject(reason); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismissAll = function (reason) { + var topModal = this.getTop(); + while (topModal) { + this.dismiss(topModal.key, reason); + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function () { + return openedWindows.top(); + }; + + return $modalStack; + }]) + + .provider('$modal', function () { + + var $modalProvider = { + options: { + backdrop: true, //can be also false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', + function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { + + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl, + {cache: $templateCache}).then(function (result) { + return result.data; + }); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function (value) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } + }); + return promisesArr; + } + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + close: function (result) { + $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + + templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function (value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + if (modalOptions.controllerAs) { + modalScope[modalOptions.controllerAs] = ctrlInstance; + } + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + content: tplAndVars[0], + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + backdropClass: modalOptions.backdropClass, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size + }); + + }, function resolveError(reason) { + modalResultDeferred.reject(reason); + }); + + templateAndResolvePromise.then(function () { + modalOpenedDeferred.resolve(true); + }, function () { + modalOpenedDeferred.reject(false); + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.pagination', []) + +.controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; + + this.init = function(ngModelCtrl_, config) { + ngModelCtrl = ngModelCtrl_; + this.config = config; + + ngModelCtrl.$render = function() { + self.render(); + }; + + if ($attrs.itemsPerPage) { + $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { + self.itemsPerPage = parseInt(value, 10); + $scope.totalPages = self.calculateTotalPages(); + }); + } else { + this.itemsPerPage = config.itemsPerPage; + } + }; + + this.calculateTotalPages = function() { + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); + }; + + this.render = function() { + $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; + }; + + $scope.selectPage = function(page) { + if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) { + ngModelCtrl.$setViewValue(page); + ngModelCtrl.$render(); + } + }; + + $scope.getText = function( key ) { + return $scope[key + 'Text'] || self.config[key + 'Text']; + }; + $scope.noPrevious = function() { + return $scope.page === 1; + }; + $scope.noNext = function() { + return $scope.page === $scope.totalPages; + }; + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( $scope.page > value ) { + $scope.selectPage(value); + } else { + ngModelCtrl.$render(); + } + }); +}]) + +.constant('paginationConfig', { + itemsPerPage: 10, + boundaryLinks: false, + directionLinks: true, + firstText: 'First', + previousText: 'Previous', + nextText: 'Next', + lastText: 'Last', + rotate: true +}) + +.directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + firstText: '@', + previousText: '@', + nextText: '@', + lastText: '@' + }, + require: ['pagination', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pagination.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + // Setup configuration parameters + var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize, + rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate; + scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; + scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks; + + paginationCtrl.init(ngModelCtrl, paginationConfig); + + if (attrs.maxSize) { + scope.$parent.$watch($parse(attrs.maxSize), function(value) { + maxSize = parseInt(value, 10); + paginationCtrl.render(); + }); + } + + // Create page object used in template + function makePage(number, text, isActive) { + return { + number: number, + text: text, + active: isActive + }; + } + + function getPages(currentPage, totalPages) { + var pages = []; + + // Default page limits + var startPage = 1, endPage = totalPages; + var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); + + // recompute if maxSize + if ( isMaxSized ) { + if ( rotate ) { + // Current page is displayed in the middle of the visible ones + startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); + endPage = startPage + maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > totalPages) { + endPage = totalPages; + startPage = endPage - maxSize + 1; + } + } else { + // Visible pages are paginated with maxSize + startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; + + // Adjust last page if limit is exceeded + endPage = Math.min(startPage + maxSize - 1, totalPages); + } + } + + // Add page number links + for (var number = startPage; number <= endPage; number++) { + var page = makePage(number, number, number === currentPage); + pages.push(page); + } + + // Add links to move between page sets + if ( isMaxSized && ! rotate ) { + if ( startPage > 1 ) { + var previousPageSet = makePage(startPage - 1, '...', false); + pages.unshift(previousPageSet); + } + + if ( endPage < totalPages ) { + var nextPageSet = makePage(endPage + 1, '...', false); + pages.push(nextPageSet); + } + } + + return pages; + } + + var originalRender = paginationCtrl.render; + paginationCtrl.render = function() { + originalRender(); + if (scope.page > 0 && scope.page <= scope.totalPages) { + scope.pages = getPages(scope.page, scope.totalPages); + } + }; + } + }; +}]) + +.constant('pagerConfig', { + itemsPerPage: 10, + previousText: '« Previous', + nextText: 'Next »', + align: true +}) + +.directive('pager', ['pagerConfig', function(pagerConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + previousText: '@', + nextText: '@' + }, + require: ['pager', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pager.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align; + paginationCtrl.init(ngModelCtrl, pagerConfig); + } + }; +}]); + +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0 + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers ( triggers ) { + angular.extend( triggerMap, triggers ); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers ( trigger ) { + var show = trigger || options.trigger || defaultTriggerShow; + var hide = triggerMap[show] || show; + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case( type ); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
'+ + '
'; + + return { + restrict: 'EA', + scope: true, + compile: function (tElem, tAttrs) { + var tooltipLinker = $compile( template ); + + return function link ( scope, element, attrs ) { + var tooltip; + var transitionTimeout; + var popupTimeout; + var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; + var triggers = getTriggers( undefined ); + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + + var positionTooltip = function () { + + var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody); + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; + + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } + if ( scope.tt_popupDelay ) { + // Do nothing if the tooltip was already scheduled to pop-up. + // This happens if show is triggered multiple times before any hide is triggered. + if (!popupTimeout) { + popupTimeout = $timeout( show, scope.tt_popupDelay, false ); + popupTimeout.then(function(reposition){reposition();}); + } + } else { + show()(); + } + } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + + popupTimeout = null; + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + transitionTimeout = null; + } + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return angular.noop; + } + + createTooltip(); + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + positionTooltip(); + + // And show the tooltip. + scope.tt_isOpen = true; + scope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); + popupTimeout = null; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( scope.tt_animation ) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 500); + } + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltip = tooltipLinker(scope, function () {}); + + // Get contents rendered into the tooltip + scope.$digest(); + } + + function removeTooltip() { + transitionTimeout = null; + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + + if (!val && scope.tt_isOpen ) { + hide(); + } + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + var unregisterTriggers = function () { + element.unbind(triggers.show, showTooltipBind); + element.unbind(triggers.hide, hideTooltipBind); + }; + + attrs.$observe( prefix+'Trigger', function ( val ) { + unregisterTriggers(); + + triggers = getTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + }); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + + attrs.$observe( prefix+'AppendToBody', function ( val ) { + appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; + }); + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if ( appendToBody ) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { + if ( scope.tt_isOpen ) { + hide(); + } + }); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel( transitionTimeout ); + $timeout.cancel( popupTimeout ); + unregisterTriggers(); + removeTooltip(); + }); + }; + } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +}]); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html popovers, and selector delegatation. + */ +angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) + +.directive( 'popoverPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html' + }; +}) + +.directive( 'popover', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'popover', 'popover', 'click' ); +}]); + +angular.module('ui.bootstrap.progressbar', []) + +.constant('progressConfig', { + animate: true, + max: 100 +}) + +.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { + var self = this, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; + + this.bars = []; + $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max; + + this.addBar = function(bar, element) { + if ( !animate ) { + element.css({'transition': 'none'}); + } + + this.bars.push(bar); + + bar.$watch('value', function( value ) { + bar.percent = +(100 * value / $scope.max).toFixed(2); + }); + + bar.$on('$destroy', function() { + element = null; + self.removeBar(bar); + }); + }; + + this.removeBar = function(bar) { + this.bars.splice(this.bars.indexOf(bar), 1); + }; +}]) + +.directive('progress', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + require: 'progress', + scope: {}, + templateUrl: 'template/progressbar/progress.html' + }; +}) + +.directive('bar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + require: '^progress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, element); + } + }; +}) + +.directive('progressbar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, angular.element(element.children()[0])); + } + }; +}); +angular.module('ui.bootstrap.rating', []) + +.constant('ratingConfig', { + max: 5, + stateOn: null, + stateOff: null +}) + +.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) { + var ngModelCtrl = { $setViewValue: angular.noop }; + + this.init = function(ngModelCtrl_) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; + this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; + + var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : + new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max ); + $scope.range = this.buildTemplateObjects(ratingStates); + }; + + this.buildTemplateObjects = function(states) { + for (var i = 0, n = states.length; i < n; i++) { + states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]); + } + return states; + }; + + $scope.rate = function(value) { + if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) { + ngModelCtrl.$setViewValue(value); + ngModelCtrl.$render(); + } + }; + + $scope.enter = function(value) { + if ( !$scope.readonly ) { + $scope.value = value; + } + $scope.onHover({value: value}); + }; + + $scope.reset = function() { + $scope.value = ngModelCtrl.$viewValue; + $scope.onLeave(); + }; + + $scope.onKeydown = function(evt) { + if (/(37|38|39|40)/.test(evt.which)) { + evt.preventDefault(); + evt.stopPropagation(); + $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) ); + } + }; + + this.render = function() { + $scope.value = ngModelCtrl.$viewValue; + }; +}]) + +.directive('rating', function() { + return { + restrict: 'EA', + require: ['rating', 'ngModel'], + scope: { + readonly: '=?', + onHover: '&', + onLeave: '&' + }, + controller: 'RatingController', + templateUrl: 'template/rating/rating.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + ratingCtrl.init( ngModelCtrl ); + } + } + }; +}); + +/** + * @ngdoc overview + * @name ui.bootstrap.tabs + * + * @description + * AngularJS version of the tabs directive. + */ + +angular.module('ui.bootstrap.tabs', []) + +.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { + var ctrl = this, + tabs = ctrl.tabs = $scope.tabs = []; + + ctrl.select = function(selectedTab) { + angular.forEach(tabs, function(tab) { + if (tab.active && tab !== selectedTab) { + tab.active = false; + tab.onDeselect(); + } + }); + selectedTab.active = true; + selectedTab.onSelect(); + }; + + ctrl.addTab = function addTab(tab) { + tabs.push(tab); + // we can't run the select function on the first tab + // since that would select it twice + if (tabs.length === 1) { + tab.active = true; + } else if (tab.active) { + ctrl.select(tab); + } + }; + + ctrl.removeTab = function removeTab(tab) { + var index = tabs.indexOf(tab); + //Select a new tab if the tab to be removed is selected + if (tab.active && tabs.length > 1) { + //If this is the last tab, select the previous tab. else, the next tab. + var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; + ctrl.select(tabs[newActiveIndex]); + } + tabs.splice(index, 1); + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabset + * @restrict EA + * + * @description + * Tabset is the outer container for the tabs directive + * + * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. + * @param {boolean=} justified Whether or not to use justified styling for the tabs. + * + * @example + + + + First Content! + Second Content! + +
+ + First Vertical Content! + Second Vertical Content! + + + First Justified Content! + Second Justified Content! + +
+
+ */ +.directive('tabset', function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: { + type: '@' + }, + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; + } + }; +}) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tab + * @restrict EA + * + * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. + * @param {string=} select An expression to evaluate when the tab is selected. + * @param {boolean=} active A binding, telling whether or not this tab is selected. + * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. + * + * @description + * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. + * + * @example + + +
+ + +
+ + First Tab + + Alert me! + Second Tab, with alert callback and html heading! + + + {{item.content}} + + +
+
+ + function TabsDemoCtrl($scope) { + $scope.items = [ + { title:"Dynamic Title 1", content:"Dynamic Item 0" }, + { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } + ]; + + $scope.alertMe = function() { + setTimeout(function() { + alert("You've selected the alert tab!"); + }); + }; + }; + +
+ */ + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabHeading + * @restrict EA + * + * @description + * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. + * + * @example + + + + + HTML in my titles?! + And some content, too! + + + Icon heading?!? + That's right. + + + + + */ +.directive('tab', ['$parse', function($parse) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + active: '=?', + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + compile: function(elm, attrs, transclude) { + return function postLink(scope, elm, attrs, tabsetCtrl) { + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); + } + }); + + scope.disabled = false; + if ( attrs.disabled ) { + scope.$parent.$watch($parse(attrs.disabled), function(value) { + scope.disabled = !! value; + }); + } + + scope.select = function() { + if ( !scope.disabled ) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + }; + } + }; +}]) + +.directive('tabHeadingTransclude', [function() { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm, attrs, tabCtrl) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; +}]) + +.directive('tabContentTransclude', function() { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } else { + elm.append(node); + } + }); + }); + } + }; + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' + ); + } +}) + +; + +angular.module('ui.bootstrap.timepicker', []) + +.constant('timepickerConfig', { + hourStep: 1, + minuteStep: 1, + showMeridian: true, + meridians: null, + readonlyInput: false, + mousewheel: true +}) + +.controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) { + var selected = new Date(), + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; + + this.init = function( ngModelCtrl_, inputs ) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + var hoursInputEl = inputs.eq(0), + minutesInputEl = inputs.eq(1); + + var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; + if ( mousewheel ) { + this.setupMousewheelEvents( hoursInputEl, minutesInputEl ); + } + + $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; + this.setupInputEvents( hoursInputEl, minutesInputEl ); + }; + + var hourStep = timepickerConfig.hourStep; + if ($attrs.hourStep) { + $scope.$parent.$watch($parse($attrs.hourStep), function(value) { + hourStep = parseInt(value, 10); + }); + } + + var minuteStep = timepickerConfig.minuteStep; + if ($attrs.minuteStep) { + $scope.$parent.$watch($parse($attrs.minuteStep), function(value) { + minuteStep = parseInt(value, 10); + }); + } + + // 12H / 24H mode + $scope.showMeridian = timepickerConfig.showMeridian; + if ($attrs.showMeridian) { + $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { + $scope.showMeridian = !!value; + + if ( ngModelCtrl.$error.time ) { + // Evaluate from template + var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); + if (angular.isDefined( hours ) && angular.isDefined( minutes )) { + selected.setHours( hours ); + refresh(); + } + } else { + updateTemplate(); + } + }); + } + + // Get $scope.hours in 24H mode if valid + function getHoursFromTemplate ( ) { + var hours = parseInt( $scope.hours, 10 ); + var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); + if ( !valid ) { + return undefined; + } + + if ( $scope.showMeridian ) { + if ( hours === 12 ) { + hours = 0; + } + if ( $scope.meridian === meridians[1] ) { + hours = hours + 12; + } + } + return hours; + } + + function getMinutesFromTemplate() { + var minutes = parseInt($scope.minutes, 10); + return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; + } + + function pad( value ) { + return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; + } + + // Respond on mousewheel spin + this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) { + var isScrollingUp = function(e) { + if (e.originalEvent) { + e = e.originalEvent; + } + //pick correct delta variable depending on event + var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; + return (e.detail || delta > 0); + }; + + hoursInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() ); + e.preventDefault(); + }); + + minutesInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() ); + e.preventDefault(); + }); + + }; + + this.setupInputEvents = function( hoursInputEl, minutesInputEl ) { + if ( $scope.readonlyInput ) { + $scope.updateHours = angular.noop; + $scope.updateMinutes = angular.noop; + return; + } + + var invalidate = function(invalidHours, invalidMinutes) { + ngModelCtrl.$setViewValue( null ); + ngModelCtrl.$setValidity('time', false); + if (angular.isDefined(invalidHours)) { + $scope.invalidHours = invalidHours; + } + if (angular.isDefined(invalidMinutes)) { + $scope.invalidMinutes = invalidMinutes; + } + }; + + $scope.updateHours = function() { + var hours = getHoursFromTemplate(); + + if ( angular.isDefined(hours) ) { + selected.setHours( hours ); + refresh( 'h' ); + } else { + invalidate(true); + } + }; + + hoursInputEl.bind('blur', function(e) { + if ( !$scope.invalidHours && $scope.hours < 10) { + $scope.$apply( function() { + $scope.hours = pad( $scope.hours ); + }); + } + }); + + $scope.updateMinutes = function() { + var minutes = getMinutesFromTemplate(); + + if ( angular.isDefined(minutes) ) { + selected.setMinutes( minutes ); + refresh( 'm' ); + } else { + invalidate(undefined, true); + } + }; + + minutesInputEl.bind('blur', function(e) { + if ( !$scope.invalidMinutes && $scope.minutes < 10 ) { + $scope.$apply( function() { + $scope.minutes = pad( $scope.minutes ); + }); + } + }); + + }; + + this.render = function() { + var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null; + + if ( isNaN(date) ) { + ngModelCtrl.$setValidity('time', false); + $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } else { + if ( date ) { + selected = date; + } + makeValid(); + updateTemplate(); + } + }; + + // Call internally when we know that model is valid. + function refresh( keyboardChange ) { + makeValid(); + ngModelCtrl.$setViewValue( new Date(selected) ); + updateTemplate( keyboardChange ); + } + + function makeValid() { + ngModelCtrl.$setValidity('time', true); + $scope.invalidHours = false; + $scope.invalidMinutes = false; + } + + function updateTemplate( keyboardChange ) { + var hours = selected.getHours(), minutes = selected.getMinutes(); + + if ( $scope.showMeridian ) { + hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system + } + + $scope.hours = keyboardChange === 'h' ? hours : pad(hours); + $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); + $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + } + + function addMinutes( minutes ) { + var dt = new Date( selected.getTime() + minutes * 60000 ); + selected.setHours( dt.getHours(), dt.getMinutes() ); + refresh(); + } + + $scope.incrementHours = function() { + addMinutes( hourStep * 60 ); + }; + $scope.decrementHours = function() { + addMinutes( - hourStep * 60 ); + }; + $scope.incrementMinutes = function() { + addMinutes( minuteStep ); + }; + $scope.decrementMinutes = function() { + addMinutes( - minuteStep ); + }; + $scope.toggleMeridian = function() { + addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); + }; +}]) + +.directive('timepicker', function () { + return { + restrict: 'EA', + require: ['timepicker', '?^ngModel'], + controller:'TimepickerController', + replace: true, + scope: {}, + templateUrl: 'template/timepicker/timepicker.html', + link: function(scope, element, attrs, ctrls) { + var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + timepickerCtrl.init( ngModelCtrl, element.find('input') ); + } + } + }; +}); + +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) + +/** + * A helper service that can parse typeahead's syntax (string provided by users) + * Extracted to a separate service for ease of unit testing + */ + .factory('typeaheadParser', ['$parse', function ($parse) { + + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; + + return { + parse:function (input) { + + var match = input.match(TYPEAHEAD_REGEXP); + if (!match) { + throw new Error( + 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + + ' but got "' + input + '".'); + } + + return { + itemName:match[3], + source:$parse(match[4]), + viewMapper:$parse(match[2] || match[1]), + modelMapper:$parse(match[1]) + }; + } + }; +}]) + + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', + function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { + + var HOT_KEYS = [9, 13, 27, 38, 40]; + + return { + require:'ngModel', + link:function (originalScope, element, attrs, modelCtrl) { + + //SUPPORTED ATTRIBUTES (OPTIONS) + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; + + //minimal wait time after last character typed before typehead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); + + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + + var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; + + //INTERNAL VARIABLES + + //model setter executed upon match selection + var $setModelValue = $parse(attrs.ngModel).assign; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + var hasFocus; + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function(){ + scope.$destroy(); + }); + + // WAI-ARIA + var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + element.attr({ + 'aria-autocomplete': 'list', + 'aria-expanded': false, + 'aria-owns': popupId + }); + + //pop-up element used to display matches + var popUpEl = angular.element('
'); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; + + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + + var getMatchesAsync = function(inputValue) { + + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches.length > 0) { + + scope.activeIdx = 0; + scope.matches.length = 0; + + //transform labels + for(var i=0; i= minSearch) { + if (waitTime > 0) { + cancelPreviousTimeout(); + scheduleSearchWithTimeout(inputValue); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); + cancelPreviousTimeout(); + resetMatches(); + } + + if (isEditable) { + return inputValue; + } else { + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return inputValue; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } + } + }); + + modelCtrl.$formatters.push(function (modelValue) { + + var candidateViewValue, emptyViewValue; + var locals = {}; + + if (inputFormatter) { + + locals['$model'] = modelValue; + return inputFormatter(originalScope, locals); + + } else { + + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + } + }); + + scope.select = function (activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); + + resetMatches(); + + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + $timeout(function() { element[0].focus(); }, 0, false); + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function (evt) { + + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function (evt) { + hasFocus = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function (evt) { + if (element[0] !== evt.target) { + resetMatches(); + scope.$digest(); + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function(){ + $document.unbind('click', dismissClickHandler); + }); + + var $popup = $compile(popUpEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + } + }; + +}]) + + .directive('typeaheadPopup', function () { + return { + restrict:'EA', + scope:{ + matches:'=', + query:'=', + active:'=', + position:'=', + select:'&' + }, + replace:true, + templateUrl:'template/typeahead/typeahead-popup.html', + link:function (scope, element, attrs) { + + scope.templateUrl = attrs.templateUrl; + + scope.isOpen = function () { + return scope.matches.length > 0; + }; + + scope.isActive = function (matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function (matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function (activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }) + + .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { + return { + restrict:'EA', + scope:{ + index:'=', + match:'=', + query:'=' + }, + link:function (scope, element, attrs) { + var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; + $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ + element.replaceWith($compile(tplContent.trim())(scope)); + }); + } + }; + }]) + + .filter('typeaheadHighlight', function() { + + function escapeRegexp(queryToEscape) { + return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + } + + return function(matchItem, query) { + return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + }; + }); + +angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/accordion/accordion-group.html", + "
\n" + + "
\n" + + "

\n" + + " {{heading}}\n" + + "

\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
"); +}]); + +angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/accordion/accordion.html", + "
"); +}]); + +angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/alert/alert.html", + "
\n" + + " \n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/carousel/carousel.html", + "
\n" + + "
    1\">\n" + + "
  1. \n" + + "
\n" + + "
\n" + + " 1\">\n" + + " 1\">\n" + + "
\n" + + ""); +}]); + +angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/carousel/slide.html", + "
\n" + + ""); +}]); + +angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/datepicker.html", + "
\n" + + " \n" + + " \n" + + " \n" + + "
"); +}]); + +angular.module("template/datepicker/day.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/day.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
{{label.abbr}}
{{ weekNumbers[$index] }}\n" + + " \n" + + "
\n" + + ""); +}]); + +angular.module("template/datepicker/month.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/month.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + ""); +}]); + +angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/popup.html", + "
    \n" + + "
  • \n" + + "
  • \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
  • \n" + + "
\n" + + ""); +}]); + +angular.module("template/datepicker/year.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/year.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + "
\n" + + ""); +}]); + +angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/backdrop.html", + "
\n" + + ""); +}]); + +angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/window.html", + "
\n" + + "
\n" + + "
"); +}]); + +angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/pagination/pager.html", + ""); +}]); + +angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/pagination/pagination.html", + ""); +}]); + +angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", + "
\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-popup.html", + "
\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/popover/popover.html", + "
\n" + + "
\n" + + "\n" + + "
\n" + + "

\n" + + "
\n" + + "
\n" + + "
\n" + + ""); +}]); + +angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/bar.html", + "
"); +}]); + +angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/progress.html", + "
"); +}]); + +angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/progressbar.html", + "
\n" + + "
\n" + + "
"); +}]); + +angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/rating/rating.html", + "\n" + + " \n" + + " ({{ $index < value ? '*' : ' ' }})\n" + + " \n" + + ""); +}]); + +angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tab.html", + "
  • \n" + + " {{heading}}\n" + + "
  • \n" + + ""); +}]); + +angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tabset.html", + "
    \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/timepicker/timepicker.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
       
      \n" + + " \n" + + " :\n" + + " \n" + + "
       
      \n" + + ""); +}]); + +angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/typeahead/typeahead-match.html", + ""); +}]); + +angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/typeahead/typeahead-popup.html", + "
        \n" + + "
      • \n" + + "
        \n" + + "
      • \n" + + "
      \n" + + ""); +}]); diff --git a/bower_components/angular-bootstrap/ui-bootstrap-tpls-fca023a729.min.js b/bower_components/angular-bootstrap/ui-bootstrap-tpls-fca023a729.min.js new file mode 100644 index 0000000..10e140e --- /dev/null +++ b/bower_components/angular-bootstrap/ui-bootstrap-tpls-fca023a729.min.js @@ -0,0 +1,10 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.2 - 2014-09-26 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(a){return{link:function(b,c,d){function e(b){function d(){j===e&&(j=void 0)}var e=a(c,b);return j&&j.cancel(),j=e,e.then(d,d),e}function f(){k?(k=!1,g()):(c.removeClass("collapse").addClass("collapsing"),e({height:c[0].scrollHeight+"px"}).then(g))}function g(){c.removeClass("collapsing"),c.addClass("collapse in"),c.css({height:"auto"})}function h(){if(k)k=!1,i(),c.css({height:0});else{c.css({height:c[0].scrollHeight+"px"});{c[0].offsetWidth}c.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(i)}}function i(){c.removeClass("collapsing"),c.addClass("collapse")}var j,k=!0;b.$watch(d.collapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.$watch("isOpen",function(b){b&&d.closeOthers(a)}),a.toggleOpen=function(){a.isDisabled||(a.isOpen=!a.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$transition",function(a,b,c){function d(){e();var c=+a.interval;!isNaN(c)&&c>=0&&(g=b(f,c))}function e(){g&&(b.cancel(g),g=null)}function f(){h?(a.next(),d()):a.pause()}var g,h,i=this,j=i.slides=a.slides=[],k=-1;i.currentSlide=null;var l=!1;i.select=a.select=function(e,f){function g(){if(!l){if(i.currentSlide&&angular.isString(f)&&!a.noTransition&&e.$element){e.$element.addClass(f);{e.$element[0].offsetWidth}angular.forEach(j,function(a){angular.extend(a,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(e,{direction:f,active:!0,entering:!0}),angular.extend(i.currentSlide||{},{direction:f,leaving:!0}),a.$currentTransition=c(e.$element,{}),function(b,c){a.$currentTransition.then(function(){h(b,c)},function(){h(b,c)})}(e,i.currentSlide)}else h(e,i.currentSlide);i.currentSlide=e,k=m,d()}}function h(b,c){angular.extend(b,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(c||{},{direction:"",active:!1,leaving:!1,entering:!1}),a.$currentTransition=null}var m=j.indexOf(e);void 0===f&&(f=m>k?"next":"prev"),e&&e!==i.currentSlide&&(a.$currentTransition?(a.$currentTransition.cancel(),b(g)):g())},a.$on("$destroy",function(){l=!0}),i.indexOfSlide=function(a){return j.indexOf(a)},a.next=function(){var b=(k+1)%j.length;return a.$currentTransition?void 0:i.select(j[b],"next")},a.prev=function(){var b=0>k-1?j.length-1:k-1;return a.$currentTransition?void 0:i.select(j[b],"prev")},a.isActive=function(a){return i.currentSlide===a},a.$watch("interval",d),a.$on("$destroy",e),a.play=function(){h||(h=!0,d())},a.pause=function(){a.noPause||(h=!1,e())},i.addSlide=function(b,c){b.$element=c,j.push(b),1===j.length||b.active?(i.select(j[j.length-1]),1==j.length&&a.play()):b.active=!1},i.removeSlide=function(a){var b=j.indexOf(a);j.splice(b,1),j.length>0&&a.active?i.select(b>=j.length?j[b-1]:j[b]):k>b&&k--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$locale","orderByFilter",function(a,b){function c(a){var c=[],d=a.split("");return angular.forEach(e,function(b,e){var f=a.indexOf(e);if(f>-1){a=a.split(""),d[f]="("+b.regex+")",a[f]="$";for(var g=f+1,h=f+e.length;h>g;g++)d[g]="",a[g]="$";a=a.join(""),c.push({index:f,apply:b.apply})}}),{regex:new RegExp("^"+d.join("")+"$"),map:b(c,"index")}}function d(a,b,c){return 1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}this.parsers={};var e={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:a.DATETIME_FORMATS.MONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.MONTH.indexOf(b)}},MMM:{regex:a.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.SHORTMONTH.indexOf(b)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:a.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:a.DATETIME_FORMATS.SHORTDAY.join("|")}};this.parse=function(b,e){if(!angular.isString(b)||!e)return b;e=a.DATETIME_FORMATS[e]||e,this.parsers[e]||(this.parsers[e]=c(e));var f=this.parsers[e],g=f.regex,h=f.map,i=b.match(g);if(i&&i.length){for(var j,k={year:1900,month:0,date:1,hours:0},l=1,m=i.length;m>l;l++){var n=h[l-1];n.apply&&n.apply.call(k,i[l])}return d(k.year,k.month,k.date)&&(j=new Date(k.year,k.month,k.date,k.hours)),j}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$timeout","$log","dateFilter","datepickerConfig",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","minMode","maxMode","showWeeks","startingDay","yearRange"],function(c,e){i[c]=angular.isDefined(b[c])?8>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):h[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=h[d]?new Date(h[d]):null}),a.datepickerMode=a.datepickerMode||h.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),this.activeDate=angular.isDefined(b.initDate)?a.$parent.$eval(b.initDate):new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$modelValue){var a=new Date(j.$modelValue),b=!isNaN(a);b?this.activeDate=a:f.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'),j.$setValidity("date",b)}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$modelValue?new Date(j.$modelValue):null;j.$setValidity("date-disabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$modelValue?new Date(j.$modelValue):null;return{date:a,label:g(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$modelValue?new Date(j.$modelValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){e(function(){i.element[0].focus()},0,!1)};a.$on("datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate),k()}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):(a.toggleMode("up"===c?1:-1),k())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{datepickerMode:"=?",dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}).directive("daypicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(b,c,d,e){function f(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?i[b]:29}function g(a,b){var c=new Array(b),d=new Date(a),e=0;for(d.setHours(12);b>e;)c[e++]=new Date(d),d.setDate(d.getDate()+1);return c}function h(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}b.showWeeks=e.showWeeks,e.step={months:1},e.element=c;var i=[31,28,31,30,31,30,31,31,30,31,30,31];e._refreshView=function(){var c=e.activeDate.getFullYear(),d=e.activeDate.getMonth(),f=new Date(c,d,1),i=e.startingDay-f.getDay(),j=i>0?7-i:-i,k=new Date(f);j>0&&k.setDate(-j+1);for(var l=g(k,42),m=0;42>m;m++)l[m]=angular.extend(e.createDateObject(l[m],e.formatDay),{secondary:l[m].getMonth()!==d,uid:b.uniqueId+"-"+m});b.labels=new Array(7);for(var n=0;7>n;n++)b.labels[n]={abbr:a(l[n].date,e.formatDayHeader),full:a(l[n].date,"EEEE")};if(b.title=a(e.activeDate,e.formatDayTitle),b.rows=e.split(l,7),b.showWeeks){b.weekNumbers=[];for(var o=h(b.rows[0][0].date),p=b.rows.length;b.weekNumbers.push(o++)f;f++)c[f]=angular.extend(e.createDateObject(new Date(d,f,1),e.formatMonth),{uid:b.uniqueId+"-"+f});b.title=a(e.activeDate,e.formatMonthTitle),b.rows=e.split(c,3)},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},e.handleKeyDown=function(a){var b=e.activeDate.getMonth();if("left"===a)b-=1;else if("up"===a)b-=3;else if("right"===a)b+=1;else if("down"===a)b+=3;else if("pageup"===a||"pagedown"===a){var c=e.activeDate.getFullYear()+("pageup"===a?-1:1);e.activeDate.setFullYear(c)}else"home"===a?b=0:"end"===a&&(b=11);e.activeDate.setMonth(b)},e.refreshView()}}}]).directive("yearpicker",["dateFilter",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(a,b,c,d){function e(a){return parseInt((a-1)/f,10)*f+1}var f=d.yearRange;d.step={years:f},d.element=b,d._refreshView=function(){for(var b=new Array(f),c=0,g=e(d.activeDate.getFullYear());f>c;c++)b[c]=angular.extend(d.createDateObject(new Date(g+c,0,1),d.formatYear),{uid:a.uniqueId+"-"+c});a.title=[b[0].label,b[f-1].label].join(" - "),a.rows=d.split(b,5)},d.compare=function(a,b){return a.getFullYear()-b.getFullYear()},d.handleKeyDown=function(a){var b=d.activeDate.getFullYear();"left"===a?b-=1:"up"===a?b-=5:"right"===a?b+=1:"down"===a?b+=5:"pageup"===a||"pagedown"===a?b+=("pageup"===a?-1:1)*d.step.years:"home"===a?b=e(d.activeDate.getFullYear()):"end"===a&&(b=e(d.activeDate.getFullYear())+f-1),d.activeDate.setFullYear(b)},d.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","dateParser","datepickerPopupConfig",function(a,b,c,d,e,f,g){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&"},link:function(h,i,j,k){function l(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function m(a){if(a){if(angular.isDate(a)&&!isNaN(a))return k.$setValidity("date",!0),a;if(angular.isString(a)){var b=f.parse(a,n)||new Date(a);return isNaN(b)?void k.$setValidity("date",!1):(k.$setValidity("date",!0),b)}return void k.$setValidity("date",!1)}return k.$setValidity("date",!0),null}var n,o=angular.isDefined(j.closeOnDateSelection)?h.$parent.$eval(j.closeOnDateSelection):g.closeOnDateSelection,p=angular.isDefined(j.datepickerAppendToBody)?h.$parent.$eval(j.datepickerAppendToBody):g.appendToBody;h.showButtonBar=angular.isDefined(j.showButtonBar)?h.$parent.$eval(j.showButtonBar):g.showButtonBar,h.getText=function(a){return h[a+"Text"]||g[a+"Text"]},j.$observe("datepickerPopup",function(a){n=a||g.datepickerPopup,k.$render()});var q=angular.element("
      ");q.attr({"ng-model":"date","ng-change":"dateSelection()"});var r=angular.element(q.children()[0]);j.datepickerOptions&&angular.forEach(h.$parent.$eval(j.datepickerOptions),function(a,b){r.attr(l(b),a)}),h.watchData={},angular.forEach(["minDate","maxDate","datepickerMode"],function(a){if(j[a]){var c=b(j[a]);if(h.$parent.$watch(c,function(b){h.watchData[a]=b}),r.attr(l(a),"watchData."+a),"datepickerMode"===a){var d=c.assign;h.$watch("watchData."+a,function(a,b){a!==b&&d(h.$parent,a)})}}}),j.dateDisabled&&r.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),k.$parsers.unshift(m),h.dateSelection=function(a){angular.isDefined(a)&&(h.date=a),k.$setViewValue(h.date),k.$render(),o&&(h.isOpen=!1,i[0].focus())},i.bind("input change keyup",function(){h.$apply(function(){h.date=k.$modelValue})}),k.$render=function(){var a=k.$viewValue?e(k.$viewValue,n):"";i.val(a),h.date=m(k.$modelValue)};var s=function(a){h.isOpen&&a.target!==i[0]&&h.$apply(function(){h.isOpen=!1})},t=function(a){h.keydown(a)};i.bind("keydown",t),h.keydown=function(a){27===a.which?(a.preventDefault(),a.stopPropagation(),h.close()):40!==a.which||h.isOpen||(h.isOpen=!0)},h.$watch("isOpen",function(a){a?(h.$broadcast("datepicker.focus"),h.position=p?d.offset(i):d.position(i),h.position.top=h.position.top+i.prop("offsetHeight"),c.bind("click",s)):c.unbind("click",s)}),h.select=function(a){if("today"===a){var b=new Date;angular.isDate(k.$modelValue)?(a=new Date(k.$modelValue),a.setFullYear(b.getFullYear(),b.getMonth(),b.getDate())):a=new Date(b.setHours(0,0,0,0))}h.dateSelection(a)},h.close=function(){h.isOpen=!1,i[0].focus()};var u=a(q)(h);q.remove(),p?c.find("body").append(u):i.after(u),h.$on("$destroy",function(){u.remove(),i.unbind("keydown",t),c.unbind("click",s)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(a,b){b.bind("click",function(a){a.preventDefault(),a.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function(a){var b=null;this.open=function(e){b||(a.bind("click",c),a.bind("keydown",d)),b&&b!==e&&(b.isOpen=!1),b=e},this.close=function(e){b===e&&(b=null,a.unbind("click",c),a.unbind("keydown",d))};var c=function(a){var c=b.getToggleElement();a&&c&&c[0].contains(a.target)||b.$apply(function(){b.isOpen=!1})},d=function(a){27===a.which&&(b.focusToggleElement(),c())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function(a,b,c,d,e,f){var g,h=this,i=a.$new(),j=d.openClass,k=angular.noop,l=b.onToggle?c(b.onToggle):angular.noop;this.init=function(d){h.$element=d,b.isOpen&&(g=c(b.isOpen),k=g.assign,a.$watch(g,function(a){i.isOpen=!!a}))},this.toggle=function(a){return i.isOpen=arguments.length?!!a:!i.isOpen},this.isOpen=function(){return i.isOpen},i.getToggleElement=function(){return h.toggleElement},i.focusToggleElement=function(){h.toggleElement&&h.toggleElement[0].focus()},i.$watch("isOpen",function(b,c){f[b?"addClass":"removeClass"](h.$element,j),b?(i.focusToggleElement(),e.open(i)):e.close(i),k(a,b),angular.isDefined(b)&&b!==c&&l(a,{open:!!b})}),a.$on("$locationChangeSuccess",function(){i.isOpen=!1}),a.$on("$destroy",function(){i.$destroy()})}]).directive("dropdown",function(){return{restrict:"CA",controller:"DropdownController",link:function(a,b,c,d){d.init(b)}}}).directive("dropdownToggle",function(){return{restrict:"CA",require:"?^dropdown",link:function(a,b,c,d){if(d){d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0),i()})}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&(a.preventDefault(),e.$apply(function(){o.dismiss(b.key,"escape key press")})))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();if(h>=0&&!k){l=e.$new(!0),l.index=h;var i=angular.element("
      ");i.attr("backdrop-class",b.backdropClass),k=d(i)(l),f.append(k)}var j=angular.element("
      ");j.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,size:b.size,index:n.length()-1,animate:"animate"}).html(b.content);var o=d(j)(b.scope);n.top().value.modalDomEl=o,f.append(o),f.addClass(m)},o.close=function(a,b){var c=n.get(a);c&&(c.value.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a);c&&(c.value.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,backdropClass:b.backdropClass,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(f,g){e=f,this.config=g,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=g.itemsPerPage},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b){a.page!==b&&b>0&&b<=a.totalPages&&(e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages},a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render() +});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@"},require:["pager","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
      ';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!y||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?v||(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return v=null,u&&(g.cancel(u),u=null),b.tt_content?(r(),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),z(),b.tt_isOpen=!0,b.$digest(),z):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),v=null,b.tt_animation?u||(u=g(s,500)):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){u=null,t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=angular.isDefined(d[l+"Enable"]),z=function(){var a=j.positionElements(c,t,b.tt_placement,w);a.top+="px",a.left+="px",t.css(a)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var A=function(){c.unbind(x.show,k),c.unbind(x.hide,m)};d.$observe(l+"Trigger",function(a){A(),x=n(a),x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m))});var B=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(B)?!!B:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),A(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,this.addBar=function(b,c){e||c.css({transition:"none"}),this.bars.push(b),b.$watch("value",function(c){b.percent=+(100*c/a.max).toFixed(2)}),b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(f)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff},a[b]);return a},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect())}),a.active=!0,a.onSelect()},b.addTab=function(a){c.push(a),1===c.length?a.active=!0:a.active&&b.select(a)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){b.$watch("active",function(a){a&&f.select(b)}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function(a,b,c,d,e,f){function g(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===p[1]&&(b+=12)),b):void 0}function h(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function i(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a}function j(a){k(),o.$setViewValue(new Date(n)),l(a)}function k(){o.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function l(b){var c=n.getHours(),d=n.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:i(c),a.minutes="m"===b?d:i(d),a.meridian=n.getHours()<12?p[0]:p[1]}function m(a){var b=new Date(n.getTime()+6e4*a);n.setHours(b.getHours(),b.getMinutes()),j()}var n=new Date,o={$setViewValue:angular.noop},p=angular.isDefined(b.meridians)?a.$parent.$eval(b.meridians):f.meridians||e.DATETIME_FORMATS.AMPMS;this.init=function(c,d){o=c,o.$render=this.render;var e=d.eq(0),g=d.eq(1),h=angular.isDefined(b.mousewheel)?a.$parent.$eval(b.mousewheel):f.mousewheel;h&&this.setupMousewheelEvents(e,g),a.readonlyInput=angular.isDefined(b.readonlyInput)?a.$parent.$eval(b.readonlyInput):f.readonlyInput,this.setupInputEvents(e,g)};var q=f.hourStep;b.hourStep&&a.$parent.$watch(c(b.hourStep),function(a){q=parseInt(a,10)});var r=f.minuteStep;b.minuteStep&&a.$parent.$watch(c(b.minuteStep),function(a){r=parseInt(a,10)}),a.showMeridian=f.showMeridian,b.showMeridian&&a.$parent.$watch(c(b.showMeridian),function(b){if(a.showMeridian=!!b,o.$error.time){var c=g(),d=h();angular.isDefined(c)&&angular.isDefined(d)&&(n.setHours(c),j())}else l()}),this.setupMousewheelEvents=function(b,c){var d=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){o.$setViewValue(null),o.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=g();angular.isDefined(a)?(n.setHours(a),j("h")):d(!0)},b.bind("blur",function(){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=i(a.hours)})}),a.updateMinutes=function(){var a=h();angular.isDefined(a)?(n.setMinutes(a),j("m")):d(void 0,!0)},c.bind("blur",function(){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=i(a.minutes)})})},this.render=function(){var a=o.$modelValue?new Date(o.$modelValue):null;isNaN(a)?(o.$setValidity("time",!1),d.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(a&&(n=a),k(),l())},a.incrementHours=function(){m(60*q)},a.decrementHours=function(){m(60*-q)},a.incrementMinutes=function(){m(r)},a.decrementMinutes=function(){m(-r)},a.toggleMeridian=function(){m(720*(n.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",replace:!0,scope:{},templateUrl:"template/timepicker/timepicker.html",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?i.$eval(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=i.$new();i.$on("$destroy",function(){w.$destroy()});var x="typeahead-"+w.$id+"-"+Math.floor(1e4*Math.random());j.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":x});var y=angular.element("
      ");y.attr({id:x,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&y.attr("template-url",k.typeaheadTemplateUrl);var z=function(){w.matches=[],w.activeIdx=-1,j.attr("aria-expanded",!1)},A=function(a){return x+"-option-"+a};w.$watch("activeIdx",function(a){0>a?j.removeAttr("aria-activedescendant"):j.attr("aria-activedescendant",A(a))});var B=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){var d=a===l.$viewValue;if(d&&m)if(c.length>0){w.activeIdx=0,w.matches.length=0;for(var e=0;e=n?o>0?(E(),D(a)):B(a):(q(i,!1),E(),z()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),w.select=function(a){var b,c,e={};e[v.itemName]=c=w.matches[a].model,b=v.modelMapper(i,e),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,e)}),z(),d(function(){j[0].focus()},0,!1)},j.bind("keydown",function(a){0!==w.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(w.activeIdx=(w.activeIdx+1)%w.matches.length,w.$digest()):38===a.which?(w.activeIdx=(w.activeIdx?w.activeIdx:w.matches.length)-1,w.$digest()):13===a.which||9===a.which?w.$apply(function(){w.select(w.activeIdx)}):27===a.which&&(a.stopPropagation(),z(),w.$digest()))}),j.bind("blur",function(){m=!1});var F=function(a){j[0]!==a.target&&(z(),w.$digest())};e.bind("click",F),i.$on("$destroy",function(){e.unbind("click",F)});var G=a(y)(w);t?e.find("body").append(G):j.after(G)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?(""+b).replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
      \n
      \n

      \n {{heading}}\n

      \n
      \n
      \n
      \n
      \n
      ')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
      ')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html",'\n')}]),angular.module("template/carousel/carousel.html",[]).run(["$templateCache",function(a){a.put("template/carousel/carousel.html",'\n')}]),angular.module("template/carousel/slide.html",[]).run(["$templateCache",function(a){a.put("template/carousel/slide.html","
      \n")}]),angular.module("template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/datepicker.html",'
      \n \n \n \n
      ')}]),angular.module("template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
      {{label.abbr}}
      {{ weekNumbers[$index] }}\n \n
      \n')}]),angular.module("template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
      \n \n
      \n')}]),angular.module("template/datepicker/popup.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/popup.html",'\n')}]),angular.module("template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
      \n \n
      \n')}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'
      \n
      \n
      \n
      \n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'
      \n
      \n
      \n
      \n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
      \n
      \n\n
      \n

      \n
      \n
      \n
      \n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'
      ')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
      ')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'
      \n
      \n
      ')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n ({{ $index < value ? \'*\' : \' \' }})\n \n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'
    • \n {{heading}}\n
    • \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'
      \n \n
      \n
      \n
      \n
      \n
      \n')}]),angular.module("template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("template/timepicker/timepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
       
      \n \n :\n \n
       
      \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'') +}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html",'\n')}]); \ No newline at end of file diff --git a/bower_components/angular-bootstrap/ui-bootstrap-tpls.js b/bower_components/angular-bootstrap/ui-bootstrap-tpls.js new file mode 100644 index 0000000..260c276 --- /dev/null +++ b/bower_components/angular-bootstrap/ui-bootstrap-tpls.js @@ -0,0 +1,4167 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.2 - 2014-09-26 + * License: MIT + */ +angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); +angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]); +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) + + .directive('collapse', ['$transition', function ($transition) { + + return { + link: function (scope, element, attrs) { + + var initialAnimSkip = true; + var currentTransition; + + function doTransition(change) { + var newTransition = $transition(element, change); + if (currentTransition) { + currentTransition.cancel(); + } + currentTransition = newTransition; + newTransition.then(newTransitionDone, newTransitionDone); + return newTransition; + + function newTransitionDone() { + // Make sure it's this transition, otherwise, leave it alone. + if (currentTransition === newTransition) { + currentTransition = undefined; + } + } + } + + function expand() { + if (initialAnimSkip) { + initialAnimSkip = false; + expandDone(); + } else { + element.removeClass('collapse').addClass('collapsing'); + doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); + } + } + + function expandDone() { + element.removeClass('collapsing'); + element.addClass('collapse in'); + element.css({height: 'auto'}); + } + + function collapse() { + if (initialAnimSkip) { + initialAnimSkip = false; + collapseDone(); + element.css({height: 0}); + } else { + // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value + element.css({ height: element[0].scrollHeight + 'px' }); + //trigger reflow so a browser realizes that height was updated from auto to a specific value + var x = element[0].offsetWidth; + + element.removeClass('collapse in').addClass('collapsing'); + + doTransition({ height: 0 }).then(collapseDone); + } + } + + function collapseDone() { + element.removeClass('collapsing'); + element.addClass('collapse'); + } + + scope.$watch(attrs.collapse, function (shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) + +.constant('accordionConfig', { + closeOthers: true +}) + +.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { + + // This array keeps track of the accordion groups + this.groups = []; + + // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to + this.closeOthers = function(openGroup) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if ( closeOthers ) { + angular.forEach(this.groups, function (group) { + if ( group !== openGroup ) { + group.isOpen = false; + } + }); + } + }; + + // This is called from the accordion-group directive to add itself to the accordion + this.addGroup = function(groupScope) { + var that = this; + this.groups.push(groupScope); + + groupScope.$on('$destroy', function (event) { + that.removeGroup(groupScope); + }); + }; + + // This is called from the accordion-group directive when to remove itself + this.removeGroup = function(group) { + var index = this.groups.indexOf(group); + if ( index !== -1 ) { + this.groups.splice(index, 1); + } + }; + +}]) + +// The accordion directive simply sets up the directive controller +// and adds an accordion CSS class to itself element. +.directive('accordion', function () { + return { + restrict:'EA', + controller:'AccordionController', + transclude: true, + replace: false, + templateUrl: 'template/accordion/accordion.html' + }; +}) + +// The accordion-group directive indicates a block of html that will expand and collapse in an accordion +.directive('accordionGroup', function() { + return { + require:'^accordion', // We need this directive to be inside an accordion + restrict:'EA', + transclude:true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl:'template/accordion/accordion-group.html', + scope: { + heading: '@', // Interpolate the heading attribute onto this scope + isOpen: '=?', + isDisabled: '=?' + }, + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + accordionCtrl.addGroup(scope); + + scope.$watch('isOpen', function(value) { + if ( value ) { + accordionCtrl.closeOthers(scope); + } + }); + + scope.toggleOpen = function() { + if ( !scope.isDisabled ) { + scope.isOpen = !scope.isOpen; + } + }; + } + }; +}) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +// +// Heading containing HTML - +// +.directive('accordionHeading', function() { + return { + restrict: 'EA', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + link: function(scope, element, attr, accordionGroupCtrl, transclude) { + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, function() {})); + } + }; +}) + +// Use in the accordion-group template to indicate where you want the heading to be transcluded +// You must provide the property on the accordion-group controller that will hold the transcluded element +//
      +// +// ... +//
      +.directive('accordionTransclude', function() { + return { + require: '^accordionGroup', + link: function(scope, element, attr, controller) { + scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { + if ( heading ) { + element.html(''); + element.append(heading); + } + }); + } + }; +}); + +angular.module('ui.bootstrap.alert', []) + +.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { + $scope.closeable = 'close' in $attrs; +}]) + +.directive('alert', function () { + return { + restrict:'EA', + controller:'AlertController', + templateUrl:'template/alert/alert.html', + transclude:true, + replace:true, + scope: { + type: '@', + close: '&' + } + }; +}); + +angular.module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function () { + return function (scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +angular.module('ui.bootstrap.buttons', []) + +.constant('buttonConfig', { + activeClass: 'active', + toggleEvent: 'click' +}) + +.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { + this.activeClass = buttonConfig.activeClass || 'active'; + this.toggleEvent = buttonConfig.toggleEvent || 'click'; +}]) + +.directive('btnRadio', function () { + return { + require: ['btnRadio', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + var isActive = element.hasClass(buttonsCtrl.activeClass); + + if (!isActive || angular.isDefined(attrs.uncheckable)) { + scope.$apply(function () { + ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); + ngModelCtrl.$render(); + }); + } + }); + } + }; +}) + +.directive('btnCheckbox', function () { + return { + require: ['btnCheckbox', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + function getTrueValue() { + return getCheckboxValue(attrs.btnCheckboxTrue, true); + } + + function getFalseValue() { + return getCheckboxValue(attrs.btnCheckboxFalse, false); + } + + function getCheckboxValue(attributeValue, defaultValue) { + var val = scope.$eval(attributeValue); + return angular.isDefined(val) ? val : defaultValue; + } + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + scope.$apply(function () { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + } + }; +}); + +/** +* @ngdoc overview +* @name ui.bootstrap.carousel +* +* @description +* AngularJS version of an image carousel. +* +*/ +angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) +.controller('CarouselController', ['$scope', '$timeout', '$transition', function ($scope, $timeout, $transition) { + var self = this, + slides = self.slides = $scope.slides = [], + currentIndex = -1, + currentTimeout, isPlaying; + self.currentSlide = null; + + var destroyed = false; + /* direction: "prev" or "next" */ + self.select = $scope.select = function(nextSlide, direction) { + var nextIndex = slides.indexOf(nextSlide); + //Decide direction if it's not given + if (direction === undefined) { + direction = nextIndex > currentIndex ? 'next' : 'prev'; + } + if (nextSlide && nextSlide !== self.currentSlide) { + if ($scope.$currentTransition) { + $scope.$currentTransition.cancel(); + //Timeout so ng-class in template has time to fix classes for finished slide + $timeout(goNext); + } else { + goNext(); + } + } + function goNext() { + // Scope has been destroyed, stop here. + if (destroyed) { return; } + //If we have a slide to transition from and we have a transition type and we're allowed, go + if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { + //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime + nextSlide.$element.addClass(direction); + var reflow = nextSlide.$element[0].offsetWidth; //force reflow + + //Set all other slides to stop doing their stuff for the new transition + angular.forEach(slides, function(slide) { + angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); + }); + angular.extend(nextSlide, {direction: direction, active: true, entering: true}); + angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); + + $scope.$currentTransition = $transition(nextSlide.$element, {}); + //We have to create new pointers inside a closure since next & current will change + (function(next,current) { + $scope.$currentTransition.then( + function(){ transitionDone(next, current); }, + function(){ transitionDone(next, current); } + ); + }(nextSlide, self.currentSlide)); + } else { + transitionDone(nextSlide, self.currentSlide); + } + self.currentSlide = nextSlide; + currentIndex = nextIndex; + //every time you change slides, reset the timer + restartTimer(); + } + function transitionDone(next, current) { + angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); + angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); + $scope.$currentTransition = null; + } + }; + $scope.$on('$destroy', function () { + destroyed = true; + }); + + /* Allow outside people to call indexOf on slides array */ + self.indexOfSlide = function(slide) { + return slides.indexOf(slide); + }; + + $scope.next = function() { + var newIndex = (currentIndex + 1) % slides.length; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'next'); + } + }; + + $scope.prev = function() { + var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'prev'); + } + }; + + $scope.isActive = function(slide) { + return self.currentSlide === slide; + }; + + $scope.$watch('interval', restartTimer); + $scope.$on('$destroy', resetTimer); + + function restartTimer() { + resetTimer(); + var interval = +$scope.interval; + if (!isNaN(interval) && interval>=0) { + currentTimeout = $timeout(timerFn, interval); + } + } + + function resetTimer() { + if (currentTimeout) { + $timeout.cancel(currentTimeout); + currentTimeout = null; + } + } + + function timerFn() { + if (isPlaying) { + $scope.next(); + restartTimer(); + } else { + $scope.pause(); + } + } + + $scope.play = function() { + if (!isPlaying) { + isPlaying = true; + restartTimer(); + } + }; + $scope.pause = function() { + if (!$scope.noPause) { + isPlaying = false; + resetTimer(); + } + }; + + self.addSlide = function(slide, element) { + slide.$element = element; + slides.push(slide); + //if this is the first slide or the slide is set to active, select it + if(slides.length === 1 || slide.active) { + self.select(slides[slides.length-1]); + if (slides.length == 1) { + $scope.play(); + } + } else { + slide.active = false; + } + }; + + self.removeSlide = function(slide) { + //get the index of the slide inside the carousel + var index = slides.indexOf(slide); + slides.splice(index, 1); + if (slides.length > 0 && slide.active) { + if (index >= slides.length) { + self.select(slides[index-1]); + } else { + self.select(slides[index]); + } + } else if (currentIndex > index) { + currentIndex--; + } + }; + +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:carousel + * @restrict EA + * + * @description + * Carousel is the outer container for a set of image 'slides' to showcase. + * + * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. + * @param {boolean=} noTransition Whether to disable transitions on the carousel. + * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). + * + * @example + + + + + + + + + + + + + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + + + */ +.directive('carousel', [function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + controller: 'CarouselController', + require: 'carousel', + templateUrl: 'template/carousel/carousel.html', + scope: { + interval: '=', + noTransition: '=', + noPause: '=' + } + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:slide + * @restrict EA + * + * @description + * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. + * + * @param {boolean=} active Model binding, whether or not this slide is currently active. + * + * @example + + +
      + + + + + + + Interval, in milliseconds: +
      Enter a negative number to stop the interval. +
      +
      + +function CarouselDemoCtrl($scope) { + $scope.myInterval = 5000; +} + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + +
      +*/ + +.directive('slide', function() { + return { + require: '^carousel', + restrict: 'EA', + transclude: true, + replace: true, + templateUrl: 'template/carousel/slide.html', + scope: { + active: '=?' + }, + link: function (scope, element, attrs, carouselCtrl) { + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}); + +angular.module('ui.bootstrap.dateparser', []) + +.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) { + + this.parsers = {}; + + var formatCodeToRegex = { + 'yyyy': { + regex: '\\d{4}', + apply: function(value) { this.year = +value; } + }, + 'yy': { + regex: '\\d{2}', + apply: function(value) { this.year = +value + 2000; } + }, + 'y': { + regex: '\\d{1,4}', + apply: function(value) { this.year = +value; } + }, + 'MMMM': { + regex: $locale.DATETIME_FORMATS.MONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } + }, + 'MMM': { + regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } + }, + 'MM': { + regex: '0[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'M': { + regex: '[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'dd': { + regex: '[0-2][0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'd': { + regex: '[1-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'EEEE': { + regex: $locale.DATETIME_FORMATS.DAY.join('|') + }, + 'EEE': { + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') + } + }; + + function createParser(format) { + var map = [], regex = format.split(''); + + angular.forEach(formatCodeToRegex, function(data, code) { + var index = format.indexOf(code); + + if (index > -1) { + format = format.split(''); + + regex[index] = '(' + data.regex + ')'; + format[index] = '$'; // Custom symbol to define consumed part of format + for (var i = index + 1, n = index + code.length; i < n; i++) { + regex[i] = ''; + format[i] = '$'; + } + format = format.join(''); + + map.push({ index: index, apply: data.apply }); + } + }); + + return { + regex: new RegExp('^' + regex.join('') + '$'), + map: orderByFilter(map, 'index') + }; + } + + this.parse = function(input, format) { + if ( !angular.isString(input) || !format ) { + return input; + } + + format = $locale.DATETIME_FORMATS[format] || format; + + if ( !this.parsers[format] ) { + this.parsers[format] = createParser(format); + } + + var parser = this.parsers[format], + regex = parser.regex, + map = parser.map, + results = input.match(regex); + + if ( results && results.length ) { + var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt; + + for( var i = 1, n = results.length; i < n; i++ ) { + var mapper = map[i-1]; + if ( mapper.apply ) { + mapper.apply.call(fields, results[i]); + } + } + + if ( isValid(fields.year, fields.month, fields.date) ) { + dt = new Date( fields.year, fields.month, fields.date, fields.hours); + } + + return dt; + } + }; + + // Check if date is valid for specific month (and year for February). + // Month: 0 = Jan, 1 = Feb, etc + function isValid(year, month, date) { + if ( month === 1 && date > 28) { + return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); + } + + if ( month === 3 || month === 5 || month === 8 || month === 10) { + return date < 31; + } + + return true; + } +}]); + +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, 'position') || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) + }; + }, + + /** + * Provides coordinates for the targetEl in relation to hostEl + */ + positionElements: function (hostEl, targetEl, positionStr, appendToBody) { + + var positionStrParts = positionStr.split('-'); + var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; + + var hostElPos, + targetElWidth, + targetElHeight, + targetElPos; + + hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + + targetElWidth = targetEl.prop('offsetWidth'); + targetElHeight = targetEl.prop('offsetHeight'); + + var shiftWidth = { + center: function () { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function () { + return hostElPos.left; + }, + right: function () { + return hostElPos.left + hostElPos.width; + } + }; + + var shiftHeight = { + center: function () { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function () { + return hostElPos.top; + }, + bottom: function () { + return hostElPos.top + hostElPos.height; + } + }; + + switch (pos0) { + case 'right': + targetElPos = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0]() + }; + break; + case 'left': + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth + }; + break; + case 'bottom': + targetElPos = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1]() + }; + break; + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElPos; + } + }; + }]); + +angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) + +.constant('datepickerConfig', { + formatDay: 'dd', + formatMonth: 'MMMM', + formatYear: 'yyyy', + formatDayHeader: 'EEE', + formatDayTitle: 'MMMM yyyy', + formatMonthTitle: 'yyyy', + datepickerMode: 'day', + minMode: 'day', + maxMode: 'year', + showWeeks: true, + startingDay: 0, + yearRange: 20, + minDate: null, + maxDate: null +}) + +.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; + + // Modes chain + this.modes = ['day', 'month', 'year']; + + // Configuration attributes + angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', + 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) { + self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; + }); + + // Watchable date attributes + angular.forEach(['minDate', 'maxDate'], function( key ) { + if ( $attrs[key] ) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = value ? new Date(value) : null; + self.refreshView(); + }); + } else { + self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; + } + }); + + $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; + $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); + this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date(); + + $scope.isActive = function(dateObject) { + if (self.compare(dateObject.date, self.activeDate) === 0) { + $scope.activeDateId = dateObject.uid; + return true; + } + return false; + }; + + this.init = function( ngModelCtrl_ ) { + ngModelCtrl = ngModelCtrl_; + + ngModelCtrl.$render = function() { + self.render(); + }; + }; + + this.render = function() { + if ( ngModelCtrl.$modelValue ) { + var date = new Date( ngModelCtrl.$modelValue ), + isValid = !isNaN(date); + + if ( isValid ) { + this.activeDate = date; + } else { + $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } + ngModelCtrl.$setValidity('date', isValid); + } + this.refreshView(); + }; + + this.refreshView = function() { + if ( this.element ) { + this._refreshView(); + + var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); + } + }; + + this.createDateObject = function(date, format) { + var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + return { + date: date, + label: dateFilter(date, format), + selected: model && this.compare(date, model) === 0, + disabled: this.isDisabled(date), + current: this.compare(date, new Date()) === 0 + }; + }; + + this.isDisabled = function( date ) { + return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); + }; + + // Split array into smaller arrays + this.split = function(arr, size) { + var arrays = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + }; + + $scope.select = function( date ) { + if ( $scope.datepickerMode === self.minMode ) { + var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); + ngModelCtrl.$setViewValue( dt ); + ngModelCtrl.$render(); + } else { + self.activeDate = date; + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ]; + } + }; + + $scope.move = function( direction ) { + var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), + month = self.activeDate.getMonth() + direction * (self.step.months || 0); + self.activeDate.setFullYear(year, month, 1); + self.refreshView(); + }; + + $scope.toggleMode = function( direction ) { + direction = direction || 1; + + if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { + return; + } + + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ]; + }; + + // Key event mapper + $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' }; + + var focusElement = function() { + $timeout(function() { + self.element[0].focus(); + }, 0 , false); + }; + + // Listen for focus requests from popup directive + $scope.$on('datepicker.focus', focusElement); + + $scope.keydown = function( evt ) { + var key = $scope.keys[evt.which]; + + if ( !key || evt.shiftKey || evt.altKey ) { + return; + } + + evt.preventDefault(); + evt.stopPropagation(); + + if (key === 'enter' || key === 'space') { + if ( self.isDisabled(self.activeDate)) { + return; // do nothing + } + $scope.select(self.activeDate); + focusElement(); + } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { + $scope.toggleMode(key === 'up' ? 1 : -1); + focusElement(); + } else { + self.handleKeyDown(key, evt); + self.refreshView(); + } + }; +}]) + +.directive( 'datepicker', function () { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/datepicker.html', + scope: { + datepickerMode: '=?', + dateDisabled: '&' + }, + require: ['datepicker', '?^ngModel'], + controller: 'DatepickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + datepickerCtrl.init( ngModelCtrl ); + } + } + }; +}) + +.directive('daypicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/day.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + scope.showWeeks = ctrl.showWeeks; + + ctrl.step = { months: 1 }; + ctrl.element = element; + + var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + function getDaysInMonth( year, month ) { + return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; + } + + function getDates(startDate, n) { + var dates = new Array(n), current = new Date(startDate), i = 0; + current.setHours(12); // Prevent repeated dates because of timezone bug + while ( i < n ) { + dates[i++] = new Date(current); + current.setDate( current.getDate() + 1 ); + } + return dates; + } + + ctrl._refreshView = function() { + var year = ctrl.activeDate.getFullYear(), + month = ctrl.activeDate.getMonth(), + firstDayOfMonth = new Date(year, month, 1), + difference = ctrl.startingDay - firstDayOfMonth.getDay(), + numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, + firstDate = new Date(firstDayOfMonth); + + if ( numDisplayedFromPreviousMonth > 0 ) { + firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); + } + + // 42 is the number of days on a six-month calendar + var days = getDates(firstDate, 42); + for (var i = 0; i < 42; i ++) { + days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), { + secondary: days[i].getMonth() !== month, + uid: scope.uniqueId + '-' + i + }); + } + + scope.labels = new Array(7); + for (var j = 0; j < 7; j++) { + scope.labels[j] = { + abbr: dateFilter(days[j].date, ctrl.formatDayHeader), + full: dateFilter(days[j].date, 'EEEE') + }; + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); + scope.rows = ctrl.split(days, 7); + + if ( scope.showWeeks ) { + scope.weekNumbers = []; + var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ), + numWeeks = scope.rows.length; + while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {} + } + }; + + ctrl.compare = function(date1, date2) { + return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); + }; + + function getISO8601WeekNumber(date) { + var checkDate = new Date(date); + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + } + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getDate(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 7; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 7; + } else if (key === 'pageup' || key === 'pagedown') { + var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setMonth(month, 1); + date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); + } else if (key === 'home') { + date = 1; + } else if (key === 'end') { + date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); + } + ctrl.activeDate.setDate(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('monthpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/month.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + ctrl.step = { years: 1 }; + ctrl.element = element; + + ctrl._refreshView = function() { + var months = new Array(12), + year = ctrl.activeDate.getFullYear(); + + for ( var i = 0; i < 12; i++ ) { + months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); + scope.rows = ctrl.split(months, 3); + }; + + ctrl.compare = function(date1, date2) { + return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getMonth(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 3; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 3; + } else if (key === 'pageup' || key === 'pagedown') { + var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setFullYear(year); + } else if (key === 'home') { + date = 0; + } else if (key === 'end') { + date = 11; + } + ctrl.activeDate.setMonth(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('yearpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/year.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + var range = ctrl.yearRange; + + ctrl.step = { years: range }; + ctrl.element = element; + + function getStartingYear( year ) { + return parseInt((year - 1) / range, 10) * range + 1; + } + + ctrl._refreshView = function() { + var years = new Array(range); + + for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) { + years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = [years[0].label, years[range - 1].label].join(' - '); + scope.rows = ctrl.split(years, 5); + }; + + ctrl.compare = function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getFullYear(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 5; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 5; + } else if (key === 'pageup' || key === 'pagedown') { + date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; + } else if (key === 'home') { + date = getStartingYear( ctrl.activeDate.getFullYear() ); + } else if (key === 'end') { + date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1; + } + ctrl.activeDate.setFullYear(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.constant('datepickerPopupConfig', { + datepickerPopup: 'yyyy-MM-dd', + currentText: 'Today', + clearText: 'Clear', + closeText: 'Done', + closeOnDateSelection: true, + appendToBody: false, + showButtonBar: true +}) + +.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', +function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { + return { + restrict: 'EA', + require: 'ngModel', + scope: { + isOpen: '=?', + currentText: '@', + clearText: '@', + closeText: '@', + dateDisabled: '&' + }, + link: function(scope, element, attrs, ngModel) { + var dateFormat, + closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, + appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + + scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; + + scope.getText = function( key ) { + return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; + }; + + attrs.$observe('datepickerPopup', function(value) { + dateFormat = value || datepickerPopupConfig.datepickerPopup; + ngModel.$render(); + }); + + // popup element used to display calendar + var popupEl = angular.element('
      '); + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection()' + }); + + function cameltoDash( string ){ + return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); + } + + // datepicker element + var datepickerEl = angular.element(popupEl.children()[0]); + if ( attrs.datepickerOptions ) { + angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) { + datepickerEl.attr( cameltoDash(option), value ); + }); + } + + scope.watchData = {}; + angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function( key ) { + if ( attrs[key] ) { + var getAttribute = $parse(attrs[key]); + scope.$parent.$watch(getAttribute, function(value){ + scope.watchData[key] = value; + }); + datepickerEl.attr(cameltoDash(key), 'watchData.' + key); + + // Propagate changes from datepicker to outside + if ( key === 'datepickerMode' ) { + var setAttribute = getAttribute.assign; + scope.$watch('watchData.' + key, function(value, oldvalue) { + if ( value !== oldvalue ) { + setAttribute(scope.$parent, value); + } + }); + } + } + }); + if (attrs.dateDisabled) { + datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); + } + + function parseDate(viewValue) { + if (!viewValue) { + ngModel.$setValidity('date', true); + return null; + } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { + ngModel.$setValidity('date', true); + return viewValue; + } else if (angular.isString(viewValue)) { + var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue); + if (isNaN(date)) { + ngModel.$setValidity('date', false); + return undefined; + } else { + ngModel.$setValidity('date', true); + return date; + } + } else { + ngModel.$setValidity('date', false); + return undefined; + } + } + ngModel.$parsers.unshift(parseDate); + + // Inner change + scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + scope.date = dt; + } + ngModel.$setViewValue(scope.date); + ngModel.$render(); + + if ( closeOnDateSelection ) { + scope.isOpen = false; + element[0].focus(); + } + }; + + element.bind('input change keyup', function() { + scope.$apply(function() { + scope.date = ngModel.$modelValue; + }); + }); + + // Outter change + ngModel.$render = function() { + var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; + element.val(date); + scope.date = parseDate( ngModel.$modelValue ); + }; + + var documentClickBind = function(event) { + if (scope.isOpen && event.target !== element[0]) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + }; + + var keydown = function(evt, noApply) { + scope.keydown(evt); + }; + element.bind('keydown', keydown); + + scope.keydown = function(evt) { + if (evt.which === 27) { + evt.preventDefault(); + evt.stopPropagation(); + scope.close(); + } else if (evt.which === 40 && !scope.isOpen) { + scope.isOpen = true; + } + }; + + scope.$watch('isOpen', function(value) { + if (value) { + scope.$broadcast('datepicker.focus'); + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); + + $document.bind('click', documentClickBind); + } else { + $document.unbind('click', documentClickBind); + } + }); + + scope.select = function( date ) { + if (date === 'today') { + var today = new Date(); + if (angular.isDate(ngModel.$modelValue)) { + date = new Date(ngModel.$modelValue); + date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); + } else { + date = new Date(today.setHours(0, 0, 0, 0)); + } + } + scope.dateSelection( date ); + }; + + scope.close = function() { + scope.isOpen = false; + element[0].focus(); + }; + + var $popup = $compile(popupEl)(scope); + // Prevent jQuery cache memory leak (template is now redundant after linking) + popupEl.remove(); + + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + + scope.$on('$destroy', function() { + $popup.remove(); + element.unbind('keydown', keydown); + $document.unbind('click', documentClickBind); + }); + } + }; +}]) + +.directive('datepickerPopupWrap', function() { + return { + restrict:'EA', + replace: true, + transclude: true, + templateUrl: 'template/datepicker/popup.html', + link:function (scope, element, attrs) { + element.bind('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + }); + } + }; +}); + +angular.module('ui.bootstrap.dropdown', []) + +.constant('dropdownConfig', { + openClass: 'open' +}) + +.service('dropdownService', ['$document', function($document) { + var openScope = null; + + this.open = function( dropdownScope ) { + if ( !openScope ) { + $document.bind('click', closeDropdown); + $document.bind('keydown', escapeKeyBind); + } + + if ( openScope && openScope !== dropdownScope ) { + openScope.isOpen = false; + } + + openScope = dropdownScope; + }; + + this.close = function( dropdownScope ) { + if ( openScope === dropdownScope ) { + openScope = null; + $document.unbind('click', closeDropdown); + $document.unbind('keydown', escapeKeyBind); + } + }; + + var closeDropdown = function( evt ) { + var toggleElement = openScope.getToggleElement(); + if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { + return; + } + + openScope.$apply(function() { + openScope.isOpen = false; + }); + }; + + var escapeKeyBind = function( evt ) { + if ( evt.which === 27 ) { + openScope.focusToggleElement(); + closeDropdown(); + } + }; +}]) + +.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) { + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop; + + this.init = function( element ) { + self.$element = element; + + if ( $attrs.isOpen ) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + }; + + this.toggle = function( open ) { + return scope.isOpen = arguments.length ? !!open : !scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.getToggleElement = function() { + return self.toggleElement; + }; + + scope.focusToggleElement = function() { + if ( self.toggleElement ) { + self.toggleElement[0].focus(); + } + }; + + scope.$watch('isOpen', function( isOpen, wasOpen ) { + $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); + + if ( isOpen ) { + scope.focusToggleElement(); + dropdownService.open( scope ); + } else { + dropdownService.close( scope ); + } + + setIsOpen($scope, isOpen); + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + + $scope.$on('$locationChangeSuccess', function() { + scope.isOpen = false; + }); + + $scope.$on('$destroy', function() { + scope.$destroy(); + }); +}]) + +.directive('dropdown', function() { + return { + restrict: 'CA', + controller: 'DropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + dropdownCtrl.init( element ); + } + }; +}) + +.directive('dropdownToggle', function() { + return { + restrict: 'CA', + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if ( !dropdownCtrl ) { + return; + } + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if ( !element.hasClass('disabled') && !attrs.disabled ) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } + }; +}); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) + +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function () { + return { + createNew: function () { + var stack = []; + + return { + add: function (key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function (key) { + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function () { + return stack[stack.length - 1]; + }, + remove: function (key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function () { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function () { + return stack.length; + } + }; + } + }; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', ['$timeout', function ($timeout) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + link: function (scope, element, attrs) { + scope.backdropClass = attrs.backdropClass || ''; + + scope.animate = false; + + //trigger CSS transitions + $timeout(function () { + scope.animate = true; + }); + } + }; + }]) + + .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + return { + restrict: 'EA', + scope: { + index: '@', + animate: '=' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'template/modal/window.html'; + }, + link: function (scope, element, attrs) { + element.addClass(attrs.windowClass || ''); + scope.size = attrs.size; + + $timeout(function () { + // trigger CSS transitions + scope.animate = true; + + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to loose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (!element[0].querySelectorAll('[autofocus]').length) { + element[0].focus(); + } + }); + + scope.close = function (evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + } + }; + }]) + + .directive('modalTransclude', function () { + return { + link: function($scope, $element, $attrs, controller, $transclude) { + $transclude($scope.$parent, function(clone) { + $element.empty(); + $element.append(clone); + }); + } + }; + }) + + .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', + function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var $modalStack = {}; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance) { + + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + //remove window DOM element + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() { + modalWindow.modalScope.$destroy(); + body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); + checkRemoveBackdrop(); + }); + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { + backdropScopeRef.$destroy(); + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, emulateTime, done) { + // Closing animation + scope.animate = false; + + var transitionEndEventName = $transition.transitionEndEventName; + if (transitionEndEventName) { + // transition out + var timeout = $timeout(afterAnimating, emulateTime); + + domEl.bind(transitionEndEventName, function () { + $timeout.cancel(timeout); + afterAnimating(); + scope.$apply(); + }); + } else { + // Ensure this call is async + $timeout(afterAnimating); + } + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + domEl.remove(); + if (done) { + done(); + } + } + } + + $document.bind('keydown', function (evt) { + var modal; + + if (evt.which === 27) { + modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + evt.preventDefault(); + $rootScope.$apply(function () { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + } + } + }); + + $modalStack.open = function (modalInstance, modal) { + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard + }); + + var body = $document.find('body').eq(0), + currBackdropIndex = backdropIndex(); + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.index = currBackdropIndex; + var angularBackgroundDomEl = angular.element('
      '); + angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); + backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
      '); + angularDomEl.attr({ + 'template-url': modal.windowTemplateUrl, + 'window-class': modal.windowClass, + 'size': modal.size, + 'index': openedWindows.length() - 1, + 'animate': 'animate' + }).html(modal.content); + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + body.append(modalDomEl); + body.addClass(OPENED_MODAL_CLASS); + }; + + $modalStack.close = function (modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.deferred.resolve(result); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismiss = function (modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.deferred.reject(reason); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismissAll = function (reason) { + var topModal = this.getTop(); + while (topModal) { + this.dismiss(topModal.key, reason); + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function () { + return openedWindows.top(); + }; + + return $modalStack; + }]) + + .provider('$modal', function () { + + var $modalProvider = { + options: { + backdrop: true, //can be also false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', + function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { + + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl, + {cache: $templateCache}).then(function (result) { + return result.data; + }); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function (value) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } + }); + return promisesArr; + } + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + close: function (result) { + $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + + templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function (value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + if (modalOptions.controllerAs) { + modalScope[modalOptions.controllerAs] = ctrlInstance; + } + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + content: tplAndVars[0], + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + backdropClass: modalOptions.backdropClass, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size + }); + + }, function resolveError(reason) { + modalResultDeferred.reject(reason); + }); + + templateAndResolvePromise.then(function () { + modalOpenedDeferred.resolve(true); + }, function () { + modalOpenedDeferred.reject(false); + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.pagination', []) + +.controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; + + this.init = function(ngModelCtrl_, config) { + ngModelCtrl = ngModelCtrl_; + this.config = config; + + ngModelCtrl.$render = function() { + self.render(); + }; + + if ($attrs.itemsPerPage) { + $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { + self.itemsPerPage = parseInt(value, 10); + $scope.totalPages = self.calculateTotalPages(); + }); + } else { + this.itemsPerPage = config.itemsPerPage; + } + }; + + this.calculateTotalPages = function() { + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); + }; + + this.render = function() { + $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; + }; + + $scope.selectPage = function(page) { + if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) { + ngModelCtrl.$setViewValue(page); + ngModelCtrl.$render(); + } + }; + + $scope.getText = function( key ) { + return $scope[key + 'Text'] || self.config[key + 'Text']; + }; + $scope.noPrevious = function() { + return $scope.page === 1; + }; + $scope.noNext = function() { + return $scope.page === $scope.totalPages; + }; + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( $scope.page > value ) { + $scope.selectPage(value); + } else { + ngModelCtrl.$render(); + } + }); +}]) + +.constant('paginationConfig', { + itemsPerPage: 10, + boundaryLinks: false, + directionLinks: true, + firstText: 'First', + previousText: 'Previous', + nextText: 'Next', + lastText: 'Last', + rotate: true +}) + +.directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + firstText: '@', + previousText: '@', + nextText: '@', + lastText: '@' + }, + require: ['pagination', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pagination.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + // Setup configuration parameters + var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize, + rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate; + scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; + scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks; + + paginationCtrl.init(ngModelCtrl, paginationConfig); + + if (attrs.maxSize) { + scope.$parent.$watch($parse(attrs.maxSize), function(value) { + maxSize = parseInt(value, 10); + paginationCtrl.render(); + }); + } + + // Create page object used in template + function makePage(number, text, isActive) { + return { + number: number, + text: text, + active: isActive + }; + } + + function getPages(currentPage, totalPages) { + var pages = []; + + // Default page limits + var startPage = 1, endPage = totalPages; + var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); + + // recompute if maxSize + if ( isMaxSized ) { + if ( rotate ) { + // Current page is displayed in the middle of the visible ones + startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); + endPage = startPage + maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > totalPages) { + endPage = totalPages; + startPage = endPage - maxSize + 1; + } + } else { + // Visible pages are paginated with maxSize + startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; + + // Adjust last page if limit is exceeded + endPage = Math.min(startPage + maxSize - 1, totalPages); + } + } + + // Add page number links + for (var number = startPage; number <= endPage; number++) { + var page = makePage(number, number, number === currentPage); + pages.push(page); + } + + // Add links to move between page sets + if ( isMaxSized && ! rotate ) { + if ( startPage > 1 ) { + var previousPageSet = makePage(startPage - 1, '...', false); + pages.unshift(previousPageSet); + } + + if ( endPage < totalPages ) { + var nextPageSet = makePage(endPage + 1, '...', false); + pages.push(nextPageSet); + } + } + + return pages; + } + + var originalRender = paginationCtrl.render; + paginationCtrl.render = function() { + originalRender(); + if (scope.page > 0 && scope.page <= scope.totalPages) { + scope.pages = getPages(scope.page, scope.totalPages); + } + }; + } + }; +}]) + +.constant('pagerConfig', { + itemsPerPage: 10, + previousText: '« Previous', + nextText: 'Next »', + align: true +}) + +.directive('pager', ['pagerConfig', function(pagerConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + previousText: '@', + nextText: '@' + }, + require: ['pager', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pager.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align; + paginationCtrl.init(ngModelCtrl, pagerConfig); + } + }; +}]); + +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0 + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers ( triggers ) { + angular.extend( triggerMap, triggers ); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers ( trigger ) { + var show = trigger || options.trigger || defaultTriggerShow; + var hide = triggerMap[show] || show; + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case( type ); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
      '+ + '
      '; + + return { + restrict: 'EA', + scope: true, + compile: function (tElem, tAttrs) { + var tooltipLinker = $compile( template ); + + return function link ( scope, element, attrs ) { + var tooltip; + var transitionTimeout; + var popupTimeout; + var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; + var triggers = getTriggers( undefined ); + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + + var positionTooltip = function () { + + var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody); + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; + + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } + if ( scope.tt_popupDelay ) { + // Do nothing if the tooltip was already scheduled to pop-up. + // This happens if show is triggered multiple times before any hide is triggered. + if (!popupTimeout) { + popupTimeout = $timeout( show, scope.tt_popupDelay, false ); + popupTimeout.then(function(reposition){reposition();}); + } + } else { + show()(); + } + } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + + popupTimeout = null; + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + transitionTimeout = null; + } + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return angular.noop; + } + + createTooltip(); + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + positionTooltip(); + + // And show the tooltip. + scope.tt_isOpen = true; + scope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); + popupTimeout = null; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( scope.tt_animation ) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 500); + } + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltip = tooltipLinker(scope, function () {}); + + // Get contents rendered into the tooltip + scope.$digest(); + } + + function removeTooltip() { + transitionTimeout = null; + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + + if (!val && scope.tt_isOpen ) { + hide(); + } + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + var unregisterTriggers = function () { + element.unbind(triggers.show, showTooltipBind); + element.unbind(triggers.hide, hideTooltipBind); + }; + + attrs.$observe( prefix+'Trigger', function ( val ) { + unregisterTriggers(); + + triggers = getTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + }); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + + attrs.$observe( prefix+'AppendToBody', function ( val ) { + appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; + }); + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if ( appendToBody ) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { + if ( scope.tt_isOpen ) { + hide(); + } + }); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel( transitionTimeout ); + $timeout.cancel( popupTimeout ); + unregisterTriggers(); + removeTooltip(); + }); + }; + } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +}]); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html popovers, and selector delegatation. + */ +angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) + +.directive( 'popoverPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html' + }; +}) + +.directive( 'popover', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'popover', 'popover', 'click' ); +}]); + +angular.module('ui.bootstrap.progressbar', []) + +.constant('progressConfig', { + animate: true, + max: 100 +}) + +.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { + var self = this, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; + + this.bars = []; + $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max; + + this.addBar = function(bar, element) { + if ( !animate ) { + element.css({'transition': 'none'}); + } + + this.bars.push(bar); + + bar.$watch('value', function( value ) { + bar.percent = +(100 * value / $scope.max).toFixed(2); + }); + + bar.$on('$destroy', function() { + element = null; + self.removeBar(bar); + }); + }; + + this.removeBar = function(bar) { + this.bars.splice(this.bars.indexOf(bar), 1); + }; +}]) + +.directive('progress', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + require: 'progress', + scope: {}, + templateUrl: 'template/progressbar/progress.html' + }; +}) + +.directive('bar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + require: '^progress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, element); + } + }; +}) + +.directive('progressbar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, angular.element(element.children()[0])); + } + }; +}); +angular.module('ui.bootstrap.rating', []) + +.constant('ratingConfig', { + max: 5, + stateOn: null, + stateOff: null +}) + +.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) { + var ngModelCtrl = { $setViewValue: angular.noop }; + + this.init = function(ngModelCtrl_) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; + this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; + + var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : + new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max ); + $scope.range = this.buildTemplateObjects(ratingStates); + }; + + this.buildTemplateObjects = function(states) { + for (var i = 0, n = states.length; i < n; i++) { + states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]); + } + return states; + }; + + $scope.rate = function(value) { + if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) { + ngModelCtrl.$setViewValue(value); + ngModelCtrl.$render(); + } + }; + + $scope.enter = function(value) { + if ( !$scope.readonly ) { + $scope.value = value; + } + $scope.onHover({value: value}); + }; + + $scope.reset = function() { + $scope.value = ngModelCtrl.$viewValue; + $scope.onLeave(); + }; + + $scope.onKeydown = function(evt) { + if (/(37|38|39|40)/.test(evt.which)) { + evt.preventDefault(); + evt.stopPropagation(); + $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) ); + } + }; + + this.render = function() { + $scope.value = ngModelCtrl.$viewValue; + }; +}]) + +.directive('rating', function() { + return { + restrict: 'EA', + require: ['rating', 'ngModel'], + scope: { + readonly: '=?', + onHover: '&', + onLeave: '&' + }, + controller: 'RatingController', + templateUrl: 'template/rating/rating.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + ratingCtrl.init( ngModelCtrl ); + } + } + }; +}); + +/** + * @ngdoc overview + * @name ui.bootstrap.tabs + * + * @description + * AngularJS version of the tabs directive. + */ + +angular.module('ui.bootstrap.tabs', []) + +.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { + var ctrl = this, + tabs = ctrl.tabs = $scope.tabs = []; + + ctrl.select = function(selectedTab) { + angular.forEach(tabs, function(tab) { + if (tab.active && tab !== selectedTab) { + tab.active = false; + tab.onDeselect(); + } + }); + selectedTab.active = true; + selectedTab.onSelect(); + }; + + ctrl.addTab = function addTab(tab) { + tabs.push(tab); + // we can't run the select function on the first tab + // since that would select it twice + if (tabs.length === 1) { + tab.active = true; + } else if (tab.active) { + ctrl.select(tab); + } + }; + + ctrl.removeTab = function removeTab(tab) { + var index = tabs.indexOf(tab); + //Select a new tab if the tab to be removed is selected + if (tab.active && tabs.length > 1) { + //If this is the last tab, select the previous tab. else, the next tab. + var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; + ctrl.select(tabs[newActiveIndex]); + } + tabs.splice(index, 1); + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabset + * @restrict EA + * + * @description + * Tabset is the outer container for the tabs directive + * + * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. + * @param {boolean=} justified Whether or not to use justified styling for the tabs. + * + * @example + + + + First Content! + Second Content! + +
      + + First Vertical Content! + Second Vertical Content! + + + First Justified Content! + Second Justified Content! + +
      +
      + */ +.directive('tabset', function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: { + type: '@' + }, + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; + } + }; +}) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tab + * @restrict EA + * + * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. + * @param {string=} select An expression to evaluate when the tab is selected. + * @param {boolean=} active A binding, telling whether or not this tab is selected. + * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. + * + * @description + * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. + * + * @example + + +
      + + +
      + + First Tab + + Alert me! + Second Tab, with alert callback and html heading! + + + {{item.content}} + + +
      +
      + + function TabsDemoCtrl($scope) { + $scope.items = [ + { title:"Dynamic Title 1", content:"Dynamic Item 0" }, + { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } + ]; + + $scope.alertMe = function() { + setTimeout(function() { + alert("You've selected the alert tab!"); + }); + }; + }; + +
      + */ + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabHeading + * @restrict EA + * + * @description + * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. + * + * @example + + + + + HTML in my titles?! + And some content, too! + + + Icon heading?!? + That's right. + + + + + */ +.directive('tab', ['$parse', function($parse) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + active: '=?', + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + compile: function(elm, attrs, transclude) { + return function postLink(scope, elm, attrs, tabsetCtrl) { + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); + } + }); + + scope.disabled = false; + if ( attrs.disabled ) { + scope.$parent.$watch($parse(attrs.disabled), function(value) { + scope.disabled = !! value; + }); + } + + scope.select = function() { + if ( !scope.disabled ) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + }; + } + }; +}]) + +.directive('tabHeadingTransclude', [function() { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm, attrs, tabCtrl) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; +}]) + +.directive('tabContentTransclude', function() { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } else { + elm.append(node); + } + }); + }); + } + }; + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' + ); + } +}) + +; + +angular.module('ui.bootstrap.timepicker', []) + +.constant('timepickerConfig', { + hourStep: 1, + minuteStep: 1, + showMeridian: true, + meridians: null, + readonlyInput: false, + mousewheel: true +}) + +.controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) { + var selected = new Date(), + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; + + this.init = function( ngModelCtrl_, inputs ) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + var hoursInputEl = inputs.eq(0), + minutesInputEl = inputs.eq(1); + + var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; + if ( mousewheel ) { + this.setupMousewheelEvents( hoursInputEl, minutesInputEl ); + } + + $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; + this.setupInputEvents( hoursInputEl, minutesInputEl ); + }; + + var hourStep = timepickerConfig.hourStep; + if ($attrs.hourStep) { + $scope.$parent.$watch($parse($attrs.hourStep), function(value) { + hourStep = parseInt(value, 10); + }); + } + + var minuteStep = timepickerConfig.minuteStep; + if ($attrs.minuteStep) { + $scope.$parent.$watch($parse($attrs.minuteStep), function(value) { + minuteStep = parseInt(value, 10); + }); + } + + // 12H / 24H mode + $scope.showMeridian = timepickerConfig.showMeridian; + if ($attrs.showMeridian) { + $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { + $scope.showMeridian = !!value; + + if ( ngModelCtrl.$error.time ) { + // Evaluate from template + var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); + if (angular.isDefined( hours ) && angular.isDefined( minutes )) { + selected.setHours( hours ); + refresh(); + } + } else { + updateTemplate(); + } + }); + } + + // Get $scope.hours in 24H mode if valid + function getHoursFromTemplate ( ) { + var hours = parseInt( $scope.hours, 10 ); + var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); + if ( !valid ) { + return undefined; + } + + if ( $scope.showMeridian ) { + if ( hours === 12 ) { + hours = 0; + } + if ( $scope.meridian === meridians[1] ) { + hours = hours + 12; + } + } + return hours; + } + + function getMinutesFromTemplate() { + var minutes = parseInt($scope.minutes, 10); + return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; + } + + function pad( value ) { + return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; + } + + // Respond on mousewheel spin + this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) { + var isScrollingUp = function(e) { + if (e.originalEvent) { + e = e.originalEvent; + } + //pick correct delta variable depending on event + var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; + return (e.detail || delta > 0); + }; + + hoursInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() ); + e.preventDefault(); + }); + + minutesInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() ); + e.preventDefault(); + }); + + }; + + this.setupInputEvents = function( hoursInputEl, minutesInputEl ) { + if ( $scope.readonlyInput ) { + $scope.updateHours = angular.noop; + $scope.updateMinutes = angular.noop; + return; + } + + var invalidate = function(invalidHours, invalidMinutes) { + ngModelCtrl.$setViewValue( null ); + ngModelCtrl.$setValidity('time', false); + if (angular.isDefined(invalidHours)) { + $scope.invalidHours = invalidHours; + } + if (angular.isDefined(invalidMinutes)) { + $scope.invalidMinutes = invalidMinutes; + } + }; + + $scope.updateHours = function() { + var hours = getHoursFromTemplate(); + + if ( angular.isDefined(hours) ) { + selected.setHours( hours ); + refresh( 'h' ); + } else { + invalidate(true); + } + }; + + hoursInputEl.bind('blur', function(e) { + if ( !$scope.invalidHours && $scope.hours < 10) { + $scope.$apply( function() { + $scope.hours = pad( $scope.hours ); + }); + } + }); + + $scope.updateMinutes = function() { + var minutes = getMinutesFromTemplate(); + + if ( angular.isDefined(minutes) ) { + selected.setMinutes( minutes ); + refresh( 'm' ); + } else { + invalidate(undefined, true); + } + }; + + minutesInputEl.bind('blur', function(e) { + if ( !$scope.invalidMinutes && $scope.minutes < 10 ) { + $scope.$apply( function() { + $scope.minutes = pad( $scope.minutes ); + }); + } + }); + + }; + + this.render = function() { + var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null; + + if ( isNaN(date) ) { + ngModelCtrl.$setValidity('time', false); + $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } else { + if ( date ) { + selected = date; + } + makeValid(); + updateTemplate(); + } + }; + + // Call internally when we know that model is valid. + function refresh( keyboardChange ) { + makeValid(); + ngModelCtrl.$setViewValue( new Date(selected) ); + updateTemplate( keyboardChange ); + } + + function makeValid() { + ngModelCtrl.$setValidity('time', true); + $scope.invalidHours = false; + $scope.invalidMinutes = false; + } + + function updateTemplate( keyboardChange ) { + var hours = selected.getHours(), minutes = selected.getMinutes(); + + if ( $scope.showMeridian ) { + hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system + } + + $scope.hours = keyboardChange === 'h' ? hours : pad(hours); + $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); + $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + } + + function addMinutes( minutes ) { + var dt = new Date( selected.getTime() + minutes * 60000 ); + selected.setHours( dt.getHours(), dt.getMinutes() ); + refresh(); + } + + $scope.incrementHours = function() { + addMinutes( hourStep * 60 ); + }; + $scope.decrementHours = function() { + addMinutes( - hourStep * 60 ); + }; + $scope.incrementMinutes = function() { + addMinutes( minuteStep ); + }; + $scope.decrementMinutes = function() { + addMinutes( - minuteStep ); + }; + $scope.toggleMeridian = function() { + addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); + }; +}]) + +.directive('timepicker', function () { + return { + restrict: 'EA', + require: ['timepicker', '?^ngModel'], + controller:'TimepickerController', + replace: true, + scope: {}, + templateUrl: 'template/timepicker/timepicker.html', + link: function(scope, element, attrs, ctrls) { + var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + timepickerCtrl.init( ngModelCtrl, element.find('input') ); + } + } + }; +}); + +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) + +/** + * A helper service that can parse typeahead's syntax (string provided by users) + * Extracted to a separate service for ease of unit testing + */ + .factory('typeaheadParser', ['$parse', function ($parse) { + + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; + + return { + parse:function (input) { + + var match = input.match(TYPEAHEAD_REGEXP); + if (!match) { + throw new Error( + 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + + ' but got "' + input + '".'); + } + + return { + itemName:match[3], + source:$parse(match[4]), + viewMapper:$parse(match[2] || match[1]), + modelMapper:$parse(match[1]) + }; + } + }; +}]) + + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', + function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { + + var HOT_KEYS = [9, 13, 27, 38, 40]; + + return { + require:'ngModel', + link:function (originalScope, element, attrs, modelCtrl) { + + //SUPPORTED ATTRIBUTES (OPTIONS) + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; + + //minimal wait time after last character typed before typehead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); + + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + + var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; + + //INTERNAL VARIABLES + + //model setter executed upon match selection + var $setModelValue = $parse(attrs.ngModel).assign; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + var hasFocus; + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function(){ + scope.$destroy(); + }); + + // WAI-ARIA + var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + element.attr({ + 'aria-autocomplete': 'list', + 'aria-expanded': false, + 'aria-owns': popupId + }); + + //pop-up element used to display matches + var popUpEl = angular.element('
      '); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; + + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + + var getMatchesAsync = function(inputValue) { + + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches.length > 0) { + + scope.activeIdx = 0; + scope.matches.length = 0; + + //transform labels + for(var i=0; i= minSearch) { + if (waitTime > 0) { + cancelPreviousTimeout(); + scheduleSearchWithTimeout(inputValue); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); + cancelPreviousTimeout(); + resetMatches(); + } + + if (isEditable) { + return inputValue; + } else { + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return inputValue; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } + } + }); + + modelCtrl.$formatters.push(function (modelValue) { + + var candidateViewValue, emptyViewValue; + var locals = {}; + + if (inputFormatter) { + + locals['$model'] = modelValue; + return inputFormatter(originalScope, locals); + + } else { + + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + } + }); + + scope.select = function (activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); + + resetMatches(); + + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + $timeout(function() { element[0].focus(); }, 0, false); + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function (evt) { + + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function (evt) { + hasFocus = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function (evt) { + if (element[0] !== evt.target) { + resetMatches(); + scope.$digest(); + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function(){ + $document.unbind('click', dismissClickHandler); + }); + + var $popup = $compile(popUpEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + } + }; + +}]) + + .directive('typeaheadPopup', function () { + return { + restrict:'EA', + scope:{ + matches:'=', + query:'=', + active:'=', + position:'=', + select:'&' + }, + replace:true, + templateUrl:'template/typeahead/typeahead-popup.html', + link:function (scope, element, attrs) { + + scope.templateUrl = attrs.templateUrl; + + scope.isOpen = function () { + return scope.matches.length > 0; + }; + + scope.isActive = function (matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function (matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function (activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }) + + .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { + return { + restrict:'EA', + scope:{ + index:'=', + match:'=', + query:'=' + }, + link:function (scope, element, attrs) { + var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; + $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ + element.replaceWith($compile(tplContent.trim())(scope)); + }); + } + }; + }]) + + .filter('typeaheadHighlight', function() { + + function escapeRegexp(queryToEscape) { + return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + } + + return function(matchItem, query) { + return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + }; + }); + +angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/accordion/accordion-group.html", + "
      \n" + + "
      \n" + + "

      \n" + + " {{heading}}\n" + + "

      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + "
      "); +}]); + +angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/accordion/accordion.html", + "
      "); +}]); + +angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/alert/alert.html", + "
      \n" + + " \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/carousel/carousel.html", + "
      \n" + + "
        1\">\n" + + "
      1. \n" + + "
      \n" + + "
      \n" + + " 1\">\n" + + " 1\">\n" + + "
      \n" + + ""); +}]); + +angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/carousel/slide.html", + "
      \n" + + ""); +}]); + +angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/datepicker.html", + "
      \n" + + " \n" + + " \n" + + " \n" + + "
      "); +}]); + +angular.module("template/datepicker/day.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/day.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
      {{label.abbr}}
      {{ weekNumbers[$index] }}\n" + + " \n" + + "
      \n" + + ""); +}]); + +angular.module("template/datepicker/month.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/month.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
      \n" + + " \n" + + "
      \n" + + ""); +}]); + +angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/popup.html", + "
        \n" + + "
      • \n" + + "
      • \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
      • \n" + + "
      \n" + + ""); +}]); + +angular.module("template/datepicker/year.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/datepicker/year.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
      \n" + + " \n" + + "
      \n" + + ""); +}]); + +angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/backdrop.html", + "
      \n" + + ""); +}]); + +angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/modal/window.html", + "
      \n" + + "
      \n" + + "
      "); +}]); + +angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/pagination/pager.html", + ""); +}]); + +angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/pagination/pagination.html", + ""); +}]); + +angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tooltip/tooltip-popup.html", + "
      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/popover/popover.html", + "
      \n" + + "
      \n" + + "\n" + + "
      \n" + + "

      \n" + + "
      \n" + + "
      \n" + + "
      \n" + + ""); +}]); + +angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/bar.html", + "
      "); +}]); + +angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/progress.html", + "
      "); +}]); + +angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/progressbar/progressbar.html", + "
      \n" + + "
      \n" + + "
      "); +}]); + +angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/rating/rating.html", + "\n" + + " \n" + + " ({{ $index < value ? '*' : ' ' }})\n" + + " \n" + + ""); +}]); + +angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tab.html", + "
    • \n" + + " {{heading}}\n" + + "
    • \n" + + ""); +}]); + +angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/tabs/tabset.html", + "
      \n" + + "
        \n" + + "
        \n" + + "
        \n" + + "
        \n" + + "
        \n" + + "
        \n" + + ""); +}]); + +angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/timepicker/timepicker.html", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
         
        \n" + + " \n" + + " :\n" + + " \n" + + "
         
        \n" + + ""); +}]); + +angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/typeahead/typeahead-match.html", + ""); +}]); + +angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { + $templateCache.put("template/typeahead/typeahead-popup.html", + "
          \n" + + "
        • \n" + + "
          \n" + + "
        • \n" + + "
        \n" + + ""); +}]); diff --git a/bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js b/bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js new file mode 100644 index 0000000..10e140e --- /dev/null +++ b/bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js @@ -0,0 +1,10 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.2 - 2014-09-26 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.tpls","ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(a){return{link:function(b,c,d){function e(b){function d(){j===e&&(j=void 0)}var e=a(c,b);return j&&j.cancel(),j=e,e.then(d,d),e}function f(){k?(k=!1,g()):(c.removeClass("collapse").addClass("collapsing"),e({height:c[0].scrollHeight+"px"}).then(g))}function g(){c.removeClass("collapsing"),c.addClass("collapse in"),c.css({height:"auto"})}function h(){if(k)k=!1,i(),c.css({height:0});else{c.css({height:c[0].scrollHeight+"px"});{c[0].offsetWidth}c.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(i)}}function i(){c.removeClass("collapsing"),c.addClass("collapse")}var j,k=!0;b.$watch(d.collapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.$watch("isOpen",function(b){b&&d.closeOthers(a)}),a.toggleOpen=function(){a.isDisabled||(a.isOpen=!a.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$transition",function(a,b,c){function d(){e();var c=+a.interval;!isNaN(c)&&c>=0&&(g=b(f,c))}function e(){g&&(b.cancel(g),g=null)}function f(){h?(a.next(),d()):a.pause()}var g,h,i=this,j=i.slides=a.slides=[],k=-1;i.currentSlide=null;var l=!1;i.select=a.select=function(e,f){function g(){if(!l){if(i.currentSlide&&angular.isString(f)&&!a.noTransition&&e.$element){e.$element.addClass(f);{e.$element[0].offsetWidth}angular.forEach(j,function(a){angular.extend(a,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(e,{direction:f,active:!0,entering:!0}),angular.extend(i.currentSlide||{},{direction:f,leaving:!0}),a.$currentTransition=c(e.$element,{}),function(b,c){a.$currentTransition.then(function(){h(b,c)},function(){h(b,c)})}(e,i.currentSlide)}else h(e,i.currentSlide);i.currentSlide=e,k=m,d()}}function h(b,c){angular.extend(b,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(c||{},{direction:"",active:!1,leaving:!1,entering:!1}),a.$currentTransition=null}var m=j.indexOf(e);void 0===f&&(f=m>k?"next":"prev"),e&&e!==i.currentSlide&&(a.$currentTransition?(a.$currentTransition.cancel(),b(g)):g())},a.$on("$destroy",function(){l=!0}),i.indexOfSlide=function(a){return j.indexOf(a)},a.next=function(){var b=(k+1)%j.length;return a.$currentTransition?void 0:i.select(j[b],"next")},a.prev=function(){var b=0>k-1?j.length-1:k-1;return a.$currentTransition?void 0:i.select(j[b],"prev")},a.isActive=function(a){return i.currentSlide===a},a.$watch("interval",d),a.$on("$destroy",e),a.play=function(){h||(h=!0,d())},a.pause=function(){a.noPause||(h=!1,e())},i.addSlide=function(b,c){b.$element=c,j.push(b),1===j.length||b.active?(i.select(j[j.length-1]),1==j.length&&a.play()):b.active=!1},i.removeSlide=function(a){var b=j.indexOf(a);j.splice(b,1),j.length>0&&a.active?i.select(b>=j.length?j[b-1]:j[b]):k>b&&k--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$locale","orderByFilter",function(a,b){function c(a){var c=[],d=a.split("");return angular.forEach(e,function(b,e){var f=a.indexOf(e);if(f>-1){a=a.split(""),d[f]="("+b.regex+")",a[f]="$";for(var g=f+1,h=f+e.length;h>g;g++)d[g]="",a[g]="$";a=a.join(""),c.push({index:f,apply:b.apply})}}),{regex:new RegExp("^"+d.join("")+"$"),map:b(c,"index")}}function d(a,b,c){return 1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}this.parsers={};var e={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:a.DATETIME_FORMATS.MONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.MONTH.indexOf(b)}},MMM:{regex:a.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.SHORTMONTH.indexOf(b)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:a.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:a.DATETIME_FORMATS.SHORTDAY.join("|")}};this.parse=function(b,e){if(!angular.isString(b)||!e)return b;e=a.DATETIME_FORMATS[e]||e,this.parsers[e]||(this.parsers[e]=c(e));var f=this.parsers[e],g=f.regex,h=f.map,i=b.match(g);if(i&&i.length){for(var j,k={year:1900,month:0,date:1,hours:0},l=1,m=i.length;m>l;l++){var n=h[l-1];n.apply&&n.apply.call(k,i[l])}return d(k.year,k.month,k.date)&&(j=new Date(k.year,k.month,k.date,k.hours)),j}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$timeout","$log","dateFilter","datepickerConfig",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","minMode","maxMode","showWeeks","startingDay","yearRange"],function(c,e){i[c]=angular.isDefined(b[c])?8>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):h[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=h[d]?new Date(h[d]):null}),a.datepickerMode=a.datepickerMode||h.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),this.activeDate=angular.isDefined(b.initDate)?a.$parent.$eval(b.initDate):new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$modelValue){var a=new Date(j.$modelValue),b=!isNaN(a);b?this.activeDate=a:f.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'),j.$setValidity("date",b)}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$modelValue?new Date(j.$modelValue):null;j.$setValidity("date-disabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$modelValue?new Date(j.$modelValue):null;return{date:a,label:g(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$modelValue?new Date(j.$modelValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){e(function(){i.element[0].focus()},0,!1)};a.$on("datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate),k()}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):(a.toggleMode("up"===c?1:-1),k())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{datepickerMode:"=?",dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}).directive("daypicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(b,c,d,e){function f(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?i[b]:29}function g(a,b){var c=new Array(b),d=new Date(a),e=0;for(d.setHours(12);b>e;)c[e++]=new Date(d),d.setDate(d.getDate()+1);return c}function h(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}b.showWeeks=e.showWeeks,e.step={months:1},e.element=c;var i=[31,28,31,30,31,30,31,31,30,31,30,31];e._refreshView=function(){var c=e.activeDate.getFullYear(),d=e.activeDate.getMonth(),f=new Date(c,d,1),i=e.startingDay-f.getDay(),j=i>0?7-i:-i,k=new Date(f);j>0&&k.setDate(-j+1);for(var l=g(k,42),m=0;42>m;m++)l[m]=angular.extend(e.createDateObject(l[m],e.formatDay),{secondary:l[m].getMonth()!==d,uid:b.uniqueId+"-"+m});b.labels=new Array(7);for(var n=0;7>n;n++)b.labels[n]={abbr:a(l[n].date,e.formatDayHeader),full:a(l[n].date,"EEEE")};if(b.title=a(e.activeDate,e.formatDayTitle),b.rows=e.split(l,7),b.showWeeks){b.weekNumbers=[];for(var o=h(b.rows[0][0].date),p=b.rows.length;b.weekNumbers.push(o++)f;f++)c[f]=angular.extend(e.createDateObject(new Date(d,f,1),e.formatMonth),{uid:b.uniqueId+"-"+f});b.title=a(e.activeDate,e.formatMonthTitle),b.rows=e.split(c,3)},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},e.handleKeyDown=function(a){var b=e.activeDate.getMonth();if("left"===a)b-=1;else if("up"===a)b-=3;else if("right"===a)b+=1;else if("down"===a)b+=3;else if("pageup"===a||"pagedown"===a){var c=e.activeDate.getFullYear()+("pageup"===a?-1:1);e.activeDate.setFullYear(c)}else"home"===a?b=0:"end"===a&&(b=11);e.activeDate.setMonth(b)},e.refreshView()}}}]).directive("yearpicker",["dateFilter",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(a,b,c,d){function e(a){return parseInt((a-1)/f,10)*f+1}var f=d.yearRange;d.step={years:f},d.element=b,d._refreshView=function(){for(var b=new Array(f),c=0,g=e(d.activeDate.getFullYear());f>c;c++)b[c]=angular.extend(d.createDateObject(new Date(g+c,0,1),d.formatYear),{uid:a.uniqueId+"-"+c});a.title=[b[0].label,b[f-1].label].join(" - "),a.rows=d.split(b,5)},d.compare=function(a,b){return a.getFullYear()-b.getFullYear()},d.handleKeyDown=function(a){var b=d.activeDate.getFullYear();"left"===a?b-=1:"up"===a?b-=5:"right"===a?b+=1:"down"===a?b+=5:"pageup"===a||"pagedown"===a?b+=("pageup"===a?-1:1)*d.step.years:"home"===a?b=e(d.activeDate.getFullYear()):"end"===a&&(b=e(d.activeDate.getFullYear())+f-1),d.activeDate.setFullYear(b)},d.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","dateParser","datepickerPopupConfig",function(a,b,c,d,e,f,g){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&"},link:function(h,i,j,k){function l(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function m(a){if(a){if(angular.isDate(a)&&!isNaN(a))return k.$setValidity("date",!0),a;if(angular.isString(a)){var b=f.parse(a,n)||new Date(a);return isNaN(b)?void k.$setValidity("date",!1):(k.$setValidity("date",!0),b)}return void k.$setValidity("date",!1)}return k.$setValidity("date",!0),null}var n,o=angular.isDefined(j.closeOnDateSelection)?h.$parent.$eval(j.closeOnDateSelection):g.closeOnDateSelection,p=angular.isDefined(j.datepickerAppendToBody)?h.$parent.$eval(j.datepickerAppendToBody):g.appendToBody;h.showButtonBar=angular.isDefined(j.showButtonBar)?h.$parent.$eval(j.showButtonBar):g.showButtonBar,h.getText=function(a){return h[a+"Text"]||g[a+"Text"]},j.$observe("datepickerPopup",function(a){n=a||g.datepickerPopup,k.$render()});var q=angular.element("
        ");q.attr({"ng-model":"date","ng-change":"dateSelection()"});var r=angular.element(q.children()[0]);j.datepickerOptions&&angular.forEach(h.$parent.$eval(j.datepickerOptions),function(a,b){r.attr(l(b),a)}),h.watchData={},angular.forEach(["minDate","maxDate","datepickerMode"],function(a){if(j[a]){var c=b(j[a]);if(h.$parent.$watch(c,function(b){h.watchData[a]=b}),r.attr(l(a),"watchData."+a),"datepickerMode"===a){var d=c.assign;h.$watch("watchData."+a,function(a,b){a!==b&&d(h.$parent,a)})}}}),j.dateDisabled&&r.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),k.$parsers.unshift(m),h.dateSelection=function(a){angular.isDefined(a)&&(h.date=a),k.$setViewValue(h.date),k.$render(),o&&(h.isOpen=!1,i[0].focus())},i.bind("input change keyup",function(){h.$apply(function(){h.date=k.$modelValue})}),k.$render=function(){var a=k.$viewValue?e(k.$viewValue,n):"";i.val(a),h.date=m(k.$modelValue)};var s=function(a){h.isOpen&&a.target!==i[0]&&h.$apply(function(){h.isOpen=!1})},t=function(a){h.keydown(a)};i.bind("keydown",t),h.keydown=function(a){27===a.which?(a.preventDefault(),a.stopPropagation(),h.close()):40!==a.which||h.isOpen||(h.isOpen=!0)},h.$watch("isOpen",function(a){a?(h.$broadcast("datepicker.focus"),h.position=p?d.offset(i):d.position(i),h.position.top=h.position.top+i.prop("offsetHeight"),c.bind("click",s)):c.unbind("click",s)}),h.select=function(a){if("today"===a){var b=new Date;angular.isDate(k.$modelValue)?(a=new Date(k.$modelValue),a.setFullYear(b.getFullYear(),b.getMonth(),b.getDate())):a=new Date(b.setHours(0,0,0,0))}h.dateSelection(a)},h.close=function(){h.isOpen=!1,i[0].focus()};var u=a(q)(h);q.remove(),p?c.find("body").append(u):i.after(u),h.$on("$destroy",function(){u.remove(),i.unbind("keydown",t),c.unbind("click",s)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(a,b){b.bind("click",function(a){a.preventDefault(),a.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function(a){var b=null;this.open=function(e){b||(a.bind("click",c),a.bind("keydown",d)),b&&b!==e&&(b.isOpen=!1),b=e},this.close=function(e){b===e&&(b=null,a.unbind("click",c),a.unbind("keydown",d))};var c=function(a){var c=b.getToggleElement();a&&c&&c[0].contains(a.target)||b.$apply(function(){b.isOpen=!1})},d=function(a){27===a.which&&(b.focusToggleElement(),c())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function(a,b,c,d,e,f){var g,h=this,i=a.$new(),j=d.openClass,k=angular.noop,l=b.onToggle?c(b.onToggle):angular.noop;this.init=function(d){h.$element=d,b.isOpen&&(g=c(b.isOpen),k=g.assign,a.$watch(g,function(a){i.isOpen=!!a}))},this.toggle=function(a){return i.isOpen=arguments.length?!!a:!i.isOpen},this.isOpen=function(){return i.isOpen},i.getToggleElement=function(){return h.toggleElement},i.focusToggleElement=function(){h.toggleElement&&h.toggleElement[0].focus()},i.$watch("isOpen",function(b,c){f[b?"addClass":"removeClass"](h.$element,j),b?(i.focusToggleElement(),e.open(i)):e.close(i),k(a,b),angular.isDefined(b)&&b!==c&&l(a,{open:!!b})}),a.$on("$locationChangeSuccess",function(){i.isOpen=!1}),a.$on("$destroy",function(){i.$destroy()})}]).directive("dropdown",function(){return{restrict:"CA",controller:"DropdownController",link:function(a,b,c,d){d.init(b)}}}).directive("dropdownToggle",function(){return{restrict:"CA",require:"?^dropdown",link:function(a,b,c,d){if(d){d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0),i()})}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&(a.preventDefault(),e.$apply(function(){o.dismiss(b.key,"escape key press")})))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();if(h>=0&&!k){l=e.$new(!0),l.index=h;var i=angular.element("
        ");i.attr("backdrop-class",b.backdropClass),k=d(i)(l),f.append(k)}var j=angular.element("
        ");j.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,size:b.size,index:n.length()-1,animate:"animate"}).html(b.content);var o=d(j)(b.scope);n.top().value.modalDomEl=o,f.append(o),f.addClass(m)},o.close=function(a,b){var c=n.get(a);c&&(c.value.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a);c&&(c.value.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,backdropClass:b.backdropClass,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(f,g){e=f,this.config=g,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=g.itemsPerPage},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b){a.page!==b&&b>0&&b<=a.totalPages&&(e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages},a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render() +});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@"},require:["pager","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
        ';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!y||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?v||(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return v=null,u&&(g.cancel(u),u=null),b.tt_content?(r(),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),z(),b.tt_isOpen=!0,b.$digest(),z):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),v=null,b.tt_animation?u||(u=g(s,500)):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){u=null,t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=angular.isDefined(d[l+"Enable"]),z=function(){var a=j.positionElements(c,t,b.tt_placement,w);a.top+="px",a.left+="px",t.css(a)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var A=function(){c.unbind(x.show,k),c.unbind(x.hide,m)};d.$observe(l+"Trigger",function(a){A(),x=n(a),x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m))});var B=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(B)?!!B:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),A(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,this.addBar=function(b,c){e||c.css({transition:"none"}),this.bars.push(b),b.$watch("value",function(c){b.percent=+(100*c/a.max).toFixed(2)}),b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(f)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff},a[b]);return a},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect())}),a.active=!0,a.onSelect()},b.addTab=function(a){c.push(a),1===c.length?a.active=!0:a.active&&b.select(a)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){b.$watch("active",function(a){a&&f.select(b)}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function(a,b,c,d,e,f){function g(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===p[1]&&(b+=12)),b):void 0}function h(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function i(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a}function j(a){k(),o.$setViewValue(new Date(n)),l(a)}function k(){o.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function l(b){var c=n.getHours(),d=n.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:i(c),a.minutes="m"===b?d:i(d),a.meridian=n.getHours()<12?p[0]:p[1]}function m(a){var b=new Date(n.getTime()+6e4*a);n.setHours(b.getHours(),b.getMinutes()),j()}var n=new Date,o={$setViewValue:angular.noop},p=angular.isDefined(b.meridians)?a.$parent.$eval(b.meridians):f.meridians||e.DATETIME_FORMATS.AMPMS;this.init=function(c,d){o=c,o.$render=this.render;var e=d.eq(0),g=d.eq(1),h=angular.isDefined(b.mousewheel)?a.$parent.$eval(b.mousewheel):f.mousewheel;h&&this.setupMousewheelEvents(e,g),a.readonlyInput=angular.isDefined(b.readonlyInput)?a.$parent.$eval(b.readonlyInput):f.readonlyInput,this.setupInputEvents(e,g)};var q=f.hourStep;b.hourStep&&a.$parent.$watch(c(b.hourStep),function(a){q=parseInt(a,10)});var r=f.minuteStep;b.minuteStep&&a.$parent.$watch(c(b.minuteStep),function(a){r=parseInt(a,10)}),a.showMeridian=f.showMeridian,b.showMeridian&&a.$parent.$watch(c(b.showMeridian),function(b){if(a.showMeridian=!!b,o.$error.time){var c=g(),d=h();angular.isDefined(c)&&angular.isDefined(d)&&(n.setHours(c),j())}else l()}),this.setupMousewheelEvents=function(b,c){var d=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){o.$setViewValue(null),o.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=g();angular.isDefined(a)?(n.setHours(a),j("h")):d(!0)},b.bind("blur",function(){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=i(a.hours)})}),a.updateMinutes=function(){var a=h();angular.isDefined(a)?(n.setMinutes(a),j("m")):d(void 0,!0)},c.bind("blur",function(){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=i(a.minutes)})})},this.render=function(){var a=o.$modelValue?new Date(o.$modelValue):null;isNaN(a)?(o.$setValidity("time",!1),d.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(a&&(n=a),k(),l())},a.incrementHours=function(){m(60*q)},a.decrementHours=function(){m(60*-q)},a.incrementMinutes=function(){m(r)},a.decrementMinutes=function(){m(-r)},a.toggleMeridian=function(){m(720*(n.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",replace:!0,scope:{},templateUrl:"template/timepicker/timepicker.html",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?i.$eval(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=i.$new();i.$on("$destroy",function(){w.$destroy()});var x="typeahead-"+w.$id+"-"+Math.floor(1e4*Math.random());j.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":x});var y=angular.element("
        ");y.attr({id:x,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&y.attr("template-url",k.typeaheadTemplateUrl);var z=function(){w.matches=[],w.activeIdx=-1,j.attr("aria-expanded",!1)},A=function(a){return x+"-option-"+a};w.$watch("activeIdx",function(a){0>a?j.removeAttr("aria-activedescendant"):j.attr("aria-activedescendant",A(a))});var B=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){var d=a===l.$viewValue;if(d&&m)if(c.length>0){w.activeIdx=0,w.matches.length=0;for(var e=0;e=n?o>0?(E(),D(a)):B(a):(q(i,!1),E(),z()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),w.select=function(a){var b,c,e={};e[v.itemName]=c=w.matches[a].model,b=v.modelMapper(i,e),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,e)}),z(),d(function(){j[0].focus()},0,!1)},j.bind("keydown",function(a){0!==w.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(w.activeIdx=(w.activeIdx+1)%w.matches.length,w.$digest()):38===a.which?(w.activeIdx=(w.activeIdx?w.activeIdx:w.matches.length)-1,w.$digest()):13===a.which||9===a.which?w.$apply(function(){w.select(w.activeIdx)}):27===a.which&&(a.stopPropagation(),z(),w.$digest()))}),j.bind("blur",function(){m=!1});var F=function(a){j[0]!==a.target&&(z(),w.$digest())};e.bind("click",F),i.$on("$destroy",function(){e.unbind("click",F)});var G=a(y)(w);t?e.find("body").append(G):j.after(G)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?(""+b).replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'
        \n
        \n

        \n {{heading}}\n

        \n
        \n
        \n
        \n
        \n
        ')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
        ')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html",'\n')}]),angular.module("template/carousel/carousel.html",[]).run(["$templateCache",function(a){a.put("template/carousel/carousel.html",'\n')}]),angular.module("template/carousel/slide.html",[]).run(["$templateCache",function(a){a.put("template/carousel/slide.html","
        \n")}]),angular.module("template/datepicker/datepicker.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/datepicker.html",'
        \n \n \n \n
        ')}]),angular.module("template/datepicker/day.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/day.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
        {{label.abbr}}
        {{ weekNumbers[$index] }}\n \n
        \n')}]),angular.module("template/datepicker/month.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/month.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
        \n \n
        \n')}]),angular.module("template/datepicker/popup.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/popup.html",'\n')}]),angular.module("template/datepicker/year.html",[]).run(["$templateCache",function(a){a.put("template/datepicker/year.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n
        \n \n
        \n')}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'
        \n
        \n
        \n
        \n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'
        \n
        \n
        \n
        \n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'
        \n
        \n\n
        \n

        \n
        \n
        \n
        \n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'
        ')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'
        ')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'
        \n
        \n
        ')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n ({{ $index < value ? \'*\' : \' \' }})\n \n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'
      • \n {{heading}}\n
      • \n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'
        \n \n
        \n
        \n
        \n
        \n
        \n')}]),angular.module("template/timepicker/timepicker.html",[]).run(["$templateCache",function(a){a.put("template/timepicker/timepicker.html",'\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
         
        \n \n :\n \n
         
        \n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'') +}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html",'\n')}]); \ No newline at end of file diff --git a/bower_components/angular-bootstrap/ui-bootstrap.js b/bower_components/angular-bootstrap/ui-bootstrap.js new file mode 100644 index 0000000..9d36932 --- /dev/null +++ b/bower_components/angular-bootstrap/ui-bootstrap.js @@ -0,0 +1,3857 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.2 - 2014-09-26 + * License: MIT + */ +angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); +angular.module('ui.bootstrap.transition', []) + +/** + * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. + * @param {DOMElement} element The DOMElement that will be animated. + * @param {string|object|function} trigger The thing that will cause the transition to start: + * - As a string, it represents the css class to be added to the element. + * - As an object, it represents a hash of style attributes to be applied to the element. + * - As a function, it represents a function to be called that will cause the transition to occur. + * @return {Promise} A promise that is resolved when the transition finishes. + */ +.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) { + + var $transition = function(element, trigger, options) { + options = options || {}; + var deferred = $q.defer(); + var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; + + var transitionEndHandler = function(event) { + $rootScope.$apply(function() { + element.unbind(endEventName, transitionEndHandler); + deferred.resolve(element); + }); + }; + + if (endEventName) { + element.bind(endEventName, transitionEndHandler); + } + + // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur + $timeout(function() { + if ( angular.isString(trigger) ) { + element.addClass(trigger); + } else if ( angular.isFunction(trigger) ) { + trigger(element); + } else if ( angular.isObject(trigger) ) { + element.css(trigger); + } + //If browser does not support transitions, instantly resolve + if ( !endEventName ) { + deferred.resolve(element); + } + }); + + // Add our custom cancel function to the promise that is returned + // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, + // i.e. it will therefore never raise a transitionEnd event for that transition + deferred.promise.cancel = function() { + if ( endEventName ) { + element.unbind(endEventName, transitionEndHandler); + } + deferred.reject('Transition cancelled'); + }; + + return deferred.promise; + }; + + // Work out the name of the transitionEnd event + var transElement = document.createElement('trans'); + var transitionEndEventNames = { + 'WebkitTransition': 'webkitTransitionEnd', + 'MozTransition': 'transitionend', + 'OTransition': 'oTransitionEnd', + 'transition': 'transitionend' + }; + var animationEndEventNames = { + 'WebkitTransition': 'webkitAnimationEnd', + 'MozTransition': 'animationend', + 'OTransition': 'oAnimationEnd', + 'transition': 'animationend' + }; + function findEndEventName(endEventNames) { + for (var name in endEventNames){ + if (transElement.style[name] !== undefined) { + return endEventNames[name]; + } + } + } + $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); + $transition.animationEndEventName = findEndEventName(animationEndEventNames); + return $transition; +}]); + +angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition']) + + .directive('collapse', ['$transition', function ($transition) { + + return { + link: function (scope, element, attrs) { + + var initialAnimSkip = true; + var currentTransition; + + function doTransition(change) { + var newTransition = $transition(element, change); + if (currentTransition) { + currentTransition.cancel(); + } + currentTransition = newTransition; + newTransition.then(newTransitionDone, newTransitionDone); + return newTransition; + + function newTransitionDone() { + // Make sure it's this transition, otherwise, leave it alone. + if (currentTransition === newTransition) { + currentTransition = undefined; + } + } + } + + function expand() { + if (initialAnimSkip) { + initialAnimSkip = false; + expandDone(); + } else { + element.removeClass('collapse').addClass('collapsing'); + doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone); + } + } + + function expandDone() { + element.removeClass('collapsing'); + element.addClass('collapse in'); + element.css({height: 'auto'}); + } + + function collapse() { + if (initialAnimSkip) { + initialAnimSkip = false; + collapseDone(); + element.css({height: 0}); + } else { + // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value + element.css({ height: element[0].scrollHeight + 'px' }); + //trigger reflow so a browser realizes that height was updated from auto to a specific value + var x = element[0].offsetWidth; + + element.removeClass('collapse in').addClass('collapsing'); + + doTransition({ height: 0 }).then(collapseDone); + } + } + + function collapseDone() { + element.removeClass('collapsing'); + element.addClass('collapse'); + } + + scope.$watch(attrs.collapse, function (shouldCollapse) { + if (shouldCollapse) { + collapse(); + } else { + expand(); + } + }); + } + }; + }]); + +angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) + +.constant('accordionConfig', { + closeOthers: true +}) + +.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { + + // This array keeps track of the accordion groups + this.groups = []; + + // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to + this.closeOthers = function(openGroup) { + var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; + if ( closeOthers ) { + angular.forEach(this.groups, function (group) { + if ( group !== openGroup ) { + group.isOpen = false; + } + }); + } + }; + + // This is called from the accordion-group directive to add itself to the accordion + this.addGroup = function(groupScope) { + var that = this; + this.groups.push(groupScope); + + groupScope.$on('$destroy', function (event) { + that.removeGroup(groupScope); + }); + }; + + // This is called from the accordion-group directive when to remove itself + this.removeGroup = function(group) { + var index = this.groups.indexOf(group); + if ( index !== -1 ) { + this.groups.splice(index, 1); + } + }; + +}]) + +// The accordion directive simply sets up the directive controller +// and adds an accordion CSS class to itself element. +.directive('accordion', function () { + return { + restrict:'EA', + controller:'AccordionController', + transclude: true, + replace: false, + templateUrl: 'template/accordion/accordion.html' + }; +}) + +// The accordion-group directive indicates a block of html that will expand and collapse in an accordion +.directive('accordionGroup', function() { + return { + require:'^accordion', // We need this directive to be inside an accordion + restrict:'EA', + transclude:true, // It transcludes the contents of the directive into the template + replace: true, // The element containing the directive will be replaced with the template + templateUrl:'template/accordion/accordion-group.html', + scope: { + heading: '@', // Interpolate the heading attribute onto this scope + isOpen: '=?', + isDisabled: '=?' + }, + controller: function() { + this.setHeading = function(element) { + this.heading = element; + }; + }, + link: function(scope, element, attrs, accordionCtrl) { + accordionCtrl.addGroup(scope); + + scope.$watch('isOpen', function(value) { + if ( value ) { + accordionCtrl.closeOthers(scope); + } + }); + + scope.toggleOpen = function() { + if ( !scope.isDisabled ) { + scope.isOpen = !scope.isOpen; + } + }; + } + }; +}) + +// Use accordion-heading below an accordion-group to provide a heading containing HTML +// +// Heading containing HTML - +// +.directive('accordionHeading', function() { + return { + restrict: 'EA', + transclude: true, // Grab the contents to be used as the heading + template: '', // In effect remove this element! + replace: true, + require: '^accordionGroup', + link: function(scope, element, attr, accordionGroupCtrl, transclude) { + // Pass the heading to the accordion-group controller + // so that it can be transcluded into the right place in the template + // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] + accordionGroupCtrl.setHeading(transclude(scope, function() {})); + } + }; +}) + +// Use in the accordion-group template to indicate where you want the heading to be transcluded +// You must provide the property on the accordion-group controller that will hold the transcluded element +//
        +// +// ... +//
        +.directive('accordionTransclude', function() { + return { + require: '^accordionGroup', + link: function(scope, element, attr, controller) { + scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { + if ( heading ) { + element.html(''); + element.append(heading); + } + }); + } + }; +}); + +angular.module('ui.bootstrap.alert', []) + +.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { + $scope.closeable = 'close' in $attrs; +}]) + +.directive('alert', function () { + return { + restrict:'EA', + controller:'AlertController', + templateUrl:'template/alert/alert.html', + transclude:true, + replace:true, + scope: { + type: '@', + close: '&' + } + }; +}); + +angular.module('ui.bootstrap.bindHtml', []) + + .directive('bindHtmlUnsafe', function () { + return function (scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); + scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; + }); +angular.module('ui.bootstrap.buttons', []) + +.constant('buttonConfig', { + activeClass: 'active', + toggleEvent: 'click' +}) + +.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { + this.activeClass = buttonConfig.activeClass || 'active'; + this.toggleEvent = buttonConfig.toggleEvent || 'click'; +}]) + +.directive('btnRadio', function () { + return { + require: ['btnRadio', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + var isActive = element.hasClass(buttonsCtrl.activeClass); + + if (!isActive || angular.isDefined(attrs.uncheckable)) { + scope.$apply(function () { + ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); + ngModelCtrl.$render(); + }); + } + }); + } + }; +}) + +.directive('btnCheckbox', function () { + return { + require: ['btnCheckbox', 'ngModel'], + controller: 'ButtonsController', + link: function (scope, element, attrs, ctrls) { + var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + function getTrueValue() { + return getCheckboxValue(attrs.btnCheckboxTrue, true); + } + + function getFalseValue() { + return getCheckboxValue(attrs.btnCheckboxFalse, false); + } + + function getCheckboxValue(attributeValue, defaultValue) { + var val = scope.$eval(attributeValue); + return angular.isDefined(val) ? val : defaultValue; + } + + //model -> UI + ngModelCtrl.$render = function () { + element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); + }; + + //ui->model + element.bind(buttonsCtrl.toggleEvent, function () { + scope.$apply(function () { + ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); + ngModelCtrl.$render(); + }); + }); + } + }; +}); + +/** +* @ngdoc overview +* @name ui.bootstrap.carousel +* +* @description +* AngularJS version of an image carousel. +* +*/ +angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition']) +.controller('CarouselController', ['$scope', '$timeout', '$transition', function ($scope, $timeout, $transition) { + var self = this, + slides = self.slides = $scope.slides = [], + currentIndex = -1, + currentTimeout, isPlaying; + self.currentSlide = null; + + var destroyed = false; + /* direction: "prev" or "next" */ + self.select = $scope.select = function(nextSlide, direction) { + var nextIndex = slides.indexOf(nextSlide); + //Decide direction if it's not given + if (direction === undefined) { + direction = nextIndex > currentIndex ? 'next' : 'prev'; + } + if (nextSlide && nextSlide !== self.currentSlide) { + if ($scope.$currentTransition) { + $scope.$currentTransition.cancel(); + //Timeout so ng-class in template has time to fix classes for finished slide + $timeout(goNext); + } else { + goNext(); + } + } + function goNext() { + // Scope has been destroyed, stop here. + if (destroyed) { return; } + //If we have a slide to transition from and we have a transition type and we're allowed, go + if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) { + //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime + nextSlide.$element.addClass(direction); + var reflow = nextSlide.$element[0].offsetWidth; //force reflow + + //Set all other slides to stop doing their stuff for the new transition + angular.forEach(slides, function(slide) { + angular.extend(slide, {direction: '', entering: false, leaving: false, active: false}); + }); + angular.extend(nextSlide, {direction: direction, active: true, entering: true}); + angular.extend(self.currentSlide||{}, {direction: direction, leaving: true}); + + $scope.$currentTransition = $transition(nextSlide.$element, {}); + //We have to create new pointers inside a closure since next & current will change + (function(next,current) { + $scope.$currentTransition.then( + function(){ transitionDone(next, current); }, + function(){ transitionDone(next, current); } + ); + }(nextSlide, self.currentSlide)); + } else { + transitionDone(nextSlide, self.currentSlide); + } + self.currentSlide = nextSlide; + currentIndex = nextIndex; + //every time you change slides, reset the timer + restartTimer(); + } + function transitionDone(next, current) { + angular.extend(next, {direction: '', active: true, leaving: false, entering: false}); + angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false}); + $scope.$currentTransition = null; + } + }; + $scope.$on('$destroy', function () { + destroyed = true; + }); + + /* Allow outside people to call indexOf on slides array */ + self.indexOfSlide = function(slide) { + return slides.indexOf(slide); + }; + + $scope.next = function() { + var newIndex = (currentIndex + 1) % slides.length; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'next'); + } + }; + + $scope.prev = function() { + var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1; + + //Prevent this user-triggered transition from occurring if there is already one in progress + if (!$scope.$currentTransition) { + return self.select(slides[newIndex], 'prev'); + } + }; + + $scope.isActive = function(slide) { + return self.currentSlide === slide; + }; + + $scope.$watch('interval', restartTimer); + $scope.$on('$destroy', resetTimer); + + function restartTimer() { + resetTimer(); + var interval = +$scope.interval; + if (!isNaN(interval) && interval>=0) { + currentTimeout = $timeout(timerFn, interval); + } + } + + function resetTimer() { + if (currentTimeout) { + $timeout.cancel(currentTimeout); + currentTimeout = null; + } + } + + function timerFn() { + if (isPlaying) { + $scope.next(); + restartTimer(); + } else { + $scope.pause(); + } + } + + $scope.play = function() { + if (!isPlaying) { + isPlaying = true; + restartTimer(); + } + }; + $scope.pause = function() { + if (!$scope.noPause) { + isPlaying = false; + resetTimer(); + } + }; + + self.addSlide = function(slide, element) { + slide.$element = element; + slides.push(slide); + //if this is the first slide or the slide is set to active, select it + if(slides.length === 1 || slide.active) { + self.select(slides[slides.length-1]); + if (slides.length == 1) { + $scope.play(); + } + } else { + slide.active = false; + } + }; + + self.removeSlide = function(slide) { + //get the index of the slide inside the carousel + var index = slides.indexOf(slide); + slides.splice(index, 1); + if (slides.length > 0 && slide.active) { + if (index >= slides.length) { + self.select(slides[index-1]); + } else { + self.select(slides[index]); + } + } else if (currentIndex > index) { + currentIndex--; + } + }; + +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:carousel + * @restrict EA + * + * @description + * Carousel is the outer container for a set of image 'slides' to showcase. + * + * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. + * @param {boolean=} noTransition Whether to disable transitions on the carousel. + * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). + * + * @example + + + + + + + + + + + + + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + + + */ +.directive('carousel', [function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + controller: 'CarouselController', + require: 'carousel', + templateUrl: 'template/carousel/carousel.html', + scope: { + interval: '=', + noTransition: '=', + noPause: '=' + } + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.carousel.directive:slide + * @restrict EA + * + * @description + * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. + * + * @param {boolean=} active Model binding, whether or not this slide is currently active. + * + * @example + + +
        + + + + + + + Interval, in milliseconds: +
        Enter a negative number to stop the interval. +
        +
        + +function CarouselDemoCtrl($scope) { + $scope.myInterval = 5000; +} + + + .carousel-indicators { + top: auto; + bottom: 15px; + } + +
        +*/ + +.directive('slide', function() { + return { + require: '^carousel', + restrict: 'EA', + transclude: true, + replace: true, + templateUrl: 'template/carousel/slide.html', + scope: { + active: '=?' + }, + link: function (scope, element, attrs, carouselCtrl) { + carouselCtrl.addSlide(scope, element); + //when the scope is destroyed then remove the slide from the current slides array + scope.$on('$destroy', function() { + carouselCtrl.removeSlide(scope); + }); + + scope.$watch('active', function(active) { + if (active) { + carouselCtrl.select(scope); + } + }); + } + }; +}); + +angular.module('ui.bootstrap.dateparser', []) + +.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) { + + this.parsers = {}; + + var formatCodeToRegex = { + 'yyyy': { + regex: '\\d{4}', + apply: function(value) { this.year = +value; } + }, + 'yy': { + regex: '\\d{2}', + apply: function(value) { this.year = +value + 2000; } + }, + 'y': { + regex: '\\d{1,4}', + apply: function(value) { this.year = +value; } + }, + 'MMMM': { + regex: $locale.DATETIME_FORMATS.MONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } + }, + 'MMM': { + regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), + apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } + }, + 'MM': { + regex: '0[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'M': { + regex: '[1-9]|1[0-2]', + apply: function(value) { this.month = value - 1; } + }, + 'dd': { + regex: '[0-2][0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'd': { + regex: '[1-2]?[0-9]{1}|3[0-1]{1}', + apply: function(value) { this.date = +value; } + }, + 'EEEE': { + regex: $locale.DATETIME_FORMATS.DAY.join('|') + }, + 'EEE': { + regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') + } + }; + + function createParser(format) { + var map = [], regex = format.split(''); + + angular.forEach(formatCodeToRegex, function(data, code) { + var index = format.indexOf(code); + + if (index > -1) { + format = format.split(''); + + regex[index] = '(' + data.regex + ')'; + format[index] = '$'; // Custom symbol to define consumed part of format + for (var i = index + 1, n = index + code.length; i < n; i++) { + regex[i] = ''; + format[i] = '$'; + } + format = format.join(''); + + map.push({ index: index, apply: data.apply }); + } + }); + + return { + regex: new RegExp('^' + regex.join('') + '$'), + map: orderByFilter(map, 'index') + }; + } + + this.parse = function(input, format) { + if ( !angular.isString(input) || !format ) { + return input; + } + + format = $locale.DATETIME_FORMATS[format] || format; + + if ( !this.parsers[format] ) { + this.parsers[format] = createParser(format); + } + + var parser = this.parsers[format], + regex = parser.regex, + map = parser.map, + results = input.match(regex); + + if ( results && results.length ) { + var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt; + + for( var i = 1, n = results.length; i < n; i++ ) { + var mapper = map[i-1]; + if ( mapper.apply ) { + mapper.apply.call(fields, results[i]); + } + } + + if ( isValid(fields.year, fields.month, fields.date) ) { + dt = new Date( fields.year, fields.month, fields.date, fields.hours); + } + + return dt; + } + }; + + // Check if date is valid for specific month (and year for February). + // Month: 0 = Jan, 1 = Feb, etc + function isValid(year, month, date) { + if ( month === 1 && date > 28) { + return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); + } + + if ( month === 3 || month === 5 || month === 8 || month === 10) { + return date < 31; + } + + return true; + } +}]); + +angular.module('ui.bootstrap.position', []) + +/** + * A set of utility methods that can be use to retrieve position of DOM elements. + * It is meant to be used where we need to absolute-position DOM elements in + * relation to other, existing elements (this is the case for tooltips, popovers, + * typeahead suggestions etc.). + */ + .factory('$position', ['$document', '$window', function ($document, $window) { + + function getStyle(el, cssprop) { + if (el.currentStyle) { //IE + return el.currentStyle[cssprop]; + } else if ($window.getComputedStyle) { + return $window.getComputedStyle(el)[cssprop]; + } + // finally try and get inline style + return el.style[cssprop]; + } + + /** + * Checks if a given element is statically positioned + * @param element - raw DOM element + */ + function isStaticPositioned(element) { + return (getStyle(element, 'position') || 'static' ) === 'static'; + } + + /** + * returns the closest, non-statically positioned parentOffset of a given element + * @param element + */ + var parentOffsetEl = function (element) { + var docDomEl = $document[0]; + var offsetParent = element.offsetParent || docDomEl; + while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { + offsetParent = offsetParent.offsetParent; + } + return offsetParent || docDomEl; + }; + + return { + /** + * Provides read-only equivalent of jQuery's position function: + * http://api.jquery.com/position/ + */ + position: function (element) { + var elBCR = this.offset(element); + var offsetParentBCR = { top: 0, left: 0 }; + var offsetParentEl = parentOffsetEl(element[0]); + if (offsetParentEl != $document[0]) { + offsetParentBCR = this.offset(angular.element(offsetParentEl)); + offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; + offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; + } + + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: elBCR.top - offsetParentBCR.top, + left: elBCR.left - offsetParentBCR.left + }; + }, + + /** + * Provides read-only equivalent of jQuery's offset function: + * http://api.jquery.com/offset/ + */ + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: boundingClientRect.width || element.prop('offsetWidth'), + height: boundingClientRect.height || element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) + }; + }, + + /** + * Provides coordinates for the targetEl in relation to hostEl + */ + positionElements: function (hostEl, targetEl, positionStr, appendToBody) { + + var positionStrParts = positionStr.split('-'); + var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; + + var hostElPos, + targetElWidth, + targetElHeight, + targetElPos; + + hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + + targetElWidth = targetEl.prop('offsetWidth'); + targetElHeight = targetEl.prop('offsetHeight'); + + var shiftWidth = { + center: function () { + return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; + }, + left: function () { + return hostElPos.left; + }, + right: function () { + return hostElPos.left + hostElPos.width; + } + }; + + var shiftHeight = { + center: function () { + return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; + }, + top: function () { + return hostElPos.top; + }, + bottom: function () { + return hostElPos.top + hostElPos.height; + } + }; + + switch (pos0) { + case 'right': + targetElPos = { + top: shiftHeight[pos1](), + left: shiftWidth[pos0]() + }; + break; + case 'left': + targetElPos = { + top: shiftHeight[pos1](), + left: hostElPos.left - targetElWidth + }; + break; + case 'bottom': + targetElPos = { + top: shiftHeight[pos0](), + left: shiftWidth[pos1]() + }; + break; + default: + targetElPos = { + top: hostElPos.top - targetElHeight, + left: shiftWidth[pos1]() + }; + break; + } + + return targetElPos; + } + }; + }]); + +angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) + +.constant('datepickerConfig', { + formatDay: 'dd', + formatMonth: 'MMMM', + formatYear: 'yyyy', + formatDayHeader: 'EEE', + formatDayTitle: 'MMMM yyyy', + formatMonthTitle: 'yyyy', + datepickerMode: 'day', + minMode: 'day', + maxMode: 'year', + showWeeks: true, + startingDay: 0, + yearRange: 20, + minDate: null, + maxDate: null +}) + +.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; + + // Modes chain + this.modes = ['day', 'month', 'year']; + + // Configuration attributes + angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', + 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) { + self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; + }); + + // Watchable date attributes + angular.forEach(['minDate', 'maxDate'], function( key ) { + if ( $attrs[key] ) { + $scope.$parent.$watch($parse($attrs[key]), function(value) { + self[key] = value ? new Date(value) : null; + self.refreshView(); + }); + } else { + self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; + } + }); + + $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; + $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); + this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date(); + + $scope.isActive = function(dateObject) { + if (self.compare(dateObject.date, self.activeDate) === 0) { + $scope.activeDateId = dateObject.uid; + return true; + } + return false; + }; + + this.init = function( ngModelCtrl_ ) { + ngModelCtrl = ngModelCtrl_; + + ngModelCtrl.$render = function() { + self.render(); + }; + }; + + this.render = function() { + if ( ngModelCtrl.$modelValue ) { + var date = new Date( ngModelCtrl.$modelValue ), + isValid = !isNaN(date); + + if ( isValid ) { + this.activeDate = date; + } else { + $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } + ngModelCtrl.$setValidity('date', isValid); + } + this.refreshView(); + }; + + this.refreshView = function() { + if ( this.element ) { + this._refreshView(); + + var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); + } + }; + + this.createDateObject = function(date, format) { + var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null; + return { + date: date, + label: dateFilter(date, format), + selected: model && this.compare(date, model) === 0, + disabled: this.isDisabled(date), + current: this.compare(date, new Date()) === 0 + }; + }; + + this.isDisabled = function( date ) { + return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); + }; + + // Split array into smaller arrays + this.split = function(arr, size) { + var arrays = []; + while (arr.length > 0) { + arrays.push(arr.splice(0, size)); + } + return arrays; + }; + + $scope.select = function( date ) { + if ( $scope.datepickerMode === self.minMode ) { + var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0); + dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); + ngModelCtrl.$setViewValue( dt ); + ngModelCtrl.$render(); + } else { + self.activeDate = date; + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ]; + } + }; + + $scope.move = function( direction ) { + var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), + month = self.activeDate.getMonth() + direction * (self.step.months || 0); + self.activeDate.setFullYear(year, month, 1); + self.refreshView(); + }; + + $scope.toggleMode = function( direction ) { + direction = direction || 1; + + if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { + return; + } + + $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ]; + }; + + // Key event mapper + $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' }; + + var focusElement = function() { + $timeout(function() { + self.element[0].focus(); + }, 0 , false); + }; + + // Listen for focus requests from popup directive + $scope.$on('datepicker.focus', focusElement); + + $scope.keydown = function( evt ) { + var key = $scope.keys[evt.which]; + + if ( !key || evt.shiftKey || evt.altKey ) { + return; + } + + evt.preventDefault(); + evt.stopPropagation(); + + if (key === 'enter' || key === 'space') { + if ( self.isDisabled(self.activeDate)) { + return; // do nothing + } + $scope.select(self.activeDate); + focusElement(); + } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { + $scope.toggleMode(key === 'up' ? 1 : -1); + focusElement(); + } else { + self.handleKeyDown(key, evt); + self.refreshView(); + } + }; +}]) + +.directive( 'datepicker', function () { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/datepicker.html', + scope: { + datepickerMode: '=?', + dateDisabled: '&' + }, + require: ['datepicker', '?^ngModel'], + controller: 'DatepickerController', + link: function(scope, element, attrs, ctrls) { + var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + datepickerCtrl.init( ngModelCtrl ); + } + } + }; +}) + +.directive('daypicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/day.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + scope.showWeeks = ctrl.showWeeks; + + ctrl.step = { months: 1 }; + ctrl.element = element; + + var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + function getDaysInMonth( year, month ) { + return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; + } + + function getDates(startDate, n) { + var dates = new Array(n), current = new Date(startDate), i = 0; + current.setHours(12); // Prevent repeated dates because of timezone bug + while ( i < n ) { + dates[i++] = new Date(current); + current.setDate( current.getDate() + 1 ); + } + return dates; + } + + ctrl._refreshView = function() { + var year = ctrl.activeDate.getFullYear(), + month = ctrl.activeDate.getMonth(), + firstDayOfMonth = new Date(year, month, 1), + difference = ctrl.startingDay - firstDayOfMonth.getDay(), + numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, + firstDate = new Date(firstDayOfMonth); + + if ( numDisplayedFromPreviousMonth > 0 ) { + firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); + } + + // 42 is the number of days on a six-month calendar + var days = getDates(firstDate, 42); + for (var i = 0; i < 42; i ++) { + days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), { + secondary: days[i].getMonth() !== month, + uid: scope.uniqueId + '-' + i + }); + } + + scope.labels = new Array(7); + for (var j = 0; j < 7; j++) { + scope.labels[j] = { + abbr: dateFilter(days[j].date, ctrl.formatDayHeader), + full: dateFilter(days[j].date, 'EEEE') + }; + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); + scope.rows = ctrl.split(days, 7); + + if ( scope.showWeeks ) { + scope.weekNumbers = []; + var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ), + numWeeks = scope.rows.length; + while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {} + } + }; + + ctrl.compare = function(date1, date2) { + return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); + }; + + function getISO8601WeekNumber(date) { + var checkDate = new Date(date); + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday + var time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; + } + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getDate(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 7; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 7; + } else if (key === 'pageup' || key === 'pagedown') { + var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setMonth(month, 1); + date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); + } else if (key === 'home') { + date = 1; + } else if (key === 'end') { + date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); + } + ctrl.activeDate.setDate(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('monthpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/month.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + ctrl.step = { years: 1 }; + ctrl.element = element; + + ctrl._refreshView = function() { + var months = new Array(12), + year = ctrl.activeDate.getFullYear(); + + for ( var i = 0; i < 12; i++ ) { + months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); + scope.rows = ctrl.split(months, 3); + }; + + ctrl.compare = function(date1, date2) { + return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getMonth(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 3; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 3; + } else if (key === 'pageup' || key === 'pagedown') { + var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); + ctrl.activeDate.setFullYear(year); + } else if (key === 'home') { + date = 0; + } else if (key === 'end') { + date = 11; + } + ctrl.activeDate.setMonth(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.directive('yearpicker', ['dateFilter', function (dateFilter) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/datepicker/year.html', + require: '^datepicker', + link: function(scope, element, attrs, ctrl) { + var range = ctrl.yearRange; + + ctrl.step = { years: range }; + ctrl.element = element; + + function getStartingYear( year ) { + return parseInt((year - 1) / range, 10) * range + 1; + } + + ctrl._refreshView = function() { + var years = new Array(range); + + for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) { + years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), { + uid: scope.uniqueId + '-' + i + }); + } + + scope.title = [years[0].label, years[range - 1].label].join(' - '); + scope.rows = ctrl.split(years, 5); + }; + + ctrl.compare = function(date1, date2) { + return date1.getFullYear() - date2.getFullYear(); + }; + + ctrl.handleKeyDown = function( key, evt ) { + var date = ctrl.activeDate.getFullYear(); + + if (key === 'left') { + date = date - 1; // up + } else if (key === 'up') { + date = date - 5; // down + } else if (key === 'right') { + date = date + 1; // down + } else if (key === 'down') { + date = date + 5; + } else if (key === 'pageup' || key === 'pagedown') { + date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; + } else if (key === 'home') { + date = getStartingYear( ctrl.activeDate.getFullYear() ); + } else if (key === 'end') { + date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1; + } + ctrl.activeDate.setFullYear(date); + }; + + ctrl.refreshView(); + } + }; +}]) + +.constant('datepickerPopupConfig', { + datepickerPopup: 'yyyy-MM-dd', + currentText: 'Today', + clearText: 'Clear', + closeText: 'Done', + closeOnDateSelection: true, + appendToBody: false, + showButtonBar: true +}) + +.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', +function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { + return { + restrict: 'EA', + require: 'ngModel', + scope: { + isOpen: '=?', + currentText: '@', + clearText: '@', + closeText: '@', + dateDisabled: '&' + }, + link: function(scope, element, attrs, ngModel) { + var dateFormat, + closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, + appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; + + scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; + + scope.getText = function( key ) { + return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; + }; + + attrs.$observe('datepickerPopup', function(value) { + dateFormat = value || datepickerPopupConfig.datepickerPopup; + ngModel.$render(); + }); + + // popup element used to display calendar + var popupEl = angular.element('
        '); + popupEl.attr({ + 'ng-model': 'date', + 'ng-change': 'dateSelection()' + }); + + function cameltoDash( string ){ + return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); + } + + // datepicker element + var datepickerEl = angular.element(popupEl.children()[0]); + if ( attrs.datepickerOptions ) { + angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) { + datepickerEl.attr( cameltoDash(option), value ); + }); + } + + scope.watchData = {}; + angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function( key ) { + if ( attrs[key] ) { + var getAttribute = $parse(attrs[key]); + scope.$parent.$watch(getAttribute, function(value){ + scope.watchData[key] = value; + }); + datepickerEl.attr(cameltoDash(key), 'watchData.' + key); + + // Propagate changes from datepicker to outside + if ( key === 'datepickerMode' ) { + var setAttribute = getAttribute.assign; + scope.$watch('watchData.' + key, function(value, oldvalue) { + if ( value !== oldvalue ) { + setAttribute(scope.$parent, value); + } + }); + } + } + }); + if (attrs.dateDisabled) { + datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); + } + + function parseDate(viewValue) { + if (!viewValue) { + ngModel.$setValidity('date', true); + return null; + } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { + ngModel.$setValidity('date', true); + return viewValue; + } else if (angular.isString(viewValue)) { + var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue); + if (isNaN(date)) { + ngModel.$setValidity('date', false); + return undefined; + } else { + ngModel.$setValidity('date', true); + return date; + } + } else { + ngModel.$setValidity('date', false); + return undefined; + } + } + ngModel.$parsers.unshift(parseDate); + + // Inner change + scope.dateSelection = function(dt) { + if (angular.isDefined(dt)) { + scope.date = dt; + } + ngModel.$setViewValue(scope.date); + ngModel.$render(); + + if ( closeOnDateSelection ) { + scope.isOpen = false; + element[0].focus(); + } + }; + + element.bind('input change keyup', function() { + scope.$apply(function() { + scope.date = ngModel.$modelValue; + }); + }); + + // Outter change + ngModel.$render = function() { + var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : ''; + element.val(date); + scope.date = parseDate( ngModel.$modelValue ); + }; + + var documentClickBind = function(event) { + if (scope.isOpen && event.target !== element[0]) { + scope.$apply(function() { + scope.isOpen = false; + }); + } + }; + + var keydown = function(evt, noApply) { + scope.keydown(evt); + }; + element.bind('keydown', keydown); + + scope.keydown = function(evt) { + if (evt.which === 27) { + evt.preventDefault(); + evt.stopPropagation(); + scope.close(); + } else if (evt.which === 40 && !scope.isOpen) { + scope.isOpen = true; + } + }; + + scope.$watch('isOpen', function(value) { + if (value) { + scope.$broadcast('datepicker.focus'); + scope.position = appendToBody ? $position.offset(element) : $position.position(element); + scope.position.top = scope.position.top + element.prop('offsetHeight'); + + $document.bind('click', documentClickBind); + } else { + $document.unbind('click', documentClickBind); + } + }); + + scope.select = function( date ) { + if (date === 'today') { + var today = new Date(); + if (angular.isDate(ngModel.$modelValue)) { + date = new Date(ngModel.$modelValue); + date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); + } else { + date = new Date(today.setHours(0, 0, 0, 0)); + } + } + scope.dateSelection( date ); + }; + + scope.close = function() { + scope.isOpen = false; + element[0].focus(); + }; + + var $popup = $compile(popupEl)(scope); + // Prevent jQuery cache memory leak (template is now redundant after linking) + popupEl.remove(); + + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + + scope.$on('$destroy', function() { + $popup.remove(); + element.unbind('keydown', keydown); + $document.unbind('click', documentClickBind); + }); + } + }; +}]) + +.directive('datepickerPopupWrap', function() { + return { + restrict:'EA', + replace: true, + transclude: true, + templateUrl: 'template/datepicker/popup.html', + link:function (scope, element, attrs) { + element.bind('click', function(event) { + event.preventDefault(); + event.stopPropagation(); + }); + } + }; +}); + +angular.module('ui.bootstrap.dropdown', []) + +.constant('dropdownConfig', { + openClass: 'open' +}) + +.service('dropdownService', ['$document', function($document) { + var openScope = null; + + this.open = function( dropdownScope ) { + if ( !openScope ) { + $document.bind('click', closeDropdown); + $document.bind('keydown', escapeKeyBind); + } + + if ( openScope && openScope !== dropdownScope ) { + openScope.isOpen = false; + } + + openScope = dropdownScope; + }; + + this.close = function( dropdownScope ) { + if ( openScope === dropdownScope ) { + openScope = null; + $document.unbind('click', closeDropdown); + $document.unbind('keydown', escapeKeyBind); + } + }; + + var closeDropdown = function( evt ) { + var toggleElement = openScope.getToggleElement(); + if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { + return; + } + + openScope.$apply(function() { + openScope.isOpen = false; + }); + }; + + var escapeKeyBind = function( evt ) { + if ( evt.which === 27 ) { + openScope.focusToggleElement(); + closeDropdown(); + } + }; +}]) + +.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) { + var self = this, + scope = $scope.$new(), // create a child scope so we are not polluting original one + openClass = dropdownConfig.openClass, + getIsOpen, + setIsOpen = angular.noop, + toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop; + + this.init = function( element ) { + self.$element = element; + + if ( $attrs.isOpen ) { + getIsOpen = $parse($attrs.isOpen); + setIsOpen = getIsOpen.assign; + + $scope.$watch(getIsOpen, function(value) { + scope.isOpen = !!value; + }); + } + }; + + this.toggle = function( open ) { + return scope.isOpen = arguments.length ? !!open : !scope.isOpen; + }; + + // Allow other directives to watch status + this.isOpen = function() { + return scope.isOpen; + }; + + scope.getToggleElement = function() { + return self.toggleElement; + }; + + scope.focusToggleElement = function() { + if ( self.toggleElement ) { + self.toggleElement[0].focus(); + } + }; + + scope.$watch('isOpen', function( isOpen, wasOpen ) { + $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); + + if ( isOpen ) { + scope.focusToggleElement(); + dropdownService.open( scope ); + } else { + dropdownService.close( scope ); + } + + setIsOpen($scope, isOpen); + if (angular.isDefined(isOpen) && isOpen !== wasOpen) { + toggleInvoker($scope, { open: !!isOpen }); + } + }); + + $scope.$on('$locationChangeSuccess', function() { + scope.isOpen = false; + }); + + $scope.$on('$destroy', function() { + scope.$destroy(); + }); +}]) + +.directive('dropdown', function() { + return { + restrict: 'CA', + controller: 'DropdownController', + link: function(scope, element, attrs, dropdownCtrl) { + dropdownCtrl.init( element ); + } + }; +}) + +.directive('dropdownToggle', function() { + return { + restrict: 'CA', + require: '?^dropdown', + link: function(scope, element, attrs, dropdownCtrl) { + if ( !dropdownCtrl ) { + return; + } + + dropdownCtrl.toggleElement = element; + + var toggleDropdown = function(event) { + event.preventDefault(); + + if ( !element.hasClass('disabled') && !attrs.disabled ) { + scope.$apply(function() { + dropdownCtrl.toggle(); + }); + } + }; + + element.bind('click', toggleDropdown); + + // WAI-ARIA + element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); + scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { + element.attr('aria-expanded', !!isOpen); + }); + + scope.$on('$destroy', function() { + element.unbind('click', toggleDropdown); + }); + } + }; +}); + +angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition']) + +/** + * A helper, internal data structure that acts as a map but also allows getting / removing + * elements in the LIFO order + */ + .factory('$$stackedMap', function () { + return { + createNew: function () { + var stack = []; + + return { + add: function (key, value) { + stack.push({ + key: key, + value: value + }); + }, + get: function (key) { + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + return stack[i]; + } + } + }, + keys: function() { + var keys = []; + for (var i = 0; i < stack.length; i++) { + keys.push(stack[i].key); + } + return keys; + }, + top: function () { + return stack[stack.length - 1]; + }, + remove: function (key) { + var idx = -1; + for (var i = 0; i < stack.length; i++) { + if (key == stack[i].key) { + idx = i; + break; + } + } + return stack.splice(idx, 1)[0]; + }, + removeTop: function () { + return stack.splice(stack.length - 1, 1)[0]; + }, + length: function () { + return stack.length; + } + }; + } + }; + }) + +/** + * A helper directive for the $modal service. It creates a backdrop element. + */ + .directive('modalBackdrop', ['$timeout', function ($timeout) { + return { + restrict: 'EA', + replace: true, + templateUrl: 'template/modal/backdrop.html', + link: function (scope, element, attrs) { + scope.backdropClass = attrs.backdropClass || ''; + + scope.animate = false; + + //trigger CSS transitions + $timeout(function () { + scope.animate = true; + }); + } + }; + }]) + + .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) { + return { + restrict: 'EA', + scope: { + index: '@', + animate: '=' + }, + replace: true, + transclude: true, + templateUrl: function(tElement, tAttrs) { + return tAttrs.templateUrl || 'template/modal/window.html'; + }, + link: function (scope, element, attrs) { + element.addClass(attrs.windowClass || ''); + scope.size = attrs.size; + + $timeout(function () { + // trigger CSS transitions + scope.animate = true; + + /** + * Auto-focusing of a freshly-opened modal element causes any child elements + * with the autofocus attribute to loose focus. This is an issue on touch + * based devices which will show and then hide the onscreen keyboard. + * Attempts to refocus the autofocus element via JavaScript will not reopen + * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus + * the modal element if the modal does not contain an autofocus element. + */ + if (!element[0].querySelectorAll('[autofocus]').length) { + element[0].focus(); + } + }); + + scope.close = function (evt) { + var modal = $modalStack.getTop(); + if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { + evt.preventDefault(); + evt.stopPropagation(); + $modalStack.dismiss(modal.key, 'backdrop click'); + } + }; + } + }; + }]) + + .directive('modalTransclude', function () { + return { + link: function($scope, $element, $attrs, controller, $transclude) { + $transclude($scope.$parent, function(clone) { + $element.empty(); + $element.append(clone); + }); + } + }; + }) + + .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', + function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) { + + var OPENED_MODAL_CLASS = 'modal-open'; + + var backdropDomEl, backdropScope; + var openedWindows = $$stackedMap.createNew(); + var $modalStack = {}; + + function backdropIndex() { + var topBackdropIndex = -1; + var opened = openedWindows.keys(); + for (var i = 0; i < opened.length; i++) { + if (openedWindows.get(opened[i]).value.backdrop) { + topBackdropIndex = i; + } + } + return topBackdropIndex; + } + + $rootScope.$watch(backdropIndex, function(newBackdropIndex){ + if (backdropScope) { + backdropScope.index = newBackdropIndex; + } + }); + + function removeModalWindow(modalInstance) { + + var body = $document.find('body').eq(0); + var modalWindow = openedWindows.get(modalInstance).value; + + //clean up the stack + openedWindows.remove(modalInstance); + + //remove window DOM element + removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() { + modalWindow.modalScope.$destroy(); + body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); + checkRemoveBackdrop(); + }); + } + + function checkRemoveBackdrop() { + //remove backdrop if no longer needed + if (backdropDomEl && backdropIndex() == -1) { + var backdropScopeRef = backdropScope; + removeAfterAnimate(backdropDomEl, backdropScope, 150, function () { + backdropScopeRef.$destroy(); + backdropScopeRef = null; + }); + backdropDomEl = undefined; + backdropScope = undefined; + } + } + + function removeAfterAnimate(domEl, scope, emulateTime, done) { + // Closing animation + scope.animate = false; + + var transitionEndEventName = $transition.transitionEndEventName; + if (transitionEndEventName) { + // transition out + var timeout = $timeout(afterAnimating, emulateTime); + + domEl.bind(transitionEndEventName, function () { + $timeout.cancel(timeout); + afterAnimating(); + scope.$apply(); + }); + } else { + // Ensure this call is async + $timeout(afterAnimating); + } + + function afterAnimating() { + if (afterAnimating.done) { + return; + } + afterAnimating.done = true; + + domEl.remove(); + if (done) { + done(); + } + } + } + + $document.bind('keydown', function (evt) { + var modal; + + if (evt.which === 27) { + modal = openedWindows.top(); + if (modal && modal.value.keyboard) { + evt.preventDefault(); + $rootScope.$apply(function () { + $modalStack.dismiss(modal.key, 'escape key press'); + }); + } + } + }); + + $modalStack.open = function (modalInstance, modal) { + + openedWindows.add(modalInstance, { + deferred: modal.deferred, + modalScope: modal.scope, + backdrop: modal.backdrop, + keyboard: modal.keyboard + }); + + var body = $document.find('body').eq(0), + currBackdropIndex = backdropIndex(); + + if (currBackdropIndex >= 0 && !backdropDomEl) { + backdropScope = $rootScope.$new(true); + backdropScope.index = currBackdropIndex; + var angularBackgroundDomEl = angular.element('
        '); + angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); + backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope); + body.append(backdropDomEl); + } + + var angularDomEl = angular.element('
        '); + angularDomEl.attr({ + 'template-url': modal.windowTemplateUrl, + 'window-class': modal.windowClass, + 'size': modal.size, + 'index': openedWindows.length() - 1, + 'animate': 'animate' + }).html(modal.content); + + var modalDomEl = $compile(angularDomEl)(modal.scope); + openedWindows.top().value.modalDomEl = modalDomEl; + body.append(modalDomEl); + body.addClass(OPENED_MODAL_CLASS); + }; + + $modalStack.close = function (modalInstance, result) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.deferred.resolve(result); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismiss = function (modalInstance, reason) { + var modalWindow = openedWindows.get(modalInstance); + if (modalWindow) { + modalWindow.value.deferred.reject(reason); + removeModalWindow(modalInstance); + } + }; + + $modalStack.dismissAll = function (reason) { + var topModal = this.getTop(); + while (topModal) { + this.dismiss(topModal.key, reason); + topModal = this.getTop(); + } + }; + + $modalStack.getTop = function () { + return openedWindows.top(); + }; + + return $modalStack; + }]) + + .provider('$modal', function () { + + var $modalProvider = { + options: { + backdrop: true, //can be also false or 'static' + keyboard: true + }, + $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack', + function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) { + + var $modal = {}; + + function getTemplatePromise(options) { + return options.template ? $q.when(options.template) : + $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl, + {cache: $templateCache}).then(function (result) { + return result.data; + }); + } + + function getResolvePromises(resolves) { + var promisesArr = []; + angular.forEach(resolves, function (value) { + if (angular.isFunction(value) || angular.isArray(value)) { + promisesArr.push($q.when($injector.invoke(value))); + } + }); + return promisesArr; + } + + $modal.open = function (modalOptions) { + + var modalResultDeferred = $q.defer(); + var modalOpenedDeferred = $q.defer(); + + //prepare an instance of a modal to be injected into controllers and returned to a caller + var modalInstance = { + result: modalResultDeferred.promise, + opened: modalOpenedDeferred.promise, + close: function (result) { + $modalStack.close(modalInstance, result); + }, + dismiss: function (reason) { + $modalStack.dismiss(modalInstance, reason); + } + }; + + //merge and clean up options + modalOptions = angular.extend({}, $modalProvider.options, modalOptions); + modalOptions.resolve = modalOptions.resolve || {}; + + //verify options + if (!modalOptions.template && !modalOptions.templateUrl) { + throw new Error('One of template or templateUrl options is required.'); + } + + var templateAndResolvePromise = + $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); + + + templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { + + var modalScope = (modalOptions.scope || $rootScope).$new(); + modalScope.$close = modalInstance.close; + modalScope.$dismiss = modalInstance.dismiss; + + var ctrlInstance, ctrlLocals = {}; + var resolveIter = 1; + + //controllers + if (modalOptions.controller) { + ctrlLocals.$scope = modalScope; + ctrlLocals.$modalInstance = modalInstance; + angular.forEach(modalOptions.resolve, function (value, key) { + ctrlLocals[key] = tplAndVars[resolveIter++]; + }); + + ctrlInstance = $controller(modalOptions.controller, ctrlLocals); + if (modalOptions.controllerAs) { + modalScope[modalOptions.controllerAs] = ctrlInstance; + } + } + + $modalStack.open(modalInstance, { + scope: modalScope, + deferred: modalResultDeferred, + content: tplAndVars[0], + backdrop: modalOptions.backdrop, + keyboard: modalOptions.keyboard, + backdropClass: modalOptions.backdropClass, + windowClass: modalOptions.windowClass, + windowTemplateUrl: modalOptions.windowTemplateUrl, + size: modalOptions.size + }); + + }, function resolveError(reason) { + modalResultDeferred.reject(reason); + }); + + templateAndResolvePromise.then(function () { + modalOpenedDeferred.resolve(true); + }, function () { + modalOpenedDeferred.reject(false); + }); + + return modalInstance; + }; + + return $modal; + }] + }; + + return $modalProvider; + }); + +angular.module('ui.bootstrap.pagination', []) + +.controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) { + var self = this, + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; + + this.init = function(ngModelCtrl_, config) { + ngModelCtrl = ngModelCtrl_; + this.config = config; + + ngModelCtrl.$render = function() { + self.render(); + }; + + if ($attrs.itemsPerPage) { + $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { + self.itemsPerPage = parseInt(value, 10); + $scope.totalPages = self.calculateTotalPages(); + }); + } else { + this.itemsPerPage = config.itemsPerPage; + } + }; + + this.calculateTotalPages = function() { + var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); + return Math.max(totalPages || 0, 1); + }; + + this.render = function() { + $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; + }; + + $scope.selectPage = function(page) { + if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) { + ngModelCtrl.$setViewValue(page); + ngModelCtrl.$render(); + } + }; + + $scope.getText = function( key ) { + return $scope[key + 'Text'] || self.config[key + 'Text']; + }; + $scope.noPrevious = function() { + return $scope.page === 1; + }; + $scope.noNext = function() { + return $scope.page === $scope.totalPages; + }; + + $scope.$watch('totalItems', function() { + $scope.totalPages = self.calculateTotalPages(); + }); + + $scope.$watch('totalPages', function(value) { + setNumPages($scope.$parent, value); // Readonly variable + + if ( $scope.page > value ) { + $scope.selectPage(value); + } else { + ngModelCtrl.$render(); + } + }); +}]) + +.constant('paginationConfig', { + itemsPerPage: 10, + boundaryLinks: false, + directionLinks: true, + firstText: 'First', + previousText: 'Previous', + nextText: 'Next', + lastText: 'Last', + rotate: true +}) + +.directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + firstText: '@', + previousText: '@', + nextText: '@', + lastText: '@' + }, + require: ['pagination', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pagination.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + // Setup configuration parameters + var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize, + rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate; + scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks; + scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks; + + paginationCtrl.init(ngModelCtrl, paginationConfig); + + if (attrs.maxSize) { + scope.$parent.$watch($parse(attrs.maxSize), function(value) { + maxSize = parseInt(value, 10); + paginationCtrl.render(); + }); + } + + // Create page object used in template + function makePage(number, text, isActive) { + return { + number: number, + text: text, + active: isActive + }; + } + + function getPages(currentPage, totalPages) { + var pages = []; + + // Default page limits + var startPage = 1, endPage = totalPages; + var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); + + // recompute if maxSize + if ( isMaxSized ) { + if ( rotate ) { + // Current page is displayed in the middle of the visible ones + startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); + endPage = startPage + maxSize - 1; + + // Adjust if limit is exceeded + if (endPage > totalPages) { + endPage = totalPages; + startPage = endPage - maxSize + 1; + } + } else { + // Visible pages are paginated with maxSize + startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; + + // Adjust last page if limit is exceeded + endPage = Math.min(startPage + maxSize - 1, totalPages); + } + } + + // Add page number links + for (var number = startPage; number <= endPage; number++) { + var page = makePage(number, number, number === currentPage); + pages.push(page); + } + + // Add links to move between page sets + if ( isMaxSized && ! rotate ) { + if ( startPage > 1 ) { + var previousPageSet = makePage(startPage - 1, '...', false); + pages.unshift(previousPageSet); + } + + if ( endPage < totalPages ) { + var nextPageSet = makePage(endPage + 1, '...', false); + pages.push(nextPageSet); + } + } + + return pages; + } + + var originalRender = paginationCtrl.render; + paginationCtrl.render = function() { + originalRender(); + if (scope.page > 0 && scope.page <= scope.totalPages) { + scope.pages = getPages(scope.page, scope.totalPages); + } + }; + } + }; +}]) + +.constant('pagerConfig', { + itemsPerPage: 10, + previousText: '« Previous', + nextText: 'Next »', + align: true +}) + +.directive('pager', ['pagerConfig', function(pagerConfig) { + return { + restrict: 'EA', + scope: { + totalItems: '=', + previousText: '@', + nextText: '@' + }, + require: ['pager', '?ngModel'], + controller: 'PaginationController', + templateUrl: 'template/pagination/pager.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if (!ngModelCtrl) { + return; // do nothing if no ng-model + } + + scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align; + paginationCtrl.init(ngModelCtrl, pagerConfig); + } + }; +}]); + +/** + * The following features are still outstanding: animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html tooltips, and selector delegation. + */ +angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true, + popupDelay: 0 + }; + + // Default hide triggers for each show trigger + var triggerMap = { + 'mouseenter': 'mouseleave', + 'click': 'click', + 'focus': 'blur' + }; + + // The options specified to the provider globally. + var globalOptions = {}; + + /** + * `options({})` allows global configuration of all tooltips in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * This allows you to extend the set of trigger mappings available. E.g.: + * + * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); + */ + this.setTriggers = function setTriggers ( triggers ) { + angular.extend( triggerMap, triggers ); + }; + + /** + * This is a helper function for translating camel-case to snake-case. + */ + function snake_case(name){ + var regexp = /[A-Z]/g; + var separator = '-'; + return name.replace(regexp, function(letter, pos) { + return (pos ? separator : '') + letter.toLowerCase(); + }); + } + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) { + return function $tooltip ( type, prefix, defaultTriggerShow ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + /** + * Returns an object of show and hide triggers. + * + * If a trigger is supplied, + * it is used to show the tooltip; otherwise, it will use the `trigger` + * option passed to the `$tooltipProvider.options` method; else it will + * default to the trigger supplied to this directive factory. + * + * The hide trigger is based on the show trigger. If the `trigger` option + * was passed to the `$tooltipProvider.options` method, it will use the + * mapped trigger from `triggerMap` or the passed trigger if the map is + * undefined; otherwise, it uses the `triggerMap` value of the show + * trigger; else it will just use the show trigger. + */ + function getTriggers ( trigger ) { + var show = trigger || options.trigger || defaultTriggerShow; + var hide = triggerMap[show] || show; + return { + show: show, + hide: hide + }; + } + + var directiveName = snake_case( type ); + + var startSym = $interpolate.startSymbol(); + var endSym = $interpolate.endSymbol(); + var template = + '
        '+ + '
        '; + + return { + restrict: 'EA', + scope: true, + compile: function (tElem, tAttrs) { + var tooltipLinker = $compile( template ); + + return function link ( scope, element, attrs ) { + var tooltip; + var transitionTimeout; + var popupTimeout; + var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; + var triggers = getTriggers( undefined ); + var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); + + var positionTooltip = function () { + + var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody); + ttPosition.top += 'px'; + ttPosition.left += 'px'; + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + }; + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; + + function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + showTooltipBind(); + } else { + hideTooltipBind(); + } + } + + // Show the tooltip with delay if specified, otherwise show it immediately + function showTooltipBind() { + if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { + return; + } + if ( scope.tt_popupDelay ) { + // Do nothing if the tooltip was already scheduled to pop-up. + // This happens if show is triggered multiple times before any hide is triggered. + if (!popupTimeout) { + popupTimeout = $timeout( show, scope.tt_popupDelay, false ); + popupTimeout.then(function(reposition){reposition();}); + } + } else { + show()(); + } + } + + function hideTooltipBind () { + scope.$apply(function () { + hide(); + }); + } + + // Show the tooltip popup element. + function show() { + + popupTimeout = null; + + // If there is a pending remove transition, we must cancel it, lest the + // tooltip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + transitionTimeout = null; + } + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return angular.noop; + } + + createTooltip(); + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + if ( appendToBody ) { + $document.find( 'body' ).append( tooltip ); + } else { + element.after( tooltip ); + } + + positionTooltip(); + + // And show the tooltip. + scope.tt_isOpen = true; + scope.$digest(); // digest required as $apply is not called + + // Return positioning function as promise callback for correct + // positioning after draw. + return positionTooltip; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + scope.tt_isOpen = false; + + //if tooltip is going to be shown after delay, we must cancel this + $timeout.cancel( popupTimeout ); + popupTimeout = null; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( scope.tt_animation ) { + if (!transitionTimeout) { + transitionTimeout = $timeout(removeTooltip, 500); + } + } else { + removeTooltip(); + } + } + + function createTooltip() { + // There can only be one tooltip element per directive shown at once. + if (tooltip) { + removeTooltip(); + } + tooltip = tooltipLinker(scope, function () {}); + + // Get contents rendered into the tooltip + scope.$digest(); + } + + function removeTooltip() { + transitionTimeout = null; + if (tooltip) { + tooltip.remove(); + tooltip = null; + } + } + + /** + * Observe the relevant attributes. + */ + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + + if (!val && scope.tt_isOpen ) { + hide(); + } + }); + + attrs.$observe( prefix+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( prefix+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( prefix+'PopupDelay', function ( val ) { + var delay = parseInt( val, 10 ); + scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; + }); + + var unregisterTriggers = function () { + element.unbind(triggers.show, showTooltipBind); + element.unbind(triggers.hide, hideTooltipBind); + }; + + attrs.$observe( prefix+'Trigger', function ( val ) { + unregisterTriggers(); + + triggers = getTriggers( val ); + + if ( triggers.show === triggers.hide ) { + element.bind( triggers.show, toggleTooltipBind ); + } else { + element.bind( triggers.show, showTooltipBind ); + element.bind( triggers.hide, hideTooltipBind ); + } + }); + + var animation = scope.$eval(attrs[prefix + 'Animation']); + scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation; + + attrs.$observe( prefix+'AppendToBody', function ( val ) { + appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody; + }); + + // if a tooltip is attached to we need to remove it on + // location change as its parent scope will probably not be destroyed + // by the change. + if ( appendToBody ) { + scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { + if ( scope.tt_isOpen ) { + hide(); + } + }); + } + + // Make sure tooltip is destroyed and removed. + scope.$on('$destroy', function onDestroyTooltip() { + $timeout.cancel( transitionTimeout ); + $timeout.cancel( popupTimeout ); + unregisterTriggers(); + removeTooltip(); + }); + }; + } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' + }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); +}]) + +.directive( 'tooltipHtmlUnsafePopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' + }; +}) + +.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); +}]); + +/** + * The following features are still outstanding: popup delay, animation as a + * function, placement as a function, inside, support for more triggers than + * just mouse enter/leave, html popovers, and selector delegatation. + */ +angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) + +.directive( 'popoverPopup', function () { + return { + restrict: 'EA', + replace: true, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/popover/popover.html' + }; +}) + +.directive( 'popover', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'popover', 'popover', 'click' ); +}]); + +angular.module('ui.bootstrap.progressbar', []) + +.constant('progressConfig', { + animate: true, + max: 100 +}) + +.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { + var self = this, + animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; + + this.bars = []; + $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max; + + this.addBar = function(bar, element) { + if ( !animate ) { + element.css({'transition': 'none'}); + } + + this.bars.push(bar); + + bar.$watch('value', function( value ) { + bar.percent = +(100 * value / $scope.max).toFixed(2); + }); + + bar.$on('$destroy', function() { + element = null; + self.removeBar(bar); + }); + }; + + this.removeBar = function(bar) { + this.bars.splice(this.bars.indexOf(bar), 1); + }; +}]) + +.directive('progress', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + require: 'progress', + scope: {}, + templateUrl: 'template/progressbar/progress.html' + }; +}) + +.directive('bar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + require: '^progress', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/bar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, element); + } + }; +}) + +.directive('progressbar', function() { + return { + restrict: 'EA', + replace: true, + transclude: true, + controller: 'ProgressController', + scope: { + value: '=', + type: '@' + }, + templateUrl: 'template/progressbar/progressbar.html', + link: function(scope, element, attrs, progressCtrl) { + progressCtrl.addBar(scope, angular.element(element.children()[0])); + } + }; +}); +angular.module('ui.bootstrap.rating', []) + +.constant('ratingConfig', { + max: 5, + stateOn: null, + stateOff: null +}) + +.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) { + var ngModelCtrl = { $setViewValue: angular.noop }; + + this.init = function(ngModelCtrl_) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; + this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; + + var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : + new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max ); + $scope.range = this.buildTemplateObjects(ratingStates); + }; + + this.buildTemplateObjects = function(states) { + for (var i = 0, n = states.length; i < n; i++) { + states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]); + } + return states; + }; + + $scope.rate = function(value) { + if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) { + ngModelCtrl.$setViewValue(value); + ngModelCtrl.$render(); + } + }; + + $scope.enter = function(value) { + if ( !$scope.readonly ) { + $scope.value = value; + } + $scope.onHover({value: value}); + }; + + $scope.reset = function() { + $scope.value = ngModelCtrl.$viewValue; + $scope.onLeave(); + }; + + $scope.onKeydown = function(evt) { + if (/(37|38|39|40)/.test(evt.which)) { + evt.preventDefault(); + evt.stopPropagation(); + $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) ); + } + }; + + this.render = function() { + $scope.value = ngModelCtrl.$viewValue; + }; +}]) + +.directive('rating', function() { + return { + restrict: 'EA', + require: ['rating', 'ngModel'], + scope: { + readonly: '=?', + onHover: '&', + onLeave: '&' + }, + controller: 'RatingController', + templateUrl: 'template/rating/rating.html', + replace: true, + link: function(scope, element, attrs, ctrls) { + var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + ratingCtrl.init( ngModelCtrl ); + } + } + }; +}); + +/** + * @ngdoc overview + * @name ui.bootstrap.tabs + * + * @description + * AngularJS version of the tabs directive. + */ + +angular.module('ui.bootstrap.tabs', []) + +.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { + var ctrl = this, + tabs = ctrl.tabs = $scope.tabs = []; + + ctrl.select = function(selectedTab) { + angular.forEach(tabs, function(tab) { + if (tab.active && tab !== selectedTab) { + tab.active = false; + tab.onDeselect(); + } + }); + selectedTab.active = true; + selectedTab.onSelect(); + }; + + ctrl.addTab = function addTab(tab) { + tabs.push(tab); + // we can't run the select function on the first tab + // since that would select it twice + if (tabs.length === 1) { + tab.active = true; + } else if (tab.active) { + ctrl.select(tab); + } + }; + + ctrl.removeTab = function removeTab(tab) { + var index = tabs.indexOf(tab); + //Select a new tab if the tab to be removed is selected + if (tab.active && tabs.length > 1) { + //If this is the last tab, select the previous tab. else, the next tab. + var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; + ctrl.select(tabs[newActiveIndex]); + } + tabs.splice(index, 1); + }; +}]) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabset + * @restrict EA + * + * @description + * Tabset is the outer container for the tabs directive + * + * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. + * @param {boolean=} justified Whether or not to use justified styling for the tabs. + * + * @example + + + + First Content! + Second Content! + +
        + + First Vertical Content! + Second Vertical Content! + + + First Justified Content! + Second Justified Content! + +
        +
        + */ +.directive('tabset', function() { + return { + restrict: 'EA', + transclude: true, + replace: true, + scope: { + type: '@' + }, + controller: 'TabsetController', + templateUrl: 'template/tabs/tabset.html', + link: function(scope, element, attrs) { + scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false; + scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; + } + }; +}) + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tab + * @restrict EA + * + * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. + * @param {string=} select An expression to evaluate when the tab is selected. + * @param {boolean=} active A binding, telling whether or not this tab is selected. + * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. + * + * @description + * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. + * + * @example + + +
        + + +
        + + First Tab + + Alert me! + Second Tab, with alert callback and html heading! + + + {{item.content}} + + +
        +
        + + function TabsDemoCtrl($scope) { + $scope.items = [ + { title:"Dynamic Title 1", content:"Dynamic Item 0" }, + { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } + ]; + + $scope.alertMe = function() { + setTimeout(function() { + alert("You've selected the alert tab!"); + }); + }; + }; + +
        + */ + +/** + * @ngdoc directive + * @name ui.bootstrap.tabs.directive:tabHeading + * @restrict EA + * + * @description + * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. + * + * @example + + + + + HTML in my titles?! + And some content, too! + + + Icon heading?!? + That's right. + + + + + */ +.directive('tab', ['$parse', function($parse) { + return { + require: '^tabset', + restrict: 'EA', + replace: true, + templateUrl: 'template/tabs/tab.html', + transclude: true, + scope: { + active: '=?', + heading: '@', + onSelect: '&select', //This callback is called in contentHeadingTransclude + //once it inserts the tab's content into the dom + onDeselect: '&deselect' + }, + controller: function() { + //Empty controller so other directives can require being 'under' a tab + }, + compile: function(elm, attrs, transclude) { + return function postLink(scope, elm, attrs, tabsetCtrl) { + scope.$watch('active', function(active) { + if (active) { + tabsetCtrl.select(scope); + } + }); + + scope.disabled = false; + if ( attrs.disabled ) { + scope.$parent.$watch($parse(attrs.disabled), function(value) { + scope.disabled = !! value; + }); + } + + scope.select = function() { + if ( !scope.disabled ) { + scope.active = true; + } + }; + + tabsetCtrl.addTab(scope); + scope.$on('$destroy', function() { + tabsetCtrl.removeTab(scope); + }); + + //We need to transclude later, once the content container is ready. + //when this link happens, we're inside a tab heading. + scope.$transcludeFn = transclude; + }; + } + }; +}]) + +.directive('tabHeadingTransclude', [function() { + return { + restrict: 'A', + require: '^tab', + link: function(scope, elm, attrs, tabCtrl) { + scope.$watch('headingElement', function updateHeadingElement(heading) { + if (heading) { + elm.html(''); + elm.append(heading); + } + }); + } + }; +}]) + +.directive('tabContentTransclude', function() { + return { + restrict: 'A', + require: '^tabset', + link: function(scope, elm, attrs) { + var tab = scope.$eval(attrs.tabContentTransclude); + + //Now our tab is ready to be transcluded: both the tab heading area + //and the tab content area are loaded. Transclude 'em both. + tab.$transcludeFn(tab.$parent, function(contents) { + angular.forEach(contents, function(node) { + if (isTabHeading(node)) { + //Let tabHeadingTransclude know. + tab.headingElement = node; + } else { + elm.append(node); + } + }); + }); + } + }; + function isTabHeading(node) { + return node.tagName && ( + node.hasAttribute('tab-heading') || + node.hasAttribute('data-tab-heading') || + node.tagName.toLowerCase() === 'tab-heading' || + node.tagName.toLowerCase() === 'data-tab-heading' + ); + } +}) + +; + +angular.module('ui.bootstrap.timepicker', []) + +.constant('timepickerConfig', { + hourStep: 1, + minuteStep: 1, + showMeridian: true, + meridians: null, + readonlyInput: false, + mousewheel: true +}) + +.controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) { + var selected = new Date(), + ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl + meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; + + this.init = function( ngModelCtrl_, inputs ) { + ngModelCtrl = ngModelCtrl_; + ngModelCtrl.$render = this.render; + + var hoursInputEl = inputs.eq(0), + minutesInputEl = inputs.eq(1); + + var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; + if ( mousewheel ) { + this.setupMousewheelEvents( hoursInputEl, minutesInputEl ); + } + + $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; + this.setupInputEvents( hoursInputEl, minutesInputEl ); + }; + + var hourStep = timepickerConfig.hourStep; + if ($attrs.hourStep) { + $scope.$parent.$watch($parse($attrs.hourStep), function(value) { + hourStep = parseInt(value, 10); + }); + } + + var minuteStep = timepickerConfig.minuteStep; + if ($attrs.minuteStep) { + $scope.$parent.$watch($parse($attrs.minuteStep), function(value) { + minuteStep = parseInt(value, 10); + }); + } + + // 12H / 24H mode + $scope.showMeridian = timepickerConfig.showMeridian; + if ($attrs.showMeridian) { + $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { + $scope.showMeridian = !!value; + + if ( ngModelCtrl.$error.time ) { + // Evaluate from template + var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); + if (angular.isDefined( hours ) && angular.isDefined( minutes )) { + selected.setHours( hours ); + refresh(); + } + } else { + updateTemplate(); + } + }); + } + + // Get $scope.hours in 24H mode if valid + function getHoursFromTemplate ( ) { + var hours = parseInt( $scope.hours, 10 ); + var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); + if ( !valid ) { + return undefined; + } + + if ( $scope.showMeridian ) { + if ( hours === 12 ) { + hours = 0; + } + if ( $scope.meridian === meridians[1] ) { + hours = hours + 12; + } + } + return hours; + } + + function getMinutesFromTemplate() { + var minutes = parseInt($scope.minutes, 10); + return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; + } + + function pad( value ) { + return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value; + } + + // Respond on mousewheel spin + this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) { + var isScrollingUp = function(e) { + if (e.originalEvent) { + e = e.originalEvent; + } + //pick correct delta variable depending on event + var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; + return (e.detail || delta > 0); + }; + + hoursInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() ); + e.preventDefault(); + }); + + minutesInputEl.bind('mousewheel wheel', function(e) { + $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() ); + e.preventDefault(); + }); + + }; + + this.setupInputEvents = function( hoursInputEl, minutesInputEl ) { + if ( $scope.readonlyInput ) { + $scope.updateHours = angular.noop; + $scope.updateMinutes = angular.noop; + return; + } + + var invalidate = function(invalidHours, invalidMinutes) { + ngModelCtrl.$setViewValue( null ); + ngModelCtrl.$setValidity('time', false); + if (angular.isDefined(invalidHours)) { + $scope.invalidHours = invalidHours; + } + if (angular.isDefined(invalidMinutes)) { + $scope.invalidMinutes = invalidMinutes; + } + }; + + $scope.updateHours = function() { + var hours = getHoursFromTemplate(); + + if ( angular.isDefined(hours) ) { + selected.setHours( hours ); + refresh( 'h' ); + } else { + invalidate(true); + } + }; + + hoursInputEl.bind('blur', function(e) { + if ( !$scope.invalidHours && $scope.hours < 10) { + $scope.$apply( function() { + $scope.hours = pad( $scope.hours ); + }); + } + }); + + $scope.updateMinutes = function() { + var minutes = getMinutesFromTemplate(); + + if ( angular.isDefined(minutes) ) { + selected.setMinutes( minutes ); + refresh( 'm' ); + } else { + invalidate(undefined, true); + } + }; + + minutesInputEl.bind('blur', function(e) { + if ( !$scope.invalidMinutes && $scope.minutes < 10 ) { + $scope.$apply( function() { + $scope.minutes = pad( $scope.minutes ); + }); + } + }); + + }; + + this.render = function() { + var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null; + + if ( isNaN(date) ) { + ngModelCtrl.$setValidity('time', false); + $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'); + } else { + if ( date ) { + selected = date; + } + makeValid(); + updateTemplate(); + } + }; + + // Call internally when we know that model is valid. + function refresh( keyboardChange ) { + makeValid(); + ngModelCtrl.$setViewValue( new Date(selected) ); + updateTemplate( keyboardChange ); + } + + function makeValid() { + ngModelCtrl.$setValidity('time', true); + $scope.invalidHours = false; + $scope.invalidMinutes = false; + } + + function updateTemplate( keyboardChange ) { + var hours = selected.getHours(), minutes = selected.getMinutes(); + + if ( $scope.showMeridian ) { + hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system + } + + $scope.hours = keyboardChange === 'h' ? hours : pad(hours); + $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes); + $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; + } + + function addMinutes( minutes ) { + var dt = new Date( selected.getTime() + minutes * 60000 ); + selected.setHours( dt.getHours(), dt.getMinutes() ); + refresh(); + } + + $scope.incrementHours = function() { + addMinutes( hourStep * 60 ); + }; + $scope.decrementHours = function() { + addMinutes( - hourStep * 60 ); + }; + $scope.incrementMinutes = function() { + addMinutes( minuteStep ); + }; + $scope.decrementMinutes = function() { + addMinutes( - minuteStep ); + }; + $scope.toggleMeridian = function() { + addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); + }; +}]) + +.directive('timepicker', function () { + return { + restrict: 'EA', + require: ['timepicker', '?^ngModel'], + controller:'TimepickerController', + replace: true, + scope: {}, + templateUrl: 'template/timepicker/timepicker.html', + link: function(scope, element, attrs, ctrls) { + var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; + + if ( ngModelCtrl ) { + timepickerCtrl.init( ngModelCtrl, element.find('input') ); + } + } + }; +}); + +angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) + +/** + * A helper service that can parse typeahead's syntax (string provided by users) + * Extracted to a separate service for ease of unit testing + */ + .factory('typeaheadParser', ['$parse', function ($parse) { + + // 00000111000000000000022200000000000000003333333333333330000000000044000 + var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; + + return { + parse:function (input) { + + var match = input.match(TYPEAHEAD_REGEXP); + if (!match) { + throw new Error( + 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + + ' but got "' + input + '".'); + } + + return { + itemName:match[3], + source:$parse(match[4]), + viewMapper:$parse(match[2] || match[1]), + modelMapper:$parse(match[1]) + }; + } + }; +}]) + + .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', + function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { + + var HOT_KEYS = [9, 13, 27, 38, 40]; + + return { + require:'ngModel', + link:function (originalScope, element, attrs, modelCtrl) { + + //SUPPORTED ATTRIBUTES (OPTIONS) + + //minimal no of characters that needs to be entered before typeahead kicks-in + var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; + + //minimal wait time after last character typed before typehead kicks-in + var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; + + //should it restrict model values to the ones selected from the popup only? + var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; + + //binding to a variable that indicates if matches are being retrieved asynchronously + var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + + //a callback executed when a match is selected + var onSelectCallback = $parse(attrs.typeaheadOnSelect); + + var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; + + var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; + + //INTERNAL VARIABLES + + //model setter executed upon match selection + var $setModelValue = $parse(attrs.ngModel).assign; + + //expressions used by typeahead + var parserResult = typeaheadParser.parse(attrs.typeahead); + + var hasFocus; + + //create a child scope for the typeahead directive so we are not polluting original scope + //with typeahead-specific data (matches, query etc.) + var scope = originalScope.$new(); + originalScope.$on('$destroy', function(){ + scope.$destroy(); + }); + + // WAI-ARIA + var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); + element.attr({ + 'aria-autocomplete': 'list', + 'aria-expanded': false, + 'aria-owns': popupId + }); + + //pop-up element used to display matches + var popUpEl = angular.element('
        '); + popUpEl.attr({ + id: popupId, + matches: 'matches', + active: 'activeIdx', + select: 'select(activeIdx)', + query: 'query', + position: 'position' + }); + //custom item template + if (angular.isDefined(attrs.typeaheadTemplateUrl)) { + popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); + } + + var resetMatches = function() { + scope.matches = []; + scope.activeIdx = -1; + element.attr('aria-expanded', false); + }; + + var getMatchId = function(index) { + return popupId + '-option-' + index; + }; + + // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. + // This attribute is added or removed automatically when the `activeIdx` changes. + scope.$watch('activeIdx', function(index) { + if (index < 0) { + element.removeAttr('aria-activedescendant'); + } else { + element.attr('aria-activedescendant', getMatchId(index)); + } + }); + + var getMatchesAsync = function(inputValue) { + + var locals = {$viewValue: inputValue}; + isLoadingSetter(originalScope, true); + $q.when(parserResult.source(originalScope, locals)).then(function(matches) { + + //it might happen that several async queries were in progress if a user were typing fast + //but we are interested only in responses that correspond to the current view value + var onCurrentRequest = (inputValue === modelCtrl.$viewValue); + if (onCurrentRequest && hasFocus) { + if (matches.length > 0) { + + scope.activeIdx = 0; + scope.matches.length = 0; + + //transform labels + for(var i=0; i= minSearch) { + if (waitTime > 0) { + cancelPreviousTimeout(); + scheduleSearchWithTimeout(inputValue); + } else { + getMatchesAsync(inputValue); + } + } else { + isLoadingSetter(originalScope, false); + cancelPreviousTimeout(); + resetMatches(); + } + + if (isEditable) { + return inputValue; + } else { + if (!inputValue) { + // Reset in case user had typed something previously. + modelCtrl.$setValidity('editable', true); + return inputValue; + } else { + modelCtrl.$setValidity('editable', false); + return undefined; + } + } + }); + + modelCtrl.$formatters.push(function (modelValue) { + + var candidateViewValue, emptyViewValue; + var locals = {}; + + if (inputFormatter) { + + locals['$model'] = modelValue; + return inputFormatter(originalScope, locals); + + } else { + + //it might happen that we don't have enough info to properly render input value + //we need to check for this situation and simply return model value if we can't apply custom formatting + locals[parserResult.itemName] = modelValue; + candidateViewValue = parserResult.viewMapper(originalScope, locals); + locals[parserResult.itemName] = undefined; + emptyViewValue = parserResult.viewMapper(originalScope, locals); + + return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; + } + }); + + scope.select = function (activeIdx) { + //called from within the $digest() cycle + var locals = {}; + var model, item; + + locals[parserResult.itemName] = item = scope.matches[activeIdx].model; + model = parserResult.modelMapper(originalScope, locals); + $setModelValue(originalScope, model); + modelCtrl.$setValidity('editable', true); + + onSelectCallback(originalScope, { + $item: item, + $model: model, + $label: parserResult.viewMapper(originalScope, locals) + }); + + resetMatches(); + + //return focus to the input element if a match was selected via a mouse click event + // use timeout to avoid $rootScope:inprog error + $timeout(function() { element[0].focus(); }, 0, false); + }; + + //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) + element.bind('keydown', function (evt) { + + //typeahead is open and an "interesting" key was pressed + if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { + return; + } + + evt.preventDefault(); + + if (evt.which === 40) { + scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; + scope.$digest(); + + } else if (evt.which === 38) { + scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1; + scope.$digest(); + + } else if (evt.which === 13 || evt.which === 9) { + scope.$apply(function () { + scope.select(scope.activeIdx); + }); + + } else if (evt.which === 27) { + evt.stopPropagation(); + + resetMatches(); + scope.$digest(); + } + }); + + element.bind('blur', function (evt) { + hasFocus = false; + }); + + // Keep reference to click handler to unbind it. + var dismissClickHandler = function (evt) { + if (element[0] !== evt.target) { + resetMatches(); + scope.$digest(); + } + }; + + $document.bind('click', dismissClickHandler); + + originalScope.$on('$destroy', function(){ + $document.unbind('click', dismissClickHandler); + }); + + var $popup = $compile(popUpEl)(scope); + if ( appendToBody ) { + $document.find('body').append($popup); + } else { + element.after($popup); + } + } + }; + +}]) + + .directive('typeaheadPopup', function () { + return { + restrict:'EA', + scope:{ + matches:'=', + query:'=', + active:'=', + position:'=', + select:'&' + }, + replace:true, + templateUrl:'template/typeahead/typeahead-popup.html', + link:function (scope, element, attrs) { + + scope.templateUrl = attrs.templateUrl; + + scope.isOpen = function () { + return scope.matches.length > 0; + }; + + scope.isActive = function (matchIdx) { + return scope.active == matchIdx; + }; + + scope.selectActive = function (matchIdx) { + scope.active = matchIdx; + }; + + scope.selectMatch = function (activeIdx) { + scope.select({activeIdx:activeIdx}); + }; + } + }; + }) + + .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) { + return { + restrict:'EA', + scope:{ + index:'=', + match:'=', + query:'=' + }, + link:function (scope, element, attrs) { + var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; + $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){ + element.replaceWith($compile(tplContent.trim())(scope)); + }); + } + }; + }]) + + .filter('typeaheadHighlight', function() { + + function escapeRegexp(queryToEscape) { + return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); + } + + return function(matchItem, query) { + return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem; + }; + }); diff --git a/bower_components/angular-bootstrap/ui-bootstrap.min.js b/bower_components/angular-bootstrap/ui-bootstrap.min.js new file mode 100644 index 0000000..ffd1a9e --- /dev/null +++ b/bower_components/angular-bootstrap/ui-bootstrap.min.js @@ -0,0 +1,9 @@ +/* + * angular-ui-bootstrap + * http://angular-ui.github.io/bootstrap/ + + * Version: 0.11.2 - 2014-09-26 + * License: MIT + */ +angular.module("ui.bootstrap",["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]),angular.module("ui.bootstrap.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("ui.bootstrap.collapse",["ui.bootstrap.transition"]).directive("collapse",["$transition",function(a){return{link:function(b,c,d){function e(b){function d(){j===e&&(j=void 0)}var e=a(c,b);return j&&j.cancel(),j=e,e.then(d,d),e}function f(){k?(k=!1,g()):(c.removeClass("collapse").addClass("collapsing"),e({height:c[0].scrollHeight+"px"}).then(g))}function g(){c.removeClass("collapsing"),c.addClass("collapse in"),c.css({height:"auto"})}function h(){if(k)k=!1,i(),c.css({height:0});else{c.css({height:c[0].scrollHeight+"px"});{c[0].offsetWidth}c.removeClass("collapse in").addClass("collapsing"),e({height:0}).then(i)}}function i(){c.removeClass("collapsing"),c.addClass("collapse")}var j,k=!0;b.$watch(d.collapse,function(a){a?h():f()})}}}]),angular.module("ui.bootstrap.accordion",["ui.bootstrap.collapse"]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",function(){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@",isOpen:"=?",isDisabled:"=?"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(a,b,c,d){d.addGroup(a),a.$watch("isOpen",function(b){b&&d.closeOthers(a)}),a.toggleOpen=function(){a.isDisabled||(a.isOpen=!a.isOpen)}}}}).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",link:function(a,b,c,d,e){d.setHeading(e(a,function(){}))}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("ui.bootstrap.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"@",close:"&"}}}),angular.module("ui.bootstrap.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("ui.bootstrap.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass||"active",this.toggleEvent=a.toggleEvent||"click"}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){var d=b.hasClass(e.activeClass);(!d||angular.isDefined(c.uncheckable))&&a.$apply(function(){f.$setViewValue(d?null:a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("ui.bootstrap.carousel",["ui.bootstrap.transition"]).controller("CarouselController",["$scope","$timeout","$transition",function(a,b,c){function d(){e();var c=+a.interval;!isNaN(c)&&c>=0&&(g=b(f,c))}function e(){g&&(b.cancel(g),g=null)}function f(){h?(a.next(),d()):a.pause()}var g,h,i=this,j=i.slides=a.slides=[],k=-1;i.currentSlide=null;var l=!1;i.select=a.select=function(e,f){function g(){if(!l){if(i.currentSlide&&angular.isString(f)&&!a.noTransition&&e.$element){e.$element.addClass(f);{e.$element[0].offsetWidth}angular.forEach(j,function(a){angular.extend(a,{direction:"",entering:!1,leaving:!1,active:!1})}),angular.extend(e,{direction:f,active:!0,entering:!0}),angular.extend(i.currentSlide||{},{direction:f,leaving:!0}),a.$currentTransition=c(e.$element,{}),function(b,c){a.$currentTransition.then(function(){h(b,c)},function(){h(b,c)})}(e,i.currentSlide)}else h(e,i.currentSlide);i.currentSlide=e,k=m,d()}}function h(b,c){angular.extend(b,{direction:"",active:!0,leaving:!1,entering:!1}),angular.extend(c||{},{direction:"",active:!1,leaving:!1,entering:!1}),a.$currentTransition=null}var m=j.indexOf(e);void 0===f&&(f=m>k?"next":"prev"),e&&e!==i.currentSlide&&(a.$currentTransition?(a.$currentTransition.cancel(),b(g)):g())},a.$on("$destroy",function(){l=!0}),i.indexOfSlide=function(a){return j.indexOf(a)},a.next=function(){var b=(k+1)%j.length;return a.$currentTransition?void 0:i.select(j[b],"next")},a.prev=function(){var b=0>k-1?j.length-1:k-1;return a.$currentTransition?void 0:i.select(j[b],"prev")},a.isActive=function(a){return i.currentSlide===a},a.$watch("interval",d),a.$on("$destroy",e),a.play=function(){h||(h=!0,d())},a.pause=function(){a.noPause||(h=!1,e())},i.addSlide=function(b,c){b.$element=c,j.push(b),1===j.length||b.active?(i.select(j[j.length-1]),1==j.length&&a.play()):b.active=!1},i.removeSlide=function(a){var b=j.indexOf(a);j.splice(b,1),j.length>0&&a.active?i.select(b>=j.length?j[b-1]:j[b]):k>b&&k--}}]).directive("carousel",[function(){return{restrict:"EA",transclude:!0,replace:!0,controller:"CarouselController",require:"carousel",templateUrl:"template/carousel/carousel.html",scope:{interval:"=",noTransition:"=",noPause:"="}}}]).directive("slide",function(){return{require:"^carousel",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/carousel/slide.html",scope:{active:"=?"},link:function(a,b,c,d){d.addSlide(a,b),a.$on("$destroy",function(){d.removeSlide(a)}),a.$watch("active",function(b){b&&d.select(a)})}}}),angular.module("ui.bootstrap.dateparser",[]).service("dateParser",["$locale","orderByFilter",function(a,b){function c(a){var c=[],d=a.split("");return angular.forEach(e,function(b,e){var f=a.indexOf(e);if(f>-1){a=a.split(""),d[f]="("+b.regex+")",a[f]="$";for(var g=f+1,h=f+e.length;h>g;g++)d[g]="",a[g]="$";a=a.join(""),c.push({index:f,apply:b.apply})}}),{regex:new RegExp("^"+d.join("")+"$"),map:b(c,"index")}}function d(a,b,c){return 1===b&&c>28?29===c&&(a%4===0&&a%100!==0||a%400===0):3===b||5===b||8===b||10===b?31>c:!0}this.parsers={};var e={yyyy:{regex:"\\d{4}",apply:function(a){this.year=+a}},yy:{regex:"\\d{2}",apply:function(a){this.year=+a+2e3}},y:{regex:"\\d{1,4}",apply:function(a){this.year=+a}},MMMM:{regex:a.DATETIME_FORMATS.MONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.MONTH.indexOf(b)}},MMM:{regex:a.DATETIME_FORMATS.SHORTMONTH.join("|"),apply:function(b){this.month=a.DATETIME_FORMATS.SHORTMONTH.indexOf(b)}},MM:{regex:"0[1-9]|1[0-2]",apply:function(a){this.month=a-1}},M:{regex:"[1-9]|1[0-2]",apply:function(a){this.month=a-1}},dd:{regex:"[0-2][0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},d:{regex:"[1-2]?[0-9]{1}|3[0-1]{1}",apply:function(a){this.date=+a}},EEEE:{regex:a.DATETIME_FORMATS.DAY.join("|")},EEE:{regex:a.DATETIME_FORMATS.SHORTDAY.join("|")}};this.parse=function(b,e){if(!angular.isString(b)||!e)return b;e=a.DATETIME_FORMATS[e]||e,this.parsers[e]||(this.parsers[e]=c(e));var f=this.parsers[e],g=f.regex,h=f.map,i=b.match(g);if(i&&i.length){for(var j,k={year:1900,month:0,date:1,hours:0},l=1,m=i.length;m>l;l++){var n=h[l-1];n.apply&&n.apply.call(k,i[l])}return d(k.year,k.month,k.date)&&(j=new Date(k.year,k.month,k.date,k.hours)),j}}}]),angular.module("ui.bootstrap.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].documentElement.scrollLeft)}},positionElements:function(a,b,c,d){var e,f,g,h,i=c.split("-"),j=i[0],k=i[1]||"center";e=d?this.offset(a):this.position(a),f=b.prop("offsetWidth"),g=b.prop("offsetHeight");var l={center:function(){return e.left+e.width/2-f/2},left:function(){return e.left},right:function(){return e.left+e.width}},m={center:function(){return e.top+e.height/2-g/2},top:function(){return e.top},bottom:function(){return e.top+e.height}};switch(j){case"right":h={top:m[k](),left:l[j]()};break;case"left":h={top:m[k](),left:e.left-f};break;case"bottom":h={top:m[j](),left:l[k]()};break;default:h={top:e.top-g,left:l[k]()}}return h}}}]),angular.module("ui.bootstrap.datepicker",["ui.bootstrap.dateparser","ui.bootstrap.position"]).constant("datepickerConfig",{formatDay:"dd",formatMonth:"MMMM",formatYear:"yyyy",formatDayHeader:"EEE",formatDayTitle:"MMMM yyyy",formatMonthTitle:"yyyy",datepickerMode:"day",minMode:"day",maxMode:"year",showWeeks:!0,startingDay:0,yearRange:20,minDate:null,maxDate:null}).controller("DatepickerController",["$scope","$attrs","$parse","$interpolate","$timeout","$log","dateFilter","datepickerConfig",function(a,b,c,d,e,f,g,h){var i=this,j={$setViewValue:angular.noop};this.modes=["day","month","year"],angular.forEach(["formatDay","formatMonth","formatYear","formatDayHeader","formatDayTitle","formatMonthTitle","minMode","maxMode","showWeeks","startingDay","yearRange"],function(c,e){i[c]=angular.isDefined(b[c])?8>e?d(b[c])(a.$parent):a.$parent.$eval(b[c]):h[c]}),angular.forEach(["minDate","maxDate"],function(d){b[d]?a.$parent.$watch(c(b[d]),function(a){i[d]=a?new Date(a):null,i.refreshView()}):i[d]=h[d]?new Date(h[d]):null}),a.datepickerMode=a.datepickerMode||h.datepickerMode,a.uniqueId="datepicker-"+a.$id+"-"+Math.floor(1e4*Math.random()),this.activeDate=angular.isDefined(b.initDate)?a.$parent.$eval(b.initDate):new Date,a.isActive=function(b){return 0===i.compare(b.date,i.activeDate)?(a.activeDateId=b.uid,!0):!1},this.init=function(a){j=a,j.$render=function(){i.render()}},this.render=function(){if(j.$modelValue){var a=new Date(j.$modelValue),b=!isNaN(a);b?this.activeDate=a:f.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.'),j.$setValidity("date",b)}this.refreshView()},this.refreshView=function(){if(this.element){this._refreshView();var a=j.$modelValue?new Date(j.$modelValue):null;j.$setValidity("date-disabled",!a||this.element&&!this.isDisabled(a))}},this.createDateObject=function(a,b){var c=j.$modelValue?new Date(j.$modelValue):null;return{date:a,label:g(a,b),selected:c&&0===this.compare(a,c),disabled:this.isDisabled(a),current:0===this.compare(a,new Date)}},this.isDisabled=function(c){return this.minDate&&this.compare(c,this.minDate)<0||this.maxDate&&this.compare(c,this.maxDate)>0||b.dateDisabled&&a.dateDisabled({date:c,mode:a.datepickerMode})},this.split=function(a,b){for(var c=[];a.length>0;)c.push(a.splice(0,b));return c},a.select=function(b){if(a.datepickerMode===i.minMode){var c=j.$modelValue?new Date(j.$modelValue):new Date(0,0,0,0,0,0,0);c.setFullYear(b.getFullYear(),b.getMonth(),b.getDate()),j.$setViewValue(c),j.$render()}else i.activeDate=b,a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)-1]},a.move=function(a){var b=i.activeDate.getFullYear()+a*(i.step.years||0),c=i.activeDate.getMonth()+a*(i.step.months||0);i.activeDate.setFullYear(b,c,1),i.refreshView()},a.toggleMode=function(b){b=b||1,a.datepickerMode===i.maxMode&&1===b||a.datepickerMode===i.minMode&&-1===b||(a.datepickerMode=i.modes[i.modes.indexOf(a.datepickerMode)+b])},a.keys={13:"enter",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down"};var k=function(){e(function(){i.element[0].focus()},0,!1)};a.$on("datepicker.focus",k),a.keydown=function(b){var c=a.keys[b.which];if(c&&!b.shiftKey&&!b.altKey)if(b.preventDefault(),b.stopPropagation(),"enter"===c||"space"===c){if(i.isDisabled(i.activeDate))return;a.select(i.activeDate),k()}else!b.ctrlKey||"up"!==c&&"down"!==c?(i.handleKeyDown(c,b),i.refreshView()):(a.toggleMode("up"===c?1:-1),k())}}]).directive("datepicker",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/datepicker.html",scope:{datepickerMode:"=?",dateDisabled:"&"},require:["datepicker","?^ngModel"],controller:"DatepickerController",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}).directive("daypicker",["dateFilter",function(a){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/day.html",require:"^datepicker",link:function(b,c,d,e){function f(a,b){return 1!==b||a%4!==0||a%100===0&&a%400!==0?i[b]:29}function g(a,b){var c=new Array(b),d=new Date(a),e=0;for(d.setHours(12);b>e;)c[e++]=new Date(d),d.setDate(d.getDate()+1);return c}function h(a){var b=new Date(a);b.setDate(b.getDate()+4-(b.getDay()||7));var c=b.getTime();return b.setMonth(0),b.setDate(1),Math.floor(Math.round((c-b)/864e5)/7)+1}b.showWeeks=e.showWeeks,e.step={months:1},e.element=c;var i=[31,28,31,30,31,30,31,31,30,31,30,31];e._refreshView=function(){var c=e.activeDate.getFullYear(),d=e.activeDate.getMonth(),f=new Date(c,d,1),i=e.startingDay-f.getDay(),j=i>0?7-i:-i,k=new Date(f);j>0&&k.setDate(-j+1);for(var l=g(k,42),m=0;42>m;m++)l[m]=angular.extend(e.createDateObject(l[m],e.formatDay),{secondary:l[m].getMonth()!==d,uid:b.uniqueId+"-"+m});b.labels=new Array(7);for(var n=0;7>n;n++)b.labels[n]={abbr:a(l[n].date,e.formatDayHeader),full:a(l[n].date,"EEEE")};if(b.title=a(e.activeDate,e.formatDayTitle),b.rows=e.split(l,7),b.showWeeks){b.weekNumbers=[];for(var o=h(b.rows[0][0].date),p=b.rows.length;b.weekNumbers.push(o++)f;f++)c[f]=angular.extend(e.createDateObject(new Date(d,f,1),e.formatMonth),{uid:b.uniqueId+"-"+f});b.title=a(e.activeDate,e.formatMonthTitle),b.rows=e.split(c,3)},e.compare=function(a,b){return new Date(a.getFullYear(),a.getMonth())-new Date(b.getFullYear(),b.getMonth())},e.handleKeyDown=function(a){var b=e.activeDate.getMonth();if("left"===a)b-=1;else if("up"===a)b-=3;else if("right"===a)b+=1;else if("down"===a)b+=3;else if("pageup"===a||"pagedown"===a){var c=e.activeDate.getFullYear()+("pageup"===a?-1:1);e.activeDate.setFullYear(c)}else"home"===a?b=0:"end"===a&&(b=11);e.activeDate.setMonth(b)},e.refreshView()}}}]).directive("yearpicker",["dateFilter",function(){return{restrict:"EA",replace:!0,templateUrl:"template/datepicker/year.html",require:"^datepicker",link:function(a,b,c,d){function e(a){return parseInt((a-1)/f,10)*f+1}var f=d.yearRange;d.step={years:f},d.element=b,d._refreshView=function(){for(var b=new Array(f),c=0,g=e(d.activeDate.getFullYear());f>c;c++)b[c]=angular.extend(d.createDateObject(new Date(g+c,0,1),d.formatYear),{uid:a.uniqueId+"-"+c});a.title=[b[0].label,b[f-1].label].join(" - "),a.rows=d.split(b,5)},d.compare=function(a,b){return a.getFullYear()-b.getFullYear()},d.handleKeyDown=function(a){var b=d.activeDate.getFullYear();"left"===a?b-=1:"up"===a?b-=5:"right"===a?b+=1:"down"===a?b+=5:"pageup"===a||"pagedown"===a?b+=("pageup"===a?-1:1)*d.step.years:"home"===a?b=e(d.activeDate.getFullYear()):"end"===a&&(b=e(d.activeDate.getFullYear())+f-1),d.activeDate.setFullYear(b)},d.refreshView()}}}]).constant("datepickerPopupConfig",{datepickerPopup:"yyyy-MM-dd",currentText:"Today",clearText:"Clear",closeText:"Done",closeOnDateSelection:!0,appendToBody:!1,showButtonBar:!0}).directive("datepickerPopup",["$compile","$parse","$document","$position","dateFilter","dateParser","datepickerPopupConfig",function(a,b,c,d,e,f,g){return{restrict:"EA",require:"ngModel",scope:{isOpen:"=?",currentText:"@",clearText:"@",closeText:"@",dateDisabled:"&"},link:function(h,i,j,k){function l(a){return a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()})}function m(a){if(a){if(angular.isDate(a)&&!isNaN(a))return k.$setValidity("date",!0),a;if(angular.isString(a)){var b=f.parse(a,n)||new Date(a);return isNaN(b)?void k.$setValidity("date",!1):(k.$setValidity("date",!0),b)}return void k.$setValidity("date",!1)}return k.$setValidity("date",!0),null}var n,o=angular.isDefined(j.closeOnDateSelection)?h.$parent.$eval(j.closeOnDateSelection):g.closeOnDateSelection,p=angular.isDefined(j.datepickerAppendToBody)?h.$parent.$eval(j.datepickerAppendToBody):g.appendToBody;h.showButtonBar=angular.isDefined(j.showButtonBar)?h.$parent.$eval(j.showButtonBar):g.showButtonBar,h.getText=function(a){return h[a+"Text"]||g[a+"Text"]},j.$observe("datepickerPopup",function(a){n=a||g.datepickerPopup,k.$render()});var q=angular.element("
        ");q.attr({"ng-model":"date","ng-change":"dateSelection()"});var r=angular.element(q.children()[0]);j.datepickerOptions&&angular.forEach(h.$parent.$eval(j.datepickerOptions),function(a,b){r.attr(l(b),a)}),h.watchData={},angular.forEach(["minDate","maxDate","datepickerMode"],function(a){if(j[a]){var c=b(j[a]);if(h.$parent.$watch(c,function(b){h.watchData[a]=b}),r.attr(l(a),"watchData."+a),"datepickerMode"===a){var d=c.assign;h.$watch("watchData."+a,function(a,b){a!==b&&d(h.$parent,a)})}}}),j.dateDisabled&&r.attr("date-disabled","dateDisabled({ date: date, mode: mode })"),k.$parsers.unshift(m),h.dateSelection=function(a){angular.isDefined(a)&&(h.date=a),k.$setViewValue(h.date),k.$render(),o&&(h.isOpen=!1,i[0].focus())},i.bind("input change keyup",function(){h.$apply(function(){h.date=k.$modelValue})}),k.$render=function(){var a=k.$viewValue?e(k.$viewValue,n):"";i.val(a),h.date=m(k.$modelValue)};var s=function(a){h.isOpen&&a.target!==i[0]&&h.$apply(function(){h.isOpen=!1})},t=function(a){h.keydown(a)};i.bind("keydown",t),h.keydown=function(a){27===a.which?(a.preventDefault(),a.stopPropagation(),h.close()):40!==a.which||h.isOpen||(h.isOpen=!0)},h.$watch("isOpen",function(a){a?(h.$broadcast("datepicker.focus"),h.position=p?d.offset(i):d.position(i),h.position.top=h.position.top+i.prop("offsetHeight"),c.bind("click",s)):c.unbind("click",s)}),h.select=function(a){if("today"===a){var b=new Date;angular.isDate(k.$modelValue)?(a=new Date(k.$modelValue),a.setFullYear(b.getFullYear(),b.getMonth(),b.getDate())):a=new Date(b.setHours(0,0,0,0))}h.dateSelection(a)},h.close=function(){h.isOpen=!1,i[0].focus()};var u=a(q)(h);q.remove(),p?c.find("body").append(u):i.after(u),h.$on("$destroy",function(){u.remove(),i.unbind("keydown",t),c.unbind("click",s)})}}}]).directive("datepickerPopupWrap",function(){return{restrict:"EA",replace:!0,transclude:!0,templateUrl:"template/datepicker/popup.html",link:function(a,b){b.bind("click",function(a){a.preventDefault(),a.stopPropagation()})}}}),angular.module("ui.bootstrap.dropdown",[]).constant("dropdownConfig",{openClass:"open"}).service("dropdownService",["$document",function(a){var b=null;this.open=function(e){b||(a.bind("click",c),a.bind("keydown",d)),b&&b!==e&&(b.isOpen=!1),b=e},this.close=function(e){b===e&&(b=null,a.unbind("click",c),a.unbind("keydown",d))};var c=function(a){var c=b.getToggleElement();a&&c&&c[0].contains(a.target)||b.$apply(function(){b.isOpen=!1})},d=function(a){27===a.which&&(b.focusToggleElement(),c())}}]).controller("DropdownController",["$scope","$attrs","$parse","dropdownConfig","dropdownService","$animate",function(a,b,c,d,e,f){var g,h=this,i=a.$new(),j=d.openClass,k=angular.noop,l=b.onToggle?c(b.onToggle):angular.noop;this.init=function(d){h.$element=d,b.isOpen&&(g=c(b.isOpen),k=g.assign,a.$watch(g,function(a){i.isOpen=!!a}))},this.toggle=function(a){return i.isOpen=arguments.length?!!a:!i.isOpen},this.isOpen=function(){return i.isOpen},i.getToggleElement=function(){return h.toggleElement},i.focusToggleElement=function(){h.toggleElement&&h.toggleElement[0].focus()},i.$watch("isOpen",function(b,c){f[b?"addClass":"removeClass"](h.$element,j),b?(i.focusToggleElement(),e.open(i)):e.close(i),k(a,b),angular.isDefined(b)&&b!==c&&l(a,{open:!!b})}),a.$on("$locationChangeSuccess",function(){i.isOpen=!1}),a.$on("$destroy",function(){i.$destroy()})}]).directive("dropdown",function(){return{restrict:"CA",controller:"DropdownController",link:function(a,b,c,d){d.init(b)}}}).directive("dropdownToggle",function(){return{restrict:"CA",require:"?^dropdown",link:function(a,b,c,d){if(d){d.toggleElement=b;var e=function(e){e.preventDefault(),b.hasClass("disabled")||c.disabled||a.$apply(function(){d.toggle()})};b.bind("click",e),b.attr({"aria-haspopup":!0,"aria-expanded":!1}),a.$watch(d.isOpen,function(a){b.attr("aria-expanded",!!a)}),a.$on("$destroy",function(){b.unbind("click",e)})}}}}),angular.module("ui.bootstrap.modal",["ui.bootstrap.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0),i()})}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&(a.preventDefault(),e.$apply(function(){o.dismiss(b.key,"escape key press")})))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();if(h>=0&&!k){l=e.$new(!0),l.index=h;var i=angular.element("
        ");i.attr("backdrop-class",b.backdropClass),k=d(i)(l),f.append(k)}var j=angular.element("
        ");j.attr({"template-url":b.windowTemplateUrl,"window-class":b.windowClass,size:b.size,index:n.length()-1,animate:"animate"}).html(b.content);var o=d(j)(b.scope);n.top().value.modalDomEl=o,f.append(o),f.addClass(m)},o.close=function(a,b){var c=n.get(a);c&&(c.value.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a);c&&(c.value.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(angular.isFunction(a.templateUrl)?a.templateUrl():a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,backdropClass:b.backdropClass,windowClass:b.windowClass,windowTemplateUrl:b.windowTemplateUrl,size:b.size})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("ui.bootstrap.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse",function(a,b,c){var d=this,e={$setViewValue:angular.noop},f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(f,g){e=f,this.config=g,e.$render=function(){d.render()},b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){d.itemsPerPage=parseInt(b,10),a.totalPages=d.calculateTotalPages()}):this.itemsPerPage=g.itemsPerPage},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.render=function(){a.page=parseInt(e.$viewValue,10)||1},a.selectPage=function(b){a.page!==b&&b>0&&b<=a.totalPages&&(e.$setViewValue(b),e.$render())},a.getText=function(b){return a[b+"Text"]||d.config[b+"Text"]},a.noPrevious=function(){return 1===a.page},a.noNext=function(){return a.page===a.totalPages},a.$watch("totalItems",function(){a.totalPages=d.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),a.page>b?a.selectPage(b):e.$render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@"},require:["pagination","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c){return{number:a,text:b,active:c}}function h(a,b){var c=[],d=1,e=b,f=angular.isDefined(k)&&b>k;f&&(l?(d=Math.max(a-Math.floor(k/2),1),e=d+k-1,e>b&&(e=b,d=e-k+1)):(d=(Math.ceil(a/k)-1)*k+1,e=Math.min(d+k-1,b)));for(var h=d;e>=h;h++){var i=g(h,h,h===a);c.push(i)}if(f&&!l){if(d>1){var j=g(d-1,"...",!1);c.unshift(j)}if(b>e){var m=g(e+1,"...",!1);c.push(m)}}return c}var i=f[0],j=f[1];if(j){var k=angular.isDefined(e.maxSize)?c.$parent.$eval(e.maxSize):b.maxSize,l=angular.isDefined(e.rotate)?c.$parent.$eval(e.rotate):b.rotate;c.boundaryLinks=angular.isDefined(e.boundaryLinks)?c.$parent.$eval(e.boundaryLinks):b.boundaryLinks,c.directionLinks=angular.isDefined(e.directionLinks)?c.$parent.$eval(e.directionLinks):b.directionLinks,i.init(j,b),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){k=parseInt(a,10),i.render()});var m=i.render;i.render=function(){m(),c.page>0&&c.page<=c.totalPages&&(c.pages=h(c.page,c.totalPages))}}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{totalItems:"=",previousText:"@",nextText:"@"},require:["pager","?ngModel"],controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){var f=e[0],g=e[1];g&&(b.align=angular.isDefined(d.align)?b.$parent.$eval(d.align):a.align,f.init(g,a))}}}]),angular.module("ui.bootstrap.tooltip",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a) +},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="
        ';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!y||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?v||(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return v=null,u&&(g.cancel(u),u=null),b.tt_content?(r(),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),z(),b.tt_isOpen=!0,b.$digest(),z):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),v=null,b.tt_animation?u||(u=g(s,500)):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){u=null,t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=angular.isDefined(d[l+"Enable"]),z=function(){var a=j.positionElements(c,t,b.tt_placement,w);a.top+="px",a.left+="px",t.css(a)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var A=function(){c.unbind(x.show,k),c.unbind(x.hide,m)};d.$observe(l+"Trigger",function(a){A(),x=n(a),x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m))});var B=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(B)?!!B:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),A(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("ui.bootstrap.popover",["ui.bootstrap.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("ui.bootstrap.progressbar",[]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig",function(a,b,c){var d=this,e=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.bars=[],a.max=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,this.addBar=function(b,c){e||c.css({transition:"none"}),this.bars.push(b),b.$watch("value",function(c){b.percent=+(100*c/a.max).toFixed(2)}),b.$on("$destroy",function(){c=null,d.removeBar(b)})},this.removeBar=function(a){this.bars.splice(this.bars.indexOf(a),1)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},templateUrl:"template/progressbar/progress.html"}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("ui.bootstrap.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","ratingConfig",function(a,b,c){var d={$setViewValue:angular.noop};this.init=function(e){d=e,d.$render=this.render,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):c.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):c.stateOff;var f=angular.isDefined(b.ratingStates)?a.$parent.$eval(b.ratingStates):new Array(angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max);a.range=this.buildTemplateObjects(f)},this.buildTemplateObjects=function(a){for(var b=0,c=a.length;c>b;b++)a[b]=angular.extend({index:b},{stateOn:this.stateOn,stateOff:this.stateOff},a[b]);return a},a.rate=function(b){!a.readonly&&b>=0&&b<=a.range.length&&(d.$setViewValue(b),d.$render())},a.enter=function(b){a.readonly||(a.value=b),a.onHover({value:b})},a.reset=function(){a.value=d.$viewValue,a.onLeave()},a.onKeydown=function(b){/(37|38|39|40)/.test(b.which)&&(b.preventDefault(),b.stopPropagation(),a.rate(a.value+(38===b.which||39===b.which?1:-1)))},this.render=function(){a.value=d.$viewValue}}]).directive("rating",function(){return{restrict:"EA",require:["rating","ngModel"],scope:{readonly:"=?",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0,link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f)}}}),angular.module("ui.bootstrap.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){angular.forEach(c,function(b){b.active&&b!==a&&(b.active=!1,b.onDeselect())}),a.active=!0,a.onSelect()},b.addTab=function(a){c.push(a),1===c.length?a.active=!0:a.active&&b.select(a)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{type:"@"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{active:"=?",heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){b.$watch("active",function(a){a&&f.select(b)}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("ui.bootstrap.timepicker",[]).constant("timepickerConfig",{hourStep:1,minuteStep:1,showMeridian:!0,meridians:null,readonlyInput:!1,mousewheel:!0}).controller("TimepickerController",["$scope","$attrs","$parse","$log","$locale","timepickerConfig",function(a,b,c,d,e,f){function g(){var b=parseInt(a.hours,10),c=a.showMeridian?b>0&&13>b:b>=0&&24>b;return c?(a.showMeridian&&(12===b&&(b=0),a.meridian===p[1]&&(b+=12)),b):void 0}function h(){var b=parseInt(a.minutes,10);return b>=0&&60>b?b:void 0}function i(a){return angular.isDefined(a)&&a.toString().length<2?"0"+a:a}function j(a){k(),o.$setViewValue(new Date(n)),l(a)}function k(){o.$setValidity("time",!0),a.invalidHours=!1,a.invalidMinutes=!1}function l(b){var c=n.getHours(),d=n.getMinutes();a.showMeridian&&(c=0===c||12===c?12:c%12),a.hours="h"===b?c:i(c),a.minutes="m"===b?d:i(d),a.meridian=n.getHours()<12?p[0]:p[1]}function m(a){var b=new Date(n.getTime()+6e4*a);n.setHours(b.getHours(),b.getMinutes()),j()}var n=new Date,o={$setViewValue:angular.noop},p=angular.isDefined(b.meridians)?a.$parent.$eval(b.meridians):f.meridians||e.DATETIME_FORMATS.AMPMS;this.init=function(c,d){o=c,o.$render=this.render;var e=d.eq(0),g=d.eq(1),h=angular.isDefined(b.mousewheel)?a.$parent.$eval(b.mousewheel):f.mousewheel;h&&this.setupMousewheelEvents(e,g),a.readonlyInput=angular.isDefined(b.readonlyInput)?a.$parent.$eval(b.readonlyInput):f.readonlyInput,this.setupInputEvents(e,g)};var q=f.hourStep;b.hourStep&&a.$parent.$watch(c(b.hourStep),function(a){q=parseInt(a,10)});var r=f.minuteStep;b.minuteStep&&a.$parent.$watch(c(b.minuteStep),function(a){r=parseInt(a,10)}),a.showMeridian=f.showMeridian,b.showMeridian&&a.$parent.$watch(c(b.showMeridian),function(b){if(a.showMeridian=!!b,o.$error.time){var c=g(),d=h();angular.isDefined(c)&&angular.isDefined(d)&&(n.setHours(c),j())}else l()}),this.setupMousewheelEvents=function(b,c){var d=function(a){a.originalEvent&&(a=a.originalEvent);var b=a.wheelDelta?a.wheelDelta:-a.deltaY;return a.detail||b>0};b.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementHours():a.decrementHours()),b.preventDefault()}),c.bind("mousewheel wheel",function(b){a.$apply(d(b)?a.incrementMinutes():a.decrementMinutes()),b.preventDefault()})},this.setupInputEvents=function(b,c){if(a.readonlyInput)return a.updateHours=angular.noop,void(a.updateMinutes=angular.noop);var d=function(b,c){o.$setViewValue(null),o.$setValidity("time",!1),angular.isDefined(b)&&(a.invalidHours=b),angular.isDefined(c)&&(a.invalidMinutes=c)};a.updateHours=function(){var a=g();angular.isDefined(a)?(n.setHours(a),j("h")):d(!0)},b.bind("blur",function(){!a.invalidHours&&a.hours<10&&a.$apply(function(){a.hours=i(a.hours)})}),a.updateMinutes=function(){var a=h();angular.isDefined(a)?(n.setMinutes(a),j("m")):d(void 0,!0)},c.bind("blur",function(){!a.invalidMinutes&&a.minutes<10&&a.$apply(function(){a.minutes=i(a.minutes)})})},this.render=function(){var a=o.$modelValue?new Date(o.$modelValue):null;isNaN(a)?(o.$setValidity("time",!1),d.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.')):(a&&(n=a),k(),l())},a.incrementHours=function(){m(60*q)},a.decrementHours=function(){m(60*-q)},a.incrementMinutes=function(){m(r)},a.decrementMinutes=function(){m(-r)},a.toggleMeridian=function(){m(720*(n.getHours()<12?1:-1))}}]).directive("timepicker",function(){return{restrict:"EA",require:["timepicker","?^ngModel"],controller:"TimepickerController",replace:!0,scope:{},templateUrl:"template/timepicker/timepicker.html",link:function(a,b,c,d){var e=d[0],f=d[1];f&&e.init(f,b.find("input"))}}}),angular.module("ui.bootstrap.typeahead",["ui.bootstrap.position","ui.bootstrap.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error('Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_" but got "'+c+'".');return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?i.$eval(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=i.$new();i.$on("$destroy",function(){w.$destroy()});var x="typeahead-"+w.$id+"-"+Math.floor(1e4*Math.random());j.attr({"aria-autocomplete":"list","aria-expanded":!1,"aria-owns":x});var y=angular.element("
        ");y.attr({id:x,matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&y.attr("template-url",k.typeaheadTemplateUrl);var z=function(){w.matches=[],w.activeIdx=-1,j.attr("aria-expanded",!1)},A=function(a){return x+"-option-"+a};w.$watch("activeIdx",function(a){0>a?j.removeAttr("aria-activedescendant"):j.attr("aria-activedescendant",A(a))});var B=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){var d=a===l.$viewValue;if(d&&m)if(c.length>0){w.activeIdx=0,w.matches.length=0;for(var e=0;e=n?o>0?(E(),D(a)):B(a):(q(i,!1),E(),z()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),w.select=function(a){var b,c,e={};e[v.itemName]=c=w.matches[a].model,b=v.modelMapper(i,e),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,e)}),z(),d(function(){j[0].focus()},0,!1)},j.bind("keydown",function(a){0!==w.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(w.activeIdx=(w.activeIdx+1)%w.matches.length,w.$digest()):38===a.which?(w.activeIdx=(w.activeIdx?w.activeIdx:w.matches.length)-1,w.$digest()):13===a.which||9===a.which?w.$apply(function(){w.select(w.activeIdx)}):27===a.which&&(a.stopPropagation(),z(),w.$digest()))}),j.bind("blur",function(){m=!1});var F=function(a){j[0]!==a.target&&(z(),w.$digest())};e.bind("click",F),i.$on("$destroy",function(){e.unbind("click",F)});var G=a(y)(w);t?e.find("body").append(G):j.after(G)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?(""+b).replace(new RegExp(a(c),"gi"),"$&"):b}}); \ No newline at end of file diff --git a/bower_components/angular-cookies/README.md b/bower_components/angular-cookies/README.md new file mode 100644 index 0000000..58b54ca --- /dev/null +++ b/bower_components/angular-cookies/README.md @@ -0,0 +1,4 @@ +bower-angular-cookies +===================== + +angular-cookies bower repo \ No newline at end of file diff --git a/bower_components/angular-cookies/angular-cookies-2929d62773.min.js b/bower_components/angular-cookies/angular-cookies-2929d62773.min.js new file mode 100755 index 0000000..cf0b5a3 --- /dev/null +++ b/bower_components/angular-cookies/angular-cookies-2929d62773.min.js @@ -0,0 +1,7 @@ +/* + AngularJS v1.0.8 + (c) 2010-2012 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,b){var c={},g={},h,i=!1,j=f.copy,k=f.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,j(a,g),j(a,c),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(c[a])&&b.cookies(a,l);for(a in c)e=c[a],f.isString(e)?e!==g[a]&&(b.cookies(a,e),d=!0):f.isDefined(g[a])?c[a]=g[a]:delete c[a];if(d)for(a in e=b.cookies(),c)c[a]!==e[a]&&(k(e[a])?delete c[a]:c[a]=e[a])});return c}]).factory("$cookieStore", +["$cookies",function(d){return{get:function(b){return(b=d[b])?f.fromJson(b):b},put:function(b,c){d[b]=f.toJson(c)},remove:function(b){delete d[b]}}}])})(window,window.angular); diff --git a/bower_components/angular-cookies/angular-cookies-f8f31ce93a.js b/bower_components/angular-cookies/angular-cookies-f8f31ce93a.js new file mode 100755 index 0000000..db27099 --- /dev/null +++ b/bower_components/angular-cookies/angular-cookies-f8f31ce93a.js @@ -0,0 +1,196 @@ +/** + * @license AngularJS v1.0.8 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) { +'use strict'; + +/** + * @ngdoc overview + * @name ngCookies + */ + + +angular.module('ngCookies', ['ng']). + /** + * @ngdoc object + * @name ngCookies.$cookies + * @requires $browser + * + * @description + * Provides read/write access to browser's cookies. + * + * Only a simple Object is exposed and by adding or removing properties to/from + * this object, new cookies are created/deleted at the end of current $eval. + * + * # Installation + * To use $cookies make sure you have included the `angular-cookies.js` that comes in Angular + * package. You can also find this file on Google CDN, bower as well as at + * {@link http://code.angularjs.org/ code.angularjs.org}. + * + * Finally load the module in your application: + * + * angular.module('app', ['ngCookies']); + * + * and you are ready to get started! + * + * @example + + + + + + */ + factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { + var cookies = {}, + lastCookies = {}, + lastBrowserCookies, + runEval = false, + copy = angular.copy, + isUndefined = angular.isUndefined; + + //creates a poller fn that copies all cookies from the $browser to service & inits the service + $browser.addPollFn(function() { + var currentCookies = $browser.cookies(); + if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl + lastBrowserCookies = currentCookies; + copy(currentCookies, lastCookies); + copy(currentCookies, cookies); + if (runEval) $rootScope.$apply(); + } + })(); + + runEval = true; + + //at the end of each eval, push cookies + //TODO: this should happen before the "delayed" watches fire, because if some cookies are not + // strings or browser refuses to store some cookies, we update the model in the push fn. + $rootScope.$watch(push); + + return cookies; + + + /** + * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. + */ + function push() { + var name, + value, + browserCookies, + updated; + + //delete any cookies deleted in $cookies + for (name in lastCookies) { + if (isUndefined(cookies[name])) { + $browser.cookies(name, undefined); + } + } + + //update all cookies updated in $cookies + for(name in cookies) { + value = cookies[name]; + if (!angular.isString(value)) { + if (angular.isDefined(lastCookies[name])) { + cookies[name] = lastCookies[name]; + } else { + delete cookies[name]; + } + } else if (value !== lastCookies[name]) { + $browser.cookies(name, value); + updated = true; + } + } + + //verify what was actually stored + if (updated){ + updated = false; + browserCookies = $browser.cookies(); + + for (name in cookies) { + if (cookies[name] !== browserCookies[name]) { + //delete or reset all cookies that the browser dropped from $cookies + if (isUndefined(browserCookies[name])) { + delete cookies[name]; + } else { + cookies[name] = browserCookies[name]; + } + updated = true; + } + } + } + } + }]). + + + /** + * @ngdoc object + * @name ngCookies.$cookieStore + * @requires $cookies + * + * @description + * Provides a key-value (string-object) storage, that is backed by session cookies. + * Objects put or retrieved from this storage are automatically serialized or + * deserialized by angular's toJson/fromJson. + * @example + */ + factory('$cookieStore', ['$cookies', function($cookies) { + + return { + /** + * @ngdoc method + * @name ngCookies.$cookieStore#get + * @methodOf ngCookies.$cookieStore + * + * @description + * Returns the value of given cookie key + * + * @param {string} key Id to use for lookup. + * @returns {Object} Deserialized cookie value. + */ + get: function(key) { + var value = $cookies[key]; + return value ? angular.fromJson(value) : value; + }, + + /** + * @ngdoc method + * @name ngCookies.$cookieStore#put + * @methodOf ngCookies.$cookieStore + * + * @description + * Sets a value for given cookie key + * + * @param {string} key Id for the `value`. + * @param {Object} value Value to be stored. + */ + put: function(key, value) { + $cookies[key] = angular.toJson(value); + }, + + /** + * @ngdoc method + * @name ngCookies.$cookieStore#remove + * @methodOf ngCookies.$cookieStore + * + * @description + * Remove given cookie + * + * @param {string} key Id of the key-value pair to delete. + */ + remove: function(key) { + delete $cookies[key]; + } + }; + + }]); + + +})(window, window.angular); diff --git a/bower_components/angular-cookies/angular-cookies.js b/bower_components/angular-cookies/angular-cookies.js new file mode 100755 index 0000000..db27099 --- /dev/null +++ b/bower_components/angular-cookies/angular-cookies.js @@ -0,0 +1,196 @@ +/** + * @license AngularJS v1.0.8 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) { +'use strict'; + +/** + * @ngdoc overview + * @name ngCookies + */ + + +angular.module('ngCookies', ['ng']). + /** + * @ngdoc object + * @name ngCookies.$cookies + * @requires $browser + * + * @description + * Provides read/write access to browser's cookies. + * + * Only a simple Object is exposed and by adding or removing properties to/from + * this object, new cookies are created/deleted at the end of current $eval. + * + * # Installation + * To use $cookies make sure you have included the `angular-cookies.js` that comes in Angular + * package. You can also find this file on Google CDN, bower as well as at + * {@link http://code.angularjs.org/ code.angularjs.org}. + * + * Finally load the module in your application: + * + * angular.module('app', ['ngCookies']); + * + * and you are ready to get started! + * + * @example + + + + + + */ + factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { + var cookies = {}, + lastCookies = {}, + lastBrowserCookies, + runEval = false, + copy = angular.copy, + isUndefined = angular.isUndefined; + + //creates a poller fn that copies all cookies from the $browser to service & inits the service + $browser.addPollFn(function() { + var currentCookies = $browser.cookies(); + if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl + lastBrowserCookies = currentCookies; + copy(currentCookies, lastCookies); + copy(currentCookies, cookies); + if (runEval) $rootScope.$apply(); + } + })(); + + runEval = true; + + //at the end of each eval, push cookies + //TODO: this should happen before the "delayed" watches fire, because if some cookies are not + // strings or browser refuses to store some cookies, we update the model in the push fn. + $rootScope.$watch(push); + + return cookies; + + + /** + * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. + */ + function push() { + var name, + value, + browserCookies, + updated; + + //delete any cookies deleted in $cookies + for (name in lastCookies) { + if (isUndefined(cookies[name])) { + $browser.cookies(name, undefined); + } + } + + //update all cookies updated in $cookies + for(name in cookies) { + value = cookies[name]; + if (!angular.isString(value)) { + if (angular.isDefined(lastCookies[name])) { + cookies[name] = lastCookies[name]; + } else { + delete cookies[name]; + } + } else if (value !== lastCookies[name]) { + $browser.cookies(name, value); + updated = true; + } + } + + //verify what was actually stored + if (updated){ + updated = false; + browserCookies = $browser.cookies(); + + for (name in cookies) { + if (cookies[name] !== browserCookies[name]) { + //delete or reset all cookies that the browser dropped from $cookies + if (isUndefined(browserCookies[name])) { + delete cookies[name]; + } else { + cookies[name] = browserCookies[name]; + } + updated = true; + } + } + } + } + }]). + + + /** + * @ngdoc object + * @name ngCookies.$cookieStore + * @requires $cookies + * + * @description + * Provides a key-value (string-object) storage, that is backed by session cookies. + * Objects put or retrieved from this storage are automatically serialized or + * deserialized by angular's toJson/fromJson. + * @example + */ + factory('$cookieStore', ['$cookies', function($cookies) { + + return { + /** + * @ngdoc method + * @name ngCookies.$cookieStore#get + * @methodOf ngCookies.$cookieStore + * + * @description + * Returns the value of given cookie key + * + * @param {string} key Id to use for lookup. + * @returns {Object} Deserialized cookie value. + */ + get: function(key) { + var value = $cookies[key]; + return value ? angular.fromJson(value) : value; + }, + + /** + * @ngdoc method + * @name ngCookies.$cookieStore#put + * @methodOf ngCookies.$cookieStore + * + * @description + * Sets a value for given cookie key + * + * @param {string} key Id for the `value`. + * @param {Object} value Value to be stored. + */ + put: function(key, value) { + $cookies[key] = angular.toJson(value); + }, + + /** + * @ngdoc method + * @name ngCookies.$cookieStore#remove + * @methodOf ngCookies.$cookieStore + * + * @description + * Remove given cookie + * + * @param {string} key Id of the key-value pair to delete. + */ + remove: function(key) { + delete $cookies[key]; + } + }; + + }]); + + +})(window, window.angular); diff --git a/bower_components/angular-cookies/angular-cookies.min.js b/bower_components/angular-cookies/angular-cookies.min.js new file mode 100755 index 0000000..cf0b5a3 --- /dev/null +++ b/bower_components/angular-cookies/angular-cookies.min.js @@ -0,0 +1,7 @@ +/* + AngularJS v1.0.8 + (c) 2010-2012 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(m,f,l){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,b){var c={},g={},h,i=!1,j=f.copy,k=f.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,j(a,g),j(a,c),i&&d.$apply())})();i=!0;d.$watch(function(){var a,e,d;for(a in g)k(c[a])&&b.cookies(a,l);for(a in c)e=c[a],f.isString(e)?e!==g[a]&&(b.cookies(a,e),d=!0):f.isDefined(g[a])?c[a]=g[a]:delete c[a];if(d)for(a in e=b.cookies(),c)c[a]!==e[a]&&(k(e[a])?delete c[a]:c[a]=e[a])});return c}]).factory("$cookieStore", +["$cookies",function(d){return{get:function(b){return(b=d[b])?f.fromJson(b):b},put:function(b,c){d[b]=f.toJson(c)},remove:function(b){delete d[b]}}}])})(window,window.angular); diff --git a/bower_components/angular-cookies/bower.json b/bower_components/angular-cookies/bower.json new file mode 100644 index 0000000..a91fcbf --- /dev/null +++ b/bower_components/angular-cookies/bower.json @@ -0,0 +1,8 @@ +{ + "name": "angular-cookies", + "version": "1.0.8", + "main": "./angular-cookies.js", + "dependencies": { + "angular": "1.0.8" + } +} diff --git a/bower_components/angular-disqus/Gruntfile-96f1d3b172.js b/bower_components/angular-disqus/Gruntfile-96f1d3b172.js new file mode 100644 index 0000000..21a590d --- /dev/null +++ b/bower_components/angular-disqus/Gruntfile-96f1d3b172.js @@ -0,0 +1,49 @@ +module.exports = function( grunt ) { + 'use strict'; + + var SRC = 'src'; + + grunt.initConfig({ + pkg : grunt.file.readJSON('package.json'), + banner : '/* \n * <%= pkg.name %> <%= pkg.version %>\n * <%= pkg.homepage %>\n * \n * Licensed under the <%= pkg.license %> license\n */', + uglify : { + production : { + src: [ SRC + '/**/*.js' ], + dest: 'angular-disqus.min.js' + } + }, + + copy : { + production : { + files : [ + { src: SRC + '/angular-disqus.js', dest : 'angular-disqus.js' } + ] + } + }, + + karma : { + spec: { + configFile : 'karma.conf.js' + } + }, + + usebanner : { + options : { + position: 'top', + banner : '<%= banner %>' + }, + files : { + src : [ 'angular-disqus.js', 'angular-disqus.min.js' ] + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-karma'); + grunt.loadNpmTasks('grunt-banner'); + + grunt.registerTask('test', [ 'karma' ]); + grunt.registerTask('build', [ 'test', 'copy', 'uglify', 'usebanner' ]); + grunt.registerTask('default', [ 'build' ]); +}; diff --git a/bower_components/angular-disqus/Gruntfile.js b/bower_components/angular-disqus/Gruntfile.js new file mode 100644 index 0000000..21a590d --- /dev/null +++ b/bower_components/angular-disqus/Gruntfile.js @@ -0,0 +1,49 @@ +module.exports = function( grunt ) { + 'use strict'; + + var SRC = 'src'; + + grunt.initConfig({ + pkg : grunt.file.readJSON('package.json'), + banner : '/* \n * <%= pkg.name %> <%= pkg.version %>\n * <%= pkg.homepage %>\n * \n * Licensed under the <%= pkg.license %> license\n */', + uglify : { + production : { + src: [ SRC + '/**/*.js' ], + dest: 'angular-disqus.min.js' + } + }, + + copy : { + production : { + files : [ + { src: SRC + '/angular-disqus.js', dest : 'angular-disqus.js' } + ] + } + }, + + karma : { + spec: { + configFile : 'karma.conf.js' + } + }, + + usebanner : { + options : { + position: 'top', + banner : '<%= banner %>' + }, + files : { + src : [ 'angular-disqus.js', 'angular-disqus.min.js' ] + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-karma'); + grunt.loadNpmTasks('grunt-banner'); + + grunt.registerTask('test', [ 'karma' ]); + grunt.registerTask('build', [ 'test', 'copy', 'uglify', 'usebanner' ]); + grunt.registerTask('default', [ 'build' ]); +}; diff --git a/bower_components/angular-disqus/LICENSE b/bower_components/angular-disqus/LICENSE new file mode 100644 index 0000000..0f3ed81 --- /dev/null +++ b/bower_components/angular-disqus/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mikk Kirstein + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/bower_components/angular-disqus/README.md b/bower_components/angular-disqus/README.md new file mode 100644 index 0000000..9dc7439 --- /dev/null +++ b/bower_components/angular-disqus/README.md @@ -0,0 +1,78 @@ +# angular-disqus [![Build Status](https://travis-ci.org/kirstein/angular-disqus.png)](https://travis-ci.org/kirstein/angular-disqus) + + > ```angular.js``` and ```disqus``` integration made easy + +A set of directive(s) and services to simplify the life of developers. + +### Getting started +--- +Add ```ngDisqus``` to required modules list + +``` + angular.module('myApp', [ …, 'ngDisqus' ]); +``` + +Register your ```shortname```: + + 1. by just adding it to ```window.disqus_shortname``` + 2. by configure with ```$disqusProvider``` and registering it via ```$disqusProvider.setShortname``` + +Add comments to threads by using the ```disqus``` directive + +``` + +
        + + +
        +``` + +### Need to know +--- +Disqus will only update on sites which use `hashbang` ( `#!` ). +Thats not something I have control over, so in order to use this plugin, please make sure that you have your `$locationProvider.hashPrefix('!')` set. + + +### Disqus identifiers +--- +Disqus identifiers must be passed to the directive as as expressions. If the plan is to pass a constant then one must make sure that the constant is wrapped in `'` apostrophes (_disqus="'id'""_) + +### Comment count +--- +Angular-disqus will display comment using the `data-disqus-identifier` attribute. + +Example on how to show the comment count: + +``` + test page 1 | + +``` + +This will replace the content of the anchor tag with given comment count. + +There is some talk of this in the [disqus spec][1] + +### API +--- + +1. ```$disqus#getShortname``` getter for the current shortname +2. ```$disqus#comment``` will reset comments (or generate comments if needed) +3. ```$disqusProvider#setShortname``` setter for shortname +4. ```$loadCount``` initiates the thread comment count loading (generally should not be used) + +### Devel +--- + +``` + npm install + bower install + grunt test + grunt build +``` + +### License +--- + +MIT + +[1]: http://help.disqus.com/customer/portal/articles/565624-tightening-your-disqus-integration#using-an-identifier diff --git a/bower_components/angular-disqus/angular-disqus-3c6b376e35.js b/bower_components/angular-disqus/angular-disqus-3c6b376e35.js new file mode 100644 index 0000000..3a1114c --- /dev/null +++ b/bower_components/angular-disqus/angular-disqus-3c6b376e35.js @@ -0,0 +1,245 @@ +/* + * angular-disqus 1.1.0 + * http://github.com/kirstein/angular-disqus + * + * Licensed under the MIT license + */ +(function (angular, window) { + 'use strict'; + + var disqusModule = angular.module('ngDisqus', [ ]); + + /** + * $disqus provider. + */ + disqusModule.provider('$disqus', function() { + var TYPE_EMBED = 'embed.js'; // general disqus embed script + var TYPE_COUNT = 'count.js'; // used for count + + // Placeholder for the disqus shortname + var shortname; + + /** + * @return {Element} dom element for script adding + */ + function getScriptContainer() { + return (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]); + } + + /** + * @return {String} disqus shortname + */ + function getShortname() { + return shortname || window.disqus_shortname; + } + + /** + * @param {String} shortname disqus shortname + * @param {String} file file name to add. + * @return {String} disqus embed src with embedded shortname + */ + function getScriptSrc(shortname, file) { + return '//' + shortname + '.disqus.com/' + file; + } + + /** + * Builds the script tag + * + * @param {String} src script source + * @return {Element} script element + */ + function buildScriptTag(src) { + var script = document.createElement('script'); + + // Configure the script tag + script.type = 'text/javascript'; + script.async = true; + script.src = src; + + return script; + } + + /** + * Searches the given element for defined script tag + * if its already there then return true. Otherwise return false + * + * @param {Element} element element to search within + * @param {String} scriptSrc script src + * @return {Boolean} true if its there, false if its not + */ + function hasScriptTagInPlace(container, scriptSrc) { + var scripts = container.getElementsByTagName('script'), + script, i; + + for (i = 0; i < scripts.length; i += 1) { + script = scripts[i]; + + // Check if the name contains the given values + // We need to check with indexOf because browsers replace // with their protocol + if (~script.src.indexOf(scriptSrc)) { + return true; + } + } + + return false; + } + + /** + * Writes disqus globals to window object. + * Needed for the first load. Otherwise the disqus wouldn't know what thread comments to load. + * + * @param {String} id disqus identifier + * @param {String} url disqus url + * @param {String} shortname disqus shortname + */ + function setGlobals(id, url, shortname) { + window.disqus_identifier = id; + window.disqus_url = url; + window.disqus_shortname = shortname; + } + + /** + * Refreshes the count from DISQUSWIDGETS. + */ + function getCount() { + var widgets = window.DISQUSWIDGETS; + if (widgets && angular.isFunction(widgets.getCount)) { + widgets.getCount(); + } + } + + /** + * Trigger the reset comment call + * @param {String} $location location service + * @param {String} id Thread id + */ + function resetCommit($location, id) { + window.DISQUS.reset({ + reload: true, + config : function() { + this.page.identifier = id; + this.page.url = $location.absUrl(); + } + }); + } + + /** + * Adds disqus script tag to header by its type. + * If the script tag already exists in header then wont continue. + * + * Adds script tags by their type. + * Currently we are using two types: + * 1. count.js + * 2. embed.js + * + * @param {String} shortname disqus shortname + * @param {String} type disqus scirpt tag type + */ + function addScriptTag(shortname, type) { + var container = getScriptContainer(), + scriptSrc = getScriptSrc(shortname, type); + + // If it already has a script tag in place then lets not do anything + // This might happen if the user changes the page faster than then disqus can load + if (hasScriptTagInPlace(container, scriptSrc)) { + return; + } + + // Build the script tag and append it to container + container.appendChild(buildScriptTag(scriptSrc)); + } + + + /** + * @param {String} sname shortname + */ + this.setShortname = function(sname) { + shortname = sname; + }; + + // Provider constructor + this.$get = [ '$location', function($location) { + + /** + * Resets the comment for thread. + * If disqus was not defined then it will add disqus to script tags. + * If disqus was initialized earlier then it will just use disqus api to reset it + * + * @param {String} id required thread id + */ + function commit(id) { + var shortname = getShortname(); + + if (!angular.isDefined(shortname)) { + throw new Error('No disqus shortname defined'); + } else if (!angular.isDefined(id)) { + throw new Error('No disqus thread id defined'); + } else if (angular.isDefined(window.DISQUS)) { + resetCommit($location, id); + } else { + setGlobals(id, $location.absUrl(), shortname); + addScriptTag(shortname, TYPE_EMBED); + } + } + + /** + * Loads the comment script tag and initiates the comments. + * Sets the globals according to the current page. + * + * If the embed disqus is not added to page then adds that. + * + * @param {Stirng} id thread id + */ + function loadCount(id) { + setGlobals(id, $location.absUrl(), shortname); + addScriptTag(getShortname(), TYPE_EMBED); + addScriptTag(getShortname(), TYPE_COUNT); + getCount(); + } + + // Expose public api + return { + commit : commit, + getShortname : getShortname, + loadCount : loadCount + }; + }]; + }); + + /** + * Disqus thread comment directive. + * Used to display the comments block for a thread. + */ + disqusModule.directive('disqus', [ '$disqus', function($disqus) { + + return { + restrict : 'AC', + replace : true, + scope : { + id : '=disqus', + }, + template : '
        ', + link: function link(scope) { + scope.$watch('id', function(id) { + if (angular.isDefined(id)) { + $disqus.commit(id); + } + }); + } + }; + }]); + + /** + * Disqus comment count directive. + * Just wraps `disqus-identifier` to load the disqus comments count script tag on page + */ + disqusModule.directive('disqusIdentifier', [ '$disqus', function($disqus) { + return { + restrict : 'A', + link : function(scope, elem, attr) { + $disqus.loadCount(attr.disqusIdentifier); + } + }; + }]); + +})(angular, this); diff --git a/bower_components/angular-disqus/angular-disqus-a6c6f1988a.min.js b/bower_components/angular-disqus/angular-disqus-a6c6f1988a.min.js new file mode 100644 index 0000000..7c16bb8 --- /dev/null +++ b/bower_components/angular-disqus/angular-disqus-a6c6f1988a.min.js @@ -0,0 +1,7 @@ +/* + * angular-disqus 1.1.0 + * http://github.com/kirstein/angular-disqus + * + * Licensed under the MIT license + */ +!function(a,b){"use strict";var c=a.module("ngDisqus",[]);c.provider("$disqus",function(){function c(){return document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]}function d(){return l||b.disqus_shortname}function e(a,b){return"//"+a+".disqus.com/"+b}function f(a){var b=document.createElement("script");return b.type="text/javascript",b.async=!0,b.src=a,b}function g(a,b){var c,d,e=a.getElementsByTagName("script");for(d=0;d',link:function(c){c.$watch("id",function(c){a.isDefined(c)&&b.commit(c)})}}}]),c.directive("disqusIdentifier",["$disqus",function(a){return{restrict:"A",link:function(b,c,d){a.loadCount(d.disqusIdentifier)}}}])}(angular,this); \ No newline at end of file diff --git a/bower_components/angular-disqus/angular-disqus.js b/bower_components/angular-disqus/angular-disqus.js new file mode 100644 index 0000000..3a1114c --- /dev/null +++ b/bower_components/angular-disqus/angular-disqus.js @@ -0,0 +1,245 @@ +/* + * angular-disqus 1.1.0 + * http://github.com/kirstein/angular-disqus + * + * Licensed under the MIT license + */ +(function (angular, window) { + 'use strict'; + + var disqusModule = angular.module('ngDisqus', [ ]); + + /** + * $disqus provider. + */ + disqusModule.provider('$disqus', function() { + var TYPE_EMBED = 'embed.js'; // general disqus embed script + var TYPE_COUNT = 'count.js'; // used for count + + // Placeholder for the disqus shortname + var shortname; + + /** + * @return {Element} dom element for script adding + */ + function getScriptContainer() { + return (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]); + } + + /** + * @return {String} disqus shortname + */ + function getShortname() { + return shortname || window.disqus_shortname; + } + + /** + * @param {String} shortname disqus shortname + * @param {String} file file name to add. + * @return {String} disqus embed src with embedded shortname + */ + function getScriptSrc(shortname, file) { + return '//' + shortname + '.disqus.com/' + file; + } + + /** + * Builds the script tag + * + * @param {String} src script source + * @return {Element} script element + */ + function buildScriptTag(src) { + var script = document.createElement('script'); + + // Configure the script tag + script.type = 'text/javascript'; + script.async = true; + script.src = src; + + return script; + } + + /** + * Searches the given element for defined script tag + * if its already there then return true. Otherwise return false + * + * @param {Element} element element to search within + * @param {String} scriptSrc script src + * @return {Boolean} true if its there, false if its not + */ + function hasScriptTagInPlace(container, scriptSrc) { + var scripts = container.getElementsByTagName('script'), + script, i; + + for (i = 0; i < scripts.length; i += 1) { + script = scripts[i]; + + // Check if the name contains the given values + // We need to check with indexOf because browsers replace // with their protocol + if (~script.src.indexOf(scriptSrc)) { + return true; + } + } + + return false; + } + + /** + * Writes disqus globals to window object. + * Needed for the first load. Otherwise the disqus wouldn't know what thread comments to load. + * + * @param {String} id disqus identifier + * @param {String} url disqus url + * @param {String} shortname disqus shortname + */ + function setGlobals(id, url, shortname) { + window.disqus_identifier = id; + window.disqus_url = url; + window.disqus_shortname = shortname; + } + + /** + * Refreshes the count from DISQUSWIDGETS. + */ + function getCount() { + var widgets = window.DISQUSWIDGETS; + if (widgets && angular.isFunction(widgets.getCount)) { + widgets.getCount(); + } + } + + /** + * Trigger the reset comment call + * @param {String} $location location service + * @param {String} id Thread id + */ + function resetCommit($location, id) { + window.DISQUS.reset({ + reload: true, + config : function() { + this.page.identifier = id; + this.page.url = $location.absUrl(); + } + }); + } + + /** + * Adds disqus script tag to header by its type. + * If the script tag already exists in header then wont continue. + * + * Adds script tags by their type. + * Currently we are using two types: + * 1. count.js + * 2. embed.js + * + * @param {String} shortname disqus shortname + * @param {String} type disqus scirpt tag type + */ + function addScriptTag(shortname, type) { + var container = getScriptContainer(), + scriptSrc = getScriptSrc(shortname, type); + + // If it already has a script tag in place then lets not do anything + // This might happen if the user changes the page faster than then disqus can load + if (hasScriptTagInPlace(container, scriptSrc)) { + return; + } + + // Build the script tag and append it to container + container.appendChild(buildScriptTag(scriptSrc)); + } + + + /** + * @param {String} sname shortname + */ + this.setShortname = function(sname) { + shortname = sname; + }; + + // Provider constructor + this.$get = [ '$location', function($location) { + + /** + * Resets the comment for thread. + * If disqus was not defined then it will add disqus to script tags. + * If disqus was initialized earlier then it will just use disqus api to reset it + * + * @param {String} id required thread id + */ + function commit(id) { + var shortname = getShortname(); + + if (!angular.isDefined(shortname)) { + throw new Error('No disqus shortname defined'); + } else if (!angular.isDefined(id)) { + throw new Error('No disqus thread id defined'); + } else if (angular.isDefined(window.DISQUS)) { + resetCommit($location, id); + } else { + setGlobals(id, $location.absUrl(), shortname); + addScriptTag(shortname, TYPE_EMBED); + } + } + + /** + * Loads the comment script tag and initiates the comments. + * Sets the globals according to the current page. + * + * If the embed disqus is not added to page then adds that. + * + * @param {Stirng} id thread id + */ + function loadCount(id) { + setGlobals(id, $location.absUrl(), shortname); + addScriptTag(getShortname(), TYPE_EMBED); + addScriptTag(getShortname(), TYPE_COUNT); + getCount(); + } + + // Expose public api + return { + commit : commit, + getShortname : getShortname, + loadCount : loadCount + }; + }]; + }); + + /** + * Disqus thread comment directive. + * Used to display the comments block for a thread. + */ + disqusModule.directive('disqus', [ '$disqus', function($disqus) { + + return { + restrict : 'AC', + replace : true, + scope : { + id : '=disqus', + }, + template : '
        ', + link: function link(scope) { + scope.$watch('id', function(id) { + if (angular.isDefined(id)) { + $disqus.commit(id); + } + }); + } + }; + }]); + + /** + * Disqus comment count directive. + * Just wraps `disqus-identifier` to load the disqus comments count script tag on page + */ + disqusModule.directive('disqusIdentifier', [ '$disqus', function($disqus) { + return { + restrict : 'A', + link : function(scope, elem, attr) { + $disqus.loadCount(attr.disqusIdentifier); + } + }; + }]); + +})(angular, this); diff --git a/bower_components/angular-disqus/angular-disqus.min.js b/bower_components/angular-disqus/angular-disqus.min.js new file mode 100644 index 0000000..7c16bb8 --- /dev/null +++ b/bower_components/angular-disqus/angular-disqus.min.js @@ -0,0 +1,7 @@ +/* + * angular-disqus 1.1.0 + * http://github.com/kirstein/angular-disqus + * + * Licensed under the MIT license + */ +!function(a,b){"use strict";var c=a.module("ngDisqus",[]);c.provider("$disqus",function(){function c(){return document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0]}function d(){return l||b.disqus_shortname}function e(a,b){return"//"+a+".disqus.com/"+b}function f(a){var b=document.createElement("script");return b.type="text/javascript",b.async=!0,b.src=a,b}function g(a,b){var c,d,e=a.getElementsByTagName("script");for(d=0;d',link:function(c){c.$watch("id",function(c){a.isDefined(c)&&b.commit(c)})}}}]),c.directive("disqusIdentifier",["$disqus",function(a){return{restrict:"A",link:function(b,c,d){a.loadCount(d.disqusIdentifier)}}}])}(angular,this); \ No newline at end of file diff --git a/bower_components/angular-disqus/bower.json b/bower_components/angular-disqus/bower.json new file mode 100644 index 0000000..7a02a11 --- /dev/null +++ b/bower_components/angular-disqus/bower.json @@ -0,0 +1,16 @@ +{ + "name": "angular-disqus", + "version": "1.1.0", + "main": "angular-disqus.js", + "ignore": [ + "node_modules", + "components" + ], + "dependencies": { + "angular": ">1.0.0" + }, + "devDependencies": { + "angular-mocks": "~1.0.6", + "jquery": "~2.0.0" + } +} diff --git a/bower_components/angular-disqus/karma-9a57aa2583.conf.js b/bower_components/angular-disqus/karma-9a57aa2583.conf.js new file mode 100644 index 0000000..bdfba16 --- /dev/null +++ b/bower_components/angular-disqus/karma-9a57aa2583.conf.js @@ -0,0 +1,21 @@ +files = [ + + JASMINE, + JASMINE_ADAPTER, + + 'components/jquery/jquery.js', + 'components/angular/angular.js', + 'components/angular-mocks/angular-mocks.js', + + // The library itself + 'src/angular-disqus.js', + + 'test/**.spec.js' +]; + +growl = true; +colors = true; +singleRun = true; +autoWatch = false; +browsers = ['PhantomJS']; +reporters = ['progress', 'growl']; diff --git a/bower_components/angular-disqus/karma.conf.js b/bower_components/angular-disqus/karma.conf.js new file mode 100644 index 0000000..bdfba16 --- /dev/null +++ b/bower_components/angular-disqus/karma.conf.js @@ -0,0 +1,21 @@ +files = [ + + JASMINE, + JASMINE_ADAPTER, + + 'components/jquery/jquery.js', + 'components/angular/angular.js', + 'components/angular-mocks/angular-mocks.js', + + // The library itself + 'src/angular-disqus.js', + + 'test/**.spec.js' +]; + +growl = true; +colors = true; +singleRun = true; +autoWatch = false; +browsers = ['PhantomJS']; +reporters = ['progress', 'growl']; diff --git a/bower_components/angular-disqus/package.json b/bower_components/angular-disqus/package.json new file mode 100644 index 0000000..1525d29 --- /dev/null +++ b/bower_components/angular-disqus/package.json @@ -0,0 +1,23 @@ +{ + "name": "angular-disqus", + "version": "1.1.0", + "description": "Disqus directive for angular.js", + "homepage": "http://github.com/kirstein/angular-disqus", + "directories": { + "test": "test" + }, + "repository": { + "type": "git", + "url": "https://github.com/kirstein/angular-disqus.git" + }, + "author": "Mikk Kirstein", + "license": "MIT", + "devDependencies": { + "karma": "~0.8.5", + "grunt": "~0.4.1", + "grunt-karma": "~0.4.4", + "grunt-contrib-uglify": "~0.2.0", + "grunt-contrib-copy": "~0.4.1", + "grunt-banner": "~0.1.4" + } +} diff --git a/bower_components/angular-disqus/src/angular-disqus.js b/bower_components/angular-disqus/src/angular-disqus.js new file mode 100644 index 0000000..14aa2a2 --- /dev/null +++ b/bower_components/angular-disqus/src/angular-disqus.js @@ -0,0 +1,239 @@ +(function (angular, window) { + 'use strict'; + + var disqusModule = angular.module('ngDisqus', [ ]); + + /** + * $disqus provider. + */ + disqusModule.provider('$disqus', function() { + var TYPE_EMBED = 'embed.js'; // general disqus embed script + var TYPE_COUNT = 'count.js'; // used for count + + // Placeholder for the disqus shortname + var shortname; + + /** + * @return {Element} dom element for script adding + */ + function getScriptContainer() { + return (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]); + } + + /** + * @return {String} disqus shortname + */ + function getShortname() { + return shortname || window.disqus_shortname; + } + + /** + * @param {String} shortname disqus shortname + * @param {String} file file name to add. + * @return {String} disqus embed src with embedded shortname + */ + function getScriptSrc(shortname, file) { + return '//' + shortname + '.disqus.com/' + file; + } + + /** + * Builds the script tag + * + * @param {String} src script source + * @return {Element} script element + */ + function buildScriptTag(src) { + var script = document.createElement('script'); + + // Configure the script tag + script.type = 'text/javascript'; + script.async = true; + script.src = src; + + return script; + } + + /** + * Searches the given element for defined script tag + * if its already there then return true. Otherwise return false + * + * @param {Element} element element to search within + * @param {String} scriptSrc script src + * @return {Boolean} true if its there, false if its not + */ + function hasScriptTagInPlace(container, scriptSrc) { + var scripts = container.getElementsByTagName('script'), + script, i; + + for (i = 0; i < scripts.length; i += 1) { + script = scripts[i]; + + // Check if the name contains the given values + // We need to check with indexOf because browsers replace // with their protocol + if (~script.src.indexOf(scriptSrc)) { + return true; + } + } + + return false; + } + + /** + * Writes disqus globals to window object. + * Needed for the first load. Otherwise the disqus wouldn't know what thread comments to load. + * + * @param {String} id disqus identifier + * @param {String} url disqus url + * @param {String} shortname disqus shortname + */ + function setGlobals(id, url, shortname) { + window.disqus_identifier = id; + window.disqus_url = url; + window.disqus_shortname = shortname; + } + + /** + * Refreshes the count from DISQUSWIDGETS. + */ + function getCount() { + var widgets = window.DISQUSWIDGETS; + if (widgets && angular.isFunction(widgets.getCount)) { + widgets.getCount(); + } + } + + /** + * Trigger the reset comment call + * @param {String} $location location service + * @param {String} id Thread id + */ + function resetCommit($location, id) { + window.DISQUS.reset({ + reload: true, + config : function() { + this.page.identifier = id; + this.page.url = $location.absUrl(); + } + }); + } + + /** + * Adds disqus script tag to header by its type. + * If the script tag already exists in header then wont continue. + * + * Adds script tags by their type. + * Currently we are using two types: + * 1. count.js + * 2. embed.js + * + * @param {String} shortname disqus shortname + * @param {String} type disqus scirpt tag type + */ + function addScriptTag(shortname, type) { + var container = getScriptContainer(), + scriptSrc = getScriptSrc(shortname, type); + + // If it already has a script tag in place then lets not do anything + // This might happen if the user changes the page faster than then disqus can load + if (hasScriptTagInPlace(container, scriptSrc)) { + return; + } + + // Build the script tag and append it to container + container.appendChild(buildScriptTag(scriptSrc)); + } + + + /** + * @param {String} sname shortname + */ + this.setShortname = function(sname) { + shortname = sname; + }; + + // Provider constructor + this.$get = [ '$location', function($location) { + + /** + * Resets the comment for thread. + * If disqus was not defined then it will add disqus to script tags. + * If disqus was initialized earlier then it will just use disqus api to reset it + * + * @param {String} id required thread id + */ + function commit(id) { + var shortname = getShortname(); + + if (!angular.isDefined(shortname)) { + throw new Error('No disqus shortname defined'); + } else if (!angular.isDefined(id)) { + throw new Error('No disqus thread id defined'); + } else if (angular.isDefined(window.DISQUS)) { + resetCommit($location, id); + } else { + setGlobals(id, $location.absUrl(), shortname); + addScriptTag(shortname, TYPE_EMBED); + } + } + + /** + * Loads the comment script tag and initiates the comments. + * Sets the globals according to the current page. + * + * If the embed disqus is not added to page then adds that. + * + * @param {Stirng} id thread id + */ + function loadCount(id) { + setGlobals(id, $location.absUrl(), shortname); + addScriptTag(getShortname(), TYPE_EMBED); + addScriptTag(getShortname(), TYPE_COUNT); + getCount(); + } + + // Expose public api + return { + commit : commit, + getShortname : getShortname, + loadCount : loadCount + }; + }]; + }); + + /** + * Disqus thread comment directive. + * Used to display the comments block for a thread. + */ + disqusModule.directive('disqus', [ '$disqus', function($disqus) { + + return { + restrict : 'AC', + replace : true, + scope : { + id : '=disqus', + }, + template : '
        ', + link: function link(scope) { + scope.$watch('id', function(id) { + if (angular.isDefined(id)) { + $disqus.commit(id); + } + }); + } + }; + }]); + + /** + * Disqus comment count directive. + * Just wraps `disqus-identifier` to load the disqus comments count script tag on page + */ + disqusModule.directive('disqusIdentifier', [ '$disqus', function($disqus) { + return { + restrict : 'A', + link : function(scope, elem, attr) { + $disqus.loadCount(attr.disqusIdentifier); + } + }; + }]); + +})(angular, this); diff --git a/bower_components/angular-disqus/test/angular-disqus.spec.js b/bower_components/angular-disqus/test/angular-disqus.spec.js new file mode 100644 index 0000000..f5bdd32 --- /dev/null +++ b/bower_components/angular-disqus/test/angular-disqus.spec.js @@ -0,0 +1,274 @@ +describe('Angular-disqus', function() { + var $disqusProvider; + + beforeEach(module('ngDisqus')); + + // Get the provider for testing + beforeEach(function() { + var mod = angular.module('providerTest', []).config(function(_$disqusProvider_) { + $disqusProvider = _$disqusProvider_; + }); + + module(mod.name); + inject(function() {}); + }); + + describe ('$disqusProvider', function() { + it ('should contain the shortname function', function() { + expect($disqusProvider.setShortname).toEqual(jasmine.any(Function)); + }); + + it ('should set the shortname', inject(function($disqus) { + $disqusProvider.setShortname('test-name'); + expect($disqus.getShortname()).toEqual('test-name'); + })); + }); + + describe('$disqus', function() { + + describe('#commit', function() { + + describe ('exception throwing', function() { + it('should throw when no disqus shortname defined', inject(function($disqus, $window) { + expect(function () { + $disqus.commit(); + }).toThrow('No disqus shortname defined'); + })); + + it('should throw when no disqus shortname defined (on window)', inject(function($disqus, $window) { + $window.disqus_shortname = undefined; + expect(function () { + $disqus.commit(); + }).toThrow('No disqus shortname defined'); + })); + + it('should not throw when disqus_shortname is defined on window', inject(function($disqus, $window) { + $window.disqus_shortname = 123; + expect(function () { + $disqus.commit(); + }).not.toThrow('No disqus shortname defined'); + })); + + it('should throw when no id is defined', inject(function($disqus, $window) { + $disqusProvider.setShortname('defined'); + expect(function () { + $disqus.commit(undefined); + }).toThrow('No disqus thread id defined'); + })); + }); + + it('should write the script tag to head if there are other script tags present', inject(function($disqus) { + var el = $('', { + src : 'http://www.google.com' + }).appendTo((document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0])); + + $disqusProvider.setShortname('shortname'); + $disqus.commit('test'); + var tags = $('script[type="text/javascript"][src="//shortname.disqus.com/embed.js"]'); + + expect(tags.length).toBe(1); + })); + + it('should write the script tag to head if not initialized (using provider)', inject(function($disqus) { + + $disqusProvider.setShortname('shortname'); + $disqus.commit('test'); + var tags = $('script[type="text/javascript"][src="//shortname.disqus.com/embed.js"]'); + + expect(tags.length).toBe(1); + })); + + it('should write the script tag to head if not initialized (using $window.disqus_shortname)', inject(function($disqus) { + + $disqusProvider.setShortname('shortname'); + $disqus.commit('test'); + var tags = $('script[type="text/javascript"][src="//shortname.disqus.com/embed.js"]'); + + expect(tags.length).toBe(1); + })); + + it('should write ONLY ONE script tag', inject(function($disqus) { + + $disqusProvider.setShortname('shortname'); + $disqus.commit('test'); + $disqus.commit('test'); + $disqus.commit('test'); + $disqus.commit('test'); + var tags = $('script[type="text/javascript"][src="//shortname.disqus.com/embed.js"]'); + + expect(tags.length).toBe(1); + })); + + it('should write default values to window', inject(function($disqus, $window, $location) { + $disqusProvider.setShortname('shortname'); + $disqus.commit('test'); + + expect($window.disqus_shortname).toEqual('shortname'); + expect($window.disqus_identifier).toEqual('test'); + expect($window.disqus_url).toEqual($location.absUrl()); + })); + + it ('should reset the thread with correct url', inject(function($disqus, $window, $location) { + var spy = jasmine.createSpy('reset spy'); + var data = { + page : {} + }; + + $disqusProvider.setShortname('shortname'); + $window.DISQUS = { + reset : function(opts) { + opts.config.call(data); + } + }; + + $disqus.commit('$location test'); + expect(data.page.url).toBe($location.absUrl()); + })); + + it('should reset the thread if initialized', inject(function($disqus, $window) { + var spy = jasmine.createSpy('disqus reset spy'); + + $disqusProvider.setShortname('shortname'); + // Overwrite the reset method + $window.DISQUS = { + reset : spy + }; + + $disqus.commit('wat'); + expect(spy).toHaveBeenCalled(); + expect(spy.callCount).toBe(1); + + // Validate arguments + expect(spy.mostRecentCall.args[0].reload).toBe(true); + expect(spy.mostRecentCall.args[0].config).toEqual(jasmine.any(Function)); + })); + }); + + describe('#getShortname', function() { + it('should contain #getShortname method', inject(function($disqus) { + expect($disqus.getShortname).toEqual(jasmine.any(Function)); + })); + }); + + describe('#loadCount', function() { + it('should should contain #loadCount method', inject(function($disqus) { + expect($disqus.loadCount).toEqual(jasmine.any(Function)); + })); + + it('should set globals on initializing', inject(function($disqus, $window, $location) { + $disqusProvider.setShortname('shortname'); + $disqus.loadCount('test'); + + expect($window.disqus_shortname).toEqual('shortname'); + expect($window.disqus_identifier).toEqual('test'); + expect($window.disqus_url).toEqual($location.absUrl()); + })); + + it('should add embed script tag if its not added', inject(function($disqus) { + $disqusProvider.setShortname('shortname'); + $disqus.loadCount('test'); + var tags = $('script[type="text/javascript"][src="//shortname.disqus.com/embed.js"]'); + + expect(tags.length).toBe(1); + })); + + it('should add count script tag if its not added', inject(function($disqus) { + $disqusProvider.setShortname('shortname'); + $disqus.loadCount('test'); + var tags = $('script[type="text/javascript"][src="//shortname.disqus.com/count.js"]'); + + expect(tags.length).toBe(1); + })); + + it('should add only one script tag for embed and count', inject(function($disqus) { + $disqusProvider.setShortname('shortname'); + $disqus.loadCount('test'); + $disqus.loadCount('test'); + $disqus.loadCount('test'); + + var count = $('script[type="text/javascript"][src="//shortname.disqus.com/count.js"]'); + var embed = $('script[type="text/javascript"][src="//shortname.disqus.com/embed.js"]'); + + expect(count.length).toBe(1); + expect(embed.length).toBe(1); + })); + + it('should ask getCount from DISQUSWIDGETS on loading', inject(function($disqus, $window) { + $disqusProvider.setShortname('shortname'); + $window.DISQUSWIDGETS = { + getCount : jasmine.createSpy() + }; + + $disqus.loadCount(); + expect($window.DISQUSWIDGETS.getCount).toHaveBeenCalled(); + })); + }); + }); + + describe('directive', function() { + + function compileHtml(html) { + var elem = $compile(html)($rootScope); + $rootScope.$digest(); + + return elem; + } + + var ID = 'disqus_thread', + $compile, $rootScope; + + beforeEach(inject(function(_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + })); + + describe('thread directive', function() { + + it('should compile as class', function() { + var element = compileHtml('
        '); + expect(element.scope().id).toEqual('test-id'); + expect(element.attr('id')).toEqual(ID); + }); + + it('should compile as attribute', function() { + var element = compileHtml('
        '); + expect(element.scope().id).toEqual('test-id'); + expect(element.attr('id')).toEqual(ID); + }); + + it('should trigger commit if id is defined', inject(function($disqus) { + $disqus.commit = jasmine.createSpy('commit call spy'); + compileHtml('
        '); + + expect($disqus.commit).toHaveBeenCalledWith('test-id'); + })); + + it('should trigger commit if id changes', inject(function($window, $disqus, $rootScope) { + $disqus.commit = jasmine.createSpy('commit call spy'); + $rootScope.id = 'test-id'; + compileHtml('
        '); + + $rootScope.id = 'hello-kitty'; + $rootScope.$apply(); + + expect($disqus.commit).toHaveBeenCalledWith('hello-kitty'); + })); + }); + + describe('disqus-identifier directive', function() { + + it('should trigger disqusProvider loadCount', inject(function($disqus) { + spyOn($disqus, 'loadCount'); + var element = compileHtml('
        '); + expect($disqus.loadCount).toHaveBeenCalled(); + })); + + it('should trigger disqusProvider loadCount with given id', inject(function($disqus) { + spyOn($disqus, 'loadCount'); + var element = compileHtml('
        '); + expect($disqus.loadCount).toHaveBeenCalledWith('id'); + })); + + }); + }); +}); diff --git a/bower_components/angular-google-maps/Gruntfile.coffee b/bower_components/angular-google-maps/Gruntfile.coffee new file mode 100644 index 0000000..bfe4c74 --- /dev/null +++ b/bower_components/angular-google-maps/Gruntfile.coffee @@ -0,0 +1,203 @@ +log = require('util').log +jasmineSettings = require("./jasmineSettings") +module.exports = (grunt) -> + # Load the required plugins + grunt.loadNpmTasks "grunt-contrib-uglify" + grunt.loadNpmTasks "grunt-contrib-jshint" + grunt.loadNpmTasks "grunt-contrib-concat" + grunt.loadNpmTasks "grunt-contrib-clean" + grunt.loadNpmTasks "grunt-contrib-connect" + grunt.loadNpmTasks "grunt-contrib-copy" + grunt.loadNpmTasks "grunt-contrib-watch" + grunt.loadNpmTasks "grunt-open" + grunt.loadNpmTasks "grunt-mkdir" + grunt.loadNpmTasks "grunt-contrib-coffee" + grunt.loadNpmTasks "grunt-contrib-jasmine" + grunt.loadNpmTasks "grunt-conventional-changelog" + grunt.loadNpmTasks "grunt-bump" + + options = + # Project configuration. + bump: + options: + files: ['package.json','bower.json'] + updateConfigs: [] + commit: true + commitMessage: "Release %VERSION%" + commitFiles: ['package.json','bower.json','Gruntfile.coffee','dist/*'] + createTag: true + tagName: "%VERSION%" + tagMessage: "Version %VERSION%" + push: false + pushTo: "origin" + gitDescribeOptions: "--tags --always --abbrev=1 --dirty=-d" + pkg: grunt.file.readJSON("package.json") + pkgFn: -> + grunt.file.readJSON("package.json") #always get latest! + clean: + coffee: ["tmp/output_coffee.js", "tmp"] + dist: ["dist/*", "tmp"] + example: ["example/<%= pkg.name %>.js"] + + mkdir: + all: + options: + mode: 0o0700 + create: ["tmp"] + + coffee: + compile: + files: + "tmp/output_coffee.js": [ + "src/coffee/module.coffee" + "src/coffee/extensions/*.coffee" + "src/coffee/directives/api/utils/*.coffee" + "src/coffee/directives/api/managers/*.coffee" + + "src/coffee/controllers/polyline-display.js" + "src/coffee/utils/LatLngArraySync.coffee" + "src/coffee/utils/MapEvents.coffee" + + "src/coffee/directives/api/models/child/*.coffee" + "src/coffee/directives/api/models/parent/*.coffee" + "src/coffee/directives/api/*.coffee" + "src/coffee/directives/map.coffee" + "src/coffee/directives/marker.coffee" + "src/coffee/directives/markers.coffee" + "src/coffee/directives/label.coffee" + "src/coffee/directives/polygon.coffee" + "src/coffee/directives/circle.coffee" + "src/coffee/directives/polyline*.coffee" + "src/coffee/directives/rectangle.coffee" + "src/coffee/directives/window.coffee" + "src/coffee/directives/windows.coffee" + "src/coffee/directives/layer.coffee"] + + #specs + "tmp/spec/js/bootstrap.js": "spec/coffee/bootstrap.coffee" + "tmp/spec/js/helpers/helpers.js": "spec/coffee/helpers/*.coffee" + "tmp/spec/js/ng-gmap-module.spec.js": "spec/coffee/ng-gmap-module.spec.coffee" + "tmp/spec/js/usage/usage.spec.js": "spec/coffee/usage/*.spec.coffee" + "tmp/spec/js/directives/api/apis.spec.js": "spec/coffee/directives/api/*.spec.coffee" + "tmp/spec/js/directives/api/models/child/children.spec.js": "spec/coffee/directives/api/models/child/*.spec.coffee" + "tmp/spec/js/directives/api/models/parent/parents.spec.js": "spec/coffee/directives/api/models/parent/*.spec.coffee" + "tmp/spec/js/directives/api/utils/utils.spec.js": "spec/coffee/directives/api/utils/*.spec.coffee" + + concat: + options: + banner: "/*! <%= pkg.name %> <%= pkgFn().version %> <%= grunt.template.today(\"yyyy-mm-dd\") %>\n * <%= pkg.description %>\n * <%= pkg.repository.type %>: <%= pkg.repository.url %>\n */\n" + separator: ";" + + dist: + src: ["tmp/output_coffee.js", + "src/js/**/*.js", #this all will only work if the dependency orders do not matter + "src/js/**/**/*.js", + "src/js/**/**/**/*.js", + "lib/*.js"] + dest: "tmp/output.js" + + copy: + dist: + files: [ + src: "tmp/output.js" + dest: "dist/<%= pkg.name %>.js" + ] + + uglify: + options: + banner: "/*! <%= pkg.name %> <%= pkgFn().version %> <%= grunt.template.today(\"yyyy-mm-dd\") %>\n * <%= pkg.description %>\n * <%= pkg.repository.type %>: <%= pkg.repository.url %>\n */\n" + compress: true + report: "gzip" + + dist: + src: "tmp/output.js" + dest: "dist/<%= pkg.name %>.min.js" + + jshint: + all: ["Gruntfile.js", "temp/spec/js/*.js", "temp/spec/js/**/*.js", "temp/spec/js/**/**/*.js", + "src/js/**/*.js", "src/js/**/**/*.js","src/js/**/**/**/*.js" + ] + + test: {} + watch: + all: + options: + livereload: true + + files: [ + "src/coffee/*.coffee", "src/coffee/**/*.coffee", "src/coffee/**/**/*.coffee", + "src/js/*.js", "src/js/**/*.js", "src/js/**/**/*.js", "spec/**/*.spec.coffee", "spec/coffee/helpers/**" + ] + tasks: ["clean:dist", "jshint", "mkdir", "coffee", "concat:dist", "copy:dist", "jasmine:spec", #"uglify" + "clean:example", "coffee"] + spec: + options: + livereload: true + + files: ["src/coffee/**/*.coffee", "src/coffee/*.coffee", "src/coffee/**/**/*.coffee", "spec/**/*.spec.coffee", "spec/coffee/helpers/**"] + tasks: ["clean:dist", "jshint", "mkdir", "coffee", "concat:dist", "copy:dist", "jasmine:spec", "clean:example", "coffee"] + + open: + example: + path: "http://localhost:3100/example/example.html" + + twomaps: + path: "http://localhost:3100/example/two-maps.html" + + geojson: + path: "http://localhost:3100/example/geojson.html" + + hugedata: + path: "http://localhost:3100/example/hugedata.html" + + version: + path: "http://localhost:3100/package.json" + + jasmine: + path: "http://localhost:8069/_SpecRunner.html" + + connect: + server: + options: + hostname: "0.0.0.0" + port: 3100 + base: "" + + jasmineServer: + options: + hostname: "0.0.0.0" + port: 8069 + base: "" + changelog: + options: + dest: "CHANGELOG.md" + + jasmine: + spec: jasmineSettings.spec + + options.jasmine.coverage = jasmineSettings.coverage if jasmineSettings.coverage + grunt.initConfig options + + + # Default task: build a release in dist/ + grunt.registerTask "default", ["clean:dist", "jshint", "mkdir", "coffee", "concat:dist", "copy:dist", + "uglify", "jasmine:spec"] + + # run default "grunt" prior to generate _SpecRunner.html + grunt.registerTask "spec", ["clean:dist", "jshint", "mkdir", "coffee", "concat:dist", "copy:dist", + "connect:jasmineServer", "open:jasmine", "watch:spec"] + + grunt.registerTask "coverage", ["clean:dist", "jshint", "mkdir", "coffee", "concat:dist", "copy:dist", + "uglify", "jasmine:coverage"] + + # Run the example page by creating a local copy of angular-google-maps.js + # and running a webserver on port 3100 with livereload. Web page is opened + # automatically in the default browser. + grunt.registerTask "example", ["clean:example", "connect:server", "open:example", "watch:all"] + grunt.registerTask "twomaps", ["clean:example", "connect:server", "open:twomaps", "watch:all"] + grunt.registerTask "geojson", ["clean:example", "connect:server", "open:geojson", "watch:all"] + grunt.registerTask "hugedata", ["clean:example", "connect:server", "open:hugedata", "watch:all"] + + grunt.registerTask 'bump-@', ['bump-only','default','bump-commit'] + grunt.registerTask 'bump-@-minor', ['bump-only:minor','default','bump-commit'] + grunt.registerTask 'bump-@-major', ['bump-only:major','default','bump-commit'] diff --git a/bower_components/angular-google-maps/README.markdown b/bower_components/angular-google-maps/README.markdown new file mode 100644 index 0000000..d7a483a --- /dev/null +++ b/bower_components/angular-google-maps/README.markdown @@ -0,0 +1,50 @@ +# angular-google-maps + +> AngularJS directives for Google Maps + +[![Dependencies](https://david-dm.org/nlaplante/angular-google-maps.png)](https://david-dm.org/nlaplante/angular-google-maps)  +[![Dependencies](https://david-dm.org/nlaplante/angular-google-maps/dev-status.png)](https://david-dm.org/nlaplante/angular-google-maps)  +[![Build Status](https://travis-ci.org/nlaplante/angular-google-maps.png?branch=r1-dev)](https://travis-ci.org/nlaplante/angular-google-maps) + +## Getting started +This is a directive for AngularJS `~1.0.7+, ~1.2.2+`. + +If you plan to hack on the directives or want to run the example, first thing to do is to install NPM dependencies: + +```shell +npm install +bower install ( for specs and dev dependencies) +``` + +### Building +To build the library after you made changes, simply run grunt: + +```shell +grunt +``` + +If you get errors from `jshint`, just add the `--force` argument. + +### Running the example +To run the example page, just run + +```shell +grunt example +``` + +and open your browser on `http://localhost:3000/example.html`. + +### Documentation +The various directives are documented at [official site](http://angular-google-maps.org). + +### Contributing + +Pull requests more than welcome! If you're adding new features, it would be appreciated if you would provide some docs about the feature. This can be done either by adding a card to our [Trello board](https://trello.com/b/WwTRrkfh/angular-google-maps), forking the website branch and issuing a PR with the updated documentation page, or by opening an issue for us to add the documentation to the site. + +[Branching Model w Git Flow](http://nvie.com/posts/a-successful-git-branching-model/) +We are trying to follow the git flow branching model where all bugs that are considered urgent / patches will be pull +requested against master. If the PR (pull request) is an improvement and a non urgent fix it will go towards develop +which is the working(SNAPSHOT) next version of what master will be. + +When patches and bugs are rolled into master they will be immediatley rolled into develop as well. Where the flow is +PR(bug fix) -> merge master -> merge develop . diff --git a/bower_components/angular-google-maps/bower.json b/bower_components/angular-google-maps/bower.json new file mode 100644 index 0000000..67dec19 --- /dev/null +++ b/bower_components/angular-google-maps/bower.json @@ -0,0 +1,13 @@ +{ + "name": "angular-google-maps", + "version": "1.1.13", + "main": "./dist/angular-google-maps.js", + "dependencies": { + "angular": "1.2.x", + "lodash": "~2.4.1" + }, + "devDependencies": { + "angular-mocks": "1.2.x", + "jquery": "2.1.x" + } +} diff --git a/bower_components/angular-google-maps/dist/angular-google-maps.js b/bower_components/angular-google-maps/dist/angular-google-maps.js new file mode 100644 index 0000000..4ceb3cb --- /dev/null +++ b/bower_components/angular-google-maps/dist/angular-google-maps.js @@ -0,0 +1,8339 @@ +/*! angular-google-maps 1.1.13 2014-07-29 + * AngularJS directives for Google Maps + * git: https://github.com/nlaplante/angular-google-maps.git + */ +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +(function() { + angular.module("google-maps.extensions", []); + + angular.module("google-maps.directives.api.utils", ['google-maps.extensions']); + + angular.module("google-maps.directives.api.managers", []); + + angular.module("google-maps.directives.api.models.child", ["google-maps.directives.api.utils"]); + + angular.module("google-maps.directives.api.models.parent", ["google-maps.directives.api.managers", "google-maps.directives.api.models.child"]); + + angular.module("google-maps.directives.api", ["google-maps.directives.api.models.parent"]); + + angular.module("google-maps", ["google-maps.directives.api"]).factory("debounce", [ + "$timeout", function($timeout) { + return function(fn) { + var nthCall; + nthCall = 0; + return function() { + var argz, later, that; + that = this; + argz = arguments; + nthCall++; + later = (function(version) { + return function() { + if (version === nthCall) { + return fn.apply(that, argz); + } + }; + })(nthCall); + return $timeout(later, 0, true); + }; + }; + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps.extensions").service('ExtendGWin', function() { + return { + init: _.once(function() { + if (!(google || (typeof google !== "undefined" && google !== null ? google.maps : void 0) || (google.maps.InfoWindow != null))) { + return; + } + google.maps.InfoWindow.prototype._open = google.maps.InfoWindow.prototype.open; + google.maps.InfoWindow.prototype._close = google.maps.InfoWindow.prototype.close; + google.maps.InfoWindow.prototype._isOpen = false; + google.maps.InfoWindow.prototype.open = function(map, anchor) { + this._isOpen = true; + this._open(map, anchor); + }; + google.maps.InfoWindow.prototype.close = function() { + this._isOpen = false; + this._close(); + }; + google.maps.InfoWindow.prototype.isOpen = function(val) { + if (val == null) { + val = void 0; + } + if (val == null) { + return this._isOpen; + } else { + return this._isOpen = val; + } + }; + /* + Do the same for InfoBox + TODO: Clean this up so the logic is defined once, wait until develop becomes master as this will be easier + */ + + if (!window.InfoBox) { + return; + } + window.InfoBox.prototype._open = window.InfoBox.prototype.open; + window.InfoBox.prototype._close = window.InfoBox.prototype.close; + window.InfoBox.prototype._isOpen = false; + window.InfoBox.prototype.open = function(map, anchor) { + this._isOpen = true; + this._open(map, anchor); + }; + window.InfoBox.prototype.close = function() { + this._isOpen = false; + this._close(); + }; + window.InfoBox.prototype.isOpen = function(val) { + if (val == null) { + val = void 0; + } + if (val == null) { + return this._isOpen; + } else { + return this._isOpen = val; + } + }; + MarkerLabel_.prototype.setContent = function() { + var content; + content = this.marker_.get("labelContent"); + if (!content || _.isEqual(this.oldContent, content)) { + return; + } + if (typeof (content != null ? content.nodeType : void 0) === "undefined") { + this.labelDiv_.innerHTML = content; + this.eventDiv_.innerHTML = this.labelDiv_.innerHTML; + this.oldContent = content; + } else { + this.labelDiv_.innerHTML = ""; + this.labelDiv_.appendChild(content); + content = content.cloneNode(true); + this.eventDiv_.appendChild(content); + this.oldContent = content; + } + }; + /* + Removes the DIV for the label from the DOM. It also removes all event handlers. + This method is called automatically when the marker's setMap(null) + method is called. + @private + */ + + return MarkerLabel_.prototype.onRemove = function() { + if (this.labelDiv_.parentNode != null) { + this.labelDiv_.parentNode.removeChild(this.labelDiv_); + } + if (this.eventDiv_.parentNode != null) { + this.eventDiv_.parentNode.removeChild(this.eventDiv_); + } + if (!this.listeners_) { + return; + } + if (!this.listeners_.length) { + return; + } + this.listeners_.forEach(function(l) { + return google.maps.event.removeListener(l); + }); + }; + }) + }; + }); + +}).call(this); + +/* + Author Nick McCready + Intersection of Objects if the arrays have something in common each intersecting object will be returned + in an new array. +*/ + + +(function() { + _.intersectionObjects = function(array1, array2, comparison) { + var res, + _this = this; + if (comparison == null) { + comparison = void 0; + } + res = _.map(array1, function(obj1) { + return _.find(array2, function(obj2) { + if (comparison != null) { + return comparison(obj1, obj2); + } else { + return _.isEqual(obj1, obj2); + } + }); + }); + return _.filter(res, function(o) { + return o != null; + }); + }; + + _.containsObject = _.includeObject = function(obj, target, comparison) { + var _this = this; + if (comparison == null) { + comparison = void 0; + } + if (obj === null) { + return false; + } + return _.any(obj, function(value) { + if (comparison != null) { + return comparison(value, target); + } else { + return _.isEqual(value, target); + } + }); + }; + + _.differenceObjects = function(array1, array2, comparison) { + if (comparison == null) { + comparison = void 0; + } + return _.filter(array1, function(value) { + return !_.containsObject(array2, value); + }); + }; + + _.withoutObjects = function(array, array2) { + return _.differenceObjects(array, array2); + }; + + _.indexOfObject = function(array, item, comparison, isSorted) { + var i, length; + if (array == null) { + return -1; + } + i = 0; + length = array.length; + if (isSorted) { + if (typeof isSorted === "number") { + i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return (array[i] === item ? i : -1); + } + } + while (i < length) { + if (comparison != null) { + if (comparison(array[i], item)) { + return i; + } + } else { + if (_.isEqual(array[i], item)) { + return i; + } + } + i++; + } + return -1; + }; + + _["extends"] = function(arrayOfObjectsToCombine) { + return _.reduce(arrayOfObjectsToCombine, function(combined, toAdd) { + return _.extend(combined, toAdd); + }, {}); + }; + +}).call(this); + +/* + Author: Nicholas McCready & jfriend00 + _async handles things asynchronous-like :), to allow the UI to be free'd to do other things + Code taken from http://stackoverflow.com/questions/10344498/best-way-to-iterate-over-an-array-without-blocking-the-ui + + The design of any funcitonality of _async is to be like lodash/underscore and replicate it but call things + asynchronously underneath. Each should be sufficient for most things to be derrived from. + + TODO: Handle Object iteration like underscore and lodash as well.. not that important right now +*/ + + +(function() { + var async; + + async = { + each: function(array, callback, doneCallBack, pausedCallBack, chunk, index, pause) { + var doChunk; + if (chunk == null) { + chunk = 20; + } + if (index == null) { + index = 0; + } + if (pause == null) { + pause = 1; + } + if (!pause) { + throw "pause (delay) must be set from _async!"; + return; + } + if (array === void 0 || (array != null ? array.length : void 0) <= 0) { + doneCallBack(); + return; + } + doChunk = function() { + var cnt, i; + cnt = chunk; + i = index; + while (cnt-- && i < (array ? array.length : i + 1)) { + callback(array[i], i); + ++i; + } + if (array) { + if (i < array.length) { + index = i; + if (pausedCallBack != null) { + pausedCallBack(); + } + return setTimeout(doChunk, pause); + } else { + if (doneCallBack) { + return doneCallBack(); + } + } + } + }; + return doChunk(); + }, + map: function(objs, iterator, doneCallBack, pausedCallBack, chunk) { + var results; + results = []; + if (objs == null) { + return results; + } + return _async.each(objs, function(o) { + return results.push(iterator(o)); + }, function() { + return doneCallBack(results); + }, pausedCallBack, chunk); + } + }; + + window._async = async; + + angular.module("google-maps.directives.api.utils").factory("async", function() { + return window._async; + }); + +}).call(this); + +(function() { + var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + angular.module("google-maps.directives.api.utils").factory("BaseObject", function() { + var BaseObject, baseObjectKeywords; + baseObjectKeywords = ['extended', 'included']; + BaseObject = (function() { + function BaseObject() {} + + BaseObject.extend = function(obj) { + var key, value, _ref; + for (key in obj) { + value = obj[key]; + if (__indexOf.call(baseObjectKeywords, key) < 0) { + this[key] = value; + } + } + if ((_ref = obj.extended) != null) { + _ref.apply(this); + } + return this; + }; + + BaseObject.include = function(obj) { + var key, value, _ref; + for (key in obj) { + value = obj[key]; + if (__indexOf.call(baseObjectKeywords, key) < 0) { + this.prototype[key] = value; + } + } + if ((_ref = obj.included) != null) { + _ref.apply(this); + } + return this; + }; + + return BaseObject; + + })(); + return BaseObject; + }); + +}).call(this); + +/* + Useful function callbacks that should be defined at later time. + Mainly to be used for specs to verify creation / linking. + + This is to lead a common design in notifying child stuff. +*/ + + +(function() { + angular.module("google-maps.directives.api.utils").factory("ChildEvents", function() { + return { + onChildCreation: function(child) {} + }; + }); + +}).call(this); + +(function() { + angular.module("google-maps.directives.api.utils").service('CtrlHandle', [ + '$q', function($q) { + var CtrlHandle; + return CtrlHandle = { + handle: function($scope, $element) { + $scope.deferred = $q.defer(); + return { + getScope: function() { + return $scope; + } + }; + } + }; + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps.directives.api.utils").service("EventsHelper", [ + "Logger", function($log) { + return { + setEvents: function(marker, scope, model) { + if (angular.isDefined(scope.events) && (scope.events != null) && angular.isObject(scope.events)) { + return _.compact(_.map(scope.events, function(eventHandler, eventName) { + if (scope.events.hasOwnProperty(eventName) && angular.isFunction(scope.events[eventName])) { + return google.maps.event.addListener(marker, eventName, function() { + return eventHandler.apply(scope, [marker, eventName, model, arguments]); + }); + } else { + return $log.info("MarkerEventHelper: invalid event listener " + eventName); + } + })); + } + } + }; + } + ]); + +}).call(this); + +(function() { + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.utils").factory("FitHelper", [ + "BaseObject", "Logger", function(BaseObject, $log) { + var FitHelper, _ref; + return FitHelper = (function(_super) { + __extends(FitHelper, _super); + + function FitHelper() { + _ref = FitHelper.__super__.constructor.apply(this, arguments); + return _ref; + } + + FitHelper.prototype.fit = function(gMarkers, gMap) { + var bounds, everSet, + _this = this; + if (gMap && gMarkers && gMarkers.length > 0) { + bounds = new google.maps.LatLngBounds(); + everSet = false; + return _async.each(gMarkers, function(gMarker) { + if (gMarker) { + if (!everSet) { + everSet = true; + } + return bounds.extend(gMarker.getPosition()); + } + }, function() { + if (everSet) { + return gMap.fitBounds(bounds); + } + }); + } + }; + + return FitHelper; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps.directives.api.utils").service("GmapUtil", [ + "Logger", "$compile", function(Logger, $compile) { + var getCoords, validateCoords; + getCoords = function(value) { + if (!value) { + return; + } + if (Array.isArray(value) && value.length === 2) { + return new google.maps.LatLng(value[1], value[0]); + } else if (angular.isDefined(value.type) && value.type === "Point") { + return new google.maps.LatLng(value.coordinates[1], value.coordinates[0]); + } else { + return new google.maps.LatLng(value.latitude, value.longitude); + } + }; + validateCoords = function(coords) { + if (angular.isUndefined(coords)) { + return false; + } + if (_.isArray(coords)) { + if (coords.length === 2) { + return true; + } + } else if ((coords != null) && (coords != null ? coords.type : void 0)) { + if (coords.type === "Point" && _.isArray(coords.coordinates) && coords.coordinates.length === 2) { + return true; + } + } + if (coords && angular.isDefined((coords != null ? coords.latitude : void 0) && angular.isDefined(coords != null ? coords.longitude : void 0))) { + return true; + } + return false; + }; + return { + getLabelPositionPoint: function(anchor) { + var xPos, yPos; + if (anchor === void 0) { + return void 0; + } + anchor = /^([-\d\.]+)\s([-\d\.]+)$/.exec(anchor); + xPos = parseFloat(anchor[1]); + yPos = parseFloat(anchor[2]); + if ((xPos != null) && (yPos != null)) { + return new google.maps.Point(xPos, yPos); + } + }, + createMarkerOptions: function(coords, icon, defaults, map) { + var opts; + if (map == null) { + map = void 0; + } + if (defaults == null) { + defaults = {}; + } + opts = angular.extend({}, defaults, { + position: defaults.position != null ? defaults.position : getCoords(coords), + icon: defaults.icon != null ? defaults.icon : icon, + visible: defaults.visible != null ? defaults.visible : validateCoords(coords) + }); + if (map != null) { + opts.map = map; + } + return opts; + }, + createWindowOptions: function(gMarker, scope, content, defaults) { + if ((content != null) && (defaults != null) && ($compile != null)) { + return angular.extend({}, defaults, { + content: this.buildContent(scope, defaults, content), + position: defaults.position != null ? defaults.position : angular.isObject(gMarker) ? gMarker.getPosition() : getCoords(scope.coords) + }); + } else { + if (!defaults) { + Logger.error("infoWindow defaults not defined"); + if (!content) { + return Logger.error("infoWindow content not defined"); + } + } else { + return defaults; + } + } + }, + buildContent: function(scope, defaults, content) { + var parsed, ret; + if (defaults.content != null) { + ret = defaults.content; + } else { + if ($compile != null) { + parsed = $compile(content)(scope); + if (parsed.length > 0) { + ret = parsed[0]; + } + } else { + ret = content; + } + } + return ret; + }, + defaultDelay: 50, + isTrue: function(val) { + return angular.isDefined(val) && val !== null && val === true || val === "1" || val === "y" || val === "true"; + }, + isFalse: function(value) { + return ['false', 'FALSE', 0, 'n', 'N', 'no', 'NO'].indexOf(value) !== -1; + }, + getCoords: getCoords, + validateCoords: validateCoords, + validatePath: function(path) { + var array, i, polygon, trackMaxVertices; + i = 0; + if (angular.isUndefined(path.type)) { + if (!Array.isArray(path) || path.length < 2) { + return false; + } + while (i < path.length) { + if (!((angular.isDefined(path[i].latitude) && angular.isDefined(path[i].longitude)) || (typeof path[i].lat === "function" && typeof path[i].lng === "function"))) { + return false; + } + i++; + } + return true; + } else { + if (angular.isUndefined(path.coordinates)) { + return false; + } + if (path.type === "Polygon") { + if (path.coordinates[0].length < 4) { + return false; + } + array = path.coordinates[0]; + } else if (path.type === "MultiPolygon") { + trackMaxVertices = { + max: 0, + index: 0 + }; + _.forEach(path.coordinates, function(polygon, index) { + if (polygon[0].length > this.max) { + this.max = polygon[0].length; + return this.index = index; + } + }, trackMaxVertices); + polygon = path.coordinates[trackMaxVertices.index]; + array = polygon[0]; + if (array.length < 4) { + return false; + } + } else if (path.type === "LineString") { + if (path.coordinates.length < 2) { + return false; + } + array = path.coordinates; + } else { + return false; + } + while (i < array.length) { + if (array[i].length !== 2) { + return false; + } + i++; + } + return true; + } + }, + convertPathPoints: function(path) { + var array, i, latlng, result, trackMaxVertices; + i = 0; + result = new google.maps.MVCArray(); + if (angular.isUndefined(path.type)) { + while (i < path.length) { + latlng; + if (angular.isDefined(path[i].latitude) && angular.isDefined(path[i].longitude)) { + latlng = new google.maps.LatLng(path[i].latitude, path[i].longitude); + } else if (typeof path[i].lat === "function" && typeof path[i].lng === "function") { + latlng = path[i]; + } + result.push(latlng); + i++; + } + } else { + array; + if (path.type === "Polygon") { + array = path.coordinates[0]; + } else if (path.type === "MultiPolygon") { + trackMaxVertices = { + max: 0, + index: 0 + }; + _.forEach(path.coordinates, function(polygon, index) { + if (polygon[0].length > this.max) { + this.max = polygon[0].length; + return this.index = index; + } + }, trackMaxVertices); + array = path.coordinates[trackMaxVertices.index][0]; + } else if (path.type === "LineString") { + array = path.coordinates; + } + while (i < array.length) { + result.push(new google.maps.LatLng(array[i][1], array[i][0])); + i++; + } + } + return result; + }, + extendMapBounds: function(map, points) { + var bounds, i; + bounds = new google.maps.LatLngBounds(); + i = 0; + while (i < points.length) { + bounds.extend(points.getAt(i)); + i++; + } + return map.fitBounds(bounds); + } + }; + } + ]); + +}).call(this); + +(function() { + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.utils").factory("Linked", [ + "BaseObject", function(BaseObject) { + var Linked; + Linked = (function(_super) { + __extends(Linked, _super); + + function Linked(scope, element, attrs, ctrls) { + this.scope = scope; + this.element = element; + this.attrs = attrs; + this.ctrls = ctrls; + } + + return Linked; + + })(BaseObject); + return Linked; + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps.directives.api.utils").service("Logger", [ + "$log", function($log) { + return { + logger: $log, + doLog: false, + info: function(msg) { + if (this.doLog) { + if (this.logger != null) { + return this.logger.info(msg); + } else { + return console.info(msg); + } + } + }, + error: function(msg) { + if (this.doLog) { + if (this.logger != null) { + return this.logger.error(msg); + } else { + return console.error(msg); + } + } + }, + warn: function(msg) { + if (this.doLog) { + if (this.logger != null) { + return this.logger.warn(msg); + } else { + return console.warn(msg); + } + } + } + }; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.utils").factory("ModelKey", [ + "BaseObject", function(BaseObject) { + var ModelKey; + return ModelKey = (function(_super) { + __extends(ModelKey, _super); + + function ModelKey(scope) { + this.scope = scope; + this.setIdKey = __bind(this.setIdKey, this); + this.modelKeyComparison = __bind(this.modelKeyComparison, this); + ModelKey.__super__.constructor.call(this); + this.defaultIdKey = "id"; + this.idKey = void 0; + } + + ModelKey.prototype.evalModelHandle = function(model, modelKey) { + if (model === void 0) { + return void 0; + } + if (modelKey === 'self') { + return model; + } else { + return model[modelKey]; + } + }; + + ModelKey.prototype.modelKeyComparison = function(model1, model2) { + var scope; + scope = this.scope.coords != null ? this.scope : this.parentScope; + if (scope == null) { + throw "No scope or parentScope set!"; + } + return this.evalModelHandle(model1, scope.coords).latitude === this.evalModelHandle(model2, scope.coords).latitude && this.evalModelHandle(model1, scope.coords).longitude === this.evalModelHandle(model2, scope.coords).longitude; + }; + + ModelKey.prototype.setIdKey = function(scope) { + return this.idKey = scope.idKey != null ? scope.idKey : this.defaultIdKey; + }; + + return ModelKey; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps.directives.api.utils").factory("ModelsWatcher", [ + "Logger", function(Logger) { + return { + figureOutState: function(idKey, scope, childObjects, comparison, callBack) { + var adds, mappedScopeModelIds, removals, + _this = this; + adds = []; + mappedScopeModelIds = {}; + removals = []; + return _async.each(scope.models, function(m) { + var child; + if (m[idKey] != null) { + mappedScopeModelIds[m[idKey]] = {}; + if (childObjects[m[idKey]] == null) { + return adds.push(m); + } else { + child = childObjects[m[idKey]]; + if (!comparison(m, child.model)) { + adds.push(m); + return removals.push(child); + } + } + } else { + return Logger.error("id missing for model " + (m.toString()) + ", can not use do comparison/insertion"); + } + }, function() { + return _async.each(childObjects.values(), function(c) { + var id; + if (c == null) { + Logger.error("child undefined in ModelsWatcher."); + return; + } + if (c.model == null) { + Logger.error("child.model undefined in ModelsWatcher."); + return; + } + id = c.model[idKey]; + if (mappedScopeModelIds[id] == null) { + return removals.push(c); + } + }, function() { + return callBack({ + adds: adds, + removals: removals + }); + }); + }); + } + }; + } + ]); + +}).call(this); + +/* + Simple Object Map with a lenght property to make it easy to track length/size +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + angular.module("google-maps.directives.api.utils").factory("PropMap", function() { + var PropMap, propsToPop; + propsToPop = ['get', 'put', 'remove', 'values', 'keys', 'length']; + PropMap = (function() { + function PropMap() { + this.keys = __bind(this.keys, this); + this.values = __bind(this.values, this); + this.remove = __bind(this.remove, this); + this.put = __bind(this.put, this); + this.get = __bind(this.get, this); + this.length = 0; + } + + PropMap.prototype.get = function(key) { + return this[key]; + }; + + PropMap.prototype.put = function(key, value) { + if (this[key] == null) { + this.length++; + } + return this[key] = value; + }; + + PropMap.prototype.remove = function(key) { + delete this[key]; + return this.length--; + }; + + PropMap.prototype.values = function() { + var all, keys, + _this = this; + all = []; + keys = _.keys(this); + _.each(keys, function(value) { + if (_.indexOf(propsToPop, value) === -1) { + return all.push(_this[value]); + } + }); + return all; + }; + + PropMap.prototype.keys = function() { + var all, keys, + _this = this; + keys = _.keys(this); + all = []; + _.each(keys, function(prop) { + if (_.indexOf(propsToPop, prop) === -1) { + return all.push(prop); + } + }); + return all; + }; + + return PropMap; + + })(); + return PropMap; + }); + +}).call(this); + +(function() { + angular.module("google-maps.directives.api.utils").factory("nggmap-PropertyAction", [ + "Logger", function(Logger) { + var PropertyAction; + PropertyAction = function(setterFn, isFirstSet) { + var _this = this; + this.setIfChange = function(newVal, oldVal) { + if (!_.isEqual(oldVal, newVal || isFirstSet)) { + return setterFn(newVal); + } + }; + this.sic = function(oldVal, newVal) { + return _this.setIfChange(oldVal, newVal); + }; + return this; + }; + return PropertyAction; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.managers").factory("ClustererMarkerManager", [ + "Logger", "FitHelper", function($log, FitHelper) { + var ClustererMarkerManager; + ClustererMarkerManager = (function(_super) { + __extends(ClustererMarkerManager, _super); + + function ClustererMarkerManager(gMap, opt_markers, opt_options, opt_events) { + var self; + this.opt_events = opt_events; + this.fit = __bind(this.fit, this); + this.destroy = __bind(this.destroy, this); + this.clear = __bind(this.clear, this); + this.draw = __bind(this.draw, this); + this.removeMany = __bind(this.removeMany, this); + this.remove = __bind(this.remove, this); + this.addMany = __bind(this.addMany, this); + this.add = __bind(this.add, this); + ClustererMarkerManager.__super__.constructor.call(this); + self = this; + this.opt_options = opt_options; + if ((opt_options != null) && opt_markers === void 0) { + this.clusterer = new MarkerClusterer(gMap, void 0, opt_options); + } else if ((opt_options != null) && (opt_markers != null)) { + this.clusterer = new MarkerClusterer(gMap, opt_markers, opt_options); + } else { + this.clusterer = new MarkerClusterer(gMap); + } + this.attachEvents(this.opt_events, "opt_events"); + this.clusterer.setIgnoreHidden(true); + this.noDrawOnSingleAddRemoves = true; + $log.info(this); + } + + ClustererMarkerManager.prototype.add = function(gMarker) { + return this.clusterer.addMarker(gMarker, this.noDrawOnSingleAddRemoves); + }; + + ClustererMarkerManager.prototype.addMany = function(gMarkers) { + return this.clusterer.addMarkers(gMarkers); + }; + + ClustererMarkerManager.prototype.remove = function(gMarker) { + return this.clusterer.removeMarker(gMarker, this.noDrawOnSingleAddRemoves); + }; + + ClustererMarkerManager.prototype.removeMany = function(gMarkers) { + return this.clusterer.addMarkers(gMarkers); + }; + + ClustererMarkerManager.prototype.draw = function() { + return this.clusterer.repaint(); + }; + + ClustererMarkerManager.prototype.clear = function() { + this.clusterer.clearMarkers(); + return this.clusterer.repaint(); + }; + + ClustererMarkerManager.prototype.attachEvents = function(options, optionsName) { + var eventHandler, eventName, _results; + if (angular.isDefined(options) && (options != null) && angular.isObject(options)) { + _results = []; + for (eventName in options) { + eventHandler = options[eventName]; + if (options.hasOwnProperty(eventName) && angular.isFunction(options[eventName])) { + $log.info("" + optionsName + ": Attaching event: " + eventName + " to clusterer"); + _results.push(google.maps.event.addListener(this.clusterer, eventName, options[eventName])); + } else { + _results.push(void 0); + } + } + return _results; + } + }; + + ClustererMarkerManager.prototype.clearEvents = function(options) { + var eventHandler, eventName, _results; + if (angular.isDefined(options) && (options != null) && angular.isObject(options)) { + _results = []; + for (eventName in options) { + eventHandler = options[eventName]; + if (options.hasOwnProperty(eventName) && angular.isFunction(options[eventName])) { + $log.info("" + optionsName + ": Clearing event: " + eventName + " to clusterer"); + _results.push(google.maps.event.clearListeners(this.clusterer, eventName)); + } else { + _results.push(void 0); + } + } + return _results; + } + }; + + ClustererMarkerManager.prototype.destroy = function() { + this.clearEvents(this.opt_events); + this.clearEvents(this.opt_internal_events); + return this.clear(); + }; + + ClustererMarkerManager.prototype.fit = function() { + return ClustererMarkerManager.__super__.fit.call(this, this.clusterer.getMarkers(), this.clusterer.getMap()); + }; + + return ClustererMarkerManager; + + })(FitHelper); + return ClustererMarkerManager; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.managers").factory("MarkerManager", [ + "Logger", "FitHelper", function(Logger, FitHelper) { + var MarkerManager; + MarkerManager = (function(_super) { + __extends(MarkerManager, _super); + + MarkerManager.include(FitHelper); + + function MarkerManager(gMap, opt_markers, opt_options) { + this.fit = __bind(this.fit, this); + this.handleOptDraw = __bind(this.handleOptDraw, this); + this.clear = __bind(this.clear, this); + this.draw = __bind(this.draw, this); + this.removeMany = __bind(this.removeMany, this); + this.remove = __bind(this.remove, this); + this.addMany = __bind(this.addMany, this); + this.add = __bind(this.add, this); + var self; + MarkerManager.__super__.constructor.call(this); + self = this; + this.gMap = gMap; + this.gMarkers = []; + this.$log = Logger; + this.$log.info(this); + } + + MarkerManager.prototype.add = function(gMarker, optDraw, redraw) { + if (redraw == null) { + redraw = true; + } + this.handleOptDraw(gMarker, optDraw, redraw); + return this.gMarkers.push(gMarker); + }; + + MarkerManager.prototype.addMany = function(gMarkers) { + var gMarker, _i, _len, _results; + _results = []; + for (_i = 0, _len = gMarkers.length; _i < _len; _i++) { + gMarker = gMarkers[_i]; + _results.push(this.add(gMarker)); + } + return _results; + }; + + MarkerManager.prototype.remove = function(gMarker, optDraw) { + var index, tempIndex; + this.handleOptDraw(gMarker, optDraw, false); + if (!optDraw) { + return; + } + index = void 0; + if (this.gMarkers.indexOf != null) { + index = this.gMarkers.indexOf(gMarker); + } else { + tempIndex = 0; + _.find(this.gMarkers, function(marker) { + tempIndex += 1; + if (marker === gMarker) { + index = tempIndex; + } + }); + } + if (index != null) { + return this.gMarkers.splice(index, 1); + } + }; + + MarkerManager.prototype.removeMany = function(gMarkers) { + var _this = this; + return this.gMarkers.forEach(function(marker) { + return _this.remove(marker); + }); + }; + + MarkerManager.prototype.draw = function() { + var deletes, + _this = this; + deletes = []; + this.gMarkers.forEach(function(gMarker) { + if (!gMarker.isDrawn) { + if (gMarker.doAdd) { + gMarker.setMap(_this.gMap); + return gMarker.isDrawn = true; + } else { + return deletes.push(gMarker); + } + } + }); + return deletes.forEach(function(gMarker) { + gMarker.isDrawn = false; + return _this.remove(gMarker, true); + }); + }; + + MarkerManager.prototype.clear = function() { + var gMarker, _i, _len, _ref; + _ref = this.gMarkers; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + gMarker = _ref[_i]; + gMarker.setMap(null); + } + delete this.gMarkers; + return this.gMarkers = []; + }; + + MarkerManager.prototype.handleOptDraw = function(gMarker, optDraw, doAdd) { + if (optDraw === true) { + if (doAdd) { + gMarker.setMap(this.gMap); + } else { + gMarker.setMap(null); + } + return gMarker.isDrawn = true; + } else { + gMarker.isDrawn = false; + return gMarker.doAdd = doAdd; + } + }; + + MarkerManager.prototype.fit = function() { + return MarkerManager.__super__.fit.call(this, this.gMarkers, this.gMap); + }; + + return MarkerManager; + + })(FitHelper); + return MarkerManager; + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps").factory("array-sync", [ + "add-events", function(mapEvents) { + return function(mapArray, scope, pathEval, pathChangedFn) { + var geojsonArray, geojsonHandlers, geojsonWatcher, isSetFromScope, legacyHandlers, legacyWatcher, mapArrayListener, scopePath, watchListener; + isSetFromScope = false; + scopePath = scope.$eval(pathEval); + if (!scope["static"]) { + legacyHandlers = { + set_at: function(index) { + var value; + if (isSetFromScope) { + return; + } + value = mapArray.getAt(index); + if (!value) { + return; + } + if (!value.lng || !value.lat) { + return scopePath[index] = value; + } else { + scopePath[index].latitude = value.lat(); + return scopePath[index].longitude = value.lng(); + } + }, + insert_at: function(index) { + var value; + if (isSetFromScope) { + return; + } + value = mapArray.getAt(index); + if (!value) { + return; + } + if (!value.lng || !value.lat) { + return scopePath.splice(index, 0, value); + } else { + return scopePath.splice(index, 0, { + latitude: value.lat(), + longitude: value.lng() + }); + } + }, + remove_at: function(index) { + if (isSetFromScope) { + return; + } + return scopePath.splice(index, 1); + } + }; + geojsonArray; + if (scopePath.type === "Polygon") { + geojsonArray = scopePath.coordinates[0]; + } else if (scopePath.type === "LineString") { + geojsonArray = scopePath.coordinates; + } + geojsonHandlers = { + set_at: function(index) { + var value; + if (isSetFromScope) { + return; + } + value = mapArray.getAt(index); + if (!value) { + return; + } + if (!value.lng || !value.lat) { + return; + } + geojsonArray[index][1] = value.lat(); + return geojsonArray[index][0] = value.lng(); + }, + insert_at: function(index) { + var value; + if (isSetFromScope) { + return; + } + value = mapArray.getAt(index); + if (!value) { + return; + } + if (!value.lng || !value.lat) { + return; + } + return geojsonArray.splice(index, 0, [value.lng(), value.lat()]); + }, + remove_at: function(index) { + if (isSetFromScope) { + return; + } + return geojsonArray.splice(index, 1); + } + }; + mapArrayListener = mapEvents(mapArray, angular.isUndefined(scopePath.type) ? legacyHandlers : geojsonHandlers); + } + legacyWatcher = function(newPath) { + var i, l, newLength, newValue, oldArray, oldLength, oldValue; + isSetFromScope = true; + oldArray = mapArray; + if (newPath) { + i = 0; + oldLength = oldArray.getLength(); + newLength = newPath.length; + l = Math.min(oldLength, newLength); + newValue = void 0; + while (i < l) { + oldValue = oldArray.getAt(i); + newValue = newPath[i]; + if (typeof newValue.equals === "function") { + if (!newValue.equals(oldValue)) { + oldArray.setAt(i, newValue); + } + } else { + if ((oldValue.lat() !== newValue.latitude) || (oldValue.lng() !== newValue.longitude)) { + oldArray.setAt(i, new google.maps.LatLng(newValue.latitude, newValue.longitude)); + } + } + i++; + } + while (i < newLength) { + newValue = newPath[i]; + if (typeof newValue.lat === "function" && typeof newValue.lng === "function") { + oldArray.push(newValue); + } else { + oldArray.push(new google.maps.LatLng(newValue.latitude, newValue.longitude)); + } + i++; + } + while (i < oldLength) { + oldArray.pop(); + i++; + } + } + return isSetFromScope = false; + }; + geojsonWatcher = function(newPath) { + var array, i, l, newLength, newValue, oldArray, oldLength, oldValue; + isSetFromScope = true; + oldArray = mapArray; + if (newPath) { + array; + if (scopePath.type === "Polygon") { + array = newPath.coordinates[0]; + } else if (scopePath.type === "LineString") { + array = newPath.coordinates; + } + i = 0; + oldLength = oldArray.getLength(); + newLength = array.length; + l = Math.min(oldLength, newLength); + newValue = void 0; + while (i < l) { + oldValue = oldArray.getAt(i); + newValue = array[i]; + if ((oldValue.lat() !== newValue[1]) || (oldValue.lng() !== newValue[0])) { + oldArray.setAt(i, new google.maps.LatLng(newValue[1], newValue[0])); + } + i++; + } + while (i < newLength) { + newValue = array[i]; + oldArray.push(new google.maps.LatLng(newValue[1], newValue[0])); + i++; + } + while (i < oldLength) { + oldArray.pop(); + i++; + } + } + return isSetFromScope = false; + }; + watchListener; + if (!scope["static"]) { + if (angular.isUndefined(scopePath.type)) { + watchListener = scope.$watchCollection(pathEval, legacyWatcher); + } else { + watchListener = scope.$watch(pathEval, geojsonWatcher, true); + } + } + return function() { + if (mapArrayListener) { + mapArrayListener(); + mapArrayListener = null; + } + if (watchListener) { + watchListener(); + return watchListener = null; + } + }; + }; + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps").factory("add-events", [ + "$timeout", function($timeout) { + var addEvent, addEvents; + addEvent = function(target, eventName, handler) { + return google.maps.event.addListener(target, eventName, function() { + handler.apply(this, arguments); + return $timeout((function() {}), true); + }); + }; + addEvents = function(target, eventName, handler) { + var remove; + if (handler) { + return addEvent(target, eventName, handler); + } + remove = []; + angular.forEach(eventName, function(_handler, key) { + return remove.push(addEvent(target, key, _handler)); + }); + return function() { + angular.forEach(remove, function(listener) { + return google.maps.event.removeListener(listener); + }); + return remove = null; + }; + }; + return addEvents; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.child").factory("MarkerLabelChildModel", [ + "BaseObject", "GmapUtil", function(BaseObject, GmapUtil) { + var MarkerLabelChildModel; + MarkerLabelChildModel = (function(_super) { + __extends(MarkerLabelChildModel, _super); + + MarkerLabelChildModel.include(GmapUtil); + + function MarkerLabelChildModel(gMarker, options, map) { + this.destroy = __bind(this.destroy, this); + this.setOptions = __bind(this.setOptions, this); + var self; + MarkerLabelChildModel.__super__.constructor.call(this); + self = this; + this.gMarker = gMarker; + this.setOptions(options); + this.gMarkerLabel = new MarkerLabel_(this.gMarker, options.crossImage, options.handCursor); + this.gMarker.set("setMap", function(theMap) { + self.gMarkerLabel.setMap(theMap); + return google.maps.Marker.prototype.setMap.apply(this, arguments); + }); + this.gMarker.setMap(map); + } + + MarkerLabelChildModel.prototype.setOption = function(optStr, content) { + /* + COMENTED CODE SHOWS AWFUL CHROME BUG in Google Maps SDK 3, still happens in version 3.16 + any animation will cause markers to disappear + */ + + return this.gMarker.set(optStr, content); + }; + + MarkerLabelChildModel.prototype.setOptions = function(options) { + var _ref, _ref1; + if (options != null ? options.labelContent : void 0) { + this.gMarker.set("labelContent", options.labelContent); + } + if (options != null ? options.labelAnchor : void 0) { + this.gMarker.set("labelAnchor", this.getLabelPositionPoint(options.labelAnchor)); + } + this.gMarker.set("labelClass", options.labelClass || 'labels'); + this.gMarker.set("labelStyle", options.labelStyle || { + opacity: 100 + }); + this.gMarker.set("labelInBackground", options.labelInBackground || false); + if (!options.labelVisible) { + this.gMarker.set("labelVisible", true); + } + if (!options.raiseOnDrag) { + this.gMarker.set("raiseOnDrag", true); + } + if (!options.clickable) { + this.gMarker.set("clickable", true); + } + if (!options.draggable) { + this.gMarker.set("draggable", false); + } + if (!options.optimized) { + this.gMarker.set("optimized", false); + } + options.crossImage = (_ref = options.crossImage) != null ? _ref : document.location.protocol + "//maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"; + return options.handCursor = (_ref1 = options.handCursor) != null ? _ref1 : document.location.protocol + "//maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"; + }; + + MarkerLabelChildModel.prototype.destroy = function() { + if ((this.gMarkerLabel.labelDiv_.parentNode != null) && (this.gMarkerLabel.eventDiv_.parentNode != null)) { + return this.gMarkerLabel.onRemove(); + } + }; + + return MarkerLabelChildModel; + + })(BaseObject); + return MarkerLabelChildModel; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.child").factory("MarkerChildModel", [ + "ModelKey", "GmapUtil", "Logger", "$injector", "EventsHelper", function(ModelKey, GmapUtil, Logger, $injector, EventsHelper) { + var MarkerChildModel; + MarkerChildModel = (function(_super) { + __extends(MarkerChildModel, _super); + + MarkerChildModel.include(GmapUtil); + + MarkerChildModel.include(EventsHelper); + + function MarkerChildModel(model, parentScope, gMap, $timeout, defaults, doClick, gMarkerManager, idKey) { + var self, + _this = this; + this.model = model; + this.parentScope = parentScope; + this.gMap = gMap; + this.$timeout = $timeout; + this.defaults = defaults; + this.doClick = doClick; + this.gMarkerManager = gMarkerManager; + this.idKey = idKey; + this.watchDestroy = __bind(this.watchDestroy, this); + this.setLabelOptions = __bind(this.setLabelOptions, this); + this.isLabelDefined = __bind(this.isLabelDefined, this); + this.setOptions = __bind(this.setOptions, this); + this.setIcon = __bind(this.setIcon, this); + this.setCoords = __bind(this.setCoords, this); + this.destroy = __bind(this.destroy, this); + this.maybeSetScopeValue = __bind(this.maybeSetScopeValue, this); + this.createMarker = __bind(this.createMarker, this); + this.setMyScope = __bind(this.setMyScope, this); + self = this; + if (this.model[this.idKey]) { + this.id = this.model[this.idKey]; + } + this.iconKey = this.parentScope.icon; + this.coordsKey = this.parentScope.coords; + this.clickKey = this.parentScope.click(); + this.labelContentKey = this.parentScope.labelContent; + this.optionsKey = this.parentScope.options; + this.labelOptionsKey = this.parentScope.labelOptions; + MarkerChildModel.__super__.constructor.call(this, this.parentScope.$new(false)); + this.scope.model = this.model; + this.setMyScope(this.model, void 0, true); + this.createMarker(this.model); + this.scope.$watch('model', function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.setMyScope(newValue, oldValue); + } + }, true); + this.$log = Logger; + this.$log.info(self); + this.watchDestroy(this.scope); + } + + MarkerChildModel.prototype.setMyScope = function(model, oldModel, isInit) { + var _this = this; + if (oldModel == null) { + oldModel = void 0; + } + if (isInit == null) { + isInit = false; + } + this.maybeSetScopeValue('icon', model, oldModel, this.iconKey, this.evalModelHandle, isInit, this.setIcon); + this.maybeSetScopeValue('coords', model, oldModel, this.coordsKey, this.evalModelHandle, isInit, this.setCoords); + this.maybeSetScopeValue('labelContent', model, oldModel, this.labelContentKey, this.evalModelHandle, isInit); + if (_.isFunction(this.clickKey) && $injector) { + return this.scope.click = function() { + return $injector.invoke(_this.clickKey, void 0, { + "$markerModel": model + }); + }; + } else { + this.maybeSetScopeValue('click', model, oldModel, this.clickKey, this.evalModelHandle, isInit); + return this.createMarker(model, oldModel, isInit); + } + }; + + MarkerChildModel.prototype.createMarker = function(model, oldModel, isInit) { + var _this = this; + if (oldModel == null) { + oldModel = void 0; + } + if (isInit == null) { + isInit = false; + } + return this.maybeSetScopeValue('options', model, oldModel, this.optionsKey, function(lModel, lModelKey) { + var value; + if (lModel === void 0) { + return void 0; + } + value = lModelKey === 'self' ? lModel : lModel[lModelKey]; + if (value === void 0) { + return value = lModelKey === void 0 ? _this.defaults : _this.scope.options; + } else { + return value; + } + }, isInit, this.setOptions); + }; + + MarkerChildModel.prototype.maybeSetScopeValue = function(scopePropName, model, oldModel, modelKey, evaluate, isInit, gSetter) { + var newValue, oldVal; + if (gSetter == null) { + gSetter = void 0; + } + if (oldModel === void 0) { + this.scope[scopePropName] = evaluate(model, modelKey); + if (!isInit) { + if (gSetter != null) { + gSetter(this.scope); + } + } + return; + } + oldVal = evaluate(oldModel, modelKey); + newValue = evaluate(model, modelKey); + if (newValue !== oldVal && this.scope[scopePropName] !== newValue) { + this.scope[scopePropName] = newValue; + if (!isInit) { + if (gSetter != null) { + gSetter(this.scope); + } + return this.gMarkerManager.draw(); + } + } + }; + + MarkerChildModel.prototype.destroy = function() { + return this.scope.$destroy(); + }; + + MarkerChildModel.prototype.setCoords = function(scope) { + if (scope.$id !== this.scope.$id || this.gMarker === void 0) { + return; + } + if ((scope.coords != null)) { + if (!this.validateCoords(this.scope.coords)) { + this.$log.error("MarkerChildMarker cannot render marker as scope.coords as no position on marker: " + (JSON.stringify(this.model))); + return; + } + this.gMarker.setPosition(this.getCoords(scope.coords)); + this.gMarker.setVisible(this.validateCoords(scope.coords)); + this.gMarkerManager.remove(this.gMarker); + return this.gMarkerManager.add(this.gMarker); + } else { + return this.gMarkerManager.remove(this.gMarker); + } + }; + + MarkerChildModel.prototype.setIcon = function(scope) { + if (scope.$id !== this.scope.$id || this.gMarker === void 0) { + return; + } + this.gMarkerManager.remove(this.gMarker); + this.gMarker.setIcon(scope.icon); + this.gMarkerManager.add(this.gMarker); + this.gMarker.setPosition(this.getCoords(scope.coords)); + return this.gMarker.setVisible(this.validateCoords(scope.coords)); + }; + + MarkerChildModel.prototype.setOptions = function(scope) { + var _ref, + _this = this; + if (scope.$id !== this.scope.$id) { + return; + } + if (this.gMarker != null) { + this.gMarkerManager.remove(this.gMarker); + delete this.gMarker; + } + if (!((_ref = scope.coords) != null ? _ref : typeof scope.icon === "function" ? scope.icon(scope.options != null) : void 0)) { + return; + } + this.opts = this.createMarkerOptions(scope.coords, scope.icon, scope.options); + delete this.gMarker; + if (this.isLabelDefined(scope)) { + this.gMarker = new MarkerWithLabel(this.setLabelOptions(this.opts, scope)); + } else { + this.gMarker = new google.maps.Marker(this.opts); + } + this.setEvents(this.gMarker, this.parentScope, this.model); + if (this.id) { + this.gMarker.key = this.id; + } + this.gMarkerManager.add(this.gMarker); + return google.maps.event.addListener(this.gMarker, 'click', function() { + if (_this.doClick && (_this.scope.click != null)) { + return _this.scope.click(); + } + }); + }; + + MarkerChildModel.prototype.isLabelDefined = function(scope) { + return scope.labelContent != null; + }; + + MarkerChildModel.prototype.setLabelOptions = function(opts, scope) { + opts.labelAnchor = this.getLabelPositionPoint(scope.labelAnchor); + opts.labelClass = scope.labelClass; + opts.labelContent = scope.labelContent; + return opts; + }; + + MarkerChildModel.prototype.watchDestroy = function(scope) { + var _this = this; + return scope.$on("$destroy", function() { + var self, _ref; + if (_this.gMarker != null) { + google.maps.event.clearListeners(_this.gMarker, 'click'); + if (((_ref = _this.parentScope) != null ? _ref.events : void 0) && _.isArray(_this.parentScope.events)) { + _this.parentScope.events.forEach(function(event, eventName) { + return google.maps.event.clearListeners(this.gMarker, eventName); + }); + } + _this.gMarkerManager.remove(_this.gMarker, true); + delete _this.gMarker; + } + return self = void 0; + }); + }; + + return MarkerChildModel; + + })(ModelKey); + return MarkerChildModel; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("PolylineChildModel", [ + "BaseObject", "Logger", "$timeout", "array-sync", "GmapUtil", function(BaseObject, Logger, $timeout, arraySync, GmapUtil) { + var $log, PolylineChildModel; + $log = Logger; + return PolylineChildModel = (function(_super) { + __extends(PolylineChildModel, _super); + + PolylineChildModel.include(GmapUtil); + + function PolylineChildModel(scope, attrs, map, defaults, model) { + var arraySyncer, pathPoints, + _this = this; + this.scope = scope; + this.attrs = attrs; + this.map = map; + this.defaults = defaults; + this.model = model; + this.buildOpts = __bind(this.buildOpts, this); + pathPoints = this.convertPathPoints(scope.path); + this.polyline = new google.maps.Polyline(this.buildOpts(pathPoints)); + if (scope.fit) { + GmapUtil.extendMapBounds(map, pathPoints); + } + if (!scope["static"] && angular.isDefined(scope.editable)) { + scope.$watch("editable", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setEditable(newValue); + } + }); + } + if (angular.isDefined(scope.draggable)) { + scope.$watch("draggable", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setDraggable(newValue); + } + }); + } + if (angular.isDefined(scope.visible)) { + scope.$watch("visible", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setVisible(newValue); + } + }); + } + if (angular.isDefined(scope.geodesic)) { + scope.$watch("geodesic", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setOptions(_this.buildOpts(_this.polyline.getPath())); + } + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.weight)) { + scope.$watch("stroke.weight", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setOptions(_this.buildOpts(_this.polyline.getPath())); + } + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.color)) { + scope.$watch("stroke.color", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setOptions(_this.buildOpts(_this.polyline.getPath())); + } + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.opacity)) { + scope.$watch("stroke.opacity", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setOptions(_this.buildOpts(_this.polyline.getPath())); + } + }); + } + if (angular.isDefined(scope.icons)) { + scope.$watch("icons", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setOptions(_this.buildOpts(_this.polyline.getPath())); + } + }); + } + arraySyncer = arraySync(this.polyline.getPath(), scope, "path", function(pathPoints) { + if (scope.fit) { + return GmapUtil.extendMapBounds(map, pathPoints); + } + }); + scope.$on("$destroy", function() { + _this.polyline.setMap(null); + _this.polyline = null; + _this.scope = null; + if (arraySyncer) { + arraySyncer(); + return arraySyncer = null; + } + }); + $log.info(this); + } + + PolylineChildModel.prototype.buildOpts = function(pathPoints) { + var opts, + _this = this; + opts = angular.extend({}, this.defaults, { + map: this.map, + path: pathPoints, + icons: this.scope.icons, + strokeColor: this.scope.stroke && this.scope.stroke.color, + strokeOpacity: this.scope.stroke && this.scope.stroke.opacity, + strokeWeight: this.scope.stroke && this.scope.stroke.weight + }); + angular.forEach({ + clickable: true, + draggable: false, + editable: false, + geodesic: false, + visible: true, + "static": false, + fit: false + }, function(defaultValue, key) { + if (angular.isUndefined(_this.scope[key]) || _this.scope[key] === null) { + return opts[key] = defaultValue; + } else { + return opts[key] = _this.scope[key]; + } + }); + if (opts["static"]) { + opts.editable = false; + } + return opts; + }; + + PolylineChildModel.prototype.destroy = function() { + return this.scope.$destroy(); + }; + + return PolylineChildModel; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.child").factory("WindowChildModel", [ + "BaseObject", "GmapUtil", "Logger", "$compile", "$http", "$templateCache", function(BaseObject, GmapUtil, Logger, $compile, $http, $templateCache) { + var WindowChildModel; + WindowChildModel = (function(_super) { + __extends(WindowChildModel, _super); + + WindowChildModel.include(GmapUtil); + + function WindowChildModel(model, scope, opts, isIconVisibleOnClick, mapCtrl, markerCtrl, element, needToManualDestroy, markerIsVisibleAfterWindowClose) { + this.model = model; + this.scope = scope; + this.opts = opts; + this.isIconVisibleOnClick = isIconVisibleOnClick; + this.mapCtrl = mapCtrl; + this.markerCtrl = markerCtrl; + this.element = element; + this.needToManualDestroy = needToManualDestroy != null ? needToManualDestroy : false; + this.markerIsVisibleAfterWindowClose = markerIsVisibleAfterWindowClose != null ? markerIsVisibleAfterWindowClose : true; + this.destroy = __bind(this.destroy, this); + this.remove = __bind(this.remove, this); + this.hideWindow = __bind(this.hideWindow, this); + this.getLatestPosition = __bind(this.getLatestPosition, this); + this.showWindow = __bind(this.showWindow, this); + this.handleClick = __bind(this.handleClick, this); + this.watchCoords = __bind(this.watchCoords, this); + this.watchShow = __bind(this.watchShow, this); + this.createGWin = __bind(this.createGWin, this); + this.watchElement = __bind(this.watchElement, this); + this.googleMapsHandles = []; + this.$log = Logger; + this.createGWin(); + if (this.markerCtrl != null) { + this.markerCtrl.setClickable(true); + } + this.handleClick(); + this.watchElement(); + this.watchShow(); + this.watchCoords(); + this.$log.info(this); + } + + WindowChildModel.prototype.watchElement = function() { + var _this = this; + return this.scope.$watch(function() { + var _ref; + if (!_this.element || !_this.html) { + return; + } + if (_this.html !== _this.element.html()) { + if (_this.gWin) { + if ((_ref = _this.opts) != null) { + _ref.content = void 0; + } + _this.remove(); + _this.createGWin(); + return _this.showHide(); + } + } + }); + }; + + WindowChildModel.prototype.createGWin = function() { + var defaults, + _this = this; + if (this.gWin == null) { + defaults = {}; + if (this.opts != null) { + if (this.scope.coords) { + this.opts.position = this.getCoords(this.scope.coords); + } + defaults = this.opts; + } + if (this.element) { + this.html = _.isObject(this.element) ? this.element.html() : this.element; + } + this.opts = this.createWindowOptions(this.markerCtrl, this.scope, this.html, defaults); + } + if ((this.opts != null) && !this.gWin) { + if (this.opts.boxClass && (window.InfoBox && typeof window.InfoBox === 'function')) { + this.gWin = new window.InfoBox(this.opts); + } else { + this.gWin = new google.maps.InfoWindow(this.opts); + } + return this.googleMapsHandles.push(google.maps.event.addListener(this.gWin, 'closeclick', function() { + if (_this.markerCtrl) { + _this.markerCtrl.setAnimation(_this.oldMarkerAnimation); + if (_this.markerIsVisibleAfterWindowClose) { + _.delay(function() { + _this.markerCtrl.setVisible(false); + return _this.markerCtrl.setVisible(_this.markerIsVisibleAfterWindowClose); + }, 250); + } + } + _this.gWin.isOpen(false); + if (_this.scope.closeClick != null) { + return _this.scope.closeClick(); + } + })); + } + }; + + WindowChildModel.prototype.watchShow = function() { + var _this = this; + return this.scope.$watch('show', function(newValue, oldValue) { + if (newValue !== oldValue) { + if (newValue) { + return _this.showWindow(); + } else { + return _this.hideWindow(); + } + } else { + if (_this.gWin != null) { + if (newValue && !_this.gWin.getMap()) { + return _this.showWindow(); + } + } + } + }, true); + }; + + WindowChildModel.prototype.watchCoords = function() { + var scope, + _this = this; + scope = this.markerCtrl != null ? this.scope.$parent : this.scope; + return scope.$watch('coords', function(newValue, oldValue) { + var pos; + if (newValue !== oldValue) { + if (newValue == null) { + return _this.hideWindow(); + } else { + if (!_this.validateCoords(newValue)) { + _this.$log.error("WindowChildMarker cannot render marker as scope.coords as no position on marker: " + (JSON.stringify(_this.model))); + return; + } + pos = _this.getCoords(newValue); + _this.gWin.setPosition(pos); + if (_this.opts) { + return _this.opts.position = pos; + } + } + } + }, true); + }; + + WindowChildModel.prototype.handleClick = function(forceClick) { + var click, + _this = this; + click = function() { + var pos; + if (_this.gWin == null) { + _this.createGWin(); + } + pos = _this.markerCtrl.getPosition(); + if (_this.gWin != null) { + _this.gWin.setPosition(pos); + if (_this.opts) { + _this.opts.position = pos; + } + _this.showWindow(); + } + _this.initialMarkerVisibility = _this.markerCtrl.getVisible(); + _this.oldMarkerAnimation = _this.markerCtrl.getAnimation(); + return _this.markerCtrl.setVisible(_this.isIconVisibleOnClick); + }; + if (this.markerCtrl != null) { + if (forceClick) { + click(); + } + return this.googleMapsHandles.push(google.maps.event.addListener(this.markerCtrl, 'click', click)); + } + }; + + WindowChildModel.prototype.showWindow = function() { + var show, + _this = this; + show = function() { + if (_this.gWin) { + if ((_this.scope.show || (_this.scope.show == null)) && !_this.gWin.isOpen()) { + return _this.gWin.open(_this.mapCtrl); + } + } + }; + if (this.scope.templateUrl) { + if (this.gWin) { + $http.get(this.scope.templateUrl, { + cache: $templateCache + }).then(function(content) { + var compiled, templateScope; + templateScope = _this.scope.$new(); + if (angular.isDefined(_this.scope.templateParameter)) { + templateScope.parameter = _this.scope.templateParameter; + } + compiled = $compile(content.data)(templateScope); + return _this.gWin.setContent(compiled[0]); + }); + } + return show(); + } else { + return show(); + } + }; + + WindowChildModel.prototype.showHide = function() { + if (this.scope.show) { + return this.showWindow(); + } else { + return this.hideWindow(); + } + }; + + WindowChildModel.prototype.getLatestPosition = function(overridePos) { + if ((this.gWin != null) && (this.markerCtrl != null) && !overridePos) { + return this.gWin.setPosition(this.markerCtrl.getPosition()); + } else { + if (overridePos) { + return this.gWin.setPosition(overridePos); + } + } + }; + + WindowChildModel.prototype.hideWindow = function() { + if ((this.gWin != null) && this.gWin.isOpen()) { + return this.gWin.close(); + } + }; + + WindowChildModel.prototype.remove = function() { + this.hideWindow(); + _.each(this.googleMapsHandles, function(h) { + return google.maps.event.removeListener(h); + }); + this.googleMapsHandles.length = 0; + return delete this.gWin; + }; + + WindowChildModel.prototype.destroy = function(manualOverride) { + var self; + if (manualOverride == null) { + manualOverride = false; + } + this.remove(); + if ((this.scope != null) && (this.needToManualDestroy || manualOverride)) { + this.scope.$destroy(); + } + return self = void 0; + }; + + return WindowChildModel; + + })(BaseObject); + return WindowChildModel; + } + ]); + +}).call(this); + +/* + - interface for all markers to derrive from + - to enforce a minimum set of requirements + - attributes + - coords + - icon + - implementation needed on watches +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("IMarkerParentModel", [ + "ModelKey", "Logger", function(ModelKey, Logger) { + var IMarkerParentModel; + IMarkerParentModel = (function(_super) { + __extends(IMarkerParentModel, _super); + + IMarkerParentModel.prototype.DEFAULTS = {}; + + function IMarkerParentModel(scope, element, attrs, map, $timeout) { + var self, + _this = this; + this.scope = scope; + this.element = element; + this.attrs = attrs; + this.map = map; + this.$timeout = $timeout; + this.onDestroy = __bind(this.onDestroy, this); + this.onWatch = __bind(this.onWatch, this); + this.watch = __bind(this.watch, this); + this.validateScope = __bind(this.validateScope, this); + IMarkerParentModel.__super__.constructor.call(this, this.scope); + self = this; + this.$log = Logger; + if (!this.validateScope(scope)) { + throw new String("Unable to construct IMarkerParentModel due to invalid scope"); + } + this.doClick = angular.isDefined(attrs.click); + if (scope.options != null) { + this.DEFAULTS = scope.options; + } + this.watch('coords', this.scope); + this.watch('icon', this.scope); + this.watch('options', this.scope); + scope.$on("$destroy", function() { + return _this.onDestroy(scope); + }); + } + + IMarkerParentModel.prototype.validateScope = function(scope) { + var ret; + if (scope == null) { + this.$log.error(this.constructor.name + ": invalid scope used"); + return false; + } + ret = scope.coords != null; + if (!ret) { + this.$log.error(this.constructor.name + ": no valid coords attribute found"); + return false; + } + return ret; + }; + + IMarkerParentModel.prototype.watch = function(propNameToWatch, scope) { + var _this = this; + return scope.$watch(propNameToWatch, function(newValue, oldValue) { + if (!_.isEqual(newValue, oldValue)) { + return _this.onWatch(propNameToWatch, scope, newValue, oldValue); + } + }, true); + }; + + IMarkerParentModel.prototype.onWatch = function(propNameToWatch, scope, newValue, oldValue) { + throw new String("OnWatch Not Implemented!!"); + }; + + IMarkerParentModel.prototype.onDestroy = function(scope) { + throw new String("OnDestroy Not Implemented!!"); + }; + + return IMarkerParentModel; + + })(ModelKey); + return IMarkerParentModel; + } + ]); + +}).call(this); + +/* + - interface directive for all window(s) to derrive from +*/ + + +(function() { + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("IWindowParentModel", [ + "ModelKey", "GmapUtil", "Logger", function(ModelKey, GmapUtil, Logger) { + var IWindowParentModel; + IWindowParentModel = (function(_super) { + __extends(IWindowParentModel, _super); + + IWindowParentModel.include(GmapUtil); + + IWindowParentModel.prototype.DEFAULTS = {}; + + function IWindowParentModel(scope, element, attrs, ctrls, $timeout, $compile, $http, $templateCache) { + var self; + IWindowParentModel.__super__.constructor.call(this, scope); + self = this; + this.$log = Logger; + this.$timeout = $timeout; + this.$compile = $compile; + this.$http = $http; + this.$templateCache = $templateCache; + if (scope.options != null) { + this.DEFAULTS = scope.options; + } + } + + return IWindowParentModel; + + })(ModelKey); + return IWindowParentModel; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("LayerParentModel", [ + "BaseObject", "Logger", '$timeout', function(BaseObject, Logger, $timeout) { + var LayerParentModel; + LayerParentModel = (function(_super) { + __extends(LayerParentModel, _super); + + function LayerParentModel(scope, element, attrs, gMap, onLayerCreated, $log) { + var _this = this; + this.scope = scope; + this.element = element; + this.attrs = attrs; + this.gMap = gMap; + this.onLayerCreated = onLayerCreated != null ? onLayerCreated : void 0; + this.$log = $log != null ? $log : Logger; + this.createGoogleLayer = __bind(this.createGoogleLayer, this); + if (this.attrs.type == null) { + this.$log.info("type attribute for the layer directive is mandatory. Layer creation aborted!!"); + return; + } + this.createGoogleLayer(); + this.doShow = true; + if (angular.isDefined(this.attrs.show)) { + this.doShow = this.scope.show; + } + if (this.doShow && (this.gMap != null)) { + this.layer.setMap(this.gMap); + } + this.scope.$watch("show", function(newValue, oldValue) { + if (newValue !== oldValue) { + _this.doShow = newValue; + if (newValue) { + return _this.layer.setMap(_this.gMap); + } else { + return _this.layer.setMap(null); + } + } + }, true); + this.scope.$watch("options", function(newValue, oldValue) { + if (newValue !== oldValue) { + _this.layer.setMap(null); + _this.layer = null; + return _this.createGoogleLayer(); + } + }, true); + this.scope.$on("$destroy", function() { + return _this.layer.setMap(null); + }); + } + + LayerParentModel.prototype.createGoogleLayer = function() { + var fn; + if (this.attrs.options == null) { + this.layer = this.attrs.namespace === void 0 ? new google.maps[this.attrs.type]() : new google.maps[this.attrs.namespace][this.attrs.type](); + } else { + this.layer = this.attrs.namespace === void 0 ? new google.maps[this.attrs.type](this.scope.options) : new google.maps[this.attrs.namespace][this.attrs.type](this.scope.options); + } + if ((this.layer != null) && (this.onLayerCreated != null)) { + fn = this.onLayerCreated(this.scope, this.layer); + if (fn) { + return fn(this.layer); + } + } + }; + + return LayerParentModel; + + })(BaseObject); + return LayerParentModel; + } + ]); + +}).call(this); + +/* + Basic Directive api for a marker. Basic in the sense that this directive contains 1:1 on scope and model. + Thus there will be one html element per marker within the directive. +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("MarkerParentModel", [ + "IMarkerParentModel", "GmapUtil", "EventsHelper", function(IMarkerParentModel, GmapUtil, EventsHelper) { + var MarkerParentModel; + MarkerParentModel = (function(_super) { + __extends(MarkerParentModel, _super); + + MarkerParentModel.include(GmapUtil); + + MarkerParentModel.include(EventsHelper); + + function MarkerParentModel(scope, element, attrs, map, $timeout, gMarkerManager, doFit) { + var opts, self, + _this = this; + this.gMarkerManager = gMarkerManager; + this.doFit = doFit; + this.onDestroy = __bind(this.onDestroy, this); + this.setGMarker = __bind(this.setGMarker, this); + this.onWatch = __bind(this.onWatch, this); + MarkerParentModel.__super__.constructor.call(this, scope, element, attrs, map, $timeout); + self = this; + opts = this.createMarkerOptions(scope.coords, scope.icon, scope.options, this.map); + this.setGMarker(new google.maps.Marker(opts)); + this.listener = google.maps.event.addListener(this.scope.gMarker, 'click', function() { + if (_this.doClick && (scope.click != null)) { + return _this.$timeout(function() { + return _this.scope.click(); + }); + } + }); + this.setEvents(this.scope.gMarker, scope, scope); + this.$log.info(this); + } + + MarkerParentModel.prototype.onWatch = function(propNameToWatch, scope) { + var old, pos, _ref; + switch (propNameToWatch) { + case 'coords': + if (this.validateCoords(scope.coords) && (this.scope.gMarker != null)) { + pos = (_ref = this.scope.gMarker) != null ? _ref.getPosition() : void 0; + if (pos.lat() === this.scope.coords.latitude && this.scope.coords.longitude === pos.lng()) { + return; + } + old = this.scope.gMarker.getAnimation(); + this.scope.gMarker.setMap(this.map); + this.scope.gMarker.setPosition(this.getCoords(scope.coords)); + this.scope.gMarker.setVisible(this.validateCoords(scope.coords)); + return this.scope.gMarker.setAnimation(old); + } else { + return this.scope.gMarker.setMap(null); + } + break; + case 'icon': + if ((scope.icon != null) && this.validateCoords(scope.coords) && (this.scope.gMarker != null)) { + this.scope.gMarker.setIcon(scope.icon); + this.scope.gMarker.setMap(null); + this.scope.gMarker.setMap(this.map); + this.scope.gMarker.setPosition(this.getCoords(scope.coords)); + return this.scope.gMarker.setVisible(this.validateCoords(scope.coords)); + } + break; + case 'options': + if (this.validateCoords(scope.coords) && (scope.icon != null) && scope.options) { + if (this.scope.gMarker != null) { + this.onDestroy(scope); + return this.onTimeOut(scope); + } + } + } + }; + + MarkerParentModel.prototype.setGMarker = function(gMarker) { + if (this.scope.gMarker) { + delete this.scope.gMarker; + this.gMarkerManager.remove(this.scope.gMarker, false); + } + this.scope.gMarker = gMarker; + if (this.scope.gMarker) { + this.gMarkerManager.add(this.scope.gMarker, false); + if (this.doFit) { + return this.gMarkerManager.fit(); + } + } + }; + + MarkerParentModel.prototype.onDestroy = function(scope) { + var self; + if (!this.scope.gMarker) { + self = void 0; + return; + } + this.scope.gMarker.setMap(null); + google.maps.event.removeListener(this.listener); + this.listener = null; + this.gMarkerManager.remove(this.scope.gMarker, false); + delete this.scope.gMarker; + return self = void 0; + }; + + return MarkerParentModel; + + })(IMarkerParentModel); + return MarkerParentModel; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("MarkersParentModel", [ + "IMarkerParentModel", "ModelsWatcher", "PropMap", "MarkerChildModel", "ClustererMarkerManager", "MarkerManager", function(IMarkerParentModel, ModelsWatcher, PropMap, MarkerChildModel, ClustererMarkerManager, MarkerManager) { + var MarkersParentModel; + MarkersParentModel = (function(_super) { + __extends(MarkersParentModel, _super); + + MarkersParentModel.include(ModelsWatcher); + + function MarkersParentModel(scope, element, attrs, map, $timeout) { + this.onDestroy = __bind(this.onDestroy, this); + this.newChildMarker = __bind(this.newChildMarker, this); + this.pieceMealMarkers = __bind(this.pieceMealMarkers, this); + this.reBuildMarkers = __bind(this.reBuildMarkers, this); + this.createMarkersFromScratch = __bind(this.createMarkersFromScratch, this); + this.validateScope = __bind(this.validateScope, this); + this.onWatch = __bind(this.onWatch, this); + var self, + _this = this; + MarkersParentModel.__super__.constructor.call(this, scope, element, attrs, map, $timeout); + self = this; + this.scope.markerModels = new PropMap(); + this.$timeout = $timeout; + this.$log.info(this); + this.doRebuildAll = this.scope.doRebuildAll != null ? this.scope.doRebuildAll : false; + this.setIdKey(scope); + this.scope.$watch('doRebuildAll', function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.doRebuildAll = newValue; + } + }); + this.watch('models', scope); + this.watch('doCluster', scope); + this.watch('clusterOptions', scope); + this.watch('clusterEvents', scope); + this.watch('fit', scope); + this.watch('idKey', scope); + this.gMarkerManager = void 0; + this.createMarkersFromScratch(scope); + } + + MarkersParentModel.prototype.onWatch = function(propNameToWatch, scope, newValue, oldValue) { + if (propNameToWatch === "idKey" && newValue !== oldValue) { + this.idKey = newValue; + } + if (this.doRebuildAll) { + return this.reBuildMarkers(scope); + } else { + return this.pieceMealMarkers(scope); + } + }; + + MarkersParentModel.prototype.validateScope = function(scope) { + var modelsNotDefined; + modelsNotDefined = angular.isUndefined(scope.models) || scope.models === void 0; + if (modelsNotDefined) { + this.$log.error(this.constructor.name + ": no valid models attribute found"); + } + return MarkersParentModel.__super__.validateScope.call(this, scope) || modelsNotDefined; + }; + + MarkersParentModel.prototype.createMarkersFromScratch = function(scope) { + var _this = this; + if (scope.doCluster) { + if (scope.clusterEvents) { + this.clusterInternalOptions = _.once(function() { + var self, _ref, _ref1, _ref2; + self = _this; + if (!_this.origClusterEvents) { + _this.origClusterEvents = { + click: (_ref = scope.clusterEvents) != null ? _ref.click : void 0, + mouseout: (_ref1 = scope.clusterEvents) != null ? _ref1.mouseout : void 0, + mouseover: (_ref2 = scope.clusterEvents) != null ? _ref2.mouseover : void 0 + }; + return _.extend(scope.clusterEvents, { + click: function(cluster) { + return self.maybeExecMappedEvent(cluster, 'click'); + }, + mouseout: function(cluster) { + return self.maybeExecMappedEvent(cluster, 'mouseout'); + }, + mouseover: function(cluster) { + return self.maybeExecMappedEvent(cluster, 'mouseover'); + } + }); + } + })(); + } + if (scope.clusterOptions || scope.clusterEvents) { + if (this.gMarkerManager === void 0) { + this.gMarkerManager = new ClustererMarkerManager(this.map, void 0, scope.clusterOptions, this.clusterInternalOptions); + } else { + if (this.gMarkerManager.opt_options !== scope.clusterOptions) { + this.gMarkerManager = new ClustererMarkerManager(this.map, void 0, scope.clusterOptions, this.clusterInternalOptions); + } + } + } else { + this.gMarkerManager = new ClustererMarkerManager(this.map); + } + } else { + this.gMarkerManager = new MarkerManager(this.map); + } + return _async.each(scope.models, function(model) { + return _this.newChildMarker(model, scope); + }, function() { + _this.gMarkerManager.draw(); + if (scope.fit) { + return _this.gMarkerManager.fit(); + } + }); + }; + + MarkersParentModel.prototype.reBuildMarkers = function(scope) { + if (!scope.doRebuild && scope.doRebuild !== void 0) { + return; + } + this.onDestroy(scope); + return this.createMarkersFromScratch(scope); + }; + + MarkersParentModel.prototype.pieceMealMarkers = function(scope) { + var _this = this; + if ((this.scope.models != null) && this.scope.models.length > 0 && this.scope.markerModels.length > 0) { + return this.figureOutState(this.idKey, scope, this.scope.markerModels, this.modelKeyComparison, function(state) { + var payload; + payload = state; + return _async.each(payload.removals, function(child) { + if (child != null) { + if (child.destroy != null) { + child.destroy(); + } + return _this.scope.markerModels.remove(child.id); + } + }, function() { + return _async.each(payload.adds, function(modelToAdd) { + return _this.newChildMarker(modelToAdd, scope); + }, function() { + if (payload.adds.length > 0 || payload.removals.length > 0) { + _this.gMarkerManager.draw(); + return scope.markerModels = _this.scope.markerModels; + } + }); + }); + }); + } else { + return this.reBuildMarkers(scope); + } + }; + + MarkersParentModel.prototype.newChildMarker = function(model, scope) { + var child; + if (model[this.idKey] == null) { + this.$log.error("Marker model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key."); + return; + } + this.$log.info('child', child, 'markers', this.scope.markerModels); + child = new MarkerChildModel(model, scope, this.map, this.$timeout, this.DEFAULTS, this.doClick, this.gMarkerManager, this.idKey); + this.scope.markerModels.put(model[this.idKey], child); + return child; + }; + + MarkersParentModel.prototype.onDestroy = function(scope) { + _.each(this.scope.markerModels.values(), function(model) { + if (model != null) { + return model.destroy(); + } + }); + delete this.scope.markerModels; + this.scope.markerModels = new PropMap(); + if (this.gMarkerManager != null) { + return this.gMarkerManager.clear(); + } + }; + + MarkersParentModel.prototype.maybeExecMappedEvent = function(cluster, fnName) { + var pair, _ref; + if (_.isFunction((_ref = this.scope.clusterEvents) != null ? _ref[fnName] : void 0)) { + pair = this.mapClusterToMarkerModels(cluster); + if (this.origClusterEvents[fnName]) { + return this.origClusterEvents[fnName](pair.cluster, pair.mapped); + } + } + }; + + MarkersParentModel.prototype.mapClusterToMarkerModels = function(cluster) { + var gMarkers, mapped, + _this = this; + gMarkers = cluster.getMarkers(); + mapped = gMarkers.map(function(g) { + return _this.scope.markerModels[g.key].model; + }); + return { + cluster: cluster, + mapped: mapped + }; + }; + + return MarkersParentModel; + + })(IMarkerParentModel); + return MarkersParentModel; + } + ]); + +}).call(this); + +/* + Windows directive where many windows map to the models property +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("PolylinesParentModel", [ + "$timeout", "Logger", "ModelKey", "ModelsWatcher", "PropMap", "PolylineChildModel", function($timeout, Logger, ModelKey, ModelsWatcher, PropMap, PolylineChildModel) { + var PolylinesParentModel; + return PolylinesParentModel = (function(_super) { + __extends(PolylinesParentModel, _super); + + PolylinesParentModel.include(ModelsWatcher); + + function PolylinesParentModel(scope, element, attrs, gMap, defaults) { + var self, + _this = this; + this.scope = scope; + this.element = element; + this.attrs = attrs; + this.gMap = gMap; + this.defaults = defaults; + this.modelKeyComparison = __bind(this.modelKeyComparison, this); + this.setChildScope = __bind(this.setChildScope, this); + this.createChild = __bind(this.createChild, this); + this.pieceMeal = __bind(this.pieceMeal, this); + this.createAllNew = __bind(this.createAllNew, this); + this.watchIdKey = __bind(this.watchIdKey, this); + this.createChildScopes = __bind(this.createChildScopes, this); + this.watchOurScope = __bind(this.watchOurScope, this); + this.watchDestroy = __bind(this.watchDestroy, this); + this.rebuildAll = __bind(this.rebuildAll, this); + this.doINeedToWipe = __bind(this.doINeedToWipe, this); + this.watchModels = __bind(this.watchModels, this); + this.watch = __bind(this.watch, this); + PolylinesParentModel.__super__.constructor.call(this, scope); + self = this; + this.$log = Logger; + this.plurals = new PropMap(); + this.scopePropNames = ['path', 'stroke', 'clickable', 'draggable', 'editable', 'geodesic', 'icons', 'visible']; + _.each(this.scopePropNames, function(name) { + return _this[name + 'Key'] = void 0; + }); + this.models = void 0; + this.firstTime = true; + this.$log.info(this); + this.watchOurScope(scope); + this.createChildScopes(); + } + + PolylinesParentModel.prototype.watch = function(scope, name, nameKey) { + var _this = this; + return scope.$watch(name, function(newValue, oldValue) { + if (newValue !== oldValue) { + _this[nameKey] = typeof newValue === 'function' ? newValue() : newValue; + return _async.each(_.values(_this.plurals), function(model) { + return model.scope[name] = _this[nameKey] === 'self' ? model : model[_this[nameKey]]; + }, function() {}); + } + }); + }; + + PolylinesParentModel.prototype.watchModels = function(scope) { + var _this = this; + return scope.$watch('models', function(newValue, oldValue) { + if (!_.isEqual(newValue, oldValue)) { + if (_this.doINeedToWipe(newValue)) { + return _this.rebuildAll(scope, true, true); + } else { + return _this.createChildScopes(false); + } + } + }, true); + }; + + PolylinesParentModel.prototype.doINeedToWipe = function(newValue) { + var newValueIsEmpty; + newValueIsEmpty = newValue != null ? newValue.length === 0 : true; + return this.plurals.length > 0 && newValueIsEmpty; + }; + + PolylinesParentModel.prototype.rebuildAll = function(scope, doCreate, doDelete) { + var _this = this; + return _async.each(this.plurals.values(), function(model) { + return model.destroy(); + }, function() { + if (doDelete) { + delete _this.plurals; + } + _this.plurals = new PropMap(); + if (doCreate) { + return _this.createChildScopes(); + } + }); + }; + + PolylinesParentModel.prototype.watchDestroy = function(scope) { + var _this = this; + return scope.$on("$destroy", function() { + return _this.rebuildAll(scope, false, true); + }); + }; + + PolylinesParentModel.prototype.watchOurScope = function(scope) { + var _this = this; + return _.each(this.scopePropNames, function(name) { + var nameKey; + nameKey = name + 'Key'; + _this[nameKey] = typeof scope[name] === 'function' ? scope[name]() : scope[name]; + return _this.watch(scope, name, nameKey); + }); + }; + + PolylinesParentModel.prototype.createChildScopes = function(isCreatingFromScratch) { + if (isCreatingFromScratch == null) { + isCreatingFromScratch = true; + } + if (angular.isUndefined(this.scope.models)) { + this.$log.error("No models to create polylines from! I Need direct models!"); + return; + } + if (this.gMap != null) { + if (this.scope.models != null) { + this.watchIdKey(this.scope); + if (isCreatingFromScratch) { + return this.createAllNew(this.scope, false); + } else { + return this.pieceMeal(this.scope, false); + } + } + } + }; + + PolylinesParentModel.prototype.watchIdKey = function(scope) { + var _this = this; + this.setIdKey(scope); + return scope.$watch('idKey', function(newValue, oldValue) { + if (newValue !== oldValue && (newValue == null)) { + _this.idKey = newValue; + return _this.rebuildAll(scope, true, true); + } + }); + }; + + PolylinesParentModel.prototype.createAllNew = function(scope, isArray) { + var _this = this; + if (isArray == null) { + isArray = false; + } + this.models = scope.models; + if (this.firstTime) { + this.watchModels(scope); + this.watchDestroy(scope); + } + return _async.each(scope.models, function(model) { + return _this.createChild(model, _this.gMap); + }, function() { + return _this.firstTime = false; + }); + }; + + PolylinesParentModel.prototype.pieceMeal = function(scope, isArray) { + var _this = this; + if (isArray == null) { + isArray = true; + } + this.models = scope.models; + if ((scope != null) && (scope.models != null) && scope.models.length > 0 && this.plurals.length > 0) { + return this.figureOutState(this.idKey, scope, this.plurals, this.modelKeyComparison, function(state) { + var payload; + payload = state; + return _async.each(payload.removals, function(id) { + var child; + child = _this.plurals[id]; + if (child != null) { + child.destroy(); + return _this.plurals.remove(id); + } + }, function() { + return _async.each(payload.adds, function(modelToAdd) { + return _this.createChild(modelToAdd, _this.gMap); + }, function() {}); + }); + }); + } else { + return this.rebuildAll(this.scope, true, true); + } + }; + + PolylinesParentModel.prototype.createChild = function(model, gMap) { + var child, childScope, + _this = this; + childScope = this.scope.$new(false); + this.setChildScope(childScope, model); + childScope.$watch('model', function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.setChildScope(childScope, newValue); + } + }, true); + childScope["static"] = this.scope["static"]; + child = new PolylineChildModel(childScope, this.attrs, gMap, this.defaults, model); + if (model[this.idKey] == null) { + this.$log.error("Polyline model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key."); + return; + } + this.plurals.put(model[this.idKey], child); + return child; + }; + + PolylinesParentModel.prototype.setChildScope = function(childScope, model) { + var _this = this; + _.each(this.scopePropNames, function(name) { + var nameKey, newValue; + nameKey = name + 'Key'; + newValue = _this[nameKey] === 'self' ? model : model[_this[nameKey]]; + if (newValue !== childScope[name]) { + return childScope[name] = newValue; + } + }); + return childScope.model = model; + }; + + PolylinesParentModel.prototype.modelKeyComparison = function(model1, model2) { + return _.isEqual(this.evalModelHandle(model1, this.scope.path), this.evalModelHandle(model2, this.scope.path)); + }; + + return PolylinesParentModel; + + })(ModelKey); + } + ]); + +}).call(this); + +/* + Windows directive where many windows map to the models property +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("WindowsParentModel", [ + "IWindowParentModel", "ModelsWatcher", "PropMap", "WindowChildModel", "Linked", function(IWindowParentModel, ModelsWatcher, PropMap, WindowChildModel, Linked) { + var WindowsParentModel; + WindowsParentModel = (function(_super) { + __extends(WindowsParentModel, _super); + + WindowsParentModel.include(ModelsWatcher); + + function WindowsParentModel(scope, element, attrs, ctrls, $timeout, $compile, $http, $templateCache, $interpolate) { + var mapScope, self, + _this = this; + this.$interpolate = $interpolate; + this.interpolateContent = __bind(this.interpolateContent, this); + this.setChildScope = __bind(this.setChildScope, this); + this.createWindow = __bind(this.createWindow, this); + this.setContentKeys = __bind(this.setContentKeys, this); + this.pieceMealWindows = __bind(this.pieceMealWindows, this); + this.createAllNewWindows = __bind(this.createAllNewWindows, this); + this.watchIdKey = __bind(this.watchIdKey, this); + this.createChildScopesWindows = __bind(this.createChildScopesWindows, this); + this.watchOurScope = __bind(this.watchOurScope, this); + this.watchDestroy = __bind(this.watchDestroy, this); + this.rebuildAll = __bind(this.rebuildAll, this); + this.doINeedToWipe = __bind(this.doINeedToWipe, this); + this.watchModels = __bind(this.watchModels, this); + this.watch = __bind(this.watch, this); + this.go = __bind(this.go, this); + WindowsParentModel.__super__.constructor.call(this, scope, element, attrs, ctrls, $timeout, $compile, $http, $templateCache); + self = this; + this.windows = new PropMap(); + this.scopePropNames = ['show', 'coords', 'templateUrl', 'templateParameter', 'isIconVisibleOnClick', 'closeClick']; + _.each(this.scopePropNames, function(name) { + return _this[name + 'Key'] = void 0; + }); + this.linked = new Linked(scope, element, attrs, ctrls); + this.models = void 0; + this.contentKeys = void 0; + this.isIconVisibleOnClick = void 0; + this.firstTime = true; + this.$log.info(self); + this.parentScope = void 0; + mapScope = ctrls[0].getScope(); + mapScope.deferred.promise.then(function(map) { + var markerCtrl; + _this.gMap = map; + markerCtrl = ctrls.length > 1 && (ctrls[1] != null) ? ctrls[1] : void 0; + if (!markerCtrl) { + _this.go(scope); + return; + } + return markerCtrl.getScope().deferred.promise.then(function() { + _this.markerScope = markerCtrl.getScope(); + return _this.go(scope); + }); + }); + } + + WindowsParentModel.prototype.go = function(scope) { + var _this = this; + this.watchOurScope(scope); + this.doRebuildAll = this.scope.doRebuildAll != null ? this.scope.doRebuildAll : false; + scope.$watch('doRebuildAll', function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.doRebuildAll = newValue; + } + }); + return this.createChildScopesWindows(); + }; + + WindowsParentModel.prototype.watch = function(scope, name, nameKey) { + var _this = this; + return scope.$watch(name, function(newValue, oldValue) { + if (newValue !== oldValue) { + _this[nameKey] = typeof newValue === 'function' ? newValue() : newValue; + return _async.each(_.values(_this.windows), function(model) { + return model.scope[name] = _this[nameKey] === 'self' ? model : model[_this[nameKey]]; + }, function() {}); + } + }); + }; + + WindowsParentModel.prototype.watchModels = function(scope) { + var _this = this; + return scope.$watch('models', function(newValue, oldValue) { + if (!_.isEqual(newValue, oldValue)) { + if (_this.doRebuildAll || _this.doINeedToWipe(newValue)) { + return _this.rebuildAll(scope, true, true); + } else { + return _this.createChildScopesWindows(false); + } + } + }); + }; + + WindowsParentModel.prototype.doINeedToWipe = function(newValue) { + var newValueIsEmpty; + newValueIsEmpty = newValue != null ? newValue.length === 0 : true; + return this.windows.length > 0 && newValueIsEmpty; + }; + + WindowsParentModel.prototype.rebuildAll = function(scope, doCreate, doDelete) { + var _this = this; + return _async.each(this.windows.values(), function(model) { + return model.destroy(); + }, function() { + if (doDelete) { + delete _this.windows; + } + _this.windows = new PropMap(); + if (doCreate) { + return _this.createChildScopesWindows(); + } + }); + }; + + WindowsParentModel.prototype.watchDestroy = function(scope) { + var _this = this; + return scope.$on("$destroy", function() { + return _this.rebuildAll(scope, false, true); + }); + }; + + WindowsParentModel.prototype.watchOurScope = function(scope) { + var _this = this; + return _.each(this.scopePropNames, function(name) { + var nameKey; + nameKey = name + 'Key'; + _this[nameKey] = typeof scope[name] === 'function' ? scope[name]() : scope[name]; + return _this.watch(scope, name, nameKey); + }); + }; + + WindowsParentModel.prototype.createChildScopesWindows = function(isCreatingFromScratch) { + var markersScope, modelsNotDefined; + if (isCreatingFromScratch == null) { + isCreatingFromScratch = true; + } + /* + being that we cannot tell the difference in Key String vs. a normal value string (TemplateUrl) + we will assume that all scope values are string expressions either pointing to a key (propName) or using + 'self' to point the model as container/object of interest. + + This may force redundant information into the model, but this appears to be the most flexible approach. + */ + + this.isIconVisibleOnClick = true; + if (angular.isDefined(this.linked.attrs.isiconvisibleonclick)) { + this.isIconVisibleOnClick = this.linked.scope.isIconVisibleOnClick; + } + markersScope = this.markerScope; + modelsNotDefined = angular.isUndefined(this.linked.scope.models); + if (modelsNotDefined && (markersScope === void 0 || (markersScope.markerModels === void 0 || markersScope.models === void 0))) { + this.$log.error("No models to create windows from! Need direct models or models derrived from markers!"); + return; + } + if (this.gMap != null) { + if (this.linked.scope.models != null) { + this.watchIdKey(this.linked.scope); + if (isCreatingFromScratch) { + return this.createAllNewWindows(this.linked.scope, false); + } else { + return this.pieceMealWindows(this.linked.scope, false); + } + } else { + this.parentScope = markersScope; + this.watchIdKey(this.parentScope); + if (isCreatingFromScratch) { + return this.createAllNewWindows(markersScope, true, 'markerModels', false); + } else { + return this.pieceMealWindows(markersScope, true, 'markerModels', false); + } + } + } + }; + + WindowsParentModel.prototype.watchIdKey = function(scope) { + var _this = this; + this.setIdKey(scope); + return scope.$watch('idKey', function(newValue, oldValue) { + if (newValue !== oldValue && (newValue == null)) { + _this.idKey = newValue; + return _this.rebuildAll(scope, true, true); + } + }); + }; + + WindowsParentModel.prototype.createAllNewWindows = function(scope, hasGMarker, modelsPropToIterate, isArray) { + var _this = this; + if (modelsPropToIterate == null) { + modelsPropToIterate = 'models'; + } + if (isArray == null) { + isArray = false; + } + this.models = scope.models; + if (this.firstTime) { + this.watchModels(scope); + this.watchDestroy(scope); + } + this.setContentKeys(scope.models); + return _async.each(scope.models, function(model) { + var gMarker; + gMarker = hasGMarker ? scope[modelsPropToIterate][[model[_this.idKey]]].gMarker : void 0; + return _this.createWindow(model, gMarker, _this.gMap); + }, function() { + return _this.firstTime = false; + }); + }; + + WindowsParentModel.prototype.pieceMealWindows = function(scope, hasGMarker, modelsPropToIterate, isArray) { + var _this = this; + if (modelsPropToIterate == null) { + modelsPropToIterate = 'models'; + } + if (isArray == null) { + isArray = true; + } + this.models = scope.models; + if ((scope != null) && (scope.models != null) && scope.models.length > 0 && this.windows.length > 0) { + return this.figureOutState(this.idKey, scope, this.windows, this.modelKeyComparison, function(state) { + var payload; + payload = state; + return _async.each(payload.removals, function(child) { + if (child != null) { + if (child.destroy != null) { + child.destroy(); + } + return _this.windows.remove(child.id); + } + }, function() { + return _async.each(payload.adds, function(modelToAdd) { + var gMarker; + gMarker = scope[modelsPropToIterate][modelToAdd[_this.idKey]].gMarker; + return _this.createWindow(modelToAdd, gMarker, _this.gMap); + }, function() {}); + }); + }); + } else { + return this.rebuildAll(this.scope, true, true); + } + }; + + WindowsParentModel.prototype.setContentKeys = function(models) { + if (models.length > 0) { + return this.contentKeys = Object.keys(models[0]); + } + }; + + WindowsParentModel.prototype.createWindow = function(model, gMarker, gMap) { + var child, childScope, fakeElement, opts, + _this = this; + childScope = this.linked.scope.$new(false); + this.setChildScope(childScope, model); + childScope.$watch('model', function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.setChildScope(childScope, newValue); + } + }, true); + fakeElement = { + html: function() { + return _this.interpolateContent(_this.linked.element.html(), model); + } + }; + opts = this.createWindowOptions(gMarker, childScope, fakeElement.html(), this.DEFAULTS); + child = new WindowChildModel(model, childScope, opts, this.isIconVisibleOnClick, gMap, gMarker, fakeElement, true, true); + if (model[this.idKey] == null) { + this.$log.error("Window model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key."); + return; + } + this.windows.put(model[this.idKey], child); + return child; + }; + + WindowsParentModel.prototype.setChildScope = function(childScope, model) { + var _this = this; + _.each(this.scopePropNames, function(name) { + var nameKey, newValue; + nameKey = name + 'Key'; + newValue = _this[nameKey] === 'self' ? model : model[_this[nameKey]]; + if (newValue !== childScope[name]) { + return childScope[name] = newValue; + } + }); + return childScope.model = model; + }; + + WindowsParentModel.prototype.interpolateContent = function(content, model) { + var exp, interpModel, key, _i, _len, _ref; + if (this.contentKeys === void 0 || this.contentKeys.length === 0) { + return; + } + exp = this.$interpolate(content); + interpModel = {}; + _ref = this.contentKeys; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + interpModel[key] = model[key]; + } + return exp(interpModel); + }; + + return WindowsParentModel; + + })(IWindowParentModel); + return WindowsParentModel; + } + ]); + +}).call(this); + +/* + - interface for all labels to derrive from + - to enforce a minimum set of requirements + - attributes + - content + - anchor + - implementation needed on watches +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("ILabel", [ + "BaseObject", "Logger", function(BaseObject, Logger) { + var ILabel; + return ILabel = (function(_super) { + __extends(ILabel, _super); + + function ILabel($timeout) { + this.link = __bind(this.link, this); + var self; + self = this; + this.restrict = 'ECMA'; + this.replace = true; + this.template = void 0; + this.require = void 0; + this.transclude = true; + this.priority = -100; + this.scope = { + labelContent: '=content', + labelAnchor: '@anchor', + labelClass: '@class', + labelStyle: '=style' + }; + this.$log = Logger; + this.$timeout = $timeout; + } + + ILabel.prototype.link = function(scope, element, attrs, ctrl) { + throw new Exception("Not Implemented!!"); + }; + + return ILabel; + + })(BaseObject); + } + ]); + +}).call(this); + +/* + - interface for all markers to derrive from + - to enforce a minimum set of requirements + - attributes + - coords + - icon + - implementation needed on watches +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("IMarker", [ + "Logger", "BaseObject", "$q", function(Logger, BaseObject, $q) { + var IMarker; + return IMarker = (function(_super) { + __extends(IMarker, _super); + + function IMarker($timeout) { + this.link = __bind(this.link, this); + var self; + self = this; + this.$log = Logger; + this.$timeout = $timeout; + this.restrict = 'ECMA'; + this.require = '^googleMap'; + this.priority = -1; + this.transclude = true; + this.replace = true; + this.scope = { + coords: '=coords', + icon: '=icon', + click: '&click', + options: '=options', + events: '=events', + fit: '=fit' + }; + } + + IMarker.prototype.link = function(scope, element, attrs, ctrl) { + if (!ctrl) { + throw new Error("No Map Control! Marker Directive Must be inside the map!"); + } + }; + + IMarker.prototype.mapPromise = function(scope, ctrl) { + var mapScope; + mapScope = ctrl.getScope(); + mapScope.deferred.promise.then(function(map) { + return scope.map = map; + }); + return mapScope.deferred.promise; + }; + + return IMarker; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("IPolyline", [ + "GmapUtil", "BaseObject", "Logger", function(GmapUtil, BaseObject, Logger) { + var IPolyline; + return IPolyline = (function(_super) { + __extends(IPolyline, _super); + + IPolyline.include(GmapUtil); + + function IPolyline() { + var self; + self = this; + } + + IPolyline.prototype.restrict = "ECA"; + + IPolyline.prototype.replace = true; + + IPolyline.prototype.require = "^googleMap"; + + IPolyline.prototype.scope = { + path: "=", + stroke: "=", + clickable: "=", + draggable: "=", + editable: "=", + geodesic: "=", + icons: "=", + visible: "=", + "static": "=", + fit: "=" + }; + + IPolyline.prototype.DEFAULTS = {}; + + IPolyline.prototype.$log = Logger; + + return IPolyline; + + })(BaseObject); + } + ]); + +}).call(this); + +/* + - interface directive for all window(s) to derrive from +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("IWindow", [ + "BaseObject", "ChildEvents", "Logger", function(BaseObject, ChildEvents, Logger) { + var IWindow; + return IWindow = (function(_super) { + __extends(IWindow, _super); + + IWindow.include(ChildEvents); + + function IWindow($timeout, $compile, $http, $templateCache) { + var self; + this.$timeout = $timeout; + this.$compile = $compile; + this.$http = $http; + this.$templateCache = $templateCache; + this.link = __bind(this.link, this); + self = this; + this.restrict = 'ECMA'; + this.template = void 0; + this.transclude = true; + this.priority = -100; + this.require = void 0; + this.replace = true; + this.scope = { + coords: '=coords', + show: '=show', + templateUrl: '=templateurl', + templateParameter: '=templateparameter', + isIconVisibleOnClick: '=isiconvisibleonclick', + closeClick: '&closeclick', + options: '=options' + }; + this.$log = Logger; + } + + IWindow.prototype.link = function(scope, element, attrs, ctrls) { + throw new Exception("Not Implemented!!"); + }; + + return IWindow; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Map", [ + "$timeout", '$q', 'Logger', "GmapUtil", "BaseObject", "ExtendGWin", "CtrlHandle", function($timeout, $q, Logger, GmapUtil, BaseObject, ExtendGWin, CtrlHandle) { + "use strict"; + var $log, DEFAULTS, Map; + $log = Logger; + DEFAULTS = { + mapTypeId: google.maps.MapTypeId.ROADMAP + }; + return Map = (function(_super) { + __extends(Map, _super); + + Map.include(GmapUtil); + + function Map() { + this.link = __bind(this.link, this); + var self; + self = this; + } + + Map.prototype.restrict = "ECMA"; + + Map.prototype.transclude = true; + + Map.prototype.replace = false; + + Map.prototype.template = "
        "; + + Map.prototype.scope = { + center: "=center", + zoom: "=zoom", + dragging: "=dragging", + control: "=", + windows: "=windows", + options: "=options", + events: "=events", + styles: "=styles", + bounds: "=bounds" + }; + + Map.prototype.controller = [ + "$scope", function($scope) { + var ctrlObj; + ctrlObj = CtrlHandle.handle($scope); + $scope.ctrlType = 'Map'; + $scope.deferred.promise.then(function() { + return ExtendGWin.init(); + }); + return _.extend(ctrlObj, { + getMap: function() { + return $scope.map; + } + }); + } + ]; + + /* + @param scope + @param element + @param attrs + */ + + + Map.prototype.link = function(scope, element, attrs) { + var dragging, el, eventName, getEventHandler, opts, settingCenterFromScope, type, _m, + _this = this; + if (!this.validateCoords(scope.center)) { + $log.error("angular-google-maps: could not find a valid center property"); + return; + } + if (!angular.isDefined(scope.zoom)) { + $log.error("angular-google-maps: map zoom property not set"); + return; + } + el = angular.element(element); + el.addClass("angular-google-map"); + opts = { + options: {} + }; + if (attrs.options) { + opts.options = scope.options; + } + if (attrs.styles) { + opts.styles = scope.styles; + } + if (attrs.type) { + type = attrs.type.toUpperCase(); + if (google.maps.MapTypeId.hasOwnProperty(type)) { + opts.mapTypeId = google.maps.MapTypeId[attrs.type.toUpperCase()]; + } else { + $log.error("angular-google-maps: invalid map type \"" + attrs.type + "\""); + } + } + _m = new google.maps.Map(el.find("div")[1], angular.extend({}, DEFAULTS, opts, { + center: this.getCoords(scope.center), + draggable: this.isTrue(attrs.draggable), + zoom: scope.zoom, + bounds: scope.bounds + })); + dragging = false; + if (!_m) { + google.maps.event.addListener(_m, 'tilesloaded ', function(map) { + return scope.deferred.resolve(map); + }); + } else { + scope.deferred.resolve(_m); + } + google.maps.event.addListener(_m, "dragstart", function() { + dragging = true; + return _.defer(function() { + return scope.$apply(function(s) { + if (s.dragging != null) { + return s.dragging = dragging; + } + }); + }); + }); + google.maps.event.addListener(_m, "dragend", function() { + dragging = false; + return _.defer(function() { + return scope.$apply(function(s) { + if (s.dragging != null) { + return s.dragging = dragging; + } + }); + }); + }); + google.maps.event.addListener(_m, "drag", function() { + var c; + c = _m.center; + return _.defer(function() { + return scope.$apply(function(s) { + if (angular.isDefined(s.center.type)) { + s.center.coordinates[1] = c.lat(); + return s.center.coordinates[0] = c.lng(); + } else { + s.center.latitude = c.lat(); + return s.center.longitude = c.lng(); + } + }); + }); + }); + google.maps.event.addListener(_m, "zoom_changed", function() { + if (scope.zoom !== _m.zoom) { + return _.defer(function() { + return scope.$apply(function(s) { + return s.zoom = _m.zoom; + }); + }); + } + }); + settingCenterFromScope = false; + google.maps.event.addListener(_m, "center_changed", function() { + var c; + c = _m.center; + if (settingCenterFromScope) { + return; + } + return _.defer(function() { + return scope.$apply(function(s) { + if (!_m.dragging) { + if (angular.isDefined(s.center.type)) { + if (s.center.coordinates[1] !== c.lat()) { + s.center.coordinates[1] = c.lat(); + } + if (s.center.coordinates[0] !== c.lng()) { + return s.center.coordinates[0] = c.lng(); + } + } else { + if (s.center.latitude !== c.lat()) { + s.center.latitude = c.lat(); + } + if (s.center.longitude !== c.lng()) { + return s.center.longitude = c.lng(); + } + } + } + }); + }); + }); + google.maps.event.addListener(_m, "idle", function() { + var b, ne, sw; + b = _m.getBounds(); + ne = b.getNorthEast(); + sw = b.getSouthWest(); + return _.defer(function() { + return scope.$apply(function(s) { + if (s.bounds !== null && s.bounds !== undefined && s.bounds !== void 0) { + s.bounds.northeast = { + latitude: ne.lat(), + longitude: ne.lng() + }; + return s.bounds.southwest = { + latitude: sw.lat(), + longitude: sw.lng() + }; + } + }); + }); + }); + if (angular.isDefined(scope.events) && scope.events !== null && angular.isObject(scope.events)) { + getEventHandler = function(eventName) { + return function() { + return scope.events[eventName].apply(scope, [_m, eventName, arguments]); + }; + }; + for (eventName in scope.events) { + if (scope.events.hasOwnProperty(eventName) && angular.isFunction(scope.events[eventName])) { + google.maps.event.addListener(_m, eventName, getEventHandler(eventName)); + } + } + } + scope.map = _m; + if ((attrs.control != null) && (scope.control != null)) { + scope.control.refresh = function(maybeCoords) { + var coords; + if (_m == null) { + return; + } + google.maps.event.trigger(_m, "resize"); + if (((maybeCoords != null ? maybeCoords.latitude : void 0) != null) && ((maybeCoords != null ? maybeCoords.latitude : void 0) != null)) { + coords = _this.getCoords(maybeCoords); + if (_this.isTrue(attrs.pan)) { + return _m.panTo(coords); + } else { + return _m.setCenter(coords); + } + } + }; + /* + I am sure you all will love this. You want the instance here you go.. BOOM! + */ + + scope.control.getGMap = function() { + return _m; + }; + } + scope.$watch("center", (function(newValue, oldValue) { + var coords; + coords = _this.getCoords(newValue); + if (coords.lat() === _m.center.lat() && coords.lng() === _m.center.lng()) { + return; + } + settingCenterFromScope = true; + if (!dragging) { + if (!_this.validateCoords(newValue)) { + $log.error("Invalid center for newValue: " + (JSON.stringify(newValue))); + } + if (_this.isTrue(attrs.pan) && scope.zoom === _m.zoom) { + _m.panTo(coords); + } else { + _m.setCenter(coords); + } + } + return settingCenterFromScope = false; + }), true); + scope.$watch("zoom", function(newValue, oldValue) { + if (newValue === _m.zoom) { + return; + } + return _.defer(function() { + return _m.setZoom(newValue); + }); + }); + scope.$watch("bounds", function(newValue, oldValue) { + var bounds, ne, sw; + if (newValue === oldValue) { + return; + } + if ((newValue.northeast.latitude == null) || (newValue.northeast.longitude == null) || (newValue.southwest.latitude == null) || (newValue.southwest.longitude == null)) { + $log.error("Invalid map bounds for new value: " + (JSON.stringify(newValue))); + return; + } + ne = new google.maps.LatLng(newValue.northeast.latitude, newValue.northeast.longitude); + sw = new google.maps.LatLng(newValue.southwest.latitude, newValue.southwest.longitude); + bounds = new google.maps.LatLngBounds(sw, ne); + return _m.fitBounds(bounds); + }); + scope.$watch("options", function(newValue, oldValue) { + if (!_.isEqual(newValue, oldValue)) { + opts.options = newValue; + if (_m != null) { + return _m.setOptions(opts); + } + } + }, true); + return scope.$watch("styles", function(newValue, oldValue) { + if (!_.isEqual(newValue, oldValue)) { + opts.styles = newValue; + if (_m != null) { + return _m.setOptions(opts); + } + } + }, true); + }; + + return Map; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Marker", [ + "IMarker", "MarkerParentModel", "MarkerManager", "CtrlHandle", function(IMarker, MarkerParentModel, MarkerManager, CtrlHandle) { + var Marker; + return Marker = (function(_super) { + var _this = this; + + __extends(Marker, _super); + + function Marker($timeout) { + this.link = __bind(this.link, this); + Marker.__super__.constructor.call(this, $timeout); + this.template = ''; + this.$log.info(this); + } + + Marker.prototype.controller = [ + '$scope', '$element', function($scope, $element) { + $scope.ctrlType = 'Marker'; + return CtrlHandle.handle($scope, $element); + } + ]; + + Marker.prototype.link = function(scope, element, attrs, ctrl) { + var doFit, + _this = this; + Marker.__super__.link.call(this, scope, element, attrs, ctrl); + if (scope.fit) { + doFit = true; + } + return this.mapPromise(scope, ctrl).then(function(map) { + if (!_this.gMarkerManager) { + _this.gMarkerManager = new MarkerManager(map); + } + new MarkerParentModel(scope, element, attrs, map, _this.$timeout, _this.gMarkerManager, doFit); + return scope.deferred.resolve(); + }); + }; + + return Marker; + + }).call(this, IMarker); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Markers", [ + "IMarker", "MarkersParentModel", "CtrlHandle", function(IMarker, MarkersParentModel, CtrlHandle) { + var Markers; + return Markers = (function(_super) { + __extends(Markers, _super); + + function Markers($timeout) { + this.link = __bind(this.link, this); + var self; + Markers.__super__.constructor.call(this, $timeout); + this.template = ''; + this.scope = _.extend(this.scope || {}, { + idKey: '=idkey', + doRebuildAll: '=dorebuildall', + models: '=models', + doCluster: '=docluster', + clusterOptions: '=clusteroptions', + clusterEvents: '=clusterevents', + labelContent: '=labelcontent', + labelAnchor: '@labelanchor', + labelClass: '@labelclass' + }); + this.$timeout = $timeout; + self = this; + this.$log.info(this); + } + + Markers.prototype.controller = [ + '$scope', '$element', function($scope, $element) { + $scope.ctrlType = 'Markers'; + return CtrlHandle.handle($scope, $element); + } + ]; + + Markers.prototype.link = function(scope, element, attrs, ctrl) { + var _this = this; + return this.mapPromise(scope, ctrl).then(function(map) { + new MarkersParentModel(scope, element, attrs, map, _this.$timeout); + return scope.deferred.resolve(); + }); + }; + + return Markers; + + })(IMarker); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Polyline", [ + "IPolyline", "$timeout", "array-sync", "PolylineChildModel", function(IPolyline, $timeout, arraySync, PolylineChildModel) { + var Polyline, _ref; + return Polyline = (function(_super) { + __extends(Polyline, _super); + + function Polyline() { + this.link = __bind(this.link, this); + _ref = Polyline.__super__.constructor.apply(this, arguments); + return _ref; + } + + Polyline.prototype.link = function(scope, element, attrs, mapCtrl) { + var _this = this; + if (angular.isUndefined(scope.path) || scope.path === null || !this.validatePath(scope.path)) { + this.$log.error("polyline: no valid path attribute found"); + return; + } + return mapCtrl.getScope().deferred.promise.then(function(map) { + return new PolylineChildModel(scope, attrs, map, _this.DEFAULTS); + }); + }; + + return Polyline; + + })(IPolyline); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Polylines", [ + "IPolyline", "$timeout", "array-sync", "PolylinesParentModel", function(IPolyline, $timeout, arraySync, PolylinesParentModel) { + var Polylines; + return Polylines = (function(_super) { + __extends(Polylines, _super); + + function Polylines() { + this.link = __bind(this.link, this); + Polylines.__super__.constructor.call(this); + this.scope.idKey = '=idkey'; + this.scope.models = '=models'; + this.$log.info(this); + } + + Polylines.prototype.link = function(scope, element, attrs, mapCtrl) { + var _this = this; + if (angular.isUndefined(scope.path) || scope.path === null) { + this.$log.error("polylines: no valid path attribute found"); + return; + } + if (!scope.models) { + this.$log.error("polylines: no models found to create from"); + return; + } + return mapCtrl.getScope().deferred.promise.then(function(map) { + return new PolylinesParentModel(scope, element, attrs, map, _this.DEFAULTS); + }); + }; + + return Polylines; + + })(IPolyline); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Window", [ + "IWindow", "GmapUtil", "WindowChildModel", "$q", function(IWindow, GmapUtil, WindowChildModel, $q) { + var Window; + return Window = (function(_super) { + __extends(Window, _super); + + Window.include(GmapUtil); + + function Window($timeout, $compile, $http, $templateCache) { + this.init = __bind(this.init, this); + this.link = __bind(this.link, this); + var self; + Window.__super__.constructor.call(this, $timeout, $compile, $http, $templateCache); + self = this; + this.require = ['^googleMap', '^?marker']; + this.template = ''; + this.$log.info(self); + } + + Window.prototype.link = function(scope, element, attrs, ctrls) { + var mapScope, + _this = this; + mapScope = ctrls[0].getScope(); + return mapScope.deferred.promise.then(function(mapCtrl) { + var isIconVisibleOnClick, markerCtrl, markerScope; + isIconVisibleOnClick = true; + if (angular.isDefined(attrs.isiconvisibleonclick)) { + isIconVisibleOnClick = scope.isIconVisibleOnClick; + } + markerCtrl = ctrls.length > 1 && (ctrls[1] != null) ? ctrls[1] : void 0; + if (!markerCtrl) { + _this.init(scope, element, isIconVisibleOnClick, mapCtrl); + return; + } + markerScope = markerCtrl.getScope(); + return markerScope.deferred.promise.then(function() { + return _this.init(scope, element, isIconVisibleOnClick, mapCtrl, markerScope); + }); + }); + }; + + Window.prototype.init = function(scope, element, isIconVisibleOnClick, mapCtrl, markerScope) { + var defaults, gMarker, hasScopeCoords, opts, window, + _this = this; + defaults = scope.options != null ? scope.options : {}; + hasScopeCoords = (scope != null) && this.validateCoords(scope.coords); + if (markerScope != null) { + gMarker = markerScope.gMarker; + markerScope.$watch('coords', function(newValue, oldValue) { + if ((markerScope.gMarker != null) && !window.markerCtrl) { + window.markerCtrl = markerScope.gMarker; + window.handleClick(true); + } + if (!_this.validateCoords(newValue)) { + return window.hideWindow(); + } + if (!angular.equals(newValue, oldValue)) { + return window.getLatestPosition(_this.getCoords(newValue)); + } + }, true); + } + opts = hasScopeCoords ? this.createWindowOptions(gMarker, scope, element.html(), defaults) : defaults; + if (mapCtrl != null) { + window = new WindowChildModel({}, scope, opts, isIconVisibleOnClick, mapCtrl, gMarker, element); + } + scope.$on("$destroy", function() { + return window != null ? window.destroy() : void 0; + }); + if ((this.onChildCreation != null) && (window != null)) { + return this.onChildCreation(window); + } + }; + + return Window; + + })(IWindow); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Windows", [ + "IWindow", "WindowsParentModel", function(IWindow, WindowsParentModel) { + /* + Windows directive where many windows map to the models property + */ + + var Windows; + return Windows = (function(_super) { + __extends(Windows, _super); + + function Windows($timeout, $compile, $http, $templateCache, $interpolate) { + this.link = __bind(this.link, this); + var self; + Windows.__super__.constructor.call(this, $timeout, $compile, $http, $templateCache); + self = this; + this.$interpolate = $interpolate; + this.require = ['^googleMap', '^?markers']; + this.template = ''; + this.scope.idKey = '=idkey'; + this.scope.doRebuildAll = '=dorebuildall'; + this.scope.models = '=models'; + this.$log.info(this); + } + + Windows.prototype.link = function(scope, element, attrs, ctrls) { + return new WindowsParentModel(scope, element, attrs, ctrls, this.$timeout, this.$compile, this.$http, this.$templateCache, this.$interpolate); + }; + + return Windows; + + })(IWindow); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +Nick Baugh - https://github.com/niftylettuce +*/ + + +(function() { + angular.module("google-maps").directive("googleMap", [ + "Map", function(Map) { + return new Map(); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +/* +Map marker directive + +This directive is used to create a marker on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute icon optional} string url to image used for marker icon +{attribute animate optional} if set to false, the marker won't be animated (on by default) +*/ + + +(function() { + angular.module("google-maps").directive("marker", [ + "$timeout", "Marker", function($timeout, Marker) { + return new Marker($timeout); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +/* +Map marker directive + +This directive is used to create a marker on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute icon optional} string url to image used for marker icon +{attribute animate optional} if set to false, the marker won't be animated (on by default) +*/ + + +(function() { + angular.module("google-maps").directive("markers", [ + "$timeout", "Markers", function($timeout, Markers) { + return new Markers($timeout); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors Bruno Queiroz, creativelikeadog@gmail.com +*/ + + +/* +Marker label directive + +This directive is used to create a marker label on an existing map. + +{attribute content required} content of the label +{attribute anchor required} string that contains the x and y point position of the label +{attribute class optional} class to DOM object +{attribute style optional} style for the label +*/ + + +/* +Basic Directive api for a label. Basic in the sense that this directive contains 1:1 on scope and model. +Thus there will be one html element per marker within the directive. +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps").directive("markerLabel", [ + "$timeout", "ILabel", "MarkerLabelChildModel", "GmapUtil", "nggmap-PropertyAction", function($timeout, ILabel, MarkerLabelChildModel, GmapUtil, PropertyAction) { + var Label; + Label = (function(_super) { + __extends(Label, _super); + + function Label($timeout) { + this.init = __bind(this.init, this); + this.link = __bind(this.link, this); + var self; + Label.__super__.constructor.call(this, $timeout); + self = this; + this.require = '^marker'; + this.template = ''; + this.$log.info(this); + } + + Label.prototype.link = function(scope, element, attrs, ctrl) { + var markerScope, + _this = this; + markerScope = ctrl.getScope(); + if (markerScope) { + return markerScope.deferred.promise.then(function() { + return _this.init(markerScope, scope); + }); + } + }; + + Label.prototype.init = function(markerScope, scope) { + var createLabel, isFirstSet, label; + label = null; + createLabel = function() { + if (!label) { + return label = new MarkerLabelChildModel(markerScope.gMarker, scope, markerScope.map); + } + }; + isFirstSet = true; + return markerScope.$watch('gMarker', function(newVal, oldVal) { + var anchorAction, classAction, contentAction, styleAction; + if (markerScope.gMarker != null) { + contentAction = new PropertyAction(function(newVal) { + createLabel(); + if (scope.labelContent) { + return label != null ? label.setOption('labelContent', scope.labelContent) : void 0; + } + }, isFirstSet); + anchorAction = new PropertyAction(function(newVal) { + createLabel(); + return label != null ? label.setOption('labelAnchor', scope.labelAnchor) : void 0; + }, isFirstSet); + classAction = new PropertyAction(function(newVal) { + createLabel(); + return label != null ? label.setOption('labelClass', scope.labelClass) : void 0; + }, isFirstSet); + styleAction = new PropertyAction(function(newVal) { + createLabel(); + return label != null ? label.setOption('labelStyle', scope.labelStyle) : void 0; + }, isFirstSet); + scope.$watch('labelContent', contentAction.sic); + scope.$watch('labelAnchor', anchorAction.sic); + scope.$watch('labelClass', classAction.sic); + scope.$watch('labelStyle', styleAction.sic); + isFirstSet = false; + } + return scope.$on('$destroy', function() { + return label != null ? label.destroy() : void 0; + }); + }); + }; + + return Label; + + })(ILabel); + return new Label($timeout); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +Rick Huizinga - https://plus.google.com/+RickHuizinga +*/ + + +(function() { + angular.module("google-maps").directive("polygon", [ + "$log", "$timeout", "array-sync", "GmapUtil", function($log, $timeout, arraySync, GmapUtil) { + /* + Check if a value is true + */ + + var DEFAULTS, isTrue; + isTrue = function(val) { + return angular.isDefined(val) && val !== null && val === true || val === "1" || val === "y" || val === "true"; + }; + "use strict"; + DEFAULTS = {}; + return { + restrict: "ECA", + replace: true, + require: "^googleMap", + scope: { + path: "=path", + stroke: "=stroke", + clickable: "=", + draggable: "=", + editable: "=", + geodesic: "=", + fill: "=", + icons: "=icons", + visible: "=", + "static": "=", + events: "=", + zIndex: "=zindex", + fit: "=" + }, + link: function(scope, element, attrs, mapCtrl) { + var _this = this; + if (angular.isUndefined(scope.path) || scope.path === null || !GmapUtil.validatePath(scope.path)) { + $log.error("polygon: no valid path attribute found"); + return; + } + return mapCtrl.getScope().deferred.promise.then(function(map) { + var arraySyncer, buildOpts, eventName, getEventHandler, pathPoints, polygon; + buildOpts = function(pathPoints) { + var opts; + opts = angular.extend({}, DEFAULTS, { + map: map, + path: pathPoints, + strokeColor: scope.stroke && scope.stroke.color, + strokeOpacity: scope.stroke && scope.stroke.opacity, + strokeWeight: scope.stroke && scope.stroke.weight, + fillColor: scope.fill && scope.fill.color, + fillOpacity: scope.fill && scope.fill.opacity + }); + angular.forEach({ + clickable: true, + draggable: false, + editable: false, + geodesic: false, + visible: true, + "static": false, + fit: false, + zIndex: 0 + }, function(defaultValue, key) { + if (angular.isUndefined(scope[key]) || scope[key] === null) { + return opts[key] = defaultValue; + } else { + return opts[key] = scope[key]; + } + }); + if (opts["static"]) { + opts.editable = false; + } + return opts; + }; + pathPoints = GmapUtil.convertPathPoints(scope.path); + polygon = new google.maps.Polygon(buildOpts(pathPoints)); + if (scope.fit) { + GmapUtil.extendMapBounds(map, pathPoints); + } + if (!scope["static"] && angular.isDefined(scope.editable)) { + scope.$watch("editable", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setEditable(newValue); + } + }); + } + if (angular.isDefined(scope.draggable)) { + scope.$watch("draggable", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setDraggable(newValue); + } + }); + } + if (angular.isDefined(scope.visible)) { + scope.$watch("visible", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setVisible(newValue); + } + }); + } + if (angular.isDefined(scope.geodesic)) { + scope.$watch("geodesic", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.opacity)) { + scope.$watch("stroke.opacity", function(newValue, oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.weight)) { + scope.$watch("stroke.weight", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.color)) { + scope.$watch("stroke.color", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.fill) && angular.isDefined(scope.fill.color)) { + scope.$watch("fill.color", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.fill) && angular.isDefined(scope.fill.opacity)) { + scope.$watch("fill.opacity", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.zIndex)) { + scope.$watch("zIndex", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.events) && scope.events !== null && angular.isObject(scope.events)) { + getEventHandler = function(eventName) { + return function() { + return scope.events[eventName].apply(scope, [polygon, eventName, arguments]); + }; + }; + for (eventName in scope.events) { + if (scope.events.hasOwnProperty(eventName) && angular.isFunction(scope.events[eventName])) { + polygon.addListener(eventName, getEventHandler(eventName)); + } + } + } + arraySyncer = arraySync(polygon.getPath(), scope, "path", function(pathPoints) { + if (scope.fit) { + return GmapUtil.extendMapBounds(map, pathPoints); + } + }); + return scope.$on("$destroy", function() { + polygon.setMap(null); + if (arraySyncer) { + arraySyncer(); + return arraySyncer = null; + } + }); + }); + } + }; + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@authors +Julian Popescu - https://github.com/jpopesculian +Rick Huizinga - https://plus.google.com/+RickHuizinga +*/ + + +(function() { + angular.module("google-maps").directive("circle", [ + "$log", "$timeout", "GmapUtil", "EventsHelper", function($log, $timeout, GmapUtil, EventsHelper) { + "use strict"; + var DEFAULTS; + DEFAULTS = {}; + return { + restrict: "ECA", + replace: true, + require: "^googleMap", + scope: { + center: "=center", + radius: "=radius", + stroke: "=stroke", + fill: "=fill", + clickable: "=", + draggable: "=", + editable: "=", + geodesic: "=", + icons: "=icons", + visible: "=", + events: "=" + }, + link: function(scope, element, attrs, mapCtrl) { + var _this = this; + return mapCtrl.getScope().deferred.promise.then(function(map) { + var buildOpts, circle; + buildOpts = function() { + var opts; + if (!GmapUtil.validateCoords(scope.center)) { + $log.error("circle: no valid center attribute found"); + return; + } + opts = angular.extend({}, DEFAULTS, { + map: map, + center: GmapUtil.getCoords(scope.center), + radius: scope.radius, + strokeColor: scope.stroke && scope.stroke.color, + strokeOpacity: scope.stroke && scope.stroke.opacity, + strokeWeight: scope.stroke && scope.stroke.weight, + fillColor: scope.fill && scope.fill.color, + fillOpacity: scope.fill && scope.fill.opacity + }); + angular.forEach({ + clickable: true, + draggable: false, + editable: false, + geodesic: false, + visible: true + }, function(defaultValue, key) { + if (angular.isUndefined(scope[key]) || scope[key] === null) { + return opts[key] = defaultValue; + } else { + return opts[key] = scope[key]; + } + }); + return opts; + }; + circle = new google.maps.Circle(buildOpts()); + scope.$watchCollection('center', function(newVals, oldVals) { + if (newVals !== oldVals) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watchCollection('stroke', function(newVals, oldVals) { + if (newVals !== oldVals) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watchCollection('fill', function(newVals, oldVals) { + if (newVals !== oldVals) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('radius', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('clickable', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('editable', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('draggable', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('visible', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('geodesic', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + EventsHelper.setEvents(circle, scope, scope); + google.maps.event.addListener(circle, 'radius_changed', function() { + scope.radius = circle.getRadius(); + return $timeout(function() { + return scope.$apply(); + }); + }); + google.maps.event.addListener(circle, 'center_changed', function() { + if (angular.isDefined(scope.center.type)) { + scope.center.coordinates[1] = circle.getCenter().lat(); + scope.center.coordinates[0] = circle.getCenter().lng(); + } else { + scope.center.latitude = circle.getCenter().lat(); + scope.center.longitude = circle.getCenter().lng(); + } + return $timeout(function() { + return scope.$apply(); + }); + }); + return scope.$on("$destroy", function() { + return circle.setMap(null); + }); + }); + } + }; + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +(function() { + angular.module("google-maps").directive("polyline", [ + "Polyline", function(Polyline) { + return new Polyline(); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +(function() { + angular.module("google-maps").directive("polylines", [ + "Polylines", function(Polylines) { + return new Polylines(); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +Chentsu Lin - https://github.com/ChenTsuLin +*/ + + +(function() { + angular.module("google-maps").directive("rectangle", [ + "$log", "$timeout", function($log, $timeout) { + var DEFAULTS, convertBoundPoints, fitMapBounds, isTrue, validateBoundPoints; + validateBoundPoints = function(bounds) { + if (angular.isUndefined(bounds.sw.latitude) || angular.isUndefined(bounds.sw.longitude) || angular.isUndefined(bounds.ne.latitude) || angular.isUndefined(bounds.ne.longitude)) { + return false; + } + return true; + }; + convertBoundPoints = function(bounds) { + var result; + result = new google.maps.LatLngBounds(new google.maps.LatLng(bounds.sw.latitude, bounds.sw.longitude), new google.maps.LatLng(bounds.ne.latitude, bounds.ne.longitude)); + return result; + }; + fitMapBounds = function(map, bounds) { + return map.fitBounds(bounds); + }; + /* + Check if a value is true + */ + + isTrue = function(val) { + return angular.isDefined(val) && val !== null && val === true || val === "1" || val === "y" || val === "true"; + }; + "use strict"; + DEFAULTS = {}; + return { + restrict: "ECA", + require: "^googleMap", + replace: true, + scope: { + bounds: "=", + stroke: "=", + clickable: "=", + draggable: "=", + editable: "=", + fill: "=", + visible: "=" + }, + link: function(scope, element, attrs, mapCtrl) { + var _this = this; + if (angular.isUndefined(scope.bounds) || scope.bounds === null || angular.isUndefined(scope.bounds.sw) || scope.bounds.sw === null || angular.isUndefined(scope.bounds.ne) || scope.bounds.ne === null || !validateBoundPoints(scope.bounds)) { + $log.error("rectangle: no valid bound attribute found"); + return; + } + return mapCtrl.getScope().deferred.promise.then(function(map) { + var buildOpts, dragging, rectangle, settingBoundsFromScope; + buildOpts = function(bounds) { + var opts; + opts = angular.extend({}, DEFAULTS, { + map: map, + bounds: bounds, + strokeColor: scope.stroke && scope.stroke.color, + strokeOpacity: scope.stroke && scope.stroke.opacity, + strokeWeight: scope.stroke && scope.stroke.weight, + fillColor: scope.fill && scope.fill.color, + fillOpacity: scope.fill && scope.fill.opacity + }); + angular.forEach({ + clickable: true, + draggable: false, + editable: false, + visible: true + }, function(defaultValue, key) { + if (angular.isUndefined(scope[key]) || scope[key] === null) { + return opts[key] = defaultValue; + } else { + return opts[key] = scope[key]; + } + }); + return opts; + }; + rectangle = new google.maps.Rectangle(buildOpts(convertBoundPoints(scope.bounds))); + if (isTrue(attrs.fit)) { + fitMapBounds(map, bounds); + } + dragging = false; + google.maps.event.addListener(rectangle, "mousedown", function() { + google.maps.event.addListener(rectangle, "mousemove", function() { + dragging = true; + return _.defer(function() { + return scope.$apply(function(s) { + if (s.dragging != null) { + return s.dragging = dragging; + } + }); + }); + }); + google.maps.event.addListener(rectangle, "mouseup", function() { + google.maps.event.clearListeners(rectangle, 'mousemove'); + google.maps.event.clearListeners(rectangle, 'mouseup'); + dragging = false; + return _.defer(function() { + return scope.$apply(function(s) { + if (s.dragging != null) { + return s.dragging = dragging; + } + }); + }); + }); + }); + settingBoundsFromScope = false; + google.maps.event.addListener(rectangle, "bounds_changed", function() { + var b, ne, sw; + b = rectangle.getBounds(); + ne = b.getNorthEast(); + sw = b.getSouthWest(); + if (settingBoundsFromScope) { + return; + } + return _.defer(function() { + return scope.$apply(function(s) { + if (!rectangle.dragging) { + if (s.bounds !== null && s.bounds !== undefined && s.bounds !== void 0) { + s.bounds.ne = { + latitude: ne.lat(), + longitude: ne.lng() + }; + s.bounds.sw = { + latitude: sw.lat(), + longitude: sw.lng() + }; + } + } + }); + }); + }); + scope.$watch("bounds", (function(newValue, oldValue) { + var bounds; + if (_.isEqual(newValue, oldValue)) { + return; + } + settingBoundsFromScope = true; + if (!dragging) { + if ((newValue.sw.latitude == null) || (newValue.sw.longitude == null) || (newValue.ne.latitude == null) || (newValue.ne.longitude == null)) { + $log.error("Invalid bounds for newValue: " + (JSON.stringify(newValue))); + } + bounds = new google.maps.LatLngBounds(new google.maps.LatLng(newValue.sw.latitude, newValue.sw.longitude), new google.maps.LatLng(newValue.ne.latitude, newValue.ne.longitude)); + rectangle.setBounds(bounds); + } + return settingBoundsFromScope = false; + }), true); + if (angular.isDefined(scope.editable)) { + scope.$watch("editable", function(newValue, oldValue) { + return rectangle.setEditable(newValue); + }); + } + if (angular.isDefined(scope.draggable)) { + scope.$watch("draggable", function(newValue, oldValue) { + return rectangle.setDraggable(newValue); + }); + } + if (angular.isDefined(scope.visible)) { + scope.$watch("visible", function(newValue, oldValue) { + return rectangle.setVisible(newValue); + }); + } + if (angular.isDefined(scope.stroke)) { + if (angular.isDefined(scope.stroke.color)) { + scope.$watch("stroke.color", function(newValue, oldValue) { + return rectangle.setOptions(buildOpts(rectangle.getBounds())); + }); + } + if (angular.isDefined(scope.stroke.weight)) { + scope.$watch("stroke.weight", function(newValue, oldValue) { + return rectangle.setOptions(buildOpts(rectangle.getBounds())); + }); + } + if (angular.isDefined(scope.stroke.opacity)) { + scope.$watch("stroke.opacity", function(newValue, oldValue) { + return rectangle.setOptions(buildOpts(rectangle.getBounds())); + }); + } + } + if (angular.isDefined(scope.fill)) { + if (angular.isDefined(scope.fill.color)) { + scope.$watch("fill.color", function(newValue, oldValue) { + return rectangle.setOptions(buildOpts(rectangle.getBounds())); + }); + } + if (angular.isDefined(scope.fill.opacity)) { + scope.$watch("fill.opacity", function(newValue, oldValue) { + return rectangle.setOptions(buildOpts(rectangle.getBounds())); + }); + } + } + return scope.$on("$destroy", function() { + return rectangle.setMap(null); + }); + }); + } + }; + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +/* +Map info window directive + +This directive is used to create an info window on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute show optional} map will show when this expression returns true +*/ + + +(function() { + angular.module("google-maps").directive("window", [ + "$timeout", "$compile", "$http", "$templateCache", "Window", function($timeout, $compile, $http, $templateCache, Window) { + return new Window($timeout, $compile, $http, $templateCache); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +/* +Map info window directive + +This directive is used to create an info window on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute show optional} map will show when this expression returns true +*/ + + +(function() { + angular.module("google-maps").directive("windows", [ + "$timeout", "$compile", "$http", "$templateCache", "$interpolate", "Windows", function($timeout, $compile, $http, $templateCache, $interpolate, Windows) { + return new Windows($timeout, $compile, $http, $templateCache, $interpolate); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors: +- Nicolas Laplante https://plus.google.com/108189012221374960701 +- Nicholas McCready - https://twitter.com/nmccready +*/ + + +/* +Map Layer directive + +This directive is used to create any type of Layer from the google maps sdk. +This directive creates a new scope. + +{attribute show optional} true (default) shows the trafficlayer otherwise it is hidden +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + angular.module("google-maps").directive("layer", [ + "$timeout", "Logger", "LayerParentModel", function($timeout, Logger, LayerParentModel) { + var Layer; + Layer = (function() { + function Layer() { + this.link = __bind(this.link, this); + this.$log = Logger; + this.restrict = "ECMA"; + this.require = "^googleMap"; + this.priority = -1; + this.transclude = true; + this.template = ''; + this.replace = true; + this.scope = { + show: "=show", + type: "=type", + namespace: "=namespace", + options: '=options', + onCreated: '&oncreated' + }; + } + + Layer.prototype.link = function(scope, element, attrs, mapCtrl) { + var _this = this; + return mapCtrl.getScope().deferred.promise.then(function(map) { + if (scope.onCreated != null) { + return new LayerParentModel(scope, element, attrs, map, scope.onCreated); + } else { + return new LayerParentModel(scope, element, attrs, map); + } + }); + }; + + return Layer; + + })(); + return new Layer(); + } + ]); + +}).call(this); +;/** + * @name InfoBox + * @version 1.1.12 [December 11, 2012] + * @author Gary Little (inspired by proof-of-concept code from Pamela Fox of Google) + * @copyright Copyright 2010 Gary Little [gary at luxcentral.com] + * @fileoverview InfoBox extends the Google Maps JavaScript API V3 OverlayView class. + *

        + * An InfoBox behaves like a google.maps.InfoWindow, but it supports several + * additional properties for advanced styling. An InfoBox can also be used as a map label. + *

        + * An InfoBox also fires the same events as a google.maps.InfoWindow. + */ + +/*! + * + * 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. + */ + +/*jslint browser:true */ +/*global google */ + +/** + * @name InfoBoxOptions + * @class This class represents the optional parameter passed to the {@link InfoBox} constructor. + * @property {string|Node} content The content of the InfoBox (plain text or an HTML DOM node). + * @property {boolean} [disableAutoPan=false] Disable auto-pan on open. + * @property {number} maxWidth The maximum width (in pixels) of the InfoBox. Set to 0 if no maximum. + * @property {Size} pixelOffset The offset (in pixels) from the top left corner of the InfoBox + * (or the bottom left corner if the alignBottom property is true) + * to the map pixel corresponding to position. + * @property {LatLng} position The geographic location at which to display the InfoBox. + * @property {number} zIndex The CSS z-index style value for the InfoBox. + * Note: This value overrides a zIndex setting specified in the boxStyle property. + * @property {string} [boxClass="infoBox"] The name of the CSS class defining the styles for the InfoBox container. + * @property {Object} [boxStyle] An object literal whose properties define specific CSS + * style values to be applied to the InfoBox. Style values defined here override those that may + * be defined in the boxClass style sheet. If this property is changed after the + * InfoBox has been created, all previously set styles (except those defined in the style sheet) + * are removed from the InfoBox before the new style values are applied. + * @property {string} closeBoxMargin The CSS margin style value for the close box. + * The default is "2px" (a 2-pixel margin on all sides). + * @property {string} closeBoxURL The URL of the image representing the close box. + * Note: The default is the URL for Google's standard close box. + * Set this property to "" if no close box is required. + * @property {Size} infoBoxClearance Minimum offset (in pixels) from the InfoBox to the + * map edge after an auto-pan. + * @property {boolean} [isHidden=false] Hide the InfoBox on open. + * [Deprecated in favor of the visible property.] + * @property {boolean} [visible=true] Show the InfoBox on open. + * @property {boolean} alignBottom Align the bottom left corner of the InfoBox to the position + * location (default is false which means that the top left corner of the InfoBox is aligned). + * @property {string} pane The pane where the InfoBox is to appear (default is "floatPane"). + * Set the pane to "mapPane" if the InfoBox is being used as a map label. + * Valid pane names are the property names for the google.maps.MapPanes object. + * @property {boolean} enableEventPropagation Propagate mousedown, mousemove, mouseover, mouseout, + * mouseup, click, dblclick, touchstart, touchend, touchmove, and contextmenu events in the InfoBox + * (default is false to mimic the behavior of a google.maps.InfoWindow). Set + * this property to true if the InfoBox is being used as a map label. + */ + +/** + * Creates an InfoBox with the options specified in {@link InfoBoxOptions}. + * Call InfoBox.open to add the box to the map. + * @constructor + * @param {InfoBoxOptions} [opt_opts] + */ +function InfoBox(opt_opts) { + + opt_opts = opt_opts || {}; + + google.maps.OverlayView.apply(this, arguments); + + // Standard options (in common with google.maps.InfoWindow): + // + this.content_ = opt_opts.content || ""; + this.disableAutoPan_ = opt_opts.disableAutoPan || false; + this.maxWidth_ = opt_opts.maxWidth || 0; + this.pixelOffset_ = opt_opts.pixelOffset || new google.maps.Size(0, 0); + this.position_ = opt_opts.position || new google.maps.LatLng(0, 0); + this.zIndex_ = opt_opts.zIndex || null; + + // Additional options (unique to InfoBox): + // + this.boxClass_ = opt_opts.boxClass || "infoBox"; + this.boxStyle_ = opt_opts.boxStyle || {}; + this.closeBoxMargin_ = opt_opts.closeBoxMargin || "2px"; + this.closeBoxURL_ = opt_opts.closeBoxURL || "http://www.google.com/intl/en_us/mapfiles/close.gif"; + if (opt_opts.closeBoxURL === "") { + this.closeBoxURL_ = ""; + } + this.infoBoxClearance_ = opt_opts.infoBoxClearance || new google.maps.Size(1, 1); + + if (typeof opt_opts.visible === "undefined") { + if (typeof opt_opts.isHidden === "undefined") { + opt_opts.visible = true; + } else { + opt_opts.visible = !opt_opts.isHidden; + } + } + this.isHidden_ = !opt_opts.visible; + + this.alignBottom_ = opt_opts.alignBottom || false; + this.pane_ = opt_opts.pane || "floatPane"; + this.enableEventPropagation_ = opt_opts.enableEventPropagation || false; + + this.div_ = null; + this.closeListener_ = null; + this.moveListener_ = null; + this.contextListener_ = null; + this.eventListeners_ = null; + this.fixedWidthSet_ = null; +} + +/* InfoBox extends OverlayView in the Google Maps API v3. + */ +InfoBox.prototype = new google.maps.OverlayView(); + +/** + * Creates the DIV representing the InfoBox. + * @private + */ +InfoBox.prototype.createInfoBoxDiv_ = function () { + + var i; + var events; + var bw; + var me = this; + + // This handler prevents an event in the InfoBox from being passed on to the map. + // + var cancelHandler = function (e) { + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + }; + + // This handler ignores the current event in the InfoBox and conditionally prevents + // the event from being passed on to the map. It is used for the contextmenu event. + // + var ignoreHandler = function (e) { + + e.returnValue = false; + + if (e.preventDefault) { + + e.preventDefault(); + } + + if (!me.enableEventPropagation_) { + + cancelHandler(e); + } + }; + + if (!this.div_) { + + this.div_ = document.createElement("div"); + + this.setBoxStyle_(); + + if (typeof this.content_.nodeType === "undefined") { + this.div_.innerHTML = this.getCloseBoxImg_() + this.content_; + } else { + this.div_.innerHTML = this.getCloseBoxImg_(); + this.div_.appendChild(this.content_); + } + + // Add the InfoBox DIV to the DOM + this.getPanes()[this.pane_].appendChild(this.div_); + + this.addClickHandler_(); + + if (this.div_.style.width) { + + this.fixedWidthSet_ = true; + + } else { + + if (this.maxWidth_ !== 0 && this.div_.offsetWidth > this.maxWidth_) { + + this.div_.style.width = this.maxWidth_; + this.div_.style.overflow = "auto"; + this.fixedWidthSet_ = true; + + } else { // The following code is needed to overcome problems with MSIE + + bw = this.getBoxWidths_(); + + this.div_.style.width = (this.div_.offsetWidth - bw.left - bw.right) + "px"; + this.fixedWidthSet_ = false; + } + } + + this.panBox_(this.disableAutoPan_); + + if (!this.enableEventPropagation_) { + + this.eventListeners_ = []; + + // Cancel event propagation. + // + // Note: mousemove not included (to resolve Issue 152) + events = ["mousedown", "mouseover", "mouseout", "mouseup", + "click", "dblclick", "touchstart", "touchend", "touchmove"]; + + for (i = 0; i < events.length; i++) { + + this.eventListeners_.push(google.maps.event.addDomListener(this.div_, events[i], cancelHandler)); + } + + // Workaround for Google bug that causes the cursor to change to a pointer + // when the mouse moves over a marker underneath InfoBox. + this.eventListeners_.push(google.maps.event.addDomListener(this.div_, "mouseover", function (e) { + this.style.cursor = "default"; + })); + } + + this.contextListener_ = google.maps.event.addDomListener(this.div_, "contextmenu", ignoreHandler); + + /** + * This event is fired when the DIV containing the InfoBox's content is attached to the DOM. + * @name InfoBox#domready + * @event + */ + google.maps.event.trigger(this, "domready"); + } +}; + +/** + * Returns the HTML tag for the close box. + * @private + */ +InfoBox.prototype.getCloseBoxImg_ = function () { + + var img = ""; + + if (this.closeBoxURL_ !== "") { + + img = " mapWidth) { + xOffset = pixPosition.x + iwWidth + iwOffsetX + padX - mapWidth; + } + if (this.alignBottom_) { + if (pixPosition.y < (-iwOffsetY + padY + iwHeight)) { + yOffset = pixPosition.y + iwOffsetY - padY - iwHeight; + } else if ((pixPosition.y + iwOffsetY + padY) > mapHeight) { + yOffset = pixPosition.y + iwOffsetY + padY - mapHeight; + } + } else { + if (pixPosition.y < (-iwOffsetY + padY)) { + yOffset = pixPosition.y + iwOffsetY - padY; + } else if ((pixPosition.y + iwHeight + iwOffsetY + padY) > mapHeight) { + yOffset = pixPosition.y + iwHeight + iwOffsetY + padY - mapHeight; + } + } + + if (!(xOffset === 0 && yOffset === 0)) { + + // Move the map to the shifted center. + // + var c = map.getCenter(); + map.panBy(xOffset, yOffset); + } + } + } +}; + +/** + * Sets the style of the InfoBox by setting the style sheet and applying + * other specific styles requested. + * @private + */ +InfoBox.prototype.setBoxStyle_ = function () { + + var i, boxStyle; + + if (this.div_) { + + // Apply style values from the style sheet defined in the boxClass parameter: + this.div_.className = this.boxClass_; + + // Clear existing inline style values: + this.div_.style.cssText = ""; + + // Apply style values defined in the boxStyle parameter: + boxStyle = this.boxStyle_; + for (i in boxStyle) { + + if (boxStyle.hasOwnProperty(i)) { + + this.div_.style[i] = boxStyle[i]; + } + } + + // Fix up opacity style for benefit of MSIE: + // + if (typeof this.div_.style.opacity !== "undefined" && this.div_.style.opacity !== "") { + + this.div_.style.filter = "alpha(opacity=" + (this.div_.style.opacity * 100) + ")"; + } + + // Apply required styles: + // + this.div_.style.position = "absolute"; + this.div_.style.visibility = 'hidden'; + if (this.zIndex_ !== null) { + + this.div_.style.zIndex = this.zIndex_; + } + } +}; + +/** + * Get the widths of the borders of the InfoBox. + * @private + * @return {Object} widths object (top, bottom left, right) + */ +InfoBox.prototype.getBoxWidths_ = function () { + + var computedStyle; + var bw = {top: 0, bottom: 0, left: 0, right: 0}; + var box = this.div_; + + if (document.defaultView && document.defaultView.getComputedStyle) { + + computedStyle = box.ownerDocument.defaultView.getComputedStyle(box, ""); + + if (computedStyle) { + + // The computed styles are always in pixel units (good!) + bw.top = parseInt(computedStyle.borderTopWidth, 10) || 0; + bw.bottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; + bw.left = parseInt(computedStyle.borderLeftWidth, 10) || 0; + bw.right = parseInt(computedStyle.borderRightWidth, 10) || 0; + } + + } else if (document.documentElement.currentStyle) { // MSIE + + if (box.currentStyle) { + + // The current styles may not be in pixel units, but assume they are (bad!) + bw.top = parseInt(box.currentStyle.borderTopWidth, 10) || 0; + bw.bottom = parseInt(box.currentStyle.borderBottomWidth, 10) || 0; + bw.left = parseInt(box.currentStyle.borderLeftWidth, 10) || 0; + bw.right = parseInt(box.currentStyle.borderRightWidth, 10) || 0; + } + } + + return bw; +}; + +/** + * Invoked when close is called. Do not call it directly. + */ +InfoBox.prototype.onRemove = function () { + + if (this.div_) { + + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + } +}; + +/** + * Draws the InfoBox based on the current map projection and zoom level. + */ +InfoBox.prototype.draw = function () { + + this.createInfoBoxDiv_(); + + var pixPosition = this.getProjection().fromLatLngToDivPixel(this.position_); + + this.div_.style.left = (pixPosition.x + this.pixelOffset_.width) + "px"; + + if (this.alignBottom_) { + this.div_.style.bottom = -(pixPosition.y + this.pixelOffset_.height) + "px"; + } else { + this.div_.style.top = (pixPosition.y + this.pixelOffset_.height) + "px"; + } + + if (this.isHidden_) { + + this.div_.style.visibility = 'hidden'; + + } else { + + this.div_.style.visibility = "visible"; + } +}; + +/** + * Sets the options for the InfoBox. Note that changes to the maxWidth, + * closeBoxMargin, closeBoxURL, and enableEventPropagation + * properties have no affect until the current InfoBox is closed and a new one + * is opened. + * @param {InfoBoxOptions} opt_opts + */ +InfoBox.prototype.setOptions = function (opt_opts) { + if (typeof opt_opts.boxClass !== "undefined") { // Must be first + + this.boxClass_ = opt_opts.boxClass; + this.setBoxStyle_(); + } + if (typeof opt_opts.boxStyle !== "undefined") { // Must be second + + this.boxStyle_ = opt_opts.boxStyle; + this.setBoxStyle_(); + } + if (typeof opt_opts.content !== "undefined") { + + this.setContent(opt_opts.content); + } + if (typeof opt_opts.disableAutoPan !== "undefined") { + + this.disableAutoPan_ = opt_opts.disableAutoPan; + } + if (typeof opt_opts.maxWidth !== "undefined") { + + this.maxWidth_ = opt_opts.maxWidth; + } + if (typeof opt_opts.pixelOffset !== "undefined") { + + this.pixelOffset_ = opt_opts.pixelOffset; + } + if (typeof opt_opts.alignBottom !== "undefined") { + + this.alignBottom_ = opt_opts.alignBottom; + } + if (typeof opt_opts.position !== "undefined") { + + this.setPosition(opt_opts.position); + } + if (typeof opt_opts.zIndex !== "undefined") { + + this.setZIndex(opt_opts.zIndex); + } + if (typeof opt_opts.closeBoxMargin !== "undefined") { + + this.closeBoxMargin_ = opt_opts.closeBoxMargin; + } + if (typeof opt_opts.closeBoxURL !== "undefined") { + + this.closeBoxURL_ = opt_opts.closeBoxURL; + } + if (typeof opt_opts.infoBoxClearance !== "undefined") { + + this.infoBoxClearance_ = opt_opts.infoBoxClearance; + } + if (typeof opt_opts.isHidden !== "undefined") { + + this.isHidden_ = opt_opts.isHidden; + } + if (typeof opt_opts.visible !== "undefined") { + + this.isHidden_ = !opt_opts.visible; + } + if (typeof opt_opts.enableEventPropagation !== "undefined") { + + this.enableEventPropagation_ = opt_opts.enableEventPropagation; + } + + if (this.div_) { + + this.draw(); + } +}; + +/** + * Sets the content of the InfoBox. + * The content can be plain text or an HTML DOM node. + * @param {string|Node} content + */ +InfoBox.prototype.setContent = function (content) { + this.content_ = content; + + if (this.div_) { + + if (this.closeListener_) { + + google.maps.event.removeListener(this.closeListener_); + this.closeListener_ = null; + } + + // Odd code required to make things work with MSIE. + // + if (!this.fixedWidthSet_) { + + this.div_.style.width = ""; + } + + if (typeof content.nodeType === "undefined") { + this.div_.innerHTML = this.getCloseBoxImg_() + content; + } else { + this.div_.innerHTML = this.getCloseBoxImg_(); + this.div_.appendChild(content); + } + + // Perverse code required to make things work with MSIE. + // (Ensures the close box does, in fact, float to the right.) + // + if (!this.fixedWidthSet_) { + this.div_.style.width = this.div_.offsetWidth + "px"; + if (typeof content.nodeType === "undefined") { + this.div_.innerHTML = this.getCloseBoxImg_() + content; + } else { + this.div_.innerHTML = this.getCloseBoxImg_(); + this.div_.appendChild(content); + } + } + + this.addClickHandler_(); + } + + /** + * This event is fired when the content of the InfoBox changes. + * @name InfoBox#content_changed + * @event + */ + google.maps.event.trigger(this, "content_changed"); +}; + +/** + * Sets the geographic location of the InfoBox. + * @param {LatLng} latlng + */ +InfoBox.prototype.setPosition = function (latlng) { + + this.position_ = latlng; + + if (this.div_) { + + this.draw(); + } + + /** + * This event is fired when the position of the InfoBox changes. + * @name InfoBox#position_changed + * @event + */ + google.maps.event.trigger(this, "position_changed"); +}; + +/** + * Sets the zIndex style for the InfoBox. + * @param {number} index + */ +InfoBox.prototype.setZIndex = function (index) { + + this.zIndex_ = index; + + if (this.div_) { + + this.div_.style.zIndex = index; + } + + /** + * This event is fired when the zIndex of the InfoBox changes. + * @name InfoBox#zindex_changed + * @event + */ + google.maps.event.trigger(this, "zindex_changed"); +}; + +/** + * Sets the visibility of the InfoBox. + * @param {boolean} isVisible + */ +InfoBox.prototype.setVisible = function (isVisible) { + + this.isHidden_ = !isVisible; + if (this.div_) { + this.div_.style.visibility = (this.isHidden_ ? "hidden" : "visible"); + } +}; + +/** + * Returns the content of the InfoBox. + * @returns {string} + */ +InfoBox.prototype.getContent = function () { + + return this.content_; +}; + +/** + * Returns the geographic location of the InfoBox. + * @returns {LatLng} + */ +InfoBox.prototype.getPosition = function () { + + return this.position_; +}; + +/** + * Returns the zIndex for the InfoBox. + * @returns {number} + */ +InfoBox.prototype.getZIndex = function () { + + return this.zIndex_; +}; + +/** + * Returns a flag indicating whether the InfoBox is visible. + * @returns {boolean} + */ +InfoBox.prototype.getVisible = function () { + + var isVisible; + + if ((typeof this.getMap() === "undefined") || (this.getMap() === null)) { + isVisible = false; + } else { + isVisible = !this.isHidden_; + } + return isVisible; +}; + +/** + * Shows the InfoBox. [Deprecated; use setVisible instead.] + */ +InfoBox.prototype.show = function () { + + this.isHidden_ = false; + if (this.div_) { + this.div_.style.visibility = "visible"; + } +}; + +/** + * Hides the InfoBox. [Deprecated; use setVisible instead.] + */ +InfoBox.prototype.hide = function () { + + this.isHidden_ = true; + if (this.div_) { + this.div_.style.visibility = "hidden"; + } +}; + +/** + * Adds the InfoBox to the specified map or Street View panorama. If anchor + * (usually a google.maps.Marker) is specified, the position + * of the InfoBox is set to the position of the anchor. If the + * anchor is dragged to a new location, the InfoBox moves as well. + * @param {Map|StreetViewPanorama} map + * @param {MVCObject} [anchor] + */ +InfoBox.prototype.open = function (map, anchor) { + + var me = this; + + if (anchor) { + + this.position_ = anchor.getPosition(); + this.moveListener_ = google.maps.event.addListener(anchor, "position_changed", function () { + me.setPosition(this.getPosition()); + }); + } + + this.setMap(map); + + if (this.div_) { + + this.panBox_(); + } +}; + +/** + * Removes the InfoBox from the map. + */ +InfoBox.prototype.close = function () { + + var i; + + if (this.closeListener_) { + + google.maps.event.removeListener(this.closeListener_); + this.closeListener_ = null; + } + + if (this.eventListeners_) { + + for (i = 0; i < this.eventListeners_.length; i++) { + + google.maps.event.removeListener(this.eventListeners_[i]); + } + this.eventListeners_ = null; + } + + if (this.moveListener_) { + + google.maps.event.removeListener(this.moveListener_); + this.moveListener_ = null; + } + + if (this.contextListener_) { + + google.maps.event.removeListener(this.contextListener_); + this.contextListener_ = null; + } + + this.setMap(null); +};;/** + * @name MarkerClustererPlus for Google Maps V3 + * @version 2.1.1 [November 4, 2013] + * @author Gary Little + * @fileoverview + * The library creates and manages per-zoom-level clusters for large amounts of markers. + *

        + * This is an enhanced V3 implementation of the + * V2 MarkerClusterer by Xiaoxi Wu. It is based on the + * V3 MarkerClusterer port by Luke Mahe. MarkerClustererPlus was created by Gary Little. + *

        + * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It + * adds support for the ignoreHidden, title, batchSizeIE, + * and calculator properties as well as support for four more events. It also allows + * greater control over the styling of the text that appears on the cluster marker. The + * documentation has been significantly improved and the overall code has been simplified and + * polished. Very large numbers of markers can now be managed without causing Javascript timeout + * errors on Internet Explorer. Note that the name of the clusterclick event has been + * deprecated. The new name is click, so please change your application code now. + */ + +/** + * 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. + */ + + +/** + * @name ClusterIconStyle + * @class This class represents the object for values in the styles array passed + * to the {@link MarkerClusterer} constructor. The element in this array that is used to + * style the cluster icon is determined by calling the calculator function. + * + * @property {string} url The URL of the cluster icon image file. Required. + * @property {number} height The display height (in pixels) of the cluster icon. Required. + * @property {number} width The display width (in pixels) of the cluster icon. Required. + * @property {Array} [anchorText] The position (in pixels) from the center of the cluster icon to + * where the text label is to be centered and drawn. The format is [yoffset, xoffset] + * where yoffset increases as you go down from center and xoffset + * increases to the right of center. The default is [0, 0]. + * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the + * spot on the cluster icon that is to be aligned with the cluster position. The format is + * [yoffset, xoffset] where yoffset increases as you go down and + * xoffset increases to the right of the top-left corner of the icon. The default + * anchor position is the center of the cluster icon. + * @property {string} [textColor="black"] The color of the label text shown on the + * cluster icon. + * @property {number} [textSize=11] The size (in pixels) of the label text shown on the + * cluster icon. + * @property {string} [textDecoration="none"] The value of the CSS text-decoration + * property for the label text shown on the cluster icon. + * @property {string} [fontWeight="bold"] The value of the CSS font-weight + * property for the label text shown on the cluster icon. + * @property {string} [fontStyle="normal"] The value of the CSS font-style + * property for the label text shown on the cluster icon. + * @property {string} [fontFamily="Arial,sans-serif"] The value of the CSS font-family + * property for the label text shown on the cluster icon. + * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image + * within the image defined by url. The format is "xpos ypos" + * (the same format as for the CSS background-position property). You must set + * this property appropriately when the image defined by url represents a sprite + * containing multiple images. Note that the position must be specified in px units. + */ +/** + * @name ClusterIconInfo + * @class This class is an object containing general information about a cluster icon. This is + * the object that a calculator function returns. + * + * @property {string} text The text of the label to be shown on the cluster icon. + * @property {number} index The index plus 1 of the element in the styles + * array to be used to style the cluster icon. + * @property {string} title The tooltip to display when the mouse moves over the cluster icon. + * If this value is undefined or "", title is set to the + * value of the title property passed to the MarkerClusterer. + */ +/** + * A cluster icon. + * + * @constructor + * @extends google.maps.OverlayView + * @param {Cluster} cluster The cluster with which the icon is to be associated. + * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons + * to use for various cluster sizes. + * @private + */ +function ClusterIcon(cluster, styles) { + cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); + + this.cluster_ = cluster; + this.className_ = cluster.getMarkerClusterer().getClusterClass(); + this.styles_ = styles; + this.center_ = null; + this.div_ = null; + this.sums_ = null; + this.visible_ = false; + + this.setMap(cluster.getMap()); // Note: this causes onAdd to be called +} + + +/** + * Adds the icon to the DOM. + */ +ClusterIcon.prototype.onAdd = function () { + var cClusterIcon = this; + var cMouseDownInCluster; + var cDraggingMapByCluster; + + this.div_ = document.createElement("div"); + this.div_.className = this.className_; + if (this.visible_) { + this.show(); + } + + this.getPanes().overlayMouseTarget.appendChild(this.div_); + + // Fix for Issue 157 + this.boundsChangedListener_ = google.maps.event.addListener(this.getMap(), "bounds_changed", function () { + cDraggingMapByCluster = cMouseDownInCluster; + }); + + google.maps.event.addDomListener(this.div_, "mousedown", function () { + cMouseDownInCluster = true; + cDraggingMapByCluster = false; + }); + + google.maps.event.addDomListener(this.div_, "click", function (e) { + cMouseDownInCluster = false; + if (!cDraggingMapByCluster) { + var theBounds; + var mz; + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when a cluster marker is clicked. + * @name MarkerClusterer#click + * @param {Cluster} c The cluster that was clicked. + * @event + */ + google.maps.event.trigger(mc, "click", cClusterIcon.cluster_); + google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name + + // The default click handler follows. Disable it by setting + // the zoomOnClick property to false. + if (mc.getZoomOnClick()) { + // Zoom into the cluster. + mz = mc.getMaxZoom(); + theBounds = cClusterIcon.cluster_.getBounds(); + mc.getMap().fitBounds(theBounds); + // There is a fix for Issue 170 here: + setTimeout(function () { + mc.getMap().fitBounds(theBounds); + // Don't zoom beyond the max zoom level + if (mz !== null && (mc.getMap().getZoom() > mz)) { + mc.getMap().setZoom(mz + 1); + } + }, 100); + } + + // Prevent event propagation to the map: + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + } + }); + + google.maps.event.addDomListener(this.div_, "mouseover", function () { + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when the mouse moves over a cluster marker. + * @name MarkerClusterer#mouseover + * @param {Cluster} c The cluster that the mouse moved over. + * @event + */ + google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_); + }); + + google.maps.event.addDomListener(this.div_, "mouseout", function () { + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when the mouse moves out of a cluster marker. + * @name MarkerClusterer#mouseout + * @param {Cluster} c The cluster that the mouse moved out of. + * @event + */ + google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_); + }); +}; + + +/** + * Removes the icon from the DOM. + */ +ClusterIcon.prototype.onRemove = function () { + if (this.div_ && this.div_.parentNode) { + this.hide(); + google.maps.event.removeListener(this.boundsChangedListener_); + google.maps.event.clearInstanceListeners(this.div_); + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + } +}; + + +/** + * Draws the icon. + */ +ClusterIcon.prototype.draw = function () { + if (this.visible_) { + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.top = pos.y + "px"; + this.div_.style.left = pos.x + "px"; + } +}; + + +/** + * Hides the icon. + */ +ClusterIcon.prototype.hide = function () { + if (this.div_) { + this.div_.style.display = "none"; + } + this.visible_ = false; +}; + + +/** + * Positions and shows the icon. + */ +ClusterIcon.prototype.show = function () { + if (this.div_) { + var img = ""; + // NOTE: values must be specified in px units + var bp = this.backgroundPosition_.split(" "); + var spriteH = parseInt(bp[0].trim(), 10); + var spriteV = parseInt(bp[1].trim(), 10); + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.cssText = this.createCss(pos); + img = ""; + this.div_.innerHTML = img + "

        " + this.sums_.text + "
        "; + if (typeof this.sums_.title === "undefined" || this.sums_.title === "") { + this.div_.title = this.cluster_.getMarkerClusterer().getTitle(); + } else { + this.div_.title = this.sums_.title; + } + this.div_.style.display = ""; + } + this.visible_ = true; +}; + + +/** + * Sets the icon styles to the appropriate element in the styles array. + * + * @param {ClusterIconInfo} sums The icon label text and styles index. + */ +ClusterIcon.prototype.useStyle = function (sums) { + this.sums_ = sums; + var index = Math.max(0, sums.index - 1); + index = Math.min(this.styles_.length - 1, index); + var style = this.styles_[index]; + this.url_ = style.url; + this.height_ = style.height; + this.width_ = style.width; + this.anchorText_ = style.anchorText || [0, 0]; + this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)]; + this.textColor_ = style.textColor || "black"; + this.textSize_ = style.textSize || 11; + this.textDecoration_ = style.textDecoration || "none"; + this.fontWeight_ = style.fontWeight || "bold"; + this.fontStyle_ = style.fontStyle || "normal"; + this.fontFamily_ = style.fontFamily || "Arial,sans-serif"; + this.backgroundPosition_ = style.backgroundPosition || "0 0"; +}; + + +/** + * Sets the position at which to center the icon. + * + * @param {google.maps.LatLng} center The latlng to set as the center. + */ +ClusterIcon.prototype.setCenter = function (center) { + this.center_ = center; +}; + + +/** + * Creates the cssText style parameter based on the position of the icon. + * + * @param {google.maps.Point} pos The position of the icon. + * @return {string} The CSS style text. + */ +ClusterIcon.prototype.createCss = function (pos) { + var style = []; + style.push("cursor: pointer;"); + style.push("position: absolute; top: " + pos.y + "px; left: " + pos.x + "px;"); + style.push("width: " + this.width_ + "px; height: " + this.height_ + "px;"); + return style.join(""); +}; + + +/** + * Returns the position at which to place the DIV depending on the latlng. + * + * @param {google.maps.LatLng} latlng The position in latlng. + * @return {google.maps.Point} The position in pixels. + */ +ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) { + var pos = this.getProjection().fromLatLngToDivPixel(latlng); + pos.x -= this.anchorIcon_[1]; + pos.y -= this.anchorIcon_[0]; + pos.x = parseInt(pos.x, 10); + pos.y = parseInt(pos.y, 10); + return pos; +}; + + +/** + * Creates a single cluster that manages a group of proximate markers. + * Used internally, do not call this constructor directly. + * @constructor + * @param {MarkerClusterer} mc The MarkerClusterer object with which this + * cluster is associated. + */ +function Cluster(mc) { + this.markerClusterer_ = mc; + this.map_ = mc.getMap(); + this.gridSize_ = mc.getGridSize(); + this.minClusterSize_ = mc.getMinimumClusterSize(); + this.averageCenter_ = mc.getAverageCenter(); + this.markers_ = []; + this.center_ = null; + this.bounds_ = null; + this.clusterIcon_ = new ClusterIcon(this, mc.getStyles()); +} + + +/** + * Returns the number of markers managed by the cluster. You can call this from + * a click, mouseover, or mouseout event handler + * for the MarkerClusterer object. + * + * @return {number} The number of markers in the cluster. + */ +Cluster.prototype.getSize = function () { + return this.markers_.length; +}; + + +/** + * Returns the array of markers managed by the cluster. You can call this from + * a click, mouseover, or mouseout event handler + * for the MarkerClusterer object. + * + * @return {Array} The array of markers in the cluster. + */ +Cluster.prototype.getMarkers = function () { + return this.markers_; +}; + + +/** + * Returns the center of the cluster. You can call this from + * a click, mouseover, or mouseout event handler + * for the MarkerClusterer object. + * + * @return {google.maps.LatLng} The center of the cluster. + */ +Cluster.prototype.getCenter = function () { + return this.center_; +}; + + +/** + * Returns the map with which the cluster is associated. + * + * @return {google.maps.Map} The map. + * @ignore + */ +Cluster.prototype.getMap = function () { + return this.map_; +}; + + +/** + * Returns the MarkerClusterer object with which the cluster is associated. + * + * @return {MarkerClusterer} The associated marker clusterer. + * @ignore + */ +Cluster.prototype.getMarkerClusterer = function () { + return this.markerClusterer_; +}; + + +/** + * Returns the bounds of the cluster. + * + * @return {google.maps.LatLngBounds} the cluster bounds. + * @ignore + */ +Cluster.prototype.getBounds = function () { + var i; + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); + var markers = this.getMarkers(); + for (i = 0; i < markers.length; i++) { + bounds.extend(markers[i].getPosition()); + } + return bounds; +}; + + +/** + * Removes the cluster from the map. + * + * @ignore + */ +Cluster.prototype.remove = function () { + this.clusterIcon_.setMap(null); + this.markers_ = []; + delete this.markers_; +}; + + +/** + * Adds a marker to the cluster. + * + * @param {google.maps.Marker} marker The marker to be added. + * @return {boolean} True if the marker was added. + * @ignore + */ +Cluster.prototype.addMarker = function (marker) { + var i; + var mCount; + var mz; + + if (this.isMarkerAlreadyAdded_(marker)) { + return false; + } + + if (!this.center_) { + this.center_ = marker.getPosition(); + this.calculateBounds_(); + } else { + if (this.averageCenter_) { + var l = this.markers_.length + 1; + var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l; + var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l; + this.center_ = new google.maps.LatLng(lat, lng); + this.calculateBounds_(); + } + } + + marker.isAdded = true; + this.markers_.push(marker); + + mCount = this.markers_.length; + mz = this.markerClusterer_.getMaxZoom(); + if (mz !== null && this.map_.getZoom() > mz) { + // Zoomed in past max zoom, so show the marker. + if (marker.getMap() !== this.map_) { + marker.setMap(this.map_); + } + } else if (mCount < this.minClusterSize_) { + // Min cluster size not reached so show the marker. + if (marker.getMap() !== this.map_) { + marker.setMap(this.map_); + } + } else if (mCount === this.minClusterSize_) { + // Hide the markers that were showing. + for (i = 0; i < mCount; i++) { + this.markers_[i].setMap(null); + } + } else { + marker.setMap(null); + } + + this.updateIcon_(); + return true; +}; + + +/** + * Determines if a marker lies within the cluster's bounds. + * + * @param {google.maps.Marker} marker The marker to check. + * @return {boolean} True if the marker lies in the bounds. + * @ignore + */ +Cluster.prototype.isMarkerInClusterBounds = function (marker) { + return this.bounds_.contains(marker.getPosition()); +}; + + +/** + * Calculates the extended bounds of the cluster with the grid. + */ +Cluster.prototype.calculateBounds_ = function () { + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); + this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); +}; + + +/** + * Updates the cluster icon. + */ +Cluster.prototype.updateIcon_ = function () { + var mCount = this.markers_.length; + var mz = this.markerClusterer_.getMaxZoom(); + + if (mz !== null && this.map_.getZoom() > mz) { + this.clusterIcon_.hide(); + return; + } + + if (mCount < this.minClusterSize_) { + // Min cluster size not yet reached. + this.clusterIcon_.hide(); + return; + } + + var numStyles = this.markerClusterer_.getStyles().length; + var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); + this.clusterIcon_.setCenter(this.center_); + this.clusterIcon_.useStyle(sums); + this.clusterIcon_.show(); +}; + + +/** + * Determines if a marker has already been added to the cluster. + * + * @param {google.maps.Marker} marker The marker to check. + * @return {boolean} True if the marker has already been added. + */ +Cluster.prototype.isMarkerAlreadyAdded_ = function (marker) { + var i; + if (this.markers_.indexOf) { + return this.markers_.indexOf(marker) !== -1; + } else { + for (i = 0; i < this.markers_.length; i++) { + if (marker === this.markers_[i]) { + return true; + } + } + } + return false; +}; + + +/** + * @name MarkerClustererOptions + * @class This class represents the optional parameter passed to + * the {@link MarkerClusterer} constructor. + * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square. + * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or + * null if clustering is to be enabled at all zoom levels. + * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is + * clicked. You may want to set this to false if you have installed a handler + * for the click event and it deals with zooming on its own. + * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be + * the average position of all markers in the cluster. If set to false, the + * cluster marker is positioned at the location of the first marker added to the cluster. + * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster + * before the markers are hidden and a cluster marker appears. + * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You + * may want to set this to true to ensure that hidden markers are not included + * in the marker count that appears on a cluster marker (this count is the value of the + * text property of the result returned by the default calculator). + * If set to true and you change the visibility of a marker being clustered, be + * sure to also call MarkerClusterer.repaint(). + * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster + * marker. (Alternatively, you can use a custom calculator function to specify a + * different tooltip for each cluster marker.) + * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine + * the text to be displayed on a cluster marker and the index indicating which style to use + * for the cluster marker. The input parameters for the function are (1) the array of markers + * represented by a cluster marker and (2) the number of cluster icon styles. It returns a + * {@link ClusterIconInfo} object. The default calculator returns a + * text property which is the number of markers in the cluster and an + * index property which is one higher than the lowest integer such that + * 10^i exceeds the number of markers in the cluster, or the size of the styles + * array, whichever is less. The styles array element used has an index of + * index minus 1. For example, the default calculator returns a + * text value of "125" and an index of 3 + * for a cluster icon representing 125 markers so the element used in the styles + * array is 2. A calculator may also return a title + * property that contains the text of the tooltip to be used for the cluster marker. If + * title is not defined, the tooltip is set to the value of the title + * property for the MarkerClusterer. + * @property {string} [clusterClass="cluster"] The name of the CSS class defining general styles + * for the cluster markers. Use this class to define CSS styles that are not set up by the code + * that processes the styles array. + * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles + * of the cluster markers to be used. The element to be used to style a given cluster marker + * is determined by the function defined by the calculator property. + * The default is an array of {@link ClusterIconStyle} elements whose properties are derived + * from the values for imagePath, imageExtension, and + * imageSizes. + * @property {boolean} [enableRetinaIcons=false] Whether to allow the use of cluster icons that + * have sizes that are some multiple (typically double) of their actual display size. Icons such + * as these look better when viewed on high-resolution monitors such as Apple's Retina displays. + * Note: if this property is true, sprites cannot be used as cluster icons. + * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the + * number of markers to be processed in a single batch when using a browser other than + * Internet Explorer (for Internet Explorer, use the batchSizeIE property instead). + * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is + * being used, markers are processed in several batches with a small delay inserted between + * each batch in an attempt to avoid Javascript timeout errors. Set this property to the + * number of markers to be processed in a single batch; select as high a number as you can + * without causing a timeout error in the browser. This number might need to be as low as 100 + * if 15,000 markers are being managed, for example. + * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH] + * The full URL of the root name of the group of image files to use for cluster icons. + * The complete file name is of the form imagePathn.imageExtension + * where n is the image file number (1, 2, etc.). + * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION] + * The extension name for the cluster icon image files (e.g., "png" or + * "jpg"). + * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES] + * An array of numbers containing the widths of the group of + * imagePathn.imageExtension image files. + * (The images are assumed to be square.) + */ +/** + * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}. + * @constructor + * @extends google.maps.OverlayView + * @param {google.maps.Map} map The Google map to attach to. + * @param {Array.} [opt_markers] The markers to be added to the cluster. + * @param {MarkerClustererOptions} [opt_options] The optional parameters. + */ +function MarkerClusterer(map, opt_markers, opt_options) { + // MarkerClusterer implements google.maps.OverlayView interface. We use the + // extend function to extend MarkerClusterer with google.maps.OverlayView + // because it might not always be available when the code is defined so we + // look for it at the last possible moment. If it doesn't exist now then + // there is no point going ahead :) + this.extend(MarkerClusterer, google.maps.OverlayView); + + opt_markers = opt_markers || []; + opt_options = opt_options || {}; + + this.markers_ = []; + this.clusters_ = []; + this.listeners_ = []; + this.activeMap_ = null; + this.ready_ = false; + + this.gridSize_ = opt_options.gridSize || 60; + this.minClusterSize_ = opt_options.minimumClusterSize || 2; + this.maxZoom_ = opt_options.maxZoom || null; + this.styles_ = opt_options.styles || []; + this.title_ = opt_options.title || ""; + this.zoomOnClick_ = true; + if (opt_options.zoomOnClick !== undefined) { + this.zoomOnClick_ = opt_options.zoomOnClick; + } + this.averageCenter_ = false; + if (opt_options.averageCenter !== undefined) { + this.averageCenter_ = opt_options.averageCenter; + } + this.ignoreHidden_ = false; + if (opt_options.ignoreHidden !== undefined) { + this.ignoreHidden_ = opt_options.ignoreHidden; + } + this.enableRetinaIcons_ = false; + if (opt_options.enableRetinaIcons !== undefined) { + this.enableRetinaIcons_ = opt_options.enableRetinaIcons; + } + this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH; + this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION; + this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES; + this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR; + this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE; + this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE; + this.clusterClass_ = opt_options.clusterClass || "cluster"; + + if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) { + // Try to avoid IE timeout when processing a huge number of markers: + this.batchSize_ = this.batchSizeIE_; + } + + this.setupStyles_(); + + this.addMarkers(opt_markers, true); + this.setMap(map); // Note: this causes onAdd to be called +} + + +/** + * Implementation of the onAdd interface method. + * @ignore + */ +MarkerClusterer.prototype.onAdd = function () { + var cMarkerClusterer = this; + + this.activeMap_ = this.getMap(); + this.ready_ = true; + + this.repaint(); + + // Add the map event listeners + this.listeners_ = [ + google.maps.event.addListener(this.getMap(), "zoom_changed", function () { + cMarkerClusterer.resetViewport_(false); + // Workaround for this Google bug: when map is at level 0 and "-" of + // zoom slider is clicked, a "zoom_changed" event is fired even though + // the map doesn't zoom out any further. In this situation, no "idle" + // event is triggered so the cluster markers that have been removed + // do not get redrawn. Same goes for a zoom in at maxZoom. + if (this.getZoom() === (this.get("minZoom") || 0) || this.getZoom() === this.get("maxZoom")) { + google.maps.event.trigger(this, "idle"); + } + }), + google.maps.event.addListener(this.getMap(), "idle", function () { + cMarkerClusterer.redraw_(); + }) + ]; +}; + + +/** + * Implementation of the onRemove interface method. + * Removes map event listeners and all cluster icons from the DOM. + * All managed markers are also put back on the map. + * @ignore + */ +MarkerClusterer.prototype.onRemove = function () { + var i; + + // Put all the managed markers back on the map: + for (i = 0; i < this.markers_.length; i++) { + if (this.markers_[i].getMap() !== this.activeMap_) { + this.markers_[i].setMap(this.activeMap_); + } + } + + // Remove all clusters: + for (i = 0; i < this.clusters_.length; i++) { + this.clusters_[i].remove(); + } + this.clusters_ = []; + + // Remove map event listeners: + for (i = 0; i < this.listeners_.length; i++) { + google.maps.event.removeListener(this.listeners_[i]); + } + this.listeners_ = []; + + this.activeMap_ = null; + this.ready_ = false; +}; + + +/** + * Implementation of the draw interface method. + * @ignore + */ +MarkerClusterer.prototype.draw = function () {}; + + +/** + * Sets up the styles object. + */ +MarkerClusterer.prototype.setupStyles_ = function () { + var i, size; + if (this.styles_.length > 0) { + return; + } + + for (i = 0; i < this.imageSizes_.length; i++) { + size = this.imageSizes_[i]; + this.styles_.push({ + url: this.imagePath_ + (i + 1) + "." + this.imageExtension_, + height: size, + width: size + }); + } +}; + + +/** + * Fits the map to the bounds of the markers managed by the clusterer. + */ +MarkerClusterer.prototype.fitMapToMarkers = function () { + var i; + var markers = this.getMarkers(); + var bounds = new google.maps.LatLngBounds(); + for (i = 0; i < markers.length; i++) { + bounds.extend(markers[i].getPosition()); + } + + this.getMap().fitBounds(bounds); +}; + + +/** + * Returns the value of the gridSize property. + * + * @return {number} The grid size. + */ +MarkerClusterer.prototype.getGridSize = function () { + return this.gridSize_; +}; + + +/** + * Sets the value of the gridSize property. + * + * @param {number} gridSize The grid size. + */ +MarkerClusterer.prototype.setGridSize = function (gridSize) { + this.gridSize_ = gridSize; +}; + + +/** + * Returns the value of the minimumClusterSize property. + * + * @return {number} The minimum cluster size. + */ +MarkerClusterer.prototype.getMinimumClusterSize = function () { + return this.minClusterSize_; +}; + +/** + * Sets the value of the minimumClusterSize property. + * + * @param {number} minimumClusterSize The minimum cluster size. + */ +MarkerClusterer.prototype.setMinimumClusterSize = function (minimumClusterSize) { + this.minClusterSize_ = minimumClusterSize; +}; + + +/** + * Returns the value of the maxZoom property. + * + * @return {number} The maximum zoom level. + */ +MarkerClusterer.prototype.getMaxZoom = function () { + return this.maxZoom_; +}; + + +/** + * Sets the value of the maxZoom property. + * + * @param {number} maxZoom The maximum zoom level. + */ +MarkerClusterer.prototype.setMaxZoom = function (maxZoom) { + this.maxZoom_ = maxZoom; +}; + + +/** + * Returns the value of the styles property. + * + * @return {Array} The array of styles defining the cluster markers to be used. + */ +MarkerClusterer.prototype.getStyles = function () { + return this.styles_; +}; + + +/** + * Sets the value of the styles property. + * + * @param {Array.} styles The array of styles to use. + */ +MarkerClusterer.prototype.setStyles = function (styles) { + this.styles_ = styles; +}; + + +/** + * Returns the value of the title property. + * + * @return {string} The content of the title text. + */ +MarkerClusterer.prototype.getTitle = function () { + return this.title_; +}; + + +/** + * Sets the value of the title property. + * + * @param {string} title The value of the title property. + */ +MarkerClusterer.prototype.setTitle = function (title) { + this.title_ = title; +}; + + +/** + * Returns the value of the zoomOnClick property. + * + * @return {boolean} True if zoomOnClick property is set. + */ +MarkerClusterer.prototype.getZoomOnClick = function () { + return this.zoomOnClick_; +}; + + +/** + * Sets the value of the zoomOnClick property. + * + * @param {boolean} zoomOnClick The value of the zoomOnClick property. + */ +MarkerClusterer.prototype.setZoomOnClick = function (zoomOnClick) { + this.zoomOnClick_ = zoomOnClick; +}; + + +/** + * Returns the value of the averageCenter property. + * + * @return {boolean} True if averageCenter property is set. + */ +MarkerClusterer.prototype.getAverageCenter = function () { + return this.averageCenter_; +}; + + +/** + * Sets the value of the averageCenter property. + * + * @param {boolean} averageCenter The value of the averageCenter property. + */ +MarkerClusterer.prototype.setAverageCenter = function (averageCenter) { + this.averageCenter_ = averageCenter; +}; + + +/** + * Returns the value of the ignoreHidden property. + * + * @return {boolean} True if ignoreHidden property is set. + */ +MarkerClusterer.prototype.getIgnoreHidden = function () { + return this.ignoreHidden_; +}; + + +/** + * Sets the value of the ignoreHidden property. + * + * @param {boolean} ignoreHidden The value of the ignoreHidden property. + */ +MarkerClusterer.prototype.setIgnoreHidden = function (ignoreHidden) { + this.ignoreHidden_ = ignoreHidden; +}; + + +/** + * Returns the value of the enableRetinaIcons property. + * + * @return {boolean} True if enableRetinaIcons property is set. + */ +MarkerClusterer.prototype.getEnableRetinaIcons = function () { + return this.enableRetinaIcons_; +}; + + +/** + * Sets the value of the enableRetinaIcons property. + * + * @param {boolean} enableRetinaIcons The value of the enableRetinaIcons property. + */ +MarkerClusterer.prototype.setEnableRetinaIcons = function (enableRetinaIcons) { + this.enableRetinaIcons_ = enableRetinaIcons; +}; + + +/** + * Returns the value of the imageExtension property. + * + * @return {string} The value of the imageExtension property. + */ +MarkerClusterer.prototype.getImageExtension = function () { + return this.imageExtension_; +}; + + +/** + * Sets the value of the imageExtension property. + * + * @param {string} imageExtension The value of the imageExtension property. + */ +MarkerClusterer.prototype.setImageExtension = function (imageExtension) { + this.imageExtension_ = imageExtension; +}; + + +/** + * Returns the value of the imagePath property. + * + * @return {string} The value of the imagePath property. + */ +MarkerClusterer.prototype.getImagePath = function () { + return this.imagePath_; +}; + + +/** + * Sets the value of the imagePath property. + * + * @param {string} imagePath The value of the imagePath property. + */ +MarkerClusterer.prototype.setImagePath = function (imagePath) { + this.imagePath_ = imagePath; +}; + + +/** + * Returns the value of the imageSizes property. + * + * @return {Array} The value of the imageSizes property. + */ +MarkerClusterer.prototype.getImageSizes = function () { + return this.imageSizes_; +}; + + +/** + * Sets the value of the imageSizes property. + * + * @param {Array} imageSizes The value of the imageSizes property. + */ +MarkerClusterer.prototype.setImageSizes = function (imageSizes) { + this.imageSizes_ = imageSizes; +}; + + +/** + * Returns the value of the calculator property. + * + * @return {function} the value of the calculator property. + */ +MarkerClusterer.prototype.getCalculator = function () { + return this.calculator_; +}; + + +/** + * Sets the value of the calculator property. + * + * @param {function(Array., number)} calculator The value + * of the calculator property. + */ +MarkerClusterer.prototype.setCalculator = function (calculator) { + this.calculator_ = calculator; +}; + + +/** + * Returns the value of the batchSizeIE property. + * + * @return {number} the value of the batchSizeIE property. + */ +MarkerClusterer.prototype.getBatchSizeIE = function () { + return this.batchSizeIE_; +}; + + +/** + * Sets the value of the batchSizeIE property. + * + * @param {number} batchSizeIE The value of the batchSizeIE property. + */ +MarkerClusterer.prototype.setBatchSizeIE = function (batchSizeIE) { + this.batchSizeIE_ = batchSizeIE; +}; + + +/** + * Returns the value of the clusterClass property. + * + * @return {string} the value of the clusterClass property. + */ +MarkerClusterer.prototype.getClusterClass = function () { + return this.clusterClass_; +}; + + +/** + * Sets the value of the clusterClass property. + * + * @param {string} clusterClass The value of the clusterClass property. + */ +MarkerClusterer.prototype.setClusterClass = function (clusterClass) { + this.clusterClass_ = clusterClass; +}; + + +/** + * Returns the array of markers managed by the clusterer. + * + * @return {Array} The array of markers managed by the clusterer. + */ +MarkerClusterer.prototype.getMarkers = function () { + return this.markers_; +}; + + +/** + * Returns the number of markers managed by the clusterer. + * + * @return {number} The number of markers. + */ +MarkerClusterer.prototype.getTotalMarkers = function () { + return this.markers_.length; +}; + + +/** + * Returns the current array of clusters formed by the clusterer. + * + * @return {Array} The array of clusters formed by the clusterer. + */ +MarkerClusterer.prototype.getClusters = function () { + return this.clusters_; +}; + + +/** + * Returns the number of clusters formed by the clusterer. + * + * @return {number} The number of clusters formed by the clusterer. + */ +MarkerClusterer.prototype.getTotalClusters = function () { + return this.clusters_.length; +}; + + +/** + * Adds a marker to the clusterer. The clusters are redrawn unless + * opt_nodraw is set to true. + * + * @param {google.maps.Marker} marker The marker to add. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + */ +MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) { + this.pushMarkerTo_(marker); + if (!opt_nodraw) { + this.redraw_(); + } +}; + + +/** + * Adds an array of markers to the clusterer. The clusters are redrawn unless + * opt_nodraw is set to true. + * + * @param {Array.} markers The markers to add. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + */ +MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) { + var key; + for (key in markers) { + if (markers.hasOwnProperty(key)) { + this.pushMarkerTo_(markers[key]); + } + } + if (!opt_nodraw) { + this.redraw_(); + } +}; + + +/** + * Pushes a marker to the clusterer. + * + * @param {google.maps.Marker} marker The marker to add. + */ +MarkerClusterer.prototype.pushMarkerTo_ = function (marker) { + // If the marker is draggable add a listener so we can update the clusters on the dragend: + if (marker.getDraggable()) { + var cMarkerClusterer = this; + google.maps.event.addListener(marker, "dragend", function () { + if (cMarkerClusterer.ready_) { + this.isAdded = false; + cMarkerClusterer.repaint(); + } + }); + } + marker.isAdded = false; + this.markers_.push(marker); +}; + + +/** + * Removes a marker from the cluster. The clusters are redrawn unless + * opt_nodraw is set to true. Returns true if the + * marker was removed from the clusterer. + * + * @param {google.maps.Marker} marker The marker to remove. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + * @return {boolean} True if the marker was removed from the clusterer. + */ +MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) { + var removed = this.removeMarker_(marker); + + if (!opt_nodraw && removed) { + this.repaint(); + } + + return removed; +}; + + +/** + * Removes an array of markers from the cluster. The clusters are redrawn unless + * opt_nodraw is set to true. Returns true if markers + * were removed from the clusterer. + * + * @param {Array.} markers The markers to remove. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + * @return {boolean} True if markers were removed from the clusterer. + */ +MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) { + var i, r; + var removed = false; + + for (i = 0; i < markers.length; i++) { + r = this.removeMarker_(markers[i]); + removed = removed || r; + } + + if (!opt_nodraw && removed) { + this.repaint(); + } + + return removed; +}; + + +/** + * Removes a marker and returns true if removed, false if not. + * + * @param {google.maps.Marker} marker The marker to remove + * @return {boolean} Whether the marker was removed or not + */ +MarkerClusterer.prototype.removeMarker_ = function (marker) { + var i; + var index = -1; + if (this.markers_.indexOf) { + index = this.markers_.indexOf(marker); + } else { + for (i = 0; i < this.markers_.length; i++) { + if (marker === this.markers_[i]) { + index = i; + break; + } + } + } + + if (index === -1) { + // Marker is not in our list of markers, so do nothing: + return false; + } + + marker.setMap(null); + this.markers_.splice(index, 1); // Remove the marker from the list of managed markers + return true; +}; + + +/** + * Removes all clusters and markers from the map and also removes all markers + * managed by the clusterer. + */ +MarkerClusterer.prototype.clearMarkers = function () { + this.resetViewport_(true); + this.markers_ = []; +}; + + +/** + * Recalculates and redraws all the marker clusters from scratch. + * Call this after changing any properties. + */ +MarkerClusterer.prototype.repaint = function () { + var oldClusters = this.clusters_.slice(); + this.clusters_ = []; + this.resetViewport_(false); + this.redraw_(); + + // Remove the old clusters. + // Do it in a timeout to prevent blinking effect. + setTimeout(function () { + var i; + for (i = 0; i < oldClusters.length; i++) { + oldClusters[i].remove(); + } + }, 0); +}; + + +/** + * Returns the current bounds extended by the grid size. + * + * @param {google.maps.LatLngBounds} bounds The bounds to extend. + * @return {google.maps.LatLngBounds} The extended bounds. + * @ignore + */ +MarkerClusterer.prototype.getExtendedBounds = function (bounds) { + var projection = this.getProjection(); + + // Turn the bounds into latlng. + var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), + bounds.getNorthEast().lng()); + var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), + bounds.getSouthWest().lng()); + + // Convert the points to pixels and the extend out by the grid size. + var trPix = projection.fromLatLngToDivPixel(tr); + trPix.x += this.gridSize_; + trPix.y -= this.gridSize_; + + var blPix = projection.fromLatLngToDivPixel(bl); + blPix.x -= this.gridSize_; + blPix.y += this.gridSize_; + + // Convert the pixel points back to LatLng + var ne = projection.fromDivPixelToLatLng(trPix); + var sw = projection.fromDivPixelToLatLng(blPix); + + // Extend the bounds to contain the new bounds. + bounds.extend(ne); + bounds.extend(sw); + + return bounds; +}; + + +/** + * Redraws all the clusters. + */ +MarkerClusterer.prototype.redraw_ = function () { + this.createClusters_(0); +}; + + +/** + * Removes all clusters from the map. The markers are also removed from the map + * if opt_hide is set to true. + * + * @param {boolean} [opt_hide] Set to true to also remove the markers + * from the map. + */ +MarkerClusterer.prototype.resetViewport_ = function (opt_hide) { + var i, marker; + // Remove all the clusters + for (i = 0; i < this.clusters_.length; i++) { + this.clusters_[i].remove(); + } + this.clusters_ = []; + + // Reset the markers to not be added and to be removed from the map. + for (i = 0; i < this.markers_.length; i++) { + marker = this.markers_[i]; + marker.isAdded = false; + if (opt_hide) { + marker.setMap(null); + } + } +}; + + +/** + * Calculates the distance between two latlng locations in km. + * + * @param {google.maps.LatLng} p1 The first lat lng point. + * @param {google.maps.LatLng} p2 The second lat lng point. + * @return {number} The distance between the two points in km. + * @see http://www.movable-type.co.uk/scripts/latlong.html + */ +MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) { + var R = 6371; // Radius of the Earth in km + var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; + var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; + var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + var d = R * c; + return d; +}; + + +/** + * Determines if a marker is contained in a bounds. + * + * @param {google.maps.Marker} marker The marker to check. + * @param {google.maps.LatLngBounds} bounds The bounds to check against. + * @return {boolean} True if the marker is in the bounds. + */ +MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) { + return bounds.contains(marker.getPosition()); +}; + + +/** + * Adds a marker to a cluster, or creates a new cluster. + * + * @param {google.maps.Marker} marker The marker to add. + */ +MarkerClusterer.prototype.addToClosestCluster_ = function (marker) { + var i, d, cluster, center; + var distance = 40000; // Some large number + var clusterToAddTo = null; + for (i = 0; i < this.clusters_.length; i++) { + cluster = this.clusters_[i]; + center = cluster.getCenter(); + if (center) { + d = this.distanceBetweenPoints_(center, marker.getPosition()); + if (d < distance) { + distance = d; + clusterToAddTo = cluster; + } + } + } + + if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { + clusterToAddTo.addMarker(marker); + } else { + cluster = new Cluster(this); + cluster.addMarker(marker); + this.clusters_.push(cluster); + } +}; + + +/** + * Creates the clusters. This is done in batches to avoid timeout errors + * in some browsers when there is a huge number of markers. + * + * @param {number} iFirst The index of the first marker in the batch of + * markers to be added to clusters. + */ +MarkerClusterer.prototype.createClusters_ = function (iFirst) { + var i, marker; + var mapBounds; + var cMarkerClusterer = this; + if (!this.ready_) { + return; + } + + // Cancel previous batch processing if we're working on the first batch: + if (iFirst === 0) { + /** + * This event is fired when the MarkerClusterer begins + * clustering markers. + * @name MarkerClusterer#clusteringbegin + * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. + * @event + */ + google.maps.event.trigger(this, "clusteringbegin", this); + + if (typeof this.timerRefStatic !== "undefined") { + clearTimeout(this.timerRefStatic); + delete this.timerRefStatic; + } + } + + // Get our current map view bounds. + // Create a new bounds object so we don't affect the map. + // + // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug: + if (this.getMap().getZoom() > 3) { + mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(), + this.getMap().getBounds().getNorthEast()); + } else { + mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625)); + } + var bounds = this.getExtendedBounds(mapBounds); + + var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length); + + for (i = iFirst; i < iLast; i++) { + marker = this.markers_[i]; + if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { + if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) { + this.addToClosestCluster_(marker); + } + } + } + + if (iLast < this.markers_.length) { + this.timerRefStatic = setTimeout(function () { + cMarkerClusterer.createClusters_(iLast); + }, 0); + } else { + delete this.timerRefStatic; + + /** + * This event is fired when the MarkerClusterer stops + * clustering markers. + * @name MarkerClusterer#clusteringend + * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. + * @event + */ + google.maps.event.trigger(this, "clusteringend", this); + } +}; + + +/** + * Extends an object's prototype by another's. + * + * @param {Object} obj1 The object to be extended. + * @param {Object} obj2 The object to extend with. + * @return {Object} The new extended object. + * @ignore + */ +MarkerClusterer.prototype.extend = function (obj1, obj2) { + return (function (object) { + var property; + for (property in object.prototype) { + this.prototype[property] = object.prototype[property]; + } + return this; + }).apply(obj1, [obj2]); +}; + + +/** + * The default function for determining the label text and style + * for a cluster icon. + * + * @param {Array.} markers The array of markers represented by the cluster. + * @param {number} numStyles The number of marker styles available. + * @return {ClusterIconInfo} The information resource for the cluster. + * @constant + * @ignore + */ +MarkerClusterer.CALCULATOR = function (markers, numStyles) { + var index = 0; + var title = ""; + var count = markers.length.toString(); + + var dv = count; + while (dv !== 0) { + dv = parseInt(dv / 10, 10); + index++; + } + + index = Math.min(index, numStyles); + return { + text: count, + index: index, + title: title + }; +}; + + +/** + * The number of markers to process in one batch. + * + * @type {number} + * @constant + */ +MarkerClusterer.BATCH_SIZE = 2000; + + +/** + * The number of markers to process in one batch (IE only). + * + * @type {number} + * @constant + */ +MarkerClusterer.BATCH_SIZE_IE = 500; + + +/** + * The default root name for the marker cluster images. + * + * @type {string} + * @constant + */ +MarkerClusterer.IMAGE_PATH = "http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m"; + + +/** + * The default extension name for the marker cluster images. + * + * @type {string} + * @constant + */ +MarkerClusterer.IMAGE_EXTENSION = "png"; + + +/** + * The default array of sizes for the marker cluster images. + * + * @type {Array.} + * @constant + */ +MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90]; + +if (typeof String.prototype.trim !== 'function') { + /** + * IE hack since trim() doesn't exist in all browsers + * @return {string} The string with removed whitespace + */ + String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g, ''); + } +} + +;/** + * 1.1.9-patched + * @name MarkerWithLabel for V3 + * @version 1.1.8 [February 26, 2013] + * @author Gary Little (inspired by code from Marc Ridey of Google). + * @copyright Copyright 2012 Gary Little [gary at luxcentral.com] + * @fileoverview MarkerWithLabel extends the Google Maps JavaScript API V3 + * google.maps.Marker class. + *

        + * MarkerWithLabel allows you to define markers with associated labels. As you would expect, + * if the marker is draggable, so too will be the label. In addition, a marker with a label + * responds to all mouse events in the same manner as a regular marker. It also fires mouse + * events and "property changed" events just as a regular marker would. Version 1.1 adds + * support for the raiseOnDrag feature introduced in API V3.3. + *

        + * If you drag a marker by its label, you can cancel the drag and return the marker to its + * original position by pressing the Esc key. This doesn't work if you drag the marker + * itself because this feature is not (yet) supported in the google.maps.Marker class. + */ + +/*! + * + * 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. + */ + +/*jslint browser:true */ +/*global document,google */ + +/** + * @param {Function} childCtor Child class. + * @param {Function} parentCtor Parent class. + */ +function inherits(childCtor, parentCtor) { + /** @constructor */ + function tempCtor() {} + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor(); + /** @override */ + childCtor.prototype.constructor = childCtor; +} + +/** + * This constructor creates a label and associates it with a marker. + * It is for the private use of the MarkerWithLabel class. + * @constructor + * @param {Marker} marker The marker with which the label is to be associated. + * @param {string} crossURL The URL of the cross image =. + * @param {string} handCursor The URL of the hand cursor. + * @private + */ +function MarkerLabel_(marker, crossURL, handCursorURL) { + this.marker_ = marker; + this.handCursorURL_ = marker.handCursorURL; + + this.labelDiv_ = document.createElement("div"); + this.labelDiv_.style.cssText = "position: absolute; overflow: hidden;"; + + // Set up the DIV for handling mouse events in the label. This DIV forms a transparent veil + // in the "overlayMouseTarget" pane, a veil that covers just the label. This is done so that + // events can be captured even if the label is in the shadow of a google.maps.InfoWindow. + // Code is included here to ensure the veil is always exactly the same size as the label. + this.eventDiv_ = document.createElement("div"); + this.eventDiv_.style.cssText = this.labelDiv_.style.cssText; + + // This is needed for proper behavior on MSIE: + this.eventDiv_.setAttribute("onselectstart", "return false;"); + this.eventDiv_.setAttribute("ondragstart", "return false;"); + + // Get the DIV for the "X" to be displayed when the marker is raised. + this.crossDiv_ = MarkerLabel_.getSharedCross(crossURL); +} +inherits(MarkerLabel_, google.maps.OverlayView); + +/** + * Returns the DIV for the cross used when dragging a marker when the + * raiseOnDrag parameter set to true. One cross is shared with all markers. + * @param {string} crossURL The URL of the cross image =. + * @private + */ +MarkerLabel_.getSharedCross = function (crossURL) { + var div; + if (typeof MarkerLabel_.getSharedCross.crossDiv === "undefined") { + div = document.createElement("img"); + div.style.cssText = "position: absolute; z-index: 1000002; display: none;"; + // Hopefully Google never changes the standard "X" attributes: + div.style.marginLeft = "-8px"; + div.style.marginTop = "-9px"; + div.src = crossURL; + MarkerLabel_.getSharedCross.crossDiv = div; + } + return MarkerLabel_.getSharedCross.crossDiv; +}; + +/** + * Adds the DIV representing the label to the DOM. This method is called + * automatically when the marker's setMap method is called. + * @private + */ +MarkerLabel_.prototype.onAdd = function () { + var me = this; + var cMouseIsDown = false; + var cDraggingLabel = false; + var cSavedZIndex; + var cLatOffset, cLngOffset; + var cIgnoreClick; + var cRaiseEnabled; + var cStartPosition; + var cStartCenter; + // Constants: + var cRaiseOffset = 20; + var cDraggingCursor = "url(" + this.handCursorURL_ + ")"; + + // Stops all processing of an event. + // + var cAbortEvent = function (e) { + if (e.preventDefault) { + e.preventDefault(); + } + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + }; + + var cStopBounce = function () { + me.marker_.setAnimation(null); + }; + + this.getPanes().overlayImage.appendChild(this.labelDiv_); + this.getPanes().overlayMouseTarget.appendChild(this.eventDiv_); + // One cross is shared with all markers, so only add it once: + if (typeof MarkerLabel_.getSharedCross.processed === "undefined") { + this.getPanes().overlayImage.appendChild(this.crossDiv_); + MarkerLabel_.getSharedCross.processed = true; + } + + this.listeners_ = [ + google.maps.event.addDomListener(this.eventDiv_, "mouseover", function (e) { + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + this.style.cursor = "pointer"; + google.maps.event.trigger(me.marker_, "mouseover", e); + } + }), + google.maps.event.addDomListener(this.eventDiv_, "mouseout", function (e) { + if ((me.marker_.getDraggable() || me.marker_.getClickable()) && !cDraggingLabel) { + this.style.cursor = me.marker_.getCursor(); + google.maps.event.trigger(me.marker_, "mouseout", e); + } + }), + google.maps.event.addDomListener(this.eventDiv_, "mousedown", function (e) { + cDraggingLabel = false; + if (me.marker_.getDraggable()) { + cMouseIsDown = true; + this.style.cursor = cDraggingCursor; + } + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + google.maps.event.trigger(me.marker_, "mousedown", e); + cAbortEvent(e); // Prevent map pan when starting a drag on a label + } + }), + google.maps.event.addDomListener(document, "mouseup", function (mEvent) { + var position; + if (cMouseIsDown) { + cMouseIsDown = false; + me.eventDiv_.style.cursor = "pointer"; + google.maps.event.trigger(me.marker_, "mouseup", mEvent); + } + if (cDraggingLabel) { + if (cRaiseEnabled) { // Lower the marker & label + position = me.getProjection().fromLatLngToDivPixel(me.marker_.getPosition()); + position.y += cRaiseOffset; + me.marker_.setPosition(me.getProjection().fromDivPixelToLatLng(position)); + // This is not the same bouncing style as when the marker portion is dragged, + // but it will have to do: + try { // Will fail if running Google Maps API earlier than V3.3 + me.marker_.setAnimation(google.maps.Animation.BOUNCE); + setTimeout(cStopBounce, 1406); + } catch (e) {} + } + me.crossDiv_.style.display = "none"; + me.marker_.setZIndex(cSavedZIndex); + cIgnoreClick = true; // Set flag to ignore the click event reported after a label drag + cDraggingLabel = false; + mEvent.latLng = me.marker_.getPosition(); + google.maps.event.trigger(me.marker_, "dragend", mEvent); + } + }), + google.maps.event.addListener(me.marker_.getMap(), "mousemove", function (mEvent) { + var position; + if (cMouseIsDown) { + if (cDraggingLabel) { + // Change the reported location from the mouse position to the marker position: + mEvent.latLng = new google.maps.LatLng(mEvent.latLng.lat() - cLatOffset, mEvent.latLng.lng() - cLngOffset); + position = me.getProjection().fromLatLngToDivPixel(mEvent.latLng); + if (cRaiseEnabled) { + me.crossDiv_.style.left = position.x + "px"; + me.crossDiv_.style.top = position.y + "px"; + me.crossDiv_.style.display = ""; + position.y -= cRaiseOffset; + } + me.marker_.setPosition(me.getProjection().fromDivPixelToLatLng(position)); + if (cRaiseEnabled) { // Don't raise the veil; this hack needed to make MSIE act properly + me.eventDiv_.style.top = (position.y + cRaiseOffset) + "px"; + } + google.maps.event.trigger(me.marker_, "drag", mEvent); + } else { + // Calculate offsets from the click point to the marker position: + cLatOffset = mEvent.latLng.lat() - me.marker_.getPosition().lat(); + cLngOffset = mEvent.latLng.lng() - me.marker_.getPosition().lng(); + cSavedZIndex = me.marker_.getZIndex(); + cStartPosition = me.marker_.getPosition(); + cStartCenter = me.marker_.getMap().getCenter(); + cRaiseEnabled = me.marker_.get("raiseOnDrag"); + cDraggingLabel = true; + me.marker_.setZIndex(1000000); // Moves the marker & label to the foreground during a drag + mEvent.latLng = me.marker_.getPosition(); + google.maps.event.trigger(me.marker_, "dragstart", mEvent); + } + } + }), + google.maps.event.addDomListener(document, "keydown", function (e) { + if (cDraggingLabel) { + if (e.keyCode === 27) { // Esc key + cRaiseEnabled = false; + me.marker_.setPosition(cStartPosition); + me.marker_.getMap().setCenter(cStartCenter); + google.maps.event.trigger(document, "mouseup", e); + } + } + }), + google.maps.event.addDomListener(this.eventDiv_, "click", function (e) { + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + if (cIgnoreClick) { // Ignore the click reported when a label drag ends + cIgnoreClick = false; + } else { + google.maps.event.trigger(me.marker_, "click", e); + cAbortEvent(e); // Prevent click from being passed on to map + } + } + }), + google.maps.event.addDomListener(this.eventDiv_, "dblclick", function (e) { + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + google.maps.event.trigger(me.marker_, "dblclick", e); + cAbortEvent(e); // Prevent map zoom when double-clicking on a label + } + }), + google.maps.event.addListener(this.marker_, "dragstart", function (mEvent) { + if (!cDraggingLabel) { + cRaiseEnabled = this.get("raiseOnDrag"); + } + }), + google.maps.event.addListener(this.marker_, "drag", function (mEvent) { + if (!cDraggingLabel) { + if (cRaiseEnabled) { + me.setPosition(cRaiseOffset); + // During a drag, the marker's z-index is temporarily set to 1000000 to + // ensure it appears above all other markers. Also set the label's z-index + // to 1000000 (plus or minus 1 depending on whether the label is supposed + // to be above or below the marker). + me.labelDiv_.style.zIndex = 1000000 + (this.get("labelInBackground") ? -1 : +1); + } + } + }), + google.maps.event.addListener(this.marker_, "dragend", function (mEvent) { + if (!cDraggingLabel) { + if (cRaiseEnabled) { + me.setPosition(0); // Also restores z-index of label + } + } + }), + google.maps.event.addListener(this.marker_, "position_changed", function () { + me.setPosition(); + }), + google.maps.event.addListener(this.marker_, "zindex_changed", function () { + me.setZIndex(); + }), + google.maps.event.addListener(this.marker_, "visible_changed", function () { + me.setVisible(); + }), + google.maps.event.addListener(this.marker_, "labelvisible_changed", function () { + me.setVisible(); + }), + google.maps.event.addListener(this.marker_, "title_changed", function () { + me.setTitle(); + }), + google.maps.event.addListener(this.marker_, "labelcontent_changed", function () { + me.setContent(); + }), + google.maps.event.addListener(this.marker_, "labelanchor_changed", function () { + me.setAnchor(); + }), + google.maps.event.addListener(this.marker_, "labelclass_changed", function () { + me.setStyles(); + }), + google.maps.event.addListener(this.marker_, "labelstyle_changed", function () { + me.setStyles(); + }) + ]; +}; + +/** + * Removes the DIV for the label from the DOM. It also removes all event handlers. + * This method is called automatically when the marker's setMap(null) + * method is called. + * @private + */ +MarkerLabel_.prototype.onRemove = function () { + var i; + if (this.labelDiv_.parentNode !== null) + this.labelDiv_.parentNode.removeChild(this.labelDiv_); + if (this.eventDiv_.parentNode !== null) + this.eventDiv_.parentNode.removeChild(this.eventDiv_); + + // Remove event listeners: + for (i = 0; i < this.listeners_.length; i++) { + google.maps.event.removeListener(this.listeners_[i]); + } +}; + +/** + * Draws the label on the map. + * @private + */ +MarkerLabel_.prototype.draw = function () { + this.setContent(); + this.setTitle(); + this.setStyles(); +}; + +/** + * Sets the content of the label. + * The content can be plain text or an HTML DOM node. + * @private + */ +MarkerLabel_.prototype.setContent = function () { + var content = this.marker_.get("labelContent"); + if (typeof content.nodeType === "undefined") { + this.labelDiv_.innerHTML = content; + this.eventDiv_.innerHTML = this.labelDiv_.innerHTML; + } else { + this.labelDiv_.innerHTML = ""; // Remove current content + this.labelDiv_.appendChild(content); + content = content.cloneNode(true); + this.eventDiv_.appendChild(content); + } +}; + +/** + * Sets the content of the tool tip for the label. It is + * always set to be the same as for the marker itself. + * @private + */ +MarkerLabel_.prototype.setTitle = function () { + this.eventDiv_.title = this.marker_.getTitle() || ""; +}; + +/** + * Sets the style of the label by setting the style sheet and applying + * other specific styles requested. + * @private + */ +MarkerLabel_.prototype.setStyles = function () { + var i, labelStyle; + + // Apply style values from the style sheet defined in the labelClass parameter: + this.labelDiv_.className = this.marker_.get("labelClass"); + this.eventDiv_.className = this.labelDiv_.className; + + // Clear existing inline style values: + this.labelDiv_.style.cssText = ""; + this.eventDiv_.style.cssText = ""; + // Apply style values defined in the labelStyle parameter: + labelStyle = this.marker_.get("labelStyle"); + for (i in labelStyle) { + if (labelStyle.hasOwnProperty(i)) { + this.labelDiv_.style[i] = labelStyle[i]; + this.eventDiv_.style[i] = labelStyle[i]; + } + } + this.setMandatoryStyles(); +}; + +/** + * Sets the mandatory styles to the DIV representing the label as well as to the + * associated event DIV. This includes setting the DIV position, z-index, and visibility. + * @private + */ +MarkerLabel_.prototype.setMandatoryStyles = function () { + this.labelDiv_.style.position = "absolute"; + this.labelDiv_.style.overflow = "hidden"; + // Make sure the opacity setting causes the desired effect on MSIE: + if (typeof this.labelDiv_.style.opacity !== "undefined" && this.labelDiv_.style.opacity !== "") { + this.labelDiv_.style.MsFilter = "\"progid:DXImageTransform.Microsoft.Alpha(opacity=" + (this.labelDiv_.style.opacity * 100) + ")\""; + this.labelDiv_.style.filter = "alpha(opacity=" + (this.labelDiv_.style.opacity * 100) + ")"; + } + + this.eventDiv_.style.position = this.labelDiv_.style.position; + this.eventDiv_.style.overflow = this.labelDiv_.style.overflow; + this.eventDiv_.style.opacity = 0.01; // Don't use 0; DIV won't be clickable on MSIE + this.eventDiv_.style.MsFilter = "\"progid:DXImageTransform.Microsoft.Alpha(opacity=1)\""; + this.eventDiv_.style.filter = "alpha(opacity=1)"; // For MSIE + + this.setAnchor(); + this.setPosition(); // This also updates z-index, if necessary. + this.setVisible(); +}; + +/** + * Sets the anchor point of the label. + * @private + */ +MarkerLabel_.prototype.setAnchor = function () { + var anchor = this.marker_.get("labelAnchor"); + this.labelDiv_.style.marginLeft = -anchor.x + "px"; + this.labelDiv_.style.marginTop = -anchor.y + "px"; + this.eventDiv_.style.marginLeft = -anchor.x + "px"; + this.eventDiv_.style.marginTop = -anchor.y + "px"; +}; + +/** + * Sets the position of the label. The z-index is also updated, if necessary. + * @private + */ +MarkerLabel_.prototype.setPosition = function (yOffset) { + var position = this.getProjection().fromLatLngToDivPixel(this.marker_.getPosition()); + if (typeof yOffset === "undefined") { + yOffset = 0; + } + this.labelDiv_.style.left = Math.round(position.x) + "px"; + this.labelDiv_.style.top = Math.round(position.y - yOffset) + "px"; + this.eventDiv_.style.left = this.labelDiv_.style.left; + this.eventDiv_.style.top = this.labelDiv_.style.top; + + this.setZIndex(); +}; + +/** + * Sets the z-index of the label. If the marker's z-index property has not been defined, the z-index + * of the label is set to the vertical coordinate of the label. This is in keeping with the default + * stacking order for Google Maps: markers to the south are in front of markers to the north. + * @private + */ +MarkerLabel_.prototype.setZIndex = function () { + var zAdjust = (this.marker_.get("labelInBackground") ? -1 : +1); + if (typeof this.marker_.getZIndex() === "undefined") { + this.labelDiv_.style.zIndex = parseInt(this.labelDiv_.style.top, 10) + zAdjust; + this.eventDiv_.style.zIndex = this.labelDiv_.style.zIndex; + } else { + this.labelDiv_.style.zIndex = this.marker_.getZIndex() + zAdjust; + this.eventDiv_.style.zIndex = this.labelDiv_.style.zIndex; + } +}; + +/** + * Sets the visibility of the label. The label is visible only if the marker itself is + * visible (i.e., its visible property is true) and the labelVisible property is true. + * @private + */ +MarkerLabel_.prototype.setVisible = function () { + if (this.marker_.get("labelVisible")) { + this.labelDiv_.style.display = this.marker_.getVisible() ? "block" : "none"; + } else { + this.labelDiv_.style.display = "none"; + } + this.eventDiv_.style.display = this.labelDiv_.style.display; +}; + +/** + * @name MarkerWithLabelOptions + * @class This class represents the optional parameter passed to the {@link MarkerWithLabel} constructor. + * The properties available are the same as for google.maps.Marker with the addition + * of the properties listed below. To change any of these additional properties after the labeled + * marker has been created, call google.maps.Marker.set(propertyName, propertyValue). + *

        + * When any of these properties changes, a property changed event is fired. The names of these + * events are derived from the name of the property and are of the form propertyname_changed. + * For example, if the content of the label changes, a labelcontent_changed event + * is fired. + *

        + * @property {string|Node} [labelContent] The content of the label (plain text or an HTML DOM node). + * @property {Point} [labelAnchor] By default, a label is drawn with its anchor point at (0,0) so + * that its top left corner is positioned at the anchor point of the associated marker. Use this + * property to change the anchor point of the label. For example, to center a 50px-wide label + * beneath a marker, specify a labelAnchor of google.maps.Point(25, 0). + * (Note: x-values increase to the right and y-values increase to the top.) + * @property {string} [labelClass] The name of the CSS class defining the styles for the label. + * Note that style values for position, overflow, top, + * left, zIndex, display, marginLeft, and + * marginTop are ignored; these styles are for internal use only. + * @property {Object} [labelStyle] An object literal whose properties define specific CSS + * style values to be applied to the label. Style values defined here override those that may + * be defined in the labelClass style sheet. If this property is changed after the + * label has been created, all previously set styles (except those defined in the style sheet) + * are removed from the label before the new style values are applied. + * Note that style values for position, overflow, top, + * left, zIndex, display, marginLeft, and + * marginTop are ignored; these styles are for internal use only. + * @property {boolean} [labelInBackground] A flag indicating whether a label that overlaps its + * associated marker should appear in the background (i.e., in a plane below the marker). + * The default is false, which causes the label to appear in the foreground. + * @property {boolean} [labelVisible] A flag indicating whether the label is to be visible. + * The default is true. Note that even if labelVisible is + * true, the label will not be visible unless the associated marker is also + * visible (i.e., unless the marker's visible property is true). + * @property {boolean} [raiseOnDrag] A flag indicating whether the label and marker are to be + * raised when the marker is dragged. The default is true. If a draggable marker is + * being created and a version of Google Maps API earlier than V3.3 is being used, this property + * must be set to false. + * @property {boolean} [optimized] A flag indicating whether rendering is to be optimized for the + * marker. Important: The optimized rendering technique is not supported by MarkerWithLabel, + * so the value of this parameter is always forced to false. + * @property {string} [crossImage="http://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"] + * The URL of the cross image to be displayed while dragging a marker. + * @property {string} [handCursor="http://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"] + * The URL of the cursor to be displayed while dragging a marker. + */ +/** + * Creates a MarkerWithLabel with the options specified in {@link MarkerWithLabelOptions}. + * @constructor + * @param {MarkerWithLabelOptions} [opt_options] The optional parameters. + */ +function MarkerWithLabel(opt_options) { + opt_options = opt_options || {}; + opt_options.labelContent = opt_options.labelContent || ""; + opt_options.labelAnchor = opt_options.labelAnchor || new google.maps.Point(0, 0); + opt_options.labelClass = opt_options.labelClass || "markerLabels"; + opt_options.labelStyle = opt_options.labelStyle || {}; + opt_options.labelInBackground = opt_options.labelInBackground || false; + if (typeof opt_options.labelVisible === "undefined") { + opt_options.labelVisible = true; + } + if (typeof opt_options.raiseOnDrag === "undefined") { + opt_options.raiseOnDrag = true; + } + if (typeof opt_options.clickable === "undefined") { + opt_options.clickable = true; + } + if (typeof opt_options.draggable === "undefined") { + opt_options.draggable = false; + } + if (typeof opt_options.optimized === "undefined") { + opt_options.optimized = false; + } + opt_options.crossImage = opt_options.crossImage || "http" + (document.location.protocol === "https:" ? "s" : "") + "://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"; + opt_options.handCursor = opt_options.handCursor || "http" + (document.location.protocol === "https:" ? "s" : "") + "://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"; + opt_options.optimized = false; // Optimized rendering is not supported + + this.label = new MarkerLabel_(this, opt_options.crossImage, opt_options.handCursor); // Bind the label to the marker + + // Call the parent constructor. It calls Marker.setValues to initialize, so all + // the new parameters are conveniently saved and can be accessed with get/set. + // Marker.set triggers a property changed event (called "propertyname_changed") + // that the marker label listens for in order to react to state changes. + google.maps.Marker.apply(this, arguments); +} +inherits(MarkerWithLabel, google.maps.Marker); + +/** + * Overrides the standard Marker setMap function. + * @param {Map} theMap The map to which the marker is to be added. + * @private + */ +MarkerWithLabel.prototype.setMap = function (theMap) { + + // Call the inherited function... + google.maps.Marker.prototype.setMap.apply(this, arguments); + + // ... then deal with the label: + this.label.setMap(theMap); +}; \ No newline at end of file diff --git a/bower_components/angular-google-maps/dist/angular-google-maps.min.js b/bower_components/angular-google-maps/dist/angular-google-maps.min.js new file mode 100644 index 0000000..3109b49 --- /dev/null +++ b/bower_components/angular-google-maps/dist/angular-google-maps.min.js @@ -0,0 +1,8 @@ +/*! angular-google-maps 1.1.13 2014-07-29 + * AngularJS directives for Google Maps + * git: https://github.com/nlaplante/angular-google-maps.git + */ +function InfoBox(a){a=a||{},google.maps.OverlayView.apply(this,arguments),this.content_=a.content||"",this.disableAutoPan_=a.disableAutoPan||!1,this.maxWidth_=a.maxWidth||0,this.pixelOffset_=a.pixelOffset||new google.maps.Size(0,0),this.position_=a.position||new google.maps.LatLng(0,0),this.zIndex_=a.zIndex||null,this.boxClass_=a.boxClass||"infoBox",this.boxStyle_=a.boxStyle||{},this.closeBoxMargin_=a.closeBoxMargin||"2px",this.closeBoxURL_=a.closeBoxURL||"http://www.google.com/intl/en_us/mapfiles/close.gif",""===a.closeBoxURL&&(this.closeBoxURL_=""),this.infoBoxClearance_=a.infoBoxClearance||new google.maps.Size(1,1),"undefined"==typeof a.visible&&(a.visible="undefined"==typeof a.isHidden?!0:!a.isHidden),this.isHidden_=!a.visible,this.alignBottom_=a.alignBottom||!1,this.pane_=a.pane||"floatPane",this.enableEventPropagation_=a.enableEventPropagation||!1,this.div_=null,this.closeListener_=null,this.moveListener_=null,this.contextListener_=null,this.eventListeners_=null,this.fixedWidthSet_=null}function ClusterIcon(a,b){a.getMarkerClusterer().extend(ClusterIcon,google.maps.OverlayView),this.cluster_=a,this.className_=a.getMarkerClusterer().getClusterClass(),this.styles_=b,this.center_=null,this.div_=null,this.sums_=null,this.visible_=!1,this.setMap(a.getMap())}function Cluster(a){this.markerClusterer_=a,this.map_=a.getMap(),this.gridSize_=a.getGridSize(),this.minClusterSize_=a.getMinimumClusterSize(),this.averageCenter_=a.getAverageCenter(),this.markers_=[],this.center_=null,this.bounds_=null,this.clusterIcon_=new ClusterIcon(this,a.getStyles())}function MarkerClusterer(a,b,c){this.extend(MarkerClusterer,google.maps.OverlayView),b=b||[],c=c||{},this.markers_=[],this.clusters_=[],this.listeners_=[],this.activeMap_=null,this.ready_=!1,this.gridSize_=c.gridSize||60,this.minClusterSize_=c.minimumClusterSize||2,this.maxZoom_=c.maxZoom||null,this.styles_=c.styles||[],this.title_=c.title||"",this.zoomOnClick_=!0,void 0!==c.zoomOnClick&&(this.zoomOnClick_=c.zoomOnClick),this.averageCenter_=!1,void 0!==c.averageCenter&&(this.averageCenter_=c.averageCenter),this.ignoreHidden_=!1,void 0!==c.ignoreHidden&&(this.ignoreHidden_=c.ignoreHidden),this.enableRetinaIcons_=!1,void 0!==c.enableRetinaIcons&&(this.enableRetinaIcons_=c.enableRetinaIcons),this.imagePath_=c.imagePath||MarkerClusterer.IMAGE_PATH,this.imageExtension_=c.imageExtension||MarkerClusterer.IMAGE_EXTENSION,this.imageSizes_=c.imageSizes||MarkerClusterer.IMAGE_SIZES,this.calculator_=c.calculator||MarkerClusterer.CALCULATOR,this.batchSize_=c.batchSize||MarkerClusterer.BATCH_SIZE,this.batchSizeIE_=c.batchSizeIE||MarkerClusterer.BATCH_SIZE_IE,this.clusterClass_=c.clusterClass||"cluster",-1!==navigator.userAgent.toLowerCase().indexOf("msie")&&(this.batchSize_=this.batchSizeIE_),this.setupStyles_(),this.addMarkers(b,!0),this.setMap(a)}function inherits(a,b){function c(){}c.prototype=b.prototype,a.superClass_=b.prototype,a.prototype=new c,a.prototype.constructor=a}function MarkerLabel_(a,b){this.marker_=a,this.handCursorURL_=a.handCursorURL,this.labelDiv_=document.createElement("div"),this.labelDiv_.style.cssText="position: absolute; overflow: hidden;",this.eventDiv_=document.createElement("div"),this.eventDiv_.style.cssText=this.labelDiv_.style.cssText,this.eventDiv_.setAttribute("onselectstart","return false;"),this.eventDiv_.setAttribute("ondragstart","return false;"),this.crossDiv_=MarkerLabel_.getSharedCross(b)}function MarkerWithLabel(a){a=a||{},a.labelContent=a.labelContent||"",a.labelAnchor=a.labelAnchor||new google.maps.Point(0,0),a.labelClass=a.labelClass||"markerLabels",a.labelStyle=a.labelStyle||{},a.labelInBackground=a.labelInBackground||!1,"undefined"==typeof a.labelVisible&&(a.labelVisible=!0),"undefined"==typeof a.raiseOnDrag&&(a.raiseOnDrag=!0),"undefined"==typeof a.clickable&&(a.clickable=!0),"undefined"==typeof a.draggable&&(a.draggable=!1),"undefined"==typeof a.optimized&&(a.optimized=!1),a.crossImage=a.crossImage||"http"+("https:"===document.location.protocol?"s":"")+"://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png",a.handCursor=a.handCursor||"http"+("https:"===document.location.protocol?"s":"")+"://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur",a.optimized=!1,this.label=new MarkerLabel_(this,a.crossImage,a.handCursor),google.maps.Marker.apply(this,arguments)}(function(){angular.module("google-maps.extensions",[]),angular.module("google-maps.directives.api.utils",["google-maps.extensions"]),angular.module("google-maps.directives.api.managers",[]),angular.module("google-maps.directives.api.models.child",["google-maps.directives.api.utils"]),angular.module("google-maps.directives.api.models.parent",["google-maps.directives.api.managers","google-maps.directives.api.models.child"]),angular.module("google-maps.directives.api",["google-maps.directives.api.models.parent"]),angular.module("google-maps",["google-maps.directives.api"]).factory("debounce",["$timeout",function(a){return function(b){var c;return c=0,function(){var d,e,f;return f=this,d=arguments,c++,e=function(a){return function(){return a===c?b.apply(f,d):void 0}}(c),a(e,0,!0)}}}])}).call(this),function(){angular.module("google-maps.extensions").service("ExtendGWin",function(){return{init:_.once(function(){return(google||("undefined"!=typeof google&&null!==google?google.maps:void 0)||null!=google.maps.InfoWindow)&&(google.maps.InfoWindow.prototype._open=google.maps.InfoWindow.prototype.open,google.maps.InfoWindow.prototype._close=google.maps.InfoWindow.prototype.close,google.maps.InfoWindow.prototype._isOpen=!1,google.maps.InfoWindow.prototype.open=function(a,b){this._isOpen=!0,this._open(a,b)},google.maps.InfoWindow.prototype.close=function(){this._isOpen=!1,this._close()},google.maps.InfoWindow.prototype.isOpen=function(a){return null==a&&(a=void 0),null==a?this._isOpen:this._isOpen=a},window.InfoBox)?(window.InfoBox.prototype._open=window.InfoBox.prototype.open,window.InfoBox.prototype._close=window.InfoBox.prototype.close,window.InfoBox.prototype._isOpen=!1,window.InfoBox.prototype.open=function(a,b){this._isOpen=!0,this._open(a,b)},window.InfoBox.prototype.close=function(){this._isOpen=!1,this._close()},window.InfoBox.prototype.isOpen=function(a){return null==a&&(a=void 0),null==a?this._isOpen:this._isOpen=a},MarkerLabel_.prototype.setContent=function(){var a;a=this.marker_.get("labelContent"),a&&!_.isEqual(this.oldContent,a)&&("undefined"==typeof(null!=a?a.nodeType:void 0)?(this.labelDiv_.innerHTML=a,this.eventDiv_.innerHTML=this.labelDiv_.innerHTML,this.oldContent=a):(this.labelDiv_.innerHTML="",this.labelDiv_.appendChild(a),a=a.cloneNode(!0),this.eventDiv_.appendChild(a),this.oldContent=a))},MarkerLabel_.prototype.onRemove=function(){null!=this.labelDiv_.parentNode&&this.labelDiv_.parentNode.removeChild(this.labelDiv_),null!=this.eventDiv_.parentNode&&this.eventDiv_.parentNode.removeChild(this.eventDiv_),this.listeners_&&this.listeners_.length&&this.listeners_.forEach(function(a){return google.maps.event.removeListener(a)})}):void 0})}})}.call(this),function(){_.intersectionObjects=function(a,b,c){var d;return null==c&&(c=void 0),d=_.map(a,function(a){return _.find(b,function(b){return null!=c?c(a,b):_.isEqual(a,b)})}),_.filter(d,function(a){return null!=a})},_.containsObject=_.includeObject=function(a,b,c){return null==c&&(c=void 0),null===a?!1:_.any(a,function(a){return null!=c?c(a,b):_.isEqual(a,b)})},_.differenceObjects=function(a,b,c){return null==c&&(c=void 0),_.filter(a,function(a){return!_.containsObject(b,a)})},_.withoutObjects=function(a,b){return _.differenceObjects(a,b)},_.indexOfObject=function(a,b,c,d){var e,f;if(null==a)return-1;if(e=0,f=a.length,d){if("number"!=typeof d)return e=_.sortedIndex(a,b),a[e]===b?e:-1;e=0>d?Math.max(0,f+d):d}for(;f>e;){if(null!=c){if(c(a[e],b))return e}else if(_.isEqual(a[e],b))return e;e++}return-1},_["extends"]=function(a){return _.reduce(a,function(a,b){return _.extend(a,b)},{})}}.call(this),function(){var a;a={each:function(a,b,c,d,e,f,g){var h;if(null==e&&(e=20),null==f&&(f=0),null==g&&(g=1),!g)throw"pause (delay) must be set from _async!";return void 0===a||(null!=a?a.length:void 0)<=0?void c():(h=function(){var i,j;for(i=e,j=f;i--&&j<(a?a.length:j+1);)b(a[j],j),++j;if(a){if(jb;b++)if(b in this&&this[b]===a)return b;return-1};angular.module("google-maps.directives.api.utils").factory("BaseObject",function(){var b,c;return c=["extended","included"],b=function(){function b(){}return b.extend=function(b){var d,e,f;for(d in b)e=b[d],a.call(c,d)<0&&(this[d]=e);return null!=(f=b.extended)&&f.apply(this),this},b.include=function(b){var d,e,f;for(d in b)e=b[d],a.call(c,d)<0&&(this.prototype[d]=e);return null!=(f=b.included)&&f.apply(this),this},b}()})}.call(this),function(){angular.module("google-maps.directives.api.utils").factory("ChildEvents",function(){return{onChildCreation:function(){}}})}.call(this),function(){angular.module("google-maps.directives.api.utils").service("CtrlHandle",["$q",function(a){var b;return b={handle:function(b){return b.deferred=a.defer(),{getScope:function(){return b}}}}}])}.call(this),function(){angular.module("google-maps.directives.api.utils").service("EventsHelper",["Logger",function(a){return{setEvents:function(b,c,d){return angular.isDefined(c.events)&&null!=c.events&&angular.isObject(c.events)?_.compact(_.map(c.events,function(e,f){return c.events.hasOwnProperty(f)&&angular.isFunction(c.events[f])?google.maps.event.addListener(b,f,function(){return e.apply(c,[b,f,d,arguments])}):a.info("MarkerEventHelper: invalid event listener "+f)})):void 0}}}])}.call(this),function(){var a={}.hasOwnProperty,b=function(b,c){function d(){this.constructor=b}for(var e in c)a.call(c,e)&&(b[e]=c[e]);return d.prototype=c.prototype,b.prototype=new d,b.__super__=c.prototype,b};angular.module("google-maps.directives.api.utils").factory("FitHelper",["BaseObject","Logger",function(a){var c,d;return c=function(a){function c(){return d=c.__super__.constructor.apply(this,arguments)}return b(c,a),c.prototype.fit=function(a,b){var c,d;return b&&a&&a.length>0?(c=new google.maps.LatLngBounds,d=!1,_async.each(a,function(a){return a?(d||(d=!0),c.extend(a.getPosition())):void 0},function(){return d?b.fitBounds(c):void 0})):void 0},c}(a)}])}.call(this),function(){angular.module("google-maps.directives.api.utils").service("GmapUtil",["Logger","$compile",function(a,b){var c,d;return c=function(a){return a?Array.isArray(a)&&2===a.length?new google.maps.LatLng(a[1],a[0]):angular.isDefined(a.type)&&"Point"===a.type?new google.maps.LatLng(a.coordinates[1],a.coordinates[0]):new google.maps.LatLng(a.latitude,a.longitude):void 0},d=function(a){if(angular.isUndefined(a))return!1;if(_.isArray(a)){if(2===a.length)return!0}else if(null!=a&&(null!=a?a.type:void 0)&&"Point"===a.type&&_.isArray(a.coordinates)&&2===a.coordinates.length)return!0;return a&&angular.isDefined((null!=a?a.latitude:void 0)&&angular.isDefined(null!=a?a.longitude:void 0))?!0:!1},{getLabelPositionPoint:function(a){var b,c;return void 0===a?void 0:(a=/^([-\d\.]+)\s([-\d\.]+)$/.exec(a),b=parseFloat(a[1]),c=parseFloat(a[2]),null!=b&&null!=c?new google.maps.Point(b,c):void 0)},createMarkerOptions:function(a,b,e,f){var g;return null==f&&(f=void 0),null==e&&(e={}),g=angular.extend({},e,{position:null!=e.position?e.position:c(a),icon:null!=e.icon?e.icon:b,visible:null!=e.visible?e.visible:d(a)}),null!=f&&(g.map=f),g},createWindowOptions:function(d,e,f,g){return null!=f&&null!=g&&null!=b?angular.extend({},g,{content:this.buildContent(e,g,f),position:null!=g.position?g.position:angular.isObject(d)?d.getPosition():c(e.coords)}):g?g:(a.error("infoWindow defaults not defined"),f?void 0:a.error("infoWindow content not defined"))},buildContent:function(a,c,d){var e,f;return null!=c.content?f=c.content:null!=b?(e=b(d)(a),e.length>0&&(f=e[0])):f=d,f},defaultDelay:50,isTrue:function(a){return angular.isDefined(a)&&null!==a&&a===!0||"1"===a||"y"===a||"true"===a},isFalse:function(a){return-1!==["false","FALSE",0,"n","N","no","NO"].indexOf(a)},getCoords:c,validateCoords:d,validatePath:function(a){var b,c,d,e;if(c=0,angular.isUndefined(a.type)){if(!Array.isArray(a)||a.length<2)return!1;for(;cthis.max?(this.max=a[0].length,this.index=b):void 0},e),d=a.coordinates[e.index],b=d[0],b.length<4)return!1}else{if("LineString"!==a.type)return!1;if(a.coordinates.length<2)return!1;b=a.coordinates}for(;cthis.max?(this.max=a[0].length,this.index=b):void 0},f),b=a.coordinates[f.index][0]):"LineString"===a.type&&(b=a.coordinates);cc;c++)b=a[c],e.push(this.add(b));return e},f.prototype.remove=function(a,b){var c,d;return this.handleOptDraw(a,b,!1),b?(c=void 0,null!=this.gMarkers.indexOf?c=this.gMarkers.indexOf(a):(d=0,_.find(this.gMarkers,function(b){d+=1,b===a&&(c=d)})),null!=c?this.gMarkers.splice(c,1):void 0):void 0},f.prototype.removeMany=function(){var a=this;return this.gMarkers.forEach(function(b){return a.remove(b)})},f.prototype.draw=function(){var a,b=this;return a=[],this.gMarkers.forEach(function(c){return c.isDrawn?void 0:c.doAdd?(c.setMap(b.gMap),c.isDrawn=!0):a.push(c)}),a.forEach(function(a){return a.isDrawn=!1,b.remove(a,!0)})},f.prototype.clear=function(){var a,b,c,d;for(d=this.gMarkers,b=0,c=d.length;c>b;b++)a=d[b],a.setMap(null);return delete this.gMarkers,this.gMarkers=[]},f.prototype.handleOptDraw=function(a,b,c){return b===!0?(a.setMap(c?this.gMap:null),a.isDrawn=!0):(a.isDrawn=!1,a.doAdd=c)},f.prototype.fit=function(){return f.__super__.fit.call(this,this.gMarkers,this.gMap)},f}(d)}])}.call(this),function(){angular.module("google-maps").factory("array-sync",["add-events",function(a){return function(b,c,d){var e,f,g,h,i,j,k,l,m;return h=!1,l=c.$eval(d),c["static"]||(i={set_at:function(a){var c;if(!h&&(c=b.getAt(a)))return c.lng&&c.lat?(l[a].latitude=c.lat(),l[a].longitude=c.lng()):l[a]=c},insert_at:function(a){var c;if(!h&&(c=b.getAt(a)))return c.lng&&c.lat?l.splice(a,0,{latitude:c.lat(),longitude:c.lng()}):l.splice(a,0,c)},remove_at:function(a){return h?void 0:l.splice(a,1)}},"Polygon"===l.type?e=l.coordinates[0]:"LineString"===l.type&&(e=l.coordinates),f={set_at:function(a){var c;if(!h&&(c=b.getAt(a),c&&c.lng&&c.lat))return e[a][1]=c.lat(),e[a][0]=c.lng()},insert_at:function(a){var c;if(!h&&(c=b.getAt(a),c&&c.lng&&c.lat))return e.splice(a,0,[c.lng(),c.lat()])},remove_at:function(a){return h?void 0:e.splice(a,1)}},k=a(b,angular.isUndefined(l.type)?i:f)),j=function(a){var c,d,e,f,g,i,j;if(h=!0,g=b,a){for(c=0,i=g.getLength(),e=a.length,d=Math.min(i,e),f=void 0;d>c;)j=g.getAt(c),f=a[c],"function"==typeof f.equals?f.equals(j)||g.setAt(c,f):(j.lat()!==f.latitude||j.lng()!==f.longitude)&&g.setAt(c,new google.maps.LatLng(f.latitude,f.longitude)),c++;for(;e>c;)f=a[c],g.push("function"==typeof f.lat&&"function"==typeof f.lng?f:new google.maps.LatLng(f.latitude,f.longitude)),c++;for(;i>c;)g.pop(),c++}return h=!1},g=function(a){var c,d,e,f,g,i,j,k;if(h=!0,i=b,a){for("Polygon"===l.type?c=a.coordinates[0]:"LineString"===l.type&&(c=a.coordinates),d=0,j=i.getLength(),f=c.length,e=Math.min(j,f),g=void 0;e>d;)k=i.getAt(d),g=c[d],(k.lat()!==g[1]||k.lng()!==g[0])&&i.setAt(d,new google.maps.LatLng(g[1],g[0])),d++;for(;f>d;)g=c[d],i.push(new google.maps.LatLng(g[1],g[0])),d++;for(;j>d;)i.pop(),d++}return h=!1},c["static"]||(m=angular.isUndefined(l.type)?c.$watchCollection(d,j):c.$watch(d,g,!0)),function(){return k&&(k(),k=null),m?(m(),m=null):void 0}}}])}.call(this),function(){angular.module("google-maps").factory("add-events",["$timeout",function(a){var b,c;return b=function(b,c,d){return google.maps.event.addListener(b,c,function(){return d.apply(this,arguments),a(function(){},!0)})},c=function(a,c,d){var e;return d?b(a,c,d):(e=[],angular.forEach(c,function(c,d){return e.push(b(a,d,c))}),function(){return angular.forEach(e,function(a){return google.maps.event.removeListener(a)}),e=null})}}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api.models.child").factory("MarkerLabelChildModel",["BaseObject","GmapUtil",function(b,d){var e;return e=function(b){function e(b,c,d){this.destroy=a(this.destroy,this),this.setOptions=a(this.setOptions,this);var f;e.__super__.constructor.call(this),f=this,this.gMarker=b,this.setOptions(c),this.gMarkerLabel=new MarkerLabel_(this.gMarker,c.crossImage,c.handCursor),this.gMarker.set("setMap",function(a){return f.gMarkerLabel.setMap(a),google.maps.Marker.prototype.setMap.apply(this,arguments)}),this.gMarker.setMap(d)}return c(e,b),e.include(d),e.prototype.setOption=function(a,b){return this.gMarker.set(a,b)},e.prototype.setOptions=function(a){var b,c;return(null!=a?a.labelContent:void 0)&&this.gMarker.set("labelContent",a.labelContent),(null!=a?a.labelAnchor:void 0)&&this.gMarker.set("labelAnchor",this.getLabelPositionPoint(a.labelAnchor)),this.gMarker.set("labelClass",a.labelClass||"labels"),this.gMarker.set("labelStyle",a.labelStyle||{opacity:100}),this.gMarker.set("labelInBackground",a.labelInBackground||!1),a.labelVisible||this.gMarker.set("labelVisible",!0),a.raiseOnDrag||this.gMarker.set("raiseOnDrag",!0),a.clickable||this.gMarker.set("clickable",!0),a.draggable||this.gMarker.set("draggable",!1),a.optimized||this.gMarker.set("optimized",!1),a.crossImage=null!=(b=a.crossImage)?b:document.location.protocol+"//maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png",a.handCursor=null!=(c=a.handCursor)?c:document.location.protocol+"//maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"},e.prototype.destroy=function(){return null!=this.gMarkerLabel.labelDiv_.parentNode&&null!=this.gMarkerLabel.eventDiv_.parentNode?this.gMarkerLabel.onRemove():void 0},e}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api.models.child").factory("MarkerChildModel",["ModelKey","GmapUtil","Logger","$injector","EventsHelper",function(b,d,e,f,g){var h;return h=function(b){function h(b,c,d,f,g,i,j,k){var l,m=this;this.model=b,this.parentScope=c,this.gMap=d,this.$timeout=f,this.defaults=g,this.doClick=i,this.gMarkerManager=j,this.idKey=k,this.watchDestroy=a(this.watchDestroy,this),this.setLabelOptions=a(this.setLabelOptions,this),this.isLabelDefined=a(this.isLabelDefined,this),this.setOptions=a(this.setOptions,this),this.setIcon=a(this.setIcon,this),this.setCoords=a(this.setCoords,this),this.destroy=a(this.destroy,this),this.maybeSetScopeValue=a(this.maybeSetScopeValue,this),this.createMarker=a(this.createMarker,this),this.setMyScope=a(this.setMyScope,this),l=this,this.model[this.idKey]&&(this.id=this.model[this.idKey]),this.iconKey=this.parentScope.icon,this.coordsKey=this.parentScope.coords,this.clickKey=this.parentScope.click(),this.labelContentKey=this.parentScope.labelContent,this.optionsKey=this.parentScope.options,this.labelOptionsKey=this.parentScope.labelOptions,h.__super__.constructor.call(this,this.parentScope.$new(!1)),this.scope.model=this.model,this.setMyScope(this.model,void 0,!0),this.createMarker(this.model),this.scope.$watch("model",function(a,b){return a!==b?m.setMyScope(a,b):void 0},!0),this.$log=e,this.$log.info(l),this.watchDestroy(this.scope)}return c(h,b),h.include(d),h.include(g),h.prototype.setMyScope=function(a,b,c){var d=this;return null==b&&(b=void 0),null==c&&(c=!1),this.maybeSetScopeValue("icon",a,b,this.iconKey,this.evalModelHandle,c,this.setIcon),this.maybeSetScopeValue("coords",a,b,this.coordsKey,this.evalModelHandle,c,this.setCoords),this.maybeSetScopeValue("labelContent",a,b,this.labelContentKey,this.evalModelHandle,c),_.isFunction(this.clickKey)&&f?this.scope.click=function(){return f.invoke(d.clickKey,void 0,{$markerModel:a})}:(this.maybeSetScopeValue("click",a,b,this.clickKey,this.evalModelHandle,c),this.createMarker(a,b,c))},h.prototype.createMarker=function(a,b,c){var d=this;return null==b&&(b=void 0),null==c&&(c=!1),this.maybeSetScopeValue("options",a,b,this.optionsKey,function(a,b){var c;return void 0===a?void 0:(c="self"===b?a:a[b],void 0===c?c=void 0===b?d.defaults:d.scope.options:c)},c,this.setOptions)},h.prototype.maybeSetScopeValue=function(a,b,c,d,e,f,g){var h,i;return null==g&&(g=void 0),void 0===c?(this.scope[a]=e(b,d),void(f||null!=g&&g(this.scope))):(i=e(c,d),h=e(b,d),h===i||this.scope[a]===h||(this.scope[a]=h,f)?void 0:(null!=g&&g(this.scope),this.gMarkerManager.draw()))},h.prototype.destroy=function(){return this.scope.$destroy()},h.prototype.setCoords=function(a){return a.$id===this.scope.$id&&void 0!==this.gMarker?null!=a.coords?this.validateCoords(this.scope.coords)?(this.gMarker.setPosition(this.getCoords(a.coords)),this.gMarker.setVisible(this.validateCoords(a.coords)),this.gMarkerManager.remove(this.gMarker),this.gMarkerManager.add(this.gMarker)):void this.$log.error("MarkerChildMarker cannot render marker as scope.coords as no position on marker: "+JSON.stringify(this.model)):this.gMarkerManager.remove(this.gMarker):void 0},h.prototype.setIcon=function(a){return a.$id===this.scope.$id&&void 0!==this.gMarker?(this.gMarkerManager.remove(this.gMarker),this.gMarker.setIcon(a.icon),this.gMarkerManager.add(this.gMarker),this.gMarker.setPosition(this.getCoords(a.coords)),this.gMarker.setVisible(this.validateCoords(a.coords))):void 0},h.prototype.setOptions=function(a){var b,c=this;if(a.$id===this.scope.$id&&(null!=this.gMarker&&(this.gMarkerManager.remove(this.gMarker),delete this.gMarker),null!=(b=a.coords)?b:"function"==typeof a.icon?a.icon(null!=a.options):void 0))return this.opts=this.createMarkerOptions(a.coords,a.icon,a.options),delete this.gMarker,this.gMarker=this.isLabelDefined(a)?new MarkerWithLabel(this.setLabelOptions(this.opts,a)):new google.maps.Marker(this.opts),this.setEvents(this.gMarker,this.parentScope,this.model),this.id&&(this.gMarker.key=this.id),this.gMarkerManager.add(this.gMarker),google.maps.event.addListener(this.gMarker,"click",function(){return c.doClick&&null!=c.scope.click?c.scope.click():void 0})},h.prototype.isLabelDefined=function(a){return null!=a.labelContent},h.prototype.setLabelOptions=function(a,b){return a.labelAnchor=this.getLabelPositionPoint(b.labelAnchor),a.labelClass=b.labelClass,a.labelContent=b.labelContent,a},h.prototype.watchDestroy=function(a){var b=this;return a.$on("$destroy",function(){var a,c;return null!=b.gMarker&&(google.maps.event.clearListeners(b.gMarker,"click"),(null!=(c=b.parentScope)?c.events:void 0)&&_.isArray(b.parentScope.events)&&b.parentScope.events.forEach(function(a,b){return google.maps.event.clearListeners(this.gMarker,b)}),b.gMarkerManager.remove(b.gMarker,!0),delete b.gMarker),a=void 0})},h}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]); +return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("PolylineChildModel",["BaseObject","Logger","$timeout","array-sync","GmapUtil",function(b,d,e,f,g){var h,i;return h=d,i=function(b){function d(b,c,d,e,i){var j,k,l=this;this.scope=b,this.attrs=c,this.map=d,this.defaults=e,this.model=i,this.buildOpts=a(this.buildOpts,this),k=this.convertPathPoints(b.path),this.polyline=new google.maps.Polyline(this.buildOpts(k)),b.fit&&g.extendMapBounds(d,k),!b["static"]&&angular.isDefined(b.editable)&&b.$watch("editable",function(a,b){return a!==b?l.polyline.setEditable(a):void 0}),angular.isDefined(b.draggable)&&b.$watch("draggable",function(a,b){return a!==b?l.polyline.setDraggable(a):void 0}),angular.isDefined(b.visible)&&b.$watch("visible",function(a,b){return a!==b?l.polyline.setVisible(a):void 0}),angular.isDefined(b.geodesic)&&b.$watch("geodesic",function(a,b){return a!==b?l.polyline.setOptions(l.buildOpts(l.polyline.getPath())):void 0}),angular.isDefined(b.stroke)&&angular.isDefined(b.stroke.weight)&&b.$watch("stroke.weight",function(a,b){return a!==b?l.polyline.setOptions(l.buildOpts(l.polyline.getPath())):void 0}),angular.isDefined(b.stroke)&&angular.isDefined(b.stroke.color)&&b.$watch("stroke.color",function(a,b){return a!==b?l.polyline.setOptions(l.buildOpts(l.polyline.getPath())):void 0}),angular.isDefined(b.stroke)&&angular.isDefined(b.stroke.opacity)&&b.$watch("stroke.opacity",function(a,b){return a!==b?l.polyline.setOptions(l.buildOpts(l.polyline.getPath())):void 0}),angular.isDefined(b.icons)&&b.$watch("icons",function(a,b){return a!==b?l.polyline.setOptions(l.buildOpts(l.polyline.getPath())):void 0}),j=f(this.polyline.getPath(),b,"path",function(a){return b.fit?g.extendMapBounds(d,a):void 0}),b.$on("$destroy",function(){return l.polyline.setMap(null),l.polyline=null,l.scope=null,j?(j(),j=null):void 0}),h.info(this)}return c(d,b),d.include(g),d.prototype.buildOpts=function(a){var b,c=this;return b=angular.extend({},this.defaults,{map:this.map,path:a,icons:this.scope.icons,strokeColor:this.scope.stroke&&this.scope.stroke.color,strokeOpacity:this.scope.stroke&&this.scope.stroke.opacity,strokeWeight:this.scope.stroke&&this.scope.stroke.weight}),angular.forEach({clickable:!0,draggable:!1,editable:!1,geodesic:!1,visible:!0,"static":!1,fit:!1},function(a,d){return b[d]=angular.isUndefined(c.scope[d])||null===c.scope[d]?a:c.scope[d]}),b["static"]&&(b.editable=!1),b},d.prototype.destroy=function(){return this.scope.$destroy()},d}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api.models.child").factory("WindowChildModel",["BaseObject","GmapUtil","Logger","$compile","$http","$templateCache",function(b,d,e,f,g,h){var i;return i=function(b){function i(b,c,d,f,g,h,i,j,k){this.model=b,this.scope=c,this.opts=d,this.isIconVisibleOnClick=f,this.mapCtrl=g,this.markerCtrl=h,this.element=i,this.needToManualDestroy=null!=j?j:!1,this.markerIsVisibleAfterWindowClose=null!=k?k:!0,this.destroy=a(this.destroy,this),this.remove=a(this.remove,this),this.hideWindow=a(this.hideWindow,this),this.getLatestPosition=a(this.getLatestPosition,this),this.showWindow=a(this.showWindow,this),this.handleClick=a(this.handleClick,this),this.watchCoords=a(this.watchCoords,this),this.watchShow=a(this.watchShow,this),this.createGWin=a(this.createGWin,this),this.watchElement=a(this.watchElement,this),this.googleMapsHandles=[],this.$log=e,this.createGWin(),null!=this.markerCtrl&&this.markerCtrl.setClickable(!0),this.handleClick(),this.watchElement(),this.watchShow(),this.watchCoords(),this.$log.info(this)}return c(i,b),i.include(d),i.prototype.watchElement=function(){var a=this;return this.scope.$watch(function(){var b;if(a.element&&a.html)return a.html!==a.element.html()&&a.gWin?(null!=(b=a.opts)&&(b.content=void 0),a.remove(),a.createGWin(),a.showHide()):void 0})},i.prototype.createGWin=function(){var a,b=this;return null==this.gWin&&(a={},null!=this.opts&&(this.scope.coords&&(this.opts.position=this.getCoords(this.scope.coords)),a=this.opts),this.element&&(this.html=_.isObject(this.element)?this.element.html():this.element),this.opts=this.createWindowOptions(this.markerCtrl,this.scope,this.html,a)),null==this.opts||this.gWin?void 0:(this.gWin=this.opts.boxClass&&window.InfoBox&&"function"==typeof window.InfoBox?new window.InfoBox(this.opts):new google.maps.InfoWindow(this.opts),this.googleMapsHandles.push(google.maps.event.addListener(this.gWin,"closeclick",function(){return b.markerCtrl&&(b.markerCtrl.setAnimation(b.oldMarkerAnimation),b.markerIsVisibleAfterWindowClose&&_.delay(function(){return b.markerCtrl.setVisible(!1),b.markerCtrl.setVisible(b.markerIsVisibleAfterWindowClose)},250)),b.gWin.isOpen(!1),null!=b.scope.closeClick?b.scope.closeClick():void 0})))},i.prototype.watchShow=function(){var a=this;return this.scope.$watch("show",function(b,c){return b!==c?b?a.showWindow():a.hideWindow():null!=a.gWin&&b&&!a.gWin.getMap()?a.showWindow():void 0},!0)},i.prototype.watchCoords=function(){var a,b=this;return a=null!=this.markerCtrl?this.scope.$parent:this.scope,a.$watch("coords",function(a,c){var d;if(a!==c){if(null==a)return b.hideWindow();if(!b.validateCoords(a))return void b.$log.error("WindowChildMarker cannot render marker as scope.coords as no position on marker: "+JSON.stringify(b.model));if(d=b.getCoords(a),b.gWin.setPosition(d),b.opts)return b.opts.position=d}},!0)},i.prototype.handleClick=function(a){var b,c=this;return b=function(){var a;return null==c.gWin&&c.createGWin(),a=c.markerCtrl.getPosition(),null!=c.gWin&&(c.gWin.setPosition(a),c.opts&&(c.opts.position=a),c.showWindow()),c.initialMarkerVisibility=c.markerCtrl.getVisible(),c.oldMarkerAnimation=c.markerCtrl.getAnimation(),c.markerCtrl.setVisible(c.isIconVisibleOnClick)},null!=this.markerCtrl?(a&&b(),this.googleMapsHandles.push(google.maps.event.addListener(this.markerCtrl,"click",b))):void 0},i.prototype.showWindow=function(){var a,b=this;return a=function(){return!b.gWin||!b.scope.show&&null!=b.scope.show||b.gWin.isOpen()?void 0:b.gWin.open(b.mapCtrl)},this.scope.templateUrl?(this.gWin&&g.get(this.scope.templateUrl,{cache:h}).then(function(a){var c,d;return d=b.scope.$new(),angular.isDefined(b.scope.templateParameter)&&(d.parameter=b.scope.templateParameter),c=f(a.data)(d),b.gWin.setContent(c[0])}),a()):a()},i.prototype.showHide=function(){return this.scope.show?this.showWindow():this.hideWindow()},i.prototype.getLatestPosition=function(a){return null==this.gWin||null==this.markerCtrl||a?a?this.gWin.setPosition(a):void 0:this.gWin.setPosition(this.markerCtrl.getPosition())},i.prototype.hideWindow=function(){return null!=this.gWin&&this.gWin.isOpen()?this.gWin.close():void 0},i.prototype.remove=function(){return this.hideWindow(),_.each(this.googleMapsHandles,function(a){return google.maps.event.removeListener(a)}),this.googleMapsHandles.length=0,delete this.gWin},i.prototype.destroy=function(a){var b;return null==a&&(a=!1),this.remove(),null!=this.scope&&(this.needToManualDestroy||a)&&this.scope.$destroy(),b=void 0},i}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api.models.parent").factory("IMarkerParentModel",["ModelKey","Logger",function(b,d){var e;return e=function(b){function e(b,c,f,g,h){var i,j=this;if(this.scope=b,this.element=c,this.attrs=f,this.map=g,this.$timeout=h,this.onDestroy=a(this.onDestroy,this),this.onWatch=a(this.onWatch,this),this.watch=a(this.watch,this),this.validateScope=a(this.validateScope,this),e.__super__.constructor.call(this,this.scope),i=this,this.$log=d,!this.validateScope(b))throw new String("Unable to construct IMarkerParentModel due to invalid scope");this.doClick=angular.isDefined(f.click),null!=b.options&&(this.DEFAULTS=b.options),this.watch("coords",this.scope),this.watch("icon",this.scope),this.watch("options",this.scope),b.$on("$destroy",function(){return j.onDestroy(b)})}return c(e,b),e.prototype.DEFAULTS={},e.prototype.validateScope=function(a){var b;return null==a?(this.$log.error(this.constructor.name+": invalid scope used"),!1):(b=null!=a.coords,b?b:(this.$log.error(this.constructor.name+": no valid coords attribute found"),!1))},e.prototype.watch=function(a,b){var c=this;return b.$watch(a,function(d,e){return _.isEqual(d,e)?void 0:c.onWatch(a,b,d,e)},!0)},e.prototype.onWatch=function(){throw new String("OnWatch Not Implemented!!")},e.prototype.onDestroy=function(){throw new String("OnDestroy Not Implemented!!")},e}(b)}])}.call(this),function(){var a={}.hasOwnProperty,b=function(b,c){function d(){this.constructor=b}for(var e in c)a.call(c,e)&&(b[e]=c[e]);return d.prototype=c.prototype,b.prototype=new d,b.__super__=c.prototype,b};angular.module("google-maps.directives.api.models.parent").factory("IWindowParentModel",["ModelKey","GmapUtil","Logger",function(a,c,d){var e;return e=function(a){function e(a,b,c,f,g,h,i,j){var k;e.__super__.constructor.call(this,a),k=this,this.$log=d,this.$timeout=g,this.$compile=h,this.$http=i,this.$templateCache=j,null!=a.options&&(this.DEFAULTS=a.options)}return b(e,a),e.include(c),e.prototype.DEFAULTS={},e}(a)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api.models.parent").factory("LayerParentModel",["BaseObject","Logger","$timeout",function(b,d){var e;return e=function(b){function e(b,c,e,f,g,h){var i=this;return this.scope=b,this.element=c,this.attrs=e,this.gMap=f,this.onLayerCreated=null!=g?g:void 0,this.$log=null!=h?h:d,this.createGoogleLayer=a(this.createGoogleLayer,this),null==this.attrs.type?void this.$log.info("type attribute for the layer directive is mandatory. Layer creation aborted!!"):(this.createGoogleLayer(),this.doShow=!0,angular.isDefined(this.attrs.show)&&(this.doShow=this.scope.show),this.doShow&&null!=this.gMap&&this.layer.setMap(this.gMap),this.scope.$watch("show",function(a,b){return a!==b?(i.doShow=a,i.layer.setMap(a?i.gMap:null)):void 0},!0),this.scope.$watch("options",function(a,b){return a!==b?(i.layer.setMap(null),i.layer=null,i.createGoogleLayer()):void 0},!0),void this.scope.$on("$destroy",function(){return i.layer.setMap(null)}))}return c(e,b),e.prototype.createGoogleLayer=function(){var a;return this.layer=null==this.attrs.options?void 0===this.attrs.namespace?new google.maps[this.attrs.type]:new google.maps[this.attrs.namespace][this.attrs.type]:void 0===this.attrs.namespace?new google.maps[this.attrs.type](this.scope.options):new google.maps[this.attrs.namespace][this.attrs.type](this.scope.options),null!=this.layer&&null!=this.onLayerCreated&&(a=this.onLayerCreated(this.scope,this.layer))?a(this.layer):void 0},e}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api.models.parent").factory("MarkerParentModel",["IMarkerParentModel","GmapUtil","EventsHelper",function(b,d,e){var f;return f=function(b){function f(b,c,d,e,g,h,i){var j,k,l=this;this.gMarkerManager=h,this.doFit=i,this.onDestroy=a(this.onDestroy,this),this.setGMarker=a(this.setGMarker,this),this.onWatch=a(this.onWatch,this),f.__super__.constructor.call(this,b,c,d,e,g),k=this,j=this.createMarkerOptions(b.coords,b.icon,b.options,this.map),this.setGMarker(new google.maps.Marker(j)),this.listener=google.maps.event.addListener(this.scope.gMarker,"click",function(){return l.doClick&&null!=b.click?l.$timeout(function(){return l.scope.click()}):void 0}),this.setEvents(this.scope.gMarker,b,b),this.$log.info(this)}return c(f,b),f.include(d),f.include(e),f.prototype.onWatch=function(a,b){var c,d,e;switch(a){case"coords":if(this.validateCoords(b.coords)&&null!=this.scope.gMarker){if(d=null!=(e=this.scope.gMarker)?e.getPosition():void 0,d.lat()===this.scope.coords.latitude&&this.scope.coords.longitude===d.lng())return;return c=this.scope.gMarker.getAnimation(),this.scope.gMarker.setMap(this.map),this.scope.gMarker.setPosition(this.getCoords(b.coords)),this.scope.gMarker.setVisible(this.validateCoords(b.coords)),this.scope.gMarker.setAnimation(c)}return this.scope.gMarker.setMap(null);case"icon":if(null!=b.icon&&this.validateCoords(b.coords)&&null!=this.scope.gMarker)return this.scope.gMarker.setIcon(b.icon),this.scope.gMarker.setMap(null),this.scope.gMarker.setMap(this.map),this.scope.gMarker.setPosition(this.getCoords(b.coords)),this.scope.gMarker.setVisible(this.validateCoords(b.coords));break;case"options":if(this.validateCoords(b.coords)&&null!=b.icon&&b.options&&null!=this.scope.gMarker)return this.onDestroy(b),this.onTimeOut(b)}},f.prototype.setGMarker=function(a){return this.scope.gMarker&&(delete this.scope.gMarker,this.gMarkerManager.remove(this.scope.gMarker,!1)),this.scope.gMarker=a,this.scope.gMarker&&(this.gMarkerManager.add(this.scope.gMarker,!1),this.doFit)?this.gMarkerManager.fit():void 0},f.prototype.onDestroy=function(){var a;return this.scope.gMarker?(this.scope.gMarker.setMap(null),google.maps.event.removeListener(this.listener),this.listener=null,this.gMarkerManager.remove(this.scope.gMarker,!1),delete this.scope.gMarker,a=void 0):void(a=void 0)},f}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api.models.parent").factory("MarkersParentModel",["IMarkerParentModel","ModelsWatcher","PropMap","MarkerChildModel","ClustererMarkerManager","MarkerManager",function(b,d,e,f,g,h){var i;return i=function(b){function i(b,c,d,f,g){this.onDestroy=a(this.onDestroy,this),this.newChildMarker=a(this.newChildMarker,this),this.pieceMealMarkers=a(this.pieceMealMarkers,this),this.reBuildMarkers=a(this.reBuildMarkers,this),this.createMarkersFromScratch=a(this.createMarkersFromScratch,this),this.validateScope=a(this.validateScope,this),this.onWatch=a(this.onWatch,this);var h,j=this;i.__super__.constructor.call(this,b,c,d,f,g),h=this,this.scope.markerModels=new e,this.$timeout=g,this.$log.info(this),this.doRebuildAll=null!=this.scope.doRebuildAll?this.scope.doRebuildAll:!1,this.setIdKey(b),this.scope.$watch("doRebuildAll",function(a,b){return a!==b?j.doRebuildAll=a:void 0}),this.watch("models",b),this.watch("doCluster",b),this.watch("clusterOptions",b),this.watch("clusterEvents",b),this.watch("fit",b),this.watch("idKey",b),this.gMarkerManager=void 0,this.createMarkersFromScratch(b)}return c(i,b),i.include(d),i.prototype.onWatch=function(a,b,c,d){return"idKey"===a&&c!==d&&(this.idKey=c),this.doRebuildAll?this.reBuildMarkers(b):this.pieceMealMarkers(b)},i.prototype.validateScope=function(a){var b;return b=angular.isUndefined(a.models)||void 0===a.models,b&&this.$log.error(this.constructor.name+": no valid models attribute found"),i.__super__.validateScope.call(this,a)||b},i.prototype.createMarkersFromScratch=function(a){var b=this;return a.doCluster?(a.clusterEvents&&(this.clusterInternalOptions=_.once(function(){var c,d,e,f;return c=b,b.origClusterEvents?void 0:(b.origClusterEvents={click:null!=(d=a.clusterEvents)?d.click:void 0,mouseout:null!=(e=a.clusterEvents)?e.mouseout:void 0,mouseover:null!=(f=a.clusterEvents)?f.mouseover:void 0},_.extend(a.clusterEvents,{click:function(a){return c.maybeExecMappedEvent(a,"click")},mouseout:function(a){return c.maybeExecMappedEvent(a,"mouseout")},mouseover:function(a){return c.maybeExecMappedEvent(a,"mouseover")}}))})()),a.clusterOptions||a.clusterEvents?void 0===this.gMarkerManager?this.gMarkerManager=new g(this.map,void 0,a.clusterOptions,this.clusterInternalOptions):this.gMarkerManager.opt_options!==a.clusterOptions&&(this.gMarkerManager=new g(this.map,void 0,a.clusterOptions,this.clusterInternalOptions)):this.gMarkerManager=new g(this.map)):this.gMarkerManager=new h(this.map),_async.each(a.models,function(c){return b.newChildMarker(c,a)},function(){return b.gMarkerManager.draw(),a.fit?b.gMarkerManager.fit():void 0})},i.prototype.reBuildMarkers=function(a){return a.doRebuild||void 0===a.doRebuild?(this.onDestroy(a),this.createMarkersFromScratch(a)):void 0},i.prototype.pieceMealMarkers=function(a){var b=this;return null!=this.scope.models&&this.scope.models.length>0&&this.scope.markerModels.length>0?this.figureOutState(this.idKey,a,this.scope.markerModels,this.modelKeyComparison,function(c){var d;return d=c,_async.each(d.removals,function(a){return null!=a?(null!=a.destroy&&a.destroy(),b.scope.markerModels.remove(a.id)):void 0},function(){return _async.each(d.adds,function(c){return b.newChildMarker(c,a)},function(){return d.adds.length>0||d.removals.length>0?(b.gMarkerManager.draw(),a.markerModels=b.scope.markerModels):void 0})})}):this.reBuildMarkers(a)},i.prototype.newChildMarker=function(a,b){var c;return null==a[this.idKey]?void this.$log.error("Marker model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key."):(this.$log.info("child",c,"markers",this.scope.markerModels),c=new f(a,b,this.map,this.$timeout,this.DEFAULTS,this.doClick,this.gMarkerManager,this.idKey),this.scope.markerModels.put(a[this.idKey],c),c)},i.prototype.onDestroy=function(){return _.each(this.scope.markerModels.values(),function(a){return null!=a?a.destroy():void 0}),delete this.scope.markerModels,this.scope.markerModels=new e,null!=this.gMarkerManager?this.gMarkerManager.clear():void 0},i.prototype.maybeExecMappedEvent=function(a,b){var c,d;return _.isFunction(null!=(d=this.scope.clusterEvents)?d[b]:void 0)&&(c=this.mapClusterToMarkerModels(a),this.origClusterEvents[b])?this.origClusterEvents[b](c.cluster,c.mapped):void 0},i.prototype.mapClusterToMarkerModels=function(a){var b,c,d=this;return b=a.getMarkers(),c=b.map(function(a){return d.scope.markerModels[a.key].model}),{cluster:a,mapped:c}},i}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api.models.parent").factory("PolylinesParentModel",["$timeout","Logger","ModelKey","ModelsWatcher","PropMap","PolylineChildModel",function(b,d,e,f,g,h){var i;return i=function(b){function e(b,c,f,h,i){var j,k=this;this.scope=b,this.element=c,this.attrs=f,this.gMap=h,this.defaults=i,this.modelKeyComparison=a(this.modelKeyComparison,this),this.setChildScope=a(this.setChildScope,this),this.createChild=a(this.createChild,this),this.pieceMeal=a(this.pieceMeal,this),this.createAllNew=a(this.createAllNew,this),this.watchIdKey=a(this.watchIdKey,this),this.createChildScopes=a(this.createChildScopes,this),this.watchOurScope=a(this.watchOurScope,this),this.watchDestroy=a(this.watchDestroy,this),this.rebuildAll=a(this.rebuildAll,this),this.doINeedToWipe=a(this.doINeedToWipe,this),this.watchModels=a(this.watchModels,this),this.watch=a(this.watch,this),e.__super__.constructor.call(this,b),j=this,this.$log=d,this.plurals=new g,this.scopePropNames=["path","stroke","clickable","draggable","editable","geodesic","icons","visible"],_.each(this.scopePropNames,function(a){return k[a+"Key"]=void 0}),this.models=void 0,this.firstTime=!0,this.$log.info(this),this.watchOurScope(b),this.createChildScopes()}return c(e,b),e.include(f),e.prototype.watch=function(a,b,c){var d=this;return a.$watch(b,function(a,e){return a!==e?(d[c]="function"==typeof a?a():a,_async.each(_.values(d.plurals),function(a){return a.scope[b]="self"===d[c]?a:a[d[c]]},function(){})):void 0})},e.prototype.watchModels=function(a){var b=this;return a.$watch("models",function(c,d){return _.isEqual(c,d)?void 0:b.doINeedToWipe(c)?b.rebuildAll(a,!0,!0):b.createChildScopes(!1)},!0)},e.prototype.doINeedToWipe=function(a){var b;return b=null!=a?0===a.length:!0,this.plurals.length>0&&b},e.prototype.rebuildAll=function(a,b,c){var d=this;return _async.each(this.plurals.values(),function(a){return a.destroy()},function(){return c&&delete d.plurals,d.plurals=new g,b?d.createChildScopes():void 0})},e.prototype.watchDestroy=function(a){var b=this;return a.$on("$destroy",function(){return b.rebuildAll(a,!1,!0)})},e.prototype.watchOurScope=function(a){var b=this;return _.each(this.scopePropNames,function(c){var d;return d=c+"Key",b[d]="function"==typeof a[c]?a[c]():a[c],b.watch(a,c,d)})},e.prototype.createChildScopes=function(a){return null==a&&(a=!0),angular.isUndefined(this.scope.models)?void this.$log.error("No models to create polylines from! I Need direct models!"):null!=this.gMap&&null!=this.scope.models?(this.watchIdKey(this.scope),a?this.createAllNew(this.scope,!1):this.pieceMeal(this.scope,!1)):void 0},e.prototype.watchIdKey=function(a){var b=this;return this.setIdKey(a),a.$watch("idKey",function(c,d){return c!==d&&null==c?(b.idKey=c,b.rebuildAll(a,!0,!0)):void 0})},e.prototype.createAllNew=function(a,b){var c=this;return null==b&&(b=!1),this.models=a.models,this.firstTime&&(this.watchModels(a),this.watchDestroy(a)),_async.each(a.models,function(a){return c.createChild(a,c.gMap)},function(){return c.firstTime=!1})},e.prototype.pieceMeal=function(a,b){var c=this;return null==b&&(b=!0),this.models=a.models,null!=a&&null!=a.models&&a.models.length>0&&this.plurals.length>0?this.figureOutState(this.idKey,a,this.plurals,this.modelKeyComparison,function(a){var b;return b=a,_async.each(b.removals,function(a){var b;return b=c.plurals[a],null!=b?(b.destroy(),c.plurals.remove(a)):void 0},function(){return _async.each(b.adds,function(a){return c.createChild(a,c.gMap)},function(){})})}):this.rebuildAll(this.scope,!0,!0)},e.prototype.createChild=function(a,b){var c,d,e=this;return d=this.scope.$new(!1),this.setChildScope(d,a),d.$watch("model",function(a,b){return a!==b?e.setChildScope(d,a):void 0},!0),d["static"]=this.scope["static"],c=new h(d,this.attrs,b,this.defaults,a),null==a[this.idKey]?void this.$log.error("Polyline model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key."):(this.plurals.put(a[this.idKey],c),c)},e.prototype.setChildScope=function(a,b){var c=this;return _.each(this.scopePropNames,function(d){var e,f;return e=d+"Key",f="self"===c[e]?b:b[c[e]],f!==a[d]?a[d]=f:void 0}),a.model=b},e.prototype.modelKeyComparison=function(a,b){return _.isEqual(this.evalModelHandle(a,this.scope.path),this.evalModelHandle(b,this.scope.path))},e}(e)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api.models.parent").factory("WindowsParentModel",["IWindowParentModel","ModelsWatcher","PropMap","WindowChildModel","Linked",function(b,d,e,f,g){var h;return h=function(b){function h(b,c,d,f,i,j,k,l,m){var n,o,p=this;this.$interpolate=m,this.interpolateContent=a(this.interpolateContent,this),this.setChildScope=a(this.setChildScope,this),this.createWindow=a(this.createWindow,this),this.setContentKeys=a(this.setContentKeys,this),this.pieceMealWindows=a(this.pieceMealWindows,this),this.createAllNewWindows=a(this.createAllNewWindows,this),this.watchIdKey=a(this.watchIdKey,this),this.createChildScopesWindows=a(this.createChildScopesWindows,this),this.watchOurScope=a(this.watchOurScope,this),this.watchDestroy=a(this.watchDestroy,this),this.rebuildAll=a(this.rebuildAll,this),this.doINeedToWipe=a(this.doINeedToWipe,this),this.watchModels=a(this.watchModels,this),this.watch=a(this.watch,this),this.go=a(this.go,this),h.__super__.constructor.call(this,b,c,d,f,i,j,k,l),o=this,this.windows=new e,this.scopePropNames=["show","coords","templateUrl","templateParameter","isIconVisibleOnClick","closeClick"],_.each(this.scopePropNames,function(a){return p[a+"Key"]=void 0}),this.linked=new g(b,c,d,f),this.models=void 0,this.contentKeys=void 0,this.isIconVisibleOnClick=void 0,this.firstTime=!0,this.$log.info(o),this.parentScope=void 0,n=f[0].getScope(),n.deferred.promise.then(function(a){var c;return p.gMap=a,c=f.length>1&&null!=f[1]?f[1]:void 0,c?c.getScope().deferred.promise.then(function(){return p.markerScope=c.getScope(),p.go(b)}):void p.go(b)})}return c(h,b),h.include(d),h.prototype.go=function(a){var b=this;return this.watchOurScope(a),this.doRebuildAll=null!=this.scope.doRebuildAll?this.scope.doRebuildAll:!1,a.$watch("doRebuildAll",function(a,c){return a!==c?b.doRebuildAll=a:void 0}),this.createChildScopesWindows()},h.prototype.watch=function(a,b,c){var d=this;return a.$watch(b,function(a,e){return a!==e?(d[c]="function"==typeof a?a():a,_async.each(_.values(d.windows),function(a){return a.scope[b]="self"===d[c]?a:a[d[c]]},function(){})):void 0})},h.prototype.watchModels=function(a){var b=this;return a.$watch("models",function(c,d){return _.isEqual(c,d)?void 0:b.doRebuildAll||b.doINeedToWipe(c)?b.rebuildAll(a,!0,!0):b.createChildScopesWindows(!1)})},h.prototype.doINeedToWipe=function(a){var b;return b=null!=a?0===a.length:!0,this.windows.length>0&&b},h.prototype.rebuildAll=function(a,b,c){var d=this;return _async.each(this.windows.values(),function(a){return a.destroy()},function(){return c&&delete d.windows,d.windows=new e,b?d.createChildScopesWindows():void 0})},h.prototype.watchDestroy=function(a){var b=this;return a.$on("$destroy",function(){return b.rebuildAll(a,!1,!0)})},h.prototype.watchOurScope=function(a){var b=this;return _.each(this.scopePropNames,function(c){var d;return d=c+"Key",b[d]="function"==typeof a[c]?a[c]():a[c],b.watch(a,c,d)})},h.prototype.createChildScopesWindows=function(a){var b,c;return null==a&&(a=!0),this.isIconVisibleOnClick=!0,angular.isDefined(this.linked.attrs.isiconvisibleonclick)&&(this.isIconVisibleOnClick=this.linked.scope.isIconVisibleOnClick),b=this.markerScope,c=angular.isUndefined(this.linked.scope.models),!c||void 0!==b&&void 0!==b.markerModels&&void 0!==b.models?null!=this.gMap?null!=this.linked.scope.models?(this.watchIdKey(this.linked.scope),a?this.createAllNewWindows(this.linked.scope,!1):this.pieceMealWindows(this.linked.scope,!1)):(this.parentScope=b,this.watchIdKey(this.parentScope),a?this.createAllNewWindows(b,!0,"markerModels",!1):this.pieceMealWindows(b,!0,"markerModels",!1)):void 0:void this.$log.error("No models to create windows from! Need direct models or models derrived from markers!")},h.prototype.watchIdKey=function(a){var b=this;return this.setIdKey(a),a.$watch("idKey",function(c,d){return c!==d&&null==c?(b.idKey=c,b.rebuildAll(a,!0,!0)):void 0})},h.prototype.createAllNewWindows=function(a,b,c,d){var e=this;return null==c&&(c="models"),null==d&&(d=!1),this.models=a.models,this.firstTime&&(this.watchModels(a),this.watchDestroy(a)),this.setContentKeys(a.models),_async.each(a.models,function(d){var f;return f=b?a[c][[d[e.idKey]]].gMarker:void 0,e.createWindow(d,f,e.gMap)},function(){return e.firstTime=!1})},h.prototype.pieceMealWindows=function(a,b,c,d){var e=this;return null==c&&(c="models"),null==d&&(d=!0),this.models=a.models,null!=a&&null!=a.models&&a.models.length>0&&this.windows.length>0?this.figureOutState(this.idKey,a,this.windows,this.modelKeyComparison,function(b){var d;return d=b,_async.each(d.removals,function(a){return null!=a?(null!=a.destroy&&a.destroy(),e.windows.remove(a.id)):void 0},function(){return _async.each(d.adds,function(b){var d;return d=a[c][b[e.idKey]].gMarker,e.createWindow(b,d,e.gMap)},function(){})})}):this.rebuildAll(this.scope,!0,!0)},h.prototype.setContentKeys=function(a){return a.length>0?this.contentKeys=Object.keys(a[0]):void 0},h.prototype.createWindow=function(a,b,c){var d,e,g,h,i=this;return e=this.linked.scope.$new(!1),this.setChildScope(e,a),e.$watch("model",function(a,b){return a!==b?i.setChildScope(e,a):void 0},!0),g={html:function(){return i.interpolateContent(i.linked.element.html(),a)}},h=this.createWindowOptions(b,e,g.html(),this.DEFAULTS),d=new f(a,e,h,this.isIconVisibleOnClick,c,b,g,!0,!0),null==a[this.idKey]?void this.$log.error("Window model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key."):(this.windows.put(a[this.idKey],d),d)},h.prototype.setChildScope=function(a,b){var c=this;return _.each(this.scopePropNames,function(d){var e,f;return e=d+"Key",f="self"===c[e]?b:b[c[e]],f!==a[d]?a[d]=f:void 0}),a.model=b},h.prototype.interpolateContent=function(a,b){var c,d,e,f,g,h;if(void 0!==this.contentKeys&&0!==this.contentKeys.length){for(c=this.$interpolate(a),d={},h=this.contentKeys,f=0,g=h.length;g>f;f++)e=h[f],d[e]=b[e];return c(d)}},h}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("ILabel",["BaseObject","Logger",function(b,d){var e;return e=function(b){function e(b){this.link=a(this.link,this);var c;c=this,this.restrict="ECMA",this.replace=!0,this.template=void 0,this.require=void 0,this.transclude=!0,this.priority=-100,this.scope={labelContent:"=content",labelAnchor:"@anchor",labelClass:"@class",labelStyle:"=style"},this.$log=d,this.$timeout=b}return c(e,b),e.prototype.link=function(){throw new Exception("Not Implemented!!")},e}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("IMarker",["Logger","BaseObject","$q",function(b,d){var e;return e=function(d){function e(c){this.link=a(this.link,this);var d;d=this,this.$log=b,this.$timeout=c,this.restrict="ECMA",this.require="^googleMap",this.priority=-1,this.transclude=!0,this.replace=!0,this.scope={coords:"=coords",icon:"=icon",click:"&click",options:"=options",events:"=events",fit:"=fit"}}return c(e,d),e.prototype.link=function(a,b,c,d){if(!d)throw new Error("No Map Control! Marker Directive Must be inside the map!")},e.prototype.mapPromise=function(a,b){var c;return c=b.getScope(),c.deferred.promise.then(function(b){return a.map=b}),c.deferred.promise},e}(d)}])}.call(this),function(){var a={}.hasOwnProperty,b=function(b,c){function d(){this.constructor=b}for(var e in c)a.call(c,e)&&(b[e]=c[e]);return d.prototype=c.prototype,b.prototype=new d,b.__super__=c.prototype,b};angular.module("google-maps.directives.api").factory("IPolyline",["GmapUtil","BaseObject","Logger",function(a,c,d){var e;return e=function(c){function e(){var a;a=this}return b(e,c),e.include(a),e.prototype.restrict="ECA",e.prototype.replace=!0,e.prototype.require="^googleMap",e.prototype.scope={path:"=",stroke:"=",clickable:"=",draggable:"=",editable:"=",geodesic:"=",icons:"=",visible:"=","static":"=",fit:"="},e.prototype.DEFAULTS={},e.prototype.$log=d,e +}(c)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("IWindow",["BaseObject","ChildEvents","Logger",function(b,d,e){var f;return f=function(b){function f(b,c,d,f){var g;this.$timeout=b,this.$compile=c,this.$http=d,this.$templateCache=f,this.link=a(this.link,this),g=this,this.restrict="ECMA",this.template=void 0,this.transclude=!0,this.priority=-100,this.require=void 0,this.replace=!0,this.scope={coords:"=coords",show:"=show",templateUrl:"=templateurl",templateParameter:"=templateparameter",isIconVisibleOnClick:"=isiconvisibleonclick",closeClick:"&closeclick",options:"=options"},this.$log=e}return c(f,b),f.include(d),f.prototype.link=function(){throw new Exception("Not Implemented!!")},f}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("Map",["$timeout","$q","Logger","GmapUtil","BaseObject","ExtendGWin","CtrlHandle",function(b,d,e,f,g,h,i){"use strict";var j,k,l;return j=e,k={mapTypeId:google.maps.MapTypeId.ROADMAP},l=function(b){function d(){this.link=a(this.link,this);var b;b=this}return c(d,b),d.include(f),d.prototype.restrict="ECMA",d.prototype.transclude=!0,d.prototype.replace=!1,d.prototype.template='

        ',d.prototype.scope={center:"=center",zoom:"=zoom",dragging:"=dragging",control:"=",windows:"=windows",options:"=options",events:"=events",styles:"=styles",bounds:"=bounds"},d.prototype.controller=["$scope",function(a){var b;return b=i.handle(a),a.ctrlType="Map",a.deferred.promise.then(function(){return h.init()}),_.extend(b,{getMap:function(){return a.map}})}],d.prototype.link=function(a,b,c){var d,e,f,g,h,i,l,m,n=this;if(!this.validateCoords(a.center))return void j.error("angular-google-maps: could not find a valid center property");if(!angular.isDefined(a.zoom))return void j.error("angular-google-maps: map zoom property not set");if(e=angular.element(b),e.addClass("angular-google-map"),h={options:{}},c.options&&(h.options=a.options),c.styles&&(h.styles=a.styles),c.type&&(l=c.type.toUpperCase(),google.maps.MapTypeId.hasOwnProperty(l)?h.mapTypeId=google.maps.MapTypeId[c.type.toUpperCase()]:j.error('angular-google-maps: invalid map type "'+c.type+'"')),m=new google.maps.Map(e.find("div")[1],angular.extend({},k,h,{center:this.getCoords(a.center),draggable:this.isTrue(c.draggable),zoom:a.zoom,bounds:a.bounds})),d=!1,m?a.deferred.resolve(m):google.maps.event.addListener(m,"tilesloaded ",function(b){return a.deferred.resolve(b)}),google.maps.event.addListener(m,"dragstart",function(){return d=!0,_.defer(function(){return a.$apply(function(a){return null!=a.dragging?a.dragging=d:void 0})})}),google.maps.event.addListener(m,"dragend",function(){return d=!1,_.defer(function(){return a.$apply(function(a){return null!=a.dragging?a.dragging=d:void 0})})}),google.maps.event.addListener(m,"drag",function(){var b;return b=m.center,_.defer(function(){return a.$apply(function(a){return angular.isDefined(a.center.type)?(a.center.coordinates[1]=b.lat(),a.center.coordinates[0]=b.lng()):(a.center.latitude=b.lat(),a.center.longitude=b.lng())})})}),google.maps.event.addListener(m,"zoom_changed",function(){return a.zoom!==m.zoom?_.defer(function(){return a.$apply(function(a){return a.zoom=m.zoom})}):void 0}),i=!1,google.maps.event.addListener(m,"center_changed",function(){var b;return b=m.center,i?void 0:_.defer(function(){return a.$apply(function(a){if(!m.dragging)if(angular.isDefined(a.center.type)){if(a.center.coordinates[1]!==b.lat()&&(a.center.coordinates[1]=b.lat()),a.center.coordinates[0]!==b.lng())return a.center.coordinates[0]=b.lng()}else if(a.center.latitude!==b.lat()&&(a.center.latitude=b.lat()),a.center.longitude!==b.lng())return a.center.longitude=b.lng()})})}),google.maps.event.addListener(m,"idle",function(){var b,c,d;return b=m.getBounds(),c=b.getNorthEast(),d=b.getSouthWest(),_.defer(function(){return a.$apply(function(a){return null!==a.bounds&&void 0!==a.bounds&&void 0!==a.bounds?(a.bounds.northeast={latitude:c.lat(),longitude:c.lng()},a.bounds.southwest={latitude:d.lat(),longitude:d.lng()}):void 0})})}),angular.isDefined(a.events)&&null!==a.events&&angular.isObject(a.events)){g=function(b){return function(){return a.events[b].apply(a,[m,b,arguments])}};for(f in a.events)a.events.hasOwnProperty(f)&&angular.isFunction(a.events[f])&&google.maps.event.addListener(m,f,g(f))}return a.map=m,null!=c.control&&null!=a.control&&(a.control.refresh=function(a){var b;if(null!=m)return google.maps.event.trigger(m,"resize"),null!=(null!=a?a.latitude:void 0)&&null!=(null!=a?a.latitude:void 0)?(b=n.getCoords(a),n.isTrue(c.pan)?m.panTo(b):m.setCenter(b)):void 0},a.control.getGMap=function(){return m}),a.$watch("center",function(b){var e;return e=n.getCoords(b),e.lat()!==m.center.lat()||e.lng()!==m.center.lng()?(i=!0,d||(n.validateCoords(b)||j.error("Invalid center for newValue: "+JSON.stringify(b)),n.isTrue(c.pan)&&a.zoom===m.zoom?m.panTo(e):m.setCenter(e)),i=!1):void 0},!0),a.$watch("zoom",function(a){return a!==m.zoom?_.defer(function(){return m.setZoom(a)}):void 0}),a.$watch("bounds",function(a,b){var c,d,e;if(a!==b)return null==a.northeast.latitude||null==a.northeast.longitude||null==a.southwest.latitude||null==a.southwest.longitude?void j.error("Invalid map bounds for new value: "+JSON.stringify(a)):(d=new google.maps.LatLng(a.northeast.latitude,a.northeast.longitude),e=new google.maps.LatLng(a.southwest.latitude,a.southwest.longitude),c=new google.maps.LatLngBounds(e,d),m.fitBounds(c))}),a.$watch("options",function(a,b){return _.isEqual(a,b)||(h.options=a,null==m)?void 0:m.setOptions(h)},!0),a.$watch("styles",function(a,b){return _.isEqual(a,b)||(h.styles=a,null==m)?void 0:m.setOptions(h)},!0)},d}(g)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("Marker",["IMarker","MarkerParentModel","MarkerManager","CtrlHandle",function(b,d,e,f){var g;return g=function(b){function g(b){this.link=a(this.link,this),g.__super__.constructor.call(this,b),this.template='',this.$log.info(this)}return c(g,b),g.prototype.controller=["$scope","$element",function(a,b){return a.ctrlType="Marker",f.handle(a,b)}],g.prototype.link=function(a,b,c,f){var h,i=this;return g.__super__.link.call(this,a,b,c,f),a.fit&&(h=!0),this.mapPromise(a,f).then(function(f){return i.gMarkerManager||(i.gMarkerManager=new e(f)),new d(a,b,c,f,i.$timeout,i.gMarkerManager,h),a.deferred.resolve()})},g}.call(this,b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("Markers",["IMarker","MarkersParentModel","CtrlHandle",function(b,d,e){var f;return f=function(b){function f(b){this.link=a(this.link,this);var c;f.__super__.constructor.call(this,b),this.template='',this.scope=_.extend(this.scope||{},{idKey:"=idkey",doRebuildAll:"=dorebuildall",models:"=models",doCluster:"=docluster",clusterOptions:"=clusteroptions",clusterEvents:"=clusterevents",labelContent:"=labelcontent",labelAnchor:"@labelanchor",labelClass:"@labelclass"}),this.$timeout=b,c=this,this.$log.info(this)}return c(f,b),f.prototype.controller=["$scope","$element",function(a,b){return a.ctrlType="Markers",e.handle(a,b)}],f.prototype.link=function(a,b,c,e){var f=this;return this.mapPromise(a,e).then(function(e){return new d(a,b,c,e,f.$timeout),a.deferred.resolve()})},f}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("Polyline",["IPolyline","$timeout","array-sync","PolylineChildModel",function(b,d,e,f){var g,h;return g=function(b){function d(){return this.link=a(this.link,this),h=d.__super__.constructor.apply(this,arguments)}return c(d,b),d.prototype.link=function(a,b,c,d){var e=this;return angular.isUndefined(a.path)||null===a.path||!this.validatePath(a.path)?void this.$log.error("polyline: no valid path attribute found"):d.getScope().deferred.promise.then(function(b){return new f(a,c,b,e.DEFAULTS)})},d}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("Polylines",["IPolyline","$timeout","array-sync","PolylinesParentModel",function(b,d,e,f){var g;return g=function(b){function d(){this.link=a(this.link,this),d.__super__.constructor.call(this),this.scope.idKey="=idkey",this.scope.models="=models",this.$log.info(this)}return c(d,b),d.prototype.link=function(a,b,c,d){var e=this;return angular.isUndefined(a.path)||null===a.path?void this.$log.error("polylines: no valid path attribute found"):a.models?d.getScope().deferred.promise.then(function(d){return new f(a,b,c,d,e.DEFAULTS)}):void this.$log.error("polylines: no models found to create from")},d}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("Window",["IWindow","GmapUtil","WindowChildModel","$q",function(b,d,e){var f;return f=function(b){function f(b,c,d,e){this.init=a(this.init,this),this.link=a(this.link,this);var g;f.__super__.constructor.call(this,b,c,d,e),g=this,this.require=["^googleMap","^?marker"],this.template='',this.$log.info(g)}return c(f,b),f.include(d),f.prototype.link=function(a,b,c,d){var e,f=this;return e=d[0].getScope(),e.deferred.promise.then(function(e){var g,h,i;return g=!0,angular.isDefined(c.isiconvisibleonclick)&&(g=a.isIconVisibleOnClick),(h=d.length>1&&null!=d[1]?d[1]:void 0)?(i=h.getScope(),i.deferred.promise.then(function(){return f.init(a,b,g,e,i)})):void f.init(a,b,g,e)})},f.prototype.init=function(a,b,c,d,f){var g,h,i,j,k,l=this;return g=null!=a.options?a.options:{},i=null!=a&&this.validateCoords(a.coords),null!=f&&(h=f.gMarker,f.$watch("coords",function(a,b){return null==f.gMarker||k.markerCtrl||(k.markerCtrl=f.gMarker,k.handleClick(!0)),l.validateCoords(a)?angular.equals(a,b)?void 0:k.getLatestPosition(l.getCoords(a)):k.hideWindow()},!0)),j=i?this.createWindowOptions(h,a,b.html(),g):g,null!=d&&(k=new e({},a,j,c,d,h,b)),a.$on("$destroy",function(){return null!=k?k.destroy():void 0}),null!=this.onChildCreation&&null!=k?this.onChildCreation(k):void 0},f}(b)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps.directives.api").factory("Windows",["IWindow","WindowsParentModel",function(b,d){var e;return e=function(b){function e(b,c,d,f,g){this.link=a(this.link,this);var h;e.__super__.constructor.call(this,b,c,d,f),h=this,this.$interpolate=g,this.require=["^googleMap","^?markers"],this.template='',this.scope.idKey="=idkey",this.scope.doRebuildAll="=dorebuildall",this.scope.models="=models",this.$log.info(this)}return c(e,b),e.prototype.link=function(a,b,c,e){return new d(a,b,c,e,this.$timeout,this.$compile,this.$http,this.$templateCache,this.$interpolate)},e}(b)}])}.call(this),function(){angular.module("google-maps").directive("googleMap",["Map",function(a){return new a}])}.call(this),function(){angular.module("google-maps").directive("marker",["$timeout","Marker",function(a,b){return new b(a)}])}.call(this),function(){angular.module("google-maps").directive("markers",["$timeout","Markers",function(a,b){return new b(a)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};angular.module("google-maps").directive("markerLabel",["$timeout","ILabel","MarkerLabelChildModel","GmapUtil","nggmap-PropertyAction",function(b,d,e,f,g){var h;return new(h=function(b){function d(b){this.init=a(this.init,this),this.link=a(this.link,this);var c;d.__super__.constructor.call(this,b),c=this,this.require="^marker",this.template='',this.$log.info(this)}return c(d,b),d.prototype.link=function(a,b,c,d){var e,f=this;return e=d.getScope(),e?e.deferred.promise.then(function(){return f.init(e,a)}):void 0},d.prototype.init=function(a,b){var c,d,f;return f=null,c=function(){return f?void 0:f=new e(a.gMarker,b,a.map)},d=!0,a.$watch("gMarker",function(){var e,h,i,j;return null!=a.gMarker&&(i=new g(function(){return c(),b.labelContent&&null!=f?f.setOption("labelContent",b.labelContent):void 0},d),e=new g(function(){return c(),null!=f?f.setOption("labelAnchor",b.labelAnchor):void 0},d),h=new g(function(){return c(),null!=f?f.setOption("labelClass",b.labelClass):void 0},d),j=new g(function(){return c(),null!=f?f.setOption("labelStyle",b.labelStyle):void 0},d),b.$watch("labelContent",i.sic),b.$watch("labelAnchor",e.sic),b.$watch("labelClass",h.sic),b.$watch("labelStyle",j.sic),d=!1),b.$on("$destroy",function(){return null!=f?f.destroy():void 0})})},d}(d))(b)}])}.call(this),function(){angular.module("google-maps").directive("polygon",["$log","$timeout","array-sync","GmapUtil",function(a,b,c,d){var e,f;return f=function(a){return angular.isDefined(a)&&null!==a&&a===!0||"1"===a||"y"===a||"true"===a},e={},{restrict:"ECA",replace:!0,require:"^googleMap",scope:{path:"=path",stroke:"=stroke",clickable:"=",draggable:"=",editable:"=",geodesic:"=",fill:"=",icons:"=icons",visible:"=","static":"=",events:"=",zIndex:"=zindex",fit:"="},link:function(b,f,g,h){return angular.isUndefined(b.path)||null===b.path||!d.validatePath(b.path)?void a.error("polygon: no valid path attribute found"):h.getScope().deferred.promise.then(function(a){var f,g,h,i,j,k;if(g=function(c){var d;return d=angular.extend({},e,{map:a,path:c,strokeColor:b.stroke&&b.stroke.color,strokeOpacity:b.stroke&&b.stroke.opacity,strokeWeight:b.stroke&&b.stroke.weight,fillColor:b.fill&&b.fill.color,fillOpacity:b.fill&&b.fill.opacity}),angular.forEach({clickable:!0,draggable:!1,editable:!1,geodesic:!1,visible:!0,"static":!1,fit:!1,zIndex:0},function(a,c){return d[c]=angular.isUndefined(b[c])||null===b[c]?a:b[c]}),d["static"]&&(d.editable=!1),d},j=d.convertPathPoints(b.path),k=new google.maps.Polygon(g(j)),b.fit&&d.extendMapBounds(a,j),!b["static"]&&angular.isDefined(b.editable)&&b.$watch("editable",function(a,b){return a!==b?k.setEditable(a):void 0}),angular.isDefined(b.draggable)&&b.$watch("draggable",function(a,b){return a!==b?k.setDraggable(a):void 0}),angular.isDefined(b.visible)&&b.$watch("visible",function(a,b){return a!==b?k.setVisible(a):void 0}),angular.isDefined(b.geodesic)&&b.$watch("geodesic",function(a,b){return a!==b?k.setOptions(g(k.getPath())):void 0}),angular.isDefined(b.stroke)&&angular.isDefined(b.stroke.opacity)&&b.$watch("stroke.opacity",function(){return k.setOptions(g(k.getPath()))}),angular.isDefined(b.stroke)&&angular.isDefined(b.stroke.weight)&&b.$watch("stroke.weight",function(a,b){return a!==b?k.setOptions(g(k.getPath())):void 0}),angular.isDefined(b.stroke)&&angular.isDefined(b.stroke.color)&&b.$watch("stroke.color",function(a,b){return a!==b?k.setOptions(g(k.getPath())):void 0}),angular.isDefined(b.fill)&&angular.isDefined(b.fill.color)&&b.$watch("fill.color",function(a,b){return a!==b?k.setOptions(g(k.getPath())):void 0}),angular.isDefined(b.fill)&&angular.isDefined(b.fill.opacity)&&b.$watch("fill.opacity",function(a,b){return a!==b?k.setOptions(g(k.getPath())):void 0}),angular.isDefined(b.zIndex)&&b.$watch("zIndex",function(a,b){return a!==b?k.setOptions(g(k.getPath())):void 0}),angular.isDefined(b.events)&&null!==b.events&&angular.isObject(b.events)){i=function(a){return function(){return b.events[a].apply(b,[k,a,arguments])}};for(h in b.events)b.events.hasOwnProperty(h)&&angular.isFunction(b.events[h])&&k.addListener(h,i(h))}return f=c(k.getPath(),b,"path",function(c){return b.fit?d.extendMapBounds(a,c):void 0}),b.$on("$destroy",function(){return k.setMap(null),f?(f(),f=null):void 0})})}}}])}.call(this),function(){angular.module("google-maps").directive("circle",["$log","$timeout","GmapUtil","EventsHelper",function(a,b,c,d){"use strict";var e;return e={},{restrict:"ECA",replace:!0,require:"^googleMap",scope:{center:"=center",radius:"=radius",stroke:"=stroke",fill:"=fill",clickable:"=",draggable:"=",editable:"=",geodesic:"=",icons:"=icons",visible:"=",events:"="},link:function(f,g,h,i){return i.getScope().deferred.promise.then(function(g){var h,i;return h=function(){var b;return c.validateCoords(f.center)?(b=angular.extend({},e,{map:g,center:c.getCoords(f.center),radius:f.radius,strokeColor:f.stroke&&f.stroke.color,strokeOpacity:f.stroke&&f.stroke.opacity,strokeWeight:f.stroke&&f.stroke.weight,fillColor:f.fill&&f.fill.color,fillOpacity:f.fill&&f.fill.opacity}),angular.forEach({clickable:!0,draggable:!1,editable:!1,geodesic:!1,visible:!0},function(a,c){return b[c]=angular.isUndefined(f[c])||null===f[c]?a:f[c]}),b):void a.error("circle: no valid center attribute found")},i=new google.maps.Circle(h()),f.$watchCollection("center",function(a,b){return a!==b?i.setOptions(h()):void 0}),f.$watchCollection("stroke",function(a,b){return a!==b?i.setOptions(h()):void 0}),f.$watchCollection("fill",function(a,b){return a!==b?i.setOptions(h()):void 0}),f.$watch("radius",function(a,b){return a!==b?i.setOptions(h()):void 0}),f.$watch("clickable",function(a,b){return a!==b?i.setOptions(h()):void 0}),f.$watch("editable",function(a,b){return a!==b?i.setOptions(h()):void 0}),f.$watch("draggable",function(a,b){return a!==b?i.setOptions(h()):void 0}),f.$watch("visible",function(a,b){return a!==b?i.setOptions(h()):void 0}),f.$watch("geodesic",function(a,b){return a!==b?i.setOptions(h()):void 0}),d.setEvents(i,f,f),google.maps.event.addListener(i,"radius_changed",function(){return f.radius=i.getRadius(),b(function(){return f.$apply()})}),google.maps.event.addListener(i,"center_changed",function(){return angular.isDefined(f.center.type)?(f.center.coordinates[1]=i.getCenter().lat(),f.center.coordinates[0]=i.getCenter().lng()):(f.center.latitude=i.getCenter().lat(),f.center.longitude=i.getCenter().lng()),b(function(){return f.$apply()})}),f.$on("$destroy",function(){return i.setMap(null)})})}}}])}.call(this),function(){angular.module("google-maps").directive("polyline",["Polyline",function(a){return new a}])}.call(this),function(){angular.module("google-maps").directive("polylines",["Polylines",function(a){return new a}])}.call(this),function(){angular.module("google-maps").directive("rectangle",["$log","$timeout",function(a){var b,c,d,e,f;return f=function(a){return angular.isUndefined(a.sw.latitude)||angular.isUndefined(a.sw.longitude)||angular.isUndefined(a.ne.latitude)||angular.isUndefined(a.ne.longitude)?!1:!0},c=function(a){var b;return b=new google.maps.LatLngBounds(new google.maps.LatLng(a.sw.latitude,a.sw.longitude),new google.maps.LatLng(a.ne.latitude,a.ne.longitude))},d=function(a,b){return a.fitBounds(b)},e=function(a){return angular.isDefined(a)&&null!==a&&a===!0||"1"===a||"y"===a||"true"===a},b={},{restrict:"ECA",require:"^googleMap",replace:!0,scope:{bounds:"=",stroke:"=",clickable:"=",draggable:"=",editable:"=",fill:"=",visible:"="},link:function(g,h,i,j){return angular.isUndefined(g.bounds)||null===g.bounds||angular.isUndefined(g.bounds.sw)||null===g.bounds.sw||angular.isUndefined(g.bounds.ne)||null===g.bounds.ne||!f(g.bounds)?void a.error("rectangle: no valid bound attribute found"):j.getScope().deferred.promise.then(function(f){var h,j,k,l;return h=function(a){var c;return c=angular.extend({},b,{map:f,bounds:a,strokeColor:g.stroke&&g.stroke.color,strokeOpacity:g.stroke&&g.stroke.opacity,strokeWeight:g.stroke&&g.stroke.weight,fillColor:g.fill&&g.fill.color,fillOpacity:g.fill&&g.fill.opacity}),angular.forEach({clickable:!0,draggable:!1,editable:!1,visible:!0},function(a,b){return c[b]=angular.isUndefined(g[b])||null===g[b]?a:g[b]}),c},k=new google.maps.Rectangle(h(c(g.bounds))),e(i.fit)&&d(f,bounds),j=!1,google.maps.event.addListener(k,"mousedown",function(){google.maps.event.addListener(k,"mousemove",function(){return j=!0,_.defer(function(){return g.$apply(function(a){return null!=a.dragging?a.dragging=j:void 0})})}),google.maps.event.addListener(k,"mouseup",function(){return google.maps.event.clearListeners(k,"mousemove"),google.maps.event.clearListeners(k,"mouseup"),j=!1,_.defer(function(){return g.$apply(function(a){return null!=a.dragging?a.dragging=j:void 0})})})}),l=!1,google.maps.event.addListener(k,"bounds_changed",function(){var a,b,c;return a=k.getBounds(),b=a.getNorthEast(),c=a.getSouthWest(),l?void 0:_.defer(function(){return g.$apply(function(a){k.dragging||null!==a.bounds&&void 0!==a.bounds&&void 0!==a.bounds&&(a.bounds.ne={latitude:b.lat(),longitude:b.lng()},a.bounds.sw={latitude:c.lat(),longitude:c.lng()})})})}),g.$watch("bounds",function(b,c){var d;if(!_.isEqual(b,c))return l=!0,j||((null==b.sw.latitude||null==b.sw.longitude||null==b.ne.latitude||null==b.ne.longitude)&&a.error("Invalid bounds for newValue: "+JSON.stringify(b)),d=new google.maps.LatLngBounds(new google.maps.LatLng(b.sw.latitude,b.sw.longitude),new google.maps.LatLng(b.ne.latitude,b.ne.longitude)),k.setBounds(d)),l=!1},!0),angular.isDefined(g.editable)&&g.$watch("editable",function(a){return k.setEditable(a)}),angular.isDefined(g.draggable)&&g.$watch("draggable",function(a){return k.setDraggable(a)}),angular.isDefined(g.visible)&&g.$watch("visible",function(a){return k.setVisible(a)}),angular.isDefined(g.stroke)&&(angular.isDefined(g.stroke.color)&&g.$watch("stroke.color",function(){return k.setOptions(h(k.getBounds()))}),angular.isDefined(g.stroke.weight)&&g.$watch("stroke.weight",function(){return k.setOptions(h(k.getBounds()))}),angular.isDefined(g.stroke.opacity)&&g.$watch("stroke.opacity",function(){return k.setOptions(h(k.getBounds()))})),angular.isDefined(g.fill)&&(angular.isDefined(g.fill.color)&&g.$watch("fill.color",function(){return k.setOptions(h(k.getBounds()))}),angular.isDefined(g.fill.opacity)&&g.$watch("fill.opacity",function(){return k.setOptions(h(k.getBounds()))})),g.$on("$destroy",function(){return k.setMap(null)})})}}}])}.call(this),function(){angular.module("google-maps").directive("window",["$timeout","$compile","$http","$templateCache","Window",function(a,b,c,d,e){return new e(a,b,c,d)}])}.call(this),function(){angular.module("google-maps").directive("windows",["$timeout","$compile","$http","$templateCache","$interpolate","Windows",function(a,b,c,d,e,f){return new f(a,b,c,d,e)}])}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}};angular.module("google-maps").directive("layer",["$timeout","Logger","LayerParentModel",function(b,c,d){var e;return new(e=function(){function b(){this.link=a(this.link,this),this.$log=c,this.restrict="ECMA",this.require="^googleMap",this.priority=-1,this.transclude=!0,this.template='',this.replace=!0,this.scope={show:"=show",type:"=type",namespace:"=namespace",options:"=options",onCreated:"&oncreated"}}return b.prototype.link=function(a,b,c,e){return e.getScope().deferred.promise.then(function(e){return null!=a.onCreated?new d(a,b,c,e,a.onCreated):new d(a,b,c,e)})},b}())}])}.call(this),InfoBox.prototype=new google.maps.OverlayView,InfoBox.prototype.createInfoBoxDiv_=function(){var a,b,c,d=this,e=function(a){a.cancelBubble=!0,a.stopPropagation&&a.stopPropagation()},f=function(a){a.returnValue=!1,a.preventDefault&&a.preventDefault(),d.enableEventPropagation_||e(a)};if(!this.div_){if(this.div_=document.createElement("div"),this.setBoxStyle_(),"undefined"==typeof this.content_.nodeType?this.div_.innerHTML=this.getCloseBoxImg_()+this.content_:(this.div_.innerHTML=this.getCloseBoxImg_(),this.div_.appendChild(this.content_)),this.getPanes()[this.pane_].appendChild(this.div_),this.addClickHandler_(),this.div_.style.width?this.fixedWidthSet_=!0:0!==this.maxWidth_&&this.div_.offsetWidth>this.maxWidth_?(this.div_.style.width=this.maxWidth_,this.div_.style.overflow="auto",this.fixedWidthSet_=!0):(c=this.getBoxWidths_(),this.div_.style.width=this.div_.offsetWidth-c.left-c.right+"px",this.fixedWidthSet_=!1),this.panBox_(this.disableAutoPan_),!this.enableEventPropagation_){for(this.eventListeners_=[],b=["mousedown","mouseover","mouseout","mouseup","click","dblclick","touchstart","touchend","touchmove"],a=0;ag&&(d=o.x+k+i+m-g),this.alignBottom_?o.y<-j+n+l?e=o.y+j-n-l:o.y+j+n>h&&(e=o.y+j+n-h):o.y<-j+n?e=o.y+j-n:o.y+l+j+n>h&&(e=o.y+l+j+n-h),0!==d||0!==e){{b.getCenter()}b.panBy(d,e)}}},InfoBox.prototype.setBoxStyle_=function(){var a,b;if(this.div_){this.div_.className=this.boxClass_,this.div_.style.cssText="",b=this.boxStyle_;for(a in b)b.hasOwnProperty(a)&&(this.div_.style[a]=b[a]);"undefined"!=typeof this.div_.style.opacity&&""!==this.div_.style.opacity&&(this.div_.style.filter="alpha(opacity="+100*this.div_.style.opacity+")"),this.div_.style.position="absolute",this.div_.style.visibility="hidden",null!==this.zIndex_&&(this.div_.style.zIndex=this.zIndex_)}},InfoBox.prototype.getBoxWidths_=function(){var a,b={top:0,bottom:0,left:0,right:0},c=this.div_;return document.defaultView&&document.defaultView.getComputedStyle?(a=c.ownerDocument.defaultView.getComputedStyle(c,""),a&&(b.top=parseInt(a.borderTopWidth,10)||0,b.bottom=parseInt(a.borderBottomWidth,10)||0,b.left=parseInt(a.borderLeftWidth,10)||0,b.right=parseInt(a.borderRightWidth,10)||0)):document.documentElement.currentStyle&&c.currentStyle&&(b.top=parseInt(c.currentStyle.borderTopWidth,10)||0,b.bottom=parseInt(c.currentStyle.borderBottomWidth,10)||0,b.left=parseInt(c.currentStyle.borderLeftWidth,10)||0,b.right=parseInt(c.currentStyle.borderRightWidth,10)||0),b},InfoBox.prototype.onRemove=function(){this.div_&&(this.div_.parentNode.removeChild(this.div_),this.div_=null)},InfoBox.prototype.draw=function(){this.createInfoBoxDiv_();var a=this.getProjection().fromLatLngToDivPixel(this.position_);this.div_.style.left=a.x+this.pixelOffset_.width+"px",this.alignBottom_?this.div_.style.bottom=-(a.y+this.pixelOffset_.height)+"px":this.div_.style.top=a.y+this.pixelOffset_.height+"px",this.div_.style.visibility=this.isHidden_?"hidden":"visible"},InfoBox.prototype.setOptions=function(a){"undefined"!=typeof a.boxClass&&(this.boxClass_=a.boxClass,this.setBoxStyle_()),"undefined"!=typeof a.boxStyle&&(this.boxStyle_=a.boxStyle,this.setBoxStyle_()),"undefined"!=typeof a.content&&this.setContent(a.content),"undefined"!=typeof a.disableAutoPan&&(this.disableAutoPan_=a.disableAutoPan),"undefined"!=typeof a.maxWidth&&(this.maxWidth_=a.maxWidth),"undefined"!=typeof a.pixelOffset&&(this.pixelOffset_=a.pixelOffset),"undefined"!=typeof a.alignBottom&&(this.alignBottom_=a.alignBottom),"undefined"!=typeof a.position&&this.setPosition(a.position),"undefined"!=typeof a.zIndex&&this.setZIndex(a.zIndex),"undefined"!=typeof a.closeBoxMargin&&(this.closeBoxMargin_=a.closeBoxMargin),"undefined"!=typeof a.closeBoxURL&&(this.closeBoxURL_=a.closeBoxURL),"undefined"!=typeof a.infoBoxClearance&&(this.infoBoxClearance_=a.infoBoxClearance),"undefined"!=typeof a.isHidden&&(this.isHidden_=a.isHidden),"undefined"!=typeof a.visible&&(this.isHidden_=!a.visible),"undefined"!=typeof a.enableEventPropagation&&(this.enableEventPropagation_=a.enableEventPropagation),this.div_&&this.draw()},InfoBox.prototype.setContent=function(a){this.content_=a,this.div_&&(this.closeListener_&&(google.maps.event.removeListener(this.closeListener_),this.closeListener_=null),this.fixedWidthSet_||(this.div_.style.width=""),"undefined"==typeof a.nodeType?this.div_.innerHTML=this.getCloseBoxImg_()+a:(this.div_.innerHTML=this.getCloseBoxImg_(),this.div_.appendChild(a)),this.fixedWidthSet_||(this.div_.style.width=this.div_.offsetWidth+"px","undefined"==typeof a.nodeType?this.div_.innerHTML=this.getCloseBoxImg_()+a:(this.div_.innerHTML=this.getCloseBoxImg_(),this.div_.appendChild(a))),this.addClickHandler_()),google.maps.event.trigger(this,"content_changed")},InfoBox.prototype.setPosition=function(a){this.position_=a,this.div_&&this.draw(),google.maps.event.trigger(this,"position_changed")},InfoBox.prototype.setZIndex=function(a){this.zIndex_=a,this.div_&&(this.div_.style.zIndex=a),google.maps.event.trigger(this,"zindex_changed")},InfoBox.prototype.setVisible=function(a){this.isHidden_=!a,this.div_&&(this.div_.style.visibility=this.isHidden_?"hidden":"visible")},InfoBox.prototype.getContent=function(){return this.content_},InfoBox.prototype.getPosition=function(){return this.position_},InfoBox.prototype.getZIndex=function(){return this.zIndex_},InfoBox.prototype.getVisible=function(){var a;return a="undefined"==typeof this.getMap()||null===this.getMap()?!1:!this.isHidden_ +},InfoBox.prototype.show=function(){this.isHidden_=!1,this.div_&&(this.div_.style.visibility="visible")},InfoBox.prototype.hide=function(){this.isHidden_=!0,this.div_&&(this.div_.style.visibility="hidden")},InfoBox.prototype.open=function(a,b){var c=this;b&&(this.position_=b.getPosition(),this.moveListener_=google.maps.event.addListener(b,"position_changed",function(){c.setPosition(this.getPosition())})),this.setMap(a),this.div_&&this.panBox_()},InfoBox.prototype.close=function(){var a;if(this.closeListener_&&(google.maps.event.removeListener(this.closeListener_),this.closeListener_=null),this.eventListeners_){for(a=0;af&&g.getMap().setZoom(f+1)},100)),d.cancelBubble=!0,d.stopPropagation&&d.stopPropagation()}}),google.maps.event.addDomListener(this.div_,"mouseover",function(){var a=c.cluster_.getMarkerClusterer();google.maps.event.trigger(a,"mouseover",c.cluster_)}),google.maps.event.addDomListener(this.div_,"mouseout",function(){var a=c.cluster_.getMarkerClusterer();google.maps.event.trigger(a,"mouseout",c.cluster_)})},ClusterIcon.prototype.onRemove=function(){this.div_&&this.div_.parentNode&&(this.hide(),google.maps.event.removeListener(this.boundsChangedListener_),google.maps.event.clearInstanceListeners(this.div_),this.div_.parentNode.removeChild(this.div_),this.div_=null)},ClusterIcon.prototype.draw=function(){if(this.visible_){var a=this.getPosFromLatLng_(this.center_);this.div_.style.top=a.y+"px",this.div_.style.left=a.x+"px"}},ClusterIcon.prototype.hide=function(){this.div_&&(this.div_.style.display="none"),this.visible_=!1},ClusterIcon.prototype.show=function(){if(this.div_){var a="",b=this.backgroundPosition_.split(" "),c=parseInt(b[0].trim(),10),d=parseInt(b[1].trim(),10),e=this.getPosFromLatLng_(this.center_);this.div_.style.cssText=this.createCss(e),a="",this.div_.innerHTML=a+"
        "+this.sums_.text+"
        ",this.div_.title="undefined"==typeof this.sums_.title||""===this.sums_.title?this.cluster_.getMarkerClusterer().getTitle():this.sums_.title,this.div_.style.display=""}this.visible_=!0},ClusterIcon.prototype.useStyle=function(a){this.sums_=a;var b=Math.max(0,a.index-1);b=Math.min(this.styles_.length-1,b);var c=this.styles_[b];this.url_=c.url,this.height_=c.height,this.width_=c.width,this.anchorText_=c.anchorText||[0,0],this.anchorIcon_=c.anchorIcon||[parseInt(this.height_/2,10),parseInt(this.width_/2,10)],this.textColor_=c.textColor||"black",this.textSize_=c.textSize||11,this.textDecoration_=c.textDecoration||"none",this.fontWeight_=c.fontWeight||"bold",this.fontStyle_=c.fontStyle||"normal",this.fontFamily_=c.fontFamily||"Arial,sans-serif",this.backgroundPosition_=c.backgroundPosition||"0 0"},ClusterIcon.prototype.setCenter=function(a){this.center_=a},ClusterIcon.prototype.createCss=function(a){var b=[];return b.push("cursor: pointer;"),b.push("position: absolute; top: "+a.y+"px; left: "+a.x+"px;"),b.push("width: "+this.width_+"px; height: "+this.height_+"px;"),b.join("")},ClusterIcon.prototype.getPosFromLatLng_=function(a){var b=this.getProjection().fromLatLngToDivPixel(a);return b.x-=this.anchorIcon_[1],b.y-=this.anchorIcon_[0],b.x=parseInt(b.x,10),b.y=parseInt(b.y,10),b},Cluster.prototype.getSize=function(){return this.markers_.length},Cluster.prototype.getMarkers=function(){return this.markers_},Cluster.prototype.getCenter=function(){return this.center_},Cluster.prototype.getMap=function(){return this.map_},Cluster.prototype.getMarkerClusterer=function(){return this.markerClusterer_},Cluster.prototype.getBounds=function(){var a,b=new google.maps.LatLngBounds(this.center_,this.center_),c=this.getMarkers();for(a=0;ad)a.getMap()!==this.map_&&a.setMap(this.map_);else if(cb;b++)this.markers_[b].setMap(null);else a.setMap(null);return this.updateIcon_(),!0},Cluster.prototype.isMarkerInClusterBounds=function(a){return this.bounds_.contains(a.getPosition())},Cluster.prototype.calculateBounds_=function(){var a=new google.maps.LatLngBounds(this.center_,this.center_);this.bounds_=this.markerClusterer_.getExtendedBounds(a)},Cluster.prototype.updateIcon_=function(){var a=this.markers_.length,b=this.markerClusterer_.getMaxZoom();if(null!==b&&this.map_.getZoom()>b)return void this.clusterIcon_.hide();if(a0))for(a=0;ac&&(f=c,g=d));g&&g.isMarkerInClusterBounds(a)?g.addMarker(a):(d=new Cluster(this),d.addMarker(a),this.clusters_.push(d))},MarkerClusterer.prototype.createClusters_=function(a){var b,c,d,e=this;if(this.ready_){0===a&&(google.maps.event.trigger(this,"clusteringbegin",this),"undefined"!=typeof this.timerRefStatic&&(clearTimeout(this.timerRefStatic),delete this.timerRefStatic)),d=this.getMap().getZoom()>3?new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(),this.getMap().getBounds().getNorthEast()):new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472,-178.48388434375),new google.maps.LatLng(-85.08136444384544,178.00048865625));var f=this.getExtendedBounds(d),g=Math.min(a+this.batchSize_,this.markers_.length);for(b=a;g>b;b++)c=this.markers_[b],!c.isAdded&&this.isMarkerInBounds_(c,f)&&(!this.ignoreHidden_||this.ignoreHidden_&&c.getVisible())&&this.addToClosestCluster_(c);g 0) { + var maxMarker = _.max($scope.map.mexiMarkers, function (marker) { + return marker.mid; + }); + id = maxMarker.mid; + } + for (var i = 0; i < 4; i++) { + id++; + markers.push(createRandomMarker(id, $scope.map.bounds, "mid")); + } + $scope.map.mexiMarkers = markers.concat($scope.map.mexiMarkers); + }); + } + }, + infoWindow: { + coords: { + latitude: 36.270850, + longitude: -44.296875 + }, + options: { + disableAutoPan: true + }, + show: false + }, + infoWindowWithCustomClass: { + coords: { + latitude: 36.270850, + longitude: -44.296875 + }, + options: { + boxClass: 'custom-info-window' + }, + show: true + }, + templatedInfoWindow: { + coords: { + latitude: 48.654686, + longitude: -75.937500 + }, + options: { + disableAutoPan: true + }, + show: true, + templateUrl: 'assets/templates/info.html', + templateParameter: { + message: 'passed in from the opener' + } + }, + circles: [ + { + id: 1, + center: { + latitude: 44, + longitude: -108 + }, + radius: 500000, + stroke: { + color: '#08B21F', + weight: 2, + opacity: 1 + }, + fill: { + color: '#08B21F', + opacity: 0.5 + }, + geodesic: true, // optional: defaults to false + draggable: true, // optional: defaults to false + clickable: true, // optional: defaults to true + editable: true, // optional: defaults to false + visible: true // optional: defaults to true + } + ], + polygons: [ + { + id: 1, + path: [ + { + latitude: 50, + longitude: -80 + }, + { + latitude: 30, + longitude: -120 + }, + { + latitude: 20, + longitude: -95 + } + ], + stroke: { + color: '#6060FB', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: false, + visible: true, + fill: { + color: '#ff0000', + opacity: 0.8 + } + } + ], + polygons2: [ + { + id: 1, + path: [ + { + latitude: 60, + longitude: -80 + }, + { + latitude: 40, + longitude: -120 + }, + { + latitude: 45, + longitude: -95 + } + ], + stroke: { + color: '#33CDDC', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: false, + visible: true, + fill: { + color: '#33CCCC', + opacity: 0.8 + } + } + ], + polylines: [ + { + id: 1, + path: [ + { + latitude: 45, + longitude: -74 + }, + { + latitude: 30, + longitude: -89 + }, + { + latitude: 37, + longitude: -122 + }, + { + latitude: 60, + longitude: -95 + } + ], + stroke: { + color: '#6060FB', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: true, + visible: true, + icons: [{ + icon: { + path: google.maps.SymbolPath.BACKWARD_OPEN_ARROW + }, + offset: '25px', + repeat: '50px' + }] + }, + { + id: 2, + path: [ + { + latitude: 47, + longitude: -74 + }, + { + latitude: 32, + longitude: -89 + }, + { + latitude: 39, + longitude: -122 + }, + { + latitude: 62, + longitude: -95 + } + ], + stroke: { + color: '#6060FB', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: true, + visible: true, + icons: [{ + icon: { + path: google.maps.SymbolPath.BACKWARD_OPEN_ARROW + }, + offset: '25px', + repeat: '50px' + }] + }, + { + id: 3, + path: google.maps.geometry.encoding.decodePath("uowfHnzb}Uyll@i|i@syAcx}Cpj[_wXpd}AhhCxu[ria@_{AznyCnt^|re@nt~B?m|Awn`G?vk`RzyD}nr@uhjHuqGrf^ren@"), + stroke: { + color: '#4EAE47', + weight: 3 + }, + editable: false, + draggable: false, + geodesic: false, + visible: true + } + ] + }, + toggleColor: function (color) { + return color == 'red' ? '#6060FB' : 'red'; + } + + }); + + _.each($scope.map.markers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + onMarkerClicked(marker); + }; + }); + + _.each($scope.map.markers2, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + onMarkerClicked(marker); + }; + }); + + $scope.removeMarkers = function () { + $log.info("Clearing markers. They should disappear from the map now"); + $scope.map.markers = []; + $scope.map.markers2 = []; + $scope.map.dynamicMarkers = []; + $scope.map.randomMarkers = []; + $scope.map.mexiMarkers = []; + $scope.map.polylines = []; + $scope.map.clickedMarker = null; + $scope.searchLocationMarker = null; + $scope.map.infoWindow.show = false; + $scope.map.templatedInfoWindow.show = false; + // $scope.map.infoWindow.coords = null; + }; + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + return; + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + return; + } + $scope.map.clusterOptionsText = JSON.stringify($scope.map.clusterOptions); + $scope.$watch('map.clusterOptionsText', function (newValue, oldValue) { + if (newValue !== oldValue) + $scope.map.clusterOptions = angular.fromJson($scope.map.clusterOptionsText); + }); + + $scope.$watch('map.doUgly', function (newValue, oldValue) { + var json; + if (newValue !== oldValue) { + if (newValue) + json = {title: 'Hi I am a Cluster!', gridSize: 60, ignoreHidden: true, minimumClusterSize: 2, + imageExtension: 'png', imagePath: 'http://localhost:3000/example/cluster', imageSizes: [72]}; + else + json = {title: 'Hi I am a Cluster!', gridSize: 60, ignoreHidden: true, minimumClusterSize: 2}; + $scope.map.clusterOptions = json; + $scope.map.clusterOptionsText = angular.toJson(json); + } + }); + + $scope.genRandomMarkers = function (numberOfMarkers) { + genRandomMarkers(numberOfMarkers, $scope); + }; + + $scope.searchLocationMarker = { + coords: { + latitude: 40.1451, + longitude: -99.6680 + }, + options: { draggable: true }, + events: { + dragend: function (marker, eventName, args) { + $log.log('marker dragend'); + $log.log(marker.getPosition().lat()); + $log.log(marker.getPosition().lng()); + } + } + } + $scope.onMarkerClicked = onMarkerClicked; + + $scope.clackMarker = function ($markerModel) { + $log.log("from clackMarker"); + $log.log($markerModel); + }; + + $timeout(function () { + $scope.map.infoWindow.show = true; + dynamicMarkers = [ + { + id: 1, + latitude: 46, + longitude: -79, + showWindow: false + }, + { + id: 2, + latitude: 33, + longitude: -79, + showWindow: false + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127, + showWindow: false + } + ]; + + $scope.map.polylines.push({ + id: 3, + path: [ + { + latitude: 65, + longitude: -74 + }, + { + latitude: 50, + longitude: -89 + }, + { + latitude: 57, + longitude: -122 + }, + { + latitude: 20, + longitude: -95 + } + ], + stroke: { + color: '#FF0066', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: true, + visible: true + }); + + $scope.map.polylines = $scope.map.polylines.slice(1); + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); +} diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/geojson.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/geojson.js new file mode 100644 index 0000000..b742f58 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/geojson.js @@ -0,0 +1,524 @@ +(function () { + var module = angular.module("angular-google-maps-example", ["google-maps"]); +}()); + +var rndAddToLatLon = function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +} + +function ExampleController($scope, $timeout, $log, $http, Logger) { + Logger.doLog = true + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + var onMarkerClicked = function (marker) { + marker.showWindow = true; + //window.alert("Marker: lat: " + marker.latitude + ", lon: " + marker.longitude + " clicked!!") + }; + + var genRandomMarkers = function (numberOfMarkers, scope) { + var markers = []; + for (var i = 0; i < numberOfMarkers; i++) { + markers.push(createRandomMarker(i, scope.map.bounds)); + } + + scope.map.randomMarkers = markers; + }; + + var createRandomMarker = function (i, bounds, idKey) { + var lat_min = bounds.southwest.latitude, + lat_range = bounds.northeast.latitude - lat_min, + lng_min = bounds.southwest.longitude, + lng_range = bounds.northeast.longitude - lng_min; + + if(idKey == null) + idKey = "id"; + + var latitude = lat_min + (Math.random() * lat_range); + var longitude = lng_min + (Math.random() * lng_range); + var ret = { + geometry: { + type: "Point", + coordinates: [ longitude, latitude ] + }, + title: 'm' + i + }; + ret[idKey] = i; + return ret; + }; + + angular.extend($scope, { + example2: { + doRebuildAll: false + }, + clickWindow: function () { + $log.info('CLICK CLICK'); + Logger.info('CLICK CLICK'); + }, + map: { + control:{}, + version: "uknown", + heatLayerCallback: function (layer) { + //set the heat layers backend data + var mockHeatLayer = new MockHeatLayer(layer); + }, + showTraffic: true, + showBicycling: false, + showWeather: false, + showHeat: false, + center: { + type: "Point", + coordinates: [ -73, 45 ] + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + markers: [ + { + id:1, + geometry: { + type: "Point", + coordinates: [ -74, 45 ] + }, + showWindow: false, + title: 'Marker 2' + }, + { + id:2, + geometry: { + type: "Point", + coordinates: [ 30, 15 ] + }, + showWindow: false, + title: 'Marker 2' + }, + { + id:3, + icon: 'assets/images/plane.png', + geometry: { + type: "Point", + coordinates: [ -122, 37 ] + }, + showWindow: false, + title: 'Plane' + } + ], + markers2: [ + { + id:1, + geometry: { + type: "Point", + coordinates: [ -77, 46 ] + }, + showWindow: false, + title: '[46,-77]' + }, + { + id:2, + geometry: { + type: "Point", + coordinates: [ -77, 33 ] + }, + showWindow: false, + title: '[33,-77]' + }, + { + id:3, + icon: 'assets/images/plane.png', + geometry: { + type: "Point", + coordinates: [ -125, 35 ] + }, + showWindow: false, + title: '[35,-125]' + } + ], + mexiIdKey: 'mid', + mexiMarkers: [ + { + mid:1, + geometry: { + type: "Point", + coordinates: [ -106.248779, 29.302567 ] + } + }, + { + mid:2, + geometry: { + type: "Point", + coordinates: [ -109.434814, 30.369913 ] + } + }, + { + mid:3, + geometry: { + type: "Point", + coordinates: [ -108.61084, 26.739478 ] + } + } + ], + dynamicMarkers: [], + randomMarkers: [], + doClusterRandomMarkers: true, + doUgly: true, //great name :) + clusterOptions: {title: 'Hi I am a Cluster!', gridSize: 60, ignoreHidden: true, minimumClusterSize: 2, + imageExtension: 'png', imagePath: 'assets/images/cluster', imageSizes: [72]}, + clickedMarker: { + title: 'You clicked here', + geometry: { + type: "Point", + coordinates: [ null, null ] + } + }, + events: { + tilesloaded: function (map, eventName, originalEventArgs) { + }, + click: function (mapModel, eventName, originalEventArgs) { + // 'this' is the directive's scope + $log.log("user defined event: " + eventName, mapModel, originalEventArgs); + + var e = originalEventArgs[0]; + + if (!$scope.map.clickedMarker) { + $scope.map.clickedMarker = { + title: 'You clicked here', + geometry: { + type: "Point", + coordinates: [ e.latLng.lng(), e.latLng.lat() ] + } + }; + } + else { + var marker = { + geometry: { + type: "Point", + coordinates: [ e.latLng.lng(), e.latLng.lat() ] + } + }; + $scope.map.clickedMarker = marker; + } + //scope apply required because this event handler is outside of the angular domain + $scope.$apply(); + }, + dragend: function () { + self = this; + $timeout(function () { + var markers = []; + var id = 0; + if($scope.map.mexiMarkers !== null && $scope.map.mexiMarkers.length > 0){ + var maxMarker = _.max($scope.map.mexiMarkers, function(marker){ + return marker.mid; + }); + id = maxMarker.mid; + } + for (var i = 0; i < 4; i++) { + id++; + markers.push(createRandomMarker(id, $scope.map.bounds,"mid")); + } + $scope.map.mexiMarkers = markers.concat($scope.map.mexiMarkers); + }); + } + }, + infoWindow: { + coords: { + type: "Point", + coordinates: [ -44.296875, 36.270850 ] + }, + options:{ + disableAutoPan:true + }, + show: false + }, + infoWindowWithCustomClass: { + coords: { + type: "Point", + coordinates: [ -44.296875, 36.270850 ] + }, + options:{ + boxClass: 'custom-info-window' + }, + show: true + }, + templatedInfoWindow: { + coords: { + type: "Point", + coordinates: [ -75.937500, 48.654686 ] + }, + options:{ + disableAutoPan:true + }, + show: true, + templateUrl: 'assets/templates/info.html', + templateParameter: { + message: 'passed in from the opener' + } + }, + circles: [ + { + id: 1, + center: { + type: "Point", + coordinates: [ -108, 44 ] + }, + radius: 500000, + stroke: { + color: '#08B21F', + weight: 2, + opacity: 1 + }, + fill: { + color: '#08B21F', + opacity: 0.5 + }, + geodesic: true, // optional: defaults to false + draggable: true, // optional: defaults to false + clickable: true, // optional: defaults to true + editable: true, // optional: defaults to false + visible: true // optional: defaults to true + } + ], + polygons: [ + { + id: 1, + path: { + type: "Polygon", + coordinates: [[ + [ -80, 50 ], + [ -120, 30 ], + [ -95, 20 ], + [ -80, 50 ] + ]] + }, + stroke: { + color: '#6060FB', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: false, + visible: true, + fill: { + color: '#ff0000', + opacity: 0.8 + } + } + ], + polylines: [ + { + id:1, + path: { + type: "LineString", + coordinates: [ + [ -74, 45 ], + [ -89, 30 ], + [ -122, 37 ], + [ -95, 60 ] + ] + }, + stroke: { + color: '#6060FB', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: true, + visible: true + }, + { + id:2, + path: [ + { + latitude: 47, + longitude: -74 + }, + { + latitude: 32, + longitude: -89 + }, + { + latitude: 39, + longitude: -122 + }, + { + latitude: 62, + longitude: -95 + } + ], + stroke: { + color: '#6060FB', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: true, + visible: true + } + ] + }, + toggleColor: function (color) { + return color == 'red' ? '#6060FB' : 'red'; + } + + }); + + _.each($scope.map.markers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + onMarkerClicked(marker); + }; + }); + + _.each($scope.map.markers2, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + onMarkerClicked(marker); + }; + }); + + $scope.removeMarkers = function () { + $log.info("Clearing markers. They should disappear from the map now"); + $scope.map.markers = []; + $scope.map.markers2 = []; + $scope.map.dynamicMarkers = []; + $scope.map.randomMarkers = []; + $scope.map.mexiMarkers = []; + $scope.map.polylines = []; + $scope.map.clickedMarker = null; + $scope.searchLocationMarker = null; + $scope.map.infoWindow.show = false; + $scope.map.templatedInfoWindow.show = false; + // $scope.map.infoWindow.coords = null; + }; + $scope.refreshMap = function() { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude:32.779680,longitude:-79.935493}); + $scope.map.control.getGMap().setZoom(11); + return; + }; + $scope.getMapInstance = function() { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + return; + } + $scope.map.clusterOptionsText = JSON.stringify($scope.map.clusterOptions); + $scope.$watch('map.clusterOptionsText', function (newValue, oldValue) { + if (newValue !== oldValue) + $scope.map.clusterOptions = angular.fromJson($scope.map.clusterOptionsText); + }); + + $scope.$watch('map.doUgly', function (newValue, oldValue) { + var json; + if (newValue !== oldValue) { + if (newValue) + json = {title: 'Hi I am a Cluster!', gridSize: 60, ignoreHidden: true, minimumClusterSize: 2, + imageExtension: 'png', imagePath: 'http://localhost:3000/example/cluster', imageSizes: [72]}; + else + json = {title: 'Hi I am a Cluster!', gridSize: 60, ignoreHidden: true, minimumClusterSize: 2}; + $scope.map.clusterOptions = json; + $scope.map.clusterOptionsText = angular.toJson(json); + } + }); + + $scope.genRandomMarkers = function (numberOfMarkers) { + genRandomMarkers(numberOfMarkers, $scope); + }; + + $scope.searchLocationMarker = { + geometry: { + type: "Point", + coordinates: [ -99.6680, 40.1451 ] + }, + options: { draggable: true }, + events: { + dragend: function (marker, eventName, args) { + $log.log('marker dragend'); + $log.log(marker.getPosition().lat()); + $log.log(marker.getPosition().lng()); + } + } + } + $scope.onMarkerClicked = onMarkerClicked; + + $timeout(function () { + $scope.map.infoWindow.show = true; + dynamicMarkers = [ + { + id: 1, + geometry: { + type: "Point", + coordinates: [ -79, 46 ] + }, + showWindow: false + }, + { + id: 2, + geometry: { + type: "Point", + coordinates: [ -79, 33 ] + }, + showWindow: false + }, + { + id: 3, + icon: 'assets/images/plane.png', + geometry: { + type: "Point", + coordinates: [ -127, 35 ] + }, + showWindow: false + } + ]; + + $scope.map.polylines.push({ + id:3, + path: { + type: "LineString", + coordinates: [ + [ -74, 65 ], + [ -89, 50 ], + [ -122, 57 ], + [ -95, 20 ] + ] + }, + stroke: { + color: '#FF0066', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: true, + visible: true + }); + + $scope.map.polylines = $scope.map.polylines.slice(1); + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + }; + marker.onClicked = function () { + onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); +} diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/hugedata.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/hugedata.js new file mode 100644 index 0000000..5fbf7e0 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/hugedata.js @@ -0,0 +1,10 @@ +var app = angular.module("HugeData", ["google-maps"]); + +app.controller("HugeDataCtrl", function ($scope) { + $scope.center = { + latitude: 47.6201, + longitude: -122.1653 + }; + + $scope.places = [{"boundary":{"type":"Polygon","coordinates":[[[-122.22279999999999,47.619885999999994],[-122.22207999999999,47.620368000000006],[-122.22205,47.620407],[-122.222028,47.620449],[-122.22202,47.620483],[-122.22203999999999,47.620520000000006],[-122.22207999999999,47.620560000000005],[-122.222156,47.62059500000001],[-122.22246,47.620630000000006],[-122.22236999999998,47.621057],[-122.220464,47.621049],[-122.21786000000002,47.621052000000006],[-122.21541500000001,47.62106],[-122.21002000000001,47.621079],[-122.20992000000001,47.62153800000001],[-122.20994,47.622927000000004],[-122.2098,47.623850000000004],[-122.20978,47.62472],[-122.20976999999999,47.626540000000006],[-122.20978,47.627430000000004],[-122.20978,47.628260000000004],[-122.20976999999999,47.62835000000001],[-122.20976,47.628490000000006],[-122.20976,47.629245000000004],[-122.209765,47.630160000000004],[-122.20976999999999,47.63108],[-122.20975,47.63192],[-122.20972400000001,47.63198],[-122.20972,47.63288],[-122.20972,47.63378],[-122.20971,47.63469],[-122.20971,47.635619000000005],[-122.2097,47.636523000000004],[-122.2097,47.637434],[-122.20971,47.63832000000001],[-122.2097,47.639252000000006],[-122.2097,47.64047],[-122.20969000000001,47.64134],[-122.20965000000001,47.6414],[-122.20973,47.642320000000005],[-122.20974,47.64247],[-122.20975,47.64286],[-122.20975,47.64294],[-122.20974,47.643054],[-122.20974,47.643105000000006],[-122.20973,47.64325],[-122.20951000000001,47.64331000000001],[-122.209236,47.643369],[-122.20889,47.643433],[-122.20854,47.64348],[-122.20837999999999,47.64349],[-122.20777,47.64354],[-122.20757,47.64354],[-122.20716000000002,47.64354],[-122.20701000000001,47.643530000000005],[-122.20699,47.643533000000005],[-122.20676,47.643510000000006],[-122.20667,47.643498],[-122.20567,47.643350000000005],[-122.20542999999999,47.64331000000001],[-122.20499000000001,47.643246],[-122.20456000000001,47.64318],[-122.20427000000001,47.643141],[-122.20401000000001,47.64312],[-122.20365000000001,47.64312],[-122.20349,47.643142000000005],[-122.20335,47.643170000000005],[-122.20320000000001,47.64321],[-122.203041,47.643273],[-122.202947,47.64332],[-122.20282999999999,47.643406],[-122.20268,47.643539000000004],[-122.20257,47.6437],[-122.20241,47.64382],[-122.20232999999999,47.64385],[-122.20205,47.644098],[-122.20193,47.644206999999994],[-122.20188,47.644252],[-122.20184,47.6443],[-122.20179,47.64436],[-122.20175,47.64442],[-122.201727,47.644479999999994],[-122.20171,47.644529],[-122.20169000000001,47.644583],[-122.20168000000001,47.64472],[-122.20165000000001,47.64444699999999],[-122.20164000000001,47.64439],[-122.20160000000001,47.644268],[-122.20154000000001,47.644151],[-122.20148,47.64404],[-122.20146000000001,47.644009],[-122.20144,47.64398],[-122.20141000000001,47.643930000000005],[-122.20136400000001,47.643879999999996],[-122.20132000000001,47.64383],[-122.20132000000001,47.64382],[-122.20128000000001,47.6438],[-122.20105000000001,47.643673],[-122.20066200000001,47.643390000000004],[-122.20060000000001,47.64334],[-122.20048,47.64321],[-122.200196,47.642849],[-122.20004,47.64265],[-122.2,47.6426],[-122.19995,47.64255000000001],[-122.1999,47.642500000000005],[-122.19981,47.64242],[-122.19845,47.64242],[-122.19846,47.642635000000006],[-122.19846,47.642846],[-122.19806,47.642843],[-122.19753,47.642844],[-122.19753,47.642810000000004],[-122.19711000000001,47.642813000000004],[-122.19703,47.642813000000004],[-122.19685,47.642810000000004],[-122.19662000000001,47.642810000000004],[-122.19628,47.642809],[-122.19628,47.64288],[-122.19628,47.642900000000004],[-122.19617,47.642900000000004],[-122.19625,47.643390000000004],[-122.19625,47.643758000000005],[-122.19629,47.645430000000005],[-122.19629,47.64631500000001],[-122.19629,47.64643],[-122.19592,47.64643],[-122.19525,47.64643],[-122.19085,47.64643],[-122.18765,47.64643],[-122.18716,47.64643],[-122.18682999999999,47.64643],[-122.18554999999999,47.64641],[-122.18022,47.646370000000005],[-122.17503999999998,47.646374],[-122.17228999999999,47.646313000000006],[-122.16927,47.64625],[-122.16512,47.646148000000004],[-122.16468,47.6462],[-122.16466000000001,47.646530000000006],[-122.16460000000001,47.651373],[-122.16453,47.653382],[-122.16451,47.653729],[-122.164457,47.654759999999996],[-122.16436,47.656639999999996],[-122.16436,47.657059999999994],[-122.16438,47.657678999999995],[-122.16426000000001,47.660470000000004],[-122.16427,47.66064],[-122.16407,47.66078],[-122.1638,47.660790000000006],[-122.16005,47.660703000000005],[-122.15937999999998,47.660688],[-122.15886999999998,47.66068],[-122.15874999999998,47.66067],[-122.15874999999998,47.660644],[-122.15861,47.66064],[-122.15842999999998,47.66064],[-122.15749,47.660590000000006],[-122.15558999999999,47.66061800000001],[-122.15432,47.66058],[-122.15339999999999,47.660559000000006],[-122.15152,47.66057000000001],[-122.1495,47.660590000000006],[-122.14908,47.6606],[-122.14814199999999,47.660605000000004],[-122.14443,47.66064],[-122.14335,47.660643],[-122.14336,47.659217],[-122.14335,47.658530000000006],[-122.14334,47.657849999999996],[-122.14334,47.65745],[-122.14332999999999,47.657048999999994],[-122.14334,47.65513000000001],[-122.14334,47.65506],[-122.14341,47.65357],[-122.1434,47.653459],[-122.1434,47.653285],[-122.14335,47.65234],[-122.14335,47.65227],[-122.14326200000001,47.65054],[-122.14324,47.650002],[-122.14317,47.648700000000005],[-122.14308,47.646871],[-122.14307799999999,47.646770000000004],[-122.14305,47.645950000000006],[-122.14305,47.64504],[-122.14305,47.64437],[-122.14306,47.64403],[-122.14306,47.642404],[-122.14307,47.64147],[-122.14308,47.64014],[-122.14309,47.638821],[-122.14308199999999,47.63871000000001],[-122.14308,47.63862],[-122.14308,47.637860999999994],[-122.14308,47.63762],[-122.14308199999999,47.63689],[-122.14308,47.636655000000005],[-122.14308,47.63617000000001],[-122.14309,47.63591500000001],[-122.14309,47.63564],[-122.14308199999999,47.635360000000006],[-122.143046,47.63521000000001],[-122.14304,47.634750000000004],[-122.14304,47.63453500000001],[-122.14304,47.63434],[-122.143046,47.634189],[-122.14305,47.633741],[-122.14306,47.63326],[-122.14305,47.63311000000001],[-122.14302,47.63298],[-122.14301,47.63286],[-122.143,47.63243000000001],[-122.14302,47.63208],[-122.14304,47.631491],[-122.14309,47.628246000000004],[-122.14309,47.627863],[-122.14311000000001,47.62713000000001],[-122.1424,47.62748],[-122.14183,47.62778],[-122.1418,47.62778],[-122.14174,47.627790000000005],[-122.14169500000001,47.627790000000005],[-122.14157,47.627790000000005],[-122.14145,47.627790000000005],[-122.14124000000001,47.627790000000005],[-122.1409,47.627790000000005],[-122.13978999999999,47.627790000000005],[-122.13892,47.627790000000005],[-122.13808999999999,47.6278],[-122.13773099999999,47.627790000000005],[-122.13772999999999,47.62788],[-122.13727,47.62787],[-122.13677999999999,47.628040000000006],[-122.13652,47.62813000000001],[-122.13621,47.628260000000004],[-122.13586999999998,47.628420000000006],[-122.13561,47.62857000000001],[-122.1355,47.628640000000004],[-122.13528,47.628820000000005],[-122.135179,47.62891000000001],[-122.13501,47.629090000000005],[-122.13493,47.62919000000001],[-122.13476,47.62944],[-122.13465000000001,47.629675000000006],[-122.13463,47.629760000000005],[-122.13458,47.629960000000004],[-122.13452000000001,47.630230000000005],[-122.13441,47.63091000000001],[-122.13439,47.631005],[-122.13432999999999,47.63116],[-122.13425000000001,47.6313],[-122.13415,47.63147],[-122.13412000000001,47.631501],[-122.13427,47.6315],[-122.1342,47.631581],[-122.13419,47.6316],[-122.13414,47.63166],[-122.13398,47.63182],[-122.13385,47.631930000000004],[-122.13376,47.632000000000005],[-122.13365,47.632070000000006],[-122.13347999999999,47.63217300000001],[-122.13336,47.632249],[-122.13286999999998,47.63252000000001],[-122.13272999999998,47.632600000000004],[-122.132612,47.632669],[-122.13257999999999,47.632690000000004],[-122.13256,47.63270800000001],[-122.13253999999999,47.63273000000001],[-122.13252999999999,47.63274500000001],[-122.13252,47.633654],[-122.13234999999999,47.633658000000004],[-122.13234999999999,47.63513700000001],[-122.13165000000001,47.63512000000001],[-122.13037999999999,47.63510000000001],[-122.12966999999999,47.63510000000001],[-122.12966999999999,47.635222000000006],[-122.12898999999999,47.63521000000001],[-122.12836099999998,47.635204],[-122.12821799999999,47.635200000000005],[-122.12777999999999,47.635450000000006],[-122.12754,47.63559000000001],[-122.12729,47.63574200000001],[-122.127,47.63590000000001],[-122.127,47.63597000000001],[-122.1262,47.63642],[-122.12588999999998,47.6366],[-122.12499999999999,47.637130000000006],[-122.12391,47.637735000000006],[-122.12375999999999,47.63783],[-122.12362999999999,47.63783],[-122.12346999999998,47.63794],[-122.12322,47.63811000000001],[-122.12257999999999,47.638580000000005],[-122.12235999999999,47.638740000000006],[-122.12226999999999,47.638740000000006],[-122.12219999999999,47.638740000000006],[-122.12188799999998,47.638960000000004],[-122.12178999999999,47.639029],[-122.12136999999998,47.63936],[-122.12105,47.6396],[-122.12101,47.639570000000006],[-122.12075499999999,47.639790000000005],[-122.12042,47.64005],[-122.12006999999998,47.64032],[-122.11984,47.640505000000005],[-122.1198,47.64054],[-122.11956,47.64072],[-122.11902,47.64115],[-122.1188,47.64134],[-122.11874999999999,47.641365],[-122.11829999999999,47.64173],[-122.11819,47.64181],[-122.11805,47.641928],[-122.11785,47.642085],[-122.11773,47.64218],[-122.117571,47.64231000000001],[-122.11748,47.64238],[-122.11742000000001,47.64238],[-122.11723,47.64251000000001],[-122.11720000000001,47.642499],[-122.11710000000001,47.64258],[-122.11648,47.643023],[-122.11586199999999,47.64356],[-122.11507999999999,47.64429],[-122.11434,47.645071],[-122.11414,47.645340000000004],[-122.11403,47.645618000000006],[-122.11403,47.64594],[-122.11346999999999,47.645920000000004],[-122.11288799999998,47.64593000000001],[-122.11174,47.64594],[-122.11111500000001,47.64594],[-122.11110000000001,47.64526],[-122.11109,47.644859999999994],[-122.11107,47.643910000000005],[-122.11108,47.64347],[-122.11108300000001,47.643235000000004],[-122.11107,47.642364],[-122.11107,47.64232500000001],[-122.11106000000001,47.64129],[-122.11108,47.640646],[-122.11107,47.639450000000004],[-122.11106000000001,47.63922],[-122.11108,47.638740000000006],[-122.11090300000001,47.638740000000006],[-122.1109,47.638604],[-122.1109,47.638450000000006],[-122.1109,47.638076000000005],[-122.11091,47.637259],[-122.11091,47.636720000000004],[-122.11091,47.63631000000001],[-122.11092000000001,47.635605000000005],[-122.11091,47.635450000000006],[-122.11089,47.63541000000001],[-122.11086999999999,47.63537000000001],[-122.11082999999999,47.635360000000006],[-122.11085,47.635307000000005],[-122.11085,47.63506],[-122.11085,47.634014],[-122.11085,47.633900000000004],[-122.11095,47.632940000000005],[-122.11086,47.63124],[-122.11095,47.630770000000005],[-122.11092000000001,47.63009],[-122.11093,47.62935000000001],[-122.11093,47.628454000000005],[-122.11093,47.62816000000001],[-122.11094,47.627634],[-122.11039,47.62763],[-122.10862,47.627637],[-122.10686,47.62762],[-122.10565,47.627611],[-122.10434,47.627604],[-122.10434,47.62758],[-122.10370999999999,47.627561],[-122.10372,47.62758],[-122.10356999999999,47.62758],[-122.10266,47.62758],[-122.10236999999998,47.62758],[-122.10111,47.627570000000006],[-122.10036,47.627570000000006],[-122.09972,47.62756],[-122.098486,47.627561],[-122.09829,47.62755800000001],[-122.09732000000001,47.627553000000006],[-122.096548,47.627548000000004],[-122.09597,47.627545000000005],[-122.09556,47.62754],[-122.09552000000001,47.62754],[-122.09492000000002,47.62754],[-122.0937,47.62753000000001],[-122.09297,47.62753000000001],[-122.09255,47.62753000000001],[-122.09222000000001,47.62718],[-122.09209,47.627244],[-122.09203,47.62728],[-122.09195000000001,47.627344],[-122.09186000000001,47.62745],[-122.09182000000001,47.627511000000005],[-122.0909,47.62751000000001],[-122.08989,47.62753000000001],[-122.08872999999998,47.62753000000001],[-122.08819,47.62753000000001],[-122.08693,47.62753000000001],[-122.08693,47.627320000000005],[-122.08753,47.626422000000005],[-122.08753,47.625820000000004],[-122.08753,47.62532000000001],[-122.0881,47.62476],[-122.088201,47.624522000000006],[-122.08824,47.624384],[-122.08841,47.624100000000006],[-122.08849,47.62398],[-122.08861,47.62382],[-122.08869,47.623698000000005],[-122.08876,47.62371000000001],[-122.08896999999999,47.62348],[-122.08886999999999,47.623441],[-122.08888999999999,47.62341000000001],[-122.08897999999999,47.623290000000004],[-122.08915,47.62308],[-122.08931,47.62312000000001],[-122.08941,47.623003000000004],[-122.08926000000001,47.62295200000001],[-122.08933999999999,47.62285200000001],[-122.0894,47.62276000000001],[-122.08942,47.62269800000001],[-122.08951,47.622580000000006],[-122.08981,47.62268],[-122.090092,47.62237000000001],[-122.09012000000001,47.62234000000001],[-122.08986,47.622240000000005],[-122.09003,47.62210000000001],[-122.09019,47.621961],[-122.09038,47.62181],[-122.0905,47.621700000000004],[-122.09068,47.621566],[-122.09073,47.62154],[-122.09086,47.62146],[-122.09102000000001,47.62135800000001],[-122.09117,47.62125],[-122.09134,47.62111000000001],[-122.09147,47.620967],[-122.09163000000001,47.620746000000004],[-122.09172000000001,47.620655000000006],[-122.09181000000001,47.620567],[-122.09201,47.62042],[-122.09225,47.62026],[-122.09262000000001,47.620050000000006],[-122.09266000000001,47.62001000000001],[-122.09275,47.61994],[-122.09360000000001,47.61947],[-122.09367900000001,47.61943],[-122.09371,47.61942],[-122.09397,47.619324],[-122.09413,47.61926],[-122.09422000000002,47.61921],[-122.09455000000001,47.61906],[-122.09482000000001,47.618966],[-122.09512000000001,47.618845],[-122.09520300000001,47.61882],[-122.09546,47.61872],[-122.09588,47.618610000000004],[-122.09612000000001,47.61853000000001],[-122.09627,47.61844],[-122.09647,47.61836],[-122.09673,47.61826],[-122.09693,47.61818],[-122.09723000000001,47.61804],[-122.09740000000001,47.617895],[-122.09756000000002,47.61775],[-122.0977,47.61761],[-122.09781000000001,47.61753],[-122.09808,47.617332000000005],[-122.09821400000001,47.617219999999996],[-122.09864,47.61683],[-122.09906000000001,47.6164],[-122.09920000000001,47.6163],[-122.09926000000002,47.61625],[-122.09931,47.61622],[-122.09934,47.61619],[-122.09939,47.616150000000005],[-122.09945,47.61611500000001],[-122.0995,47.61608],[-122.09956100000001,47.61604],[-122.09961000000001,47.61601],[-122.09969000000001,47.615957],[-122.09971,47.61594],[-122.09973,47.61592],[-122.09975,47.61591000000001],[-122.09976999999999,47.61589],[-122.0998,47.61586],[-122.09982000000001,47.61585],[-122.099835,47.61584],[-122.099873,47.615808],[-122.09996300000002,47.61574],[-122.10012,47.615629],[-122.10022000000001,47.615570000000005],[-122.10037999999999,47.615508000000005],[-122.10055,47.61542],[-122.10065,47.61533800000001],[-122.10069999999999,47.615300000000005],[-122.10082,47.61524],[-122.10085,47.615216000000004],[-122.10087999999999,47.615190000000005],[-122.1009,47.615170000000006],[-122.10094,47.61513000000001],[-122.101,47.615100000000005],[-122.10113,47.615010000000005],[-122.10117,47.614982],[-122.10122000000001,47.614959999999996],[-122.10126000000001,47.614959999999996],[-122.10141,47.614939],[-122.10148,47.61493],[-122.10151,47.61491],[-122.10153,47.61489399999999],[-122.10157,47.614869999999996],[-122.1016,47.61485699999999],[-122.10166000000001,47.614839999999994],[-122.10177999999999,47.61479],[-122.10189,47.61475],[-122.10202999999998,47.61467],[-122.10207999999999,47.614619999999995],[-122.10215,47.61459],[-122.1022,47.61457],[-122.10244699999998,47.614416],[-122.10352999999999,47.61362],[-122.106021,47.612469],[-122.10763,47.61172],[-122.10902999999999,47.60892200000001],[-122.11173,47.60532200000001],[-122.11082999999999,47.602920000000005],[-122.11061000000001,47.602419000000005],[-122.10982999999999,47.60062],[-122.10862999999999,47.599920000000004],[-122.10865,47.599830000000004],[-122.10863499999999,47.59977000000001],[-122.10862,47.59973900000001],[-122.10861,47.59968],[-122.10861,47.599630000000005],[-122.1086,47.599590000000006],[-122.10859099999999,47.599529000000004],[-122.10856999999999,47.599469],[-122.10855,47.599410000000006],[-122.10856,47.59929],[-122.10856999999999,47.599230000000006],[-122.10857999999999,47.599160000000005],[-122.10857999999999,47.59911000000001],[-122.10856999999999,47.59908],[-122.10856999999999,47.59906],[-122.10854599999999,47.59902],[-122.10852,47.59899000000001],[-122.10848999999999,47.59895000000001],[-122.10843999999999,47.598890000000004],[-122.10838999999999,47.59884],[-122.10834999999999,47.598800000000004],[-122.10833999999998,47.598760000000006],[-122.10839999999999,47.59872000000001],[-122.10852999999999,47.59864],[-122.10858999999999,47.59861000000001],[-122.10862999999999,47.59857000000001],[-122.10870999999999,47.59851000000001],[-122.10877999999998,47.598448000000005],[-122.10892,47.59834000000001],[-122.10896,47.59830200000001],[-122.10897999999999,47.59828],[-122.11013,47.59282],[-122.11025000000001,47.592594000000005],[-122.11053,47.59202200000001],[-122.11035,47.58992],[-122.11018,47.58803],[-122.10987999999999,47.585950000000004],[-122.10986,47.58485999999999],[-122.11016000000001,47.583330000000004],[-122.110465,47.582510000000006],[-122.11065,47.5819],[-122.11105,47.58131],[-122.11160000000001,47.58071],[-122.1118,47.579992000000004],[-122.11184,47.579350000000005],[-122.11165000000001,47.579100000000004],[-122.11123,47.578722000000006],[-122.11136,47.578210000000006],[-122.11141,47.578],[-122.11145,47.577813],[-122.11143,47.57642],[-122.10976,47.575282],[-122.10896999999999,47.574742],[-122.10724,47.57361],[-122.10056,47.57249],[-122.09789,47.57204],[-122.09790000000001,47.571878],[-122.09790000000001,47.57157],[-122.09789,47.57052],[-122.09789,47.569810000000004],[-122.09894,47.56982],[-122.09894,47.56987],[-122.09919000000001,47.570009],[-122.0994,47.56992],[-122.09963,47.569762000000004],[-122.09926000000002,47.569285],[-122.09912000000001,47.569334000000005],[-122.09877999999999,47.568830000000005],[-122.0999,47.568450000000006],[-122.09978199999999,47.56812800000001],[-122.09966000000001,47.56777],[-122.09914,47.56669],[-122.09905,47.566234],[-122.09889,47.56525200000001],[-122.09872999999999,47.564710000000005],[-122.09866000000001,47.56442],[-122.09831,47.56297000000001],[-122.09824,47.56270000000001],[-122.09818,47.562475000000006],[-122.09834,47.561879],[-122.0987,47.56058],[-122.09797,47.559228],[-122.10019,47.559225],[-122.10024,47.557379999999995],[-122.1004,47.555676],[-122.10054,47.554199999999994],[-122.10049,47.553716],[-122.10041,47.552865],[-122.10041,47.551035],[-122.10045,47.550059999999995],[-122.10046999999999,47.548925000000004],[-122.10046999999999,47.54858],[-122.10046999999999,47.54849],[-122.10052,47.544933],[-122.10191,47.54488299999999],[-122.10298999999999,47.54484399999999],[-122.10383999999999,47.54481],[-122.10439,47.544798],[-122.104366,47.5469],[-122.10462000000001,47.54689],[-122.10538999999999,47.54688599999999],[-122.10537999999998,47.546102000000005],[-122.10539999999999,47.54491],[-122.10536999999998,47.54477],[-122.10544999999999,47.544754],[-122.10547799999998,47.544753],[-122.10677999999999,47.5447],[-122.1071,47.544689999999996],[-122.10712000000001,47.545277],[-122.10726000000001,47.54526],[-122.10736,47.545237],[-122.10758,47.545199000000004],[-122.10758,47.544895],[-122.10757,47.54467699999999],[-122.10792000000001,47.54465999999999],[-122.10837999999998,47.544636],[-122.10928,47.54461],[-122.10931,47.54418],[-122.10933899999999,47.543786999999995],[-122.10982999999999,47.54379],[-122.11022000000001,47.54379],[-122.1114,47.543803],[-122.11182000000001,47.544259999999994],[-122.11183,47.54354],[-122.11185,47.542978000000005],[-122.11162000000002,47.54298],[-122.11136,47.542974],[-122.1108,47.542970000000004],[-122.11081,47.54262],[-122.10998599999999,47.542642],[-122.11,47.541799999999995],[-122.11002,47.54123],[-122.11003,47.54054],[-122.10878999999998,47.540555000000005],[-122.10829,47.54056],[-122.10830999999999,47.539546],[-122.11089,47.53954],[-122.11097199999999,47.53605],[-122.11213,47.536073],[-122.11262,47.53608],[-122.11298,47.536089],[-122.11525,47.53613000000001],[-122.115227,47.536226],[-122.11522000000001,47.53633000000001],[-122.11522000000001,47.536370000000005],[-122.11523,47.536431],[-122.11528,47.53651000000001],[-122.11546999999999,47.536702000000005],[-122.11573999999999,47.536885],[-122.11591,47.53698],[-122.11599,47.53703],[-122.11602500000001,47.537056],[-122.1161,47.537098],[-122.11617,47.53714],[-122.11624,47.53722],[-122.11631,47.53732],[-122.11675,47.537948],[-122.1188,47.537966],[-122.12013999999999,47.53798],[-122.12029,47.537679999999995],[-122.12080999999999,47.537608],[-122.1216,47.537530000000004],[-122.12172,47.537442],[-122.122644,47.537279999999996],[-122.12316999999999,47.53716],[-122.12376999999998,47.537151],[-122.1242,47.536771],[-122.12486,47.536525000000005],[-122.12523999999999,47.535793000000005],[-122.12575999999999,47.53539000000001],[-122.12639999999999,47.53513000000001],[-122.12691,47.53513000000001],[-122.12727,47.53526],[-122.12765,47.535500000000006],[-122.12861,47.535650000000004],[-122.12892,47.53555000000001],[-122.12903999999999,47.535430000000005],[-122.12919,47.53531900000001],[-122.12931999999999,47.53524],[-122.1295,47.535160000000005],[-122.12972999999998,47.535089],[-122.13004,47.535030000000006],[-122.13035599999999,47.535013000000006],[-122.13056999999999,47.53502],[-122.130725,47.53504],[-122.13086999999999,47.535070000000005],[-122.131,47.53511000000001],[-122.13113,47.535160000000005],[-122.13123,47.5352],[-122.13134,47.535250000000005],[-122.13144,47.535309000000005],[-122.13152000000001,47.535365000000006],[-122.13162700000001,47.535443],[-122.13222,47.53598],[-122.13236999999998,47.536120000000004],[-122.13262999999999,47.53636],[-122.13277999999998,47.536485],[-122.133046,47.53668],[-122.133104,47.53672],[-122.13328,47.53681],[-122.13346999999999,47.536930000000005],[-122.13389,47.53712],[-122.13548999999999,47.53783],[-122.13604,47.538070000000005],[-122.13636999999999,47.5382],[-122.136528,47.538250000000005],[-122.13682,47.538340000000005],[-122.13726000000001,47.538430000000005],[-122.13753,47.53848],[-122.14108,47.53893000000001],[-122.14129000000001,47.53896],[-122.14160000000001,47.539006],[-122.14204,47.539104],[-122.14240199999999,47.5392],[-122.14292,47.53936],[-122.14294199999999,47.53936],[-122.14292,47.54167999999999],[-122.15342999999999,47.54175],[-122.15342,47.544560999999995],[-122.15342,47.54538],[-122.15356,47.545370000000005],[-122.15996,47.54543],[-122.16198,47.545455000000004],[-122.1626,47.545463],[-122.16424,47.54548],[-122.16424,47.544672],[-122.16425000000001,47.54431],[-122.16415,47.54399],[-122.16415,47.54369],[-122.16414,47.54338],[-122.16414,47.543071],[-122.164187,47.542727],[-122.16445,47.54208],[-122.16476,47.54132],[-122.16534999999999,47.54002],[-122.16556,47.54001],[-122.16765000000001,47.54007],[-122.16822,47.540085],[-122.16837999999998,47.540088],[-122.16879999999999,47.5401],[-122.169166,47.5401],[-122.16963,47.5401],[-122.16976999999999,47.5401],[-122.16986999999999,47.5401],[-122.16991,47.54014],[-122.17022999999999,47.54044],[-122.17044999999999,47.5406],[-122.170656,47.540696],[-122.17073999999998,47.54072],[-122.17083999999998,47.540756],[-122.17106999999999,47.54081],[-122.17135999999999,47.540855],[-122.17152999999999,47.540868999999994],[-122.17385999999999,47.54093],[-122.1749,47.54096],[-122.17580999999998,47.54099],[-122.17702,47.541025],[-122.17803999999998,47.54105],[-122.17903999999999,47.541078],[-122.17981999999999,47.5411],[-122.18092,47.54113],[-122.18115,47.54114],[-122.18125,47.54115],[-122.18163,47.54119],[-122.18198,47.54124099999999],[-122.1822,47.54127],[-122.18356999999999,47.541489999999996],[-122.184256,47.54160399999999],[-122.18446999999999,47.54165],[-122.18481,47.54176],[-122.18502999999998,47.54183],[-122.18544999999999,47.542010000000005],[-122.18561,47.54205],[-122.18576999999998,47.54209],[-122.18587999999998,47.542100000000005],[-122.18711,47.542120000000004],[-122.18813999999999,47.54213000000001],[-122.18834899999999,47.54213000000001],[-122.18881999999999,47.54213000000001],[-122.19055,47.54214],[-122.19065,47.54215000000001],[-122.19069,47.54216],[-122.19081,47.542212000000006],[-122.19086999999999,47.54227],[-122.1909,47.542333000000006],[-122.19092,47.54242],[-122.19091,47.54299],[-122.1909,47.54357],[-122.1909,47.54491],[-122.1909,47.545662],[-122.1909,47.5458],[-122.19098699999999,47.545798000000005],[-122.1929,47.545792000000006],[-122.19355,47.545798000000005],[-122.19382999999999,47.5458],[-122.19389,47.545795000000005],[-122.194542,47.54582],[-122.19485,47.54583],[-122.19485,47.54582],[-122.19486,47.54568],[-122.19501,47.54568],[-122.19539599999999,47.54569],[-122.19563,47.545700000000004],[-122.19562,47.54609],[-122.19628,47.54604],[-122.19649,47.54605],[-122.19676,47.54604],[-122.19727,47.54603],[-122.19727,47.546079999999996],[-122.19728,47.54618],[-122.19728,47.546274],[-122.19728300000001,47.546369],[-122.19729000000001,47.546459999999996],[-122.19729000000001,47.54655],[-122.19729000000001,47.54665],[-122.19729000000001,47.54674],[-122.1973,47.546839],[-122.1973,47.546934],[-122.1973,47.54703],[-122.19731,47.54712],[-122.19731,47.547219999999996],[-122.19731,47.547309],[-122.19732,47.547395],[-122.19732,47.54747999999999],[-122.19732,47.547560999999995],[-122.19732,47.547599999999996],[-122.19732,47.5477],[-122.19732,47.54772],[-122.19733,47.547795],[-122.197328,47.54783],[-122.19733,47.547889999999995],[-122.19733,47.54795],[-122.19733,47.547979999999995],[-122.19733,47.54806],[-122.19733,47.54808],[-122.19734,47.548170000000006],[-122.19734,47.548269],[-122.19734,47.54829],[-122.19734,47.54836],[-122.19734,47.548398000000006],[-122.19735,47.54846],[-122.19735,47.54851000000001],[-122.19735,47.548550000000006],[-122.19735,47.54863],[-122.19735,47.54865],[-122.19735,47.548744],[-122.19736,47.548837],[-122.19736,47.54886],[-122.19736,47.548930000000006],[-122.19736,47.548969],[-122.19736,47.54902],[-122.19736,47.54908],[-122.19736,47.549110000000006],[-122.19736999999999,47.5492],[-122.19736999999999,47.549219],[-122.19736999999999,47.54923],[-122.19736999999999,47.54931800000001],[-122.19737099999999,47.54936],[-122.19736999999999,47.54941],[-122.19736999999999,47.549510000000005],[-122.19738,47.54961],[-122.19738,47.54964],[-122.19738,47.54978],[-122.19738,47.549912000000006],[-122.19739,47.550048],[-122.19739,47.550185],[-122.19739,47.55032],[-122.1974,47.550459999999994],[-122.1974,47.55059],[-122.1974,47.550733],[-122.19741,47.55086299999999],[-122.19741,47.55100699999999],[-122.19741,47.55114],[-122.19741,47.55127999999999],[-122.19742000000001,47.55141999999999],[-122.19742000000001,47.551472],[-122.19742000000001,47.551559999999995],[-122.19742000000001,47.55169399999999],[-122.19742000000001,47.551832],[-122.19743,47.551955],[-122.19743,47.55209],[-122.19744,47.55245],[-122.19744,47.552710000000005],[-122.19745,47.55307],[-122.19745,47.553166],[-122.19731,47.55317],[-122.19639,47.55323],[-122.19632999999999,47.553239999999995],[-122.19616,47.553248999999994],[-122.19592999999999,47.55327],[-122.19572,47.553279999999994],[-122.1956,47.55329],[-122.1956,47.553301],[-122.19556999999999,47.553335000000004],[-122.19555,47.553379],[-122.1955,47.55345],[-122.19546999999999,47.553515000000004],[-122.19542,47.55359],[-122.195409,47.55361],[-122.19532999999998,47.55375],[-122.19525,47.553889999999996],[-122.19516999999999,47.554019999999994],[-122.19515,47.55405],[-122.19509,47.554156],[-122.19501,47.554292999999994],[-122.19493,47.55442099999999],[-122.19485,47.554559999999995],[-122.194767,47.5547],[-122.19469000000001,47.554829999999995],[-122.19464,47.554919999999996],[-122.19460000000001,47.554973999999994],[-122.19452000000001,47.555110000000006],[-122.19444,47.555245],[-122.194362,47.555381],[-122.19428,47.55552],[-122.19420000000001,47.55565],[-122.19416000000001,47.55571200000001],[-122.19411000000001,47.5558],[-122.19406000000001,47.55589],[-122.19403,47.555943],[-122.193951,47.55607],[-122.19386999999999,47.55621],[-122.19384,47.556259999999995],[-122.19378999999999,47.55634],[-122.19368,47.55652],[-122.19358,47.5567],[-122.19348,47.556869999999996],[-122.19344,47.55694],[-122.19339,47.55702599999999],[-122.193317,47.557142],[-122.1933,47.55717],[-122.19321000000001,47.557316],[-122.1932,47.55733],[-122.19312000000001,47.55747999999999],[-122.19304,47.55761],[-122.19296999999999,47.557732],[-122.19288999999999,47.55785999999999],[-122.19286999999998,47.55789599999999],[-122.19281,47.55799],[-122.19274799999998,47.558101],[-122.19266999999999,47.55824],[-122.19259,47.55836],[-122.192519,47.55849],[-122.19243999999999,47.55861],[-122.19242999999999,47.55863],[-122.19236999999998,47.55874],[-122.19229,47.55887],[-122.19222,47.55899],[-122.19214,47.55912],[-122.1921,47.559200999999995],[-122.19206999999999,47.55925],[-122.19199,47.55937],[-122.19193,47.55949],[-122.19184,47.55963],[-122.19176999999999,47.55975],[-122.19172,47.559889999999996],[-122.19176,47.55988899999999],[-122.19166400000002,47.560049],[-122.19156000000001,47.560230000000004],[-122.191489,47.560340000000004],[-122.19139,47.560520000000004],[-122.19128,47.560750000000006],[-122.19119,47.560921],[-122.191131,47.56103],[-122.19097,47.561310000000006],[-122.19085,47.561510000000006],[-122.19082,47.56158],[-122.19076999999999,47.56169],[-122.19075,47.56178],[-122.190729,47.56187],[-122.19072999999999,47.561906],[-122.190726,47.562007],[-122.190733,47.56206],[-122.19072999999999,47.56208],[-122.19073999999999,47.56209200000001],[-122.19073999999999,47.56211000000001],[-122.19073999999999,47.56212000000001],[-122.19075,47.56213700000001],[-122.19075,47.56215000000001],[-122.19075,47.56217000000001],[-122.19076,47.56218200000001],[-122.19076,47.562200000000004],[-122.19076999999999,47.56221000000001],[-122.19076999999999,47.562227],[-122.19077799999998,47.56224],[-122.19077999999999,47.56226],[-122.19078999999999,47.562270000000005],[-122.1908,47.562290000000004],[-122.1908,47.56230000000001],[-122.19081,47.56231500000001],[-122.19082,47.56233000000001],[-122.19082,47.562340000000006],[-122.19082999999999,47.562360000000005],[-122.19084,47.56237000000001],[-122.19346999999999,47.56676],[-122.1936,47.56696],[-122.1938,47.5673],[-122.194,47.567634],[-122.19398,47.56765],[-122.19401300000001,47.56765],[-122.19420000000001,47.56796],[-122.19432400000001,47.56817000000001],[-122.19434,47.56830000000001],[-122.19439,47.568650000000005],[-122.19444,47.569],[-122.19448,47.569340000000004],[-122.19453,47.56966],[-122.19457,47.56998],[-122.19461000000001,47.570236],[-122.19466000000001,47.570280999999994],[-122.19472999999999,47.570370000000004],[-122.19476999999999,47.57041],[-122.19478,47.57043],[-122.1948,47.57046],[-122.19482,47.570493],[-122.19484,47.570530000000005],[-122.19485,47.570583],[-122.19486300000001,47.570673],[-122.19488,47.57076],[-122.1949,47.57081],[-122.19491000000001,47.57087],[-122.19491000000001,47.57094],[-122.1949,47.571],[-122.1949,47.57102],[-122.19487,47.571059999999996],[-122.19485,47.57109],[-122.19475,47.57115],[-122.19472999999999,47.57116],[-122.19476,47.57133],[-122.19444,47.571492],[-122.19375,47.57185],[-122.193339,47.572064],[-122.19304,47.572219000000004],[-122.19274999999999,47.57237200000001],[-122.19246,47.572522000000006],[-122.19224,47.572630000000004],[-122.19218,47.572669],[-122.19202,47.57276],[-122.19183,47.573130000000006],[-122.19163,47.573527],[-122.19159,47.573612000000004],[-122.1915,47.5738],[-122.191444,47.573910000000005],[-122.1914,47.57399],[-122.19135,47.574098],[-122.19101,47.57478],[-122.19095,47.574918000000004],[-122.19091,47.575],[-122.19069999999999,47.575486],[-122.19033999999999,47.576214],[-122.19024,47.576419],[-122.18999,47.576416],[-122.18975999999999,47.576860999999994],[-122.18986,47.57696],[-122.19001,47.5771],[-122.190544,47.577636],[-122.19108,47.577639999999995],[-122.19121000000001,47.577639999999995],[-122.19153,47.577639999999995],[-122.19207999999999,47.57764099999999],[-122.19246,47.577642],[-122.19317,47.577639999999995],[-122.19353,47.577639999999995],[-122.19388,47.57764399999999],[-122.19427900000001,47.57764399999999],[-122.19468,47.57774],[-122.1948,47.577768],[-122.1949,47.57779],[-122.19522,47.57787],[-122.19552999999999,47.577948],[-122.19572999999998,47.577999],[-122.19603,47.578074],[-122.19636999999999,47.578160000000004],[-122.19832999999998,47.578641],[-122.19888999999999,47.578776000000005],[-122.19839999999999,47.57893000000001],[-122.19816999999999,47.579],[-122.19771,47.57914],[-122.19777099999999,47.579170000000005],[-122.19784,47.579252000000004],[-122.1979,47.57931000000001],[-122.19809,47.57941],[-122.19816,47.57945],[-122.19836199999999,47.579550000000005],[-122.19855,47.57965],[-122.19864,47.5797],[-122.19868,47.57974],[-122.19888999999999,47.579879999999996],[-122.19909,47.58004],[-122.19909,47.58005],[-122.19912000000001,47.580076],[-122.19919,47.58014],[-122.19924,47.58018],[-122.19929,47.58022],[-122.199349,47.58027],[-122.19936999999999,47.58029],[-122.19939,47.5803],[-122.1994,47.580310000000004],[-122.19945,47.58035],[-122.19948,47.580374],[-122.19951,47.58039],[-122.19954,47.580439999999996],[-122.19956,47.58047],[-122.19957,47.580496999999994],[-122.19958,47.58053],[-122.19958,47.580551],[-122.19957,47.58058],[-122.19957,47.580625],[-122.19957,47.580645999999994],[-122.1996,47.58075],[-122.19962000000001,47.58078],[-122.19963,47.580798],[-122.19969,47.58091],[-122.19973999999999,47.580943],[-122.19977999999999,47.58096],[-122.1998,47.58098],[-122.19981,47.58099],[-122.19982999999999,47.58101],[-122.19986999999999,47.58103],[-122.19988,47.581039999999994],[-122.19991,47.581058],[-122.19994,47.58107999999999],[-122.19996,47.58107999999999],[-122.19999,47.581089999999996],[-122.20001,47.581105],[-122.20002600000001,47.58112],[-122.20004,47.58114],[-122.20005,47.58115],[-122.20006000000001,47.581188],[-122.20006000000001,47.581202],[-122.20007,47.581219999999995],[-122.2001,47.58127999999999],[-122.20021000000001,47.581415],[-122.200291,47.58152],[-122.20035,47.581559999999996],[-122.20054,47.58167099999999],[-122.20057,47.58167999999999],[-122.20059,47.581689999999995],[-122.20061300000002,47.5817],[-122.20064,47.5817],[-122.20067,47.581711],[-122.20071,47.58172],[-122.20073,47.58172],[-122.20075,47.58173],[-122.20076999999999,47.58173],[-122.20082000000001,47.581739999999996],[-122.20087,47.581739999999996],[-122.20092000000001,47.581739999999996],[-122.20095,47.58175],[-122.20097,47.58175],[-122.20103,47.58175],[-122.20108,47.581759],[-122.20114000000001,47.581759999999996],[-122.20119000000001,47.581768],[-122.20127000000001,47.581779999999995],[-122.20129000000001,47.581779999999995],[-122.2013,47.581779999999995],[-122.20133,47.58179],[-122.20136400000001,47.58179],[-122.20138,47.58179],[-122.20141300000002,47.58179],[-122.20144,47.58179],[-122.20147,47.58179],[-122.20153,47.58179],[-122.20161000000002,47.581792],[-122.20167000000001,47.58179],[-122.20175,47.58179],[-122.20178,47.58179],[-122.2018,47.58179],[-122.20184,47.58179],[-122.20192000000002,47.58179],[-122.20201,47.581779999999995],[-122.20208,47.581779999999995],[-122.20211,47.581779999999995],[-122.20214,47.581779999999995],[-122.20216,47.581779999999995],[-122.20218,47.58177],[-122.2022,47.58176699999999],[-122.20222000000001,47.581759999999996],[-122.20225,47.581759999999996],[-122.20229,47.58175],[-122.20232999999999,47.58175],[-122.20237999999999,47.581739999999996],[-122.20242999999999,47.581739999999996],[-122.20248,47.581738],[-122.202493,47.581739999999996],[-122.202493,47.581779999999995],[-122.20255,47.581779999999995],[-122.20263,47.581779999999995],[-122.202677,47.581782999999994],[-122.202677,47.581756],[-122.20282,47.581758],[-122.20285,47.58177],[-122.20295,47.581779999999995],[-122.20298,47.581779999999995],[-122.203,47.58179],[-122.20301,47.58180099999999],[-122.20305,47.581802999999994],[-122.2031,47.58180399999999],[-122.20315000000001,47.58181],[-122.203178,47.58181999999999],[-122.20321000000001,47.58183],[-122.20323,47.581849999999996],[-122.20326000000001,47.58185999999999],[-122.20328,47.58188499999999],[-122.2033,47.58192],[-122.20331,47.581939999999996],[-122.20332,47.581979999999994],[-122.20332,47.58203],[-122.20332,47.58213000000001],[-122.20333,47.582170000000005],[-122.203336,47.58226],[-122.20334,47.58234],[-122.20335,47.582390000000004],[-122.20336,47.58243],[-122.20336999999999,47.582448],[-122.20336999999999,47.58247],[-122.20336,47.58248],[-122.20339799999999,47.58261],[-122.20343,47.58272],[-122.20349,47.582710000000006],[-122.20351000000001,47.58278],[-122.20352000000001,47.582876],[-122.20354,47.582975000000005],[-122.20353,47.58298],[-122.20352000000001,47.58299],[-122.20351000000001,47.582996],[-122.2035,47.583],[-122.2035,47.58301],[-122.20349,47.58302],[-122.20348,47.58303],[-122.20347,47.583040999999994],[-122.203471,47.583051],[-122.20347,47.583059999999996],[-122.20347,47.58307],[-122.20346,47.583079999999995],[-122.20346,47.58309],[-122.20347,47.5831],[-122.20347,47.583106],[-122.203471,47.58312],[-122.20347,47.58312],[-122.20349,47.58318],[-122.20351000000001,47.58325],[-122.20352000000001,47.583279999999995],[-122.20352000000001,47.5833],[-122.203531,47.58332],[-122.20354,47.583330000000004],[-122.20355,47.58334],[-122.20355,47.58336],[-122.20356000000001,47.58337],[-122.203576,47.58339],[-122.20359,47.5834],[-122.20360000000001,47.583413],[-122.20367,47.58347],[-122.20372,47.583496999999994],[-122.20376,47.583525],[-122.20386,47.583528],[-122.20405000000001,47.58353],[-122.20406000000001,47.58353],[-122.20419000000001,47.58356],[-122.20424000000001,47.583588],[-122.204327,47.583659],[-122.20436000000001,47.583679999999994],[-122.20444,47.583751],[-122.20453,47.583819999999996],[-122.20249,47.5838],[-122.20215,47.583805],[-122.20206,47.5838],[-122.20202,47.58376],[-122.20195000000001,47.58378],[-122.20190000000001,47.583786999999994],[-122.20103,47.58379],[-122.20091000000001,47.583756],[-122.20082000000001,47.583743],[-122.19982,47.583745],[-122.19975,47.58374],[-122.19966000000001,47.58373],[-122.19962000000001,47.583715000000005],[-122.19954,47.583679999999994],[-122.19948,47.583701],[-122.19932999999999,47.58374],[-122.19921000000001,47.583759],[-122.1991,47.58377],[-122.19842999999999,47.58377],[-122.19796000000001,47.58378],[-122.19731,47.58378],[-122.19654,47.58378],[-122.19654799999999,47.583929],[-122.196557,47.58427],[-122.196566,47.58532],[-122.19657,47.585623],[-122.196557,47.58655],[-122.19655,47.58672],[-122.19728,47.586722],[-122.198454,47.58672],[-122.19963,47.58672],[-122.20106000000001,47.58673],[-122.201367,47.58673],[-122.20149,47.58673],[-122.20239,47.586731],[-122.202587,47.586749],[-122.20327,47.586752000000004],[-122.20327,47.58692],[-122.20360000000001,47.586923],[-122.20369000000001,47.586921],[-122.20362000000002,47.58673],[-122.20379,47.58674],[-122.203842,47.586839999999995],[-122.20399,47.586839999999995],[-122.203981,47.586734],[-122.20568,47.58674],[-122.205906,47.586737],[-122.20603,47.587002],[-122.20615000000001,47.58725999999999],[-122.20629000000001,47.587311],[-122.20641,47.58755],[-122.206531,47.58775],[-122.206534,47.58792],[-122.20651000000001,47.58808],[-122.20651000000001,47.58823],[-122.20662900000002,47.58846],[-122.20667900000001,47.588653],[-122.20681,47.58891200000001],[-122.20686,47.589110000000005],[-122.20694,47.58925],[-122.206939,47.589388],[-122.20701000000001,47.589479999999995],[-122.20711000000001,47.589512000000006],[-122.20706200000001,47.58965],[-122.20706000000001,47.589710000000004],[-122.20706000000001,47.58981],[-122.20707,47.58987],[-122.20708,47.589957],[-122.20712000000002,47.59002],[-122.20709000000001,47.59011000000001],[-122.20717,47.590140000000005],[-122.20727000000001,47.590160000000004],[-122.20729000000001,47.590205000000005],[-122.20729000000001,47.590297],[-122.20732000000001,47.59035000000001],[-122.20741000000001,47.590450000000004],[-122.20749,47.59050800000001],[-122.20753,47.59050800000001],[-122.20774,47.590557000000004],[-122.20788,47.590662],[-122.20796000000001,47.59078],[-122.20806,47.590903000000004],[-122.2081,47.59105],[-122.208107,47.59127],[-122.20821000000001,47.5914],[-122.20836,47.591570000000004],[-122.2085,47.591663999999994],[-122.20869,47.591695],[-122.20879,47.591676],[-122.20886,47.591682],[-122.20889,47.59174],[-122.20891,47.59179],[-122.20894,47.59182],[-122.20889,47.59192],[-122.20889,47.59194],[-122.20889,47.59203000000001],[-122.20893,47.59207800000001],[-122.20898,47.59210300000001],[-122.20908,47.59215200000001],[-122.20921000000001,47.59223000000001],[-122.20923,47.592290000000006],[-122.20916000000001,47.59237500000001],[-122.20917,47.592424],[-122.20920000000001,47.59245000000001],[-122.20926000000001,47.592490000000005],[-122.20934,47.59250000000001],[-122.20944,47.59250000000001],[-122.20956000000001,47.59252000000001],[-122.20968,47.59252000000001],[-122.20981,47.59254800000001],[-122.20988,47.592628000000005],[-122.20991000000001,47.59275000000001],[-122.20992000000001,47.59286],[-122.20992000000001,47.59301200000001],[-122.20991000000001,47.593140000000005],[-122.20996000000001,47.593300000000006],[-122.209987,47.59344],[-122.21004900000001,47.593494],[-122.21012000000002,47.59357000000001],[-122.21020000000001,47.59384],[-122.21024000000001,47.594013000000004],[-122.21026500000002,47.59408],[-122.21033,47.59416],[-122.21044,47.5942],[-122.21056000000002,47.594210000000004],[-122.21061000000002,47.594210000000004],[-122.21067000000001,47.59424],[-122.21083,47.59433000000001],[-122.21093,47.5944],[-122.21102000000002,47.59471200000001],[-122.21104000000001,47.59492],[-122.21103000000001,47.59512000000001],[-122.21099000000001,47.595240000000004],[-122.21081000000001,47.595448000000005],[-122.21064000000001,47.59565800000001],[-122.21059000000001,47.59583000000001],[-122.21033,47.596323000000005],[-122.21025700000001,47.59667],[-122.21023000000001,47.596990000000005],[-122.21063400000001,47.597621],[-122.21066000000002,47.598180000000006],[-122.21101000000002,47.59890000000001],[-122.21115000000002,47.59933000000001],[-122.21102000000002,47.599410000000006],[-122.21092000000002,47.59953000000001],[-122.21089,47.599740000000004],[-122.21099000000001,47.59989],[-122.21130400000001,47.599990000000005],[-122.21178,47.600127],[-122.212185,47.60026],[-122.21249,47.600438000000004],[-122.21267,47.600570000000005],[-122.21269000000001,47.60071000000001],[-122.212905,47.60086],[-122.21305000000001,47.60098],[-122.21324000000001,47.6011],[-122.213848,47.60141],[-122.215117,47.602734000000005],[-122.21576,47.605430000000005],[-122.21598,47.60643],[-122.215486,47.607659],[-122.21541,47.60779],[-122.21541,47.60781],[-122.21539,47.607876],[-122.21535,47.60792],[-122.21533699999999,47.60801000000001],[-122.21534,47.60806],[-122.21523,47.608380000000004],[-122.21515000000001,47.608470000000004],[-122.21514,47.608494],[-122.21512000000001,47.608498000000004],[-122.21511300000002,47.608520000000006],[-122.21514,47.608543000000004],[-122.21513,47.608574000000004],[-122.2151,47.60858],[-122.21506400000001,47.608661],[-122.21503,47.60869],[-122.21501,47.608700000000006],[-122.21497000000001,47.608700000000006],[-122.21493000000001,47.60869],[-122.21492000000002,47.608700000000006],[-122.21491000000002,47.60873000000001],[-122.214882,47.60873000000001],[-122.21473,47.60882],[-122.21471000000001,47.608829],[-122.21467000000001,47.60882],[-122.21465000000002,47.60882],[-122.21460000000002,47.608796000000005],[-122.21457000000001,47.60877000000001],[-122.21455000000002,47.60877000000001],[-122.21439000000001,47.60877000000001],[-122.21436000000001,47.60877000000001],[-122.21425000000002,47.60873000000001],[-122.21423000000001,47.608720000000005],[-122.21362000000002,47.608658000000005],[-122.212185,47.60857800000001],[-122.21078,47.60804],[-122.20774,47.606621],[-122.20689,47.606883999999994],[-122.20703400000001,47.607921],[-122.20787,47.60857800000001],[-122.20836,47.60917200000001],[-122.20963,47.610001],[-122.21011000000001,47.60981],[-122.21036000000001,47.60971000000001],[-122.21018400000001,47.609970000000004],[-122.21003,47.61022],[-122.21608,47.613304],[-122.21778,47.61417],[-122.21983,47.61522],[-122.22024,47.615342000000005],[-122.22017,47.615390000000005],[-122.21998,47.615610000000004],[-122.22,47.616504],[-122.22011,47.61656],[-122.22023,47.616634],[-122.221,47.617025],[-122.22129000000001,47.61717],[-122.22269999999999,47.617183999999995],[-122.22256999999999,47.61740699999999],[-122.22249,47.617408],[-122.22247999999999,47.618376000000005],[-122.22152000000001,47.618550000000006],[-122.22142000000001,47.618570000000005],[-122.22146000000001,47.618635000000005],[-122.22179,47.61922],[-122.22249,47.619679999999995],[-122.22279999999999,47.619885999999994]],[[-122.10194,47.54858],[-122.10069999999999,47.54858],[-122.10069999999999,47.549033],[-122.10102,47.549215000000004],[-122.10115,47.549302000000004],[-122.1013,47.54937],[-122.10153,47.54943],[-122.10194,47.549510000000005],[-122.10194,47.54858]],[[-122.10806,47.545334000000004],[-122.10784,47.545390000000005],[-122.1074,47.545505000000006],[-122.1073,47.54553000000001],[-122.10710900000001,47.545547],[-122.10662,47.54553000000001],[-122.10664,47.54659],[-122.10806,47.54656],[-122.10806,47.545334000000004]]]},"name":"Bellevue"},{"boundary":{"type":"Polygon","coordinates":[[[-122.22798,47.621300000000005],[-122.22792000000001,47.62657000000001],[-122.22791400000001,47.62708],[-122.2279,47.62830000000001],[-122.22788,47.630069],[-122.22786900000001,47.63118],[-122.22788,47.63195],[-122.22789,47.633879],[-122.22789,47.63409],[-122.22787,47.63553000000001],[-122.22737099999999,47.635540000000006],[-122.22586999999999,47.63555000000001],[-122.22507699999998,47.635563000000005],[-122.22431,47.635604],[-122.22371,47.63568],[-122.22326000000001,47.63573900000001],[-122.22249,47.635833000000005],[-122.22234499999999,47.63586],[-122.22219,47.635920000000006],[-122.222118,47.635951000000006],[-122.222,47.63602],[-122.22148,47.636393000000005],[-122.21957,47.637825],[-122.21797000000001,47.639009],[-122.21784000000001,47.63917000000001],[-122.21770000000001,47.63917000000001],[-122.21769000000002,47.639630000000004],[-122.21769000000002,47.64005],[-122.21767000000001,47.640378000000005],[-122.21767000000001,47.640423],[-122.21767000000001,47.64052],[-122.21767000000001,47.64074],[-122.21767000000001,47.64104],[-122.21766000000002,47.641245],[-122.21743000000001,47.64125],[-122.21737,47.641259999999996],[-122.21727000000001,47.64128699999999],[-122.21720000000002,47.641321],[-122.21704000000001,47.641425],[-122.21668000000001,47.64166899999999],[-122.21538,47.642545000000005],[-122.21519,47.64264],[-122.21512000000001,47.642669],[-122.215,47.642714000000005],[-122.21482000000002,47.642761],[-122.21468000000002,47.642783],[-122.21460000000002,47.642796000000004],[-122.21449000000001,47.6428],[-122.21433,47.642810000000004],[-122.21412400000003,47.6428],[-122.21401000000002,47.642790000000005],[-122.213901,47.642764],[-122.21381000000001,47.64274],[-122.2137,47.642700000000005],[-122.21368000000001,47.64269],[-122.21364000000001,47.64268],[-122.21320000000001,47.642541],[-122.21306000000001,47.642500000000005],[-122.21292000000001,47.64247],[-122.21275,47.64244],[-122.21266000000001,47.642430000000004],[-122.21254,47.642430000000004],[-122.21239,47.642430000000004],[-122.21218,47.64244],[-122.21196000000002,47.64249],[-122.21172000000001,47.64255000000001],[-122.21144000000001,47.64265],[-122.21091000000001,47.642830000000004],[-122.21021600000002,47.643099],[-122.20986,47.64321],[-122.20973,47.64325],[-122.20974,47.643105000000006],[-122.20974,47.643054],[-122.20975,47.64294],[-122.20975,47.64286],[-122.20974,47.64247],[-122.20973,47.642320000000005],[-122.20965000000001,47.6414],[-122.20969000000001,47.64134],[-122.2097,47.64047],[-122.2097,47.639252000000006],[-122.20971,47.63832000000001],[-122.2097,47.637434],[-122.2097,47.636523000000004],[-122.20971,47.635619000000005],[-122.20971,47.63469],[-122.20972,47.63378],[-122.20972,47.63288],[-122.20972400000001,47.63198],[-122.20975,47.63192],[-122.20976999999999,47.63108],[-122.209765,47.630160000000004],[-122.20976,47.629245000000004],[-122.20976,47.628490000000006],[-122.20976999999999,47.62835000000001],[-122.20978,47.628260000000004],[-122.20978,47.627430000000004],[-122.20976999999999,47.626540000000006],[-122.20978,47.62472],[-122.2098,47.623850000000004],[-122.20994,47.622927000000004],[-122.20992000000001,47.62153800000001],[-122.21002000000001,47.621079],[-122.21541500000001,47.62106],[-122.21786000000002,47.621052000000006],[-122.220464,47.621049],[-122.22236999999998,47.621057],[-122.22305,47.62107],[-122.22473,47.621007],[-122.22496000000001,47.62104],[-122.22516999999999,47.621049],[-122.22688,47.62104],[-122.22756500000001,47.621030000000005],[-122.228,47.621030000000005],[-122.22798,47.621300000000005]]]},"name":"Clyde Hill"},{"boundary":{"type":"Polygon","coordinates":[[[-122.24335,47.62567000000001],[-122.24325400000002,47.625870000000006],[-122.24320000000002,47.626000000000005],[-122.24317,47.62623200000001],[-122.24314000000001,47.626490000000004],[-122.24314000000001,47.626768000000006],[-122.24322000000002,47.62715000000001],[-122.24322000000002,47.627282],[-122.24303,47.62749],[-122.24302000000002,47.627720000000004],[-122.242385,47.62771000000001],[-122.24217,47.629810000000006],[-122.24213,47.630120000000005],[-122.24218,47.630900000000004],[-122.24221000000001,47.631502000000005],[-122.24223,47.631975000000004],[-122.24229000000001,47.631975000000004],[-122.242275,47.632780000000004],[-122.24235,47.634150000000005],[-122.2424,47.63514000000001],[-122.24242300000002,47.63562],[-122.24253,47.637785],[-122.24251000000001,47.637856],[-122.24225000000001,47.63891000000001],[-122.24191000000002,47.640283999999994],[-122.24162000000003,47.64147],[-122.24110000000002,47.643619],[-122.24083,47.64472],[-122.24043,47.64522],[-122.24027000000001,47.645340000000004],[-122.24026000000002,47.64546],[-122.24020000000002,47.64555000000001],[-122.24020000000002,47.64558],[-122.24020000000002,47.6456],[-122.24023000000001,47.645610000000005],[-122.24026100000002,47.64562],[-122.24028000000001,47.645645],[-122.24027000000001,47.64566],[-122.24023000000001,47.64569],[-122.24017,47.645714000000005],[-122.24008,47.64575000000001],[-122.23999,47.645770000000006],[-122.23981,47.645793000000005],[-122.23975,47.6458],[-122.23962000000002,47.645790000000005],[-122.23943,47.645920000000004],[-122.23926000000002,47.64574],[-122.23904,47.64568],[-122.23893,47.64564],[-122.23883,47.645590000000006],[-122.23876,47.64555000000001],[-122.23866000000001,47.645500000000006],[-122.23859,47.64546],[-122.23846,47.645390000000006],[-122.23839,47.64535600000001],[-122.23835,47.64533000000001],[-122.238279,47.64528],[-122.23825000000001,47.64526],[-122.23823,47.645210000000006],[-122.23821000000001,47.645160000000004],[-122.23818,47.645085],[-122.23812000000001,47.64494],[-122.23809,47.6449],[-122.23805,47.644819999999996],[-122.23801900000001,47.644752000000004],[-122.23796000000002,47.644659999999995],[-122.23792000000002,47.644574],[-122.23790000000001,47.644496999999994],[-122.23789000000001,47.64447],[-122.23786000000001,47.644396],[-122.23784,47.644354],[-122.23779,47.644279],[-122.23775,47.644233],[-122.23763000000001,47.64412],[-122.23759000000001,47.64407],[-122.23749000000001,47.64397],[-122.23746000000001,47.643926],[-122.237346,47.64381],[-122.23730400000001,47.64377],[-122.23724000000001,47.643708000000004],[-122.23717,47.643629],[-122.23711000000002,47.643550000000005],[-122.23703,47.64348],[-122.23697,47.64342],[-122.2369,47.643350000000005],[-122.23685,47.643283999999994],[-122.23678,47.6432],[-122.23654,47.642942000000005],[-122.23603,47.64242],[-122.23304,47.63977500000001],[-122.23271,47.63949],[-122.23263,47.639421],[-122.2327,47.63936],[-122.23272999999999,47.639342000000006],[-122.23275,47.639320000000005],[-122.23281,47.63938],[-122.23282,47.63938],[-122.23284,47.63937000000001],[-122.23293,47.639320000000005],[-122.23295,47.63933600000001],[-122.23313,47.639213000000005],[-122.23317,47.639213000000005],[-122.23319000000001,47.63921200000001],[-122.23321000000001,47.6392],[-122.23322000000002,47.639190000000006],[-122.23325000000001,47.639190000000006],[-122.23325000000001,47.638360000000006],[-122.23331,47.63832500000001],[-122.23326000000002,47.63828],[-122.23324000000001,47.63821900000001],[-122.23323,47.63811000000001],[-122.23326000000002,47.637010000000004],[-122.23326300000002,47.63685],[-122.23327,47.636827],[-122.23327,47.63647],[-122.23327,47.636390000000006],[-122.23329000000001,47.63617000000001],[-122.23328000000001,47.63559000000001],[-122.23190000000001,47.63557600000001],[-122.23102000000002,47.63557000000001],[-122.2309,47.63559000000001],[-122.230792,47.63564],[-122.23073,47.635690000000004],[-122.22948,47.63562],[-122.22787,47.63553000000001],[-122.22789,47.63409],[-122.22789,47.633879],[-122.22788,47.63195],[-122.22786900000001,47.63118],[-122.22788,47.630069],[-122.2279,47.62830000000001],[-122.22791400000001,47.62708],[-122.22792000000001,47.62657000000001],[-122.22798,47.621300000000005],[-122.228,47.621030000000005],[-122.22756500000001,47.621030000000005],[-122.22688,47.62104],[-122.22516999999999,47.621049],[-122.22496000000001,47.62104],[-122.22473,47.621007],[-122.22305,47.62107],[-122.22236999999998,47.621057],[-122.22246,47.620630000000006],[-122.222156,47.62059500000001],[-122.22207999999999,47.620560000000005],[-122.22203999999999,47.620520000000006],[-122.22202,47.620483],[-122.222028,47.620449],[-122.22205,47.620407],[-122.22207999999999,47.620368000000006],[-122.22279999999999,47.619885999999994],[-122.22249,47.619679999999995],[-122.22179,47.61922],[-122.22146000000001,47.618635000000005],[-122.22142000000001,47.618570000000005],[-122.22152000000001,47.618550000000006],[-122.22247999999999,47.618376000000005],[-122.22249,47.617408],[-122.22256999999999,47.61740699999999],[-122.22269999999999,47.617183999999995],[-122.22129000000001,47.61717],[-122.221,47.617025],[-122.22023,47.616634],[-122.22011,47.61656],[-122.22,47.616504],[-122.21998,47.615610000000004],[-122.22017,47.615390000000005],[-122.22024,47.615342000000005],[-122.22082999999999,47.615520000000004],[-122.22095300000001,47.615209],[-122.22167,47.614279999999994],[-122.22242,47.613110000000006],[-122.22201,47.61246],[-122.22276,47.61047],[-122.2235,47.60855000000001],[-122.22372999999999,47.607921],[-122.224631,47.608160000000005],[-122.22606999999999,47.60913000000001],[-122.227286,47.610271],[-122.22781,47.61099],[-122.22783,47.61132],[-122.22823,47.61152],[-122.2284,47.611667999999995],[-122.22932,47.61298],[-122.23085,47.613856],[-122.23103,47.61402],[-122.23333,47.61397],[-122.23568,47.61393],[-122.23603,47.61392],[-122.23661000000001,47.614153],[-122.23716900000002,47.614439999999995],[-122.23738,47.614509],[-122.23764000000001,47.614563],[-122.23803,47.614635],[-122.2384,47.61474],[-122.23848,47.614779999999996],[-122.2387,47.61487999999999],[-122.23882,47.614943999999994],[-122.23886999999999,47.614959999999996],[-122.239,47.614959999999996],[-122.23905,47.614959999999996],[-122.23913,47.614979999999996],[-122.23918,47.61502],[-122.23951000000001,47.61529],[-122.239631,47.615390000000005],[-122.23982000000001,47.615590000000005],[-122.23999,47.615773000000004],[-122.23997,47.615790000000004],[-122.240042,47.615862],[-122.24020000000002,47.61602],[-122.24027000000001,47.61609],[-122.24042000000001,47.61623],[-122.24291000000001,47.61996],[-122.242871,47.62115000000001],[-122.24286000000001,47.621364],[-122.24281,47.622420000000005],[-122.24281,47.62256000000001],[-122.24259,47.62292000000001],[-122.24237,47.623360000000005],[-122.2423,47.623810000000006],[-122.2423,47.624190000000006],[-122.24248,47.624479],[-122.24273,47.624703000000004],[-122.24273,47.62512900000001],[-122.24307,47.62517200000001],[-122.24317,47.625220000000006],[-122.2433,47.62541000000001],[-122.24335,47.62567000000001]]]},"name":"Medina"},{"boundary":{"type":"Polygon","coordinates":[[[-122.16498,47.68982],[-122.16496000000001,47.69014000000001],[-122.16472999999999,47.69014000000001],[-122.16438,47.69014200000001],[-122.16403,47.69014000000001],[-122.16402000000001,47.69044],[-122.16369,47.69044],[-122.16371,47.690180000000005],[-122.16372,47.689910000000005],[-122.16373999999999,47.689550000000004],[-122.16373999999999,47.68946699999999],[-122.16272999999998,47.68947],[-122.16269,47.690270000000005],[-122.16265,47.69097300000001],[-122.16256,47.69319000000001],[-122.15976999999998,47.69319300000001],[-122.15957999999999,47.693180000000005],[-122.15952,47.693160000000006],[-122.15944999999999,47.69311000000001],[-122.15943999999999,47.693090000000005],[-122.15941,47.693000000000005],[-122.15944999999999,47.69212600000001],[-122.15946,47.691801],[-122.15874799999997,47.691798000000006],[-122.15674999999999,47.69179200000001],[-122.15673999999999,47.692080000000004],[-122.15672999999998,47.69245300000001],[-122.156729,47.692780000000006],[-122.15671999999999,47.69311300000001],[-122.15671999999999,47.69319000000001],[-122.15669999999999,47.694120000000005],[-122.15853999999999,47.694120000000005],[-122.15872999999998,47.694120000000005],[-122.15938999999999,47.694120000000005],[-122.15937999999998,47.69446],[-122.15937999999998,47.694539000000006],[-122.15936999999998,47.69504800000001],[-122.15932,47.69661000000001],[-122.15930999999999,47.69690200000001],[-122.15978999999999,47.69690200000001],[-122.16022000000001,47.69690200000001],[-122.16037999999999,47.69690000000001],[-122.16046,47.696874],[-122.16142,47.69690000000001],[-122.16197,47.69688],[-122.1625,47.696821],[-122.16442,47.69686],[-122.16442,47.696909000000005],[-122.16441,47.697432000000006],[-122.16436999999999,47.69838000000001],[-122.16436,47.699020000000004],[-122.16436,47.69937000000001],[-122.16436,47.69977300000001],[-122.16435,47.70018],[-122.16435,47.70106],[-122.16432999999999,47.70166],[-122.16432,47.702000000000005],[-122.16429000000001,47.70255000000001],[-122.16428,47.703],[-122.16427,47.70327],[-122.16424,47.704130000000006],[-122.16426000000001,47.705670000000005],[-122.16425000000001,47.705763000000005],[-122.16424,47.70631000000001],[-122.16425000000001,47.706624],[-122.16428,47.70727],[-122.16429000000001,47.70776],[-122.1643,47.7079],[-122.16376,47.70789],[-122.16314,47.70789],[-122.16322000000001,47.709832000000006],[-122.16317,47.711459999999995],[-122.16317,47.71157],[-122.16169000000001,47.71155],[-122.16046999999999,47.71153],[-122.15984999999999,47.71154],[-122.15926,47.711619999999996],[-122.15856,47.71175],[-122.15813999999999,47.711819999999996],[-122.15793,47.71185],[-122.157543,47.71183],[-122.15738999999999,47.71181],[-122.15714,47.711777],[-122.15665,47.71168399999999],[-122.15614199999999,47.71157],[-122.15570999999998,47.711509],[-122.15530099999998,47.711479999999995],[-122.15482999999999,47.711479999999995],[-122.15423,47.711465],[-122.15426000000001,47.71138],[-122.15416,47.711376],[-122.15423,47.711059999999996],[-122.15431,47.71054],[-122.15432,47.710524],[-122.15431,47.71011800000001],[-122.15425,47.709616000000004],[-122.15422000000001,47.70937000000001],[-122.15411,47.70884],[-122.15392,47.70782],[-122.15366,47.7078],[-122.14296,47.70767],[-122.14273999999999,47.70767],[-122.14286999999999,47.70425],[-122.14287999999999,47.70409],[-122.14287999999999,47.70408],[-122.14261,47.70407],[-122.14254,47.70407],[-122.14066000000001,47.704062],[-122.14035,47.704059],[-122.13842,47.70404],[-122.13486999999999,47.703990000000005],[-122.13465000000001,47.703981],[-122.13364,47.70396],[-122.13364,47.703994],[-122.13364,47.70402],[-122.13365,47.70408],[-122.13366,47.70421],[-122.13367,47.704276],[-122.13374199999998,47.70447],[-122.13391,47.704833],[-122.13391,47.704843],[-122.13415,47.70532000000001],[-122.13427,47.705560000000006],[-122.134333,47.70570000000001],[-122.13442,47.705934000000006],[-122.13448,47.70613000000001],[-122.13452000000001,47.70626],[-122.13453,47.70635000000001],[-122.134544,47.70646],[-122.134544,47.706500000000005],[-122.13445,47.706500000000005],[-122.13197,47.706500000000005],[-122.13197,47.707],[-122.13195,47.70758],[-122.13195,47.707879],[-122.13195,47.707969],[-122.13195,47.70801000000001],[-122.13196,47.70912500000001],[-122.13199999999999,47.71018],[-122.13199,47.71116],[-122.13199999999999,47.71123],[-122.13185,47.71122],[-122.13116000000001,47.71121],[-122.12856999999998,47.711177],[-122.12777999999999,47.71118],[-122.12729999999999,47.71118],[-122.12536999999998,47.711132000000006],[-122.12457699999999,47.71114],[-122.12418,47.711166],[-122.12377999999998,47.71119],[-122.12352999999999,47.71123],[-122.12307999999999,47.711307],[-122.12298999999999,47.71132],[-122.12156999999999,47.71163],[-122.12077999999998,47.711710000000004],[-122.11976,47.71174],[-122.11967,47.71175],[-122.11925000000001,47.7118],[-122.11876,47.711879999999994],[-122.11726000000002,47.712360000000004],[-122.11726000000002,47.71229],[-122.11729000000001,47.71164399999999],[-122.117303,47.71124399999999],[-122.11731,47.71102],[-122.11733,47.710657],[-122.11711900000002,47.710657],[-122.11704,47.710657],[-122.11599199999999,47.71066],[-122.116,47.71055500000001],[-122.115999,47.710426],[-122.11531,47.71042],[-122.11531,47.710190000000004],[-122.11472,47.710191],[-122.11471,47.710370000000005],[-122.11402000000001,47.710370000000005],[-122.11399,47.710930000000005],[-122.11399,47.71101],[-122.11399,47.711033],[-122.11399,47.71114],[-122.11343799999999,47.71114],[-122.11190300000001,47.711133000000004],[-122.1114,47.711130000000004],[-122.11088,47.711129],[-122.11077999999999,47.711129],[-122.11073999999999,47.711129],[-122.11066000000001,47.711129],[-122.11052000000001,47.711130000000004],[-122.11042,47.711133000000004],[-122.11031,47.711130000000004],[-122.11014,47.71114],[-122.1092,47.71115],[-122.10871999999999,47.711163],[-122.10871999999999,47.71101],[-122.102443,47.711072],[-122.09975,47.7111],[-122.09976,47.710985],[-122.09976,47.71097],[-122.09978799999999,47.71012],[-122.09979,47.71006],[-122.0998,47.70955000000001],[-122.0998,47.70917000000001],[-122.09984,47.707350000000005],[-122.09984,47.7072],[-122.09985,47.707003],[-122.09985,47.706801],[-122.09986,47.706520000000005],[-122.09987,47.70573000000001],[-122.09989,47.70512000000001],[-122.09991000000001,47.704145000000004],[-122.09991000000001,47.703966],[-122.09991000000001,47.703810000000004],[-122.099597,47.703790000000005],[-122.09897,47.70373000000001],[-122.09882,47.703720000000004],[-122.09672,47.70353000000001],[-122.09504,47.703390000000006],[-122.093,47.703190000000006],[-122.09297,47.703187],[-122.09294,47.70318],[-122.09291,47.70318],[-122.09282,47.70317000000001],[-122.09216,47.703120000000006],[-122.09215,47.703054],[-122.09215,47.70299000000001],[-122.09128000000001,47.70291000000001],[-122.09105000000001,47.702891],[-122.09103,47.702780000000004],[-122.09100000000001,47.702540000000006],[-122.09095,47.70238200000001],[-122.09086,47.70231000000001],[-122.09079,47.702245000000005],[-122.09074,47.702200000000005],[-122.09059,47.702090000000005],[-122.09068,47.702090000000005],[-122.09083,47.70210000000001],[-122.09087,47.70210000000001],[-122.09093,47.70210000000001],[-122.09096000000001,47.702107000000005],[-122.09094,47.70161],[-122.09093,47.70161],[-122.09052000000001,47.70159],[-122.09052000000001,47.70139],[-122.09049,47.701010000000004],[-122.09053,47.700970000000005],[-122.09069000000001,47.7008],[-122.09113,47.700320000000005],[-122.09107,47.700247],[-122.09043,47.69957000000001],[-122.09043,47.69944],[-122.09042000000001,47.699383000000005],[-122.09168000000001,47.69948],[-122.09269,47.699554000000006],[-122.09279,47.699563000000005],[-122.09293,47.699580000000005],[-122.09301,47.69928],[-122.09309,47.699000000000005],[-122.09312000000001,47.698890000000006],[-122.09312000000001,47.69832000000001],[-122.09313,47.697790000000005],[-122.09313,47.69749],[-122.09314,47.6968],[-122.09312000000001,47.695690000000006],[-122.0929,47.694406],[-122.09288,47.69428],[-122.09284,47.693470000000005],[-122.09277699999998,47.692820000000005],[-122.0926,47.69213000000001],[-122.09185000000001,47.69075000000001],[-122.09181000000001,47.6906],[-122.09178,47.69037900000001],[-122.09178,47.690064],[-122.09178,47.69004],[-122.09171,47.69005000000001],[-122.09147,47.69004],[-122.09141000000001,47.69001900000001],[-122.09134,47.690022000000006],[-122.09124000000001,47.69001000000001],[-122.09114000000001,47.690000000000005],[-122.09107300000001,47.68999],[-122.09096000000001,47.689941],[-122.09087,47.689910000000005],[-122.09079,47.689859999999996],[-122.09065000000001,47.68981],[-122.09049,47.689730000000004],[-122.09039,47.689688],[-122.0903,47.68962],[-122.09025000000001,47.689589],[-122.09022000000002,47.689498],[-122.09013,47.68944],[-122.09007,47.689372000000006],[-122.08995,47.689370000000004],[-122.08992,47.68934],[-122.08988,47.68932],[-122.089853,47.68927],[-122.08986,47.689237],[-122.089867,47.68916],[-122.0899,47.68905],[-122.08993099999999,47.68898],[-122.08981,47.688970000000005],[-122.089724,47.688970000000005],[-122.08946,47.689076],[-122.0894,47.689095],[-122.08937999999999,47.6891],[-122.08937999999999,47.68902],[-122.08937999999999,47.688987],[-122.08937999999999,47.688941],[-122.08937999999999,47.68891000000001],[-122.08937999999999,47.688855000000004],[-122.08942,47.68758],[-122.08943,47.687279999999994],[-122.08943,47.687205],[-122.08944,47.687068999999994],[-122.089493,47.687068999999994],[-122.08995,47.687072],[-122.09047,47.687079999999995],[-122.09046000000001,47.685750000000006],[-122.09046000000001,47.68571000000001],[-122.09038,47.68571000000001],[-122.09005,47.68571000000001],[-122.08949,47.68571000000001],[-122.089222,47.68571000000001],[-122.08686,47.68571000000001],[-122.08686999999999,47.685597],[-122.08689,47.68529],[-122.08691,47.684684999999995],[-122.08692,47.684419999999996],[-122.08694,47.684052],[-122.08694,47.6839],[-122.08642,47.683910000000004],[-122.08435,47.683918000000006],[-122.08432,47.68438],[-122.08202999999999,47.68438],[-122.081597,47.68439],[-122.08135,47.68438],[-122.08129000000001,47.68438],[-122.08136,47.68433],[-122.08142000000001,47.683183],[-122.07966,47.68321],[-122.07956999999999,47.683045],[-122.07927,47.682539000000006],[-122.07917499999999,47.68233000000001],[-122.07941,47.68228],[-122.08432,47.68224],[-122.08432,47.681963999999994],[-122.08434,47.68052],[-122.08436,47.67944],[-122.08436,47.67882],[-122.08436999999999,47.67862],[-122.08424000000001,47.67862],[-122.08351,47.678625000000004],[-122.08169000000001,47.678629],[-122.08169000000001,47.678233000000006],[-122.08169000000001,47.677724],[-122.08169000000001,47.677579],[-122.08169000000001,47.67739],[-122.08169400000001,47.677170000000004],[-122.08169000000001,47.67682],[-122.08169600000001,47.67616],[-122.0817,47.67590800000001],[-122.0817,47.67550800000001],[-122.0817,47.675450000000005],[-122.0817,47.67511800000001],[-122.08148,47.675084],[-122.07898999999999,47.67508],[-122.07898999999999,47.675000000000004],[-122.07887999999998,47.674997],[-122.07886999999998,47.67372],[-122.07886999999998,47.673245],[-122.07898999999999,47.67325],[-122.07898999999999,47.673190000000005],[-122.08033999999999,47.673190000000005],[-122.08056300000001,47.673190000000005],[-122.08099,47.673190000000005],[-122.081137,47.673190000000005],[-122.08113,47.672875000000005],[-122.08143,47.67288],[-122.08165000000001,47.672874],[-122.08222,47.672872000000005],[-122.08216,47.67255000000001],[-122.08212999999999,47.67237000000001],[-122.08207999999999,47.671330000000005],[-122.08168,47.671330000000005],[-122.08169000000001,47.670410000000004],[-122.08154,47.670058000000004],[-122.08141,47.66978],[-122.08136999999999,47.66967],[-122.08126000000001,47.669430000000006],[-122.08117,47.669230000000006],[-122.08109,47.66904],[-122.08085,47.66854300000001],[-122.08082,47.668470000000006],[-122.08073999999999,47.66830000000001],[-122.08071,47.66825000000001],[-122.08066400000001,47.66811300000001],[-122.08049,47.66767],[-122.08046,47.667629],[-122.08035,47.66743],[-122.08013,47.667062],[-122.08009,47.666990000000006],[-122.07995,47.66677000000001],[-122.07991,47.666714000000006],[-122.07965,47.66630000000001],[-122.0796,47.666230000000006],[-122.07946999999999,47.66604],[-122.07936,47.66571200000001],[-122.07914,47.665400000000005],[-122.07903999999999,47.66510000000001],[-122.07902,47.664730000000006],[-122.07897999999999,47.664468],[-122.07911,47.66418],[-122.07928,47.663970000000006],[-122.07954,47.663857],[-122.07985,47.663768000000005],[-122.08003,47.66372200000001],[-122.08019,47.663683],[-122.08073999999999,47.663689],[-122.0811,47.663644],[-122.08138,47.663590000000006],[-122.08152000000001,47.663509000000005],[-122.08176999999999,47.663334000000006],[-122.08243999999999,47.66316200000001],[-122.08289699999999,47.66316200000001],[-122.08333999999999,47.66302],[-122.08351,47.66301000000001],[-122.083805,47.66297000000001],[-122.08409,47.662909000000006],[-122.08439,47.66277600000001],[-122.08486,47.662440000000004],[-122.08578999999999,47.661950000000004],[-122.08585,47.66188],[-122.08594,47.66178],[-122.08596999999999,47.66165],[-122.08606,47.6614],[-122.08621000000001,47.661223],[-122.08635,47.660940000000004],[-122.086452,47.66070200000001],[-122.08652000000001,47.66033000000001],[-122.08649,47.660120000000006],[-122.086722,47.65983],[-122.08685,47.659403],[-122.08693,47.65922],[-122.08703,47.659008],[-122.08711600000001,47.65882],[-122.08721000000001,47.658581],[-122.08726100000001,47.658274],[-122.08716000000001,47.657979999999995],[-122.08714,47.65773],[-122.08705,47.65751],[-122.08729000000001,47.657453],[-122.08755000000001,47.65740699999999],[-122.08812,47.65748099999999],[-122.08816,47.657489999999996],[-122.08847999999999,47.657199999999996],[-122.08873999999999,47.657058],[-122.08891,47.656977],[-122.08902,47.656917],[-122.08912000000001,47.65683],[-122.08925,47.656672],[-122.08936,47.656531],[-122.08958,47.656079999999996],[-122.08991,47.65564],[-122.09022000000002,47.65514],[-122.09046000000001,47.65472],[-122.09061000000001,47.65447999999999],[-122.0907,47.654363999999994],[-122.09075,47.654319],[-122.09087,47.65424599999999],[-122.09104,47.65417],[-122.09123000000001,47.654114],[-122.091147,47.653986999999994],[-122.09109000000001,47.65391],[-122.09139,47.653816],[-122.09160000000001,47.65375],[-122.09187,47.653659999999995],[-122.092,47.65362],[-122.09203,47.653679999999994],[-122.09210300000001,47.65379],[-122.09216,47.653901],[-122.09219,47.65396],[-122.09222000000001,47.654016],[-122.092276,47.65416],[-122.092325,47.65427999999999],[-122.09235,47.654328],[-122.0924,47.654416],[-122.09246,47.654508],[-122.09251,47.65457],[-122.09253,47.654619999999994],[-122.09261000000001,47.65471],[-122.09272999999999,47.65484099999999],[-122.09275,47.654869999999995],[-122.09279,47.6549],[-122.09284,47.654939999999996],[-122.09286999999999,47.654959999999996],[-122.0929,47.655],[-122.09294,47.655026],[-122.09272,47.65538],[-122.09320000000001,47.655730000000005],[-122.09366000000001,47.655978000000005],[-122.09414000000001,47.656330000000004],[-122.09500799999999,47.657889999999995],[-122.09534,47.65856],[-122.096475,47.659737],[-122.09772000000001,47.659745],[-122.09772000000001,47.65937],[-122.10009,47.65937],[-122.10012,47.66071000000001],[-122.10014,47.66116],[-122.10015,47.66131000000001],[-122.10015,47.661570000000005],[-122.10015,47.661873],[-122.10015,47.66216000000001],[-122.10176,47.662180000000006],[-122.10194,47.66217800000001],[-122.103603,47.662200000000006],[-122.10446999999999,47.662209000000004],[-122.10548999999999,47.662220000000005],[-122.10555,47.662220000000005],[-122.10555,47.66328],[-122.10555,47.663360000000004],[-122.10555,47.66367],[-122.10555,47.663924],[-122.10555,47.664010000000005],[-122.1056,47.664010000000005],[-122.10577999999998,47.664],[-122.10729,47.66402],[-122.10797,47.664029],[-122.10875999999999,47.664037],[-122.10955,47.66405],[-122.11065,47.66406],[-122.11094,47.66406],[-122.11093,47.665281],[-122.11092000000001,47.66622],[-122.11091,47.666740000000004],[-122.11091,47.66709],[-122.11114,47.66705],[-122.11202,47.666889],[-122.1124,47.66687],[-122.12161,47.66702],[-122.12637999999998,47.66677000000001],[-122.12642,47.66662],[-122.12646999999998,47.66637000000001],[-122.12675999999999,47.66617000000001],[-122.12702999999999,47.665243000000004],[-122.12705,47.66513000000001],[-122.127,47.66478],[-122.12701,47.66448],[-122.12697999999999,47.664165000000004],[-122.12692999999999,47.66393000000001],[-122.12673599999998,47.663364],[-122.12597999999998,47.661750000000005],[-122.12476999999998,47.660695000000004],[-122.12438999999999,47.6604],[-122.12321,47.6595],[-122.12116999999999,47.65791],[-122.12007999999999,47.65699],[-122.11886999999999,47.6559],[-122.11871,47.655750000000005],[-122.11538999999999,47.657139],[-122.11505,47.65694],[-122.11389,47.656130000000005],[-122.11344,47.65572],[-122.11308,47.655324],[-122.1128,47.655010000000004],[-122.11224,47.65424099999999],[-122.11199,47.654146],[-122.11146000000001,47.65395],[-122.10909,47.65307],[-122.10763,47.65252],[-122.10703,47.65002],[-122.10614,47.64828],[-122.10609,47.648258000000006],[-122.10602999999999,47.648219000000005],[-122.10596999999999,47.648160000000004],[-122.105944,47.64810000000001],[-122.10589999999999,47.64789],[-122.10587999999998,47.647819999999996],[-122.10579999999999,47.64773],[-122.10569,47.647659999999995],[-122.10556999999999,47.64752],[-122.10536999999998,47.647268],[-122.10526,47.64712],[-122.10516999999999,47.646961],[-122.10498,47.646530000000006],[-122.10489,47.646136000000006],[-122.10479,47.645927],[-122.10479,47.64589],[-122.10481,47.64584],[-122.10481,47.645770000000006],[-122.10480199999999,47.645742000000006],[-122.104769,47.645665],[-122.10472999999999,47.64562],[-122.10463,47.64556],[-122.10456,47.645524],[-122.1045,47.64539200000001],[-122.10436999999999,47.64531600000001],[-122.10417,47.64529],[-122.10392999999999,47.645275000000005],[-122.1039,47.645250000000004],[-122.10356999999999,47.645070000000004],[-122.10341799999999,47.645004],[-122.10336,47.64496],[-122.10329999999999,47.64491],[-122.10316999999999,47.64476],[-122.10292999999999,47.64473],[-122.10286999999998,47.644679999999994],[-122.10282,47.6446],[-122.10266999999999,47.64447],[-122.10256999999999,47.64439],[-122.10246,47.644330000000004],[-122.10236999999998,47.64429],[-122.10230999999999,47.644279999999995],[-122.10226,47.644275],[-122.10217099999998,47.6442],[-122.10206,47.644098],[-122.102019,47.64401],[-122.10199999999999,47.643978000000004],[-122.10191,47.64392],[-122.10186999999999,47.64387],[-122.10186999999999,47.643842],[-122.10185,47.64377],[-122.10182,47.643707],[-122.10176999999999,47.643663999999994],[-122.10164,47.643574],[-122.1015,47.64352],[-122.10141,47.6435],[-122.10136999999999,47.64349],[-122.10132,47.64346],[-122.1013,47.643417],[-122.10127,47.64331000000001],[-122.10124,47.64318],[-122.10125000000001,47.64314],[-122.10128,47.64309],[-122.10134,47.64302],[-122.10142,47.64296],[-122.1015,47.642896],[-122.10155,47.642790000000005],[-122.10131,47.642120000000006],[-122.1012,47.64171],[-122.10109,47.64162699999999],[-122.101,47.64154],[-122.10082999999999,47.641465],[-122.10051,47.641310000000004],[-122.10019,47.64116],[-122.09936,47.64105],[-122.09925000000001,47.64097],[-122.09919000000001,47.640854],[-122.09907,47.640764],[-122.09892,47.64068399999999],[-122.09882999999999,47.64064],[-122.09872999999999,47.64058],[-122.0982,47.640423],[-122.09797,47.640378000000005],[-122.09764000000001,47.640248],[-122.09735,47.640170000000005],[-122.09710000000001,47.64009],[-122.09699,47.640034],[-122.09673,47.639851],[-122.096552,47.639688],[-122.09638,47.639524],[-122.09619,47.639340000000004],[-122.09599,47.639190000000006],[-122.09569,47.638980000000004],[-122.09465000000002,47.63835000000001],[-122.09443,47.63835000000001],[-122.09403,47.63813000000001],[-122.09334,47.63797],[-122.09306000000001,47.637822],[-122.09276,47.637522000000004],[-122.09261000000001,47.637326],[-122.09243,47.636970000000005],[-122.09221000000001,47.63689],[-122.09225,47.63649],[-122.09225,47.63633000000001],[-122.09124900000002,47.63526],[-122.09108,47.63524],[-122.09083,47.635222000000006],[-122.08982999999999,47.635222000000006],[-122.08753,47.633120000000005],[-122.08686,47.63111000000001],[-122.08682999999999,47.63102],[-122.08682999999999,47.630970000000005],[-122.08693,47.62753000000001],[-122.08819,47.62753000000001],[-122.08872999999998,47.62753000000001],[-122.08989,47.62753000000001],[-122.0909,47.62751000000001],[-122.09182000000001,47.627511000000005],[-122.09186000000001,47.62745],[-122.09195000000001,47.627344],[-122.09203,47.62728],[-122.09209,47.627244],[-122.09222000000001,47.62718],[-122.09255,47.62753000000001],[-122.09297,47.62753000000001],[-122.0937,47.62753000000001],[-122.09492000000002,47.62754],[-122.09552000000001,47.62754],[-122.09556,47.62754],[-122.09597,47.627545000000005],[-122.096548,47.627548000000004],[-122.09732000000001,47.627553000000006],[-122.09829,47.62755800000001],[-122.098486,47.627561],[-122.09972,47.62756],[-122.10036,47.627570000000006],[-122.10111,47.627570000000006],[-122.10236999999998,47.62758],[-122.10266,47.62758],[-122.10356999999999,47.62758],[-122.10372,47.62758],[-122.10370999999999,47.627561],[-122.10434,47.62758],[-122.10434,47.627604],[-122.10565,47.627611],[-122.10686,47.62762],[-122.10862,47.627637],[-122.11039,47.62763],[-122.11094,47.627634],[-122.11093,47.62816000000001],[-122.11093,47.628454000000005],[-122.11093,47.62935000000001],[-122.11092000000001,47.63009],[-122.11095,47.630770000000005],[-122.11086,47.63124],[-122.11095,47.632940000000005],[-122.11085,47.633900000000004],[-122.11085,47.634014],[-122.11085,47.63506],[-122.11085,47.635307000000005],[-122.11082999999999,47.635360000000006],[-122.11086999999999,47.63537000000001],[-122.11089,47.63541000000001],[-122.11091,47.635450000000006],[-122.11092000000001,47.635605000000005],[-122.11091,47.63631000000001],[-122.11091,47.636720000000004],[-122.11091,47.637259],[-122.1109,47.638076000000005],[-122.1109,47.638450000000006],[-122.1109,47.638604],[-122.11090300000001,47.638740000000006],[-122.11108,47.638740000000006],[-122.11106000000001,47.63922],[-122.11107,47.639450000000004],[-122.11108,47.640646],[-122.11106000000001,47.64129],[-122.11107,47.64232500000001],[-122.11107,47.642364],[-122.11108300000001,47.643235000000004],[-122.11108,47.64347],[-122.11107,47.643910000000005],[-122.11109,47.644859999999994],[-122.11110000000001,47.64526],[-122.11111500000001,47.64594],[-122.11174,47.64594],[-122.11288799999998,47.64593000000001],[-122.11346999999999,47.645920000000004],[-122.11403,47.64594],[-122.11403,47.645618000000006],[-122.11414,47.645340000000004],[-122.11434,47.645071],[-122.11507999999999,47.64429],[-122.11586199999999,47.64356],[-122.11648,47.643023],[-122.11710000000001,47.64258],[-122.11720000000001,47.642499],[-122.11723,47.64251000000001],[-122.11742000000001,47.64238],[-122.11748,47.64238],[-122.117571,47.64231000000001],[-122.11773,47.64218],[-122.11785,47.642085],[-122.11805,47.641928],[-122.11819,47.64181],[-122.11829999999999,47.64173],[-122.11874999999999,47.641365],[-122.1188,47.64134],[-122.11902,47.64115],[-122.11956,47.64072],[-122.1198,47.64054],[-122.11984,47.640505000000005],[-122.12006999999998,47.64032],[-122.12042,47.64005],[-122.12075499999999,47.639790000000005],[-122.12101,47.639570000000006],[-122.12105,47.6396],[-122.12136999999998,47.63936],[-122.12178999999999,47.639029],[-122.12188799999998,47.638960000000004],[-122.12219999999999,47.638740000000006],[-122.12226999999999,47.638740000000006],[-122.12235999999999,47.638740000000006],[-122.12257999999999,47.638580000000005],[-122.12322,47.63811000000001],[-122.12346999999998,47.63794],[-122.12362999999999,47.63783],[-122.12375999999999,47.63783],[-122.12391,47.637735000000006],[-122.12499999999999,47.637130000000006],[-122.12588999999998,47.6366],[-122.1262,47.63642],[-122.127,47.63597000000001],[-122.127,47.63590000000001],[-122.12729,47.63574200000001],[-122.12754,47.63559000000001],[-122.12777999999999,47.635450000000006],[-122.12821799999999,47.635200000000005],[-122.12836099999998,47.635204],[-122.12898999999999,47.63521000000001],[-122.12966999999999,47.635222000000006],[-122.12966999999999,47.63510000000001],[-122.13037999999999,47.63510000000001],[-122.13165000000001,47.63512000000001],[-122.13234999999999,47.63513700000001],[-122.13234999999999,47.633658000000004],[-122.13252,47.633654],[-122.13252999999999,47.63274500000001],[-122.13253999999999,47.63273000000001],[-122.13256,47.63270800000001],[-122.13257999999999,47.632690000000004],[-122.132612,47.632669],[-122.13272999999998,47.632600000000004],[-122.13286999999998,47.63252000000001],[-122.13336,47.632249],[-122.13347999999999,47.63217300000001],[-122.13365,47.632070000000006],[-122.13376,47.632000000000005],[-122.13385,47.631930000000004],[-122.13398,47.63182],[-122.13414,47.63166],[-122.13419,47.6316],[-122.1342,47.631581],[-122.13427,47.6315],[-122.13412000000001,47.631501],[-122.13415,47.63147],[-122.13425000000001,47.6313],[-122.13432999999999,47.63116],[-122.13439,47.631005],[-122.13441,47.63091000000001],[-122.13452000000001,47.630230000000005],[-122.13458,47.629960000000004],[-122.13463,47.629760000000005],[-122.13465000000001,47.629675000000006],[-122.13476,47.62944],[-122.13493,47.62919000000001],[-122.13501,47.629090000000005],[-122.135179,47.62891000000001],[-122.13528,47.628820000000005],[-122.1355,47.628640000000004],[-122.13561,47.62857000000001],[-122.13586999999998,47.628420000000006],[-122.13621,47.628260000000004],[-122.13652,47.62813000000001],[-122.13677999999999,47.628040000000006],[-122.13727,47.62787],[-122.13772999999999,47.62788],[-122.13773099999999,47.627790000000005],[-122.13808999999999,47.6278],[-122.13892,47.627790000000005],[-122.13978999999999,47.627790000000005],[-122.1409,47.627790000000005],[-122.14124000000001,47.627790000000005],[-122.14145,47.627790000000005],[-122.14157,47.627790000000005],[-122.14169500000001,47.627790000000005],[-122.14174,47.627790000000005],[-122.1418,47.62778],[-122.14183,47.62778],[-122.1424,47.62748],[-122.14311000000001,47.62713000000001],[-122.14309,47.627863],[-122.14309,47.628246000000004],[-122.14304,47.631491],[-122.14302,47.63208],[-122.143,47.63243000000001],[-122.14301,47.63286],[-122.14302,47.63298],[-122.14305,47.63311000000001],[-122.14306,47.63326],[-122.14305,47.633741],[-122.143046,47.634189],[-122.14304,47.63434],[-122.14304,47.63453500000001],[-122.14304,47.634750000000004],[-122.143046,47.63521000000001],[-122.14308199999999,47.635360000000006],[-122.14309,47.63564],[-122.14309,47.63591500000001],[-122.14308,47.63617000000001],[-122.14308,47.636655000000005],[-122.14308199999999,47.63689],[-122.14308,47.63762],[-122.14308,47.637860999999994],[-122.14308,47.63862],[-122.14308199999999,47.63871000000001],[-122.14309,47.638821],[-122.14308,47.64014],[-122.14307,47.64147],[-122.14306,47.642404],[-122.14306,47.64403],[-122.14305,47.64437],[-122.14305,47.64504],[-122.14305,47.645950000000006],[-122.14307799999999,47.646770000000004],[-122.14308,47.646871],[-122.14317,47.648700000000005],[-122.14324,47.650002],[-122.14326200000001,47.65054],[-122.14335,47.65227],[-122.14335,47.65234],[-122.1434,47.653285],[-122.1434,47.653459],[-122.14341,47.65357],[-122.14334,47.65506],[-122.14334,47.65513000000001],[-122.14332999999999,47.657048999999994],[-122.14334,47.65745],[-122.14334,47.657849999999996],[-122.14335,47.658530000000006],[-122.14336,47.659217],[-122.14335,47.660643],[-122.14443,47.66064],[-122.14814199999999,47.660605000000004],[-122.14908,47.6606],[-122.1495,47.660590000000006],[-122.15152,47.66057000000001],[-122.15339999999999,47.660559000000006],[-122.15432,47.66058],[-122.15558999999999,47.66061800000001],[-122.15749,47.660590000000006],[-122.15842999999998,47.66064],[-122.15861,47.66064],[-122.15874999999998,47.660644],[-122.15874999999998,47.66067],[-122.15872999999998,47.66231000000001],[-122.15872999999998,47.66246],[-122.15871999999999,47.66268],[-122.15871599999998,47.66319000000001],[-122.15870199999998,47.66431000000001],[-122.15868999999999,47.66484],[-122.15867999999999,47.66545000000001],[-122.15892,47.66545000000001],[-122.15897999999999,47.66545000000001],[-122.15907999999999,47.66545000000001],[-122.15917999999999,47.66545000000001],[-122.15932999999998,47.66545300000001],[-122.15937999999998,47.665454000000004],[-122.15959,47.665456000000006],[-122.15969,47.665456000000006],[-122.15979099999998,47.66546],[-122.15988999999999,47.66545800000001],[-122.15991,47.66545800000001],[-122.16,47.665459000000006],[-122.1601,47.66546],[-122.16041,47.665462000000005],[-122.1605,47.66546],[-122.16061,47.665464],[-122.16066000000001,47.665465000000005],[-122.16071,47.665465000000005],[-122.16081,47.665470000000006],[-122.16091,47.665467],[-122.16101,47.665467],[-122.16112000000001,47.665468000000004],[-122.16113,47.665468000000004],[-122.16122000000001,47.665470000000006],[-122.16134,47.665470000000006],[-122.16146,47.665470000000006],[-122.1617,47.665470000000006],[-122.1617,47.66553000000001],[-122.16169000000001,47.665820000000004],[-122.16168,47.66646],[-122.16168,47.66649],[-122.16197,47.66649],[-122.162596,47.66649],[-122.16354,47.666500000000006],[-122.16375,47.666500000000006],[-122.16375,47.666610000000006],[-122.16387999999999,47.66662],[-122.16399,47.666650000000004],[-122.16396999999999,47.66801200000001],[-122.163973,47.668380000000006],[-122.16396999999999,47.66891000000001],[-122.16396999999999,47.66917000000001],[-122.16396999999999,47.669404],[-122.16396900000001,47.66964],[-122.16396,47.67016],[-122.16395,47.67082],[-122.16394,47.671682999999994],[-122.16392,47.67368],[-122.163902,47.67531900000001],[-122.16391,47.676725000000005],[-122.16391,47.67721],[-122.16392,47.678990000000006],[-122.163919,47.67949],[-122.16391,47.68065],[-122.16391,47.68074],[-122.16392,47.68163],[-122.16392,47.68185999999999],[-122.163914,47.68234],[-122.16393,47.68253000000001],[-122.16394,47.6826],[-122.16399,47.68307],[-122.16411000000001,47.683782],[-122.16414,47.68397],[-122.16418,47.6842],[-122.16424,47.68459],[-122.16426000000001,47.684748],[-122.16436,47.685383],[-122.16412000000001,47.685410000000005],[-122.16182,47.685570000000006],[-122.16179,47.685386],[-122.16069,47.685770000000005],[-122.16064,47.685784],[-122.16056,47.685810000000004],[-122.16089,47.68625],[-122.16114,47.686524],[-122.16151,47.686949],[-122.16162000000001,47.687059999999995],[-122.16174,47.687169],[-122.16187,47.68727],[-122.162023,47.68736],[-122.16217999999999,47.687439999999995],[-122.162245,47.687462999999994],[-122.16234999999999,47.687508],[-122.16252999999999,47.68757],[-122.16272,47.687619999999995],[-122.16291,47.68765],[-122.163108,47.687678],[-122.16331,47.687689999999996],[-122.16351,47.687690999999994],[-122.16371,47.68767999999999],[-122.163902,47.687659999999994],[-122.16398,47.687642],[-122.16408,47.687619999999995],[-122.16425000000001,47.68758],[-122.16441,47.68753],[-122.16446,47.687514],[-122.16469000000001,47.687419],[-122.16471,47.68755],[-122.16478,47.688],[-122.16478,47.688050000000004],[-122.16479,47.68811900000001],[-122.1649,47.68887],[-122.16498,47.68946],[-122.16498,47.689558000000005],[-122.16498,47.68982]]]},"name":"Redmond"},{"boundary":{"type":"Polygon","coordinates":[[[-122.25443,47.58922],[-122.25432,47.589517],[-122.25426000000002,47.589659999999995],[-122.25422000000002,47.58976],[-122.25420000000001,47.589814],[-122.253195,47.592400000000005],[-122.25233999999999,47.592507000000005],[-122.24972000000001,47.59181],[-122.24662000000002,47.594463],[-122.24683,47.595842000000005],[-122.24683,47.59591000000001],[-122.24634,47.59577000000001],[-122.24346600000001,47.59546],[-122.24307,47.59571400000001],[-122.24286900000001,47.59543000000001],[-122.24186000000002,47.59498],[-122.23773,47.59252000000001],[-122.23508,47.593210000000006],[-122.23474,47.59329],[-122.23193,47.594021],[-122.23113000000001,47.593621],[-122.22882999999999,47.59448],[-122.22872999999998,47.59452],[-122.22802999999999,47.595321000000006],[-122.227134,47.595321000000006],[-122.22653,47.595421],[-122.22693,47.59512000000001],[-122.22443,47.58992],[-122.22425000000001,47.58905],[-122.22403,47.58802],[-122.224734,47.58712],[-122.22463400000001,47.58672],[-122.22457,47.586572000000004],[-122.22443,47.58622],[-122.22262,47.585633],[-122.22072999999999,47.58502],[-122.21931000000001,47.584962],[-122.21823,47.58492],[-122.21572,47.582390000000004],[-122.21532,47.582581],[-122.21523,47.582274],[-122.21226000000001,47.582026],[-122.20949,47.58067],[-122.20920000000001,47.58096],[-122.20920000000001,47.58055],[-122.20745000000001,47.58043],[-122.20678,47.58025],[-122.20568,47.57998],[-122.20542999999999,47.579917],[-122.20303,47.57848],[-122.202904,47.5784],[-122.20262000000001,47.578230000000005],[-122.20245,47.578126000000005],[-122.20242999999999,47.57811200000001],[-122.20232,47.578050000000005],[-122.20223,47.578050000000005],[-122.201997,47.578026],[-122.20196000000001,47.578010000000006],[-122.20194000000001,47.577996],[-122.201907,47.577963999999994],[-122.20190000000001,47.577858],[-122.20192000000002,47.57774],[-122.20188,47.57745],[-122.20174,47.57662],[-122.2026,47.57562],[-122.20354,47.574528],[-122.20332,47.57294],[-122.20303,47.572720000000004],[-122.20352000000001,47.57264],[-122.20401000000001,47.57145],[-122.20391000000001,47.571239999999996],[-122.20422000000002,47.57114],[-122.205633,47.570321],[-122.20693,47.570021],[-122.20693,47.569721],[-122.207333,47.569621],[-122.20742000000001,47.569100000000006],[-122.20769000000001,47.56769],[-122.20780300000001,47.56708],[-122.20783,47.56692],[-122.20933,47.56342],[-122.20934,47.560002000000004],[-122.20934,47.559799],[-122.20933,47.55766299999999],[-122.21006000000001,47.55752],[-122.20938,47.556332000000005],[-122.20926000000001,47.553419999999996],[-122.20924000000001,47.55232],[-122.20924000000001,47.552150000000005],[-122.20920000000001,47.549616],[-122.20919,47.54919],[-122.209182,47.548711000000004],[-122.2091,47.543639999999996],[-122.20907,47.54213000000001],[-122.209047,47.539730000000006],[-122.21051000000001,47.5384],[-122.21379,47.533836],[-122.21619000000001,47.531859999999995],[-122.21946000000001,47.528645000000004],[-122.22373999999999,47.526030000000006],[-122.22489,47.524710000000006],[-122.22523,47.52432],[-122.226167,47.524623],[-122.22645,47.52494],[-122.22715000000001,47.524909],[-122.22728000000001,47.5249],[-122.22754,47.5249],[-122.22830499999999,47.524868],[-122.2284,47.524865],[-122.22909,47.52523800000001],[-122.23391000000001,47.527843999999995],[-122.23442000000001,47.528220000000005],[-122.23505,47.528689],[-122.23624000000001,47.52957000000001],[-122.23638,47.529686],[-122.23669000000001,47.529520000000005],[-122.23686000000001,47.529517000000006],[-122.23697,47.52957000000001],[-122.23706000000001,47.529610000000005],[-122.23724000000001,47.529740000000004],[-122.23734900000001,47.52995000000001],[-122.23741000000001,47.53018],[-122.237395,47.53032],[-122.23739,47.53044],[-122.23816000000001,47.53102],[-122.23861000000001,47.530910000000006],[-122.23881,47.53092],[-122.23912000000001,47.53104],[-122.23931,47.531195000000004],[-122.239315,47.531459999999996],[-122.23926000000002,47.531679],[-122.23923300000001,47.531819999999996],[-122.24015000000001,47.532509000000005],[-122.2403,47.532624],[-122.24164000000002,47.53362],[-122.24209,47.533964],[-122.24296000000001,47.534616],[-122.24403000000001,47.53542],[-122.24411000000002,47.537521],[-122.24412000000002,47.53781],[-122.24413000000001,47.538221],[-122.24409000000001,47.53829],[-122.24394000000001,47.53851300000001],[-122.24380000000001,47.53871000000001],[-122.24269000000001,47.540391],[-122.24213,47.541219999999996],[-122.23872999999999,47.54252],[-122.23849,47.543110000000006],[-122.23745000000001,47.54564],[-122.23731000000001,47.545963],[-122.23594,47.549310000000006],[-122.23582,47.5496],[-122.23548,47.550438],[-122.23402000000002,47.55072],[-122.23499000000001,47.55101],[-122.233308,47.55241],[-122.23341,47.552710000000005],[-122.23309,47.55358],[-122.23298,47.553855],[-122.23272999999999,47.55452],[-122.23097,47.55869],[-122.23051000000001,47.55977],[-122.23039,47.56007],[-122.23031,47.56026],[-122.23022000000002,47.56046],[-122.23286999999999,47.56590800000001],[-122.23316000000001,47.56727],[-122.23333,47.568121000000005],[-122.23394,47.568121000000005],[-122.23376999999999,47.56826],[-122.23358,47.56841000000001],[-122.23503,47.569320000000005],[-122.235634,47.569320000000005],[-122.23523,47.569621],[-122.23713000000001,47.57062],[-122.23743,47.57054],[-122.237435,47.57109],[-122.23804,47.57112],[-122.23794000000001,47.57145],[-122.23834,47.57158],[-122.23894,47.57235000000001],[-122.23954,47.57239200000001],[-122.23954,47.572829],[-122.24032000000001,47.573617],[-122.24075,47.57385],[-122.24494000000001,47.57611000000001],[-122.24821000000001,47.57884],[-122.24853,47.579121],[-122.24923000000001,47.57952],[-122.24973,47.57952],[-122.24953000000001,47.57992],[-122.25193,47.58181999999999],[-122.252,47.58191],[-122.25282999999999,47.583321],[-122.25287999999999,47.583402],[-122.253434,47.58472],[-122.25393,47.584916],[-122.25393,47.585097],[-122.25393,47.587019999999995],[-122.25437,47.58885],[-122.25442000000001,47.58911500000001],[-122.25443,47.58922]]]},"name":"Mercer Island"},{"boundary":{"type":"Polygon","coordinates":[[[-122.26196700000001,47.72617000000001],[-122.26192000000002,47.72618000000001],[-122.26192000000002,47.72627000000001],[-122.26190000000001,47.726279000000005],[-122.26188,47.72633400000001],[-122.26187,47.726400000000005],[-122.26154000000001,47.72641000000001],[-122.25982,47.726400000000005],[-122.25936999999999,47.72639000000001],[-122.25961000000001,47.72619100000001],[-122.25984,47.72596000000001],[-122.25986999999999,47.72590800000001],[-122.25988,47.72589000000001],[-122.25986999999999,47.72570000000001],[-122.25982,47.725660000000005],[-122.2588,47.72565000000001],[-122.25807999999999,47.725646000000005],[-122.25748,47.725640000000006],[-122.25690900000001,47.72563500000001],[-122.25635,47.72563000000001],[-122.25553,47.725620000000006],[-122.25066000000001,47.72557000000001],[-122.25055,47.72557000000001],[-122.25049,47.72597000000001],[-122.25049,47.726020000000005],[-122.25048,47.72619000000001],[-122.25048,47.726237000000005],[-122.2505,47.72632300000001],[-122.25059900000001,47.726760000000006],[-122.25061000000001,47.72684],[-122.25059,47.727109000000006],[-122.25056000000001,47.727230000000006],[-122.25054,47.727292000000006],[-122.25052000000001,47.72733000000001],[-122.25032999999999,47.72766],[-122.25026000000001,47.727768000000005],[-122.25021000000001,47.72782],[-122.25015,47.72787],[-122.25006,47.727925000000006],[-122.24992000000002,47.727984],[-122.24985000000001,47.72800000000001],[-122.24972000000001,47.728027000000004],[-122.24950000000001,47.72803000000001],[-122.24936000000001,47.72802000000001],[-122.24923000000001,47.72800000000001],[-122.24913000000001,47.727970000000006],[-122.24906000000001,47.72795000000001],[-122.24891000000001,47.72787],[-122.24879,47.72777000000001],[-122.248575,47.72762],[-122.24851000000001,47.727590000000006],[-122.24833,47.72750200000001],[-122.24824000000001,47.727470000000004],[-122.24819000000001,47.727455000000006],[-122.24812000000001,47.727450000000005],[-122.247971,47.72744],[-122.24787,47.727450000000005],[-122.24783000000001,47.727457],[-122.24777,47.727470000000004],[-122.24764000000002,47.727520000000005],[-122.24752000000002,47.727590000000006],[-122.24744000000001,47.727650000000004],[-122.24714000000002,47.72819000000001],[-122.24706000000002,47.728330000000014],[-122.24694000000001,47.72845500000001],[-122.24682200000001,47.72854500000001],[-122.24662000000002,47.72858300000001],[-122.24651000000001,47.72859000000001],[-122.24640000000001,47.72859000000001],[-122.24615000000001,47.72855000000001],[-122.2459,47.72843900000001],[-122.24572,47.72836600000001],[-122.24538,47.72824000000001],[-122.24513,47.72816600000001],[-122.24463000000002,47.728061000000004],[-122.24414000000002,47.72803000000001],[-122.24452000000002,47.72935000000001],[-122.24463100000001,47.72972000000001],[-122.24466700000002,47.729850000000006],[-122.24480000000001,47.730320000000006],[-122.24496000000002,47.730900000000005],[-122.24515000000001,47.7316],[-122.24516000000001,47.73163],[-122.24521000000001,47.731770000000004],[-122.24438900000001,47.731885],[-122.24342000000001,47.732020000000006],[-122.24313000000001,47.732046000000004],[-122.24295000000001,47.732054000000005],[-122.2428,47.732054000000005],[-122.24277699999999,47.73209800000001],[-122.24254,47.73237000000001],[-122.24242000000001,47.73252000000001],[-122.24226000000002,47.73277000000001],[-122.24220000000001,47.732836000000006],[-122.24216000000001,47.73288],[-122.24203,47.73299000000001],[-122.24191000000002,47.733070000000005],[-122.24180000000001,47.73312500000001],[-122.24165000000002,47.73319000000001],[-122.24154000000001,47.733225000000004],[-122.24150000000002,47.73324],[-122.24139000000001,47.73326],[-122.24106000000002,47.73334500000001],[-122.240879,47.733380000000004],[-122.24061000000002,47.733410000000006],[-122.24039,47.733410000000006],[-122.23733,47.733380000000004],[-122.23649,47.73337000000001],[-122.23492000000002,47.733360000000005],[-122.23460300000002,47.733360000000005],[-122.23440000000001,47.73337000000001],[-122.23400000000001,47.73337000000001],[-122.23348,47.73337000000001],[-122.23301000000001,47.733360000000005],[-122.23176000000001,47.73333000000001],[-122.23135,47.733320000000006],[-122.22909,47.73330300000001],[-122.2273,47.733267],[-122.22583999999999,47.73324],[-122.22399300000001,47.733213000000006],[-122.22387499999999,47.73321200000001],[-122.22305,47.733200000000004],[-122.222854,47.733200000000004],[-122.22265,47.733200000000004],[-122.22189,47.73319000000001],[-122.22176999999999,47.73319000000001],[-122.22085,47.733177000000005],[-122.21946000000001,47.73317000000001],[-122.21919000000001,47.733160000000005],[-122.2189,47.733160000000005],[-122.2188,47.733160000000005],[-122.21876,47.73317000000001],[-122.218749,47.733177000000005],[-122.21876,47.73330000000001],[-122.2188,47.73431800000001],[-122.218762,47.73428],[-122.21854,47.734094],[-122.21845,47.734030000000004],[-122.21826000000001,47.73391000000001],[-122.21812000000001,47.733830000000005],[-122.21797000000001,47.73375000000001],[-122.21782000000002,47.73368],[-122.21750000000002,47.733564],[-122.21715000000002,47.733450000000005],[-122.21697,47.733410000000006],[-122.21605000000001,47.73321000000001],[-122.21589,47.73317300000001],[-122.21559,47.73311000000001],[-122.21527,47.73304],[-122.21495000000002,47.732966000000005],[-122.21439000000001,47.73281000000001],[-122.21434,47.73279300000001],[-122.21394000000001,47.732659000000005],[-122.21358000000001,47.73252000000001],[-122.21341000000001,47.732445000000006],[-122.21327000000001,47.73237000000001],[-122.21325000000002,47.73257100000001],[-122.21323000000001,47.733002000000006],[-122.21323000000001,47.73308],[-122.20785000000001,47.733008000000005],[-122.20784400000001,47.73308],[-122.20759000000001,47.733081],[-122.20736000000001,47.733083],[-122.20725000000002,47.733084],[-122.20464000000001,47.73310000000001],[-122.20460000000001,47.73310000000001],[-122.20397,47.73310000000001],[-122.20284799999999,47.73310000000001],[-122.20245,47.73310000000001],[-122.20245,47.73313800000001],[-122.20198,47.733140000000006],[-122.20177,47.733140000000006],[-122.20155700000001,47.733140000000006],[-122.20097,47.733140000000006],[-122.20085,47.73315000000001],[-122.20055,47.73315000000001],[-122.20005,47.73315300000001],[-122.19991,47.73315300000001],[-122.19942,47.73315000000001],[-122.199262,47.73315000000001],[-122.19924,47.73315000000001],[-122.19913,47.73315000000001],[-122.19824,47.733160000000005],[-122.19802,47.733160000000005],[-122.197956,47.733160000000005],[-122.19781,47.733160000000005],[-122.19748,47.733160000000005],[-122.19705,47.733160000000005],[-122.196799,47.733160000000005],[-122.19588999999999,47.733160000000005],[-122.19528,47.733160000000005],[-122.19511,47.73315000000001],[-122.1948,47.73312800000001],[-122.19448,47.73312500000001],[-122.19423400000001,47.73312000000001],[-122.19413,47.73312000000001],[-122.19405,47.73312000000001],[-122.19224,47.733126000000006],[-122.19182,47.73313000000001],[-122.19118,47.73312800000001],[-122.19063,47.733129000000005],[-122.19028,47.73313000000001],[-122.189555,47.73312800000001],[-122.18902,47.733129000000005],[-122.18902,47.73310000000001],[-122.18902,47.73306],[-122.18834999999999,47.733049],[-122.18803999999999,47.733045000000004],[-122.18754,47.733047],[-122.18699,47.73650000000001],[-122.18678999999999,47.737766],[-122.18662,47.73888],[-122.18648999999999,47.73888],[-122.1865,47.739430000000006],[-122.18652,47.740169],[-122.186645,47.740169],[-122.18661,47.74078],[-122.18649599999999,47.74074],[-122.18468,47.740117000000005],[-122.18423,47.73996],[-122.18247999999998,47.739360000000005],[-122.18204999999999,47.73922],[-122.17993999999999,47.738504000000006],[-122.17838799999997,47.73800000000001],[-122.17722,47.73762],[-122.17557999999998,47.73708],[-122.17560999999999,47.73669],[-122.17562999999998,47.736380000000004],[-122.17567999999999,47.73601000000001],[-122.17570999999998,47.73554000000001],[-122.17576999999997,47.734370000000006],[-122.17577799999997,47.734255000000005],[-122.17579999999998,47.733920000000005],[-122.17556999999998,47.73392200000001],[-122.17237999999998,47.733906000000005],[-122.17134999999999,47.733900000000006],[-122.169671,47.73391000000001],[-122.16968,47.73397000000001],[-122.16917,47.733962000000005],[-122.16717,47.73395000000001],[-122.1665,47.733956000000006],[-122.164,47.733962000000005],[-122.16402000000001,47.733450000000005],[-122.16402000000001,47.733250000000005],[-122.16403,47.733126000000006],[-122.16403,47.73302],[-122.16402000000001,47.73281000000001],[-122.16402000000001,47.732760000000006],[-122.16402000000001,47.73215000000001],[-122.16406300000001,47.730846],[-122.16407,47.73035000000001],[-122.16407,47.73021000000001],[-122.16406300000001,47.730140000000006],[-122.16404,47.730070000000005],[-122.1639,47.72999000000001],[-122.16314,47.729690000000005],[-122.16252999999999,47.729560000000006],[-122.161988,47.72952000000001],[-122.16108,47.72950000000001],[-122.16023,47.729489],[-122.15933999999999,47.729541000000005],[-122.15912,47.72953400000001],[-122.15925,47.72580000000001],[-122.15772999999999,47.72578000000001],[-122.1571,47.72577300000001],[-122.15662,47.725767000000005],[-122.15592999999998,47.72576000000001],[-122.15594999999999,47.724650000000004],[-122.15596999999998,47.723954000000006],[-122.15325,47.72392000000001],[-122.15321,47.725727000000006],[-122.15449,47.72574000000001],[-122.15467,47.72677100000001],[-122.15317999999999,47.726991000000005],[-122.15238999999998,47.727109000000006],[-122.15237799999997,47.72574000000001],[-122.15216,47.72574000000001],[-122.1495,47.725660000000005],[-122.14935,47.725660000000005],[-122.1494,47.72534000000001],[-122.14943,47.725243000000006],[-122.14958,47.724821],[-122.14986,47.723870000000005],[-122.15008999999999,47.723166000000006],[-122.150264,47.72275000000001],[-122.15042999999999,47.722260000000006],[-122.15051,47.72193000000001],[-122.15095,47.720489],[-122.15125,47.71964],[-122.15132,47.71943],[-122.15156,47.71862],[-122.15216,47.71672],[-122.15254399999999,47.715430000000005],[-122.15294999999999,47.71409],[-122.15375999999999,47.711373],[-122.15397999999999,47.71137],[-122.15416,47.711376],[-122.15426000000001,47.71138],[-122.15423,47.711465],[-122.15482999999999,47.711479999999995],[-122.15530099999998,47.711479999999995],[-122.15570999999998,47.711509],[-122.15614199999999,47.71157],[-122.15665,47.71168399999999],[-122.15714,47.711777],[-122.15738999999999,47.71181],[-122.157543,47.71183],[-122.15793,47.71185],[-122.15813999999999,47.711819999999996],[-122.15856,47.71175],[-122.15926,47.711619999999996],[-122.15984999999999,47.71154],[-122.16046999999999,47.71153],[-122.16169000000001,47.71155],[-122.16317,47.71157],[-122.16317,47.711459999999995],[-122.16322000000001,47.709832000000006],[-122.16314,47.70789],[-122.16376,47.70789],[-122.1643,47.7079],[-122.16429000000001,47.70776],[-122.16428,47.70727],[-122.16425000000001,47.706624],[-122.16424,47.70631000000001],[-122.16425000000001,47.705763000000005],[-122.16426000000001,47.705670000000005],[-122.16424,47.704130000000006],[-122.16427,47.70327],[-122.16428,47.703],[-122.16429000000001,47.70255000000001],[-122.16432,47.702000000000005],[-122.16432999999999,47.70166],[-122.16435,47.70106],[-122.16435,47.70018],[-122.16436,47.69977300000001],[-122.16436,47.69937000000001],[-122.16436,47.699020000000004],[-122.16436999999999,47.69838000000001],[-122.16441,47.697432000000006],[-122.16442,47.696909000000005],[-122.16442,47.69686],[-122.16448,47.69553000000001],[-122.16458,47.693200000000004],[-122.16468,47.69178],[-122.16472999999999,47.69014000000001],[-122.16496000000001,47.69014000000001],[-122.16498,47.68982],[-122.16498,47.689558000000005],[-122.16498,47.68946],[-122.1649,47.68887],[-122.16479,47.68811900000001],[-122.16478,47.688050000000004],[-122.16478,47.688],[-122.16471,47.68755],[-122.16469000000001,47.687419],[-122.16466000000001,47.687279999999994],[-122.16461000000001,47.68695],[-122.16447,47.686037],[-122.16436,47.685383],[-122.16426000000001,47.684748],[-122.16424,47.68459],[-122.16418,47.6842],[-122.16414,47.68397],[-122.16411000000001,47.683782],[-122.16399,47.68307],[-122.16394,47.6826],[-122.16393,47.68253000000001],[-122.163914,47.68234],[-122.16392,47.68185999999999],[-122.16392,47.68163],[-122.16391,47.68074],[-122.16391,47.68065],[-122.163919,47.67949],[-122.16392,47.678990000000006],[-122.16391,47.67721],[-122.16391,47.676725000000005],[-122.163902,47.67531900000001],[-122.16392,47.67368],[-122.16394,47.671682999999994],[-122.16395,47.67082],[-122.16396,47.67016],[-122.16396900000001,47.66964],[-122.16396999999999,47.669404],[-122.16396999999999,47.66917000000001],[-122.16396999999999,47.66891000000001],[-122.163973,47.668380000000006],[-122.16396999999999,47.66801200000001],[-122.16399,47.666650000000004],[-122.16387999999999,47.66662],[-122.16375,47.666610000000006],[-122.16375,47.666500000000006],[-122.16354,47.666500000000006],[-122.162596,47.66649],[-122.16197,47.66649],[-122.16168,47.66649],[-122.16168,47.66646],[-122.16169000000001,47.665820000000004],[-122.1617,47.66553000000001],[-122.1617,47.665470000000006],[-122.16146,47.665470000000006],[-122.16134,47.665470000000006],[-122.16122000000001,47.665470000000006],[-122.16113,47.665468000000004],[-122.16112000000001,47.665468000000004],[-122.16101,47.665467],[-122.16091,47.665467],[-122.16081,47.665470000000006],[-122.16071,47.665465000000005],[-122.16066000000001,47.665465000000005],[-122.16061,47.665464],[-122.1605,47.66546],[-122.16041,47.665462000000005],[-122.1601,47.66546],[-122.16,47.665459000000006],[-122.15991,47.66545800000001],[-122.15988999999999,47.66545800000001],[-122.15979099999998,47.66546],[-122.15969,47.665456000000006],[-122.15959,47.665456000000006],[-122.15937999999998,47.665454000000004],[-122.15932999999998,47.66545300000001],[-122.15917999999999,47.66545000000001],[-122.15907999999999,47.66545000000001],[-122.15897999999999,47.66545000000001],[-122.15892,47.66545000000001],[-122.15867999999999,47.66545000000001],[-122.15868999999999,47.66484],[-122.15870199999998,47.66431000000001],[-122.15871599999998,47.66319000000001],[-122.15871999999999,47.66268],[-122.15872999999998,47.66246],[-122.15872999999998,47.66231000000001],[-122.15874999999998,47.66067],[-122.15886999999998,47.66068],[-122.15937999999998,47.660688],[-122.16005,47.660703000000005],[-122.1638,47.660790000000006],[-122.16407,47.66078],[-122.16427,47.66064],[-122.16619,47.66062],[-122.16942,47.66058],[-122.16951,47.65711],[-122.16976,47.65712],[-122.17083999999998,47.65715],[-122.17466999999999,47.657219999999995],[-122.18021,47.657239999999994],[-122.18016999999999,47.659779],[-122.18016,47.66053000000001],[-122.18015,47.660740000000004],[-122.18015,47.660835000000006],[-122.18159,47.66088],[-122.18263999999999,47.660911000000006],[-122.18292999999998,47.660914000000005],[-122.18387999999999,47.660940000000004],[-122.18544999999999,47.660990000000005],[-122.18545999999999,47.65912],[-122.18549999999999,47.65548],[-122.18551,47.65362699999999],[-122.18542199999999,47.65362399999999],[-122.18285999999999,47.6536],[-122.18026,47.65358],[-122.18027,47.649170000000005],[-122.18022,47.646370000000005],[-122.18554999999999,47.64641],[-122.18682999999999,47.64643],[-122.18716,47.64643],[-122.18765,47.64643],[-122.19085,47.64643],[-122.19525,47.64643],[-122.19592,47.64643],[-122.19629,47.64643],[-122.19629,47.64631500000001],[-122.19629,47.645430000000005],[-122.19625,47.643758000000005],[-122.19625,47.643390000000004],[-122.19617,47.642900000000004],[-122.19628,47.642900000000004],[-122.19628,47.64288],[-122.19628,47.642809],[-122.19662000000001,47.642810000000004],[-122.19685,47.642810000000004],[-122.19703,47.642813000000004],[-122.19711000000001,47.642813000000004],[-122.19753,47.642810000000004],[-122.19753,47.642844],[-122.19806,47.642843],[-122.19846,47.642846],[-122.19846,47.642635000000006],[-122.19845,47.64242],[-122.19981,47.64242],[-122.1999,47.642500000000005],[-122.19995,47.64255000000001],[-122.2,47.6426],[-122.20004,47.64265],[-122.200196,47.642849],[-122.20048,47.64321],[-122.20060000000001,47.64334],[-122.20066200000001,47.643390000000004],[-122.20105000000001,47.643673],[-122.20128000000001,47.6438],[-122.20132000000001,47.64382],[-122.20132000000001,47.64383],[-122.20136400000001,47.643879999999996],[-122.20141000000001,47.643930000000005],[-122.20144,47.64398],[-122.20146000000001,47.644009],[-122.20148,47.64404],[-122.20154000000001,47.644151],[-122.20160000000001,47.644268],[-122.20164000000001,47.64439],[-122.20165000000001,47.64444699999999],[-122.20168000000001,47.64472],[-122.20169000000001,47.644583],[-122.20171,47.644529],[-122.201727,47.644479999999994],[-122.20175,47.64442],[-122.20179,47.64436],[-122.20184,47.6443],[-122.20188,47.644252],[-122.20193,47.644206999999994],[-122.20205,47.644098],[-122.20232999999999,47.64385],[-122.20241,47.64382],[-122.20257,47.6437],[-122.20268,47.643539000000004],[-122.20282999999999,47.643406],[-122.202947,47.64332],[-122.203041,47.643273],[-122.20320000000001,47.64321],[-122.20335,47.643170000000005],[-122.20349,47.643142000000005],[-122.20365000000001,47.64312],[-122.20401000000001,47.64312],[-122.20427000000001,47.643141],[-122.20456000000001,47.64318],[-122.20499000000001,47.643246],[-122.20542999999999,47.64331000000001],[-122.20567,47.643350000000005],[-122.20667,47.643498],[-122.20676,47.643510000000006],[-122.20699,47.643533000000005],[-122.20701000000001,47.643530000000005],[-122.20716000000002,47.64354],[-122.20757,47.64354],[-122.20777,47.64354],[-122.20837999999999,47.64349],[-122.20854,47.64348],[-122.20889,47.643433],[-122.209236,47.643369],[-122.20951000000001,47.64331000000001],[-122.20973,47.64325],[-122.20986,47.64321],[-122.21021600000002,47.643099],[-122.21091000000001,47.642830000000004],[-122.21144000000001,47.64265],[-122.21172000000001,47.64255000000001],[-122.21196000000002,47.64249],[-122.21218,47.64244],[-122.21239,47.642430000000004],[-122.21254,47.642430000000004],[-122.21266000000001,47.642430000000004],[-122.21275,47.64244],[-122.21292000000001,47.64247],[-122.21306000000001,47.642500000000005],[-122.21320000000001,47.642541],[-122.21364000000001,47.64268],[-122.21368000000001,47.64269],[-122.2137,47.642700000000005],[-122.2137,47.64273000000001],[-122.2137,47.642830000000004],[-122.2137,47.64305],[-122.21367000000001,47.644459999999995],[-122.21377,47.644459999999995],[-122.21377,47.64456],[-122.21348,47.64456],[-122.21348,47.64457],[-122.21307,47.64457],[-122.21306000000001,47.645070000000004],[-122.21306000000001,47.6452],[-122.21245,47.6452],[-122.21245,47.645485],[-122.21244,47.646102000000006],[-122.21244,47.646350000000005],[-122.21189000000001,47.64634],[-122.21189000000001,47.646358000000006],[-122.21127000000001,47.64735],[-122.21066000000002,47.648255000000006],[-122.21050000000001,47.648254],[-122.2104,47.648263],[-122.21016000000002,47.648250000000004],[-122.20998,47.648257],[-122.20982000000001,47.648270000000004],[-122.20975,47.64829],[-122.20968,47.64829],[-122.20952000000001,47.648270000000004],[-122.20935,47.64826],[-122.20916000000001,47.64824],[-122.209006,47.648250000000004],[-122.20897,47.648253000000004],[-122.2089,47.64828],[-122.20886999999999,47.64829],[-122.20884,47.64831200000001],[-122.20882,47.648320000000005],[-122.20872,47.64833000000001],[-122.20863,47.648320000000005],[-122.20857,47.64833000000001],[-122.20846,47.64837000000001],[-122.20837999999999,47.648343000000004],[-122.2083,47.64834200000001],[-122.20825,47.648360000000004],[-122.20814,47.648393000000006],[-122.20803,47.648427],[-122.20798,47.648465],[-122.20796000000001,47.648468],[-122.20791000000001,47.64846],[-122.20782000000001,47.648477],[-122.20755000000001,47.648528000000006],[-122.20752000000002,47.64855000000001],[-122.20747,47.64857000000001],[-122.2074,47.6486],[-122.20724000000001,47.64867],[-122.20715000000001,47.648692000000004],[-122.20713,47.648697],[-122.20709000000001,47.64870200000001],[-122.20706000000001,47.64871000000001],[-122.20701000000001,47.648720000000004],[-122.20696000000001,47.648740000000004],[-122.20691000000001,47.64876],[-122.20683,47.6488],[-122.2068,47.648810000000005],[-122.20674,47.648830000000004],[-122.20672,47.64884],[-122.20669000000001,47.64886],[-122.20665000000001,47.648877],[-122.20660000000001,47.648900000000005],[-122.20656000000001,47.648920000000004],[-122.20653,47.64894],[-122.2065,47.64895200000001],[-122.20646,47.648970000000006],[-122.20642000000001,47.648989],[-122.20638,47.649007],[-122.20635,47.64902],[-122.206304,47.64907],[-122.20619,47.64913000000001],[-122.206137,47.64917200000001],[-122.20609,47.649190000000004],[-122.20607,47.649210000000004],[-122.20606000000001,47.64923],[-122.20605,47.64924],[-122.20607,47.649254],[-122.20606000000001,47.64927],[-122.20603,47.64928],[-122.20599,47.649296],[-122.20597,47.64931000000001],[-122.205949,47.64931000000001],[-122.20594,47.649308000000005],[-122.20591,47.64929],[-122.20589,47.64931000000001],[-122.20589,47.649330000000006],[-122.20589,47.64935200000001],[-122.20586999999999,47.64938],[-122.20584,47.64938],[-122.2058,47.649390000000004],[-122.2058,47.6494],[-122.2058,47.64944],[-122.20579,47.64945],[-122.20577999999999,47.649479],[-122.20577999999999,47.649530000000006],[-122.20577999999999,47.64958],[-122.20577999999999,47.64964],[-122.20576999999999,47.649694],[-122.20576999999999,47.64973200000001],[-122.20576,47.64976],[-122.20576,47.64978],[-122.20576,47.649811],[-122.20575,47.64984],[-122.20575,47.649859],[-122.20573999999999,47.649874],[-122.20573999999999,47.649879999999996],[-122.20571,47.6499],[-122.20568,47.64992],[-122.20564,47.64992],[-122.20559,47.64992],[-122.20554,47.64991500000001],[-122.20552,47.64991500000001],[-122.205411,47.649910000000006],[-122.205364,47.64991200000001],[-122.20533999999999,47.649913000000005],[-122.20529,47.649916000000005],[-122.20524,47.64992],[-122.20519,47.64992],[-122.20511,47.649910000000006],[-122.205009,47.6499],[-122.20496000000001,47.649899],[-122.20491900000002,47.649901],[-122.20490000000001,47.64992],[-122.20487,47.649943],[-122.20481000000001,47.64999],[-122.20478,47.65001],[-122.20475,47.65003],[-122.20466000000002,47.65008699999999],[-122.20460000000001,47.650132000000006],[-122.20458,47.65016],[-122.20457,47.65018],[-122.20456000000001,47.6502],[-122.20454000000001,47.65023],[-122.20455000000001,47.650279999999995],[-122.20456000000001,47.650337],[-122.20456000000001,47.650389],[-122.20455000000001,47.65041],[-122.20455000000001,47.650434],[-122.20455000000001,47.650459999999995],[-122.20454000000001,47.650475],[-122.20453,47.650479999999995],[-122.20452000000002,47.65049],[-122.20454000000001,47.65063],[-122.20458,47.650635],[-122.20459000000001,47.650639],[-122.20461000000002,47.650652],[-122.20461000000002,47.650662999999994],[-122.20462000000002,47.65069],[-122.20463000000001,47.650722],[-122.20468000000001,47.65079],[-122.20473,47.65085],[-122.2048,47.650942],[-122.20482000000001,47.650983999999994],[-122.20486000000001,47.651039],[-122.204868,47.65107999999999],[-122.20488,47.6511],[-122.20490000000001,47.651123],[-122.20493,47.65114],[-122.20494000000001,47.65118],[-122.20495000000001,47.65121],[-122.20495000000001,47.65125],[-122.20495000000001,47.65127999999999],[-122.20496000000001,47.651313],[-122.20496000000001,47.651345],[-122.20499000000001,47.65139],[-122.20501,47.65141],[-122.20501,47.65143],[-122.205017,47.65145999999999],[-122.20501,47.651506],[-122.20502,47.651519],[-122.20523,47.651775],[-122.20529,47.65185999999999],[-122.20537999999999,47.651982],[-122.20537999999999,47.652],[-122.20539,47.65202],[-122.2054,47.65204],[-122.205409,47.65206],[-122.20542,47.652108000000005],[-122.20542,47.65218],[-122.20541300000001,47.65228],[-122.2054,47.652300000000004],[-122.20537999999999,47.652324],[-122.20537999999999,47.65237200000001],[-122.20539,47.65242],[-122.20539,47.65244],[-122.20537699999998,47.65244],[-122.20537699999998,47.652480999999995],[-122.20537999999999,47.65252],[-122.20544,47.652530000000006],[-122.20545,47.652547],[-122.20551,47.652547],[-122.20551,47.6526],[-122.20553,47.65262],[-122.20561000000001,47.652685],[-122.20564,47.65276],[-122.20566000000001,47.652769],[-122.20572,47.65283],[-122.20572,47.652848],[-122.20572,47.65296],[-122.205681,47.65296],[-122.20571,47.653060999999994],[-122.20572,47.6531],[-122.20573999999999,47.65318],[-122.20576,47.65325],[-122.20577999999999,47.653330000000004],[-122.20579,47.6534],[-122.2058,47.65347],[-122.2058,47.653506],[-122.20586,47.653506],[-122.20586999999999,47.653510000000004],[-122.20586999999999,47.65354],[-122.20584,47.65357],[-122.20586999999999,47.653642],[-122.2059,47.65372],[-122.20595,47.65379],[-122.20601,47.65383],[-122.20602000000001,47.653839999999995],[-122.20607,47.653839999999995],[-122.20607,47.65378],[-122.20629000000001,47.65378],[-122.20629000000001,47.653856],[-122.20625000000001,47.653859],[-122.20625000000001,47.65388299999999],[-122.20625000000001,47.653999999999996],[-122.20625000000001,47.654114],[-122.20629000000001,47.65413],[-122.20629000000001,47.65436],[-122.20618,47.65436],[-122.20617,47.65445],[-122.20616500000001,47.654489999999996],[-122.20616000000001,47.65454],[-122.20616000000001,47.654619999999994],[-122.20617,47.65467399999999],[-122.20619,47.65472],[-122.20624000000001,47.654779999999995],[-122.20631,47.654838],[-122.20638,47.654892],[-122.20642000000001,47.65493],[-122.20646,47.654959999999996],[-122.20647,47.655010000000004],[-122.20651000000001,47.655103000000004],[-122.20655000000001,47.65518],[-122.20657,47.655225],[-122.20661000000001,47.65529],[-122.20665000000001,47.655330000000006],[-122.2067,47.655367],[-122.20676900000001,47.6554],[-122.20681,47.65543],[-122.20686,47.655463],[-122.20688,47.655491],[-122.20692000000001,47.65551000000001],[-122.20700000000001,47.655570000000004],[-122.20711000000001,47.65564],[-122.20724000000001,47.655730000000005],[-122.20735,47.65582],[-122.20741000000001,47.655879999999996],[-122.20747,47.65592],[-122.20750000000001,47.65596],[-122.20758000000001,47.65605],[-122.20767000000001,47.656151],[-122.20769000000001,47.656194],[-122.20774,47.65621],[-122.2078,47.65625],[-122.20778,47.656259999999996],[-122.20775,47.65629],[-122.20778,47.65631200000001],[-122.207835,47.65635],[-122.20790000000001,47.65643],[-122.20792100000001,47.65647],[-122.20794000000001,47.65655],[-122.20795000000001,47.6566],[-122.20794000000001,47.656639999999996],[-122.20793,47.656676],[-122.20791000000001,47.656717],[-122.20799000000001,47.65673],[-122.20793,47.656819999999996],[-122.20790000000001,47.656859999999995],[-122.20789,47.65687],[-122.20790000000001,47.65691],[-122.20790000000001,47.65692],[-122.207876,47.65696],[-122.20784,47.65698],[-122.20779,47.657022],[-122.20772000000001,47.65707999999999],[-122.20764000000001,47.65716],[-122.20760000000001,47.657219999999995],[-122.20759000000001,47.65727999999999],[-122.20757,47.657379999999996],[-122.20756000000002,47.65743],[-122.20755000000001,47.657469999999996],[-122.20754000000001,47.657498],[-122.20752000000002,47.65755],[-122.20750000000001,47.65759],[-122.20747,47.657639999999994],[-122.20746000000001,47.65767999999999],[-122.20745000000001,47.65771],[-122.20745000000001,47.65772],[-122.20743900000001,47.657759999999996],[-122.20742000000001,47.657799999999995],[-122.20738,47.6579],[-122.20735,47.65797],[-122.20733,47.65804],[-122.20731,47.658094],[-122.20728000000001,47.65817500000001],[-122.20724000000001,47.65828],[-122.20720000000001,47.65842],[-122.20716000000002,47.658533000000006],[-122.20714000000001,47.6586],[-122.20711000000001,47.65865],[-122.20709000000001,47.6587],[-122.20706000000001,47.658746],[-122.20704,47.658770000000004],[-122.20701700000001,47.658770000000004],[-122.20700000000001,47.658792000000005],[-122.20697,47.658826],[-122.20695300000001,47.65889],[-122.20696000000001,47.658909],[-122.20704,47.659150000000004],[-122.20707,47.659150000000004],[-122.20709000000001,47.65916],[-122.20710700000001,47.65916],[-122.20712000000002,47.659171],[-122.20713,47.659181],[-122.20713,47.6592],[-122.20713,47.65921],[-122.20714000000001,47.65922],[-122.20717,47.65924],[-122.20719700000001,47.65925],[-122.20722000000002,47.65927],[-122.20725000000002,47.65929],[-122.20726000000002,47.659304],[-122.20728000000001,47.65932],[-122.20731,47.659344],[-122.20733,47.65937],[-122.20734,47.65938],[-122.20733,47.65941],[-122.20733,47.65943],[-122.20732000000001,47.65945],[-122.20732000000001,47.659479],[-122.20732000000001,47.65949],[-122.20731,47.6595],[-122.20728000000001,47.659510000000004],[-122.20725000000002,47.659510000000004],[-122.20721000000002,47.659510000000004],[-122.20718000000001,47.659510000000004],[-122.20716000000002,47.659510000000004],[-122.20714000000001,47.659514],[-122.20712000000002,47.659510000000004],[-122.20709000000001,47.6595],[-122.20705000000001,47.659527],[-122.20704,47.65954],[-122.20702000000001,47.659566],[-122.20700000000001,47.65959],[-122.20699,47.65961],[-122.20698,47.65963],[-122.20698,47.659639999999996],[-122.206987,47.659655],[-122.20701000000001,47.659679999999994],[-122.20702000000001,47.659698],[-122.20703,47.65972],[-122.20705000000001,47.659743],[-122.20706000000001,47.65976],[-122.20707900000001,47.65979],[-122.20710000000001,47.65981],[-122.20712000000002,47.659822],[-122.20713,47.659836],[-122.20715000000001,47.659859999999995],[-122.20716000000002,47.65987],[-122.20717,47.6599],[-122.20719000000001,47.659909],[-122.20719000000001,47.659924],[-122.20719000000001,47.65994],[-122.20717,47.65995],[-122.20716000000002,47.65997],[-122.20716000000002,47.659994],[-122.20716000000002,47.66002],[-122.20715000000001,47.660050000000005],[-122.20715000000001,47.66008],[-122.20715000000001,47.66011000000001],[-122.20714000000001,47.660140000000006],[-122.20714000000001,47.660168000000006],[-122.20714000000001,47.6602],[-122.20714000000001,47.660222000000005],[-122.20715200000001,47.66024],[-122.20716000000002,47.660270000000004],[-122.20718000000001,47.66029],[-122.20719000000001,47.660320000000006],[-122.20721000000002,47.660340000000005],[-122.20721000000002,47.66037000000001],[-122.20720000000001,47.660410000000006],[-122.20720000000001,47.660439000000004],[-122.20717,47.660450000000004],[-122.20714000000001,47.66046],[-122.20710000000001,47.660475000000005],[-122.20707,47.66049],[-122.20707,47.66051000000001],[-122.20708,47.660540000000005],[-122.207077,47.660561],[-122.20707900000001,47.660610000000005],[-122.20708,47.66066],[-122.20708,47.660688],[-122.20709000000001,47.66071000000001],[-122.20709000000001,47.66076],[-122.20709000000001,47.660810000000005],[-122.20710000000001,47.660835000000006],[-122.20712000000002,47.66088],[-122.20714000000001,47.660920000000004],[-122.20714000000001,47.660940000000004],[-122.20713,47.660970000000006],[-122.20712000000002,47.660990000000005],[-122.20712000000002,47.661014],[-122.20713,47.661100000000005],[-122.20715000000001,47.661100000000005],[-122.20716000000002,47.66111000000001],[-122.20717,47.66114],[-122.20718000000001,47.661190000000005],[-122.20720000000001,47.66124],[-122.20721000000002,47.661262],[-122.20722000000002,47.661286999999994],[-122.20722000000002,47.661308000000005],[-122.20724000000001,47.661370000000005],[-122.20725000000002,47.661390000000004],[-122.20727000000001,47.661390000000004],[-122.20729000000001,47.66144],[-122.20731,47.66149],[-122.20732000000001,47.66151800000001],[-122.20733,47.66154],[-122.20733,47.661556000000004],[-122.20734,47.661566],[-122.207336,47.66158],[-122.20733,47.661606],[-122.20733,47.66163],[-122.207336,47.66166],[-122.20734300000001,47.661680999999994],[-122.20735,47.6617],[-122.20737,47.661729],[-122.20738,47.66176],[-122.20739,47.661769],[-122.20742000000001,47.661771],[-122.20745000000001,47.661774],[-122.20746000000001,47.661783],[-122.20747,47.6618],[-122.20747,47.66183],[-122.20747,47.66185],[-122.207482,47.661901],[-122.20750000000001,47.661923],[-122.20752000000002,47.66196],[-122.20755000000001,47.662003000000006],[-122.20759000000001,47.662045000000006],[-122.20764000000001,47.66208],[-122.20767000000001,47.66214000000001],[-122.20771,47.66217100000001],[-122.20774,47.66219000000001],[-122.20776000000001,47.662214000000006],[-122.20778,47.662240000000004],[-122.207783,47.662259000000006],[-122.20779,47.662293000000005],[-122.20786000000001,47.662289],[-122.20801,47.66239200000001],[-122.20804,47.66253000000001],[-122.20808,47.662600000000005],[-122.20809,47.662620000000004],[-122.20809,47.662647],[-122.20809,47.662670000000006],[-122.20808,47.66270000000001],[-122.20808,47.66274000000001],[-122.20808,47.66283000000001],[-122.20808,47.66286],[-122.20809,47.662870000000005],[-122.208115,47.662890000000004],[-122.208143,47.66290000000001],[-122.20817,47.66291500000001],[-122.20818,47.66293000000001],[-122.20819,47.66295100000001],[-122.2082,47.66297000000001],[-122.20821000000001,47.663000000000004],[-122.20821000000001,47.663030000000006],[-122.20822000000001,47.663070000000005],[-122.20822000000001,47.66310000000001],[-122.20822000000001,47.66313000000001],[-122.20821000000001,47.663160000000005],[-122.20821000000001,47.66319000000001],[-122.20819,47.66322],[-122.20818,47.66326],[-122.2082,47.66326],[-122.2082,47.663288],[-122.2082,47.66333600000001],[-122.2082,47.66337000000001],[-122.20819,47.66344],[-122.20819,47.66346],[-122.20819,47.663590000000006],[-122.20819,47.66366],[-122.20818799999999,47.663723000000005],[-122.20814,47.663720000000005],[-122.2081,47.663720000000005],[-122.20809,47.663728000000006],[-122.20808,47.663741],[-122.20809,47.66376],[-122.20809,47.66377000000001],[-122.20818,47.663776000000006],[-122.20824,47.66378],[-122.20824,47.663823],[-122.20825,47.66388],[-122.20825,47.663920000000005],[-122.20824,47.664010000000005],[-122.20824,47.66406],[-122.20822000000001,47.66411000000001],[-122.20818,47.664117000000005],[-122.208115,47.664122000000006],[-122.20786000000001,47.66413000000001],[-122.20781000000001,47.66413000000001],[-122.20775,47.66414],[-122.20772000000001,47.664190000000005],[-122.20768000000001,47.66426],[-122.20822000000001,47.66426],[-122.20822000000001,47.664501],[-122.20765500000002,47.664500000000004],[-122.20764000000001,47.664604],[-122.20771,47.66469],[-122.20781000000001,47.664727],[-122.20790000000001,47.664729],[-122.20795000000001,47.664774],[-122.20795000000001,47.6648],[-122.20795000000001,47.66486],[-122.20794000000001,47.664950000000005],[-122.20783,47.664950000000005],[-122.20779,47.664950000000005],[-122.20777,47.664988],[-122.20775,47.665040000000005],[-122.20776000000001,47.66510000000001],[-122.207795,47.66519100000001],[-122.20779,47.66527000000001],[-122.20777,47.66535000000001],[-122.20774,47.66548],[-122.20771,47.66554000000001],[-122.20767000000001,47.665621],[-122.20765000000002,47.665666],[-122.20758000000001,47.66575000000001],[-122.20754000000001,47.665808000000006],[-122.20755000000001,47.66586],[-122.20754000000001,47.66592000000001],[-122.20751000000001,47.66597000000001],[-122.20743,47.66612000000001],[-122.20736000000001,47.666257],[-122.20733,47.666360000000005],[-122.20731,47.666491],[-122.20732000000001,47.66669],[-122.20732000000001,47.666830000000004],[-122.20827,47.666830000000004],[-122.20925000000001,47.666830000000004],[-122.20963,47.666830000000004],[-122.20963,47.667570000000005],[-122.20932,47.66756],[-122.2084,47.667530000000006],[-122.20841,47.667626],[-122.20841,47.66765],[-122.2084,47.66843000000001],[-122.20836,47.66843000000001],[-122.20788,47.668440000000004],[-122.207835,47.668440000000004],[-122.2078,47.668441],[-122.2078,47.668451000000005],[-122.20779,47.668470000000006],[-122.20777,47.668493000000005],[-122.20773,47.66851000000001],[-122.20765000000002,47.66854000000001],[-122.20759000000001,47.66857400000001],[-122.20751000000001,47.66863000000001],[-122.20743,47.66870000000001],[-122.20734300000001,47.668760000000006],[-122.20729800000001,47.66881000000001],[-122.20727000000001,47.668848000000004],[-122.20725000000002,47.66890000000001],[-122.20724000000001,47.66896200000001],[-122.20722000000002,47.66903000000001],[-122.20719000000001,47.66912000000001],[-122.20719000000001,47.669160000000005],[-122.20718000000001,47.669228000000004],[-122.20717,47.669290000000004],[-122.20716000000002,47.669380000000004],[-122.20715000000001,47.66945200000001],[-122.20715000000001,47.669520000000006],[-122.20715000000001,47.66957800000001],[-122.20714000000001,47.66961200000001],[-122.20714000000001,47.669624],[-122.20713,47.669658000000005],[-122.20712000000002,47.66968],[-122.20711000000001,47.669727],[-122.20707,47.66978],[-122.20703,47.669843],[-122.20700000000001,47.66988],[-122.20697,47.669900000000005],[-122.20692000000001,47.669970000000006],[-122.206882,47.67001500000001],[-122.20685,47.670061],[-122.20685,47.670091],[-122.20683,47.670137000000004],[-122.206804,47.670181],[-122.20676999999999,47.67022],[-122.20673,47.67026],[-122.2067,47.67028],[-122.20666000000001,47.670311000000005],[-122.20662400000002,47.670350000000006],[-122.20657,47.67038],[-122.20653,47.670410000000004],[-122.20649300000001,47.67045],[-122.20648,47.67046],[-122.206444,47.670500000000004],[-122.20643,47.67052],[-122.20636999999999,47.670590000000004],[-122.20633,47.67066],[-122.20629000000001,47.67072],[-122.20625500000001,47.670770000000005],[-122.20624000000001,47.67081],[-122.20623,47.67086],[-122.20624000000001,47.6709],[-122.20624000000001,47.67094],[-122.20621900000002,47.67099],[-122.20617,47.671039],[-122.2061,47.671110000000006],[-122.20605,47.671171],[-122.205994,47.671213],[-122.20596,47.67124],[-122.20594,47.67126699999999],[-122.20592,47.6713],[-122.205904,47.67136],[-122.2059,47.67142],[-122.20589,47.67147],[-122.20588,47.671527],[-122.2059,47.67157],[-122.20591,47.6716],[-122.20593,47.67164],[-122.20593,47.671656],[-122.20593,47.67167],[-122.20593,47.671707],[-122.20593,47.67176],[-122.20593,47.671834],[-122.20593,47.671875],[-122.20593,47.671957],[-122.20593,47.67199],[-122.20593,47.672039000000005],[-122.20594,47.672129000000005],[-122.20594,47.67215000000001],[-122.20594,47.67219000000001],[-122.20593,47.672230000000006],[-122.20593,47.672270000000005],[-122.20592,47.672346000000005],[-122.20592,47.6724],[-122.20592,47.672450000000005],[-122.20591,47.67248],[-122.20591,47.67251900000001],[-122.20591,47.672563000000004],[-122.2059,47.672601],[-122.2059,47.672637],[-122.20589,47.672650000000004],[-122.20594,47.672650000000004],[-122.20594,47.672720000000005],[-122.20594,47.672850000000004],[-122.20594,47.672871],[-122.20589,47.672872000000005],[-122.20589,47.672940000000004],[-122.20589,47.67295300000001],[-122.20588,47.67297000000001],[-122.20588,47.67302],[-122.20585,47.67302],[-122.20585,47.673030000000004],[-122.20588,47.673030000000004],[-122.20589,47.67307],[-122.2059,47.67313000000001],[-122.20593,47.67317800000001],[-122.20595,47.673230000000004],[-122.20598,47.673273],[-122.20598,47.673320000000004],[-122.20599,47.67333500000001],[-122.20599,47.67336],[-122.20602000000001,47.6734],[-122.20604,47.673437],[-122.20606000000001,47.67346],[-122.20607,47.67347],[-122.206088,47.67349],[-122.20615000000001,47.67353000000001],[-122.20618,47.673562000000004],[-122.20624000000001,47.673604],[-122.20629000000001,47.67366],[-122.20633,47.673730000000006],[-122.20638,47.6738],[-122.20641,47.67382],[-122.20642000000001,47.67384],[-122.20647,47.67385],[-122.2065,47.673857],[-122.20654,47.67386],[-122.20657,47.673863],[-122.20667,47.67385],[-122.20672,47.67385],[-122.20676,47.67385],[-122.20703,47.674433],[-122.20711000000001,47.67456],[-122.20718000000001,47.674645],[-122.20752700000001,47.67444],[-122.2077,47.674344],[-122.20774,47.674341],[-122.20776000000001,47.67434],[-122.20777,47.674352000000006],[-122.20779,47.67437],[-122.20782000000001,47.674394],[-122.20783,47.67441],[-122.20785000000001,47.674425],[-122.20787,47.67444],[-122.20790000000001,47.674459999999996],[-122.20792000000002,47.67448099999999],[-122.20792000000002,47.6745],[-122.20790000000001,47.67451500000001],[-122.20773,47.674609],[-122.20772000000001,47.67462],[-122.20769000000001,47.674659999999996],[-122.20768000000001,47.674682],[-122.20767000000001,47.674714],[-122.20767000000001,47.67476],[-122.20768000000001,47.67479],[-122.20769600000001,47.67481],[-122.2077,47.67483],[-122.2077,47.674855],[-122.207705,47.674865],[-122.20771,47.6749],[-122.2077,47.674910000000004],[-122.20769600000001,47.67493],[-122.20768000000001,47.674955000000004],[-122.20768000000001,47.67497],[-122.20768000000001,47.67498],[-122.2077,47.674997],[-122.20773799999999,47.67502],[-122.20777,47.67503500000001],[-122.20782000000001,47.67506],[-122.20783,47.67507500000001],[-122.20785000000001,47.675086],[-122.20788,47.67510000000001],[-122.20790000000001,47.67511000000001],[-122.20791000000001,47.67512000000001],[-122.20793,47.675121000000004],[-122.20795000000001,47.67512000000001],[-122.20797,47.67511000000001],[-122.20798,47.67510000000001],[-122.208,47.675090000000004],[-122.20802,47.67507500000001],[-122.20803,47.67506],[-122.20805,47.67503800000001],[-122.20806999999999,47.67502],[-122.20809,47.67501000000001],[-122.2081,47.675000000000004],[-122.20813,47.675000000000004],[-122.20815,47.675000000000004],[-122.20818,47.67501000000001],[-122.20819,47.67502],[-122.2082,47.675028000000005],[-122.20822000000001,47.675042000000005],[-122.20824,47.675050000000006],[-122.20826000000001,47.675069],[-122.20828,47.675090000000004],[-122.2083,47.67510000000001],[-122.20832,47.67511700000001],[-122.20833999999999,47.67512000000001],[-122.20839,47.67512000000001],[-122.20841,47.67512000000001],[-122.20844,47.67511000000001],[-122.20849,47.67510000000001],[-122.20866000000001,47.67506],[-122.2087,47.67506],[-122.20872,47.675050000000006],[-122.20881,47.675028000000005],[-122.20882999999999,47.67502],[-122.20893,47.67502],[-122.20896400000001,47.67503000000001],[-122.209,47.675039000000005],[-122.20903,47.675050000000006],[-122.20906000000001,47.67506],[-122.20908,47.675070000000005],[-122.20911000000001,47.675090000000004],[-122.20913,47.67510000000001],[-122.20914,47.67512000000001],[-122.20916000000001,47.675140000000006],[-122.20918,47.67515900000001],[-122.20919,47.67517000000001],[-122.20920000000001,47.67519000000001],[-122.20933,47.67522],[-122.20944,47.67533000000001],[-122.20955000000001,47.675450000000005],[-122.20968,47.675385000000006],[-122.20975,47.67549],[-122.20978,47.67553000000001],[-122.20988,47.675683],[-122.20994,47.675760000000004],[-122.20995,47.675782000000005],[-122.21002000000001,47.675850000000004],[-122.21005000000001,47.675867],[-122.21006000000001,47.675903000000005],[-122.21007,47.67595000000001],[-122.21009000000001,47.67598],[-122.21014000000001,47.676],[-122.21022000000002,47.676023],[-122.21029000000001,47.67606],[-122.21034,47.67611900000001],[-122.21039,47.676170000000006],[-122.21045000000001,47.67622],[-122.21052000000002,47.67628],[-122.210582,47.676336000000006],[-122.21060000000001,47.67636],[-122.21064000000001,47.6764],[-122.21073,47.67647],[-122.21075,47.67649],[-122.21078,47.676520000000004],[-122.21082000000001,47.67655500000001],[-122.21086000000001,47.676586],[-122.21091000000001,47.67662],[-122.21095000000001,47.67666],[-122.21100000000001,47.676700000000004],[-122.21103000000001,47.676727],[-122.21109000000001,47.67676],[-122.21113000000001,47.676790000000004],[-122.21118000000001,47.67683],[-122.21123000000001,47.676866],[-122.21125900000003,47.6769],[-122.21129000000002,47.67693200000001],[-122.21135000000001,47.67698],[-122.21136000000001,47.67699],[-122.21137,47.676995000000005],[-122.21142800000001,47.67703],[-122.21145000000001,47.67705],[-122.21151000000002,47.6771],[-122.21154000000001,47.677122000000004],[-122.21159000000002,47.677170000000004],[-122.21161000000002,47.67718],[-122.21167000000001,47.67722],[-122.21168900000002,47.67724],[-122.21174,47.67729],[-122.21176000000001,47.677310000000006],[-122.21182000000002,47.67736],[-122.211841,47.67738],[-122.21189000000001,47.677419],[-122.21191000000002,47.67743],[-122.21196000000002,47.677479999999996],[-122.21199000000001,47.677509],[-122.21203,47.677554],[-122.21206000000001,47.677585],[-122.2121,47.67762],[-122.21213,47.677659999999996],[-122.21217,47.67769],[-122.21219,47.67772],[-122.21224000000001,47.67776],[-122.21227,47.67779],[-122.2123,47.67783],[-122.21233,47.677859999999995],[-122.21236999999999,47.6779],[-122.212411,47.67794],[-122.21247,47.677988],[-122.2125,47.678019000000006],[-122.21255000000001,47.678061],[-122.212597,47.67811800000001],[-122.21263,47.678160000000005],[-122.21266000000001,47.67822],[-122.21267,47.678247],[-122.2127,47.67831000000001],[-122.21275,47.67839000000001],[-122.21276,47.678408000000005],[-122.21279,47.678450000000005],[-122.21282000000001,47.67850000000001],[-122.21287,47.678560000000004],[-122.2129,47.67859500000001],[-122.21293,47.678627],[-122.21298,47.678670000000004],[-122.212998,47.678700000000006],[-122.21303,47.67873000000001],[-122.21308,47.678790000000006],[-122.21313,47.678830000000005],[-122.21317,47.67886],[-122.21325000000002,47.67891000000001],[-122.21333,47.67895000000001],[-122.21339,47.678971000000004],[-122.21347,47.679010000000005],[-122.21352000000002,47.679030000000004],[-122.213578,47.67904],[-122.21364000000001,47.679061],[-122.2137,47.679072000000005],[-122.21375,47.679100000000005],[-122.21378,47.679124],[-122.2138,47.67915300000001],[-122.213806,47.679169],[-122.21382000000001,47.67919200000001],[-122.21383,47.67922],[-122.21382000000001,47.679300000000005],[-122.2138,47.67934],[-122.21378,47.67938],[-122.21375,47.679451],[-122.21372000000001,47.679497],[-122.21371,47.67953000000001],[-122.21371,47.67956],[-122.21371,47.679584],[-122.213716,47.67964],[-122.21375,47.679700000000004],[-122.213762,47.679730000000006],[-122.21379,47.679798000000005],[-122.21385000000001,47.67991000000001],[-122.21390600000001,47.68001],[-122.213938,47.68006],[-122.21398,47.680130000000005],[-122.214031,47.68021],[-122.21407,47.680277],[-122.21410000000002,47.680324],[-122.21412800000002,47.68038],[-122.21414000000001,47.68041],[-122.21415000000002,47.680479999999996],[-122.21418000000001,47.680541],[-122.21420000000002,47.68057],[-122.21425900000003,47.680628],[-122.21430000000001,47.680676],[-122.21437,47.680730000000004],[-122.21440000000001,47.680758000000004],[-122.21446000000002,47.6808],[-122.21452000000002,47.68085],[-122.21455000000002,47.6809],[-122.21457000000001,47.68098],[-122.21461000000002,47.681104],[-122.21463000000001,47.681182],[-122.21465000000002,47.681235],[-122.21469000000002,47.6813],[-122.21472000000001,47.681356],[-122.21475600000001,47.68141],[-122.21479000000001,47.68147],[-122.21482000000002,47.68152],[-122.21484000000001,47.68157],[-122.21487,47.68165],[-122.214882,47.6817],[-122.21492000000002,47.68180099999999],[-122.214932,47.681869999999996],[-122.21494000000001,47.6819],[-122.21496000000002,47.68199],[-122.21496000000002,47.682010000000005],[-122.21499000000001,47.68207],[-122.21503,47.68213000000001],[-122.21505,47.68215000000001],[-122.2151,47.682226],[-122.215145,47.682311000000006],[-122.21516000000001,47.682347],[-122.21524000000001,47.68245],[-122.21528,47.68251000000001],[-122.21536,47.682590000000005],[-122.21537,47.6826],[-122.2154,47.68263],[-122.21542000000001,47.68268],[-122.21545,47.68272],[-122.21545,47.68273000000001],[-122.21547199999999,47.68275200000001],[-122.21549,47.682798000000005],[-122.21549,47.68282],[-122.21551000000001,47.68282],[-122.21556000000001,47.68281],[-122.21559,47.682874],[-122.21565000000001,47.68303],[-122.21567,47.683059],[-122.21572,47.68318],[-122.21579,47.683330000000005],[-122.2158,47.683355000000006],[-122.21585,47.68347],[-122.2158,47.68348399999999],[-122.21581,47.683510000000005],[-122.21588,47.683668],[-122.21583,47.683679999999995],[-122.21585,47.68376],[-122.21588,47.68382],[-122.21588,47.683837],[-122.21589,47.68387],[-122.21592100000001,47.683913000000004],[-122.21593,47.683999],[-122.21594,47.68401],[-122.21595,47.68405],[-122.21597,47.684095],[-122.21601000000001,47.684137],[-122.21601000000001,47.68415],[-122.21604,47.684216],[-122.21604,47.68422699999999],[-122.21608,47.68429],[-122.21610000000001,47.6843],[-122.21612000000002,47.684346],[-122.21616000000002,47.6844],[-122.21618000000001,47.684432],[-122.21619000000001,47.684459999999994],[-122.21621000000002,47.6845],[-122.21622600000002,47.68455],[-122.21624200000001,47.68459],[-122.21626000000002,47.68463],[-122.21628000000001,47.68467999999999],[-122.21629100000001,47.68472],[-122.216316,47.684776],[-122.21634,47.684839999999994],[-122.21637,47.684911],[-122.21639,47.68493],[-122.21640000000001,47.684956],[-122.21643,47.68502],[-122.21647,47.685065],[-122.21648,47.685086],[-122.21652000000002,47.685137000000005],[-122.21655000000001,47.685209],[-122.21656100000001,47.68524],[-122.21657,47.685276],[-122.21659000000001,47.685309000000004],[-122.21662000000002,47.685383],[-122.21663000000001,47.6854],[-122.21665100000001,47.68546],[-122.21667000000001,47.685500000000005],[-122.2167,47.685559000000005],[-122.21672000000001,47.6856],[-122.21673,47.68565],[-122.21674,47.685700000000004],[-122.21675,47.68572],[-122.21675,47.685745000000004],[-122.21679,47.685786],[-122.2168,47.685825],[-122.2168,47.68585],[-122.21682000000001,47.685877],[-122.21683,47.68589],[-122.21686000000001,47.685930000000006],[-122.21687,47.685953000000005],[-122.21687,47.685974],[-122.21689,47.686003],[-122.21689,47.686034],[-122.21690000000001,47.68607],[-122.21691000000001,47.686110000000006],[-122.21692000000002,47.68614],[-122.21693,47.686170000000004],[-122.21695000000001,47.6862],[-122.21697,47.68622],[-122.21698,47.68623],[-122.21702000000002,47.68628],[-122.21705000000001,47.68634],[-122.21708000000001,47.686458],[-122.21710000000002,47.686530000000005],[-122.21710000000002,47.68659],[-122.21711000000002,47.68664],[-122.21713000000001,47.68669],[-122.21717000000001,47.686741],[-122.21721000000002,47.68678],[-122.21723000000001,47.68682],[-122.21725900000003,47.68687],[-122.21727000000001,47.686910000000005],[-122.21726800000002,47.686994],[-122.21726000000002,47.68709],[-122.21727000000001,47.687163],[-122.21728000000002,47.68721],[-122.21729000000002,47.687252],[-122.21731000000001,47.6873],[-122.21733,47.68735],[-122.21735000000001,47.687376],[-122.21734000000001,47.68742399999999],[-122.21734000000001,47.687472],[-122.21735300000002,47.687515000000005],[-122.21736000000001,47.687565],[-122.21738,47.687619999999995],[-122.21739000000001,47.687639],[-122.21740000000001,47.687659999999994],[-122.21741100000001,47.68767999999999],[-122.21742000000002,47.6877],[-122.21743000000001,47.68776],[-122.21745000000001,47.687799999999996],[-122.217447,47.687839999999994],[-122.21745000000001,47.68787999999999],[-122.21745000000001,47.6879],[-122.21746000000002,47.68793],[-122.21748000000001,47.68798099999999],[-122.21751000000002,47.688050000000004],[-122.21753000000001,47.688120000000005],[-122.21754000000001,47.688167],[-122.21757000000001,47.688250000000004],[-122.21759000000002,47.688331000000005],[-122.21764000000002,47.688430000000004],[-122.21767000000001,47.688469],[-122.21769000000002,47.688520000000004],[-122.21757000000001,47.688520000000004],[-122.21749000000001,47.688520000000004],[-122.217447,47.688520000000004],[-122.21747,47.6886],[-122.21749000000001,47.68865],[-122.217487,47.688686],[-122.21751000000002,47.688719000000006],[-122.21753700000001,47.688790000000004],[-122.21755000000002,47.68884],[-122.21757000000001,47.688901],[-122.21761000000002,47.689025],[-122.21762200000002,47.68911000000001],[-122.21763000000001,47.68918],[-122.21764000000002,47.68926],[-122.21764000000002,47.689370000000004],[-122.21763800000001,47.68944],[-122.21764000000002,47.689479999999996],[-122.21765000000002,47.689554],[-122.21765000000002,47.68962],[-122.21765000000002,47.68967],[-122.21765000000002,47.6897],[-122.21764000000002,47.68977],[-122.21764000000002,47.689851],[-122.21764000000002,47.689879999999995],[-122.21765000000002,47.689932000000006],[-122.21766000000002,47.69004],[-122.21766000000002,47.69008],[-122.21767000000001,47.69014000000001],[-122.21770000000001,47.69024],[-122.21771000000001,47.69033000000001],[-122.21772000000001,47.69039000000001],[-122.21773,47.690470000000005],[-122.21775000000001,47.690560000000005],[-122.21777,47.6906],[-122.21777,47.690650000000005],[-122.21777,47.690723000000006],[-122.21777,47.690763000000004],[-122.21776000000001,47.690805000000005],[-122.21777,47.69084],[-122.21779000000001,47.690891],[-122.217802,47.69095000000001],[-122.21781800000001,47.691],[-122.21784000000001,47.69104],[-122.21787,47.691096],[-122.21789500000001,47.691144],[-122.21789000000001,47.691202000000004],[-122.21788000000001,47.691230000000004],[-122.21788000000001,47.69126],[-122.21788000000001,47.69127],[-122.21788000000001,47.691323000000004],[-122.21789000000001,47.691365000000005],[-122.217892,47.691390000000006],[-122.21789000000001,47.691410000000005],[-122.21789500000001,47.691418000000006],[-122.21789000000001,47.691455000000005],[-122.21789000000001,47.69147],[-122.21789000000001,47.69151000000001],[-122.21787,47.69153000000001],[-122.21782000000002,47.691520000000004],[-122.21778,47.69153000000001],[-122.21774500000001,47.69153300000001],[-122.21771000000001,47.69155000000001],[-122.21771000000001,47.691570000000006],[-122.21770000000001,47.691582000000004],[-122.21769000000002,47.6916],[-122.21765000000002,47.69164],[-122.21760000000002,47.691666],[-122.217577,47.69167],[-122.21758000000001,47.691700000000004],[-122.21760000000002,47.69171000000001],[-122.21760000000002,47.691722000000006],[-122.21762000000003,47.691759000000005],[-122.21760000000002,47.69182],[-122.21760000000002,47.69186],[-122.21760000000002,47.691886],[-122.21759000000002,47.691900000000004],[-122.21759000000002,47.691930000000006],[-122.21757000000001,47.69200000000001],[-122.21757000000001,47.69205000000001],[-122.21756000000002,47.692080000000004],[-122.21755000000002,47.69214100000001],[-122.21754000000001,47.69218000000001],[-122.217532,47.692220000000006],[-122.21753000000001,47.692240000000005],[-122.21751000000002,47.69230000000001],[-122.21750000000002,47.69235000000001],[-122.21750000000002,47.69238500000001],[-122.21750000000002,47.69241200000001],[-122.21749000000001,47.692457000000005],[-122.21748000000001,47.692496000000006],[-122.21745000000001,47.69254000000001],[-122.21744000000001,47.692581000000004],[-122.21741000000002,47.692628000000006],[-122.21740700000001,47.692674000000004],[-122.21740000000001,47.69270900000001],[-122.21739400000001,47.69274000000001],[-122.21737,47.692800000000005],[-122.21735000000001,47.69285500000001],[-122.21733,47.69287500000001],[-122.21731000000001,47.692870000000006],[-122.21726000000002,47.692870000000006],[-122.21723000000001,47.692927000000005],[-122.21719000000002,47.69304],[-122.21728000000002,47.69304],[-122.21726200000002,47.69313000000001],[-122.21724000000002,47.693180000000005],[-122.21722300000003,47.693204],[-122.21721000000002,47.69323500000001],[-122.21718600000001,47.693290000000005],[-122.21718000000001,47.69333000000001],[-122.217172,47.693369000000004],[-122.21716900000003,47.693380000000005],[-122.21716000000002,47.693400000000004],[-122.21714000000001,47.69346],[-122.21712000000002,47.69351000000001],[-122.21711000000002,47.69353400000001],[-122.21710000000002,47.69355000000001],[-122.21707,47.6936],[-122.21706000000002,47.693658000000006],[-122.21705000000001,47.69370000000001],[-122.21704500000001,47.693720000000006],[-122.21703000000001,47.69379000000001],[-122.21700000000001,47.693841],[-122.216998,47.693850000000005],[-122.21698,47.69388],[-122.21697,47.69391000000001],[-122.21696000000001,47.693947],[-122.21694000000001,47.694004],[-122.21693,47.69402],[-122.21692000000002,47.69407],[-122.21692000000002,47.694098000000004],[-122.21690100000001,47.694120000000005],[-122.21689,47.694145000000006],[-122.21688,47.6942],[-122.21686000000001,47.69424],[-122.21685000000001,47.694250000000004],[-122.21685000000001,47.694266],[-122.21685000000001,47.69431900000001],[-122.21686000000001,47.69435000000001],[-122.21688,47.694390000000006],[-122.21689,47.69447],[-122.21692000000002,47.69453300000001],[-122.21692000000002,47.69456],[-122.21691000000001,47.694590000000005],[-122.21690000000001,47.694630000000004],[-122.21685000000001,47.694665],[-122.21683,47.69469],[-122.21681000000001,47.694700000000005],[-122.21678,47.69473000000001],[-122.21676000000001,47.69475200000001],[-122.21672000000001,47.6948],[-122.21669000000001,47.694829],[-122.21668500000001,47.69485],[-122.21667000000001,47.694883],[-122.21664300000002,47.69493000000001],[-122.21661000000002,47.694990000000004],[-122.21660000000001,47.69503000000001],[-122.21659000000001,47.69507000000001],[-122.21656000000002,47.69512000000001],[-122.21657,47.695147000000006],[-122.21656000000002,47.69520000000001],[-122.216548,47.695223000000006],[-122.21654000000001,47.69525000000001],[-122.21653,47.69529000000001],[-122.21650500000001,47.69533000000001],[-122.21647,47.69539500000001],[-122.21646000000001,47.69541600000001],[-122.21645000000001,47.695460000000004],[-122.21642600000001,47.695524000000006],[-122.21640000000001,47.69559000000001],[-122.21639,47.695600000000006],[-122.21638,47.695640000000004],[-122.21636000000001,47.69571000000001],[-122.21632300000002,47.695781000000004],[-122.21631000000001,47.69581000000001],[-122.2163,47.69586],[-122.21626000000002,47.69594000000001],[-122.21622000000002,47.69603000000001],[-122.21620000000001,47.69605500000001],[-122.21617,47.69610000000001],[-122.21611000000001,47.696214000000005],[-122.21608,47.696290000000005],[-122.21605000000001,47.69632000000001],[-122.21604,47.696343000000006],[-122.21603,47.696380000000005],[-122.21603,47.696450000000006],[-122.21602000000001,47.69650000000001],[-122.216008,47.69651800000001],[-122.21596000000001,47.696560000000005],[-122.21595,47.696580000000004],[-122.21592000000001,47.69662],[-122.21587,47.69671000000001],[-122.21584,47.6968],[-122.21581,47.696850000000005],[-122.21579,47.69689],[-122.21576999999999,47.696920000000006],[-122.21572,47.697041],[-122.21571,47.697085],[-122.2157,47.697120000000005],[-122.21567,47.6972],[-122.21565500000001,47.697230000000005],[-122.21564000000001,47.69729],[-122.21564000000001,47.69731000000001],[-122.21557,47.69736],[-122.21556000000001,47.697402000000004],[-122.21554,47.69746],[-122.21552000000001,47.697523000000004],[-122.21544,47.69767],[-122.21541,47.697720000000004],[-122.2154,47.697736000000006],[-122.21537,47.697739000000006],[-122.21521000000001,47.69773000000001],[-122.2151,47.697720000000004],[-122.21493000000001,47.69776],[-122.21483,47.6978],[-122.21469000000002,47.697874],[-122.21458000000001,47.69794],[-122.21452900000003,47.697970000000005],[-122.21448000000001,47.69801000000001],[-122.21443000000001,47.698040000000006],[-122.21434,47.698080000000004],[-122.21424000000002,47.69816000000001],[-122.21412800000002,47.698240000000006],[-122.21399100000001,47.69830700000001],[-122.21383,47.69833900000001],[-122.21372000000001,47.69834600000001],[-122.21365000000002,47.69832000000001],[-122.21364000000001,47.69832000000001],[-122.21360000000001,47.698280000000004],[-122.21359000000001,47.698240000000006],[-122.21361000000002,47.69819000000001],[-122.21365000000002,47.69816000000001],[-122.21369000000001,47.69815000000001],[-122.21374,47.69814000000001],[-122.21378,47.69815000000001],[-122.21379,47.69816000000001],[-122.21381600000001,47.69818000000001],[-122.21385000000001,47.69820000000001],[-122.21389,47.69819000000001],[-122.21395000000001,47.69816000000001],[-122.21405000000001,47.69812000000001],[-122.21409000000001,47.69807000000001],[-122.21412100000002,47.697990000000004],[-122.21412800000002,47.69793000000001],[-122.21413000000001,47.69791000000001],[-122.21407,47.69784],[-122.21400000000001,47.697810000000004],[-122.21393,47.697779000000004],[-122.21386000000001,47.697770000000006],[-122.2138,47.69782],[-122.21375,47.69788],[-122.213721,47.69795800000001],[-122.21366000000002,47.69802000000001],[-122.21360000000001,47.69802200000001],[-122.21353,47.69803200000001],[-122.21346000000001,47.69801000000001],[-122.21342000000001,47.69798],[-122.21342000000001,47.697956000000005],[-122.21346000000001,47.697917000000004],[-122.21355000000001,47.69788],[-122.21357,47.69784],[-122.21358000000001,47.697786],[-122.21361000000002,47.69768],[-122.21360000000001,47.697630000000004],[-122.21356000000002,47.697630000000004],[-122.21348,47.697655000000005],[-122.21342000000001,47.697720000000004],[-122.21334,47.697794],[-122.21325000000002,47.69785],[-122.21316000000002,47.697904],[-122.21311000000001,47.69792],[-122.21301500000001,47.697970000000005],[-122.21292000000001,47.69800600000001],[-122.21276999999999,47.698085000000006],[-122.21262000000002,47.69813600000001],[-122.21248,47.69819000000001],[-122.21238,47.69821000000001],[-122.21225000000001,47.698265000000006],[-122.21214,47.69831000000001],[-122.212,47.69838000000001],[-122.21186000000002,47.698460000000004],[-122.211746,47.69855400000001],[-122.21163000000001,47.698620000000005],[-122.21155000000002,47.69870000000001],[-122.21146000000002,47.698800000000006],[-122.21142000000002,47.698859000000006],[-122.21138,47.69893000000001],[-122.21128000000002,47.69911000000001],[-122.21125000000002,47.699166000000005],[-122.21118000000001,47.699270000000006],[-122.21110000000002,47.699377000000005],[-122.21100000000001,47.69951000000001],[-122.21091000000001,47.69965200000001],[-122.21078,47.69984],[-122.21068000000001,47.700010000000006],[-122.21063000000001,47.70008],[-122.21063000000001,47.70009],[-122.21059000000001,47.70019800000001],[-122.21055000000001,47.700258000000005],[-122.21052000000002,47.70031000000001],[-122.21050000000001,47.70037500000001],[-122.21047,47.700410000000005],[-122.21042000000001,47.70049],[-122.2104,47.70049],[-122.21041000000001,47.70088],[-122.21042000000001,47.701010000000004],[-122.21042000000001,47.70114],[-122.21043,47.7014],[-122.21043,47.701516000000005],[-122.21044,47.70166],[-122.21044,47.7018],[-122.21045000000001,47.70194],[-122.21050000000001,47.70205500000001],[-122.21058000000001,47.70217000000001],[-122.21060000000001,47.702197000000005],[-122.21064000000001,47.702239000000006],[-122.21069000000001,47.702290000000005],[-122.21074,47.70234000000001],[-122.21084,47.70243000000001],[-122.21088,47.70246],[-122.21096000000001,47.70251000000001],[-122.21107,47.70257000000001],[-122.21111000000002,47.70259000000001],[-122.21118000000001,47.702630000000006],[-122.21122000000003,47.70266],[-122.21126000000002,47.70268],[-122.21130000000001,47.70270500000001],[-122.21140000000001,47.70275600000001],[-122.21147,47.702780000000004],[-122.21151000000002,47.702801],[-122.21154000000001,47.70282],[-122.21166000000002,47.70286],[-122.21170000000001,47.70288],[-122.21178,47.70291000000001],[-122.21186000000002,47.70295000000001],[-122.21189000000001,47.702960000000004],[-122.21197000000001,47.703],[-122.21202000000001,47.703029],[-122.21204,47.703030000000005],[-122.2121,47.70306],[-122.21219,47.70309],[-122.21229000000001,47.70313000000001],[-122.21243,47.70318],[-122.21253,47.703230000000005],[-122.21267,47.703286],[-122.21282000000001,47.703340000000004],[-122.21296000000001,47.703390000000006],[-122.21307,47.70344],[-122.21313,47.70347],[-122.213175,47.70348],[-122.21322000000002,47.703494],[-122.21327000000001,47.70349],[-122.21331,47.70346],[-122.21336000000001,47.70342],[-122.21337,47.70341200000001],[-122.21339,47.7034],[-122.21342000000001,47.703390000000006],[-122.21347,47.70338],[-122.21353,47.703409],[-122.21360000000001,47.70344],[-122.21367000000001,47.70348],[-122.2137,47.703509000000004],[-122.21375,47.703520000000005],[-122.21379,47.70353000000001],[-122.21385000000001,47.703539000000006],[-122.21392000000002,47.703554000000004],[-122.21396000000001,47.703570000000006],[-122.21399000000001,47.703582000000004],[-122.21403000000001,47.70359500000001],[-122.21408000000001,47.703610000000005],[-122.21422000000003,47.70365],[-122.21431000000001,47.70368],[-122.21439400000001,47.703700000000005],[-122.21445000000001,47.70371900000001],[-122.21451000000002,47.70374],[-122.21459000000002,47.703770000000006],[-122.21468000000002,47.7038],[-122.21475000000001,47.70382],[-122.21482000000002,47.70383],[-122.214972,47.70387],[-122.21507,47.703897],[-122.21513,47.70391000000001],[-122.21522000000002,47.70393000000001],[-122.21531,47.703949],[-122.21546000000001,47.703970000000005],[-122.21558,47.703990000000005],[-122.2157,47.704010000000004],[-122.21582000000001,47.704026],[-122.21587,47.70403],[-122.21593,47.70404],[-122.21598,47.70404],[-122.216046,47.70405],[-122.21608,47.704053],[-122.21610000000001,47.704055000000004],[-122.21613,47.704057],[-122.21615000000001,47.704059],[-122.21618000000001,47.70406],[-122.21621000000002,47.70406],[-122.21624000000001,47.70406],[-122.21627000000001,47.704062],[-122.2163,47.704062],[-122.21633,47.70406],[-122.21636000000001,47.70406],[-122.21639,47.704059],[-122.21643,47.704057],[-122.21646000000001,47.704054],[-122.21649000000001,47.70405],[-122.216508,47.70405],[-122.21654000000001,47.70405],[-122.21656000000002,47.70404],[-122.21659000000001,47.70404],[-122.21664700000001,47.704024],[-122.2167,47.704010000000004],[-122.21675,47.703990000000005],[-122.21676000000001,47.703720000000004],[-122.21697,47.70379500000001],[-122.217088,47.70384],[-122.21712000000002,47.703851],[-122.21718000000001,47.70387],[-122.21726000000002,47.703900000000004],[-122.21732000000002,47.703924],[-122.21735300000002,47.703934000000004],[-122.21737,47.70394],[-122.21741000000002,47.703950000000006],[-122.21744000000001,47.70396],[-122.21745000000001,47.703969],[-122.21746000000002,47.70398],[-122.21747,47.703990000000005],[-122.21748000000001,47.704],[-122.21749000000001,47.704014],[-122.21749000000001,47.70402],[-122.21750100000001,47.70403],[-122.21752000000002,47.70405],[-122.21754000000001,47.704063],[-122.21757000000001,47.70407],[-122.21760000000002,47.70408],[-122.21767000000001,47.70408],[-122.21768700000001,47.70408],[-122.21772000000001,47.70407],[-122.21774,47.704065],[-122.21775000000001,47.704055000000004],[-122.217757,47.70405],[-122.21777,47.70404],[-122.21778,47.70403],[-122.21780000000001,47.704018000000005],[-122.21781800000001,47.704010000000004],[-122.21784000000001,47.704010000000004],[-122.21786000000002,47.704010000000004],[-122.21787,47.704011],[-122.21790000000001,47.704017],[-122.21791000000002,47.70402],[-122.21793000000001,47.70403],[-122.21795000000002,47.70404],[-122.21799000000001,47.70405],[-122.21801,47.704056],[-122.21802000000001,47.704062],[-122.21808,47.70408],[-122.21812000000001,47.704096],[-122.21814,47.704105000000006],[-122.218171,47.704114000000004],[-122.21822000000002,47.704130000000006],[-122.21826100000001,47.704141],[-122.21831,47.704156000000005],[-122.21833,47.70416],[-122.21835,47.704170000000005],[-122.21838,47.704170000000005],[-122.2184,47.70418],[-122.21845,47.704192000000006],[-122.2185,47.7042],[-122.21865000000001,47.704243],[-122.21871,47.70426],[-122.21876999999999,47.704276],[-122.2188,47.704283],[-122.21885,47.70429],[-122.21891000000001,47.7043],[-122.21897,47.70431000000001],[-122.21900000000001,47.70431000000001],[-122.21902000000001,47.70431500000001],[-122.21904,47.704316000000006],[-122.21905000000001,47.70431000000001],[-122.21908,47.70431000000001],[-122.21914000000001,47.704298],[-122.21916000000002,47.70429],[-122.21918000000001,47.70429],[-122.219198,47.704283999999994],[-122.21921000000002,47.704279],[-122.21924600000001,47.704266999999994],[-122.21926000000002,47.70426],[-122.21942000000001,47.7042],[-122.21958000000001,47.704114000000004],[-122.21986000000001,47.70403],[-122.22014,47.70396],[-122.22015,47.703976000000004],[-122.220325,47.70393800000001],[-122.22043,47.70391000000001],[-122.22054,47.703838000000005],[-122.22058,47.703812000000006],[-122.22075,47.703700000000005],[-122.22089,47.7036],[-122.22111000000001,47.703449],[-122.22129000000001,47.70332200000001],[-122.221483,47.703190000000006],[-122.22176999999999,47.703],[-122.22197,47.702870000000004],[-122.22218,47.702726000000006],[-122.22236999999998,47.702600000000004],[-122.22267,47.702400000000004],[-122.22282999999999,47.702290000000005],[-122.22305,47.70215000000001],[-122.22322000000001,47.702034000000005],[-122.22339,47.70192],[-122.22403,47.701492],[-122.22411000000001,47.70145],[-122.22428000000001,47.701350000000005],[-122.22439,47.70129],[-122.224775,47.701071],[-122.22496000000001,47.700970000000005],[-122.22521,47.70082],[-122.22547999999999,47.70067],[-122.2256,47.7006],[-122.22576,47.70051600000001],[-122.22583999999999,47.700474],[-122.225809,47.700410000000005],[-122.22577999999999,47.70037000000001],[-122.22572999999998,47.700327],[-122.22567,47.70031000000001],[-122.22564,47.700300000000006],[-122.22562500000001,47.70028],[-122.22559,47.700254],[-122.22558,47.700234],[-122.2256,47.700210000000006],[-122.2255,47.70011300000001],[-122.22552,47.70009],[-122.22556999999999,47.700038000000006],[-122.22566,47.699980000000004],[-122.22572,47.69995300000001],[-122.22585,47.699830000000006],[-122.22581,47.699670000000005],[-122.22582,47.69957000000001],[-122.22583999999999,47.69942],[-122.22583999999999,47.69935600000001],[-122.22583999999999,47.69923000000001],[-122.22583999999999,47.69913000000001],[-122.22588599999999,47.699073000000006],[-122.22592999999999,47.69905000000001],[-122.22597599999999,47.699024],[-122.22606,47.69897000000001],[-122.22615,47.69892000000001],[-122.22628,47.69888],[-122.22636,47.698856000000006],[-122.22651,47.69875100000001],[-122.22654,47.69876000000001],[-122.226608,47.698690000000006],[-122.22662000000001,47.698690000000006],[-122.22666000000001,47.69868],[-122.22668,47.69865000000001],[-122.22671,47.69863000000001],[-122.22677999999999,47.69859400000001],[-122.22682,47.69858000000001],[-122.22685,47.69857000000001],[-122.22686999999999,47.69856600000001],[-122.2269,47.69855000000001],[-122.22694,47.69851300000001],[-122.22697,47.698460000000004],[-122.22699,47.698440000000005],[-122.22704,47.69841000000001],[-122.22707,47.69839500000001],[-122.22708,47.69837000000001],[-122.22712000000001,47.69834900000001],[-122.22724600000001,47.69830500000001],[-122.22715000000001,47.69820000000001],[-122.22703,47.69825000000001],[-122.22699,47.698240000000006],[-122.22695,47.698221000000004],[-122.22692,47.69820500000001],[-122.22696,47.69818800000001],[-122.226974,47.69816000000001],[-122.22695,47.69815000000001],[-122.22696,47.69813000000001],[-122.22697,47.698110000000014],[-122.22699,47.69810000000001],[-122.22703,47.69809000000001],[-122.22707,47.698080000000004],[-122.22708,47.698060000000005],[-122.22714,47.69803500000001],[-122.22718,47.69801000000001],[-122.22719000000001,47.69800000000001],[-122.22721000000001,47.697990000000004],[-122.22723,47.697962000000004],[-122.22723,47.69794],[-122.22726000000002,47.697905000000006],[-122.227327,47.69789],[-122.22737599999999,47.69788],[-122.22742000000001,47.697873],[-122.22743,47.69786],[-122.22739,47.69785],[-122.22737,47.69785],[-122.22738,47.69784],[-122.22744,47.69784],[-122.22748,47.69783],[-122.22752000000001,47.697817],[-122.22758,47.697790000000005],[-122.22760000000001,47.697770000000006],[-122.22762000000002,47.69776],[-122.22771,47.697720000000004],[-122.22774,47.69769],[-122.2278,47.69766],[-122.22785,47.697630000000004],[-122.22788,47.69762],[-122.22789,47.6976],[-122.22797,47.69755500000001],[-122.22801,47.69753000000001],[-122.22806,47.69751000000001],[-122.22813,47.697500000000005],[-122.22818,47.697493],[-122.22823,47.697472000000005],[-122.22825,47.69746],[-122.22832,47.69746],[-122.22837999999999,47.697435000000006],[-122.22838999999999,47.697410000000005],[-122.2284,47.697390000000006],[-122.22842999999999,47.69738],[-122.22845,47.6974],[-122.22852,47.69738],[-122.228546,47.69735000000001],[-122.22852999999999,47.697320000000005],[-122.22861,47.69733000000001],[-122.22878999999999,47.69733900000001],[-122.2289,47.697340000000004],[-122.22907,47.697341],[-122.22916000000001,47.697341],[-122.22927,47.69733000000001],[-122.2294,47.697320000000005],[-122.22952000000001,47.697306000000005],[-122.22963,47.69729],[-122.22977999999999,47.69729],[-122.22984,47.697299],[-122.22994,47.69730800000001],[-122.23005,47.69729],[-122.23009,47.69729],[-122.23016000000001,47.69724],[-122.230241,47.69729],[-122.23028000000001,47.69728],[-122.2303,47.69728],[-122.23032,47.697279],[-122.23032,47.697320000000005],[-122.2304,47.69733000000001],[-122.23045,47.69733000000001],[-122.23049,47.69733000000001],[-122.23064000000001,47.69733000000001],[-122.23076999999999,47.697347],[-122.230837,47.697347],[-122.23097,47.69736],[-122.23111000000002,47.69737000000001],[-122.23116000000002,47.69737000000001],[-122.23121000000002,47.69736],[-122.23126000000002,47.697344],[-122.23131000000001,47.69735000000001],[-122.2313,47.697385000000004],[-122.2313,47.6974],[-122.23139,47.697410000000005],[-122.23146000000001,47.697427],[-122.23155000000001,47.697441],[-122.23160000000001,47.69746],[-122.23167000000001,47.697474],[-122.23189,47.697517000000005],[-122.23208,47.69755000000001],[-122.23224,47.697590000000005],[-122.23235,47.6976],[-122.232507,47.697651],[-122.23272999999999,47.69768],[-122.23288,47.69771000000001],[-122.23302000000001,47.697734000000004],[-122.23307,47.69776],[-122.23306000000001,47.69778],[-122.23315000000001,47.6978],[-122.2333,47.69784],[-122.233349,47.69786],[-122.23342000000001,47.69789],[-122.23351000000001,47.697903000000004],[-122.23363,47.697947],[-122.2337,47.697970000000005],[-122.233796,47.697989],[-122.2339,47.69803100000001],[-122.233922,47.69803200000001],[-122.23410000000001,47.69808200000001],[-122.23442000000001,47.69819000000001],[-122.23456700000001,47.69823000000001],[-122.2347,47.698257000000005],[-122.23479,47.698280000000004],[-122.23487,47.698164000000006],[-122.23515,47.698260000000005],[-122.23517,47.69827000000001],[-122.2353,47.69831000000001],[-122.235458,47.69836000000001],[-122.2354,47.698440000000005],[-122.23544,47.69845000000001],[-122.23548,47.69853000000001],[-122.23559,47.69856000000001],[-122.23562000000001,47.69856600000001],[-122.23563,47.69855300000001],[-122.23565,47.69855400000001],[-122.23563,47.69857000000001],[-122.23569,47.69859000000001],[-122.23594,47.69857000000001],[-122.236,47.698600000000006],[-122.23609,47.69863000000001],[-122.236188,47.698660000000004],[-122.23635900000001,47.69871000000001],[-122.23652000000001,47.69874000000001],[-122.23661000000001,47.69876800000001],[-122.2367,47.698814000000006],[-122.23675,47.698840000000004],[-122.236805,47.698856000000006],[-122.23688,47.69887000000001],[-122.23696000000001,47.69888],[-122.23709000000001,47.69892000000001],[-122.23727000000001,47.69895800000001],[-122.23747,47.698980000000006],[-122.23766000000002,47.69899100000001],[-122.23771,47.699000000000005],[-122.23781000000001,47.69903000000001],[-122.23789000000001,47.69905000000001],[-122.23794000000001,47.69906],[-122.23805,47.69906],[-122.23811,47.699070000000006],[-122.238189,47.69907800000001],[-122.23832999999999,47.69911000000001],[-122.23846,47.69914000000001],[-122.23853,47.69915000000001],[-122.23866000000001,47.69916500000001],[-122.238729,47.69917500000001],[-122.23882,47.699200000000005],[-122.23894,47.69924],[-122.23901000000001,47.699251000000004],[-122.23909,47.69925200000001],[-122.23917,47.699270000000006],[-122.23921000000001,47.69928],[-122.239276,47.69931000000001],[-122.23933,47.69932000000001],[-122.23939,47.69931000000001],[-122.23948,47.69932000000001],[-122.23952000000001,47.69932000000001],[-122.23956000000001,47.69934000000001],[-122.239585,47.69935600000001],[-122.23961000000001,47.69937000000001],[-122.23964400000001,47.69938200000001],[-122.23971,47.69939000000001],[-122.23982000000001,47.69939000000001],[-122.23986500000001,47.69941000000001],[-122.23985,47.699467],[-122.24004000000001,47.69950000000001],[-122.24033,47.699560000000005],[-122.24032000000001,47.699580000000005],[-122.24069000000001,47.699630000000006],[-122.24098000000001,47.699689],[-122.240982,47.69970000000001],[-122.24106000000002,47.699720000000006],[-122.24128000000002,47.699780000000004],[-122.24157000000001,47.699869],[-122.24185000000001,47.69997000000001],[-122.24232,47.700190000000006],[-122.2427,47.700303000000005],[-122.24297,47.70035000000001],[-122.24326000000002,47.70050500000001],[-122.24356000000002,47.70066],[-122.24369000000002,47.70062],[-122.24374,47.70067],[-122.24379,47.70071000000001],[-122.24387,47.70074],[-122.24391000000001,47.700770000000006],[-122.24393,47.7008],[-122.24394000000001,47.70085],[-122.24398000000001,47.700891],[-122.24402000000002,47.70092],[-122.24409000000001,47.700948000000004],[-122.24416000000002,47.70096],[-122.24421000000002,47.70098],[-122.244335,47.70106],[-122.24446000000002,47.70112],[-122.24452000000002,47.70116],[-122.24460000000002,47.70121],[-122.24465000000002,47.70128],[-122.24469000000002,47.701329],[-122.24476000000001,47.7014],[-122.24482000000002,47.70145],[-122.24486000000002,47.70149],[-122.24486000000002,47.70154],[-122.24486000000002,47.70169],[-122.24485000000001,47.701761],[-122.24480700000001,47.70184],[-122.24474000000001,47.70196],[-122.24468000000002,47.70212000000001],[-122.24464000000002,47.70228],[-122.24464000000002,47.70239000000001],[-122.24467000000001,47.702459000000005],[-122.24472000000002,47.70253000000001],[-122.24479000000001,47.70259600000001],[-122.24470000000001,47.70263800000001],[-122.24478,47.702830000000006],[-122.24487,47.70302],[-122.24494000000001,47.703199000000005],[-122.24499100000001,47.703300000000006],[-122.24504,47.7034],[-122.24514,47.70353000000001],[-122.24522000000002,47.70366],[-122.24517,47.703677],[-122.24524000000001,47.70383],[-122.24537,47.70393500000001],[-122.24542000000001,47.70398],[-122.2455,47.704053],[-122.245558,47.70411000000001],[-122.24561000000001,47.70418],[-122.24575,47.70438],[-122.24589,47.70454],[-122.24600000000001,47.704663999999994],[-122.24605000000001,47.704705000000004],[-122.24611000000002,47.70479],[-122.24615000000001,47.70487],[-122.24618000000001,47.704893],[-122.24617,47.704917],[-122.24627100000001,47.70499],[-122.24636000000001,47.705059000000006],[-122.24637,47.70510000000001],[-122.24640000000001,47.70514300000001],[-122.24645000000001,47.70517000000001],[-122.24653,47.705207],[-122.24679,47.70535000000001],[-122.24683,47.705380000000005],[-122.24700000000001,47.70545200000001],[-122.24715000000002,47.70552000000001],[-122.24723000000002,47.70559000000001],[-122.24741000000002,47.70559000000001],[-122.24754000000001,47.705740000000006],[-122.24757000000001,47.705780000000004],[-122.24774000000001,47.705886],[-122.24793000000001,47.706019000000005],[-122.24810000000001,47.706120000000006],[-122.24829900000002,47.706278000000005],[-122.248376,47.706320000000005],[-122.24847,47.706430000000005],[-122.24868000000001,47.706615000000006],[-122.24876,47.706714000000005],[-122.24878,47.706790000000005],[-122.24885,47.70682],[-122.24889,47.706810000000004],[-122.2489,47.706837],[-122.24888,47.70684],[-122.24884,47.706841],[-122.24884,47.706889],[-122.24889,47.706900000000005],[-122.24899,47.707099],[-122.24901000000001,47.707150000000006],[-122.24904000000001,47.70717500000001],[-122.24914000000001,47.70747],[-122.24918000000001,47.70764],[-122.24919000000001,47.707659],[-122.24921000000002,47.70769],[-122.24927000000001,47.70774],[-122.249331,47.70776],[-122.24948,47.70781],[-122.24962000000002,47.70786],[-122.24980000000001,47.7079],[-122.24993,47.707963],[-122.25006,47.70803000000001],[-122.25021000000001,47.708041],[-122.25035,47.70803900000001],[-122.25059,47.708020000000005],[-122.25079,47.70805000000001],[-122.25092000000001,47.708144000000004],[-122.25101000000001,47.708290000000005],[-122.25183,47.70933000000001],[-122.25196700000001,47.70961500000001],[-122.25216999999999,47.709998000000006],[-122.25261,47.710223],[-122.25304,47.71051200000001],[-122.25305,47.710530000000006],[-122.25312000000001,47.71063],[-122.253151,47.71067],[-122.25327,47.710770000000004],[-122.25336999999999,47.71082],[-122.25339,47.71085],[-122.2534,47.710879999999996],[-122.25348,47.71094],[-122.25347,47.71096],[-122.25347,47.710986],[-122.25353,47.71104],[-122.25371,47.711216],[-122.2537,47.71124],[-122.25366000000001,47.711279999999995],[-122.25367,47.71129],[-122.25371,47.71132],[-122.25376,47.711330000000004],[-122.25382,47.711330000000004],[-122.25386999999999,47.71136],[-122.25396,47.711420999999994],[-122.25392000000001,47.711439999999996],[-122.25391,47.71146399999999],[-122.25394,47.711479999999995],[-122.25404,47.711510000000004],[-122.25406000000001,47.711518000000005],[-122.254084,47.71155],[-122.25419000000001,47.711479999999995],[-122.25427,47.71158],[-122.25448,47.711723],[-122.2547,47.711839999999995],[-122.254682,47.711858],[-122.25475,47.711853],[-122.25477,47.711859999999994],[-122.2548,47.71186899999999],[-122.25484,47.711879999999994],[-122.25486000000001,47.71189],[-122.25491000000001,47.71194],[-122.25494,47.71198],[-122.25496000000001,47.71201000000001],[-122.25496000000001,47.712029],[-122.25502,47.71210000000001],[-122.255126,47.712202000000005],[-122.25519,47.71228],[-122.25524,47.71235000000001],[-122.25529999999999,47.712381],[-122.25532999999999,47.712430000000005],[-122.25536,47.71248],[-122.25556999999999,47.71266],[-122.25574999999999,47.71284],[-122.25599,47.713026],[-122.25602,47.71309],[-122.25607,47.71313000000001],[-122.25608,47.713170000000005],[-122.25612000000001,47.71322],[-122.25617,47.713288],[-122.25619,47.71331000000001],[-122.25636999999999,47.71348],[-122.25641,47.713530000000006],[-122.25645,47.71356],[-122.25667,47.71368699999999],[-122.257,47.71386],[-122.25707,47.71389],[-122.25712000000001,47.713910000000006],[-122.25717900000001,47.71396],[-122.25719000000001,47.71398],[-122.25719000000001,47.714],[-122.25726000000002,47.71404],[-122.25733,47.714079999999996],[-122.25738,47.7141],[-122.25741900000001,47.714130000000004],[-122.25753,47.71428099999999],[-122.25754,47.7143],[-122.25763,47.71432],[-122.25766000000002,47.71435],[-122.25768000000001,47.7144],[-122.25775,47.714459999999995],[-122.25777,47.714479999999995],[-122.25784,47.71452],[-122.25790300000001,47.71458],[-122.25791000000001,47.71462],[-122.25797,47.714659999999995],[-122.25805,47.714729],[-122.25804,47.714802],[-122.25802999999999,47.71487],[-122.25813,47.71497],[-122.25822000000001,47.71496],[-122.258282,47.715061],[-122.25821,47.715090000000004],[-122.258192,47.715140000000005],[-122.2582,47.715147],[-122.25823,47.715180000000004],[-122.25825,47.715270000000004],[-122.25822000000001,47.715291],[-122.25822000000001,47.715300000000006],[-122.25824,47.715329000000004],[-122.25828,47.71537000000001],[-122.25832,47.7154],[-122.25835,47.7154],[-122.25837999999999,47.715404],[-122.2584,47.715430000000005],[-122.2584,47.71546],[-122.2584,47.71548],[-122.25836999999999,47.715506000000005],[-122.25836,47.71553000000001],[-122.258359,47.7156],[-122.25836,47.71567],[-122.25836999999999,47.715700000000005],[-122.25836999999999,47.71571000000001],[-122.2584,47.71575000000001],[-122.25842999999999,47.71582],[-122.25838999999999,47.71582],[-122.25841,47.71585],[-122.25841,47.71586],[-122.2585,47.715852000000005],[-122.258543,47.71591000000001],[-122.25856,47.71594],[-122.25856999999999,47.715979000000004],[-122.25856,47.71603],[-122.25856999999999,47.71605],[-122.25858,47.71608],[-122.25859,47.716100000000004],[-122.2586,47.716108000000006],[-122.25862000000001,47.716120000000004],[-122.258635,47.71613000000001],[-122.25865,47.71613000000001],[-122.25867,47.716141],[-122.25869,47.716147],[-122.25872,47.71615500000001],[-122.25874999999999,47.716170000000005],[-122.25876999999998,47.716189],[-122.25878999999999,47.716210000000004],[-122.25882,47.716282],[-122.25881,47.71634],[-122.258815,47.71638],[-122.25881,47.716390000000004],[-122.2588,47.71649],[-122.25877999999999,47.71651000000001],[-122.25876999999998,47.716530000000006],[-122.25873999999999,47.716550000000005],[-122.25871,47.71658],[-122.25869,47.7166],[-122.25866,47.71661],[-122.25864,47.71661],[-122.25863,47.71662],[-122.25862000000001,47.716629],[-122.25862000000001,47.71664],[-122.25863,47.71665],[-122.25863,47.71667],[-122.25863,47.71668],[-122.25864,47.71669],[-122.25865,47.716705000000005],[-122.25867,47.716707],[-122.25871,47.716705000000005],[-122.25872999999999,47.716746],[-122.25873999999999,47.716770000000004],[-122.25873999999999,47.716802],[-122.25876999999998,47.71697],[-122.25876,47.71699],[-122.25876999999998,47.71703],[-122.25876999999998,47.71705],[-122.25877999999999,47.71705],[-122.25877999999999,47.71707],[-122.25878999999999,47.717099],[-122.25878999999999,47.71711200000001],[-122.25878999999999,47.717147],[-122.2588,47.71721],[-122.25846999999999,47.71725],[-122.25849,47.71738],[-122.25858,47.71737],[-122.25856,47.717405],[-122.25856,47.71742],[-122.25856,47.717439999999996],[-122.25856999999999,47.71747],[-122.25859,47.717530000000004],[-122.25861,47.717577],[-122.25861,47.717588],[-122.25861,47.71761],[-122.25863,47.717639999999996],[-122.25864,47.717659999999995],[-122.25867,47.717679999999994],[-122.25868,47.71769],[-122.25869,47.717703],[-122.25869,47.717712000000006],[-122.25869999999999,47.71772],[-122.25869999999999,47.71774],[-122.25871,47.717763],[-122.25872,47.71778],[-122.25872999999999,47.717793],[-122.25873999999999,47.717815],[-122.25872999999999,47.717833],[-122.25872,47.71785],[-122.25872,47.71787],[-122.25876999999998,47.717859999999995],[-122.25876,47.71787],[-122.25876,47.71788599999999],[-122.25876999999998,47.7179],[-122.25878999999999,47.7179],[-122.258811,47.71791],[-122.25882999999999,47.71792],[-122.25882999999999,47.717929],[-122.25883999999999,47.71794],[-122.25883999999999,47.71796],[-122.25883999999999,47.71797],[-122.25883999999999,47.71799],[-122.25883999999999,47.718],[-122.25882999999999,47.71801000000001],[-122.25882999999999,47.718030000000006],[-122.25882999999999,47.718043],[-122.25883999999999,47.71806],[-122.25883999999999,47.718070000000004],[-122.25885,47.71808],[-122.25886,47.718097],[-122.25886999999999,47.71811000000001],[-122.25889,47.718179000000006],[-122.25889,47.71819000000001],[-122.25889,47.718210000000006],[-122.25889,47.718221],[-122.25889,47.71823500000001],[-122.25889,47.718250000000005],[-122.2589,47.718270000000004],[-122.25891,47.71828],[-122.25894799999999,47.71833000000001],[-122.25895,47.718340000000005],[-122.25894,47.71835000000001],[-122.25892999999999,47.718368000000005],[-122.25892999999999,47.71838],[-122.25894,47.718396000000006],[-122.25895,47.718413000000005],[-122.25896999999999,47.718444],[-122.25898,47.71848],[-122.25901,47.718491],[-122.25902,47.71853000000001],[-122.25905,47.718574000000004],[-122.25907,47.7186],[-122.25908,47.718610000000005],[-122.25908,47.71862],[-122.25909,47.718630000000005],[-122.259091,47.71864],[-122.25909,47.71866],[-122.25911,47.718671],[-122.25913,47.71868],[-122.25914,47.71869],[-122.25915,47.718699],[-122.259161,47.71871000000001],[-122.25917,47.718720000000005],[-122.25918,47.71875000000001],[-122.25918,47.71876],[-122.25919,47.718770000000006],[-122.25919,47.718790000000006],[-122.25921000000001,47.71885],[-122.2591,47.71886],[-122.25911,47.71891000000001],[-122.25912000000001,47.71894],[-122.259132,47.71907],[-122.25913,47.71911000000001],[-122.259136,47.719141],[-122.259128,47.719228],[-122.25912000000001,47.71924],[-122.25909,47.71926],[-122.25907,47.71927],[-122.25907099999999,47.71928],[-122.25907,47.719307],[-122.259074,47.71933000000001],[-122.25912000000001,47.719404],[-122.25915,47.719448],[-122.259181,47.719485],[-122.25921000000001,47.71952],[-122.25922000000001,47.71953800000001],[-122.25925000000001,47.719550000000005],[-122.25927,47.719550000000005],[-122.259296,47.719569],[-122.25931,47.719590000000004],[-122.25935,47.71963],[-122.2594,47.71967],[-122.25944,47.719711000000004],[-122.25946,47.719730000000006],[-122.25949,47.71975200000001],[-122.259566,47.71979],[-122.25969,47.719860999999995],[-122.25976,47.71989],[-122.2599,47.71996],[-122.26002000000001,47.720000000000006],[-122.26011000000001,47.720027],[-122.26021000000001,47.72007000000001],[-122.26028000000001,47.72012900000001],[-122.2603,47.720183000000006],[-122.26034,47.720223000000004],[-122.2604,47.72026],[-122.26046400000001,47.72031000000001],[-122.26049,47.72034000000001],[-122.26054,47.72034000000001],[-122.26059000000001,47.72039200000001],[-122.26061000000001,47.720420000000004],[-122.26064000000001,47.720470000000006],[-122.26071,47.72064],[-122.26075,47.72073000000001],[-122.26077,47.72082],[-122.26086000000001,47.72093000000001],[-122.26092000000001,47.721000000000004],[-122.26099,47.72124],[-122.26101000000001,47.721340000000005],[-122.26095000000001,47.72149],[-122.26095000000001,47.721520000000005],[-122.26095000000001,47.72155600000001],[-122.26104000000001,47.721747],[-122.26117,47.72189],[-122.26116000000002,47.721920000000004],[-122.26117,47.72202000000001],[-122.26120000000002,47.72207900000001],[-122.26126000000002,47.72217300000001],[-122.26127000000001,47.722240000000006],[-122.26131000000001,47.72239300000001],[-122.26140400000001,47.72254000000001],[-122.261548,47.722681],[-122.26168000000001,47.722681],[-122.26165000000002,47.722820000000006],[-122.26159400000002,47.72285000000001],[-122.26150400000002,47.72289000000001],[-122.26150000000001,47.723020000000005],[-122.26149000000001,47.723060000000004],[-122.26145000000001,47.72319000000001],[-122.26146000000001,47.72327000000001],[-122.26141300000002,47.72327000000001],[-122.26135000000001,47.72330200000001],[-122.26129000000002,47.72336000000001],[-122.26116000000002,47.723490000000005],[-122.26102000000002,47.72366],[-122.26094,47.72377000000001],[-122.26088,47.723864],[-122.260798,47.72397000000001],[-122.26074,47.724213000000006],[-122.26060500000001,47.724472000000006],[-122.26066000000002,47.724472000000006],[-122.26061800000001,47.724590000000006],[-122.26061000000001,47.724740000000004],[-122.26060000000001,47.724740000000004],[-122.26060000000001,47.72481200000001],[-122.26059000000001,47.724899],[-122.26061000000001,47.72496],[-122.26078,47.725130000000014],[-122.26095000000001,47.725310000000015],[-122.26101000000001,47.725310000000015],[-122.26101000000001,47.72537000000001],[-122.261188,47.725510000000014],[-122.26128000000001,47.72558000000001],[-122.26136000000001,47.72564200000001],[-122.26140000000001,47.72567000000001],[-122.26153000000001,47.72567000000001],[-122.26154500000001,47.72569000000001],[-122.2617,47.72587000000001],[-122.26172000000001,47.725880000000004],[-122.26175,47.72592000000001],[-122.261774,47.72595000000001],[-122.26182000000001,47.72595000000001],[-122.26189000000001,47.726040000000005],[-122.26196000000002,47.72615000000001],[-122.26196700000001,47.72617000000001]]]},"name":"Kirkland"},{"boundary":{"type":"Polygon","coordinates":[[[-122.09951000000001,47.593025000000004],[-122.099381,47.59667],[-122.09859,47.599443],[-122.09653,47.60231000000001],[-122.09608,47.602851],[-122.09272999999999,47.60664],[-122.09106000000001,47.608340000000005],[-122.09078,47.60864],[-122.08899,47.610459999999996],[-122.08824,47.61122399999999],[-122.08197,47.61747999999999],[-122.08096,47.61842],[-122.07972999999998,47.621057],[-122.07896,47.62471000000001],[-122.07922,47.62753000000001],[-122.07966,47.630539000000006],[-122.07974999999999,47.631104],[-122.08068,47.63371000000001],[-122.08267,47.635878000000005],[-122.08562,47.63789],[-122.08798,47.639506000000004],[-122.091344,47.64267],[-122.09349,47.64511000000001],[-122.09588,47.64767],[-122.09633199999999,47.64867],[-122.09687,47.649874],[-122.09725000000002,47.650710000000004],[-122.09751000000001,47.651889999999995],[-122.09404,47.652981],[-122.09319,47.65324699999999],[-122.0931,47.65327],[-122.09203,47.65361],[-122.092,47.65362],[-122.09187,47.653659999999995],[-122.09160000000001,47.65375],[-122.09139,47.653816],[-122.09109000000001,47.65391],[-122.091147,47.653986999999994],[-122.09123000000001,47.654114],[-122.09104,47.65417],[-122.09087,47.65424599999999],[-122.09075,47.654319],[-122.0907,47.654363999999994],[-122.09061000000001,47.65447999999999],[-122.09046000000001,47.65472],[-122.09022000000002,47.65514],[-122.08991,47.65564],[-122.08958,47.656079999999996],[-122.08936,47.656531],[-122.08925,47.656672],[-122.08912000000001,47.65683],[-122.08902,47.656917],[-122.08891,47.656977],[-122.08873999999999,47.657058],[-122.08847999999999,47.657199999999996],[-122.08816,47.657489999999996],[-122.08812,47.65748099999999],[-122.08755000000001,47.65740699999999],[-122.08725000000001,47.65737],[-122.08664,47.65727699999999],[-122.08592,47.65714],[-122.08459,47.65685],[-122.08469000000001,47.65647],[-122.08471,47.656345],[-122.0844,47.65637],[-122.08434,47.65638],[-122.08426000000001,47.65638],[-122.08418,47.656368],[-122.08403,47.656330000000004],[-122.08389,47.656279999999995],[-122.08382,47.65624],[-122.08365,47.65612],[-122.082319,47.6549],[-122.0819,47.65447999999999],[-122.08160000000001,47.654219999999995],[-122.08063,47.653496],[-122.08024,47.653259999999996],[-122.08021000000001,47.653233],[-122.08009,47.65318],[-122.07992999999999,47.653119000000004],[-122.07977999999999,47.653079],[-122.07956,47.653033],[-122.07941,47.653016],[-122.07928,47.65301],[-122.07883999999999,47.653],[-122.078859,47.649381],[-122.07746,47.649370000000005],[-122.07441,47.64934],[-122.07135,47.649330000000006],[-122.06737,47.64932],[-122.066596,47.64932],[-122.06648,47.64932],[-122.06613,47.64932],[-122.06621000000001,47.64942],[-122.06622000000002,47.649474],[-122.06633,47.64956],[-122.066411,47.64958],[-122.06644,47.64962],[-122.06646500000001,47.64967],[-122.06644,47.64973500000001],[-122.06649,47.64985],[-122.06653,47.649879999999996],[-122.06642000000001,47.649910000000006],[-122.06623,47.64997],[-122.0663,47.650130000000004],[-122.06636,47.650256],[-122.06636999999999,47.650279999999995],[-122.06645,47.650478],[-122.06622000000002,47.650478],[-122.06620000000001,47.650596],[-122.06616000000001,47.65058],[-122.06609,47.65045],[-122.066,47.65032],[-122.0658,47.650279999999995],[-122.06566000000001,47.65029],[-122.06532,47.650310000000005],[-122.06515,47.650549],[-122.06511,47.65057],[-122.06489,47.65072],[-122.06474,47.65072],[-122.06461000000002,47.650635],[-122.06439900000001,47.6502],[-122.06423000000001,47.649957],[-122.06426000000002,47.64979],[-122.06431300000001,47.64966],[-122.06432000000001,47.64951000000001],[-122.06450000000001,47.64932],[-122.05582,47.649299],[-122.05577999999998,47.649170000000005],[-122.05574999999999,47.649077],[-122.05576999999998,47.6486],[-122.05577599999998,47.648557000000004],[-122.05604,47.64691200000001],[-122.05612,47.64646],[-122.05658,47.644639999999995],[-122.056585,47.644495],[-122.05659,47.6443],[-122.05657699999999,47.64423],[-122.05655,47.644130000000004],[-122.0565,47.644017],[-122.05646,47.64393200000001],[-122.05641,47.64385],[-122.05622000000001,47.643631],[-122.05611,47.643478],[-122.05586999999998,47.64314],[-122.05575999999999,47.642981],[-122.05563,47.642790000000005],[-122.055596,47.64271900000001],[-122.05542999999999,47.642430000000004],[-122.05536,47.64229],[-122.05525,47.64209],[-122.05404,47.64209],[-122.05177599999999,47.64209],[-122.05177099999999,47.64092],[-122.04988,47.64092],[-122.04766000000002,47.638461],[-122.03359,47.638470000000005],[-122.03005,47.63848],[-122.02918,47.638490000000004],[-122.02738,47.63850000000001],[-122.02376,47.63851000000001],[-122.0235,47.63851400000001],[-122.020463,47.63853000000001],[-122.02047999999999,47.63949],[-122.01961000000001,47.63937000000001],[-122.01915000000001,47.63937000000001],[-122.01914000000001,47.63853000000001],[-122.01811000000001,47.63853900000001],[-122.01759000000001,47.638540000000006],[-122.01722000000002,47.638540000000006],[-122.01665000000001,47.638540000000006],[-122.01561000000001,47.638554000000006],[-122.01365000000001,47.63855900000001],[-122.01365000000001,47.63830000000001],[-122.01367,47.63798],[-122.00831,47.63758],[-122.00836,47.6349],[-122.00836999999999,47.634190000000004],[-122.00836999999999,47.633900000000004],[-122.00836,47.63387],[-122.00833999999999,47.63336],[-122.00832999999999,47.632780000000004],[-122.00775,47.63273000000001],[-122.00769000000001,47.63311000000001],[-122.00711000000001,47.63308],[-122.00706000000001,47.63304],[-122.00692000000001,47.632960000000004],[-122.00685,47.63291600000001],[-122.00661000000001,47.632690000000004],[-122.00632999999999,47.632540000000006],[-122.00618,47.63232300000001],[-122.00609,47.63224],[-122.00611,47.631927],[-122.00613,47.631875],[-122.00619,47.631721],[-122.00625000000001,47.63138],[-122.006325,47.631361],[-122.0064,47.631322000000004],[-122.00655,47.6314],[-122.006862,47.631354],[-122.00699,47.631279],[-122.007,47.630893],[-122.00435,47.630711000000005],[-122.00296999999999,47.630632000000006],[-122.00296999999999,47.626982000000005],[-122.00438,47.627075000000005],[-122.00497,47.627114000000006],[-122.00506,47.627120000000005],[-122.00573599999998,47.62717000000001],[-122.00581,47.627171000000004],[-122.0064,47.627210000000005],[-122.00669,47.627230000000004],[-122.00707,47.627257],[-122.00710000000001,47.627258000000005],[-122.0077,47.627297],[-122.00773,47.627299],[-122.00776,47.627300000000005],[-122.00842,47.627340000000004],[-122.00901,47.627382000000004],[-122.00911,47.627390000000005],[-122.00921000000001,47.6274],[-122.00977999999999,47.627430000000004],[-122.01018,47.62746],[-122.01053,47.62748],[-122.01108,47.627520000000004],[-122.01110000000001,47.627520000000004],[-122.01127000000001,47.62753000000001],[-122.01176000000001,47.62756],[-122.01242300000001,47.627610000000004],[-122.01309,47.627649],[-122.0137,47.62769],[-122.01375,47.62769],[-122.01387,47.62769],[-122.01387,47.62765],[-122.01387,47.62679000000001],[-122.013871,47.625870000000006],[-122.01387,47.62534000000001],[-122.013868,47.624050000000004],[-122.01656400000002,47.624002000000004],[-122.01658,47.62535000000001],[-122.01656000000001,47.625840000000004],[-122.01701000000001,47.62583000000001],[-122.021275,47.62577800000001],[-122.02128,47.62211000000001],[-122.02114900000001,47.62211000000001],[-122.01549,47.62220000000001],[-122.01516000000001,47.62220000000001],[-122.01387,47.622223000000005],[-122.01386000000001,47.62113000000001],[-122.01386000000001,47.61952],[-122.01386000000001,47.61851000000001],[-122.01386000000001,47.617193],[-122.013867,47.616726],[-122.01389,47.61645],[-122.0139,47.61609],[-122.01391000000001,47.61585],[-122.013917,47.615010000000005],[-122.01391000000001,47.61461],[-122.01388,47.61405],[-122.01387,47.61363],[-122.013875,47.61322],[-122.01388,47.61306],[-122.01388,47.612970000000004],[-122.0113,47.612826],[-122.010821,47.6128],[-122.01024000000001,47.612770000000005],[-122.0067,47.612590000000004],[-122.00529999999999,47.61252],[-122.00336999999999,47.612423],[-122.00189,47.612350000000006],[-122.00088,47.612299],[-121.998242,47.612167],[-121.99762000000003,47.612140000000004],[-121.99726000000003,47.61212200000001],[-121.99713000000001,47.61215000000001],[-121.99705000000002,47.612170000000006],[-121.996987,47.612190000000005],[-121.99691000000001,47.6122],[-121.99683,47.612210000000005],[-121.99673,47.612230000000004],[-121.9967,47.612230000000004],[-121.99667400000001,47.61294],[-121.99565000000001,47.61289],[-121.99210000000001,47.61272],[-121.99211000000001,47.612120000000004],[-121.99212000000001,47.61194],[-121.99220000000001,47.60828],[-121.995438,47.608288],[-121.99542000000001,47.609076],[-121.99561000000001,47.609089],[-121.99750000000002,47.60922],[-121.99748000000001,47.61032],[-122.00134,47.61049],[-122.00243999999999,47.609950000000005],[-122.00278999999999,47.609826],[-122.00316000000001,47.60971000000001],[-122.00354,47.60964],[-122.00462000000002,47.609493],[-122.00561,47.60654],[-122.00450000000001,47.60549],[-122.00455000000001,47.605286],[-122.00410000000001,47.60474],[-122.00397,47.60438],[-122.00351,47.60401],[-122.00349,47.60392],[-122.00446000000001,47.60396],[-122.00448,47.60389],[-122.00456000000001,47.603719000000005],[-122.00460000000001,47.6036],[-122.00461000000001,47.603453],[-122.00460000000001,47.603366],[-122.00409,47.603190000000005],[-122.00402000000001,47.60149],[-122.00454,47.6015],[-122.00453,47.60136],[-122.00447,47.6012],[-122.004355,47.60105],[-122.00433,47.60103],[-122.00422000000002,47.600930000000005],[-122.00404,47.6008],[-122.00395,47.600688],[-122.00386999999999,47.60058],[-122.00383,47.60045],[-122.00381,47.60031000000001],[-122.00382,47.60028],[-122.00342,47.599990000000005],[-122.00345,47.599410000000006],[-122.00282999999999,47.59873000000001],[-121.99344,47.59861300000001],[-121.99238,47.598600000000005],[-121.99235,47.60136],[-121.98865,47.601330000000004],[-121.98698,47.601310000000005],[-121.98255,47.60127],[-121.97978499999999,47.601265],[-121.975964,47.60119],[-121.97468,47.601174],[-121.97167,47.60114],[-121.97139,47.60114],[-121.97066000000001,47.60114],[-121.97067,47.600348000000004],[-121.97068,47.60023],[-121.97076999999999,47.594758000000006],[-121.97076999999999,47.594546],[-121.97068,47.594546],[-121.97013,47.594550000000005],[-121.96992000000002,47.593329000000004],[-121.96987,47.592483],[-121.96923000000001,47.591726],[-121.96902000000001,47.59111300000001],[-121.96861000000001,47.59011000000001],[-121.96787,47.589513000000004],[-121.96752000000002,47.58902],[-121.96725000000002,47.5888],[-121.96715000000002,47.588519000000005],[-121.96664000000001,47.588170000000005],[-121.96658000000001,47.588150000000006],[-121.96562000000002,47.58791],[-121.96552000000001,47.58791],[-121.96458000000001,47.587559999999996],[-121.96522000000002,47.587179],[-121.965272,47.58711],[-121.96547,47.586832],[-121.96565000000001,47.586565],[-121.96524000000001,47.585307],[-121.96522400000002,47.58481999999999],[-121.96522000000002,47.584649999999996],[-121.96533,47.584669999999996],[-121.96548,47.584695999999994],[-121.96562000000002,47.58471],[-121.96587,47.58472],[-121.96592000000001,47.58472],[-121.96614000000001,47.584711],[-121.96698,47.584599999999995],[-121.96713000000001,47.584419999999994],[-121.96722000000003,47.58427399999999],[-121.96721000000002,47.58411],[-121.96718000000001,47.58403],[-121.96728700000001,47.583932000000004],[-121.96736000000001,47.5838],[-121.96737,47.58374],[-121.96746000000002,47.58359],[-121.96773,47.583582],[-121.96796000000002,47.58365],[-121.96821000000001,47.58367],[-121.96843,47.58357],[-121.96856000000001,47.583317],[-121.96849,47.583186],[-121.96838,47.58305],[-121.9683,47.583013],[-121.96826000000001,47.582978000000004],[-121.96831,47.58287],[-121.96809,47.5826],[-121.96826000000001,47.582501],[-121.96838,47.5825],[-121.96862000000002,47.582417],[-121.9688,47.582370000000004],[-121.96917,47.582273],[-121.96936000000001,47.582066],[-121.96933,47.581768],[-121.96926000000002,47.581619999999994],[-121.96875,47.58147999999999],[-121.96866000000001,47.58135],[-121.96848,47.58107999999999],[-121.96854,47.58083],[-121.96855000000001,47.580597],[-121.96852000000001,47.580479999999994],[-121.9685,47.58041],[-121.96889,47.58015],[-121.96982000000001,47.580158000000004],[-121.96995500000001,47.58016],[-121.97113,47.58017],[-121.971292,47.58017],[-121.97356,47.5802],[-121.97348,47.583922],[-121.97346999999999,47.584419999999994],[-121.97428000000001,47.58436],[-121.97491000000001,47.58431],[-121.97509,47.584289999999996],[-121.97642,47.584182],[-121.97635,47.58755],[-121.98174,47.587619999999994],[-121.98185000000001,47.581759999999996],[-121.98273999999999,47.58115],[-121.98293,47.58101],[-121.98412000000002,47.58017],[-121.98428000000001,47.58005],[-121.98458000000001,47.57983],[-121.98473,47.57971800000001],[-121.98486000000001,47.5796],[-121.98499000000001,47.57947],[-121.98511,47.579342000000004],[-121.98556,47.57875000000001],[-121.98569,47.57858],[-121.98646000000001,47.57756],[-121.98746000000001,47.576190000000004],[-121.98773,47.575823],[-121.98802,47.575423],[-121.9894,47.575655000000005],[-121.99066000000002,47.57586],[-121.99112000000002,47.575945000000004],[-121.99142000000002,47.576010000000004],[-121.99157000000001,47.57605],[-121.99185000000001,47.576150000000005],[-121.99206000000001,47.576236],[-121.99219000000001,47.576296],[-121.99244,47.576426],[-121.99271,47.57662],[-121.99273,47.57664],[-121.99313000000001,47.57701],[-121.99339,47.577211],[-121.99367000000001,47.57741],[-121.99390000000001,47.57754],[-121.99415000000002,47.577659999999995],[-121.99465500000002,47.57784899999999],[-121.99480000000001,47.57789],[-121.995098,47.577954],[-121.995545,47.578033000000005],[-121.99600000000001,47.578100000000006],[-121.99626000000002,47.57813000000001],[-121.99659000000001,47.578165000000006],[-121.99703000000001,47.57822],[-121.99747,47.578298000000004],[-121.99790000000002,47.578390000000006],[-121.99831,47.578499],[-121.99861000000001,47.578590000000005],[-121.99892000000001,47.578700000000005],[-121.99922000000002,47.578810000000004],[-121.99951000000001,47.57893000000001],[-122.00056000000001,47.57931000000001],[-122.00339,47.58029],[-122.00364,47.580374],[-122.00405,47.580479999999994],[-122.00464000000001,47.58057],[-122.00478,47.580582],[-122.00507999999999,47.5806],[-122.00577999999999,47.58061],[-122.00816999999999,47.580639999999995],[-122.01039,47.58067],[-122.01163000000001,47.580679999999994],[-122.01251,47.5807],[-122.01416000000002,47.580711],[-122.01818,47.580754],[-122.02029,47.58078],[-122.02071,47.58079],[-122.02158,47.580799999999996],[-122.02188,47.580805],[-122.02202999999999,47.580802],[-122.0222,47.58079],[-122.02246999999998,47.58076],[-122.02275999999999,47.580714],[-122.02289999999999,47.58068299999999],[-122.02306999999999,47.580636],[-122.02336,47.58053],[-122.0236,47.58043],[-122.023939,47.580275],[-122.02431,47.58012],[-122.024808,47.57994],[-122.02507999999999,47.579846999999994],[-122.02526999999999,47.579796],[-122.02562999999999,47.5797],[-122.02752000000001,47.5793],[-122.02766000000001,47.57927],[-122.02788,47.57915200000001],[-122.02817999999999,47.578933000000006],[-122.0276,47.578634],[-122.02746,47.57856],[-122.02708,47.578340000000004],[-122.02681,47.578165000000006],[-122.02476999999999,47.576730000000005],[-122.02317,47.575610000000005],[-122.02280999999999,47.57536],[-122.02269,47.57527],[-122.02246999999998,47.57508],[-122.02234299999999,47.574948],[-122.02203999999999,47.57452],[-122.0219,47.57418],[-122.02183,47.573792000000005],[-122.02184,47.57338],[-122.0219,47.573086999999994],[-122.02199999999999,47.572810000000004],[-122.02226,47.572278000000004],[-122.02269999999999,47.571459999999995],[-122.02359,47.569855000000004],[-122.02406300000001,47.56901500000001],[-122.02438,47.56842],[-122.02481,47.567634],[-122.02519,47.56692],[-122.02526999999999,47.56669],[-122.02530999999999,47.566520000000004],[-122.02532,47.56636],[-122.02529999999999,47.56615000000001],[-122.02525,47.565898000000004],[-122.02505,47.565187],[-122.02494,47.564710000000005],[-122.02477999999999,47.5642],[-122.02472,47.563950000000006],[-122.02471,47.563778000000006],[-122.02471,47.5636],[-122.02486,47.562630000000006],[-122.02489,47.562441],[-122.02507999999999,47.561370000000004],[-122.02512,47.56109],[-122.02512999999999,47.560914000000004],[-122.0251,47.56073000000001],[-122.025059,47.56057500000001],[-122.02498,47.560410000000005],[-122.02486999999999,47.5602],[-122.02469,47.559922],[-122.0241,47.559013],[-122.02428,47.558972000000004],[-122.02446,47.558959],[-122.02494,47.558966],[-122.02516,47.55897],[-122.0278,47.559],[-122.02829999999999,47.559],[-122.02982999999999,47.559019],[-122.03072,47.55903],[-122.03199000000001,47.559053],[-122.03391,47.55907],[-122.03582,47.55909],[-122.03599,47.55909],[-122.03606,47.559079999999994],[-122.03622000000001,47.55907],[-122.03628,47.559056],[-122.03632999999999,47.55905],[-122.03652000000001,47.559005],[-122.0365,47.559109],[-122.04212000000001,47.559222],[-122.04267,47.55923],[-122.04269000000001,47.561445],[-122.04204,47.562785000000005],[-122.04023000000001,47.5628],[-122.03983099999999,47.5628],[-122.03594,47.562740000000005],[-122.03592,47.56363],[-122.035834,47.56734],[-122.035834,47.56815800000001],[-122.03582,47.56832000000001],[-122.03582,47.56904],[-122.03577999999999,47.56926],[-122.03576,47.56974],[-122.03572,47.572],[-122.03571,47.57226],[-122.035703,47.57378],[-122.03572,47.573859999999996],[-122.03569999999999,47.574889999999996],[-122.03565,47.577334],[-122.04102000000002,47.57737],[-122.04108000000001,47.57372],[-122.0428,47.573738000000006],[-122.04329600000001,47.57374],[-122.04393,47.573747],[-122.04506,47.573750000000004],[-122.04645000000001,47.57377],[-122.04650000000001,47.57084],[-122.04654000000001,47.56850000000001],[-122.04665200000001,47.568560000000005],[-122.04741300000002,47.569016000000005],[-122.04760000000002,47.56915000000001],[-122.04790000000001,47.569359000000006],[-122.04835,47.569720000000004],[-122.04916000000001,47.57042],[-122.04966000000002,47.570679999999996],[-122.05014,47.570836],[-122.05062000000001,47.57088399999999],[-122.05127,47.570859999999996],[-122.05138,47.57085],[-122.05161000000001,47.570802],[-122.0517,47.57078],[-122.05194,47.570709],[-122.05215,47.570622],[-122.05228,47.57056],[-122.05247999999999,47.57043],[-122.05259,47.570350000000005],[-122.05274999999999,47.57021],[-122.057392,47.57024],[-122.0574,47.569922000000005],[-122.05741,47.569390000000006],[-122.05741,47.569265],[-122.05742000000001,47.5692],[-122.05739,47.568470000000005],[-122.05739,47.56839000000001],[-122.05739,47.56831000000001],[-122.05738,47.56823000000001],[-122.05738,47.56812000000001],[-122.05736999999999,47.56798],[-122.05735,47.567377],[-122.057392,47.56665],[-122.06343,47.56655000000001],[-122.07370999999999,47.566382000000004],[-122.07424,47.566373000000006],[-122.07522,47.56752],[-122.07636,47.56891000000001],[-122.07699,47.56968],[-122.078167,47.57094],[-122.07849999999999,47.57129],[-122.08008,47.573],[-122.08212999999999,47.574397],[-122.0848,47.57623],[-122.09011000000001,47.57813000000001],[-122.09038,47.57822],[-122.09575,47.57996],[-122.09817,47.58166799999999],[-122.09859,47.581968999999994],[-122.09862000000001,47.582454],[-122.09863,47.582592000000005],[-122.09866000000001,47.583059],[-122.09895,47.587935],[-122.09933,47.5914],[-122.09946000000001,47.592580000000005],[-122.09951000000001,47.593025000000004]]]},"name":"Sammamish"},{"boundary":{"type":"Polygon","coordinates":[[[-122.19516999999999,47.54483999999999],[-122.19486,47.54483],[-122.19486,47.54568],[-122.19485,47.54582],[-122.19485,47.54583],[-122.194542,47.54582],[-122.19389,47.545795000000005],[-122.19382999999999,47.5458],[-122.19355,47.545798000000005],[-122.1929,47.545792000000006],[-122.19098699999999,47.545798000000005],[-122.1909,47.5458],[-122.1909,47.545662],[-122.1909,47.54491],[-122.1909,47.54357],[-122.19091,47.54299],[-122.19092,47.54242],[-122.1909,47.542333000000006],[-122.19086999999999,47.54227],[-122.19081,47.542212000000006],[-122.19069,47.54216],[-122.19065,47.54215000000001],[-122.19055,47.54214],[-122.18881999999999,47.54213000000001],[-122.18834899999999,47.54213000000001],[-122.18813999999999,47.54213000000001],[-122.18711,47.542120000000004],[-122.18587999999998,47.542100000000005],[-122.18576999999998,47.54209],[-122.18561,47.54205],[-122.18544999999999,47.542010000000005],[-122.18502999999998,47.54183],[-122.18481,47.54176],[-122.18446999999999,47.54165],[-122.184256,47.54160399999999],[-122.18356999999999,47.541489999999996],[-122.1822,47.54127],[-122.18198,47.54124099999999],[-122.18163,47.54119],[-122.18125,47.54115],[-122.18115,47.54114],[-122.18092,47.54113],[-122.17981999999999,47.5411],[-122.17903999999999,47.541078],[-122.17803999999998,47.54105],[-122.17702,47.541025],[-122.17580999999998,47.54099],[-122.1749,47.54096],[-122.17385999999999,47.54093],[-122.17152999999999,47.540868999999994],[-122.17135999999999,47.540855],[-122.17106999999999,47.54081],[-122.17083999999998,47.540756],[-122.17073999999998,47.54072],[-122.170656,47.540696],[-122.17044999999999,47.5406],[-122.17022999999999,47.54044],[-122.16991,47.54014],[-122.16986999999999,47.5401],[-122.16976999999999,47.5401],[-122.16963,47.5401],[-122.169166,47.5401],[-122.16879999999999,47.5401],[-122.16837999999998,47.540088],[-122.16822,47.540085],[-122.16765000000001,47.54007],[-122.16556,47.54001],[-122.16534999999999,47.54002],[-122.16476,47.54132],[-122.16445,47.54208],[-122.164187,47.542727],[-122.16414,47.543071],[-122.16414,47.54338],[-122.16415,47.54369],[-122.16415,47.54399],[-122.16425000000001,47.54431],[-122.16424,47.544672],[-122.16424,47.54548],[-122.1626,47.545463],[-122.16198,47.545455000000004],[-122.15996,47.54543],[-122.15356,47.545370000000005],[-122.15342,47.54538],[-122.15342,47.544560999999995],[-122.15342999999999,47.54175],[-122.14292,47.54167999999999],[-122.14294199999999,47.53936],[-122.14292,47.53936],[-122.14240199999999,47.5392],[-122.14204,47.539104],[-122.14160000000001,47.539006],[-122.14129000000001,47.53896],[-122.14108,47.53893000000001],[-122.13753,47.53848],[-122.13726000000001,47.538430000000005],[-122.13682,47.538340000000005],[-122.136528,47.538250000000005],[-122.13636999999999,47.5382],[-122.13604,47.538070000000005],[-122.13548999999999,47.53783],[-122.13389,47.53712],[-122.13346999999999,47.536930000000005],[-122.13328,47.53681],[-122.133104,47.53672],[-122.133046,47.53668],[-122.13277999999998,47.536485],[-122.13262999999999,47.53636],[-122.13236999999998,47.536120000000004],[-122.13222,47.53598],[-122.13221,47.535613000000005],[-122.13221,47.535320000000006],[-122.13222,47.53489],[-122.13222,47.532590000000006],[-122.13096999999999,47.532590000000006],[-122.13012,47.532588000000004],[-122.12946999999998,47.532587],[-122.12906,47.53178],[-122.12756999999999,47.52884],[-122.12774999999999,47.528670000000005],[-122.12832999999998,47.52803000000001],[-122.12924799999999,47.52706],[-122.13288999999999,47.52707],[-122.13672999999999,47.527074],[-122.14313,47.52711000000001],[-122.143122,47.526830000000004],[-122.14316000000001,47.52539000000001],[-122.143217,47.52347],[-122.13776,47.523616000000004],[-122.13785,47.516346],[-122.13847999999999,47.51634],[-122.13898999999999,47.516343],[-122.13992999999999,47.51635],[-122.14175,47.51636],[-122.142725,47.51636],[-122.14311000000001,47.51652],[-122.14336,47.516635],[-122.14359,47.51675],[-122.14404,47.516903],[-122.14447,47.516999999999996],[-122.14472,47.51705],[-122.14592,47.51714],[-122.14596999999999,47.51715],[-122.14842999999999,47.51732],[-122.14956000000001,47.51743999999999],[-122.14986999999999,47.51747399999999],[-122.15096999999999,47.51761],[-122.15173999999999,47.5177],[-122.15302999999999,47.51789699999999],[-122.15357999999999,47.518],[-122.1536,47.517869999999995],[-122.15638999999999,47.518297],[-122.15912,47.518727],[-122.1592,47.517861999999994],[-122.15929,47.51672],[-122.15926999999999,47.51653],[-122.15925,47.516439999999996],[-122.15923,47.51629],[-122.15915,47.5161],[-122.15936999999998,47.516059999999996],[-122.15952,47.516059999999996],[-122.15957999999999,47.516059999999996],[-122.15964,47.516075],[-122.15970999999999,47.5161],[-122.15979099999998,47.516130000000004],[-122.15982,47.51615],[-122.16031,47.516459999999995],[-122.16099,47.51687],[-122.1622,47.517568999999995],[-122.16233499999998,47.51765999999999],[-122.16237999999998,47.5177],[-122.162433,47.51779],[-122.16244999999999,47.518056],[-122.16247999999999,47.51814],[-122.16251,47.518170000000005],[-122.16253999999999,47.518201],[-122.162579,47.51822],[-122.16264,47.518248],[-122.16272999999998,47.51827],[-122.16323,47.51827],[-122.16346999999999,47.518279],[-122.1645,47.51838],[-122.16481,47.51841],[-122.16524,47.518433],[-122.16535999999999,47.51843],[-122.165714,47.51841],[-122.16588999999999,47.518390000000004],[-122.16801,47.51809],[-122.169084,47.51795],[-122.17067999999999,47.517739999999996],[-122.17087999999998,47.51771],[-122.17142,47.517649999999996],[-122.17266999999998,47.51747999999999],[-122.17274999999998,47.51747099999999],[-122.17547999999998,47.51705],[-122.17604999999999,47.51696],[-122.17712999999999,47.51672],[-122.17734999999999,47.51664399999999],[-122.18032999999998,47.51668299999999],[-122.180403,47.5166],[-122.18047299999999,47.5166],[-122.18148,47.51661],[-122.18361,47.51663],[-122.18406999999999,47.516635],[-122.18416,47.516722],[-122.18404,47.51672],[-122.18408,47.51675],[-122.18502999999998,47.51759],[-122.18553999999999,47.51806],[-122.18574999999998,47.51806],[-122.18577999999998,47.51871200000001],[-122.18573999999998,47.520100000000006],[-122.18570999999999,47.52116],[-122.18566999999999,47.52172],[-122.18566999999999,47.521792000000005],[-122.185656,47.522040000000004],[-122.18567999999999,47.52211000000001],[-122.18581999999999,47.52211000000001],[-122.18597999999999,47.52203000000001],[-122.186013,47.52203000000001],[-122.18607999999999,47.522017000000005],[-122.18656,47.522017000000005],[-122.18702,47.52200800000001],[-122.18741,47.52201000000001],[-122.18741,47.52302],[-122.18787999999999,47.523049],[-122.18794,47.522966000000004],[-122.18800999999999,47.522850000000005],[-122.18802999999998,47.52282],[-122.18804999999999,47.522794000000005],[-122.18807999999999,47.52275900000001],[-122.18811799999999,47.52272200000001],[-122.18816,47.522690000000004],[-122.18818999999999,47.52266],[-122.18824,47.52263000000001],[-122.18827999999999,47.52261000000001],[-122.18830999999999,47.522600000000004],[-122.18834999999999,47.52257000000001],[-122.18842,47.52255000000001],[-122.18846999999998,47.52253000000001],[-122.18852999999999,47.52252000000001],[-122.18858999999999,47.52250000000001],[-122.18863999999999,47.522496000000004],[-122.18867999999999,47.522489],[-122.18873999999998,47.52248],[-122.18877999999998,47.522483],[-122.18881999999999,47.52248],[-122.18888999999999,47.52248],[-122.18896,47.52248],[-122.189026,47.522490000000005],[-122.1891,47.52250000000001],[-122.18918,47.52252000000001],[-122.18923099999999,47.52266],[-122.18937999999999,47.52259000000001],[-122.18957999999999,47.522740000000006],[-122.18978999999999,47.522963000000004],[-122.19019,47.52347],[-122.19042999999999,47.52382],[-122.19106000000001,47.5249],[-122.19106000000001,47.525090000000006],[-122.19094,47.531242],[-122.19324,47.5312],[-122.19358,47.5312],[-122.19359,47.531530000000004],[-122.193602,47.531639999999996],[-122.19363,47.53174],[-122.19368,47.53184399999999],[-122.19373999999999,47.531928],[-122.19373499999999,47.533],[-122.19297999999999,47.533],[-122.19238999999999,47.533],[-122.19236999999998,47.534819999999996],[-122.192519,47.534819999999996],[-122.19284999999999,47.534819999999996],[-122.19305,47.534819999999996],[-122.19306,47.53528],[-122.19237999999999,47.53528],[-122.19238999999999,47.53678],[-122.19239999999999,47.53737],[-122.19239999999999,47.53742],[-122.19239999999999,47.537731],[-122.19287899999999,47.537924],[-122.19284999999999,47.53796],[-122.19274999999999,47.53810000000001],[-122.19256999999999,47.538340000000005],[-122.19251,47.538438000000006],[-122.19409,47.53848],[-122.19426000000001,47.53936],[-122.19460000000001,47.540395000000004],[-122.1948,47.54097],[-122.19496000000001,47.54143],[-122.19506,47.5417],[-122.19512,47.54213000000001],[-122.19516999999999,47.54261],[-122.19515,47.54301],[-122.1951,47.54301],[-122.19511,47.54356],[-122.19511,47.543985],[-122.19517799999998,47.54399],[-122.19516999999999,47.54483999999999]]]},"name":"Newcastle"},{"boundary":{"type":"Polygon","coordinates":[[[-122.10054,47.554199999999994],[-122.1004,47.555676],[-122.10024,47.557379999999995],[-122.10019,47.559225],[-122.09797,47.559228],[-122.0987,47.56058],[-122.09834,47.561879],[-122.09818,47.562475000000006],[-122.09824,47.56270000000001],[-122.09831,47.56297000000001],[-122.09866000000001,47.56442],[-122.09872999999999,47.564710000000005],[-122.09889,47.56525200000001],[-122.09905,47.566234],[-122.09914,47.56669],[-122.09966000000001,47.56777],[-122.09978199999999,47.56812800000001],[-122.0999,47.568450000000006],[-122.09877999999999,47.568830000000005],[-122.09912000000001,47.569334000000005],[-122.09926000000002,47.569285],[-122.09963,47.569762000000004],[-122.0994,47.56992],[-122.09919000000001,47.570009],[-122.09894,47.56987],[-122.09894,47.56982],[-122.09789,47.569810000000004],[-122.09789,47.57052],[-122.09790000000001,47.57157],[-122.09790000000001,47.571878],[-122.09789,47.57204],[-122.09754000000001,47.571979999999996],[-122.0967,47.57181],[-122.09586999999999,47.57139],[-122.095,47.57078],[-122.09463000000001,47.57052],[-122.09232999999999,47.57172],[-122.09173,47.57121],[-122.09168000000001,47.5712],[-122.09161000000002,47.57118],[-122.09159000000001,47.571149],[-122.09157,47.571130000000004],[-122.09155000000001,47.57112],[-122.09153,47.571110000000004],[-122.09151000000001,47.571106],[-122.09144,47.57107],[-122.091389,47.571054],[-122.09137,47.571039999999996],[-122.0913,47.571008],[-122.09127000000001,47.57099],[-122.09119000000001,47.570977],[-122.09115000000001,47.57097],[-122.09112000000002,47.57096],[-122.09109000000001,47.57095],[-122.09105000000001,47.570930000000004],[-122.09103,47.570924],[-122.09101000000001,47.570910000000005],[-122.09099,47.5709],[-122.09098,47.570885],[-122.09096000000001,47.57087],[-122.09091000000001,47.57083],[-122.09086,47.57082],[-122.09084,47.57082],[-122.09079,47.570791],[-122.09079,47.57077],[-122.09069000000001,47.57069],[-122.09063,47.57064],[-122.09059,47.57062],[-122.09055000000001,47.57061],[-122.09054,47.57059],[-122.09055000000001,47.57056],[-122.09061900000002,47.57052],[-122.09059,47.57047],[-122.09056000000001,47.57043],[-122.09054,47.570412000000005],[-122.09052000000001,47.57039],[-122.09047,47.57036],[-122.09042000000001,47.570330000000006],[-122.09038,47.57032],[-122.09033,47.5703],[-122.0903,47.570283999999994],[-122.09027,47.57027],[-122.09023,47.570243],[-122.09020000000001,47.570232000000004],[-122.09015000000001,47.570219],[-122.09012000000001,47.57022],[-122.09002000000001,47.57023],[-122.09001,47.570229],[-122.09001,47.57018],[-122.09,47.570170000000005],[-122.08993,47.570097],[-122.08991,47.57008],[-122.08998,47.569970000000005],[-122.08995,47.569928000000004],[-122.08991,47.569888],[-122.08986999999999,47.56986],[-122.08979,47.56982],[-122.08962500000001,47.56978],[-122.08951,47.569770000000005],[-122.08941,47.569762000000004],[-122.08933999999999,47.56974],[-122.08928,47.56974],[-122.08872999999998,47.56986],[-122.08663,47.570322000000004],[-122.08627,47.570107],[-122.086218,47.570113000000006],[-122.08606,47.57011000000001],[-122.08606,47.57014],[-122.08596999999999,47.570114000000004],[-122.08592,47.570107],[-122.08572999999998,47.57008],[-122.08569999999999,47.57008],[-122.08567,47.57008],[-122.08561,47.570057],[-122.0856,47.57004],[-122.08554,47.569981],[-122.08542999999999,47.56992],[-122.08538999999999,47.569900000000004],[-122.08538999999999,47.56988],[-122.08534999999999,47.56986],[-122.0851,47.56965],[-122.08487,47.56947],[-122.08453,47.56936],[-122.08435,47.56929],[-122.08406000000001,47.569051],[-122.083985,47.568960000000004],[-122.08391,47.56886],[-122.08379199999999,47.568765000000006],[-122.08375,47.56875300000001],[-122.08369,47.56873000000001],[-122.08367,47.56868],[-122.08342999999999,47.568509000000006],[-122.0832,47.56832000000001],[-122.08305,47.568220000000004],[-122.08288999999999,47.56815100000001],[-122.08282,47.56812000000001],[-122.08277999999999,47.56810000000001],[-122.08274999999999,47.568064],[-122.08272199999999,47.56796],[-122.08272,47.567937],[-122.082726,47.56789],[-122.08271599999999,47.567851],[-122.08269999999999,47.5678],[-122.08269,47.567721],[-122.08266,47.56766],[-122.08266,47.56765],[-122.08265,47.56761],[-122.08266,47.56756],[-122.08264,47.567459],[-122.08216999999999,47.566386],[-122.08205,47.566167],[-122.08203999999999,47.56609],[-122.08197,47.566027],[-122.08192000000001,47.56598],[-122.08183,47.565858000000006],[-122.08171300000001,47.56571000000001],[-122.08166800000001,47.565601],[-122.081638,47.565540000000006],[-122.08158,47.56546],[-122.08154,47.565383000000004],[-122.08149,47.565259000000005],[-122.08141,47.56511000000001],[-122.08142000000001,47.56503000000001],[-122.08136,47.56492],[-122.08129000000001,47.564856],[-122.08125000000001,47.5648],[-122.081185,47.564646999999994],[-122.0811,47.564510000000006],[-122.08093,47.56428],[-122.08076999999999,47.56406],[-122.08062000000001,47.56385],[-122.08052,47.563691],[-122.08046,47.563590000000005],[-122.08039,47.563410000000005],[-122.08027,47.563086],[-122.08023,47.563010000000006],[-122.08022000000001,47.56299500000001],[-122.08022000000001,47.562900000000006],[-122.08022000000001,47.562830000000005],[-122.08019,47.56274500000001],[-122.08015,47.562630000000006],[-122.08010900000001,47.56259000000001],[-122.08006999999999,47.56247200000001],[-122.08002400000001,47.562349000000005],[-122.08,47.56225800000001],[-122.07997999999999,47.56221000000001],[-122.07996,47.562180000000005],[-122.07992999999999,47.56211000000001],[-122.07992,47.562020000000004],[-122.07991,47.56184],[-122.07986999999999,47.56175],[-122.07982,47.56154],[-122.07981,47.56146],[-122.07981,47.56138],[-122.07979999999999,47.56132],[-122.07973999999999,47.56114],[-122.07972999999998,47.56108],[-122.07972999999998,47.56106],[-122.07972,47.56103],[-122.07969999999999,47.56096],[-122.07968,47.56088],[-122.07964,47.56072],[-122.07959,47.560615000000006],[-122.0795,47.56051000000001],[-122.0795,47.56047],[-122.0795,47.560340000000004],[-122.0795,47.560306000000004],[-122.0795,47.56026],[-122.0795,47.5602],[-122.07948999999999,47.56017000000001],[-122.07948999999999,47.56011000000001],[-122.07948999999999,47.56009],[-122.07948999999999,47.56006],[-122.0795,47.56002],[-122.07951,47.559916],[-122.07952,47.55985],[-122.07952,47.55972],[-122.07952999999999,47.55967999999999],[-122.07954,47.559585],[-122.0796,47.55912],[-122.0796,47.559059999999995],[-122.0796,47.55903],[-122.07961,47.559014],[-122.07961,47.55901],[-122.07962,47.559],[-122.07962,47.55898],[-122.0796,47.55896],[-122.0796,47.55894],[-122.07959,47.558934],[-122.07959,47.558914],[-122.07959,47.55889],[-122.0796,47.55887],[-122.07961,47.55881],[-122.07962,47.558794],[-122.07962,47.55875],[-122.07961,47.558709],[-122.07952999999999,47.558659],[-122.07903999999999,47.55834],[-122.07831999999999,47.55787999999999],[-122.07822999999999,47.557829999999996],[-122.07800999999999,47.557339999999996],[-122.07772999999999,47.556722],[-122.07763,47.55656],[-122.07713,47.55572],[-122.07623,47.55552],[-122.07593999999999,47.555710000000005],[-122.07562999999999,47.55592],[-122.07493,47.55552],[-122.07463,47.55582],[-122.07482999999999,47.55632],[-122.07322,47.55638],[-122.07311,47.556259999999995],[-122.07287999999998,47.55609],[-122.07298999999999,47.556072],[-122.07309,47.556079999999994],[-122.073116,47.556079999999994],[-122.07312,47.55561],[-122.07279999999999,47.555479999999996],[-122.07076999999998,47.55552],[-122.0702,47.555530000000005],[-122.0682,47.55556],[-122.06822000000001,47.553889999999996],[-122.06823,47.55356],[-122.06823,47.553279999999994],[-122.06825,47.55197999999999],[-122.06825300000001,47.55187999999999],[-122.06812000000001,47.55187099999999],[-122.067938,47.551869999999994],[-122.06773,47.551869999999994],[-122.06635,47.551899999999996],[-122.06497,47.551919999999996],[-122.06042000000001,47.55198599999999],[-122.05969999999999,47.55198699999999],[-122.05945,47.55197],[-122.05927,47.551945999999994],[-122.05914,47.551899999999996],[-122.05869999999999,47.55168399999999],[-122.058563,47.55179],[-122.05841,47.55187099999999],[-122.05822,47.551939999999995],[-122.05803999999999,47.55197999999999],[-122.05785,47.55201],[-122.05766000000001,47.55202],[-122.05682999999999,47.552028],[-122.05424000000001,47.55197],[-122.05208599999999,47.55197],[-122.05202999999999,47.55424599999999],[-122.051861,47.55938],[-122.048589,47.55935],[-122.05063,47.562222000000006],[-122.050767,47.56241000000001],[-122.05112000000001,47.56291000000001],[-122.05277999999998,47.56523000000001],[-122.05326000000001,47.56514000000001],[-122.05401,47.56512000000001],[-122.05493,47.565220000000004],[-122.05552999999999,47.56562],[-122.05565,47.565780000000004],[-122.05569,47.565830000000005],[-122.05573099999998,47.566722000000006],[-122.05735,47.567377],[-122.05736999999999,47.56798],[-122.05738,47.56812000000001],[-122.05738,47.56823000000001],[-122.05739,47.56831000000001],[-122.05739,47.56839000000001],[-122.05739,47.568470000000005],[-122.05742000000001,47.5692],[-122.05741,47.569265],[-122.05741,47.569390000000006],[-122.0574,47.569922000000005],[-122.057392,47.57024],[-122.05274999999999,47.57021],[-122.05259,47.570350000000005],[-122.05247999999999,47.57043],[-122.05228,47.57056],[-122.05215,47.570622],[-122.05194,47.570709],[-122.0517,47.57078],[-122.05161000000001,47.570802],[-122.05138,47.57085],[-122.05127,47.570859999999996],[-122.05062000000001,47.57088399999999],[-122.05014,47.570836],[-122.04966000000002,47.570679999999996],[-122.04916000000001,47.57042],[-122.04835,47.569720000000004],[-122.04790000000001,47.569359000000006],[-122.04760000000002,47.56915000000001],[-122.04741300000002,47.569016000000005],[-122.04665200000001,47.568560000000005],[-122.04654000000001,47.56850000000001],[-122.04650000000001,47.57084],[-122.04645000000001,47.57377],[-122.04506,47.573750000000004],[-122.04393,47.573747],[-122.04329600000001,47.57374],[-122.0428,47.573738000000006],[-122.04108000000001,47.57372],[-122.04102000000002,47.57737],[-122.03565,47.577334],[-122.03569999999999,47.574889999999996],[-122.03572,47.573859999999996],[-122.035703,47.57378],[-122.03571,47.57226],[-122.03572,47.572],[-122.03576,47.56974],[-122.03577999999999,47.56926],[-122.03582,47.56904],[-122.03582,47.56832000000001],[-122.035834,47.56815800000001],[-122.035834,47.56734],[-122.03592,47.56363],[-122.03594,47.562740000000005],[-122.03983099999999,47.5628],[-122.04023000000001,47.5628],[-122.04204,47.562785000000005],[-122.04269000000001,47.561445],[-122.04267,47.55923],[-122.04212000000001,47.559222],[-122.0365,47.559109],[-122.03652000000001,47.559005],[-122.03632999999999,47.55905],[-122.03628,47.559056],[-122.03622000000001,47.55907],[-122.03606,47.559079999999994],[-122.03599,47.55909],[-122.03582,47.55909],[-122.03391,47.55907],[-122.03199000000001,47.559053],[-122.03072,47.55903],[-122.02982999999999,47.559019],[-122.02829999999999,47.559],[-122.0278,47.559],[-122.02516,47.55897],[-122.02494,47.558966],[-122.02446,47.558959],[-122.02428,47.558972000000004],[-122.0241,47.559013],[-122.024,47.55885],[-122.02349,47.558065],[-122.022973,47.55724299999999],[-122.02264,47.556689999999996],[-122.02230999999999,47.5562],[-122.02128400000001,47.55476699999999],[-122.02053,47.555197],[-122.02013,47.55542],[-122.02006,47.55546399999999],[-122.02002,47.555330000000005],[-122.01997,47.55518],[-122.01944,47.55360399999999],[-122.019847,47.553608],[-122.020065,47.55361],[-122.02074999999999,47.553599999999996],[-122.02073999999999,47.553512000000005],[-122.02005,47.55347999999999],[-122.02006,47.55193],[-122.020061,47.55173],[-122.02044,47.55173],[-122.02071,47.551736],[-122.02106,47.55147999999999],[-122.02111000000001,47.55143999999999],[-122.02122000000001,47.55105999999999],[-122.021461,47.55071],[-122.02147,47.55033],[-122.02149,47.550185],[-122.02148,47.550124],[-122.02143,47.550052],[-122.02122000000001,47.54981],[-122.02091,47.549679999999995],[-122.02054,47.54932],[-122.02067,47.54926699999999],[-122.02008,47.54863],[-122.02009,47.548218000000006],[-122.018986,47.54822],[-122.018584,47.548210000000005],[-122.01807,47.5482],[-122.01712000000002,47.548210000000005],[-122.01676,47.54822],[-122.016335,47.548230000000004],[-122.01602000000001,47.54822],[-122.01556000000001,47.548210000000005],[-122.01479,47.54889],[-122.01480000000001,47.54972],[-122.01481000000001,47.551016999999995],[-122.01441000000001,47.55105999999999],[-122.01356000000001,47.55115],[-122.01335,47.55116699999999],[-122.01317,47.551168],[-122.01301000000001,47.551159999999996],[-122.01286999999999,47.55114],[-122.01279,47.551119],[-122.01258,47.55105],[-122.0124,47.55097],[-122.01225000000001,47.55087699999999],[-122.01219,47.550832],[-122.01204,47.5507],[-122.01184,47.550557],[-122.01167000000001,47.55045],[-122.01154000000001,47.550380999999994],[-122.01139,47.55032],[-122.01129000000002,47.550279999999994],[-122.01111000000002,47.550218],[-122.01085,47.55015],[-122.00877999999999,47.54986399999999],[-122.00854,47.549831],[-122.0084,47.5499],[-122.0081,47.549859999999995],[-122.00792000000001,47.549839999999996],[-122.0077,47.54985],[-122.00748,47.549879999999995],[-122.00729000000001,47.54993],[-122.00710000000001,47.55001],[-122.00693,47.550110000000004],[-122.00622000000001,47.550689999999996],[-122.00601,47.55085],[-122.00583199999998,47.550953],[-122.00568,47.55103],[-122.005519,47.551069999999996],[-122.004,47.551469999999995],[-122.004,47.55152],[-122.00375,47.551773],[-122.00371,47.552],[-122.00374,47.552216],[-122.00379,47.552330000000005],[-122.00383,47.55268099999999],[-122.00364,47.55303],[-122.003369,47.553279999999994],[-122.00336,47.553619999999995],[-122.00336,47.553722],[-122.00336,47.5541],[-122.00334,47.555710000000005],[-122.00329,47.555710000000005],[-122.0032,47.555716000000004],[-122.00311,47.555745],[-122.0029,47.5559],[-122.00283999999999,47.55589],[-122.00273999999999,47.555898],[-122.00263,47.55596],[-122.00254,47.556219999999996],[-122.00234999999999,47.55628599999999],[-122.00214,47.55633],[-122.002,47.55641],[-122.00197,47.556419999999996],[-122.00182000000001,47.556493999999994],[-122.001749,47.55658],[-122.00166000000002,47.55656],[-122.00137,47.55659],[-122.00125000000001,47.556675],[-122.00114,47.55695],[-121.99985000000001,47.556819999999995],[-121.99963300000002,47.556802],[-121.99913400000001,47.55631],[-121.99892000000001,47.55645],[-121.99791000000002,47.55707999999999],[-121.99699000000001,47.55643],[-121.99625000000002,47.555910000000004],[-121.99583,47.55562],[-121.99573,47.555479999999996],[-121.99563,47.55536],[-121.99564000000001,47.55529],[-121.99560000000001,47.55521],[-121.99554,47.55518],[-121.995365,47.555130000000005],[-121.99525000000001,47.555113000000006],[-121.99516000000001,47.555130000000005],[-121.99509,47.55518],[-121.99504,47.55526],[-121.995,47.55533500000001],[-121.99498000000001,47.555367],[-121.99502000000001,47.55547],[-121.99524000000001,47.555530000000005],[-121.99528400000001,47.55565],[-121.99368000000001,47.555716000000004],[-121.99369000000002,47.55544],[-121.9937,47.554649999999995],[-121.99335,47.554649999999995],[-121.99226000000002,47.55463999999999],[-121.98883,47.554595],[-121.98817,47.55458699999999],[-121.98803,47.55457],[-121.98779,47.554579999999994],[-121.98773,47.55439],[-121.98773,47.55413],[-121.98794000000001,47.55386099999999],[-121.98796100000001,47.553619999999995],[-121.98812000000001,47.55336],[-121.98811,47.553110000000004],[-121.98826000000001,47.55289],[-121.98834,47.552459999999996],[-121.98829,47.55225],[-121.98872,47.551736],[-121.98899,47.55169899999999],[-121.98907,47.55182299999999],[-121.98929000000001,47.55185999999999],[-121.98943,47.55179],[-121.9895,47.55167999999999],[-121.98964000000001,47.551629999999996],[-121.98986000000001,47.55168699999999],[-121.9899,47.551733],[-121.989939,47.55179],[-121.99014000000001,47.551849999999995],[-121.99049000000001,47.551759999999994],[-121.99050000000001,47.55193],[-121.99063000000001,47.55202],[-121.99088,47.5521],[-121.99096000000002,47.552279999999996],[-121.99105000000002,47.552350000000004],[-121.99135000000001,47.55245],[-121.99152000000002,47.552377],[-121.99153000000001,47.55218],[-121.99160000000002,47.552163],[-121.99166700000002,47.55209],[-121.99187,47.552036],[-121.99208,47.552032000000004],[-121.99233,47.552150000000005],[-121.9924,47.552170000000004],[-121.99313000000001,47.55185999999999],[-121.99337,47.551809999999996],[-121.99374,47.55176899999999],[-121.99394000000001,47.551689999999994],[-121.99428600000002,47.55169599999999],[-121.99445000000001,47.55159],[-121.99445000000001,47.551215],[-121.99408000000001,47.55132],[-121.99352200000001,47.55141999999999],[-121.99314000000001,47.55142699999999],[-121.99282000000001,47.55155],[-121.99243,47.55134399999999],[-121.99247,47.55037],[-121.99244,47.54986399999999],[-121.99243,47.549521],[-121.99257,47.54938],[-121.99260000000001,47.549257],[-121.99265000000001,47.549223],[-121.99276,47.5492],[-121.99300000000001,47.54921],[-121.99315000000001,47.549170000000004],[-121.99321000000002,47.54916],[-121.99360300000002,47.54918],[-121.99384,47.549279999999996],[-121.99414000000002,47.549307],[-121.99439000000001,47.549271],[-121.99443000000001,47.549279999999996],[-121.99442000000002,47.54919],[-121.99440000000001,47.54912],[-121.99437,47.54904],[-121.99433,47.54896],[-121.99425000000002,47.548873],[-121.99400000000001,47.54862],[-121.99387,47.54856],[-121.99352000000002,47.548390000000005],[-121.99281,47.548030000000004],[-121.99263,47.5479],[-121.99222000000002,47.547799999999995],[-121.99185000000001,47.547792],[-121.99157000000001,47.547636999999995],[-121.99146000000002,47.54751],[-121.99132000000002,47.54744099999999],[-121.99097,47.547149],[-121.99091000000001,47.547003999999994],[-121.99071,47.54687],[-121.99058000000001,47.546859999999995],[-121.99046300000002,47.546910000000004],[-121.99015000000001,47.54743],[-121.98997,47.54756],[-121.98996000000001,47.54761],[-121.98987,47.54767999999999],[-121.98987,47.54774],[-121.98996000000001,47.54785],[-121.99014000000001,47.547928],[-121.99026000000002,47.547956],[-121.98978,47.54872],[-121.98934,47.5492],[-121.98899,47.54942],[-121.98876999999999,47.54949],[-121.98865,47.549214],[-121.98856,47.549138000000006],[-121.98768000000001,47.54904],[-121.98749000000001,47.54894],[-121.98732000000001,47.54894],[-121.9865,47.54922],[-121.985762,47.54914],[-121.98572999999999,47.54669],[-121.98635,47.546574],[-121.98655000000001,47.54669],[-121.98676,47.546755000000005],[-121.98682000000001,47.54684399999999],[-121.98703,47.546921],[-121.98728400000002,47.547039999999996],[-121.98736000000001,47.547079999999994],[-121.98743,47.547079999999994],[-121.98755000000001,47.5471],[-121.98764000000001,47.547032],[-121.98767000000001,47.54701],[-121.98767000000001,47.54694],[-121.98767000000001,47.5468],[-121.98745000000001,47.54665],[-121.98727000000001,47.54643],[-121.98723000000001,47.54638],[-121.98720100000001,47.546242],[-121.98729000000002,47.54607],[-121.98754000000001,47.545910000000006],[-121.98779,47.5458],[-121.98806,47.54575200000001],[-121.98866000000001,47.54562],[-121.98931,47.545410000000004],[-121.98977,47.54493],[-121.98985,47.544869999999996],[-121.99015400000002,47.54466599999999],[-121.99063000000001,47.544459999999994],[-121.99072100000001,47.544419999999995],[-121.99084,47.54437],[-121.99111000000002,47.54437],[-121.99122000000003,47.544200999999994],[-121.99169000000002,47.544186999999994],[-121.99170000000001,47.544106],[-121.99177,47.54403],[-121.99191800000001,47.543986],[-121.99204,47.543983],[-121.99277,47.54354],[-121.99325000000002,47.543206],[-121.99339,47.54301],[-121.99359000000001,47.54254],[-121.99340000000001,47.542549],[-121.993108,47.542570000000005],[-121.99302000000002,47.54244],[-121.99258,47.542014],[-121.99253,47.541889999999995],[-121.99239,47.541771],[-121.99215000000001,47.54167999999999],[-121.992085,47.54161],[-121.99206000000001,47.54145],[-121.99209,47.54137],[-121.99204,47.541259999999994],[-121.99187,47.54120399999999],[-121.99172000000002,47.5408],[-121.99185000000001,47.54055],[-121.99190000000002,47.54051200000001],[-121.99218,47.540476],[-121.9924,47.540382],[-121.99252000000001,47.54023],[-121.992537,47.54007],[-121.99266000000001,47.53978],[-121.99289,47.53958],[-121.993171,47.539320000000004],[-121.99333,47.53917200000001],[-121.99334,47.53916],[-121.99343,47.539136000000006],[-121.99356000000002,47.53913300000001],[-121.99371000000001,47.53911000000001],[-121.99371000000001,47.53907],[-121.99778,47.538947],[-121.99797000000001,47.538940000000004],[-121.99865000000001,47.53893000000001],[-122.00123,47.538900000000005],[-122.00123,47.53875000000001],[-122.00411000000001,47.53983],[-122.00507999999999,47.54023],[-122.00499,47.54041],[-122.00492000000001,47.54057],[-122.0049,47.54061],[-122.00486000000001,47.54073],[-122.00465000000001,47.541301],[-122.00413,47.54226],[-122.00399,47.54255500000001],[-122.00382,47.54296],[-122.00375,47.54312],[-122.00349,47.54357],[-122.003316,47.543910000000004],[-122.00322000000001,47.54414],[-122.00301,47.54456],[-122.00356000000001,47.54456],[-122.00384,47.544],[-122.00426300000002,47.544015],[-122.00553,47.54147],[-122.0056,47.54134],[-122.00733,47.541758],[-122.00835,47.541771],[-122.00833999999999,47.541908],[-122.00833999999999,47.54213000000001],[-122.00833999999999,47.543416],[-122.00927,47.543417],[-122.00955,47.542992000000005],[-122.00948,47.54223],[-122.0094,47.54191],[-122.00946,47.54181],[-122.00955,47.54167399999999],[-122.00961000000001,47.54157],[-122.00969,47.541439999999994],[-122.00979,47.54132],[-122.00989,47.54123],[-122.00992000000001,47.541199999999996],[-122.00994,47.54116],[-122.00996,47.54112],[-122.00996,47.541079999999994],[-122.00997,47.540879999999994],[-122.00996,47.540839999999996],[-122.00995,47.5408],[-122.00992400000001,47.54076],[-122.00989,47.540726],[-122.00981,47.540642],[-122.00974,47.54055],[-122.00968,47.54044],[-122.00966000000001,47.540310000000005],[-122.00981,47.54023],[-122.01006000000001,47.54019],[-122.01013,47.54018],[-122.0104,47.540088],[-122.01044,47.540074],[-122.01059000000001,47.540002],[-122.01067,47.53996],[-122.0113,47.539916000000005],[-122.01131000000001,47.54001],[-122.01139,47.54058],[-122.01147,47.541165],[-122.01227,47.541117],[-122.01234,47.541112000000005],[-122.01245,47.540330000000004],[-122.01243,47.5402],[-122.01241,47.54006],[-122.01238,47.53978],[-122.01225000000001,47.53948],[-122.01207,47.53906],[-122.01198000000001,47.53891000000001],[-122.0121,47.53881500000001],[-122.01332000000001,47.538830000000004],[-122.01408,47.53891300000001],[-122.01415000000001,47.538920000000005],[-122.01453000000001,47.538961],[-122.01457,47.538970000000006],[-122.01480000000001,47.539046],[-122.01482000000001,47.53748399999999],[-122.01499700000001,47.537478],[-122.01534799999999,47.53721],[-122.01562000000001,47.53686],[-122.01606000000001,47.53643],[-122.01628000000001,47.53602],[-122.01649,47.5356],[-122.01689,47.535270000000004],[-122.01684,47.53508],[-122.01683,47.534529],[-122.01681,47.53429],[-122.01502,47.53529],[-122.01422700000002,47.535654],[-122.01381,47.53582],[-122.01333,47.53596],[-122.01276,47.536120000000004],[-122.01215,47.536259],[-122.01163000000001,47.536362000000004],[-122.01109000000001,47.536486999999994],[-122.01072,47.536550000000005],[-122.01031,47.536590000000004],[-122.009785,47.536608],[-122.00922000000001,47.536614],[-122.00921000000001,47.53651000000001],[-122.00903,47.53651000000001],[-122.00871,47.536500000000004],[-122.00842999999999,47.536486999999994],[-122.00799,47.536446],[-122.007806,47.53642],[-122.00706000000001,47.53629],[-122.006779,47.536223],[-122.00644,47.536141],[-122.00598,47.536006],[-122.005609,47.535879],[-122.00545,47.53582],[-122.00521,47.535720000000005],[-122.00428600000001,47.53531100000001],[-122.00376999999999,47.53508],[-122.001973,47.534259999999996],[-122.00127,47.533950000000004],[-122.00089,47.53379],[-122.00001,47.53343],[-121.99971000000001,47.533321],[-121.9987,47.53296],[-121.99798000000001,47.532740000000004],[-121.99717000000001,47.532494],[-121.99677,47.53238],[-121.99573199999999,47.532109000000005],[-121.99541,47.532030000000006],[-121.99413000000001,47.531754],[-121.99289900000001,47.531530000000004],[-121.99258,47.531479999999995],[-121.99197000000001,47.53139],[-121.99124000000002,47.531295],[-121.99061000000002,47.531223999999995],[-121.98893,47.531079999999996],[-121.98877999999999,47.53107],[-121.98876999999999,47.531243999999994],[-121.98797,47.531223999999995],[-121.98798000000001,47.531025],[-121.98799000000001,47.530754],[-121.98801,47.529700000000005],[-121.98898,47.52969],[-121.98972,47.52968],[-121.98994,47.52967],[-121.99011000000002,47.52967],[-121.99031000000001,47.52969],[-121.99069000000001,47.52966],[-121.99082000000001,47.52967],[-121.99102000000002,47.52966],[-121.99133,47.529650000000004],[-121.991847,47.529686],[-121.99199100000001,47.52969],[-121.99228000000001,47.52968],[-121.99245,47.52967],[-121.99285,47.529650000000004],[-121.99321000000002,47.529683],[-121.993342,47.52969],[-121.99349000000001,47.52969],[-121.99350000000001,47.529590000000006],[-121.99351000000001,47.529472000000005],[-121.99353,47.529247],[-121.99355000000001,47.52899000000001],[-121.99406000000002,47.52253200000001],[-121.99796000000002,47.522574000000006],[-122.002959,47.52263000000001],[-122.00461000000001,47.522650000000006],[-122.00438,47.52628],[-122.00912000000001,47.526340000000005],[-122.00951,47.526340000000005],[-122.00964,47.52626],[-122.00966000000001,47.525870000000005],[-122.00969,47.52541000000001],[-122.00993,47.52079200000001],[-122.01164000000001,47.520809],[-122.01195000000001,47.520809],[-122.01222000000001,47.520810000000004],[-122.01306300000002,47.52082],[-122.01522000000001,47.520830000000004],[-122.01505,47.52476],[-122.01490700000001,47.52826],[-122.01484,47.529725000000006],[-122.01483,47.52999200000001],[-122.01483,47.53007],[-122.01483,47.530125000000005],[-122.02336999999999,47.529990000000005],[-122.02336,47.52986],[-122.02358,47.52958],[-122.02369,47.529450000000004],[-122.02394,47.52919000000001],[-122.02409,47.529050000000005],[-122.024967,47.528200000000005],[-122.02525,47.52797],[-122.02539999999999,47.527879999999996],[-122.02542,47.52786699999999],[-122.02597999999999,47.5276],[-122.02656999999999,47.52727],[-122.02678999999999,47.52714],[-122.0269,47.52706],[-122.027031,47.526944],[-122.02729000000001,47.52669],[-122.02739,47.52658],[-122.02753,47.526390000000006],[-122.02606999999999,47.52638],[-122.02593999999999,47.526382000000005],[-122.025954,47.52596500000001],[-122.02599,47.524730000000005],[-122.02556999999999,47.524730000000005],[-122.02344,47.526390000000006],[-122.02063,47.52639500000001],[-122.02063,47.52624],[-122.02063,47.52559000000001],[-122.02059,47.524533000000005],[-122.02059,47.524268],[-122.02059,47.52407],[-122.02059799999999,47.523340000000005],[-122.0206,47.522784],[-122.02066,47.52097200000001],[-122.02072,47.519439999999996],[-122.02071,47.519130000000004],[-122.02208999999999,47.519130000000004],[-122.022211,47.51544],[-122.02262,47.51544],[-122.022943,47.51544],[-122.02326000000001,47.51545],[-122.02431,47.51549],[-122.02451,47.51549],[-122.024911,47.51546],[-122.0251,47.51544],[-122.02519199999999,47.51544],[-122.02586,47.51544],[-122.02502999999999,47.514779999999995],[-122.02483,47.51460599999999],[-122.02455,47.51432],[-122.02427,47.51401],[-122.02406,47.51372],[-122.02382,47.51334],[-122.02374599999999,47.5132],[-122.02368,47.51305],[-122.02352,47.512646999999994],[-122.02345,47.51239],[-122.0234,47.51216],[-122.02335,47.511849999999995],[-122.02311,47.509099],[-122.02372999999999,47.50908],[-122.02398,47.50908],[-122.02423,47.50909],[-122.024601,47.509111000000004],[-122.02565,47.50915200000001],[-122.02574999999999,47.509130000000006],[-122.02580999999999,47.509104],[-122.02667,47.50911000000001],[-122.03106000000001,47.50912],[-122.03137,47.50918],[-122.03150000000001,47.51274],[-122.03456000000001,47.51277],[-122.036941,47.512791],[-122.03695,47.515601],[-122.04282,47.51559],[-122.04583,47.515585],[-122.04733,47.51558],[-122.04804,47.51559],[-122.05356,47.51577],[-122.05606,47.51585],[-122.05794,47.515906],[-122.05834999999999,47.51592],[-122.05834099999998,47.516202],[-122.05821,47.51953],[-122.06404,47.51958],[-122.06741000000001,47.51961],[-122.06762000000002,47.51961],[-122.06826000000001,47.519619999999996],[-122.06872999999999,47.519619999999996],[-122.06914,47.51962699999999],[-122.06925000000001,47.519619999999996],[-122.06952500000001,47.519620999999994],[-122.06976,47.51962399999999],[-122.06997,47.519971],[-122.07018,47.520326000000004],[-122.07011,47.52038],[-122.07019,47.52038],[-122.07022,47.52044],[-122.07025,47.52056],[-122.07025,47.52067],[-122.07024,47.520790000000005],[-122.07022,47.52091000000001],[-122.07016999999999,47.52104],[-122.07007999999999,47.521170000000005],[-122.07002,47.52125],[-122.069995,47.52129],[-122.06987,47.5214],[-122.07015,47.5214],[-122.07012999999999,47.522650000000006],[-122.0701,47.52317000000001],[-122.07275999999999,47.52317600000001],[-122.07283999999999,47.51963],[-122.07936999999998,47.519639999999995],[-122.07946999999999,47.519639999999995],[-122.07934999999999,47.525420000000004],[-122.07925,47.529723000000004],[-122.07924,47.53031000000001],[-122.07659,47.53031000000001],[-122.07651,47.533],[-122.07648999999999,47.5334],[-122.07636999999998,47.53755],[-122.07572999999998,47.53755],[-122.07372,47.537566],[-122.07362,47.541304],[-122.07359,47.542100000000005],[-122.07096,47.542100000000005],[-122.07094,47.542550000000006],[-122.07189,47.542550000000006],[-122.07357999999999,47.54255200000001],[-122.0735,47.54481],[-122.06825,47.544805999999994],[-122.06824,47.54544],[-122.06842999999999,47.545550000000006],[-122.06958,47.54625],[-122.06979,47.5464],[-122.06993,47.546510000000005],[-122.07015,47.54669],[-122.07029999999999,47.546839],[-122.07034999999999,47.54688399999999],[-122.07882999999998,47.54683],[-122.07886999999998,47.548266],[-122.08054,47.54828],[-122.08142000000001,47.54829],[-122.08236999999998,47.548300000000005],[-122.08324,47.548304],[-122.08344,47.548306000000004],[-122.08389,47.548311000000005],[-122.08418,47.548311000000005],[-122.08418,47.54847],[-122.084235,47.55179999999999],[-122.08611,47.551829999999995],[-122.09206,47.551939999999995],[-122.09483,47.551989999999996],[-122.0948,47.55014],[-122.09481000000001,47.550079999999994],[-122.0948,47.550018],[-122.09476000000001,47.548446],[-122.09762000000002,47.548483],[-122.09997,47.54849],[-122.10018,47.548493],[-122.10046999999999,47.54849],[-122.10046999999999,47.54858],[-122.10046999999999,47.548925000000004],[-122.10045,47.550059999999995],[-122.10041,47.551035],[-122.10041,47.552865],[-122.10049,47.553716],[-122.10054,47.554199999999994]],[[-121.995455,47.55117],[-121.99512000000001,47.55111],[-121.99490000000002,47.551173],[-121.99469000000002,47.55119],[-121.99469000000002,47.55153],[-121.99480000000001,47.55153],[-121.99543,47.55141],[-121.99553,47.551249999999996],[-121.995455,47.55117]],[[-122.03565,47.52484],[-122.03407,47.524823],[-122.03399,47.52646],[-122.03558,47.52648],[-122.03565,47.52484]],[[-122.02976199999999,47.53442],[-122.02914,47.53403],[-122.02832,47.533530000000006],[-122.02761000000001,47.533120000000004],[-122.02713,47.532862],[-122.027,47.532790000000006],[-122.02672999999999,47.532666],[-122.02656,47.532590000000006],[-122.0264,47.53253000000001],[-122.02624,47.53247],[-122.0261,47.532422000000004],[-122.02596999999999,47.53239000000001],[-122.02578999999999,47.532340000000005],[-122.02526,47.53222],[-122.02521,47.532360000000004],[-122.02507999999999,47.53269],[-122.02485,47.532700000000006],[-122.02485,47.53368],[-122.02575999999999,47.53365],[-122.02577999999998,47.53668],[-122.02578999999999,47.53721],[-122.02579999999999,47.53732],[-122.02798,47.53734],[-122.02968,47.537351],[-122.02969999999999,47.53684],[-122.02972199999999,47.53598],[-122.02972999999999,47.535720000000005],[-122.02974999999999,47.535000000000004],[-122.02976199999999,47.53442]]]},"name":"Issaquah"},{"boundary":{"type":"Polygon","coordinates":[[[-122.45968,47.676199000000004],[-122.4595,47.67762],[-122.45938,47.67871000000001],[-122.4593,47.679770000000005],[-122.45916000000001,47.68064],[-122.45904,47.68178399999999],[-122.4589,47.682320000000004],[-122.45864,47.683110000000006],[-122.45833999999999,47.68377],[-122.45818,47.684079999999994],[-122.45716000000002,47.686063999999995],[-122.45628,47.687642999999994],[-122.4559,47.688246],[-122.45286999999999,47.69389],[-122.450936,47.697500000000005],[-122.44987,47.69950000000001],[-122.44797700000001,47.70271100000001],[-122.44624000000002,47.70568],[-122.44534,47.70691000000001],[-122.44489000000002,47.707228],[-122.44416000000002,47.70784],[-122.44199000000002,47.70946],[-122.44119000000002,47.71007],[-122.43859,47.71196],[-122.43658,47.71453],[-122.43588,47.71747],[-122.43516000000001,47.721540000000005],[-122.43476000000001,47.724920000000004],[-122.43443,47.726366000000006],[-122.43406000000002,47.72796],[-122.43362000000002,47.72945000000001],[-122.43328300000002,47.730581],[-122.4329,47.73128],[-122.43276,47.731590000000004],[-122.43256000000001,47.731924],[-122.43218,47.73255000000001],[-122.432,47.732804],[-122.3812,47.7338],[-122.37494,47.73391900000001],[-122.37437599999998,47.733920000000005],[-122.37241999999999,47.73393000000001],[-122.36195900000001,47.734140000000004],[-122.36093,47.734139000000006],[-122.35916999999999,47.73413600000001],[-122.35556999999999,47.73413000000001],[-122.35426000000001,47.734128000000005],[-122.35304,47.734128000000005],[-122.35176999999999,47.734128000000005],[-122.35051,47.734128000000005],[-122.3478,47.734127],[-122.3463,47.734127],[-122.345211,47.734127],[-122.3451,47.734127],[-122.34499000000001,47.734127],[-122.34400000000001,47.734128000000005],[-122.34342000000001,47.734128000000005],[-122.34239,47.73413000000001],[-122.34134,47.73413000000001],[-122.34103,47.73413000000001],[-122.34074,47.73413000000001],[-122.33969,47.734131000000005],[-122.33863,47.73413000000001],[-122.33807999999999,47.73413000000001],[-122.33698,47.73413300000001],[-122.33592999999999,47.734134000000005],[-122.33555,47.734134000000005],[-122.33532999999998,47.734134000000005],[-122.33428,47.73413000000001],[-122.33375,47.73413000000001],[-122.33303,47.734125000000006],[-122.33158,47.73411000000001],[-122.33023,47.734100000000005],[-122.32887999999998,47.73409],[-122.32637999999999,47.73407],[-122.32622,47.73407],[-122.32565,47.73407],[-122.32531999999999,47.734062],[-122.32525,47.73406],[-122.32498,47.73406],[-122.32473399999999,47.73406],[-122.32452,47.73406],[-122.32446,47.73406],[-122.32359,47.734052000000005],[-122.32222,47.73404],[-122.32079999999999,47.734030000000004],[-122.31812000000001,47.734001],[-122.31676,47.733990000000006],[-122.31544,47.73397000000001],[-122.31275,47.73395000000001],[-122.31005,47.73391900000001],[-122.30869999999999,47.733900000000006],[-122.30803999999999,47.733896],[-122.30733,47.73389],[-122.30617,47.73388],[-122.30599,47.733874],[-122.304844,47.73386],[-122.304574,47.73386],[-122.3036,47.733850000000004],[-122.30195,47.733830000000005],[-122.30059,47.73382],[-122.29923000000001,47.733810000000005],[-122.29788,47.733790000000006],[-122.29652000000002,47.733784],[-122.29531,47.73377000000001],[-122.29516000000001,47.73377000000001],[-122.29469000000002,47.73377000000001],[-122.29381000000001,47.733760000000004],[-122.29243,47.73375000000001],[-122.291098,47.733740000000004],[-122.28975,47.73373000000001],[-122.28842999999999,47.73373000000001],[-122.28705000000001,47.733720000000005],[-122.28671,47.733720000000005],[-122.28632,47.73373000000001],[-122.28596,47.73373000000001],[-122.28586999999999,47.733720000000005],[-122.2858,47.73368],[-122.28489,47.73366],[-122.2848,47.73366],[-122.28461000000001,47.733650000000004],[-122.28447,47.733650000000004],[-122.28353,47.73361200000001],[-122.28195000000001,47.733610000000006],[-122.26981,47.733477],[-122.26974,47.73312000000001],[-122.26859,47.72737000000001],[-122.26844,47.726620000000004],[-122.2684,47.726400000000005],[-122.26719000000001,47.71916],[-122.26714000000001,47.71882],[-122.26114000000001,47.708020000000005],[-122.25974,47.70599000000001],[-122.25874999999999,47.704544],[-122.254136,47.69782],[-122.24393,47.692220000000006],[-122.23183,47.69012000000001],[-122.23058,47.684915000000004],[-122.22623,47.666920000000005],[-122.23143,47.664120000000004],[-122.23803,47.65922],[-122.24026000000002,47.6577],[-122.25572999999999,47.64712],[-122.259087,47.640375000000006],[-122.25912000000001,47.640299],[-122.26005,47.636990000000004],[-122.26142000000002,47.632008000000006],[-122.26353,47.624320000000004],[-122.26419000000001,47.62115000000001],[-122.26572999999999,47.61362],[-122.26763000000001,47.601521],[-122.26784,47.597073],[-122.26813,47.590821],[-122.26815,47.590039000000004],[-122.26815,47.58996],[-122.26815,47.58983],[-122.26816000000001,47.58953],[-122.26814,47.589314],[-122.26574,47.583974],[-122.26475,47.58175],[-122.26213,47.57592],[-122.25450000000001,47.56685],[-122.25202999999999,47.56392],[-122.25003,47.562521000000004],[-122.24403000000001,47.55962099999999],[-122.24093,47.55542],[-122.24150900000002,47.55357],[-122.24283,47.54932],[-122.24528000000001,47.54564],[-122.24882000000001,47.540302000000004],[-122.24973,47.538920000000005],[-122.24971000000001,47.53851300000001],[-122.24969000000002,47.53813900000001],[-122.24965400000002,47.537510000000005],[-122.24933,47.531819999999996],[-122.24073,47.522921000000004],[-122.23807,47.52132],[-122.23323,47.51842],[-122.22603,47.51242],[-122.22443,47.50942],[-122.23529,47.509510000000006],[-122.23623300000001,47.50952],[-122.23705000000001,47.50952],[-122.23819,47.50952],[-122.23822000000001,47.509530000000005],[-122.23832999999999,47.50954],[-122.23842,47.509544],[-122.23881,47.509530000000005],[-122.23892000000001,47.50956],[-122.24115000000002,47.50959],[-122.24156900000003,47.5096],[-122.24303,47.5096],[-122.24329000000002,47.509547],[-122.24331000000001,47.50969],[-122.24373,47.509709],[-122.24425400000003,47.50959],[-122.24504,47.50965],[-122.245387,47.509710000000005],[-122.24559,47.50971800000001],[-122.24579,47.509730000000005],[-122.24712000000002,47.509658],[-122.24694000000001,47.50923],[-122.24694000000001,47.509150000000005],[-122.24690000000001,47.50908],[-122.24683,47.509015000000005],[-122.24674,47.508970000000005],[-122.24697,47.508210000000005],[-122.24611000000002,47.50727],[-122.24604000000001,47.50616],[-122.24614000000001,47.505426],[-122.24629000000002,47.505249],[-122.2455,47.503814],[-122.24555000000001,47.503530000000005],[-122.24563,47.50306],[-122.24721000000002,47.503073],[-122.24802000000001,47.50308],[-122.248,47.503550000000004],[-122.24793900000002,47.50638],[-122.24810000000001,47.50638],[-122.24878,47.50638],[-122.24879,47.506278],[-122.24879,47.50626],[-122.24879,47.506116000000006],[-122.24881,47.50479],[-122.24882000000001,47.50413],[-122.24882000000001,47.504020999999995],[-122.24883,47.503838],[-122.248839,47.503076],[-122.24884,47.502995000000006],[-122.24564400000001,47.502953000000005],[-122.24496000000002,47.502944],[-122.243131,47.50291000000001],[-122.24238,47.502904],[-122.24125000000002,47.50289],[-122.24082000000001,47.50288],[-122.24083,47.502230000000004],[-122.24084,47.50147],[-122.24086000000001,47.49963],[-122.24086000000001,47.499570000000006],[-122.24087,47.49924],[-122.24158000000001,47.49925],[-122.24592000000001,47.499306000000004],[-122.24863,47.49933000000001],[-122.24963000000001,47.499340000000004],[-122.24975,47.499711000000005],[-122.25017,47.5011],[-122.25026000000001,47.5014],[-122.25035,47.5017],[-122.25051,47.502219000000004],[-122.25062000000001,47.50251900000001],[-122.250734,47.502720000000004],[-122.25081,47.50283],[-122.25141,47.5028],[-122.25164000000001,47.50282],[-122.25362000000001,47.50273000000001],[-122.25951,47.50254],[-122.25953,47.501306],[-122.25955,47.500150000000005],[-122.25961000000001,47.497350000000004],[-122.25964,47.495990000000006],[-122.25964,47.4956],[-122.26408,47.495551000000006],[-122.26427000000001,47.49557000000001],[-122.26485000000001,47.495560000000005],[-122.26537799999998,47.495610000000006],[-122.26694,47.495610000000006],[-122.26853,47.4956],[-122.26941000000001,47.49564],[-122.26981900000001,47.49564],[-122.27029999999999,47.495650000000005],[-122.27029999999999,47.496030000000005],[-122.270294,47.49635000000001],[-122.27029,47.496570000000006],[-122.27028,47.497],[-122.27028,47.497059],[-122.27028,47.497327],[-122.27027,47.49758],[-122.27023,47.50028],[-122.27032999999999,47.5003],[-122.27045,47.50027],[-122.27059,47.50028],[-122.270671,47.5003],[-122.27074999999999,47.50081],[-122.27069999999999,47.500826999999994],[-122.27085,47.501459999999994],[-122.27086999999999,47.50155],[-122.27089,47.501613],[-122.27078999999999,47.50159],[-122.27026000000001,47.50147],[-122.2702,47.50263],[-122.27017,47.503177],[-122.27017,47.504475],[-122.27016,47.5049],[-122.27016,47.505100000000006],[-122.27014,47.50609],[-122.27014,47.50658],[-122.27012,47.507025],[-122.27008699999999,47.50878],[-122.27006999999999,47.50954],[-122.270069,47.50966],[-122.27155,47.5097],[-122.27408,47.509750000000004],[-122.27541,47.50978],[-122.27541,47.50963],[-122.27542,47.50893000000001],[-122.27542999999999,47.507142],[-122.27542999999999,47.506930000000004],[-122.27576999999998,47.507006999999994],[-122.27606999999999,47.507130000000004],[-122.276241,47.50723],[-122.27708,47.50777],[-122.27722000000001,47.507839999999995],[-122.27739,47.507892],[-122.27754,47.507936],[-122.2777,47.507966999999994],[-122.27789,47.507992],[-122.27823,47.50799],[-122.27833999999999,47.50799],[-122.27837999999998,47.50887],[-122.27852999999999,47.509577],[-122.27853999999999,47.50981],[-122.28245,47.510023],[-122.28304,47.51004],[-122.28344,47.510055],[-122.28374,47.510059999999996],[-122.28486000000001,47.51015],[-122.28576999999999,47.510220999999994],[-122.28625000000001,47.51022],[-122.28768000000001,47.510193],[-122.29136900000002,47.51015],[-122.29133,47.510838],[-122.29132000000001,47.51162699999999],[-122.29131000000001,47.51341],[-122.29131000000001,47.51359],[-122.2913,47.51367],[-122.2913,47.51374],[-122.2913,47.51387999999999],[-122.29129000000002,47.514889999999994],[-122.29126000000002,47.517936],[-122.29126000000002,47.518510000000006],[-122.29126000000002,47.519259999999996],[-122.29126000000002,47.51996],[-122.29127000000001,47.5214],[-122.29124000000002,47.52149],[-122.291278,47.52156],[-122.29127000000001,47.52290000000001],[-122.29126000000002,47.52411000000001],[-122.29127000000001,47.52438],[-122.29134,47.52438],[-122.29178,47.524394],[-122.29335,47.52442],[-122.29363000000001,47.52442],[-122.29387,47.52442],[-122.29398400000001,47.52442],[-122.29994,47.52456],[-122.30002999999999,47.524577],[-122.3003,47.52458],[-122.30056,47.52458],[-122.30181,47.52459],[-122.30054,47.52512000000001],[-122.30051,47.52517000000001],[-122.30049,47.52521000000001],[-122.30027,47.52541000000001],[-122.30014,47.52577000000001],[-122.30035,47.52653000000001],[-122.30092,47.52769],[-122.30119,47.52875000000001],[-122.30142000000001,47.529385000000005],[-122.30229,47.53005],[-122.30278999999999,47.530225],[-122.303502,47.530530000000006],[-122.30488799999999,47.53118],[-122.304981,47.53127],[-122.30516999999999,47.53132],[-122.30529,47.53141],[-122.30542999999999,47.531530000000004],[-122.305517,47.531715000000005],[-122.30714,47.53173],[-122.30842999999999,47.53173],[-122.30892,47.53174],[-122.30918,47.53174],[-122.31328,47.53177],[-122.31681,47.53177],[-122.317958,47.53177],[-122.31803,47.531765],[-122.31835,47.531766],[-122.31845,47.531762],[-122.31853,47.53176],[-122.31851,47.53114],[-122.31846,47.53047],[-122.318453,47.52977800000001],[-122.31844099999999,47.52939000000001],[-122.31757,47.529410000000006],[-122.31757,47.52917000000001],[-122.31757,47.529026],[-122.31759000000001,47.528825000000005],[-122.31760000000001,47.528549000000005],[-122.31755000000001,47.52821000000001],[-122.31748,47.52817100000001],[-122.31490000000001,47.527370000000005],[-122.31472000000001,47.52732],[-122.31282999999999,47.52682],[-122.31264,47.52678],[-122.311887,47.52658],[-122.31184,47.526557000000004],[-122.31172400000001,47.526485],[-122.31161000000002,47.526360000000004],[-122.31143,47.526182000000006],[-122.31108,47.525760000000005],[-122.31044,47.52533000000001],[-122.30971,47.52429],[-122.30981,47.523950000000006],[-122.3096,47.52329],[-122.309651,47.52308],[-122.30972,47.522403000000004],[-122.30968,47.52218500000001],[-122.30976,47.52217000000001],[-122.31013,47.52214000000001],[-122.31099,47.522146000000006],[-122.31336,47.52213000000001],[-122.313516,47.52215000000001],[-122.31367,47.522183000000005],[-122.31427000000001,47.522183000000005],[-122.31489,47.52219100000001],[-122.31498,47.522197000000006],[-122.31511,47.52221000000001],[-122.31586,47.52221000000001],[-122.31602000000001,47.522220000000004],[-122.31766000000002,47.522222000000006],[-122.31769000000001,47.52131000000001],[-122.31769000000001,47.52102],[-122.3177,47.520627],[-122.31771,47.52048],[-122.31771,47.5202],[-122.31771,47.52006],[-122.31772000000001,47.51987999999999],[-122.31773,47.519151],[-122.31773,47.518754],[-122.31813,47.518727],[-122.318413,47.518724],[-122.3186,47.518722000000004],[-122.32014,47.518710000000006],[-122.320343,47.518710000000006],[-122.32129,47.518716000000005],[-122.32161,47.518710000000006],[-122.32314,47.51871800000001],[-122.32374999999999,47.5187],[-122.32509999999999,47.518674],[-122.32587999999998,47.51869],[-122.32587999999998,47.52023200000001],[-122.32587999999998,47.520920000000004],[-122.3279,47.520921],[-122.32959,47.52091000000001],[-122.330073,47.52091000000001],[-122.3306,47.520970000000005],[-122.33082999999999,47.52098],[-122.33097,47.520990000000005],[-122.32995,47.519552000000004],[-122.32982,47.518892],[-122.32973999999999,47.518508000000004],[-122.32956999999999,47.51811800000001],[-122.32951,47.517979999999994],[-122.3295,47.51763999999999],[-122.3295,47.51755],[-122.329422,47.517498999999994],[-122.32918,47.517219999999995],[-122.32916999999999,47.51715],[-122.32884999999999,47.51675],[-122.32870999999999,47.516593],[-122.32864,47.5165],[-122.32729,47.51393],[-122.32815,47.51393],[-122.32852999999999,47.51393],[-122.33023,47.51393],[-122.33029,47.51403],[-122.33059,47.51454],[-122.33097,47.51494399999999],[-122.33115000000001,47.515121],[-122.3335,47.51469099999999],[-122.33349,47.5145],[-122.33435300000001,47.5145],[-122.33425000000001,47.5139],[-122.33570999999999,47.513923],[-122.33651,47.513914],[-122.336645,47.51391],[-122.33704,47.513889999999996],[-122.33712000000001,47.51502],[-122.337444,47.51701],[-122.33748,47.517258],[-122.33754,47.51725999999999],[-122.33793,47.517258],[-122.33991,47.517269999999996],[-122.340134,47.51727999999999],[-122.34045,47.517239999999994],[-122.34075,47.517219999999995],[-122.34144,47.51727999999999],[-122.34161000000002,47.51727999999999],[-122.34287799999998,47.517289999999996],[-122.34387,47.517289999999996],[-122.34415000000001,47.51729399999999],[-122.34464000000001,47.517298999999994],[-122.34543,47.5173],[-122.3467,47.51731],[-122.347972,47.51732],[-122.348017,47.51732],[-122.34925000000001,47.51732],[-122.35065,47.51733],[-122.35172999999999,47.517339],[-122.35177999999999,47.517339],[-122.35291,47.51736],[-122.3534,47.51737],[-122.35404,47.517374],[-122.35516999999999,47.517379999999996],[-122.35611,47.51739],[-122.35651,47.517382],[-122.35786,47.51736],[-122.35822999999999,47.51735],[-122.35856,47.51735],[-122.35856,47.515530000000005],[-122.35918,47.515530000000005],[-122.36049,47.515530000000005],[-122.36054,47.516819999999996],[-122.36054,47.51736],[-122.36113,47.51736],[-122.36185,47.51736],[-122.36262,47.51736],[-122.36323,47.51736],[-122.36457,47.51736],[-122.36591,47.51736],[-122.36728000000001,47.517379999999996],[-122.36769000000001,47.51738099999999],[-122.36866,47.51739],[-122.3714,47.51742399999999],[-122.37132,47.51665],[-122.37127,47.516076],[-122.37127,47.516023999999994],[-122.37126,47.51574],[-122.37115,47.51445999999999],[-122.3711,47.5137],[-122.37107999999999,47.51333],[-122.37102,47.512550000000005],[-122.371,47.51229],[-122.37097999999999,47.51205],[-122.37088999999999,47.510038],[-122.37086,47.50946],[-122.37087999999999,47.508199000000005],[-122.37086199999999,47.50802],[-122.37070999999999,47.507259999999995],[-122.37052999999999,47.50666],[-122.37046,47.506409],[-122.370464,47.5064],[-122.37038999999999,47.505821],[-122.37039999999999,47.505686],[-122.37044999999999,47.505503000000004],[-122.37046999999998,47.5054],[-122.37053999999999,47.50513000000001],[-122.37055,47.50452],[-122.37056,47.50387],[-122.3706,47.503679999999996],[-122.37069,47.503510000000006],[-122.37092,47.50324],[-122.37141,47.502778000000006],[-122.37204999999999,47.502182000000005],[-122.37226,47.502010000000006],[-122.37312,47.50138],[-122.37379999999999,47.500834],[-122.37392,47.5007],[-122.37396999999999,47.50058],[-122.37406,47.49992],[-122.37413,47.499617],[-122.37427799999999,47.499221],[-122.37485,47.497659],[-122.37492,47.4975],[-122.37501999999999,47.49732],[-122.37506999999998,47.49725],[-122.37521,47.49703],[-122.37531199999998,47.49687],[-122.37565,47.496430000000004],[-122.38013,47.493203],[-122.38181,47.491912000000006],[-122.39924,47.48522],[-122.40884,47.481719999999996],[-122.40968000000001,47.486365],[-122.415,47.496050000000004],[-122.41557,47.497105000000005],[-122.41675000000001,47.499293],[-122.41750000000002,47.50067],[-122.42345,47.50833000000001],[-122.4252,47.51059],[-122.42658,47.512214],[-122.43086000000001,47.516593],[-122.43344,47.519233],[-122.43718000000001,47.52293200000001],[-122.43944,47.52565800000001],[-122.44162000000003,47.529090000000004],[-122.44324000000002,47.53264],[-122.44357000000001,47.533350000000006],[-122.44452300000003,47.53638],[-122.44510000000001,47.539832000000004],[-122.44515000000001,47.541802999999994],[-122.44515000000001,47.54185999999999],[-122.44509000000001,47.544578],[-122.44483000000001,47.54743],[-122.44446600000002,47.551382999999994],[-122.44443000000001,47.55267],[-122.44439000000001,47.554022999999994],[-122.44459000000002,47.55576],[-122.44485000000002,47.55643],[-122.44524000000001,47.55743999999999],[-122.44602000000002,47.55889],[-122.44730400000002,47.56091000000001],[-122.44808,47.56210000000001],[-122.44842000000001,47.56264],[-122.44865000000001,47.56298],[-122.44874,47.563120000000005],[-122.4497,47.56462],[-122.45074,47.56624],[-122.45158,47.5674],[-122.45248,47.568404],[-122.45338,47.56956],[-122.45415000000001,47.571059],[-122.45461000000002,47.572230000000005],[-122.45481000000001,47.57336],[-122.45481000000001,47.57389],[-122.45460000000001,47.57457],[-122.45401000000001,47.576431],[-122.45395,47.576609],[-122.453818,47.57703],[-122.45373,47.577293],[-122.45016000000001,47.588522000000005],[-122.44770000000001,47.595560000000006],[-122.44680000000001,47.59813500000001],[-122.44774000000001,47.614765],[-122.44953000000001,47.61765],[-122.45289,47.622656000000006],[-122.45475,47.626204],[-122.45475,47.62709],[-122.45469000000001,47.627970000000005],[-122.45449,47.62911000000001],[-122.45435,47.629627],[-122.454171,47.630247],[-122.45391000000001,47.63139],[-122.45345,47.632628000000004],[-122.45319500000001,47.63367],[-122.45293,47.634916000000004],[-122.45273999999999,47.636351000000005],[-122.45255,47.63811000000001],[-122.45255,47.63876500000001],[-122.45255,47.639790000000005],[-122.45261,47.64208],[-122.45281,47.64402],[-122.453,47.64555000000001],[-122.45310300000001,47.646268],[-122.45325700000001,47.647310000000004],[-122.45359,47.64924],[-122.45397,47.65082399999999],[-122.45445000000001,47.65265],[-122.45463000000001,47.65349],[-122.45501,47.65461],[-122.45546999999999,47.655836],[-122.455927,47.657148],[-122.45645,47.65841],[-122.45709000000001,47.66001000000001],[-122.4578,47.661970000000004],[-122.45825,47.663343000000005],[-122.45858,47.664381],[-122.45904,47.666470000000004],[-122.459437,47.66848],[-122.45955900000001,47.66953900000001],[-122.45962000000002,47.67112],[-122.45962000000002,47.672697],[-122.4597,47.67427],[-122.45968,47.676199000000004]]]},"name":"Seattle"},{"boundary":{"type":"Polygon","coordinates":[[[-122.17603999999999,47.75708899999999],[-122.17595999999999,47.757345],[-122.17568999999999,47.75738],[-122.17538999999998,47.75732],[-122.17522999999998,47.75725],[-122.17516999999998,47.75721],[-122.17509999999999,47.757176],[-122.17499,47.75712],[-122.17482,47.757085999999994],[-122.17459,47.75707],[-122.17459,47.757306],[-122.17459,47.7575],[-122.17457999999999,47.75788099999999],[-122.17457999999999,47.75808],[-122.17457999999999,47.75817000000001],[-122.17457999999999,47.758320000000005],[-122.1745,47.75831000000001],[-122.17340999999999,47.758160000000004],[-122.17256999999998,47.758030000000005],[-122.17188999999999,47.75795],[-122.17117999999999,47.7579],[-122.16999,47.757799999999996],[-122.16915900000001,47.75776],[-122.167598,47.757833],[-122.16703,47.757915000000004],[-122.16629999999999,47.75811000000001],[-122.16613,47.758190000000006],[-122.16606,47.758190000000006],[-122.16570999999999,47.758190000000006],[-122.16414,47.758230000000005],[-122.16393,47.75829],[-122.16395,47.758230000000005],[-122.1635,47.75824],[-122.16328,47.75824],[-122.16328,47.758320000000005],[-122.16329,47.75851000000001],[-122.16331,47.75888],[-122.163365,47.759930000000004],[-122.1634,47.760644],[-122.16342,47.761161],[-122.16342999999999,47.76131000000001],[-122.16345,47.76167],[-122.1636,47.76167],[-122.16574999999999,47.761688],[-122.16635,47.76169],[-122.166424,47.76529000000001],[-122.16376,47.76527000000001],[-122.16382,47.76539000000001],[-122.16384,47.765440000000005],[-122.16393,47.765589000000006],[-122.16396,47.76563300000001],[-122.16409,47.765800000000006],[-122.16452000000001,47.76634000000001],[-122.16492000000001,47.76683800000001],[-122.16529999999999,47.76733000000001],[-122.1659,47.768080000000005],[-122.16636,47.768668000000005],[-122.16646999999999,47.768890000000006],[-122.16649,47.76900200000001],[-122.16651,47.76919000000001],[-122.16652300000001,47.76935400000001],[-122.16657,47.77039800000001],[-122.16657,47.77046],[-122.16657,47.77064],[-122.16658,47.77074],[-122.16661,47.771468],[-122.16663,47.772090000000006],[-122.16664,47.77251000000001],[-122.16667,47.77328],[-122.16667,47.773360000000004],[-122.16669999999999,47.774370000000005],[-122.16669999999999,47.77468699999999],[-122.16671,47.77510300000001],[-122.16672999999999,47.775766000000004],[-122.16673999999999,47.77593500000001],[-122.16675,47.776249],[-122.1641,47.77622],[-122.164097,47.77622],[-122.16403,47.77622],[-122.16235999999999,47.776208000000004],[-122.16207999999999,47.776210000000006],[-122.16126000000001,47.7762],[-122.16072,47.7762],[-122.15956,47.776204],[-122.15874399999998,47.77622],[-122.15867999999999,47.77622],[-122.1582,47.776210000000006],[-122.15754,47.776210000000006],[-122.15722000000001,47.776210000000006],[-122.15708,47.776210000000006],[-122.15691,47.776208000000004],[-122.15580999999999,47.776210000000006],[-122.15516999999998,47.776210000000006],[-122.15504999999999,47.77622],[-122.15476,47.776163000000004],[-122.154616,47.77613000000001],[-122.15446999999999,47.77610800000001],[-122.1542,47.776050000000005],[-122.15406,47.776024],[-122.15392999999999,47.776019000000005],[-122.15378999999999,47.776024],[-122.15364,47.77602],[-122.15314,47.77602],[-122.15286599999999,47.77602],[-122.15176999999998,47.776019000000005],[-122.14801,47.775977000000005],[-122.14554,47.77595000000001],[-122.14272,47.775920000000006],[-122.13501,47.775830000000006],[-122.13292999999999,47.77581000000001],[-122.13199,47.77579800000001],[-122.13183,47.775800000000004],[-122.13083999999999,47.77579000000001],[-122.12624,47.77573900000001],[-122.12512,47.77572800000001],[-122.12499,47.77573000000001],[-122.12392,47.77572000000001],[-122.12234999999998,47.77570000000001],[-122.12122000000001,47.775690000000004],[-122.12112,47.775690000000004],[-122.118601,47.77566],[-122.11838999999999,47.77566],[-122.11641,47.77566],[-122.11298,47.77566],[-122.11296,47.77566],[-122.11296,47.775461],[-122.11296999999999,47.77497],[-122.11296999999999,47.774194],[-122.11296999999999,47.773270000000004],[-122.11296,47.772169000000005],[-122.11295,47.77145],[-122.112944,47.771025],[-122.11292999999999,47.770390000000006],[-122.112944,47.76977000000001],[-122.11292999999999,47.76873000000001],[-122.11363,47.76873000000001],[-122.11359,47.76868],[-122.113582,47.76850000000001],[-122.11356,47.76840000000001],[-122.1135,47.76825000000001],[-122.11346,47.76817000000001],[-122.11336,47.76802000000001],[-122.11323,47.767828],[-122.11319,47.767720000000004],[-122.113155,47.76756],[-122.11315,47.76747],[-122.11315,47.76718],[-122.11317,47.766980000000004],[-122.11321000000001,47.76662],[-122.11322000000001,47.76661000000001],[-122.11319,47.76599000000001],[-122.113207,47.765600000000006],[-122.11319,47.76510000000001],[-122.11303,47.765110000000014],[-122.11240199999999,47.765113000000014],[-122.111984,47.76510700000001],[-122.11160000000001,47.76510100000001],[-122.11160000000001,47.764812000000006],[-122.11157,47.763721000000004],[-122.11157,47.76291000000001],[-122.11156000000001,47.762841],[-122.11156000000001,47.76283000000001],[-122.11156000000001,47.76281900000001],[-122.11156000000001,47.76280800000001],[-122.11156000000001,47.762800000000006],[-122.11156000000001,47.76279000000001],[-122.11156000000001,47.76278000000001],[-122.11156000000001,47.762766000000006],[-122.11155000000001,47.76275000000001],[-122.11155000000001,47.76274000000001],[-122.11155000000001,47.76273000000001],[-122.11154,47.76272300000001],[-122.11154,47.76271000000001],[-122.11153,47.76270000000001],[-122.11153,47.762690000000006],[-122.11152000000001,47.762682000000005],[-122.11152000000001,47.76267000000001],[-122.11151000000001,47.762660000000004],[-122.11150900000001,47.76265000000001],[-122.1115,47.762640000000005],[-122.1115,47.762631000000006],[-122.11149,47.762620000000005],[-122.11148,47.76261000000001],[-122.11148,47.762600000000006],[-122.11147,47.76259000000001],[-122.11146000000001,47.76258000000001],[-122.11145,47.76257000000001],[-122.11144,47.76256000000001],[-122.11143,47.76256000000001],[-122.11143,47.762547000000005],[-122.11142000000001,47.76254000000001],[-122.11141,47.76253000000001],[-122.1114,47.76252000000001],[-122.11139,47.762512000000015],[-122.11138,47.76250400000001],[-122.11137,47.76250000000001],[-122.11136,47.76249000000001],[-122.11134,47.762480000000004],[-122.11132,47.762465000000006],[-122.1113,47.76245000000001],[-122.11129000000001,47.762440000000005],[-122.11127,47.762440000000005],[-122.11126000000002,47.76243000000001],[-122.11125000000001,47.762423000000005],[-122.11124000000001,47.762417000000006],[-122.11121000000001,47.76241000000001],[-122.11120000000001,47.76240000000001],[-122.11119000000001,47.76239000000001],[-122.11118,47.76238600000001],[-122.11116000000001,47.76238000000001],[-122.11115000000001,47.76237200000001],[-122.11114,47.76236000000001],[-122.11113,47.76236000000001],[-122.11112000000001,47.76235000000001],[-122.11111000000001,47.76234000000001],[-122.11110000000001,47.762332000000015],[-122.11109,47.76232000000001],[-122.11108,47.762310000000014],[-122.11106600000001,47.762310000000014],[-122.11106000000001,47.76229800000001],[-122.11105,47.76229000000001],[-122.11104,47.762281],[-122.11103,47.76227000000001],[-122.11102000000001,47.762260000000005],[-122.11101000000001,47.76225000000001],[-122.111,47.762243000000005],[-122.11099,47.76223000000001],[-122.11099,47.762220000000006],[-122.11098,47.76221000000001],[-122.11097,47.762204000000004],[-122.11097,47.76219400000001],[-122.11096,47.76218000000001],[-122.11095,47.76217000000001],[-122.110948,47.76216300000001],[-122.11094,47.76215300000001],[-122.11094,47.76214300000001],[-122.11093,47.76213000000001],[-122.11093,47.76212200000001],[-122.11092000000001,47.76211100000001],[-122.11092000000001,47.76210000000001],[-122.110916,47.76209000000001],[-122.11091,47.76207900000001],[-122.110858,47.76177800000001],[-122.11088,47.76155500000001],[-122.11089,47.76151000000001],[-122.11092500000001,47.761463],[-122.11167,47.76147],[-122.11243999999999,47.76148],[-122.11543999999999,47.761514000000005],[-122.11555,47.76151000000001],[-122.11715000000001,47.76153000000001],[-122.11716000000001,47.76052000000001],[-122.117177,47.760204],[-122.11721000000001,47.759727],[-122.11722200000001,47.75967],[-122.11729000000001,47.75954],[-122.11743,47.75939],[-122.11752000000001,47.75932],[-122.1178,47.75916],[-122.1182,47.758956000000005],[-122.11894,47.758603],[-122.1194,47.758370000000006],[-122.11982,47.758174000000004],[-122.11995,47.758120000000005],[-122.12006,47.75808],[-122.12017699999998,47.75804],[-122.12036999999998,47.758009],[-122.12078999999999,47.757979999999996],[-122.12098999999999,47.75797],[-122.12105,47.755520000000004],[-122.121069,47.75479],[-122.12106999999999,47.75403],[-122.12105,47.75392],[-122.12096999999999,47.75354],[-122.12293999999999,47.75394],[-122.12429,47.75418],[-122.12452,47.75421],[-122.12468,47.754228],[-122.12545999999999,47.754239999999996],[-122.12725,47.754324],[-122.12833999999998,47.75433],[-122.12883699999998,47.75433],[-122.12944999999999,47.754339],[-122.12966999999999,47.75434],[-122.12993999999999,47.754365],[-122.13012,47.75438],[-122.13029999999999,47.754411],[-122.13058,47.75447],[-122.13076999999998,47.754522],[-122.13123,47.7547],[-122.13154,47.754819999999995],[-122.13169,47.754873999999994],[-122.13188,47.754923],[-122.13195,47.75493],[-122.13202999999999,47.75494],[-122.13211199999999,47.75494],[-122.13215,47.75494],[-122.13219,47.754929],[-122.13228,47.754887999999994],[-122.13234999999999,47.754839],[-122.13237999999998,47.75481],[-122.13248099999998,47.754582],[-122.13262,47.75416],[-122.13270999999999,47.753834],[-122.13274999999999,47.753659999999996],[-122.13277999999998,47.753350000000005],[-122.13279999999999,47.753197],[-122.13282999999998,47.753099],[-122.13286999999998,47.753],[-122.132924,47.75291000000001],[-122.13298999999999,47.75282],[-122.133055,47.75274],[-122.13321,47.752586],[-122.13337999999999,47.75246],[-122.13346999999999,47.7524],[-122.13355,47.75236],[-122.13369,47.75231000000001],[-122.13376999999998,47.75229],[-122.13386,47.752285],[-122.13396,47.75229],[-122.13413,47.75231000000001],[-122.13436,47.75235000000001],[-122.13445,47.75237500000001],[-122.1346,47.752430000000004],[-122.13504999999999,47.75262],[-122.13513999999999,47.75265],[-122.13522999999999,47.752677],[-122.13534999999999,47.752700000000004],[-122.13546999999998,47.752720000000004],[-122.1356,47.75273000000001],[-122.13589999999999,47.75274],[-122.1361,47.75274],[-122.13667,47.75273000000001],[-122.1378,47.75273000000001],[-122.13807999999999,47.75273000000001],[-122.13825,47.752721],[-122.13841,47.752707],[-122.13942999999999,47.752590000000005],[-122.13959,47.752590000000005],[-122.13986,47.7526],[-122.14005,47.75262],[-122.14020000000001,47.75264],[-122.14029000000001,47.75267],[-122.1404,47.75273000000001],[-122.14082,47.752967],[-122.14098,47.75305],[-122.14110000000001,47.75309],[-122.141232,47.75314],[-122.14142000000001,47.753189],[-122.14163,47.75322],[-122.14178,47.75324],[-122.14192000000001,47.75325],[-122.14232999999999,47.75326],[-122.14247999999999,47.75325],[-122.14276,47.753232000000004],[-122.14287999999999,47.75321],[-122.14301,47.753190000000004],[-122.14326000000001,47.75311000000001],[-122.1433,47.753101],[-122.14349,47.753011],[-122.14358,47.75296],[-122.14366000000001,47.75291000000001],[-122.14372999999999,47.75285],[-122.14383,47.75274],[-122.1439,47.752626],[-122.14392400000001,47.752570000000006],[-122.14395,47.752492000000004],[-122.14398,47.75229],[-122.14396,47.751762],[-122.14396900000001,47.751715000000004],[-122.14402000000001,47.751599999999996],[-122.14405900000001,47.751548],[-122.14410000000001,47.7515],[-122.14420700000001,47.75140699999999],[-122.14426000000002,47.751377],[-122.14432000000001,47.751338000000004],[-122.14449,47.75127],[-122.14456000000001,47.75125],[-122.14464000000001,47.75123],[-122.14477,47.751218],[-122.14489,47.75121],[-122.145012,47.75121],[-122.14512,47.751219999999996],[-122.14527199999999,47.751242],[-122.14556,47.751295],[-122.14578999999999,47.75135],[-122.146547,47.751551],[-122.146759,47.75159],[-122.14692000000001,47.751619999999996],[-122.14708,47.751639999999995],[-122.14723000000001,47.75165],[-122.14738,47.75165],[-122.14797,47.75165],[-122.1481,47.751659999999994],[-122.14855,47.75165],[-122.14876999999998,47.75165],[-122.14942,47.75163],[-122.14952000000001,47.751625999999995],[-122.14948,47.751096999999994],[-122.14997699999999,47.751079999999995],[-122.14996000000001,47.750251],[-122.149949,47.749682],[-122.15239999999999,47.74971000000001],[-122.15248699999998,47.749859],[-122.152515,47.74991000000001],[-122.15253999999999,47.74996],[-122.15259999999999,47.750164],[-122.15264099999999,47.750371],[-122.152654,47.750479999999996],[-122.15301,47.7505],[-122.15343999999999,47.750544],[-122.15355,47.750550000000004],[-122.15396999999999,47.75054],[-122.15401,47.751233],[-122.15431,47.751162],[-122.15522,47.750682],[-122.15552,47.750591],[-122.156016,47.7505],[-122.1565,47.750479999999996],[-122.15702999999999,47.75047],[-122.15755,47.75049],[-122.16028,47.7505],[-122.16031,47.747948],[-122.15982999999999,47.747659999999996],[-122.15939999999999,47.747293],[-122.15987999999999,47.74729],[-122.15995,47.747279999999996],[-122.15972999999998,47.74712],[-122.15970999999999,47.747109],[-122.15942999999999,47.74685],[-122.15916,47.746565000000004],[-122.15832999999998,47.745810000000006],[-122.15802999999998,47.745560000000005],[-122.15794,47.74550000000001],[-122.15781,47.74542],[-122.15768,47.745360000000005],[-122.15724,47.745200000000004],[-122.15696,47.74510000000001],[-122.15644999999999,47.744930000000004],[-122.15625,47.744839999999996],[-122.15605,47.74472],[-122.15552999999998,47.744279999999996],[-122.15435,47.74334],[-122.1542,47.7432],[-122.15373999999998,47.74286],[-122.15357999999999,47.742706000000005],[-122.1535,47.742622000000004],[-122.15339999999999,47.742490000000004],[-122.15328,47.74231000000001],[-122.15312,47.742000000000004],[-122.1531,47.741910000000004],[-122.15308999999999,47.74188099999999],[-122.15302999999999,47.741446999999994],[-122.15298999999999,47.741170000000004],[-122.15291699999999,47.74083],[-122.15289999999999,47.740770000000005],[-122.15279999999998,47.740608],[-122.15235999999999,47.740010000000005],[-122.15177999999999,47.739250000000006],[-122.15155,47.73895000000001],[-122.15142999999999,47.738800000000005],[-122.15093999999999,47.73823000000001],[-122.15068,47.737854],[-122.15043999999999,47.73754],[-122.15030399999999,47.73731000000001],[-122.15007999999999,47.737032000000006],[-122.14991,47.736854],[-122.14984,47.7368],[-122.14979,47.73675800000001],[-122.14943,47.736580000000004],[-122.14918,47.73642],[-122.14911000000001,47.736320000000006],[-122.14907,47.736343000000005],[-122.14905,47.73633000000001],[-122.148965,47.736290000000004],[-122.14858,47.73611800000001],[-122.148016,47.73588],[-122.14783,47.73579000000001],[-122.14696,47.73536500000001],[-122.14676,47.73528],[-122.1466,47.73520200000001],[-122.14647,47.73512100000001],[-122.14630799999999,47.734994],[-122.14612000000001,47.73482],[-122.14609,47.734786],[-122.14591,47.734657],[-122.14589,47.73464],[-122.14583999999999,47.734590000000004],[-122.14576,47.734482],[-122.14551,47.734100000000005],[-122.14546999999999,47.73402],[-122.14542,47.733900000000006],[-122.14536,47.733657],[-122.14533999999999,47.733560000000004],[-122.14532,47.733320000000006],[-122.14531,47.73311000000001],[-122.14535,47.73303000000001],[-122.14537999999999,47.73297000000001],[-122.14472,47.73295900000001],[-122.14403,47.732940000000006],[-122.14385,47.732940000000006],[-122.14381,47.733340000000005],[-122.14377999999999,47.734123000000004],[-122.14222000000001,47.734127],[-122.1422,47.734500000000004],[-122.1422,47.73466],[-122.14111000000001,47.73465],[-122.14096,47.734654],[-122.141,47.733670000000004],[-122.14019,47.733670000000004],[-122.140196,47.73281000000001],[-122.14028,47.732797000000005],[-122.14048,47.732760000000006],[-122.14058,47.732760000000006],[-122.14074,47.73275000000001],[-122.13986999999999,47.73187],[-122.14073799999998,47.731527],[-122.14036,47.73107],[-122.13994,47.73055000000001],[-122.1395,47.730021],[-122.13922000000001,47.729690000000005],[-122.14092000000001,47.72905000000001],[-122.14105,47.72901000000001],[-122.141411,47.72887800000001],[-122.14206999999999,47.72863000000001],[-122.14218,47.72860000000001],[-122.14229999999999,47.72858300000001],[-122.14232,47.72858000000001],[-122.14249,47.72857000000001],[-122.14273999999999,47.728581000000005],[-122.14285,47.72858900000001],[-122.14292,47.72859000000001],[-122.142903,47.728710000000014],[-122.14289699999999,47.72874300000001],[-122.14285,47.72889000000001],[-122.14283999999999,47.72898000000001],[-122.14281,47.729447],[-122.14304,47.729704000000005],[-122.14307,47.72971800000001],[-122.143091,47.72974000000001],[-122.14314,47.72986],[-122.14319,47.729960000000005],[-122.14329000000001,47.730098000000005],[-122.14336,47.730180000000004],[-122.14341,47.730239000000005],[-122.14357,47.73042],[-122.144011,47.730810000000005],[-122.14493,47.7316],[-122.14502999999999,47.73169],[-122.14511,47.731790000000004],[-122.14525,47.73198],[-122.14529,47.732040000000005],[-122.14533999999999,47.73217800000001],[-122.14536999999999,47.73232000000001],[-122.14537999999999,47.732380000000006],[-122.1454,47.73259000000001],[-122.14542300000001,47.73268],[-122.14566,47.73268],[-122.1488,47.73275000000001],[-122.14985,47.73277000000001],[-122.14989,47.73277000000001],[-122.15001,47.73277000000001],[-122.14992000000001,47.732670000000006],[-122.14893,47.7314],[-122.14862000000001,47.730661],[-122.14849,47.73024],[-122.148427,47.729580000000006],[-122.14846,47.72910000000001],[-122.1485,47.72891000000001],[-122.148545,47.72870000000001],[-122.14871,47.72810600000001],[-122.14922600000001,47.72652000000001],[-122.1495,47.725660000000005],[-122.15216,47.72574000000001],[-122.15237799999997,47.72574000000001],[-122.15238999999998,47.727109000000006],[-122.15317999999999,47.726991000000005],[-122.15467,47.72677100000001],[-122.15449,47.72574000000001],[-122.15321,47.725727000000006],[-122.15325,47.72392000000001],[-122.15596999999998,47.723954000000006],[-122.15594999999999,47.724650000000004],[-122.15592999999998,47.72576000000001],[-122.15662,47.725767000000005],[-122.1571,47.72577300000001],[-122.15772999999999,47.72578000000001],[-122.15925,47.72580000000001],[-122.15912,47.72953400000001],[-122.15933999999999,47.729541000000005],[-122.16023,47.729489],[-122.16108,47.72950000000001],[-122.161988,47.72952000000001],[-122.16252999999999,47.729560000000006],[-122.16314,47.729690000000005],[-122.1639,47.72999000000001],[-122.16404,47.730070000000005],[-122.16406300000001,47.730140000000006],[-122.16407,47.73021000000001],[-122.16407,47.73035000000001],[-122.16406300000001,47.730846],[-122.16402000000001,47.73215000000001],[-122.16402000000001,47.732760000000006],[-122.16402000000001,47.73281000000001],[-122.16403,47.73302],[-122.16403,47.733126000000006],[-122.16402000000001,47.733250000000005],[-122.16402000000001,47.733450000000005],[-122.164,47.733962000000005],[-122.1665,47.733956000000006],[-122.16717,47.73395000000001],[-122.16917,47.733962000000005],[-122.16968,47.73397000000001],[-122.169671,47.73391000000001],[-122.17134999999999,47.733900000000006],[-122.17237999999998,47.733906000000005],[-122.17556999999998,47.73392200000001],[-122.17579999999998,47.733920000000005],[-122.17577799999997,47.734255000000005],[-122.17576999999997,47.734370000000006],[-122.17570999999998,47.73554000000001],[-122.17567999999999,47.73601000000001],[-122.17562999999998,47.736380000000004],[-122.17560999999999,47.73669],[-122.17557999999998,47.73708],[-122.17557999999998,47.737162000000005],[-122.17556999999998,47.73731000000001],[-122.17556999999998,47.737743],[-122.17536699999998,47.74027],[-122.17533999999998,47.740990000000004],[-122.17529999999998,47.741625],[-122.17512999999998,47.74348],[-122.17503999999998,47.74524],[-122.17503999999998,47.745256000000005],[-122.17502999999998,47.74541000000001],[-122.17501999999999,47.745580000000004],[-122.17492999999999,47.745580000000004],[-122.17492999999999,47.745608000000004],[-122.1749,47.746050000000004],[-122.17486999999998,47.746570000000006],[-122.17483999999999,47.74702],[-122.17486,47.74702],[-122.17486,47.747130000000006],[-122.17483999999999,47.747386],[-122.17466999999999,47.74739],[-122.17466999999999,47.74747],[-122.174654,47.747734],[-122.17465,47.747777],[-122.17464199999999,47.747928],[-122.17463,47.74807800000001],[-122.17463,47.748127000000004],[-122.17446699999999,47.748126000000006],[-122.17443999999999,47.748450000000005],[-122.17436999999998,47.749340000000004],[-122.17428,47.750350000000005],[-122.17428,47.750383],[-122.17428,47.75041],[-122.17415,47.750959],[-122.1742,47.75096],[-122.17426,47.75096],[-122.174654,47.75096],[-122.17463,47.751852],[-122.17462,47.75263],[-122.17466999999999,47.75271000000001],[-122.17497699999998,47.75314],[-122.17499999999998,47.753170000000004],[-122.17500999999999,47.75321],[-122.17501999999999,47.75324],[-122.17502999999998,47.753276],[-122.17502999999998,47.753310000000006],[-122.17502999999998,47.75334],[-122.17502999999998,47.75338],[-122.17496999999999,47.753619],[-122.17492999999999,47.753764],[-122.17493199999998,47.75389],[-122.17492999999999,47.75413],[-122.17571999999998,47.75414],[-122.17572999999997,47.75454],[-122.17586299999999,47.75454],[-122.17546999999998,47.755488],[-122.17533999999998,47.755825],[-122.17532999999997,47.75586],[-122.17554999999999,47.755930000000006],[-122.17632999999998,47.756218000000004],[-122.17603999999999,47.75708899999999]]]},"name":"Woodinville"},{"boundary":{"type":"Polygon","coordinates":[[[-122.27096,47.77702],[-122.26831,47.77702],[-122.26705000000001,47.77702],[-122.26683799999999,47.77702],[-122.26557,47.77694],[-122.26457,47.77703],[-122.26445000000001,47.77704],[-122.26360000000001,47.77707],[-122.26333699999999,47.777065],[-122.26083,47.776921],[-122.26073,47.776921],[-122.26050000000001,47.776920000000004],[-122.25784,47.776920000000004],[-122.25391,47.776920000000004],[-122.25311500000001,47.776920000000004],[-122.24993,47.77682],[-122.24914000000001,47.776810000000005],[-122.24888,47.776810000000005],[-122.24743000000001,47.77684],[-122.24386000000001,47.776891],[-122.24276,47.77697500000001],[-122.24260000000001,47.776970000000006],[-122.24258,47.776966],[-122.24222000000002,47.77696],[-122.24204,47.77696],[-122.24193000000001,47.77695000000001],[-122.24184000000001,47.77695000000001],[-122.24170000000001,47.77695000000001],[-122.24162000000003,47.77695000000001],[-122.24144000000001,47.77695000000001],[-122.24131000000001,47.77695000000001],[-122.24106000000002,47.776942000000005],[-122.24093300000001,47.77694],[-122.24082000000001,47.77693800000001],[-122.24073,47.77695000000001],[-122.23954,47.776924],[-122.23917,47.77691000000001],[-122.23909,47.77691000000001],[-122.23863,47.776904],[-122.23836999999999,47.776900000000005],[-122.23821000000001,47.776891],[-122.2381,47.77689],[-122.23771,47.77687],[-122.23717,47.77686],[-122.2365,47.77685],[-122.23594,47.776848],[-122.23568,47.77684],[-122.23489000000001,47.77684],[-122.23411000000002,47.77682],[-122.23383,47.77681200000001],[-122.23304,47.77682],[-122.23292000000001,47.77682],[-122.23183,47.7768],[-122.22761000000001,47.776740000000004],[-122.22766000000001,47.77493200000001],[-122.2277,47.774034],[-122.22775,47.772580000000005],[-122.22778,47.77205000000001],[-122.22787,47.76951000000001],[-122.22788,47.76932000000001],[-122.22797,47.768420000000006],[-122.228,47.76800000000001],[-122.22792000000001,47.767990000000005],[-122.22793,47.767783],[-122.22794,47.767300000000006],[-122.22797,47.76657000000001],[-122.22809,47.76657000000001],[-122.22901,47.766571000000006],[-122.22908,47.76593800000001],[-122.22887999999999,47.76594000000001],[-122.22881799999999,47.76594000000001],[-122.228822,47.76554000000001],[-122.228822,47.765440000000005],[-122.22807999999999,47.765420000000006],[-122.22801,47.76541400000001],[-122.22804,47.76462],[-122.22806,47.764070000000004],[-122.22806,47.763870000000004],[-122.22811,47.762690000000006],[-122.22812,47.76227000000001],[-122.22812,47.76223000000001],[-122.22813,47.76200000000001],[-122.22813,47.761790000000005],[-122.22813699999999,47.76174],[-122.22814,47.761598000000006],[-122.22816,47.761029],[-122.22816999999999,47.76068],[-122.22818,47.760298000000006],[-122.22819,47.75997],[-122.2282,47.75956],[-122.22823,47.758745000000005],[-122.228231,47.75864],[-122.22824,47.75845],[-122.22824,47.75831000000001],[-122.22824,47.758250000000004],[-122.22825,47.75811300000001],[-122.22825,47.75799],[-122.22825,47.7579],[-122.228267,47.757479999999994],[-122.22827,47.757235],[-122.22832,47.755590000000005],[-122.22836,47.754577],[-122.22836999999998,47.754310000000004],[-122.22837999999999,47.75414],[-122.22837999999999,47.754109],[-122.22837999999999,47.75399],[-122.22838999999999,47.7539],[-122.22832999999999,47.75388099999999],[-122.22774,47.75369],[-122.227686,47.753675],[-122.22714,47.753510000000006],[-122.226278,47.753243],[-122.22594,47.75314],[-122.22586,47.75312],[-122.22576999999998,47.75309],[-122.22546999999999,47.753021],[-122.22525,47.752973000000004],[-122.22501,47.752930000000006],[-122.22468500000001,47.752879],[-122.22426000000002,47.75283],[-122.22327,47.752790000000005],[-122.22214,47.75276],[-122.221807,47.752750000000006],[-122.22138,47.75273000000001],[-122.22115000000001,47.75271000000001],[-122.22098,47.752700000000004],[-122.22079,47.752679],[-122.22079,47.752629],[-122.22079,47.75258],[-122.22079,47.75253000000001],[-122.22079,47.75248],[-122.22075,47.75247],[-122.22073999999999,47.752413000000004],[-122.22072999999999,47.75235500000001],[-122.22072,47.752300000000005],[-122.22082,47.751980999999994],[-122.22086999999999,47.751819999999995],[-122.22091,47.7517],[-122.22095300000001,47.75155],[-122.22102000000001,47.75145],[-122.22113,47.75139],[-122.22121000000001,47.751376],[-122.22166000000001,47.751369],[-122.22302,47.7514],[-122.22376999999999,47.75138],[-122.22389,47.75138],[-122.22395,47.750510000000006],[-122.22397,47.750150000000005],[-122.22415000000001,47.74777],[-122.22372999999999,47.747769],[-122.21929000000002,47.747737],[-122.21926000000002,47.747031],[-122.21919000000001,47.745601],[-122.21913,47.744217],[-122.21910000000001,47.743730000000006],[-122.21900600000001,47.74161],[-122.21895,47.740448],[-122.21887,47.738620000000004],[-122.21906400000002,47.738620000000004],[-122.22015,47.73866],[-122.22092,47.738679000000005],[-122.22202,47.73865000000001],[-122.22228,47.73866],[-122.22282999999999,47.73870000000001],[-122.222462,47.73830600000001],[-122.22236999999998,47.73820800000001],[-122.22141,47.73718],[-122.22076,47.736467],[-122.21913,47.73466],[-122.21898,47.734500000000004],[-122.21894,47.734456],[-122.2188,47.73431800000001],[-122.21876,47.73330000000001],[-122.218749,47.733177000000005],[-122.21876,47.73317000000001],[-122.2188,47.733160000000005],[-122.2189,47.733160000000005],[-122.21919000000001,47.733160000000005],[-122.21946000000001,47.73317000000001],[-122.22085,47.733177000000005],[-122.22176999999999,47.73319000000001],[-122.22189,47.73319000000001],[-122.22265,47.733200000000004],[-122.222854,47.733200000000004],[-122.22305,47.733200000000004],[-122.22387499999999,47.73321200000001],[-122.22399300000001,47.733213000000006],[-122.22583999999999,47.73324],[-122.2273,47.733267],[-122.22909,47.73330300000001],[-122.23135,47.733320000000006],[-122.23176000000001,47.73333000000001],[-122.23301000000001,47.733360000000005],[-122.23348,47.73337000000001],[-122.23400000000001,47.73337000000001],[-122.23440000000001,47.73337000000001],[-122.23460300000002,47.733360000000005],[-122.23492000000002,47.733360000000005],[-122.23649,47.73337000000001],[-122.23733,47.733380000000004],[-122.24039,47.733410000000006],[-122.24061000000002,47.733410000000006],[-122.240879,47.733380000000004],[-122.24106000000002,47.73334500000001],[-122.24139000000001,47.73326],[-122.24150000000002,47.73324],[-122.24154000000001,47.733225000000004],[-122.24165000000002,47.73319000000001],[-122.24180000000001,47.73312500000001],[-122.24191000000002,47.733070000000005],[-122.24203,47.73299000000001],[-122.24216000000001,47.73288],[-122.24220000000001,47.732836000000006],[-122.24226000000002,47.73277000000001],[-122.24242000000001,47.73252000000001],[-122.24254,47.73237000000001],[-122.24277699999999,47.73209800000001],[-122.2428,47.732054000000005],[-122.24295000000001,47.732054000000005],[-122.24313000000001,47.732046000000004],[-122.24342000000001,47.732020000000006],[-122.24438900000001,47.731885],[-122.24521000000001,47.731770000000004],[-122.24516000000001,47.73163],[-122.24515000000001,47.7316],[-122.24496000000002,47.730900000000005],[-122.24480000000001,47.730320000000006],[-122.24466700000002,47.729850000000006],[-122.24463100000001,47.72972000000001],[-122.24452000000002,47.72935000000001],[-122.24414000000002,47.72803000000001],[-122.24463000000002,47.728061000000004],[-122.24513,47.72816600000001],[-122.24538,47.72824000000001],[-122.24572,47.72836600000001],[-122.2459,47.72843900000001],[-122.24615000000001,47.72855000000001],[-122.24640000000001,47.72859000000001],[-122.24651000000001,47.72859000000001],[-122.24662000000002,47.72858300000001],[-122.24682200000001,47.72854500000001],[-122.24694000000001,47.72845500000001],[-122.24706000000002,47.728330000000014],[-122.24714000000002,47.72819000000001],[-122.24744000000001,47.727650000000004],[-122.24752000000002,47.727590000000006],[-122.24764000000002,47.727520000000005],[-122.24777,47.727470000000004],[-122.24783000000001,47.727457],[-122.24787,47.727450000000005],[-122.247971,47.72744],[-122.24812000000001,47.727450000000005],[-122.24819000000001,47.727455000000006],[-122.24824000000001,47.727470000000004],[-122.24833,47.72750200000001],[-122.24851000000001,47.727590000000006],[-122.248575,47.72762],[-122.24879,47.72777000000001],[-122.24891000000001,47.72787],[-122.24906000000001,47.72795000000001],[-122.24913000000001,47.727970000000006],[-122.24923000000001,47.72800000000001],[-122.24936000000001,47.72802000000001],[-122.24950000000001,47.72803000000001],[-122.24972000000001,47.728027000000004],[-122.24985000000001,47.72800000000001],[-122.24992000000002,47.727984],[-122.25006,47.727925000000006],[-122.25015,47.72787],[-122.25021000000001,47.72782],[-122.25026000000001,47.727768000000005],[-122.25032999999999,47.72766],[-122.25052000000001,47.72733000000001],[-122.25054,47.727292000000006],[-122.25056000000001,47.727230000000006],[-122.25059,47.727109000000006],[-122.25061000000001,47.72684],[-122.25059900000001,47.726760000000006],[-122.2505,47.72632300000001],[-122.25048,47.726237000000005],[-122.25048,47.72619000000001],[-122.25049,47.726020000000005],[-122.25049,47.72597000000001],[-122.25055,47.72557000000001],[-122.25066000000001,47.72557000000001],[-122.25553,47.725620000000006],[-122.25635,47.72563000000001],[-122.25690900000001,47.72563500000001],[-122.25748,47.725640000000006],[-122.25807999999999,47.725646000000005],[-122.2588,47.72565000000001],[-122.25982,47.725660000000005],[-122.25986999999999,47.72570000000001],[-122.25988,47.72589000000001],[-122.25986999999999,47.72590800000001],[-122.25984,47.72596000000001],[-122.25961000000001,47.72619100000001],[-122.25936999999999,47.72639000000001],[-122.25982,47.726400000000005],[-122.26154000000001,47.72641000000001],[-122.26187,47.726400000000005],[-122.26185000000001,47.726470000000006],[-122.26174,47.726560000000006],[-122.26161000000002,47.72665000000001],[-122.26164000000001,47.72672000000001],[-122.26354,47.72932000000001],[-122.26384,47.73152],[-122.26449000000001,47.732997000000005],[-122.26459000000001,47.73321000000001],[-122.26459000000001,47.733250000000005],[-122.26460000000002,47.73328],[-122.26460000000002,47.73331100000001],[-122.26460000000002,47.73337000000001],[-122.26459000000001,47.73339000000001],[-122.264598,47.733410000000006],[-122.26460500000002,47.733433000000005],[-122.26461000000002,47.73346],[-122.26461000000002,47.73348],[-122.26461000000002,47.73351000000001],[-122.26462000000002,47.733523000000005],[-122.26463000000001,47.733540000000005],[-122.26464000000001,47.73357000000001],[-122.26465000000002,47.73358],[-122.26465000000002,47.733610000000006],[-122.26465000000002,47.733630000000005],[-122.26465000000002,47.73364],[-122.26465000000002,47.73368],[-122.26473,47.733747],[-122.26476000000001,47.733790000000006],[-122.26478,47.733850000000004],[-122.26479,47.73389],[-122.26482000000001,47.733940000000004],[-122.26486000000001,47.734010000000005],[-122.26491000000001,47.734088],[-122.26494000000001,47.734122000000006],[-122.26495000000001,47.73413600000001],[-122.26498000000001,47.734190000000005],[-122.265,47.734218000000006],[-122.26502,47.734254],[-122.26504,47.73428],[-122.26506,47.734320000000004],[-122.26508,47.734370000000006],[-122.2651,47.734428],[-122.26511,47.73449],[-122.26511,47.73454],[-122.26512000000001,47.734569],[-122.26511,47.7346],[-122.26512000000001,47.73463],[-122.26512000000001,47.73466],[-122.26512000000001,47.734691],[-122.26512000000001,47.73472],[-122.265102,47.734787],[-122.265095,47.73482],[-122.2651,47.73484],[-122.2651,47.734863],[-122.26509,47.73488],[-122.26509,47.7349],[-122.26508,47.734930000000006],[-122.26507,47.73496],[-122.26507,47.73498],[-122.26507,47.73501000000001],[-122.26507,47.735037000000005],[-122.265057,47.735060000000004],[-122.26504,47.73509000000001],[-122.26504,47.73511000000001],[-122.26504,47.73513000000001],[-122.26504,47.73514000000001],[-122.26504,47.73516000000001],[-122.26503,47.73518000000001],[-122.26503,47.73520500000001],[-122.26502,47.735220000000005],[-122.265012,47.735254000000005],[-122.26501,47.735290000000006],[-122.26501,47.73534900000001],[-122.26501,47.735400000000006],[-122.26502,47.73545000000001],[-122.26503,47.73550000000001],[-122.265049,47.73555000000001],[-122.26506,47.735600000000005],[-122.26507,47.73563000000001],[-122.265091,47.73566],[-122.26511,47.735696000000004],[-122.26512000000001,47.73572000000001],[-122.26513,47.73573100000001],[-122.265139,47.735760000000006],[-122.26514900000001,47.735780000000005],[-122.26517199999999,47.735814000000005],[-122.26520000000001,47.73586],[-122.26522000000001,47.73588],[-122.26524,47.735890000000005],[-122.265262,47.73591000000001],[-122.26529000000001,47.73593000000001],[-122.26532999999999,47.735949000000005],[-122.26536,47.73597000000001],[-122.2654,47.736000000000004],[-122.26544,47.73603000000001],[-122.26547,47.73606],[-122.26551,47.736090000000004],[-122.26556000000001,47.736126000000006],[-122.26561000000001,47.73617300000001],[-122.26566700000001,47.73622],[-122.26571,47.73626],[-122.26575,47.73630000000001],[-122.26579,47.73633500000001],[-122.26583,47.73637000000001],[-122.26585,47.73641000000001],[-122.26586999999999,47.73644],[-122.2659,47.73648],[-122.26593,47.73651800000001],[-122.26596,47.736548000000006],[-122.26597,47.736580000000004],[-122.26601000000001,47.73662],[-122.26603,47.736643],[-122.26605,47.736643],[-122.26609,47.736694],[-122.26610000000001,47.736700000000006],[-122.26613,47.736720000000005],[-122.26616000000001,47.73675000000001],[-122.26620000000001,47.7368],[-122.26622800000001,47.736830000000005],[-122.26625000000001,47.73686],[-122.26627,47.736900000000006],[-122.26629000000001,47.736920000000005],[-122.26632000000001,47.73695000000001],[-122.26634,47.736987],[-122.26635,47.737],[-122.26636300000001,47.73702],[-122.26638,47.737041],[-122.266395,47.73706],[-122.26642000000001,47.73708],[-122.26643,47.737100000000005],[-122.26644,47.73711000000001],[-122.26646000000001,47.73713800000001],[-122.26647,47.73716],[-122.266485,47.73718],[-122.26650000000001,47.73722],[-122.26652000000001,47.73724],[-122.26653,47.737251],[-122.26653900000001,47.73727],[-122.26654,47.73728],[-122.26655000000001,47.737294],[-122.26656000000001,47.73731000000001],[-122.26656000000001,47.737320000000004],[-122.26658,47.737334000000004],[-122.26659000000001,47.737344],[-122.26662000000002,47.73736],[-122.26665000000001,47.737366],[-122.26668000000001,47.737369],[-122.2667,47.737373000000005],[-122.26672900000001,47.737387],[-122.26674799999999,47.7374],[-122.26677,47.737410000000004],[-122.2668,47.737418000000005],[-122.26683,47.73743],[-122.26686000000001,47.73745],[-122.26687,47.737458000000004],[-122.26691000000001,47.73748],[-122.26693,47.73749],[-122.26695000000001,47.73749],[-122.26698,47.737517000000004],[-122.26699900000001,47.73752],[-122.26702000000002,47.73753000000001],[-122.26705000000001,47.73756],[-122.26707,47.73758],[-122.26710000000001,47.737593000000004],[-122.26712000000002,47.737603],[-122.26714000000001,47.737610000000004],[-122.26717000000001,47.73763],[-122.26719000000001,47.73764],[-122.26722000000002,47.73766],[-122.26724000000002,47.737679],[-122.26725000000002,47.737691],[-122.26727000000001,47.737700000000004],[-122.2673,47.737716000000006],[-122.26731000000001,47.737730000000006],[-122.26734,47.73774],[-122.26735000000001,47.737750000000005],[-122.26737,47.737750000000005],[-122.26740000000001,47.737770000000005],[-122.26743,47.737790000000004],[-122.26741000000001,47.737904],[-122.26734,47.73832000000001],[-122.26633,47.73866],[-122.26620000000001,47.73865000000001],[-122.26606000000001,47.738607],[-122.26589,47.738580000000006],[-122.26572999999999,47.73859100000001],[-122.26558,47.738645000000005],[-122.26545,47.73871000000001],[-122.26546,47.73874000000001],[-122.26546,47.73877000000001],[-122.26547,47.73879000000001],[-122.26548,47.73883000000001],[-122.26549,47.73886],[-122.26549,47.738886],[-122.2655,47.73891000000001],[-122.2655,47.73893100000001],[-122.265496,47.73895000000001],[-122.26549,47.738980000000005],[-122.26549,47.73901000000001],[-122.26548,47.739050000000006],[-122.26547,47.73908],[-122.265468,47.73911100000001],[-122.265455,47.739160000000005],[-122.265451,47.73919000000001],[-122.26545,47.73922],[-122.26545,47.739250000000006],[-122.26545,47.739277],[-122.26544,47.73931000000001],[-122.265442,47.739340000000006],[-122.26544,47.739377000000005],[-122.26544,47.73941000000001],[-122.26544,47.73944],[-122.26544,47.73946],[-122.26543,47.73948],[-122.26544,47.73951000000001],[-122.26544,47.73953000000001],[-122.26544,47.739560000000004],[-122.265455,47.73959500000001],[-122.26546400000001,47.739626],[-122.26547,47.739650000000005],[-122.26548,47.739683],[-122.265487,47.739700000000006],[-122.26549,47.73973000000001],[-122.26549,47.73975000000001],[-122.26549,47.73978],[-122.26548,47.739790000000006],[-122.26547,47.739810000000006],[-122.26546,47.739830000000005],[-122.265454,47.739846],[-122.26545,47.73987],[-122.265442,47.739900000000006],[-122.26544,47.73993000000001],[-122.26543,47.73997000000001],[-122.26543,47.74],[-122.26542,47.74004],[-122.26541,47.740057],[-122.2654,47.74009],[-122.26539,47.740100000000005],[-122.265374,47.74013000000001],[-122.26536999999999,47.740145000000005],[-122.26536,47.740170000000006],[-122.26535,47.74018],[-122.26534,47.740190000000005],[-122.26532,47.74022],[-122.26530699999999,47.74024],[-122.26529000000001,47.740274],[-122.26527,47.74031000000001],[-122.26526000000001,47.74033000000001],[-122.26526000000001,47.74034],[-122.26525000000001,47.74036],[-122.26525000000001,47.74038],[-122.26524,47.7404],[-122.26522000000001,47.740414],[-122.26521000000001,47.74042],[-122.26519,47.74043],[-122.26518,47.740442],[-122.26516000000001,47.740458000000004],[-122.26515,47.74047],[-122.26512000000001,47.740493],[-122.2651,47.740520000000004],[-122.26508,47.74053500000001],[-122.26506,47.74056],[-122.26505,47.740570000000005],[-122.26504,47.740610000000004],[-122.26503,47.740628],[-122.26502,47.74065],[-122.265,47.74067],[-122.26498000000001,47.740704],[-122.26496000000002,47.74074],[-122.26494000000001,47.740766],[-122.26490000000001,47.740770000000005],[-122.26482000000001,47.740770000000005],[-122.26481000000001,47.740790000000004],[-122.26480000000001,47.740809],[-122.26479,47.740837],[-122.26478,47.74086],[-122.26477,47.74088],[-122.26476000000001,47.7409],[-122.26476000000001,47.74091000000001],[-122.26475,47.74094],[-122.26474,47.74096],[-122.26472000000001,47.741],[-122.26466000000002,47.74109],[-122.26465000000002,47.74112],[-122.26464000000001,47.741130000000005],[-122.26461800000001,47.741170000000004],[-122.26460000000002,47.74121],[-122.26458000000001,47.74126],[-122.26457,47.74129],[-122.26456000000002,47.741321],[-122.26455300000002,47.74134],[-122.26455000000001,47.74137],[-122.26455000000001,47.7414],[-122.26455600000001,47.74143],[-122.26455300000002,47.74145],[-122.26455000000001,47.741479999999996],[-122.26456000000002,47.741537],[-122.26456000000002,47.74156],[-122.26457,47.7416],[-122.26458000000001,47.74163],[-122.26459000000001,47.741668],[-122.26460000000002,47.741730000000004],[-122.26460700000001,47.74178],[-122.26462000000002,47.741879999999995],[-122.26463000000001,47.742007],[-122.26463000000001,47.742019000000006],[-122.26464000000001,47.74204],[-122.26464000000001,47.742070000000005],[-122.26464000000001,47.742098000000006],[-122.26463000000001,47.74213000000001],[-122.26462000000002,47.74215000000001],[-122.26462000000002,47.742180000000005],[-122.26460000000002,47.742200000000004],[-122.26459000000001,47.74222],[-122.26459000000001,47.74224],[-122.26458000000001,47.742284],[-122.26457300000001,47.74233000000001],[-122.26457,47.742380000000004],[-122.26456000000002,47.7424],[-122.26454000000001,47.742430000000006],[-122.26454000000001,47.74246],[-122.26453000000001,47.742489],[-122.26453000000001,47.74251000000001],[-122.26453000000001,47.74253600000001],[-122.26452000000002,47.74255000000001],[-122.26451000000002,47.742580000000004],[-122.26450000000001,47.7426],[-122.26448,47.742630000000005],[-122.26446000000001,47.742650000000005],[-122.26445000000001,47.742670000000004],[-122.264438,47.74269],[-122.26443,47.742700000000006],[-122.26441000000001,47.742720000000006],[-122.26439,47.74275000000001],[-122.26436000000001,47.74277500000001],[-122.26432000000001,47.742810000000006],[-122.26428000000001,47.742850000000004],[-122.26424100000001,47.742883],[-122.26421000000002,47.74291300000001],[-122.26418100000001,47.74295000000001],[-122.26416800000001,47.742968000000005],[-122.264148,47.742990000000006],[-122.26411000000002,47.743030000000005],[-122.26410000000001,47.74304],[-122.26406000000001,47.74308],[-122.26403,47.743106000000004],[-122.26400000000001,47.743137000000004],[-122.26397,47.743170000000006],[-122.26394,47.743193000000005],[-122.26392000000001,47.74322],[-122.2639,47.74325],[-122.26389,47.743279],[-122.26386000000001,47.74331000000001],[-122.26384,47.74334],[-122.26382000000001,47.743370000000006],[-122.2638,47.7434],[-122.26378,47.74342],[-122.26377,47.743448],[-122.26376,47.74347],[-122.26372,47.743520000000004],[-122.26368000000001,47.743579000000004],[-122.26367,47.743590000000005],[-122.26365000000001,47.743618000000005],[-122.26363,47.74364],[-122.26361000000001,47.743659],[-122.26359000000001,47.74368],[-122.26357,47.74371300000001],[-122.26355000000001,47.74374],[-122.26353,47.743770000000005],[-122.26351000000001,47.743790000000004],[-122.26350000000001,47.743801],[-122.26348,47.74382],[-122.26342000000001,47.74389],[-122.26338,47.743950000000005],[-122.26335,47.744008],[-122.26333,47.744046],[-122.26332000000001,47.74409],[-122.263309,47.744130000000006],[-122.26331,47.744144],[-122.26331,47.74416],[-122.2633,47.744170000000004],[-122.26329000000001,47.74418],[-122.26328000000001,47.7442],[-122.26327,47.74421],[-122.26325900000002,47.744226],[-122.26325000000001,47.74424],[-122.26325000000001,47.74425],[-122.26324000000001,47.744276],[-122.26323000000001,47.74429],[-122.26322000000002,47.74431200000001],[-122.26320000000001,47.744337],[-122.26317900000001,47.74436],[-122.26316000000001,47.744382],[-122.26314,47.74442],[-122.26312000000001,47.74446],[-122.26333,47.74445],[-122.26336,47.74457],[-122.26339,47.744679999999995],[-122.2634,47.7448],[-122.26342000000001,47.74487],[-122.26346000000001,47.74506],[-122.26351000000001,47.745297],[-122.26343,47.745380000000004],[-122.26326000000002,47.74559000000001],[-122.262817,47.746100000000006],[-122.26279,47.746182000000005],[-122.26045,47.748253000000005],[-122.25824,47.75022],[-122.25832999999999,47.75155],[-122.25842999999999,47.751673],[-122.25837999999999,47.751717],[-122.25790300000001,47.752120000000005],[-122.25758,47.752410000000005],[-122.25758,47.75291000000001],[-122.25758,47.7533],[-122.25758,47.753325000000004],[-122.25758,47.753530000000005],[-122.25758,47.753710000000005],[-122.25758,47.75407],[-122.25758,47.75437],[-122.25757,47.755229],[-122.25757,47.75536],[-122.25768000000001,47.75535800000001],[-122.25907799999999,47.75545],[-122.26035,47.75553000000001],[-122.26033,47.756310000000006],[-122.26051000000001,47.75632],[-122.26164400000002,47.75642],[-122.26238,47.7565],[-122.26265000000001,47.756510000000006],[-122.26274,47.756510000000006],[-122.26321000000002,47.756550000000004],[-122.26347,47.75658],[-122.26392000000001,47.756616],[-122.26531,47.75672],[-122.26723000000001,47.755708000000006],[-122.26835,47.755720000000004],[-122.268682,47.756550000000004],[-122.26908,47.75735],[-122.26927,47.75732],[-122.26944,47.757279999999994],[-122.26959000000001,47.757239999999996],[-122.26977,47.75719],[-122.26986000000001,47.75715],[-122.27005,47.757079999999995],[-122.27024,47.756997],[-122.27042,47.7569],[-122.27053,47.75684],[-122.27064,47.75677],[-122.27072999999999,47.756709],[-122.27071099999999,47.7571],[-122.27065,47.75831000000001],[-122.27059,47.75932],[-122.27055,47.76011500000001],[-122.27053,47.76055000000001],[-122.27049,47.761161],[-122.27029999999999,47.761189],[-122.27022000000001,47.760940000000005],[-122.26941000000001,47.76112800000001],[-122.26954,47.76137000000001],[-122.26962000000002,47.7614],[-122.27002999999999,47.76153000000001],[-122.26996000000001,47.761596000000004],[-122.27046999999999,47.76173000000001],[-122.27047999999999,47.76265000000001],[-122.27047999999999,47.76275000000001],[-122.27048699999999,47.764022000000004],[-122.270496,47.76534000000001],[-122.2705,47.76578000000001],[-122.2705,47.766225000000006],[-122.2705,47.76650800000001],[-122.27052,47.76855000000001],[-122.26852000000001,47.76851000000001],[-122.26749000000001,47.76850900000001],[-122.26756000000002,47.76852000000001],[-122.26787,47.76857000000001],[-122.26805,47.76861500000001],[-122.26818,47.768660000000004],[-122.26848,47.76878000000001],[-122.26863,47.76885000000001],[-122.26877999999999,47.76892000000001],[-122.26891,47.76900500000001],[-122.26908,47.76912000000001],[-122.26917,47.76919000000001],[-122.269315,47.76933000000001],[-122.26939,47.769420000000004],[-122.26945,47.76950000000001],[-122.26954,47.76964],[-122.26962000000002,47.769821],[-122.26966000000002,47.76995000000001],[-122.26969000000001,47.77006],[-122.26972,47.770250000000004],[-122.26971,47.770482],[-122.26968000000001,47.770919000000006],[-122.26967,47.770961],[-122.26967,47.770996000000004],[-122.26962000000002,47.771510000000006],[-122.26962000000002,47.77156],[-122.26959000000001,47.771879999999996],[-122.26956000000001,47.772124000000005],[-122.26955000000001,47.77228],[-122.26952000000001,47.772648000000004],[-122.26951000000001,47.77271000000001],[-122.26949,47.77294500000001],[-122.26948,47.77311000000001],[-122.26947,47.77318],[-122.26945,47.77348],[-122.26945,47.773725000000006],[-122.26947,47.773950000000006],[-122.26951000000001,47.77408],[-122.26956000000001,47.77424],[-122.26961000000001,47.77436],[-122.26966000000002,47.774473],[-122.26985,47.774910000000006],[-122.26998,47.775180000000006],[-122.27008,47.77544],[-122.27019,47.775760000000005],[-122.27027,47.77599000000001],[-122.27029,47.776033000000005],[-122.27039599999999,47.77635000000001],[-122.27046,47.776495000000004],[-122.27055,47.77664],[-122.27066,47.776770000000006],[-122.27076999999998,47.77688],[-122.27096,47.77702]]]},"name":"Kenmore"},{"boundary":{"type":"Polygon","coordinates":[[[-122.25488,47.479163],[-122.25398,47.479639999999996],[-122.25372,47.4795],[-122.25337999999999,47.4794],[-122.25337999999999,47.47937],[-122.25242999999999,47.479248],[-122.25167,47.47942],[-122.24971000000001,47.47987],[-122.24822000000002,47.47989],[-122.24582000000001,47.479879999999994],[-122.24579,47.481089999999995],[-122.24558,47.481089999999995],[-122.24559,47.48206],[-122.24559900000001,47.48223],[-122.24509,47.482079999999996],[-122.24400000000001,47.48179999999999],[-122.24316000000002,47.48155],[-122.24245,47.481438999999995],[-122.24170400000001,47.481378],[-122.24074,47.481328],[-122.23991600000001,47.481359999999995],[-122.23893,47.481415],[-122.238,47.481449999999995],[-122.23703400000001,47.481415],[-122.23616000000001,47.48123999999999],[-122.2353,47.48107999999999],[-122.23453,47.48083],[-122.23403,47.480599999999995],[-122.23366000000001,47.480439999999994],[-122.23361000000001,47.480489999999996],[-122.23345,47.480619999999995],[-122.23341,47.48065],[-122.23335,47.48071],[-122.23317,47.48065999999999],[-122.23261000000001,47.48054],[-122.23239,47.4805],[-122.23234,47.480489999999996],[-122.23228,47.48047],[-122.23226000000001,47.48047999999999],[-122.23221000000001,47.48046699999999],[-122.23215,47.48047999999999],[-122.23216000000001,47.480489999999996],[-122.23215,47.48057],[-122.23211,47.480669999999996],[-122.23144,47.4821],[-122.23121000000002,47.4826],[-122.23117,47.482710000000004],[-122.23091000000001,47.48267],[-122.23072,47.48265],[-122.23102000000002,47.48287],[-122.23150500000001,47.483259999999994],[-122.23163000000001,47.48336],[-122.23236999999999,47.48395],[-122.23262900000002,47.484140999999994],[-122.23295,47.484399999999994],[-122.23296,47.484652999999994],[-122.23295,47.48644099999999],[-122.23109000000001,47.48641],[-122.2304,47.48641],[-122.229991,47.48641],[-122.22788,47.48637],[-122.22756000000001,47.486362],[-122.22756000000001,47.486576],[-122.22755000000001,47.48693],[-122.22755000000001,47.48701],[-122.22733,47.48701],[-122.22614300000001,47.487018],[-122.22614,47.48719],[-122.22556999999999,47.48719],[-122.22556999999999,47.48783999999999],[-122.22606999999999,47.48783999999999],[-122.22734,47.48783999999999],[-122.22734,47.487939999999995],[-122.22734,47.48805],[-122.22735,47.48816],[-122.22576999999998,47.488169],[-122.22532,47.488171],[-122.22532,47.48841],[-122.22532,47.48848399999999],[-122.22532,47.48858],[-122.22532,47.48889],[-122.22645,47.4889],[-122.22643,47.48936],[-122.22645,47.489391],[-122.22681,47.489386999999994],[-122.227,47.48939],[-122.22736,47.489382],[-122.22756500000001,47.48938],[-122.22757,47.489502],[-122.22757,47.48959],[-122.22757,47.48997],[-122.22759,47.490832000000005],[-122.22759,47.49088],[-122.22744,47.490880999999995],[-122.22623,47.49092],[-122.22606,47.490930000000006],[-122.22555,47.49092],[-122.22447,47.490926],[-122.22420000000001,47.49092],[-122.22407,47.49091500000001],[-122.22301,47.49089],[-122.22292999999999,47.49088],[-122.22292999999999,47.49124],[-122.22292999999999,47.4915],[-122.22292999999999,47.491679999999995],[-122.222927,47.49179],[-122.22292999999999,47.491937],[-122.22292999999999,47.492470000000004],[-122.22292999999999,47.49315800000001],[-122.222927,47.493486999999995],[-122.22236999999998,47.49347],[-122.2221,47.49347],[-122.21995900000002,47.49342],[-122.21989,47.493424],[-122.21989,47.493520000000004],[-122.21989,47.493590000000005],[-122.21989,47.49374],[-122.21989,47.49389],[-122.21997,47.49389],[-122.21997,47.494275],[-122.21997,47.494330000000005],[-122.22005,47.494330000000005],[-122.22005,47.49452],[-122.22017,47.49452],[-122.22017,47.4947],[-122.21997,47.4947],[-122.21986900000002,47.49469],[-122.21949000000001,47.49469],[-122.21947,47.49469],[-122.21945000000001,47.49468099999999],[-122.21951000000001,47.4949],[-122.21962000000002,47.495250000000006],[-122.21987,47.49526],[-122.22043,47.495270000000005],[-122.22197,47.49531000000001],[-122.22197,47.49539000000001],[-122.22196000000001,47.49566],[-122.22236,47.495672000000006],[-122.22234999999999,47.495844],[-122.22234399999999,47.496317000000005],[-122.22233599999998,47.496659],[-122.22231,47.496659],[-122.22229999999999,47.49689],[-122.22166000000001,47.49691000000001],[-122.22166000000001,47.496990000000004],[-122.22167,47.49708],[-122.22162000000002,47.49708],[-122.22157,47.497080999999994],[-122.22148,47.49708],[-122.22145,47.49708],[-122.22132,47.49707],[-122.22120000000001,47.49707],[-122.22120100000001,47.49727],[-122.22120000000001,47.49743],[-122.22105,47.49743],[-122.22104,47.49764],[-122.22105,47.49778],[-122.22092,47.49778],[-122.22013,47.497710000000005],[-122.21994000000001,47.497730000000004],[-122.21995500000001,47.49777],[-122.21986000000001,47.4978],[-122.21993,47.49795],[-122.21987,47.49796],[-122.21988,47.49801600000001],[-122.21981000000001,47.498020000000004],[-122.21983300000001,47.498097],[-122.21984,47.49813700000001],[-122.21986000000001,47.498180000000005],[-122.21974,47.49817000000001],[-122.21975,47.49833000000001],[-122.21981000000001,47.49833000000001],[-122.21986500000001,47.498489],[-122.21954000000001,47.498520000000006],[-122.21950000000001,47.49870000000001],[-122.21935,47.49924],[-122.21929000000002,47.499370000000006],[-122.21924000000001,47.49945],[-122.21918000000001,47.499531000000005],[-122.21909000000001,47.499612000000006],[-122.21902000000001,47.499655000000004],[-122.21888,47.49971000000001],[-122.21886,47.499749],[-122.218852,47.49976],[-122.21885,47.4998],[-122.21887,47.499990000000004],[-122.218897,47.500056],[-122.21892000000001,47.500083999999994],[-122.219018,47.50016],[-122.21906400000002,47.500222],[-122.21917,47.500392000000005],[-122.21921200000001,47.500571],[-122.21863,47.50032],[-122.21673,47.50082],[-122.21623000000001,47.50032],[-122.21103000000001,47.50092],[-122.20503,47.50422],[-122.20477,47.50427],[-122.20399,47.5044],[-122.2028,47.50461],[-122.20239,47.505230000000005],[-122.20333,47.508520000000004],[-122.20663,47.51422099999999],[-122.2097,47.51661],[-122.21203,47.51842],[-122.21090000000001,47.520050000000005],[-122.21085000000001,47.52013000000001],[-122.21078,47.520244],[-122.21069000000001,47.52037500000001],[-122.21064000000001,47.520455000000005],[-122.21058000000001,47.520540000000004],[-122.21053,47.520623],[-122.21048,47.520700000000005],[-122.21043,47.52078],[-122.21038,47.52086],[-122.21031500000001,47.52096],[-122.21027000000001,47.52103],[-122.21022000000002,47.52111000000001],[-122.21016000000002,47.521193000000004],[-122.21011300000002,47.521273],[-122.21006000000001,47.521358000000006],[-122.21001000000001,47.52144],[-122.20995,47.52152],[-122.2099,47.521602],[-122.20985,47.521679999999996],[-122.2098,47.521770000000004],[-122.20969000000001,47.521930000000005],[-122.20964000000001,47.52201000000001],[-122.20958,47.52209500000001],[-122.20953,47.52217000000001],[-122.20948,47.52226],[-122.20943,47.52234000000001],[-122.2094,47.522380000000005],[-122.20938,47.522420000000004],[-122.20932,47.522501000000005],[-122.20926900000002,47.52258500000001],[-122.20922000000002,47.52266],[-122.20916000000001,47.522749000000005],[-122.20911000000001,47.522830000000006],[-122.20906000000001,47.52291000000001],[-122.20901,47.52299000000001],[-122.20895900000001,47.523070000000004],[-122.20893,47.52310000000001],[-122.2089,47.52315000000001],[-122.20885,47.52324],[-122.2088,47.523306000000005],[-122.20873999999999,47.523404],[-122.20869,47.52348],[-122.20863,47.52357000000001],[-122.20858,47.52365],[-122.20853,47.52373000000001],[-122.20848,47.5238],[-122.20846999999999,47.52382],[-122.20844,47.523900000000005],[-122.20857,47.523900000000005],[-122.20852000000001,47.52396],[-122.20848,47.524004],[-122.20844,47.52407],[-122.2084,47.52413000000001],[-122.20836,47.52418],[-122.20832,47.52424],[-122.20826000000001,47.52431000000001],[-122.2082,47.524404],[-122.20812000000001,47.52451000000001],[-122.20804,47.52463],[-122.208,47.52468],[-122.20796300000002,47.524737],[-122.20792500000002,47.52479],[-122.20785000000001,47.5249],[-122.20781000000001,47.524954],[-122.20777,47.525009000000004],[-122.207745,47.525040000000004],[-122.20771,47.525093000000005],[-122.20767000000001,47.52514000000001],[-122.20763000000001,47.525200000000005],[-122.20752000000002,47.525360000000006],[-122.2074,47.52553000000001],[-122.20732000000001,47.52564],[-122.20725000000002,47.52574200000001],[-122.20714000000001,47.52590000000001],[-122.20704,47.52604],[-122.20698,47.526126000000005],[-122.2069,47.52624],[-122.20683,47.526346000000004],[-122.20671,47.52651000000001],[-122.20651000000001,47.526790000000005],[-122.206002,47.52754],[-122.20592,47.52766],[-122.20566000000001,47.52805000000001],[-122.20558,47.528161000000004],[-122.20551,47.52828],[-122.20552,47.52833000000001],[-122.205486,47.52838200000001],[-122.20539,47.52845000000001],[-122.20528,47.52853000000001],[-122.20479,47.528780000000005],[-122.20499000000001,47.52929],[-122.20501,47.52966],[-122.20512000000001,47.52966],[-122.20515,47.529830000000004],[-122.20515,47.530010000000004],[-122.205143,47.530390000000004],[-122.20516,47.5308],[-122.20516,47.530986],[-122.20517,47.53112],[-122.20516,47.531149],[-122.20514,47.53117],[-122.20511,47.531194],[-122.20509,47.53121],[-122.20506,47.53123],[-122.20503599999999,47.531242],[-122.20501,47.531259999999996],[-122.20499000000001,47.531268999999995],[-122.204972,47.531275],[-122.20495000000001,47.531282],[-122.20493,47.531292],[-122.20492000000002,47.5313],[-122.20491000000001,47.53132],[-122.20491000000001,47.531337],[-122.20489,47.53136],[-122.20488,47.53139],[-122.204874,47.5314],[-122.20486000000001,47.53142],[-122.20485000000001,47.531439999999996],[-122.20484,47.53145],[-122.20483,47.531459999999996],[-122.20481000000001,47.53147],[-122.20478,47.5315],[-122.20468000000001,47.53158],[-122.20465000000002,47.53161],[-122.20463000000001,47.53163],[-122.20459000000001,47.531659999999995],[-122.20457,47.53168399999999],[-122.20452700000001,47.53172],[-122.20448,47.53175],[-122.20446000000001,47.53177],[-122.2044,47.531819999999996],[-122.20438,47.53185],[-122.20435,47.53187],[-122.20422000000002,47.53199],[-122.20421000000002,47.532000000000004],[-122.20358,47.532149000000004],[-122.20352000000001,47.532209],[-122.20336,47.53226],[-122.20332,47.53237000000001],[-122.20333,47.5326],[-122.20329000000001,47.532630000000005],[-122.20323,47.53268],[-122.20320000000001,47.532700000000006],[-122.20316000000001,47.532725000000006],[-122.20313,47.53275000000001],[-122.20311000000001,47.532761],[-122.20309,47.53278],[-122.20305,47.532796000000005],[-122.20304,47.532810000000005],[-122.20302000000001,47.53282],[-122.20301,47.532837],[-122.20301,47.53286],[-122.203,47.53287],[-122.20299,47.532891],[-122.20297,47.53291000000001],[-122.20294,47.53294],[-122.20292,47.53296],[-122.2029,47.532990000000005],[-122.20289,47.533009],[-122.20288,47.533026],[-122.20288,47.53304],[-122.20286,47.53306],[-122.20284,47.53311000000001],[-122.202816,47.533148000000004],[-122.202803,47.53318],[-122.20279599999999,47.533210000000004],[-122.20279,47.53324],[-122.20276999999999,47.533288],[-122.20275,47.533350000000006],[-122.20272,47.53342],[-122.20270599999999,47.533449],[-122.20269,47.533483999999994],[-122.20268,47.53351000000001],[-122.20269,47.533529],[-122.20269,47.533550000000005],[-122.20269,47.53357200000001],[-122.20268,47.5336],[-122.20268,47.533631],[-122.202403,47.533879],[-122.20237999999999,47.533880999999994],[-122.20233999999999,47.533879999999996],[-122.20227,47.533879],[-122.20224,47.533879999999996],[-122.20223,47.533882],[-122.20211,47.533891],[-122.2021,47.53389],[-122.20206,47.5339],[-122.20204199999999,47.53389],[-122.20195000000001,47.5339],[-122.20193,47.533910000000006],[-122.20190000000001,47.53392],[-122.20188,47.533927],[-122.20186000000001,47.533930000000005],[-122.201818,47.533950000000004],[-122.2018,47.53396],[-122.20178,47.533970000000004],[-122.20172000000001,47.53402],[-122.20171,47.53404],[-122.2017,47.53406699999999],[-122.20169000000001,47.534104],[-122.20167000000001,47.53414],[-122.201638,47.53424],[-122.20163000000001,47.53435],[-122.20155700000001,47.53441],[-122.20145000000001,47.534485999999994],[-122.2014,47.53452],[-122.20134,47.53452],[-122.20123000000001,47.53452],[-122.20116000000002,47.534510000000004],[-122.20111000000001,47.5345],[-122.20107,47.534499],[-122.20103,47.534499],[-122.20101000000001,47.5345],[-122.20096000000001,47.534501],[-122.20092500000001,47.534510000000004],[-122.20091000000001,47.534510000000004],[-122.20089,47.534510000000004],[-122.20086,47.534535000000005],[-122.20086,47.53455],[-122.20087,47.53457],[-122.20088,47.534582],[-122.20091000000001,47.53459],[-122.20094,47.5346],[-122.20098,47.53461],[-122.20103,47.53461],[-122.20113,47.53461],[-122.20122000000002,47.53461],[-122.20133,47.53462],[-122.20139,47.534639999999996],[-122.20144,47.53465],[-122.20147,47.53466699999999],[-122.20149,47.534679999999994],[-122.20126000000002,47.534879999999994],[-122.20122000000002,47.534893],[-122.20119100000001,47.5349],[-122.20116000000002,47.53491],[-122.20113,47.53492],[-122.20109000000001,47.53493],[-122.20106000000001,47.53495],[-122.20104,47.534971],[-122.20102000000001,47.53501000000001],[-122.20099,47.535030000000006],[-122.20096000000001,47.53504],[-122.20091000000001,47.53505200000001],[-122.20088,47.535050000000005],[-122.20083,47.53506],[-122.20076999999999,47.53505800000001],[-122.20072,47.535064],[-122.2007,47.535070000000005],[-122.20069000000001,47.535070000000005],[-122.20066000000001,47.53506],[-122.20064,47.53505800000001],[-122.20058,47.535091],[-122.20058,47.53511000000001],[-122.20058,47.53513000000001],[-122.20058,47.535140000000006],[-122.20059,47.5352],[-122.20061000000001,47.535250000000005],[-122.20061000000001,47.53530000000001],[-122.20057,47.53533000000001],[-122.20048,47.53539800000001],[-122.200287,47.535500000000006],[-122.2001,47.535624],[-122.20006000000001,47.53566],[-122.20002600000001,47.53568],[-122.2,47.53569],[-122.19997099999999,47.53571200000001],[-122.19985,47.5358],[-122.19982,47.53582],[-122.19979,47.53584],[-122.19939,47.53609],[-122.19936,47.536100000000005],[-122.19933999999999,47.53611000000001],[-122.19931,47.53614],[-122.19929,47.53616],[-122.19927,47.536176000000005],[-122.19911,47.53626],[-122.19904,47.536307],[-122.19891,47.536410000000004],[-122.19886999999999,47.536437],[-122.19877999999999,47.536500000000004],[-122.19872,47.536550000000005],[-122.19865,47.536614],[-122.19854,47.5367],[-122.19839999999999,47.53682],[-122.19834999999999,47.53685],[-122.19827,47.53692],[-122.19821,47.536970000000004],[-122.19809,47.53705],[-122.19807999999999,47.53706],[-122.19803999999999,47.537092],[-122.19801,47.537110000000006],[-122.19799,47.537130000000005],[-122.19798,47.537144],[-122.19795,47.537165],[-122.19791900000001,47.5372],[-122.19791000000001,47.53721],[-122.19789,47.53722],[-122.19786,47.537255],[-122.197816,47.537279999999996],[-122.19778,47.537310000000005],[-122.19772,47.53735],[-122.19768,47.53737],[-122.19766000000001,47.537388],[-122.197632,47.5374],[-122.19758,47.537445],[-122.19756000000001,47.537459999999996],[-122.19752000000001,47.53749],[-122.1975,47.5375],[-122.19748,47.53752],[-122.19746,47.53754],[-122.19741,47.53758],[-122.1974,47.537603],[-122.19736999999999,47.53763],[-122.19735,47.537652],[-122.19734,47.537659999999995],[-122.197328,47.53767],[-122.1973,47.53769],[-122.19728,47.537701],[-122.19726000000001,47.537710000000004],[-122.19723,47.53772],[-122.19720000000001,47.53772],[-122.19715000000001,47.53773],[-122.1971,47.537741],[-122.19709,47.53775],[-122.197055,47.537746],[-122.19703,47.53773],[-122.19702000000001,47.53772],[-122.19699,47.537706],[-122.19694,47.53767],[-122.19693,47.537659999999995],[-122.19686999999999,47.53761],[-122.196833,47.537575000000004],[-122.19678499999999,47.537532000000006],[-122.19676,47.5375],[-122.19675,47.53749],[-122.19668,47.53747],[-122.19658,47.53745],[-122.19654,47.53745],[-122.19652,47.537459999999996],[-122.19648,47.53748099999999],[-122.19646,47.537488999999994],[-122.19644,47.53749],[-122.19641,47.537478],[-122.1964,47.53747],[-122.19637999999999,47.537475],[-122.19636999999999,47.5375],[-122.19636,47.53752],[-122.19636,47.537527],[-122.19636,47.537545],[-122.19637699999998,47.537569],[-122.19641,47.537577],[-122.196439,47.53758],[-122.19646,47.537603999999995],[-122.19646999999999,47.53762],[-122.19649,47.53763],[-122.19651,47.53764399999999],[-122.19653,47.53765],[-122.19655,47.537659999999995],[-122.19658,47.53767],[-122.1966,47.537692],[-122.19661,47.537703],[-122.19663,47.53772],[-122.19665,47.537734],[-122.19667,47.537745],[-122.19669,47.537756],[-122.19671,47.537773],[-122.19672999999999,47.537788],[-122.19674499999999,47.53779],[-122.19676,47.53781],[-122.19678599999999,47.53783],[-122.1968,47.537839999999996],[-122.19682,47.537859999999995],[-122.19685,47.53787],[-122.19686999999999,47.53789],[-122.19691,47.537909],[-122.19692,47.53793],[-122.19693,47.537952000000004],[-122.19694,47.537969],[-122.19694,47.53798],[-122.19691,47.53799],[-122.19688,47.538000000000004],[-122.19685,47.538011000000004],[-122.19677999999999,47.53801000000001],[-122.19676999999999,47.53801000000001],[-122.19672999999999,47.538005000000005],[-122.19665,47.538004],[-122.19661,47.53801000000001],[-122.19657,47.538011000000004],[-122.196512,47.538007],[-122.196439,47.53801000000001],[-122.1964,47.53801000000001],[-122.19636999999999,47.53801500000001],[-122.19636,47.53801000000001],[-122.19633499999999,47.53802],[-122.1963,47.538030000000006],[-122.19627,47.538030000000006],[-122.19623,47.538039000000005],[-122.19619,47.538047],[-122.19614,47.538050000000005],[-122.19609,47.53806],[-122.19606,47.53806],[-122.19604,47.53808],[-122.19603,47.538090000000004],[-122.19603,47.53811000000001],[-122.19603,47.538123000000006],[-122.19605,47.53813000000001],[-122.19608,47.53813900000001],[-122.1961,47.53815000000001],[-122.19612000000001,47.53815000000001],[-122.19614,47.538160000000005],[-122.19616,47.53817000000001],[-122.19618,47.53817000000001],[-122.1962,47.53817000000001],[-122.19623,47.538177000000005],[-122.19625,47.53819000000001],[-122.19628,47.53819500000001],[-122.19631,47.5382],[-122.19633999999999,47.5382],[-122.19636,47.53821000000001],[-122.19637999999999,47.538221],[-122.1964,47.538222000000005],[-122.196426,47.538230000000006],[-122.19644,47.53824],[-122.19646999999999,47.538250000000005],[-122.19649,47.538256000000004],[-122.1965,47.538270000000004],[-122.19649,47.53828],[-122.19649,47.53829],[-122.19646999999999,47.53830800000001],[-122.19644,47.538320000000006],[-122.19642,47.53833000000001],[-122.19642,47.538340000000005],[-122.19641,47.538354000000005],[-122.196394,47.53837000000001],[-122.19636999999999,47.538394000000004],[-122.19635,47.538410000000006],[-122.19626000000001,47.53848],[-122.19623,47.53851900000001],[-122.19621000000001,47.538529000000004],[-122.19621000000001,47.538540000000005],[-122.19618,47.53855000000001],[-122.19617,47.53857000000001],[-122.19614,47.53858],[-122.19611,47.53862],[-122.19606999999999,47.538654],[-122.19605,47.53868],[-122.19604,47.53871000000001],[-122.19603,47.53873600000001],[-122.19602,47.53876],[-122.19601,47.538790000000006],[-122.196,47.538817],[-122.19599,47.538848],[-122.19597999999999,47.53887],[-122.195983,47.53888],[-122.19597999999999,47.538900000000005],[-122.19597999999999,47.538908000000006],[-122.19597399999999,47.538920000000005],[-122.19596999999999,47.538940000000004],[-122.19596,47.538961],[-122.19596,47.53898],[-122.19595,47.539010000000005],[-122.19595,47.53902],[-122.19595,47.539038000000005],[-122.19595,47.539049],[-122.19595,47.53908],[-122.19595,47.539094],[-122.19595,47.539131000000005],[-122.19596,47.539173000000005],[-122.19596,47.539190000000005],[-122.19596999999999,47.539206],[-122.19596999999999,47.53922],[-122.19596999999999,47.53923],[-122.19596999999999,47.53924],[-122.19596999999999,47.53928],[-122.19597999999999,47.539307],[-122.19597999999999,47.53933000000001],[-122.19597999999999,47.539336000000006],[-122.19556,47.53934],[-122.195405,47.53934],[-122.19512999999999,47.53933800000001],[-122.1949,47.53936],[-122.19467,47.53936],[-122.19426000000001,47.53936],[-122.19409,47.53848],[-122.19251,47.538438000000006],[-122.19256999999999,47.538340000000005],[-122.19274999999999,47.53810000000001],[-122.19284999999999,47.53796],[-122.19287899999999,47.537924],[-122.19239999999999,47.537731],[-122.19239999999999,47.53742],[-122.19239999999999,47.53737],[-122.19238999999999,47.53678],[-122.19237999999999,47.53528],[-122.19306,47.53528],[-122.19305,47.534819999999996],[-122.19284999999999,47.534819999999996],[-122.192519,47.534819999999996],[-122.19236999999998,47.534819999999996],[-122.19238999999999,47.533],[-122.19297999999999,47.533],[-122.19373499999999,47.533],[-122.19373999999999,47.531928],[-122.19368,47.53184399999999],[-122.19363,47.53174],[-122.193602,47.531639999999996],[-122.19359,47.531530000000004],[-122.19358,47.5312],[-122.19324,47.5312],[-122.19094,47.531242],[-122.19106000000001,47.525090000000006],[-122.19106000000001,47.5249],[-122.19042999999999,47.52382],[-122.19019,47.52347],[-122.18978999999999,47.522963000000004],[-122.18957999999999,47.522740000000006],[-122.18937999999999,47.52259000000001],[-122.18923099999999,47.52266],[-122.18918,47.52252000000001],[-122.1891,47.52250000000001],[-122.189026,47.522490000000005],[-122.18896,47.52248],[-122.18888999999999,47.52248],[-122.18881999999999,47.52248],[-122.18877999999998,47.522483],[-122.18873999999998,47.52248],[-122.18867999999999,47.522489],[-122.18863999999999,47.522496000000004],[-122.18858999999999,47.52250000000001],[-122.18852999999999,47.52252000000001],[-122.18846999999998,47.52253000000001],[-122.18842,47.52255000000001],[-122.18834999999999,47.52257000000001],[-122.18830999999999,47.522600000000004],[-122.18827999999999,47.52261000000001],[-122.18824,47.52263000000001],[-122.18818999999999,47.52266],[-122.18816,47.522690000000004],[-122.18811799999999,47.52272200000001],[-122.18807999999999,47.52275900000001],[-122.18804999999999,47.522794000000005],[-122.18802999999998,47.52282],[-122.18800999999999,47.522850000000005],[-122.18794,47.522966000000004],[-122.18787999999999,47.523049],[-122.18741,47.52302],[-122.18741,47.52201000000001],[-122.18702,47.52200800000001],[-122.18656,47.522017000000005],[-122.18607999999999,47.522017000000005],[-122.186013,47.52203000000001],[-122.18597999999999,47.52203000000001],[-122.18581999999999,47.52211000000001],[-122.18567999999999,47.52211000000001],[-122.185656,47.522040000000004],[-122.18566999999999,47.521792000000005],[-122.18566999999999,47.52172],[-122.18570999999999,47.52116],[-122.18573999999998,47.520100000000006],[-122.18577999999998,47.51871200000001],[-122.18574999999998,47.51806],[-122.18553999999999,47.51806],[-122.18502999999998,47.51759],[-122.18408,47.51675],[-122.18404,47.51672],[-122.18416,47.516722],[-122.18406999999999,47.516635],[-122.18361,47.51663],[-122.18148,47.51661],[-122.18047299999999,47.5166],[-122.180403,47.5166],[-122.17969,47.516597],[-122.17969,47.516462],[-122.17969,47.5161],[-122.17969999999998,47.51594],[-122.17969999999998,47.51574],[-122.17969999999998,47.51552],[-122.17732,47.515479],[-122.17732999999998,47.515434],[-122.17661,47.51543],[-122.1766,47.51493],[-122.17661,47.514745],[-122.17661,47.514419999999994],[-122.17607999999998,47.514419999999994],[-122.175643,47.51441],[-122.17534999999998,47.51441],[-122.17520999999999,47.51441],[-122.17522,47.51378],[-122.17522,47.51367],[-122.174915,47.51367],[-122.17469999999999,47.51359],[-122.17318999999999,47.513574],[-122.17294999999999,47.513571],[-122.17229999999998,47.51122099999999],[-122.17226699999999,47.51112],[-122.17272999999997,47.511058],[-122.17253699999998,47.510393],[-122.17262,47.51027],[-122.17262,47.509242],[-122.17264999999999,47.508430000000004],[-122.17136999999998,47.508446],[-122.17108999999999,47.50844],[-122.1711,47.50851000000001],[-122.17124,47.50851000000001],[-122.17124,47.50887],[-122.17121900000001,47.508973000000005],[-122.17042999999998,47.50896],[-122.17069,47.510079999999995],[-122.16996,47.51007],[-122.16995,47.51021],[-122.16982,47.51021],[-122.16982,47.51103],[-122.16982,47.51163],[-122.1698,47.51186799999999],[-122.16994,47.51187099999999],[-122.17005999999999,47.511869999999995],[-122.17004999999999,47.512170000000005],[-122.17092,47.512181],[-122.17116,47.51301],[-122.17057999999999,47.51354],[-122.17070999999999,47.51355],[-122.17074999999998,47.51355],[-122.17080999999999,47.513553],[-122.16986999999999,47.514419999999994],[-122.16987999999999,47.515169],[-122.16986,47.516518000000005],[-122.16902999999999,47.51652],[-122.16891,47.516510000000004],[-122.16471,47.516479999999994],[-122.16454,47.516479999999994],[-122.16031,47.516459999999995],[-122.15982,47.51615],[-122.15979099999998,47.516130000000004],[-122.15970999999999,47.5161],[-122.15964,47.516075],[-122.15957999999999,47.516059999999996],[-122.15952,47.516059999999996],[-122.15936999999998,47.516059999999996],[-122.15915,47.5161],[-122.15923,47.51629],[-122.15925,47.516439999999996],[-122.15926999999999,47.51653],[-122.15929,47.51672],[-122.1592,47.517861999999994],[-122.15912,47.518727],[-122.15638999999999,47.518297],[-122.1536,47.517869999999995],[-122.15357999999999,47.518],[-122.15302999999999,47.51789699999999],[-122.15173999999999,47.5177],[-122.15096999999999,47.51761],[-122.14986999999999,47.51747399999999],[-122.14956000000001,47.51743999999999],[-122.14842999999999,47.51732],[-122.14596999999999,47.51715],[-122.14592,47.51714],[-122.14472,47.51705],[-122.14447,47.516999999999996],[-122.14404,47.516903],[-122.14359,47.51675],[-122.14336,47.516635],[-122.14311000000001,47.51652],[-122.14319,47.51645],[-122.14318,47.51528],[-122.14318,47.51506],[-122.14321000000001,47.514359999999996],[-122.14321300000002,47.514289999999995],[-122.143217,47.514179999999996],[-122.14324,47.513514],[-122.14325000000001,47.51345],[-122.14325000000001,47.513239999999996],[-122.14328,47.512170000000005],[-122.14429300000002,47.512176000000004],[-122.14434,47.512170000000005],[-122.144391,47.512170000000005],[-122.14529999999999,47.51208],[-122.14533999999999,47.51207],[-122.14533999999999,47.512015000000005],[-122.14533999999999,47.51197],[-122.14536999999999,47.50992],[-122.14536999999999,47.509761],[-122.14582999999999,47.509764],[-122.14669,47.509794],[-122.14846,47.509855],[-122.14856999999999,47.509859999999996],[-122.14859,47.508770000000005],[-122.14502,47.508720000000004],[-122.14441000000001,47.508720000000004],[-122.1443,47.508720000000004],[-122.14382,47.508721],[-122.14332999999999,47.508711000000005],[-122.14332999999999,47.50856],[-122.14332,47.50802],[-122.14332,47.507259999999995],[-122.14332,47.506840999999994],[-122.14332,47.506479999999996],[-122.14332999999999,47.505589],[-122.14332999999999,47.505385000000004],[-122.14332,47.504163],[-122.14332,47.50378],[-122.14331,47.502810000000004],[-122.14864,47.502873],[-122.148635,47.501965],[-122.14742000000001,47.501953],[-122.1474,47.50105],[-122.14635,47.50103],[-122.1458,47.50102],[-122.14558,47.50103],[-122.14352000000001,47.50099],[-122.14328,47.50098],[-122.14327,47.50072],[-122.14324,47.49922],[-122.14321000000001,47.49828],[-122.14319,47.497411],[-122.14319,47.49718],[-122.14315,47.49575000000001],[-122.14315,47.495551000000006],[-122.14252,47.495551000000006],[-122.14175,47.495543000000005],[-122.14062000000001,47.495520000000006],[-122.13969,47.495498000000005],[-122.13808999999999,47.495470000000005],[-122.13802999999999,47.49546],[-122.13798,47.495430000000006],[-122.13782,47.495537000000006],[-122.1352,47.495464],[-122.13513999999999,47.49233000000001],[-122.13506,47.48832],[-122.13493,47.48832],[-122.13439,47.48832],[-122.13306,47.488310000000006],[-122.13242,47.4883],[-122.13095,47.488268],[-122.12982,47.48824],[-122.12982,47.48759999999999],[-122.12982,47.487339999999996],[-122.13111,47.48735],[-122.13242,47.487362],[-122.13239999999999,47.486833],[-122.13238999999999,47.48566699999999],[-122.13277599999998,47.485658],[-122.133297,47.48566099999999],[-122.133873,47.48566699999999],[-122.13465000000001,47.48567],[-122.135253,47.485679999999995],[-122.13526999999999,47.48512],[-122.13526900000001,47.48491],[-122.135273,47.48468999999999],[-122.13696,47.4847],[-122.13762000000001,47.48467099999999],[-122.13759,47.48343],[-122.13759900000001,47.48335],[-122.13570999999999,47.483359],[-122.13235999999999,47.48327999999999],[-122.13113,47.48327],[-122.130221,47.48325],[-122.12973999999998,47.483236],[-122.12973999999998,47.48331],[-122.12973999999998,47.483376],[-122.12975999999999,47.484016],[-122.12976999999998,47.48418399999999],[-122.12899999999999,47.48417],[-122.12715,47.48415],[-122.1265,47.48413],[-122.12651,47.484592],[-122.12636999999998,47.484579999999994],[-122.12628,47.484579999999994],[-122.1262,47.48457],[-122.12612999999999,47.484559999999995],[-122.12601,47.48453],[-122.12553999999999,47.48459],[-122.12458,47.484575],[-122.12222,47.484536],[-122.12212999999998,47.48455],[-122.12207999999998,47.48457],[-122.120944,47.48448799999999],[-122.12093399999999,47.48402099999999],[-122.12062,47.484019999999994],[-122.12030999999999,47.484012],[-122.12029,47.483537],[-122.12056,47.483545],[-122.12069,47.483533],[-122.12199,47.48355],[-122.12237199999997,47.48356],[-122.12437999999999,47.483599999999996],[-122.12436999999998,47.482679999999995],[-122.12454,47.482682],[-122.124618,47.482710000000004],[-122.1248,47.482730000000004],[-122.12608999999999,47.48275],[-122.12707999999999,47.48275],[-122.12706999999999,47.48234],[-122.12706999999999,47.48189099999999],[-122.12706,47.481609999999996],[-122.12706,47.48143999999999],[-122.12706,47.480996],[-122.12499999999999,47.480996],[-122.12473999999999,47.480996],[-122.1245,47.480996],[-122.12436999999998,47.48092],[-122.12442999999999,47.48105999999999],[-122.12445,47.481227999999994],[-122.12446999999999,47.48185399999999],[-122.12432999999999,47.48187999999999],[-122.12316,47.481869999999994],[-122.12292999999998,47.48186199999999],[-122.121974,47.48183999999999],[-122.1218,47.48180399999999],[-122.12174999999999,47.48168999999999],[-122.12169999999999,47.48123999999999],[-122.12174999999999,47.48107699999999],[-122.12177999999999,47.48097],[-122.12172999999999,47.478878],[-122.12166,47.47533000000001],[-122.12154,47.47534],[-122.12138999999999,47.47524],[-122.1214,47.475197],[-122.1215,47.475186],[-122.12154,47.47516],[-122.12156999999999,47.475150000000006],[-122.12162000000001,47.475100000000005],[-122.12163,47.47495],[-122.12164,47.47432],[-122.12162000000001,47.473725],[-122.12292999999998,47.47374],[-122.12393999999999,47.473745],[-122.12438999999999,47.47375],[-122.12443999999999,47.47556],[-122.12467,47.475563],[-122.12693999999999,47.4756],[-122.12692999999999,47.47528],[-122.12692999999999,47.475120000000004],[-122.12762000000001,47.475120000000004],[-122.12781,47.47513000000001],[-122.12795,47.47513000000001],[-122.12922,47.47514],[-122.12978999999999,47.475145000000005],[-122.13016,47.475148000000004],[-122.13226999999999,47.475166],[-122.13226,47.47544],[-122.13226,47.47562],[-122.13226,47.47581],[-122.13224,47.47589],[-122.13224,47.475970000000004],[-122.13229,47.47783999999999],[-122.13229,47.47834],[-122.13428,47.47836],[-122.13501,47.478363],[-122.13502,47.478556000000005],[-122.13502999999999,47.47892],[-122.13504999999999,47.47927],[-122.13576799999998,47.479279999999996],[-122.13759900000001,47.479310000000005],[-122.13760300000001,47.4794],[-122.13761000000001,47.47956],[-122.13762000000001,47.47964699999999],[-122.13763,47.480019999999996],[-122.13763,47.48031],[-122.13761000000001,47.48083999999999],[-122.13759,47.480959999999996],[-122.13759,47.481139999999996],[-122.13767,47.481139999999996],[-122.14034,47.48111],[-122.14286999999999,47.481159999999996],[-122.14475,47.48119],[-122.14553,47.48121],[-122.14567,47.48121999999999],[-122.1458,47.48121999999999],[-122.14585,47.48121999999999],[-122.14653,47.48123],[-122.14668,47.48123],[-122.14822000000001,47.48121999999999],[-122.14954,47.48121999999999],[-122.149502,47.47957],[-122.1495,47.4794],[-122.151168,47.47941],[-122.15344999999999,47.479422],[-122.15359,47.47943],[-122.1536,47.479997],[-122.15361,47.48125999999999],[-122.153626,47.48125999999999],[-122.15415,47.481249999999996],[-122.15446999999999,47.48124599999999],[-122.15487999999999,47.481249999999996],[-122.15592,47.481272999999995],[-122.15601,47.48127999999999],[-122.15644999999999,47.48127999999999],[-122.15642999999999,47.47986399999999],[-122.15606999999999,47.479253],[-122.156421,47.478956000000004],[-122.15639999999999,47.477639999999994],[-122.15738999999999,47.477639999999994],[-122.16415,47.47771],[-122.16415,47.47723],[-122.16415,47.476355000000005],[-122.16194,47.476330000000004],[-122.16151,47.476330000000004],[-122.16151,47.475744],[-122.16151,47.475590000000004],[-122.1615,47.47406099999999],[-122.16121000000001,47.474059],[-122.16086,47.474056],[-122.15616999999999,47.474018],[-122.15485,47.47398],[-122.15442999999999,47.47399],[-122.15432,47.473977],[-122.15416,47.47397],[-122.15386999999998,47.47396],[-122.15352,47.47396],[-122.15352,47.473819999999996],[-122.15353999999999,47.472753000000004],[-122.15356999999999,47.471233],[-122.15355,47.47094],[-122.15355,47.47093],[-122.15355,47.47085],[-122.15355,47.470679999999994],[-122.15353199999998,47.47068099999999],[-122.15348999999999,47.470679999999994],[-122.15346,47.470675],[-122.15342,47.47067],[-122.15335999999999,47.470676],[-122.15333999999999,47.470676],[-122.15328,47.470679],[-122.15325,47.470679999999994],[-122.15323,47.47068399999999],[-122.15321,47.470679999999994],[-122.1532,47.47068399999999],[-122.15316999999999,47.470679999999994],[-122.15315,47.470679999999994],[-122.15311,47.470659999999995],[-122.15307999999999,47.47065],[-122.15305,47.470638],[-122.15302,47.47063],[-122.15298999999999,47.47062],[-122.15296999999998,47.47061],[-122.15293999999999,47.470597],[-122.15290999999999,47.47058],[-122.15284999999999,47.470544],[-122.15279999999998,47.470510000000004],[-122.15132999999999,47.47052],[-122.14693,47.470452],[-122.146849,47.470479999999995],[-122.146804,47.47049],[-122.14677999999999,47.4705],[-122.14672,47.47052],[-122.14663,47.47054],[-122.14655,47.47056],[-122.14651,47.47057],[-122.14643099999999,47.470589],[-122.14639,47.470596],[-122.14634,47.4706],[-122.1463,47.47061],[-122.146251,47.47062],[-122.14620000000001,47.47063],[-122.14616000000001,47.470634],[-122.14611000000001,47.470639999999996],[-122.146083,47.470639999999996],[-122.14602000000001,47.470639999999996],[-122.14595,47.470639999999996],[-122.1459,47.47065],[-122.14582999999999,47.470648999999995],[-122.14573999999999,47.47065],[-122.14562000000001,47.47065],[-122.14552,47.47065],[-122.14541,47.47065],[-122.14533999999999,47.47065],[-122.145305,47.470639999999996],[-122.14525,47.470642],[-122.14523,47.470639999999996],[-122.1452,47.470638],[-122.14518199999999,47.47063],[-122.14513,47.47063],[-122.1451,47.47062],[-122.14505,47.47061],[-122.14501,47.470596],[-122.14498,47.470591],[-122.14491000000001,47.47058],[-122.14486000000001,47.47056],[-122.144822,47.47055],[-122.14477,47.470538000000005],[-122.14472,47.47052],[-122.14469000000001,47.470510000000004],[-122.14465000000001,47.47049],[-122.14460000000001,47.47047],[-122.14457,47.470462],[-122.14455000000001,47.47045],[-122.14451000000001,47.470439999999996],[-122.14444,47.470417],[-122.14433,47.470374],[-122.14428000000001,47.47035],[-122.14422000000002,47.47032],[-122.14419000000001,47.470310000000005],[-122.14414000000001,47.47029],[-122.144068,47.470259999999996],[-122.14398,47.47022],[-122.14397,47.47022],[-122.143941,47.470206],[-122.1439,47.47019],[-122.14386,47.47017],[-122.14382,47.470151],[-122.143789,47.47014],[-122.14372999999999,47.470105000000004],[-122.14369,47.470079999999996],[-122.14365000000001,47.470058],[-122.14361000000001,47.470033],[-122.14358,47.47001],[-122.14355,47.46998],[-122.1435,47.469950000000004],[-122.14347,47.469930000000005],[-122.14343,47.469910000000006],[-122.14338,47.46989],[-122.143307,47.46987],[-122.1434,47.46947],[-122.14358,47.468830000000004],[-122.14315,47.46878],[-122.14362000000001,47.467439999999996],[-122.14236,47.467223999999995],[-122.14273999999999,47.466294],[-122.14267,47.46627],[-122.14254,47.46624],[-122.142455,47.466221],[-122.14236999999999,47.466207],[-122.14224,47.466194],[-122.14194,47.466150000000006],[-122.14089,47.466],[-122.13996,47.465830000000004],[-122.13813999999999,47.46558],[-122.13752000000001,47.46546],[-122.13746,47.46544],[-122.13696999999999,47.465348000000006],[-122.13649,47.46528],[-122.13624,47.46524],[-122.13264,47.464639999999996],[-122.13076999999998,47.46432],[-122.12885999999999,47.46403],[-122.12691,47.463798000000004],[-122.12586999999998,47.463710000000006],[-122.12447999999999,47.46359],[-122.12386999999998,47.463561],[-122.12262,47.46349],[-122.12167,47.46343],[-122.12168,47.46327],[-122.12121,47.46325],[-122.12086999999998,47.46324],[-122.12091,47.46204],[-122.12091,47.461859999999994],[-122.12168,47.461859999999994],[-122.12174999999999,47.461859999999994],[-122.12312999999999,47.46202],[-122.12312999999999,47.462160000000004],[-122.123415,47.462194000000004],[-122.12392,47.462250000000004],[-122.12733999999999,47.461879999999994],[-122.1274,47.46187],[-122.12892999999998,47.461493],[-122.12928699999999,47.46142],[-122.13165000000001,47.46142],[-122.13234999999999,47.46134],[-122.13235999999999,47.460350000000005],[-122.13235999999999,47.460100000000004],[-122.13236999999998,47.45947999999999],[-122.13842,47.45957],[-122.14292,47.45963999999999],[-122.14292,47.45980599999999],[-122.14289699999999,47.460533000000005],[-122.142815,47.46328],[-122.14459000000001,47.463274],[-122.14462000000002,47.463370000000005],[-122.14482000000001,47.463344],[-122.14508,47.463370000000005],[-122.14529999999999,47.463491],[-122.14554,47.463738000000006],[-122.14564,47.46376],[-122.14587999999999,47.46364],[-122.14618,47.463617],[-122.14646,47.463730000000005],[-122.14663,47.463730000000005],[-122.14665600000001,47.46361],[-122.14659,47.46347],[-122.14671,47.463266],[-122.14779,47.46326],[-122.14788,47.463259],[-122.14792000000001,47.46329],[-122.14806,47.46336],[-122.14816,47.463419],[-122.1482,47.463442],[-122.14818,47.464679999999994],[-122.14816,47.465406],[-122.14876999999998,47.4654],[-122.14878999999999,47.465354000000005],[-122.14882999999999,47.465309000000005],[-122.14894,47.465266],[-122.14907,47.46524],[-122.14917,47.465250000000005],[-122.149271,47.46528],[-122.149365,47.46531000000001],[-122.14959,47.46531200000001],[-122.14974,47.46526],[-122.14981,47.4652],[-122.14993,47.465160000000004],[-122.14999,47.465164],[-122.15001,47.465165000000006],[-122.15011,47.465171000000005],[-122.1502,47.465210000000006],[-122.15024,47.465270000000004],[-122.15024,47.465340000000005],[-122.1502,47.465390000000006],[-122.1502,47.465430000000005],[-122.15033999999999,47.46551300000001],[-122.15047999999999,47.465720000000005],[-122.15059,47.46573000000001],[-122.15069999999999,47.465769],[-122.15074999999999,47.465832000000006],[-122.15079999999999,47.466],[-122.15102999999999,47.466001],[-122.15102999999999,47.465875000000004],[-122.15146,47.465920000000004],[-122.151655,47.46588],[-122.15165,47.46629],[-122.1518,47.46638],[-122.15196999999999,47.46662],[-122.15204999999999,47.46655200000001],[-122.15194,47.466522000000005],[-122.15192,47.466474],[-122.15189,47.466344],[-122.15192,47.46628],[-122.15185,47.466249],[-122.15186,47.465657],[-122.1531,47.465590000000006],[-122.15323,47.465610000000005],[-122.15330999999999,47.46567],[-122.15326999999999,47.465540000000004],[-122.15353499999999,47.46553000000001],[-122.15412,47.46551200000001],[-122.15415,47.46562],[-122.15431,47.46558],[-122.15456999999999,47.46551900000001],[-122.15433999999999,47.465410000000006],[-122.15351,47.465078000000005],[-122.15315,47.464917],[-122.15302999999999,47.464859999999994],[-122.15268999999999,47.464639999999996],[-122.15222999999999,47.46436],[-122.15186999999999,47.46416],[-122.151611,47.464],[-122.15135,47.46386699999999],[-122.15116,47.463777],[-122.15086999999998,47.46367],[-122.150314,47.46353500000001],[-122.14982,47.46342],[-122.14954,47.46336],[-122.14872,47.463163],[-122.14836999999999,47.46306],[-122.148065,47.462928000000005],[-122.14771,47.46271000000001],[-122.14736,47.46246],[-122.14717,47.46228],[-122.14695,47.46201800000001],[-122.146817,47.46178],[-122.14671,47.461452],[-122.14667,47.461017],[-122.14671,47.46074],[-122.146755,47.46058],[-122.146808,47.46044],[-122.1469,47.46026],[-122.14702000000001,47.460059],[-122.14713,47.45992],[-122.14722000000002,47.45981],[-122.14736,47.45967999999999],[-122.14744,47.459619999999994],[-122.1511,47.459599999999995],[-122.15112,47.45903],[-122.15352,47.459022],[-122.15366999999999,47.45903],[-122.15366,47.45918399999999],[-122.15365,47.45958099999999],[-122.15386999999998,47.459582],[-122.15575999999999,47.459593],[-122.1561,47.45959],[-122.15636999999998,47.459592],[-122.15596,47.45991],[-122.155429,47.46045],[-122.15548999999999,47.46074],[-122.15520199999999,47.46114],[-122.15478399999999,47.46209],[-122.15456999999999,47.462922000000006],[-122.15476,47.46324],[-122.15674999999999,47.46324],[-122.15889999999999,47.465410000000006],[-122.15892,47.467037],[-122.15957999999999,47.466930000000005],[-122.16013,47.46685],[-122.16026000000001,47.466710000000006],[-122.160185,47.466549],[-122.16052,47.46667],[-122.1609,47.46684],[-122.161257,47.46618],[-122.16144,47.46602],[-122.16184,47.46576],[-122.16212999999999,47.46567],[-122.16270999999999,47.46566],[-122.16291,47.465658000000005],[-122.16413,47.46602],[-122.16425000000001,47.46607],[-122.16424,47.465810000000005],[-122.16425000000001,47.464150000000004],[-122.16425000000001,47.463217],[-122.16426000000001,47.46121],[-122.16426000000001,47.46107],[-122.16427,47.460761],[-122.16427,47.460417],[-122.16427,47.46022],[-122.16427,47.46003],[-122.16427,47.45985999999999],[-122.16428,47.459593],[-122.16429000000001,47.459205],[-122.16429000000001,47.458999999999996],[-122.16429000000001,47.45879],[-122.1643,47.458588],[-122.1643,47.45838],[-122.16431,47.45818],[-122.16431,47.457984999999994],[-122.16432,47.457699999999996],[-122.16432,47.457635999999994],[-122.16414,47.457609999999995],[-122.16396,47.45757],[-122.16385,47.457525999999994],[-122.16367,47.457449999999994],[-122.16356999999999,47.457387999999995],[-122.16353,47.45735],[-122.16345,47.45725999999999],[-122.16308,47.456717],[-122.16322000000001,47.45664299999999],[-122.16331,47.456579999999995],[-122.16332999999999,47.456559999999996],[-122.16344,47.45643],[-122.16356999999999,47.45629399999999],[-122.16363,47.456219999999995],[-122.16368,47.456163999999994],[-122.16372999999999,47.4561],[-122.16378999999999,47.456019999999995],[-122.16384,47.455909],[-122.16412000000001,47.455908],[-122.16439,47.45591],[-122.16439,47.455839999999995],[-122.16442,47.45307],[-122.16445,47.452228],[-122.16446,47.45187999999999],[-122.16396,47.451792],[-122.16344,47.45216],[-122.16315300000001,47.45202],[-122.16308,47.45201],[-122.16336,47.45161999999999],[-122.16237999999998,47.451409999999996],[-122.16184,47.451429999999995],[-122.16166000000001,47.451449999999994],[-122.16149,47.45140599999999],[-122.16124,47.45178099999999],[-122.1611,47.45171],[-122.16103,47.45166499999999],[-122.16099,47.451629999999994],[-122.16087999999999,47.451499999999996],[-122.16053,47.45078899999999],[-122.16042,47.450572],[-122.16033999999999,47.450469999999996],[-122.16023,47.45037],[-122.1606,47.45011],[-122.160369,47.44937],[-122.1616,47.44925],[-122.16153,47.4489],[-122.16176999999999,47.448875],[-122.16178,47.4491],[-122.16187,47.449534],[-122.16261,47.449459999999995],[-122.1626,47.44912],[-122.16217999999999,47.44916],[-122.16223,47.448758000000005],[-122.16223,47.44839],[-122.16206999999999,47.44783999999999],[-122.16202999999999,47.447559999999996],[-122.16206999999999,47.44740099999999],[-122.16155,47.4473],[-122.16161000000001,47.447219999999994],[-122.16165000000001,47.447145],[-122.16165000000001,47.44705],[-122.16162000000001,47.44683],[-122.16175,47.446819999999995],[-122.16201,47.44678],[-122.162293,47.44674],[-122.16257999999999,47.446687999999995],[-122.16287999999999,47.44662099999999],[-122.1647,47.446142],[-122.16586,47.44584],[-122.16629999999999,47.44574],[-122.16657,47.44569],[-122.16687999999999,47.44567],[-122.16714,47.44566],[-122.16745,47.44566],[-122.16758,47.445671],[-122.16798,47.445723],[-122.16876999999998,47.445858],[-122.17011,47.446079999999995],[-122.17012999999999,47.444979999999994],[-122.17014999999999,47.44405999999999],[-122.17014999999999,47.443813],[-122.17018999999999,47.441902],[-122.1702,47.441627999999994],[-122.1702,47.441379999999995],[-122.1702,47.441359999999996],[-122.17004999999999,47.441359999999996],[-122.16942999999999,47.441351],[-122.16699,47.439609],[-122.16727,47.43942],[-122.16855,47.438550000000006],[-122.16865,47.438570000000006],[-122.16996999999999,47.43772],[-122.17216999999998,47.437742],[-122.17275999999998,47.43629],[-122.17317999999999,47.43526],[-122.17342999999998,47.43465],[-122.17367999999999,47.434039999999996],[-122.17369,47.43379],[-122.17369999999998,47.433710000000005],[-122.17369599999999,47.43365],[-122.17369599999999,47.433591],[-122.17369599999999,47.43358],[-122.17370999999999,47.43323],[-122.17392999999998,47.43323],[-122.174202,47.43323],[-122.17451,47.43323],[-122.17446,47.432950000000005],[-122.17433999999999,47.432370000000006],[-122.17407999999999,47.431115000000005],[-122.17411,47.43029],[-122.17481,47.43029],[-122.17579999999998,47.430279999999996],[-122.17609999999999,47.430279999999996],[-122.17666,47.43027],[-122.17721,47.43027],[-122.17897999999998,47.43026],[-122.17916999999998,47.430257],[-122.18114,47.430246],[-122.18358699999999,47.43023],[-122.18446,47.43023],[-122.18646,47.430217],[-122.18782999999999,47.430215000000004],[-122.18966,47.43022],[-122.190951,47.43023],[-122.19129000000001,47.43023],[-122.19171,47.43023],[-122.19246999999999,47.43023],[-122.19384,47.43023],[-122.19556,47.43023],[-122.19709,47.43023],[-122.19708,47.42850000000001],[-122.19708,47.42669],[-122.19708,47.42582],[-122.19706000000001,47.42404],[-122.19706000000001,47.423100000000005],[-122.197944,47.42310200000001],[-122.19877999999999,47.42310500000001],[-122.19969,47.42311000000001],[-122.19988,47.42311000000001],[-122.20099,47.42311000000001],[-122.2018,47.42311300000001],[-122.20232999999999,47.42311000000001],[-122.20262000000001,47.423120000000004],[-122.20301,47.423120000000004],[-122.20396000000001,47.423120000000004],[-122.20432000000001,47.423120000000004],[-122.20443,47.42313000000001],[-122.20453,47.42313000000001],[-122.20465000000002,47.42313000000001],[-122.20503,47.42313000000001],[-122.20559,47.42313000000001],[-122.20611000000001,47.42313000000001],[-122.20757,47.42313000000001],[-122.20894,47.423120000000004],[-122.20995300000001,47.42311300000001],[-122.21151000000002,47.423097],[-122.21239,47.42309],[-122.21273,47.42308],[-122.21343,47.423076],[-122.21469000000002,47.42306],[-122.21485000000001,47.423058000000005],[-122.21626000000002,47.42304],[-122.21755000000002,47.42302],[-122.21790000000001,47.423],[-122.21797000000001,47.422984],[-122.21811000000001,47.423634],[-122.21812000000001,47.42422],[-122.21812000000001,47.42434],[-122.21813,47.42456],[-122.21809,47.425188000000006],[-122.2181,47.42533000000001],[-122.21811000000001,47.42598],[-122.21812000000001,47.42624],[-122.21812000000001,47.42646],[-122.21818,47.42749],[-122.21821600000001,47.429750000000006],[-122.21822000000002,47.429786],[-122.21822000000002,47.43007],[-122.21821000000001,47.430246],[-122.21837,47.43025],[-122.21847,47.430251],[-122.21896000000001,47.43024],[-122.21928000000001,47.43022],[-122.21976000000001,47.43018],[-122.21994000000001,47.43019],[-122.21988,47.43039],[-122.21895,47.43403],[-122.21829000000001,47.436623],[-122.21819,47.43699],[-122.21806000000001,47.4375],[-122.21805,47.43754],[-122.21798000000001,47.437619999999995],[-122.21786000000002,47.43776],[-122.21746000000002,47.438229],[-122.21731000000001,47.438406],[-122.21702000000002,47.43874],[-122.21691000000001,47.439],[-122.216856,47.43911000000001],[-122.21683,47.439170000000004],[-122.21664000000001,47.439574],[-122.21657,47.43964],[-122.21633,47.43984],[-122.21620000000001,47.43996],[-122.21605000000001,47.440082],[-122.21594,47.44017],[-122.21569000000001,47.440391],[-122.21566000000001,47.440419999999996],[-122.21562000000002,47.44060399999999],[-122.21561200000001,47.440625999999995],[-122.21558,47.44077],[-122.21554,47.44097],[-122.21553,47.441019999999995],[-122.2155,47.44114],[-122.21584,47.44115],[-122.21649900000001,47.44117],[-122.21746000000002,47.44117],[-122.21759000000002,47.44117],[-122.21772000000001,47.44116399999999],[-122.21989,47.441122],[-122.22146000000001,47.441123],[-122.22382,47.44113],[-122.22395,47.441136],[-122.22703,47.44115],[-122.22779,47.44115],[-122.22976999999999,47.441158],[-122.23289,47.441168],[-122.2351,47.441174],[-122.23525000000001,47.441175],[-122.23648,47.441178],[-122.23883,47.441179999999996],[-122.239,47.441179999999996],[-122.23908,47.441176],[-122.24098000000001,47.441199999999995],[-122.24096000000002,47.44155],[-122.24096000000002,47.441955],[-122.24093,47.44274],[-122.24084,47.44464399999999],[-122.24078,47.44643],[-122.24077,47.44824],[-122.24074,47.449639999999995],[-122.2407,47.450669999999995],[-122.24069000000001,47.45122899999999],[-122.24063000000001,47.453239999999994],[-122.24065000000002,47.45383999999999],[-122.24064000000001,47.454390999999994],[-122.24056400000002,47.45643999999999],[-122.24055000000001,47.45705999999999],[-122.24054000000001,47.45765999999999],[-122.24049000000001,47.458331],[-122.24046000000001,47.459931],[-122.24041000000001,47.461189],[-122.24036000000001,47.462576000000006],[-122.24036000000001,47.463208],[-122.24041000000001,47.46359],[-122.24049000000001,47.463923],[-122.24058000000001,47.46418],[-122.24073,47.46454],[-122.24089900000001,47.46487],[-122.24112100000002,47.465140000000005],[-122.24121000000002,47.46528],[-122.24133,47.46544],[-122.24171000000001,47.46586],[-122.24218,47.466329],[-122.24308,47.46718],[-122.24379,47.46789],[-122.24415000000002,47.468230000000005],[-122.245841,47.469910000000006],[-122.24675,47.47077],[-122.24745000000001,47.471481999999995],[-122.24843,47.472417],[-122.24916200000001,47.47317],[-122.24960000000002,47.47363],[-122.25026000000001,47.47425],[-122.25091,47.474913],[-122.25206,47.47602],[-122.25269999999999,47.476639999999996],[-122.2536,47.47754],[-122.25407,47.47814],[-122.25488,47.479163]],[[-122.14863,47.504279999999994],[-122.14854,47.504213],[-122.14845,47.504159],[-122.14826000000001,47.504079],[-122.14811,47.50403],[-122.148,47.504],[-122.14783,47.50397],[-122.14776,47.503954],[-122.14767,47.503943],[-122.1474,47.503930000000004],[-122.14728000000001,47.503930000000004],[-122.14713,47.50394],[-122.14689,47.50398],[-122.14676999999999,47.50401],[-122.14668,47.504039],[-122.146579,47.504079],[-122.14648,47.50412],[-122.14596,47.50436],[-122.14596,47.50445],[-122.14596,47.50472],[-122.14596,47.50477],[-122.14595,47.505370000000006],[-122.145952,47.505508000000006],[-122.14595,47.505817],[-122.14594,47.50627],[-122.14594799999999,47.50633200000001],[-122.14596,47.506459],[-122.14618,47.50642],[-122.14683,47.50629],[-122.14707,47.50629],[-122.14826000000001,47.50627],[-122.1486,47.50627],[-122.1486,47.50601],[-122.14861,47.50545],[-122.14862000000001,47.504937],[-122.14863,47.504279999999994]]]},"name":"Renton"},{"boundary":{"type":"Polygon","coordinates":[[[-122.24927000000001,47.802440999999995],[-122.24878,47.80243],[-122.24847,47.80243],[-122.24837,47.802426999999994],[-122.24706000000002,47.80241],[-122.24615000000001,47.80274],[-122.245571,47.80293],[-122.24557,47.8029],[-122.24557,47.802679999999995],[-122.24557,47.80247],[-122.24499000000002,47.802457],[-122.24446000000002,47.80245],[-122.24395000000001,47.80244],[-122.24294,47.80243],[-122.24036000000001,47.802382],[-122.24005000000001,47.802371],[-122.238434,47.802330000000005],[-122.23834,47.802327],[-122.23806,47.802327],[-122.23786000000001,47.802324],[-122.23603,47.8023],[-122.23537999999999,47.8023],[-122.23465000000002,47.802288],[-122.23301000000001,47.80227],[-122.23265,47.80226],[-122.23262000000001,47.80227],[-122.232597,47.802278],[-122.23258,47.8023],[-122.23257,47.80232],[-122.23257,47.80238],[-122.23258,47.80261],[-122.23258,47.802839],[-122.23258,47.802893],[-122.23258,47.80296],[-122.23258,47.80314],[-122.23258,47.80321],[-122.23258,47.80344099999999],[-122.23259,47.804579999999994],[-122.23259,47.804649999999995],[-122.23259,47.804919999999996],[-122.23255,47.80492699999999],[-122.23241,47.804959999999994],[-122.232355,47.804972],[-122.23222000000001,47.805008],[-122.23218,47.80502],[-122.23217,47.80504],[-122.23206,47.80507],[-122.23198000000001,47.805109],[-122.23193,47.80514],[-122.23184,47.8052],[-122.23181000000001,47.805234],[-122.23176000000001,47.805279999999996],[-122.23171,47.805354],[-122.23168000000001,47.805403],[-122.23166000000002,47.80545],[-122.23164000000001,47.805479999999996],[-122.23151000000001,47.805786],[-122.23144,47.805785],[-122.2313,47.805783],[-122.23023,47.80577],[-122.22832999999999,47.80574],[-122.228366,47.805879],[-122.22868,47.80651],[-122.22917,47.80745999999999],[-122.22927,47.80768799999999],[-122.22944,47.80806],[-122.23009,47.809487999999995],[-122.2297,47.80949],[-122.22875499999999,47.80947999999999],[-122.22796000000001,47.809487999999995],[-122.2279,47.80937],[-122.22763,47.80936],[-122.22761000000001,47.8094],[-122.22614,47.8094],[-122.2261,47.80937],[-122.22515,47.809383],[-122.22358,47.8094],[-122.22358,47.80958],[-122.22358,47.809653],[-122.22358,47.80971],[-122.22356,47.80971],[-122.2235,47.809689999999996],[-122.22346999999999,47.80967999999999],[-122.22344,47.80967999999999],[-122.22337999999999,47.809659999999994],[-122.22332,47.80964899999999],[-122.22319,47.809619999999995],[-122.22313,47.80961],[-122.22306999999999,47.809599],[-122.223,47.80959],[-122.22294,47.80958],[-122.22287999999999,47.80957],[-122.22281,47.809562],[-122.22274999999999,47.809554],[-122.22268,47.809548],[-122.22262,47.80954],[-122.22255,47.80954],[-122.22249,47.80953],[-122.22241,47.80953],[-122.22141,47.80953],[-122.22072,47.80954],[-122.22062000000001,47.80954],[-122.22021000000001,47.80954],[-122.21990000000001,47.80954],[-122.21942600000001,47.80954],[-122.21914000000001,47.80954],[-122.21867,47.80954],[-122.21834,47.80954],[-122.21803,47.80954],[-122.21774,47.80955],[-122.21747,47.80955],[-122.21649000000001,47.809553],[-122.21628000000001,47.80955],[-122.21576999999999,47.80955],[-122.21548,47.809556],[-122.215171,47.809557],[-122.21497000000001,47.809565],[-122.21476000000001,47.809559],[-122.21437,47.80956],[-122.21381000000001,47.809563],[-122.21360000000001,47.809565],[-122.21335,47.809566],[-122.21281,47.80957],[-122.21274,47.809565],[-122.21256100000001,47.80957],[-122.21219,47.80957],[-122.21208,47.80957],[-122.21176000000001,47.80957],[-122.21141000000001,47.80958],[-122.21138,47.80958],[-122.20914,47.809580999999994],[-122.20914,47.809594],[-122.20904,47.809594],[-122.208107,47.809599999999996],[-122.2078,47.809596],[-122.20768000000001,47.809594],[-122.20753,47.80959],[-122.20735,47.80959],[-122.20684,47.80958],[-122.20485000000001,47.80957],[-122.20198,47.8097],[-122.20083,47.810513],[-122.19997,47.811319999999995],[-122.19966000000001,47.81155999999999],[-122.19958,47.811609999999995],[-122.1995,47.81165999999999],[-122.19941,47.811699999999995],[-122.19932999999999,47.81173999999999],[-122.19924,47.81177999999999],[-122.19915,47.81181999999999],[-122.19906,47.81185599999999],[-122.19896999999999,47.81188899999999],[-122.19887999999999,47.81191999999999],[-122.19872999999998,47.81195999999999],[-122.19863,47.81198799999999],[-122.19854,47.81201],[-122.19843999999999,47.81203],[-122.19832999999998,47.81205],[-122.19823,47.81206699999999],[-122.19812999999999,47.812079999999995],[-122.19802999999999,47.81209],[-122.19792000000001,47.8121],[-122.197818,47.812107],[-122.19766000000001,47.812112000000006],[-122.19756000000001,47.812112000000006],[-122.19735,47.8121],[-122.19724000000001,47.8121],[-122.19709,47.812079999999995],[-122.19698,47.81207],[-122.19686999999999,47.81205],[-122.19686999999999,47.812110000000004],[-122.19669,47.812079999999995],[-122.19651,47.812036],[-122.19506999999999,47.811683999999985],[-122.195,47.81166999999999],[-122.19493,47.81165999999999],[-122.19487,47.81164999999999],[-122.1948,47.81164999999999],[-122.19472999999999,47.81164499999999],[-122.19466000000001,47.81164999999999],[-122.19460000000001,47.81164999999999],[-122.19453,47.81165999999999],[-122.19447,47.81166999999999],[-122.1944,47.81168999999999],[-122.19434,47.811702999999994],[-122.19427900000001,47.81172399999999],[-122.19422000000002,47.811749999999996],[-122.19411000000001,47.81179999999999],[-122.193996,47.81186999999999],[-122.1932,47.8124],[-122.1925,47.812867999999995],[-122.19232,47.81299],[-122.19199400000001,47.81320099999999],[-122.19196000000001,47.81315],[-122.19172,47.813319],[-122.19149,47.813469999999995],[-122.19153,47.81352],[-122.19149,47.813551],[-122.19126400000002,47.81367999999999],[-122.19101,47.813855],[-122.19046,47.814209999999996],[-122.19036999999999,47.81427099999999],[-122.19027,47.81432399999999],[-122.19018,47.81437],[-122.19009,47.814409999999995],[-122.18991,47.81447999999999],[-122.18975999999999,47.81453],[-122.189681,47.81455],[-122.18948999999999,47.81459999999999],[-122.18938999999999,47.81462299999999],[-122.18929,47.81463999999999],[-122.18918599999999,47.81464999999999],[-122.18907999999999,47.814666999999986],[-122.18897999999999,47.814674999999994],[-122.18886999999998,47.81467999999999],[-122.18875999999999,47.81467999999999],[-122.18866,47.81467999999999],[-122.18854999999999,47.81467999999999],[-122.18843999999999,47.81466999999999],[-122.18832999999998,47.81466499999999],[-122.18822999999999,47.81464999999999],[-122.18811699999999,47.81463999999999],[-122.18802,47.81461899999999],[-122.18789,47.81459099999999],[-122.18782,47.814575],[-122.1876,47.814519999999995],[-122.18739,47.814474999999995],[-122.18717099999999,47.81441999999999],[-122.18695,47.814373999999994],[-122.18668,47.81431],[-122.18612,47.81417999999999],[-122.18611,47.81403999999999],[-122.18612999999999,47.80951],[-122.18612999999999,47.80943],[-122.18614,47.80721],[-122.18614,47.8055],[-122.18616999999999,47.80522],[-122.18616,47.804829999999995],[-122.18618,47.80433],[-122.18621,47.804053999999994],[-122.18622,47.80367],[-122.18624,47.80323],[-122.18621,47.802730000000004],[-122.18622,47.802485999999995],[-122.18622,47.8024],[-122.18623,47.8021],[-122.18623,47.801989999999996],[-122.186234,47.8013],[-122.18626,47.80009],[-122.18623,47.799634000000005],[-122.1862,47.799060000000004],[-122.18622,47.798310000000015],[-122.18622,47.79827000000001],[-122.18633999999999,47.79827000000001],[-122.18636999999998,47.797630000000005],[-122.18638999999999,47.797000000000004],[-122.18641,47.796490000000006],[-122.18643999999999,47.79560000000001],[-122.1865,47.794250000000005],[-122.18651,47.79381000000001],[-122.18652999999999,47.79335700000001],[-122.18655,47.79291000000001],[-122.18657999999999,47.79223000000001],[-122.18657999999999,47.79203000000001],[-122.18658599999999,47.79197200000001],[-122.18662,47.791137000000006],[-122.18656,47.791137000000006],[-122.18654,47.791137000000006],[-122.18645,47.790470000000006],[-122.18645,47.790440000000004],[-122.18643999999999,47.79023300000001],[-122.18641199999999,47.789885],[-122.18637999999999,47.78946],[-122.18634999999999,47.788990000000005],[-122.18633999999999,47.788830000000004],[-122.18629999999999,47.788204],[-122.18628,47.78793],[-122.18627,47.78778],[-122.18624,47.78744],[-122.18473999999999,47.787413],[-122.18366999999999,47.78739],[-122.18285999999999,47.787382],[-122.1822,47.787371],[-122.18102999999999,47.78735],[-122.18073999999999,47.78735],[-122.17963999999999,47.787329],[-122.17828999999999,47.787310000000005],[-122.17696,47.78729],[-122.17588999999998,47.78727],[-122.17559999999999,47.787259999999996],[-122.17556999999998,47.78683],[-122.17554999999999,47.78662],[-122.17553999999998,47.78636],[-122.17550599999998,47.78589],[-122.17546999999998,47.78544],[-122.17544999999998,47.78513000000001],[-122.17542999999998,47.78483],[-122.17540999999999,47.784530000000004],[-122.17537999999998,47.78407],[-122.17534999999998,47.783614],[-122.17531999999999,47.783150000000006],[-122.17528999999999,47.782700000000006],[-122.17526,47.782230000000006],[-122.17522999999998,47.7818],[-122.17519999999999,47.781355000000005],[-122.17516999999998,47.780885],[-122.17513999999998,47.780482],[-122.17511999999999,47.78004],[-122.17242999999998,47.78004],[-122.17241999999999,47.779934000000004],[-122.17240999999999,47.77986],[-122.17234999999998,47.77895000000001],[-122.17232999999997,47.778650000000006],[-122.17230899999998,47.77834000000001],[-122.17228999999999,47.778147000000004],[-122.17228999999999,47.77812000000001],[-122.17222999999998,47.77811000000001],[-122.17208999999998,47.77811000000001],[-122.17210999999999,47.77763],[-122.17133999999999,47.777628],[-122.17133999999999,47.77743],[-122.17133999999999,47.777346],[-122.17134999999999,47.777190000000004],[-122.17067999999999,47.777190000000004],[-122.17066999999999,47.776770000000006],[-122.16944,47.776770000000006],[-122.16944,47.776682],[-122.16944,47.77626],[-122.16944,47.77558500000001],[-122.16944,47.77511000000001],[-122.16941,47.77406],[-122.16937999999999,47.773297],[-122.16929,47.77042],[-122.16929,47.770351000000005],[-122.16886,47.770360000000004],[-122.16657,47.77039800000001],[-122.16652300000001,47.76935400000001],[-122.16651,47.76919000000001],[-122.16649,47.76900200000001],[-122.16646999999999,47.768890000000006],[-122.16636,47.768668000000005],[-122.1659,47.768080000000005],[-122.16529999999999,47.76733000000001],[-122.16492000000001,47.76683800000001],[-122.16452000000001,47.76634000000001],[-122.16409,47.765800000000006],[-122.16396,47.76563300000001],[-122.16393,47.765589000000006],[-122.16384,47.765440000000005],[-122.16382,47.76539000000001],[-122.16376,47.76527000000001],[-122.166424,47.76529000000001],[-122.16635,47.76169],[-122.16574999999999,47.761688],[-122.1636,47.76167],[-122.16345,47.76167],[-122.16342999999999,47.76131000000001],[-122.16342,47.761161],[-122.1634,47.760644],[-122.163365,47.759930000000004],[-122.16331,47.75888],[-122.16329,47.75851000000001],[-122.16328,47.758320000000005],[-122.16328,47.75824],[-122.1635,47.75824],[-122.16395,47.758230000000005],[-122.16393,47.75829],[-122.16414,47.758230000000005],[-122.16570999999999,47.758190000000006],[-122.16606,47.758190000000006],[-122.16613,47.758190000000006],[-122.16629999999999,47.75811000000001],[-122.16703,47.757915000000004],[-122.167598,47.757833],[-122.16915900000001,47.75776],[-122.16999,47.757799999999996],[-122.17117999999999,47.7579],[-122.17188999999999,47.75795],[-122.17256999999998,47.758030000000005],[-122.17340999999999,47.758160000000004],[-122.1745,47.75831000000001],[-122.17457999999999,47.758320000000005],[-122.17457999999999,47.75817000000001],[-122.17457999999999,47.75808],[-122.17457999999999,47.75788099999999],[-122.17459,47.7575],[-122.17459,47.757306],[-122.17459,47.75707],[-122.17482,47.757085999999994],[-122.17499,47.75712],[-122.17509999999999,47.757176],[-122.17516999999998,47.75721],[-122.17522999999998,47.75725],[-122.17538999999998,47.75732],[-122.17568999999999,47.75738],[-122.17595999999999,47.757345],[-122.17603999999999,47.75708899999999],[-122.17632999999998,47.756218000000004],[-122.17554999999999,47.755930000000006],[-122.17532999999997,47.75586],[-122.17533999999998,47.755825],[-122.17546999999998,47.755488],[-122.17586299999999,47.75454],[-122.17572999999997,47.75454],[-122.17571999999998,47.75414],[-122.17492999999999,47.75413],[-122.17493199999998,47.75389],[-122.17492999999999,47.753764],[-122.17496999999999,47.753619],[-122.17502999999998,47.75338],[-122.17502999999998,47.75334],[-122.17502999999998,47.753310000000006],[-122.17502999999998,47.753276],[-122.17501999999999,47.75324],[-122.17500999999999,47.75321],[-122.17499999999998,47.753170000000004],[-122.17497699999998,47.75314],[-122.17466999999999,47.75271000000001],[-122.17462,47.75263],[-122.17463,47.751852],[-122.174654,47.75096],[-122.17426,47.75096],[-122.1742,47.75096],[-122.17415,47.750959],[-122.17428,47.75041],[-122.17428,47.750383],[-122.17428,47.750350000000005],[-122.17436999999998,47.749340000000004],[-122.17443999999999,47.748450000000005],[-122.17446699999999,47.748126000000006],[-122.17463,47.748127000000004],[-122.17484999999999,47.74811800000001],[-122.17602999999998,47.74811000000001],[-122.17602999999998,47.747910000000005],[-122.17602999999998,47.747385],[-122.17491,47.747386],[-122.17483999999999,47.747386],[-122.17486,47.747130000000006],[-122.17486,47.74702],[-122.17483999999999,47.74702],[-122.17486999999998,47.746570000000006],[-122.1749,47.746050000000004],[-122.17492999999999,47.745608000000004],[-122.17492999999999,47.745580000000004],[-122.17501999999999,47.745580000000004],[-122.17726,47.745580000000004],[-122.17722,47.746320000000004],[-122.17721,47.74647],[-122.17810799999998,47.746475000000004],[-122.17992,47.746475000000004],[-122.18045,47.746472000000004],[-122.18042,47.74701],[-122.180403,47.74738],[-122.18135,47.747377],[-122.18327,47.74738],[-122.18355,47.74738],[-122.18612,47.747370000000004],[-122.18619,47.746030000000005],[-122.18612,47.745874],[-122.18616,47.745160000000006],[-122.18634999999999,47.745160000000006],[-122.18669,47.74516800000001],[-122.18692999999999,47.745171000000006],[-122.18721000000001,47.74517200000001],[-122.18745,47.74517300000001],[-122.18848999999999,47.74517900000001],[-122.18887999999998,47.744917],[-122.18912999999999,47.74492],[-122.18914099999999,47.7446],[-122.19155,47.74462],[-122.19153,47.745042000000005],[-122.19152000000001,47.745360000000005],[-122.19151000000001,47.74564],[-122.19169000000001,47.74564],[-122.19278599999998,47.74564],[-122.19251,47.74613300000001],[-122.19264,47.747],[-122.19275999999999,47.74703],[-122.192921,47.74705],[-122.19291,47.747458],[-122.19183,47.747443999999994],[-122.19144,47.747439],[-122.19056,47.74743],[-122.18769,47.74739],[-122.18763,47.747389],[-122.18758,47.749731000000004],[-122.1875,47.750678],[-122.18746999999999,47.751],[-122.19042,47.751025],[-122.19325,47.751059999999995],[-122.196602,47.75109],[-122.19667,47.749300000000005],[-122.19959,47.74933000000001],[-122.19986999999999,47.749328000000006],[-122.19999,47.74936],[-122.20008,47.749384],[-122.20017,47.749390000000005],[-122.20032,47.7494],[-122.20061000000001,47.749410000000005],[-122.20085,47.74942],[-122.20114600000001,47.749423],[-122.20114000000001,47.7498],[-122.20161000000002,47.749809],[-122.20192000000002,47.749810000000004],[-122.20188,47.74922],[-122.20190000000001,47.74906],[-122.20190000000001,47.74904],[-122.2018,47.749041],[-122.20138,47.749033000000004],[-122.20139,47.748720000000006],[-122.20051000000001,47.74872200000001],[-122.200518,47.748349000000005],[-122.20052000000001,47.74833000000001],[-122.20052300000002,47.74800200000001],[-122.20103,47.74801000000001],[-122.20105000000001,47.747557],[-122.20054,47.747550000000004],[-122.19988099999999,47.74754],[-122.19958199999999,47.74754],[-122.19961,47.74663],[-122.19988699999999,47.746643],[-122.19986999999999,47.746010000000005],[-122.19986,47.745746000000004],[-122.20043,47.745737000000005],[-122.20091000000001,47.745740000000005],[-122.20136000000001,47.74575000000001],[-122.20211,47.74577000000001],[-122.20211,47.745720000000006],[-122.20212000000001,47.74551000000001],[-122.20213,47.74521000000001],[-122.20213,47.745056000000005],[-122.20214,47.74485],[-122.20236999999999,47.74485],[-122.20322000000002,47.74485],[-122.20324000000001,47.744276],[-122.20325000000001,47.74394],[-122.20214,47.743930000000006],[-122.19682,47.743877],[-122.196833,47.74311000000001],[-122.1969,47.74025],[-122.20088,47.740277],[-122.20088,47.740170000000006],[-122.20087,47.740140000000004],[-122.20086,47.739920000000005],[-122.20084,47.739810000000006],[-122.20076999999999,47.739290000000004],[-122.20073599999999,47.73883000000001],[-122.20065000000001,47.73817000000001],[-122.20063,47.738020000000006],[-122.20082000000001,47.73792],[-122.20124000000001,47.737700000000004],[-122.20164300000002,47.73758],[-122.20202,47.73708],[-122.20235799999999,47.73666],[-122.20237999999999,47.73593000000001],[-122.202405,47.73484],[-122.20431,47.7348],[-122.20441000000001,47.7348],[-122.20471,47.734790000000004],[-122.20669000000001,47.73483],[-122.20746000000001,47.73483],[-122.20779,47.73482],[-122.2078,47.73466],[-122.20782000000001,47.734100000000005],[-122.20784400000001,47.73308],[-122.20785000000001,47.733008000000005],[-122.21323000000001,47.73308],[-122.21323000000001,47.733002000000006],[-122.21325000000002,47.73257100000001],[-122.21327000000001,47.73237000000001],[-122.21341000000001,47.732445000000006],[-122.21358000000001,47.73252000000001],[-122.21394000000001,47.732659000000005],[-122.21434,47.73279300000001],[-122.21439000000001,47.73281000000001],[-122.21495000000002,47.732966000000005],[-122.21527,47.73304],[-122.21559,47.73311000000001],[-122.21589,47.73317300000001],[-122.21605000000001,47.73321000000001],[-122.21697,47.733410000000006],[-122.21715000000002,47.733450000000005],[-122.21750000000002,47.733564],[-122.21782000000002,47.73368],[-122.21797000000001,47.73375000000001],[-122.21812000000001,47.733830000000005],[-122.21826000000001,47.73391000000001],[-122.21845,47.734030000000004],[-122.21854,47.734094],[-122.218762,47.73428],[-122.2188,47.73431800000001],[-122.21894,47.734456],[-122.21898,47.734500000000004],[-122.21913,47.73466],[-122.22076,47.736467],[-122.22141,47.73718],[-122.22236999999998,47.73820800000001],[-122.222462,47.73830600000001],[-122.22282999999999,47.73870000000001],[-122.22228,47.73866],[-122.22202,47.73865000000001],[-122.22092,47.738679000000005],[-122.22015,47.73866],[-122.21906400000002,47.738620000000004],[-122.21887,47.738620000000004],[-122.21895,47.740448],[-122.21900600000001,47.74161],[-122.21910000000001,47.743730000000006],[-122.21913,47.744217],[-122.21919000000001,47.745601],[-122.21926000000002,47.747031],[-122.21929000000002,47.747737],[-122.22372999999999,47.747769],[-122.22415000000001,47.74777],[-122.22397,47.750150000000005],[-122.22395,47.750510000000006],[-122.22389,47.75138],[-122.22376999999999,47.75138],[-122.22302,47.7514],[-122.22166000000001,47.751369],[-122.22121000000001,47.751376],[-122.22113,47.75139],[-122.22102000000001,47.75145],[-122.22095300000001,47.75155],[-122.22091,47.7517],[-122.22086999999999,47.751819999999995],[-122.22082,47.751980999999994],[-122.22072,47.752300000000005],[-122.22072999999999,47.75235500000001],[-122.22062000000001,47.75233000000001],[-122.21891000000001,47.75196],[-122.2188,47.751898],[-122.21878,47.752010000000006],[-122.21876,47.752050000000004],[-122.21878,47.75206],[-122.21876,47.752210000000005],[-122.21902000000001,47.75229],[-122.21959000000001,47.75244],[-122.22018,47.75255000000001],[-122.22076,47.752627],[-122.22079,47.752629],[-122.22079,47.752679],[-122.22075,47.752809],[-122.22073999999999,47.752843],[-122.22073999999999,47.75285],[-122.22048,47.75282],[-122.21994000000001,47.75273000000001],[-122.21993,47.752750000000006],[-122.21954000000001,47.752666],[-122.21912000000002,47.75256],[-122.2187,47.75244],[-122.21864000000001,47.75326],[-122.21393,47.75325],[-122.21392900000002,47.75383],[-122.21392000000002,47.75409],[-122.21565500000001,47.75412],[-122.21563,47.755210000000005],[-122.21565000000001,47.755210000000005],[-122.2157,47.755210000000005],[-122.21675,47.75522],[-122.21686000000001,47.755224],[-122.21695000000001,47.755224],[-122.21742000000002,47.755221],[-122.217398,47.75629],[-122.21689,47.75628],[-122.21470000000001,47.756259],[-122.21471000000001,47.75604],[-122.21471500000001,47.75573000000001],[-122.21388,47.755720000000004],[-122.21387,47.75586],[-122.21385000000001,47.75622],[-122.21385000000001,47.756301],[-122.21394000000001,47.756313000000006],[-122.21392000000002,47.756924],[-122.21391000000001,47.75707],[-122.21398,47.75707],[-122.21615000000001,47.757079999999995],[-122.21634,47.757082999999994],[-122.21804,47.757093],[-122.21823,47.75712],[-122.21831,47.757138000000005],[-122.21999000000001,47.75716],[-122.22043,47.75717],[-122.22082,47.757176],[-122.22086,47.757176],[-122.22088,47.756954],[-122.2213,47.757279999999994],[-122.221393,47.757355000000004],[-122.22151000000001,47.75745],[-122.22155000000001,47.75747],[-122.22169000000001,47.75759],[-122.22193,47.75777],[-122.22234399999999,47.758097],[-122.22264,47.758100000000006],[-122.2234,47.758123000000005],[-122.2234,47.758209],[-122.22339,47.75851000000001],[-122.22344,47.758569],[-122.22339,47.759394],[-122.22334,47.76021000000001],[-122.22331,47.760689],[-122.2233,47.760850000000005],[-122.22323,47.762167000000005],[-122.22323,47.76238000000001],[-122.22322000000001,47.76305000000001],[-122.22322000000001,47.763960000000004],[-122.22321000000001,47.76487],[-122.22321000000001,47.76579500000001],[-122.2232,47.767931000000004],[-122.22558,47.767969],[-122.22672999999999,47.76798],[-122.22703,47.76798],[-122.22792000000001,47.767990000000005],[-122.228,47.76800000000001],[-122.22797,47.768420000000006],[-122.22788,47.76932000000001],[-122.22787,47.76951000000001],[-122.22778,47.77205000000001],[-122.22319,47.77196],[-122.22319,47.77215000000001],[-122.22046999999999,47.77211700000001],[-122.22046999999999,47.773010000000006],[-122.22319,47.77306],[-122.22319,47.773230000000005],[-122.22319,47.773500000000006],[-122.22319,47.77396],[-122.22046999999999,47.773920000000004],[-122.22046,47.77462],[-122.2205,47.775281],[-122.22046999999999,47.776648],[-122.2232,47.77669],[-122.22615,47.77673000000001],[-122.22727,47.776740000000004],[-122.22761000000001,47.776740000000004],[-122.23183,47.7768],[-122.23292000000001,47.77682],[-122.23304,47.77682],[-122.23383,47.77681200000001],[-122.23411000000002,47.77682],[-122.23489000000001,47.77684],[-122.23568,47.77684],[-122.23594,47.776848],[-122.2365,47.77685],[-122.23717,47.77686],[-122.23771,47.77687],[-122.2381,47.77689],[-122.23821000000001,47.776891],[-122.23836999999999,47.776900000000005],[-122.23863,47.776904],[-122.23909,47.77691000000001],[-122.23917,47.77691000000001],[-122.23954,47.776924],[-122.24073,47.77695000000001],[-122.24082000000001,47.77693800000001],[-122.24093300000001,47.77694],[-122.24106000000002,47.776942000000005],[-122.24131000000001,47.77695000000001],[-122.24144000000001,47.77695000000001],[-122.24162000000003,47.77695000000001],[-122.24170000000001,47.77695000000001],[-122.24184000000001,47.77695000000001],[-122.24193000000001,47.77695000000001],[-122.24204,47.77696],[-122.24222000000002,47.77696],[-122.24258,47.776966],[-122.24260000000001,47.776970000000006],[-122.24276,47.77697500000001],[-122.24386000000001,47.776891],[-122.24385000000001,47.77764],[-122.24382000000001,47.779900000000005],[-122.24380000000001,47.780656],[-122.24380000000001,47.780678],[-122.24292000000001,47.78067],[-122.24242300000002,47.78066],[-122.24243,47.781279999999995],[-122.24243,47.784209],[-122.24377,47.78423],[-122.24377,47.784352000000005],[-122.24377,47.785090000000004],[-122.24376000000001,47.78737],[-122.24376000000001,47.78986],[-122.24377,47.791500000000006],[-122.24379,47.79512300000001],[-122.24380000000001,47.795530000000014],[-122.24382000000001,47.79873000000001],[-122.24922000000002,47.79881500000001],[-122.24923000000001,47.80016],[-122.24923000000001,47.80075],[-122.249232,47.80173],[-122.24927000000001,47.802440999999995]],[[-122.21351000000001,47.76422],[-122.21351000000001,47.76314000000001],[-122.21253,47.76313000000001],[-122.21242000000001,47.76313000000001],[-122.21228900000001,47.76313000000001],[-122.2123,47.764230000000005],[-122.21242000000001,47.764230000000005],[-122.21254,47.764230000000005],[-122.21351000000001,47.764244],[-122.21351000000001,47.76422]],[[-122.20964000000001,47.768046000000005],[-122.20754000000001,47.76802000000001],[-122.20734,47.76802000000001],[-122.20705000000001,47.76802000000001],[-122.20694400000001,47.76801700000001],[-122.20694400000001,47.76834000000001],[-122.20710700000001,47.76834000000001],[-122.20778,47.76835000000001],[-122.20776000000001,47.76876000000001],[-122.20776000000001,47.76877000000001],[-122.20722000000002,47.76876000000001],[-122.20707,47.76876000000001],[-122.20707,47.76879500000001],[-122.20788,47.768820000000005],[-122.20829,47.768820000000005],[-122.20828,47.768921000000006],[-122.20828,47.769270000000006],[-122.20963,47.76928],[-122.20963,47.76887000000001],[-122.20964000000001,47.768046000000005]],[[-122.21755000000002,47.76465],[-122.21702000000002,47.76466],[-122.21702000000002,47.764430000000004],[-122.21651000000001,47.764430000000004],[-122.21376000000001,47.764430000000004],[-122.21376000000001,47.76482],[-122.21516000000001,47.764848],[-122.21534,47.76484],[-122.21574,47.76483],[-122.21575,47.765260000000005],[-122.21565000000001,47.765260000000005],[-122.21565000000001,47.765690000000006],[-122.21756000000002,47.76571000000001],[-122.21755000000002,47.76465]],[[-122.21752000000002,47.762800000000006],[-122.21560000000001,47.76279000000001],[-122.21558,47.763560000000005],[-122.21497000000001,47.76351000000001],[-122.21375,47.763490000000004],[-122.21376000000001,47.764230000000005],[-122.21682000000001,47.76426],[-122.21681000000001,47.763723000000006],[-122.21625000000002,47.763720000000006],[-122.21624000000001,47.76341300000001],[-122.21752000000002,47.76341000000001],[-122.21752000000002,47.762800000000006]]]},"name":"Bothell"},{"boundary":{"type":"Polygon","coordinates":[[[-122.313422,47.77015000000001],[-122.313,47.770343000000004],[-122.31179,47.77078],[-122.31152000000002,47.770874],[-122.31131,47.770951000000004],[-122.31058,47.771190000000004],[-122.31053,47.7712],[-122.31019,47.771341],[-122.31,47.77143],[-122.30982999999999,47.771350000000005],[-122.30971,47.771300000000004],[-122.30941,47.7712],[-122.309236,47.771146],[-122.308602,47.77094],[-122.30823,47.77075000000001],[-122.30805,47.770630000000004],[-122.30725000000001,47.77062],[-122.30725000000001,47.77082],[-122.30725000000001,47.77106],[-122.30726000000001,47.77154],[-122.30636999999999,47.77154],[-122.30636,47.771100000000004],[-122.30604699999999,47.771094],[-122.30604,47.77089],[-122.30566999999999,47.77088],[-122.30547999999999,47.77088],[-122.30547999999999,47.77068],[-122.30546199999999,47.77013000000001],[-122.30281,47.770120000000006],[-122.30188,47.77011000000001],[-122.30128,47.77011000000001],[-122.30009,47.77011000000001],[-122.29950000000001,47.77011000000001],[-122.29742000000002,47.770100000000006],[-122.297437,47.77062],[-122.29745000000001,47.771091],[-122.29746000000002,47.77146],[-122.29746000000002,47.771510000000006],[-122.297478,47.772449],[-122.29750000000001,47.773230000000005],[-122.29751000000002,47.773782000000004],[-122.29753000000001,47.774719000000005],[-122.297531,47.7748],[-122.29754000000001,47.77531000000001],[-122.29755000000002,47.775600000000004],[-122.29757000000001,47.77653900000001],[-122.29759000000001,47.77742],[-122.29621000000002,47.777408],[-122.296037,47.777406],[-122.29592000000001,47.777404],[-122.29575,47.777403],[-122.29391000000001,47.777390000000004],[-122.29378,47.77738],[-122.29367,47.77738],[-122.29249,47.777370000000005],[-122.29242,47.777370000000005],[-122.29232999999999,47.77737200000001],[-122.29222800000001,47.777370000000005],[-122.29176000000001,47.777367],[-122.29163000000001,47.777366],[-122.29104000000001,47.777367],[-122.290932,47.777367],[-122.29052000000001,47.777359000000004],[-122.29036,47.777359000000004],[-122.29009,47.777328000000004],[-122.28997,47.777300000000004],[-122.28995,47.777300000000004],[-122.28982500000001,47.77729],[-122.28846999999999,47.77723],[-122.28824,47.77724],[-122.2881,47.777237],[-122.28806999999999,47.777235000000005],[-122.288,47.77723],[-122.28762000000002,47.7772],[-122.28758,47.77722],[-122.28757,47.777231],[-122.28755000000001,47.777234],[-122.28751000000001,47.77724],[-122.28739,47.77724],[-122.28716000000001,47.777242],[-122.28704,47.777242],[-122.28686,47.777243],[-122.28645,47.77724],[-122.28433,47.77726],[-122.28425000000001,47.777271],[-122.28381,47.777277],[-122.281806,47.77727],[-122.2817,47.77727],[-122.27985,47.77702],[-122.27978999999999,47.77702],[-122.2796,47.77702],[-122.27954,47.77702],[-122.27935,47.77702],[-122.27878999999999,47.77702],[-122.2786,47.77702],[-122.27856,47.77702],[-122.27836999999998,47.77702],[-122.27767,47.77702],[-122.27744,47.77702],[-122.27736999999999,47.77702],[-122.27717,47.77702],[-122.27711000000001,47.77702],[-122.2738,47.77702],[-122.27096,47.77702],[-122.27076999999998,47.77688],[-122.27066,47.776770000000006],[-122.27055,47.77664],[-122.27046,47.776495000000004],[-122.27039599999999,47.77635000000001],[-122.27029,47.776033000000005],[-122.27027,47.77599000000001],[-122.27019,47.775760000000005],[-122.27008,47.77544],[-122.26998,47.775180000000006],[-122.26985,47.774910000000006],[-122.26966000000002,47.774473],[-122.26961000000001,47.77436],[-122.26956000000001,47.77424],[-122.26951000000001,47.77408],[-122.26947,47.773950000000006],[-122.26945,47.773725000000006],[-122.26945,47.77348],[-122.26947,47.77318],[-122.26948,47.77311000000001],[-122.26949,47.77294500000001],[-122.26951000000001,47.77271000000001],[-122.26952000000001,47.772648000000004],[-122.26955000000001,47.77228],[-122.26956000000001,47.772124000000005],[-122.26959000000001,47.771879999999996],[-122.26962000000002,47.77156],[-122.26962000000002,47.771510000000006],[-122.26967,47.770996000000004],[-122.26967,47.770961],[-122.26968000000001,47.770919000000006],[-122.26971,47.770482],[-122.26972,47.770250000000004],[-122.26969000000001,47.77006],[-122.26966000000002,47.76995000000001],[-122.26962000000002,47.769821],[-122.26954,47.76964],[-122.26945,47.76950000000001],[-122.26939,47.769420000000004],[-122.269315,47.76933000000001],[-122.26917,47.76919000000001],[-122.26908,47.76912000000001],[-122.26891,47.76900500000001],[-122.26877999999999,47.76892000000001],[-122.26863,47.76885000000001],[-122.26848,47.76878000000001],[-122.26818,47.768660000000004],[-122.26805,47.76861500000001],[-122.26787,47.76857000000001],[-122.26756000000002,47.76852000000001],[-122.26749000000001,47.76850900000001],[-122.26852000000001,47.76851000000001],[-122.27052,47.76855000000001],[-122.2705,47.76650800000001],[-122.2705,47.766225000000006],[-122.2705,47.76578000000001],[-122.270496,47.76534000000001],[-122.27048699999999,47.764022000000004],[-122.27047999999999,47.76275000000001],[-122.27047999999999,47.76265000000001],[-122.27046999999999,47.76173000000001],[-122.26996000000001,47.761596000000004],[-122.27002999999999,47.76153000000001],[-122.26962000000002,47.7614],[-122.26954,47.76137000000001],[-122.26941000000001,47.76112800000001],[-122.27022000000001,47.760940000000005],[-122.27029999999999,47.761189],[-122.27049,47.761161],[-122.27053,47.76055000000001],[-122.27055,47.76011500000001],[-122.27059,47.75932],[-122.27065,47.75831000000001],[-122.27071099999999,47.7571],[-122.27072999999999,47.756709],[-122.27064,47.75677],[-122.27053,47.75684],[-122.27042,47.7569],[-122.27024,47.756997],[-122.27005,47.757079999999995],[-122.26986000000001,47.75715],[-122.26977,47.75719],[-122.26959000000001,47.757239999999996],[-122.26944,47.757279999999994],[-122.26927,47.75732],[-122.26908,47.75735],[-122.268682,47.756550000000004],[-122.26835,47.755720000000004],[-122.26819,47.75535000000001],[-122.27063,47.753707],[-122.27324,47.751982],[-122.27474,47.75096],[-122.27532999999998,47.750555000000006],[-122.27532,47.750116000000006],[-122.27754,47.749050000000004],[-122.2817,47.74707],[-122.28231,47.74456],[-122.28305,47.741530000000004],[-122.28416000000001,47.736940000000004],[-122.28312000000001,47.73611000000001],[-122.28195000000001,47.733610000000006],[-122.28353,47.73361200000001],[-122.28447,47.733650000000004],[-122.28461000000001,47.733650000000004],[-122.2848,47.73366],[-122.28489,47.73366],[-122.2858,47.73368],[-122.28586999999999,47.733720000000005],[-122.28596,47.73373000000001],[-122.28632,47.73373000000001],[-122.28671,47.733720000000005],[-122.28705000000001,47.733720000000005],[-122.28842999999999,47.73373000000001],[-122.28975,47.73373000000001],[-122.291098,47.733740000000004],[-122.29243,47.73375000000001],[-122.29246,47.735290000000006],[-122.29243,47.73554000000001],[-122.29236999999999,47.735800000000005],[-122.29219,47.73631000000001],[-122.29276,47.7368],[-122.2929,47.73688],[-122.29312000000002,47.73693000000001],[-122.2938,47.736940000000004],[-122.29389,47.73693900000001],[-122.29390000000001,47.737369],[-122.29798000000001,47.73736],[-122.29919000000001,47.737390000000005],[-122.29929000000001,47.737410000000004],[-122.29931,47.73922],[-122.29933,47.74102],[-122.298595,47.74103],[-122.29663000000001,47.741046999999995],[-122.29532999999999,47.74106],[-122.295,47.74106],[-122.29468000000001,47.74106],[-122.29472000000001,47.74279800000001],[-122.29501,47.742802000000005],[-122.29536999999999,47.742810000000006],[-122.29593,47.742810000000006],[-122.29603,47.742810000000006],[-122.29602000000001,47.74313600000001],[-122.29604,47.74426],[-122.29669000000001,47.74429],[-122.2967,47.74464],[-122.29715000000002,47.74465],[-122.2973,47.74465],[-122.297334,47.74673000000001],[-122.29734,47.748298000000005],[-122.29904,47.74831000000001],[-122.29904,47.748760000000004],[-122.29906000000001,47.74904],[-122.29894,47.7494],[-122.29895,47.74955500000001],[-122.29898,47.749700000000004],[-122.29911000000001,47.7498],[-122.29923400000001,47.74982],[-122.29988,47.74982],[-122.300354,47.74958],[-122.30053,47.749750000000006],[-122.30069,47.74968],[-122.30096,47.749610000000004],[-122.30105,47.7496],[-122.30115,47.74959500000001],[-122.30134199999999,47.749598000000006],[-122.30153,47.74962],[-122.30171,47.74967],[-122.30217999999999,47.74985],[-122.303,47.750158000000006],[-122.30302999999999,47.75107],[-122.30262,47.751979],[-122.30332,47.75265],[-122.30238999999999,47.753102000000005],[-122.30205,47.754138000000005],[-122.30206999999999,47.75488599999999],[-122.30211,47.755614],[-122.30232099999999,47.756997],[-122.30239999999999,47.757183],[-122.3025,47.757309],[-122.30263,47.757433],[-122.30284999999999,47.75757],[-122.30348,47.75787],[-122.30407,47.75815800000001],[-122.30419,47.75829],[-122.30424000000001,47.758390000000006],[-122.30398,47.7584],[-122.30372999999999,47.758387],[-122.30355,47.75867],[-122.30355,47.75871000000001],[-122.30354,47.758773000000005],[-122.3036,47.75922],[-122.30364,47.75936],[-122.303724,47.75949],[-122.30385,47.75958],[-122.30452000000001,47.759949],[-122.30499999999999,47.76021300000001],[-122.30577999999998,47.760642000000004],[-122.30658,47.76106],[-122.30699,47.76128],[-122.30734,47.761465],[-122.30753,47.761842],[-122.30764,47.762071000000006],[-122.30772,47.76223000000001],[-122.30808999999999,47.76301000000001],[-122.30646999999999,47.762907000000006],[-122.30627,47.76290000000001],[-122.30624,47.76290000000001],[-122.30624,47.763180000000006],[-122.30624,47.763326000000006],[-122.306244,47.76431000000001],[-122.30627,47.764320000000005],[-122.30645,47.764340000000004],[-122.30672,47.764390000000006],[-122.30673999999999,47.76519000000001],[-122.30676,47.76661300000001],[-122.30718,47.766600000000004],[-122.307587,47.76661500000001],[-122.30821,47.766625000000005],[-122.30814,47.76668],[-122.30814,47.766799000000006],[-122.30812999999999,47.767],[-122.30924,47.767004],[-122.30924,47.76755000000001],[-122.30935,47.768084],[-122.30949,47.76878800000001],[-122.30954,47.769020000000005],[-122.30954799999999,47.76906],[-122.30958,47.769200000000005],[-122.30976,47.769200000000005],[-122.31021000000001,47.769200000000005],[-122.31033,47.769200000000005],[-122.310328,47.76964],[-122.31033,47.769760000000005],[-122.31114400000001,47.769783000000004],[-122.31118000000001,47.769920000000006],[-122.31214,47.769923000000006],[-122.31215,47.76988],[-122.31222000000001,47.76953000000001],[-122.31394,47.76953100000001],[-122.313422,47.77015000000001]]]},"name":"Lake Forest Park"},{"boundary":{"type":"Polygon","coordinates":[[[-122.31803,47.531765],[-122.317958,47.53177],[-122.31681,47.53177],[-122.31328,47.53177],[-122.30918,47.53174],[-122.30892,47.53174],[-122.30842999999999,47.53173],[-122.30714,47.53173],[-122.305517,47.531715000000005],[-122.30542999999999,47.531530000000004],[-122.30529,47.53141],[-122.30516999999999,47.53132],[-122.304981,47.53127],[-122.30488799999999,47.53118],[-122.303502,47.530530000000006],[-122.30278999999999,47.530225],[-122.30229,47.53005],[-122.30142000000001,47.529385000000005],[-122.30119,47.52875000000001],[-122.30092,47.52769],[-122.30035,47.52653000000001],[-122.30014,47.52577000000001],[-122.30027,47.52541000000001],[-122.30049,47.52521000000001],[-122.30051,47.52517000000001],[-122.30054,47.52512000000001],[-122.30181,47.52459],[-122.30056,47.52458],[-122.3003,47.52458],[-122.30002999999999,47.524577],[-122.29994,47.52456],[-122.29398400000001,47.52442],[-122.29387,47.52442],[-122.29363000000001,47.52442],[-122.29339,47.523829],[-122.29225000000001,47.521388],[-122.29175000000001,47.52031000000001],[-122.29126000000002,47.519259999999996],[-122.29126000000002,47.518510000000006],[-122.29126000000002,47.517936],[-122.29129000000002,47.514889999999994],[-122.2913,47.51387999999999],[-122.2913,47.51374],[-122.2913,47.51367],[-122.29131000000001,47.51359],[-122.29131000000001,47.51341],[-122.29132000000001,47.51162699999999],[-122.29133,47.510838],[-122.29136900000002,47.51015],[-122.28768000000001,47.510193],[-122.28625000000001,47.51022],[-122.28576999999999,47.510220999999994],[-122.28486000000001,47.51015],[-122.28374,47.510059999999996],[-122.28344,47.510055],[-122.28304,47.51004],[-122.28245,47.510023],[-122.27853999999999,47.50981],[-122.27852999999999,47.509577],[-122.27837999999998,47.50887],[-122.27833999999999,47.50799],[-122.27823,47.50799],[-122.27789,47.507992],[-122.2777,47.507966999999994],[-122.27754,47.507936],[-122.27739,47.507892],[-122.27722000000001,47.507839999999995],[-122.27708,47.50777],[-122.276241,47.50723],[-122.27606999999999,47.507130000000004],[-122.27576999999998,47.507006999999994],[-122.27542999999999,47.506930000000004],[-122.27542999999999,47.507142],[-122.27542,47.50893000000001],[-122.27541,47.50963],[-122.27541,47.50978],[-122.27408,47.509750000000004],[-122.27155,47.5097],[-122.270069,47.50966],[-122.27006999999999,47.50954],[-122.27008699999999,47.50878],[-122.27012,47.507025],[-122.27014,47.50658],[-122.27014,47.50609],[-122.27016,47.505100000000006],[-122.27016,47.5049],[-122.27017,47.504475],[-122.27017,47.503177],[-122.2702,47.50263],[-122.27026000000001,47.50147],[-122.27078999999999,47.50159],[-122.27089,47.501613],[-122.27086999999999,47.50155],[-122.27085,47.501459999999994],[-122.27069999999999,47.500826999999994],[-122.27074999999999,47.50081],[-122.270671,47.5003],[-122.27059,47.50028],[-122.27045,47.50027],[-122.27032999999999,47.5003],[-122.27023,47.50028],[-122.27027,47.49758],[-122.27028,47.497327],[-122.27028,47.497059],[-122.27028,47.497],[-122.27029,47.496570000000006],[-122.270294,47.49635000000001],[-122.27029999999999,47.496030000000005],[-122.27029999999999,47.495650000000005],[-122.26981900000001,47.49564],[-122.26941000000001,47.49564],[-122.26853,47.4956],[-122.26448,47.492382000000006],[-122.26399,47.491988],[-122.26397,47.49136],[-122.26396000000001,47.49109],[-122.26396000000001,47.49042],[-122.26395000000001,47.48958],[-122.26409000000001,47.48907],[-122.26415000000001,47.488873],[-122.26425000000002,47.48864],[-122.26416000000002,47.48859],[-122.26377,47.488110000000006],[-122.26269,47.48741],[-122.26212000000001,47.48703999999999],[-122.26140000000001,47.48643],[-122.26077,47.48606099999999],[-122.2597,47.4853],[-122.25862000000001,47.484629999999996],[-122.25784,47.484182999999994],[-122.25778,47.484148],[-122.25823,47.484154],[-122.25747,47.483599999999996],[-122.25707,47.48323],[-122.25651,47.48257],[-122.2559,47.48176399999999],[-122.25542999999999,47.48113],[-122.25453,47.47993],[-122.25434,47.47993],[-122.25426000000002,47.479832],[-122.25398,47.479639999999996],[-122.25488,47.479163],[-122.25407,47.47814],[-122.2536,47.47754],[-122.25269999999999,47.476639999999996],[-122.25206,47.47602],[-122.25091,47.474913],[-122.25026000000001,47.47425],[-122.24960000000002,47.47363],[-122.24916200000001,47.47317],[-122.24843,47.472417],[-122.24745000000001,47.471481999999995],[-122.24675,47.47077],[-122.245841,47.469910000000006],[-122.24415000000002,47.468230000000005],[-122.24379,47.46789],[-122.24308,47.46718],[-122.24218,47.466329],[-122.24171000000001,47.46586],[-122.24133,47.46544],[-122.24121000000002,47.46528],[-122.24112100000002,47.465140000000005],[-122.24089900000001,47.46487],[-122.24073,47.46454],[-122.24058000000001,47.46418],[-122.24049000000001,47.463923],[-122.24041000000001,47.46359],[-122.24036000000001,47.463208],[-122.24036000000001,47.462576000000006],[-122.24041000000001,47.461189],[-122.24046000000001,47.459931],[-122.24049000000001,47.458331],[-122.24054000000001,47.45765999999999],[-122.24055000000001,47.45705999999999],[-122.24056400000002,47.45643999999999],[-122.24064000000001,47.454390999999994],[-122.24065000000002,47.45383999999999],[-122.24063000000001,47.453239999999994],[-122.24069000000001,47.45122899999999],[-122.2407,47.450669999999995],[-122.24074,47.449639999999995],[-122.24077,47.44824],[-122.24078,47.44643],[-122.24084,47.44464399999999],[-122.24093,47.44274],[-122.24096000000002,47.441955],[-122.24096000000002,47.44155],[-122.24098000000001,47.441199999999995],[-122.24143000000001,47.441199999999995],[-122.24176000000001,47.441199999999995],[-122.24229000000001,47.44120699999999],[-122.24333,47.44121],[-122.24369000000002,47.441216],[-122.24442000000002,47.441219999999994],[-122.24563,47.44123],[-122.24546000000001,47.440433],[-122.24535,47.43992],[-122.245336,47.43979],[-122.24535,47.4396],[-122.24538,47.43941],[-122.24545,47.439226],[-122.24551000000001,47.4391],[-122.24563,47.43894],[-122.24566000000002,47.43889],[-122.24587,47.438657],[-122.24616000000002,47.43844],[-122.24657,47.4382],[-122.24677,47.43806],[-122.24697,47.4379],[-122.24714000000002,47.43773],[-122.24729000000002,47.43755],[-122.24740000000001,47.437355000000004],[-122.24752000000002,47.437053],[-122.24779000000001,47.43603],[-122.24820000000001,47.43452],[-122.2484,47.43379],[-122.2485,47.43343],[-122.2548,47.433506],[-122.25593699999999,47.43352],[-122.25628,47.43352],[-122.25645,47.43352],[-122.25685,47.43332],[-122.25701000000001,47.433288],[-122.2573,47.43323],[-122.25787,47.43327],[-122.25846999999999,47.433530000000005],[-122.25927,47.43414],[-122.25954,47.43414],[-122.25995,47.43414],[-122.26085,47.434154],[-122.26172000000001,47.43416],[-122.26208,47.43416],[-122.26229000000001,47.43417],[-122.2625,47.43417],[-122.26288,47.43417],[-122.26295,47.434101],[-122.26301000000001,47.43402699999999],[-122.26307,47.43395],[-122.26311000000001,47.43387],[-122.26315000000001,47.43379],[-122.26318,47.4337],[-122.26321000000002,47.43362],[-122.26322000000002,47.433530000000005],[-122.26323000000001,47.433443999999994],[-122.26322000000002,47.433350000000004],[-122.26321000000002,47.43327],[-122.26319000000001,47.43318],[-122.26313,47.433032000000004],[-122.26286999999999,47.432900000000004],[-122.26245,47.43269],[-122.26184,47.43253500000001],[-122.26075,47.43227],[-122.2597,47.432010000000005],[-122.25899,47.43174],[-122.25845,47.43135],[-122.25823199999999,47.430566],[-122.25825,47.43049],[-122.25829999999999,47.430330000000005],[-122.25832,47.430248],[-122.25863,47.429811],[-122.25903,47.42958],[-122.25979099999999,47.429435000000005],[-122.26262900000002,47.429410000000004],[-122.26352000000001,47.42931000000001],[-122.26418000000001,47.42918],[-122.26493,47.42888],[-122.26532999999999,47.42866],[-122.26576999999999,47.42812000000001],[-122.26621000000002,47.42694],[-122.26722000000002,47.425540000000005],[-122.26725800000001,47.425233000000006],[-122.26706000000001,47.42489],[-122.26597,47.42383],[-122.26558,47.423300000000005],[-122.26544,47.42322],[-122.26536999999999,47.423054],[-122.26521000000001,47.42266],[-122.26525000000001,47.42182],[-122.26548,47.421330000000005],[-122.2664,47.42036],[-122.26718000000001,47.419399999999996],[-122.26734,47.419395],[-122.26767000000001,47.419399999999996],[-122.27076999999998,47.41937],[-122.27312,47.419332000000004],[-122.27436,47.41931],[-122.27624,47.41929],[-122.27661,47.41928299999999],[-122.27717,47.41927],[-122.27729000000001,47.41927],[-122.27739,47.41927999999999],[-122.27746,47.419293999999994],[-122.27751,47.41932],[-122.27756000000001,47.41935],[-122.2776,47.419399999999996],[-122.277628,47.41943],[-122.27765000000001,47.41947999999999],[-122.27765000000001,47.41954],[-122.27765000000001,47.41965],[-122.277671,47.419689999999996],[-122.27772,47.41973],[-122.277761,47.41976],[-122.277803,47.419765],[-122.27785,47.41977],[-122.27791300000001,47.41976],[-122.27798,47.41973],[-122.27876999999998,47.41931],[-122.27891,47.419259999999994],[-122.27904,47.41923],[-122.27921300000001,47.419219999999996],[-122.27942999999999,47.41923],[-122.27995,47.419219999999996],[-122.28020000000001,47.419219999999996],[-122.28028,47.41923],[-122.28036999999999,47.41925],[-122.28045,47.4193],[-122.28052000000001,47.41934],[-122.28044,47.41939],[-122.27824,47.420910000000006],[-122.27787,47.421310000000005],[-122.277575,47.42178],[-122.27736999999999,47.42231000000001],[-122.27726900000002,47.42296],[-122.27729000000001,47.423373000000005],[-122.27728,47.423669],[-122.27726000000001,47.42382],[-122.277218,47.42402],[-122.27714,47.42424],[-122.27705,47.42442],[-122.27695,47.42458],[-122.27676999999998,47.42481],[-122.27548999999999,47.42648],[-122.27539999999999,47.42658],[-122.27532999999998,47.42667],[-122.27512,47.42691000000001],[-122.27486,47.42716],[-122.27436,47.42758],[-122.27362000000001,47.42819000000001],[-122.27251,47.429100000000005],[-122.27132999999999,47.42994],[-122.27071,47.430383],[-122.27049,47.43054],[-122.27036,47.430640999999994],[-122.27029999999999,47.43068699999999],[-122.27019,47.43079],[-122.27015,47.430930000000004],[-122.27017,47.43103],[-122.26993,47.43109],[-122.26981,47.431154],[-122.26971,47.43127],[-122.26964000000001,47.431425],[-122.26963,47.431475],[-122.26961000000001,47.4317],[-122.26963,47.43207],[-122.26969000000001,47.432327],[-122.26971,47.43253000000001],[-122.26968000000001,47.43273000000001],[-122.26969000000001,47.43283],[-122.26968000000001,47.432900000000004],[-122.26993,47.432897],[-122.27025,47.43289],[-122.27085,47.432894],[-122.27105,47.432897],[-122.27096,47.433805],[-122.27089,47.4344],[-122.27052,47.434689999999996],[-122.27005,47.43519200000001],[-122.26957,47.435656],[-122.26943,47.43583],[-122.269367,47.43589],[-122.26865000000001,47.43682],[-122.26814,47.437148],[-122.26687,47.43775],[-122.26544,47.438431],[-122.26522900000002,47.43873000000001],[-122.2651,47.44294],[-122.26558,47.44308699999999],[-122.26581,47.44315],[-122.26706000000001,47.44356],[-122.26700000000001,47.443799999999996],[-122.26668000000001,47.445121],[-122.26562000000001,47.44511000000001],[-122.265319,47.44618],[-122.26512000000001,47.446819999999995],[-122.26506,47.446999999999996],[-122.26507,47.447542],[-122.26507,47.44780599999999],[-122.26563,47.44780899999999],[-122.26565000000001,47.44780899999999],[-122.26650000000001,47.44780299999999],[-122.26705000000001,47.447799999999994],[-122.26715000000002,47.447799999999994],[-122.26809,47.44779],[-122.26874,47.44779],[-122.26886,47.447779999999995],[-122.26888,47.449130000000004],[-122.26730300000001,47.449110000000005],[-122.26729500000002,47.449130000000004],[-122.26726000000002,47.44918],[-122.26723000000001,47.44922],[-122.26714000000001,47.44934],[-122.26709000000001,47.449419999999996],[-122.26702000000002,47.44956],[-122.26700000000001,47.449639999999995],[-122.26701000000001,47.44993],[-122.26702000000002,47.45062099999999],[-122.26704000000001,47.45113],[-122.26791900000002,47.451132],[-122.26793500000001,47.451719999999995],[-122.26889,47.45173],[-122.26891,47.452259],[-122.26822000000001,47.45225],[-122.26784,47.45225],[-122.26783,47.45233],[-122.26781000000001,47.45392],[-122.26780000000001,47.454179999999994],[-122.26778,47.45448999999999],[-122.27051,47.454499999999996],[-122.27132999999999,47.454499999999996],[-122.27141,47.456253999999994],[-122.27205,47.456255],[-122.27286999999998,47.456253999999994],[-122.27323,47.456253],[-122.27349,47.45625999999999],[-122.27385,47.45625999999999],[-122.27444,47.456253999999994],[-122.274928,47.45625999999999],[-122.27525,47.45625999999999],[-122.27615,47.45625999999999],[-122.2771,47.45625999999999],[-122.277399,47.456269999999996],[-122.27748,47.456267999999994],[-122.27806,47.456269999999996],[-122.27830999999999,47.45627999999999],[-122.27892,47.45627999999999],[-122.27956,47.45627999999999],[-122.28124000000001,47.456269999999996],[-122.28119000000001,47.45637],[-122.28119000000001,47.45643999999999],[-122.28118,47.456513],[-122.28117,47.45706299999999],[-122.28117,47.457469999999994],[-122.28117,47.457939999999994],[-122.28118,47.45963999999999],[-122.28128000000001,47.459635],[-122.28216,47.45963999999999],[-122.28277999999999,47.459638],[-122.28327,47.45963999999999],[-122.28565,47.459649999999996],[-122.28577999999999,47.45963],[-122.28586,47.459599999999995],[-122.28595,47.459559999999996],[-122.28604,47.459495],[-122.28617,47.459536],[-122.28632999999999,47.459575],[-122.28643799999999,47.459599999999995],[-122.28658,47.459619999999994],[-122.28672999999999,47.45963999999999],[-122.28703,47.45963999999999],[-122.28826000000001,47.459649999999996],[-122.289,47.459649999999996],[-122.29150000000001,47.459649999999996],[-122.29158100000001,47.459649999999996],[-122.29173,47.45965999999999],[-122.29183,47.45965999999999],[-122.29121000000002,47.460910000000005],[-122.29090000000001,47.46153],[-122.29067,47.46201000000001],[-122.29016000000001,47.46294],[-122.28997,47.463298],[-122.28989,47.46346],[-122.28983,47.463567],[-122.28976,47.46369],[-122.28974,47.463730000000005],[-122.28944,47.464310000000005],[-122.28902000000001,47.465140000000005],[-122.28882999999999,47.465540000000004],[-122.28875,47.465700000000005],[-122.288506,47.466170000000005],[-122.28811,47.46696],[-122.28878999999999,47.46696],[-122.2889,47.46724],[-122.28896,47.467452],[-122.28909,47.46815600000001],[-122.28919,47.4688],[-122.2894,47.470234],[-122.28946,47.470639999999996],[-122.28965000000001,47.47205],[-122.28967,47.472120000000004],[-122.2897,47.47225],[-122.28976999999999,47.47247],[-122.289795,47.472530000000006],[-122.28991500000001,47.472730000000006],[-122.29100000000001,47.47434],[-122.29150000000001,47.47508],[-122.29171000000001,47.475397],[-122.29316000000001,47.477519],[-122.29343,47.477985999999994],[-122.29422000000002,47.47936],[-122.29445000000001,47.47976],[-122.29464000000002,47.480117],[-122.29471000000001,47.48031],[-122.29497,47.481553],[-122.29514,47.48242],[-122.29528,47.48296],[-122.2954,47.48338],[-122.29566000000001,47.484199999999994],[-122.29587,47.484899999999996],[-122.29606000000001,47.485510000000005],[-122.29609,47.4856],[-122.29613,47.485730000000004],[-122.29624000000001,47.486095],[-122.29629000000001,47.48625],[-122.29637,47.48649699999999],[-122.29643,47.486639999999994],[-122.29667300000001,47.48732],[-122.29691000000001,47.48796399999999],[-122.29718000000001,47.48862],[-122.29723000000001,47.488730000000004],[-122.29706000000002,47.488695],[-122.29681000000001,47.488679999999995],[-122.29626000000002,47.488668999999994],[-122.29616000000001,47.48867],[-122.29604,47.488665],[-122.29545,47.488679999999995],[-122.29479,47.48869],[-122.29469000000002,47.488710000000005],[-122.29460000000002,47.48876],[-122.29424000000002,47.48876],[-122.29225000000001,47.48878],[-122.29256000000001,47.489459999999994],[-122.29281,47.490140000000004],[-122.29429200000001,47.490094],[-122.29432000000001,47.490625],[-122.29435000000001,47.491425],[-122.29442000000002,47.49142],[-122.29437,47.49231000000001],[-122.294075,47.49230000000001],[-122.29443,47.49284],[-122.29609,47.49535000000001],[-122.29641000000001,47.495830000000005],[-122.29641000000001,47.495837],[-122.29635,47.49586],[-122.29634,47.49629],[-122.29662000000002,47.497009],[-122.29676,47.497350000000004],[-122.29654000000001,47.497350000000004],[-122.29633,47.497350000000004],[-122.29631,47.497665],[-122.29641000000001,47.497679999999995],[-122.29645900000001,47.4977],[-122.29659000000001,47.49774],[-122.29672000000001,47.49781],[-122.29681000000001,47.497893],[-122.29686000000001,47.49794],[-122.29703500000001,47.498160000000006],[-122.29723000000001,47.49848],[-122.29744000000001,47.49882],[-122.29752000000002,47.498926000000004],[-122.29723000000001,47.499010000000006],[-122.29692000000001,47.499099],[-122.2973,47.499700000000004],[-122.29754000000001,47.5002],[-122.29775000000001,47.500710000000005],[-122.29782000000002,47.50095],[-122.29885,47.502916000000006],[-122.2989,47.50304],[-122.2994,47.504202],[-122.30035,47.50645],[-122.30036999999999,47.50649],[-122.30070699999999,47.507112000000006],[-122.300806,47.50729],[-122.30104,47.507639999999995],[-122.30182,47.50864],[-122.30214099999999,47.508976000000004],[-122.30216999999999,47.509],[-122.30257999999999,47.509409],[-122.30272,47.509530000000005],[-122.30274999999999,47.50956],[-122.30312,47.509888999999994],[-122.303679,47.510323],[-122.30372,47.510351],[-122.30404,47.51057],[-122.304843,47.51113],[-122.30497,47.51122099999999],[-122.30726000000001,47.512828],[-122.30689,47.51283],[-122.30669999999999,47.51283],[-122.30588999999999,47.51283],[-122.304041,47.512836],[-122.30395,47.512837],[-122.3043,47.51371],[-122.306,47.51783],[-122.30636,47.518710000000006],[-122.30726000000001,47.520891],[-122.30778,47.52215000000001],[-122.30848999999999,47.52428],[-122.30872999999998,47.524999],[-122.31049,47.527],[-122.31221900000001,47.52819000000001],[-122.31405000000001,47.529450000000004],[-122.314682,47.52984],[-122.31495000000001,47.529970000000006],[-122.31506999999999,47.53004],[-122.31801,47.531585],[-122.31835,47.531766],[-122.31803,47.531765]]]},"name":"Tukwila"},{"boundary":{"type":"Polygon","coordinates":[[[-121.99732000000002,47.73071000000001],[-121.99682000000001,47.730810000000005],[-121.99677,47.730810000000005],[-121.99564000000001,47.730888],[-121.99520000000001,47.730971000000004],[-121.99462000000003,47.73108],[-121.99455000000002,47.731170000000006],[-121.99415000000002,47.73129],[-121.99341000000001,47.731880999999994],[-121.99293,47.73254000000001],[-121.99278,47.73275000000001],[-121.99223300000001,47.73515000000001],[-121.99217,47.735431000000005],[-121.99162200000002,47.73651400000001],[-121.99152000000002,47.73706],[-121.99116000000002,47.73801000000001],[-121.99129000000002,47.73812100000001],[-121.99121000000002,47.73832000000001],[-121.99148000000001,47.738988000000006],[-121.99222000000002,47.739976000000006],[-121.99238,47.74051000000001],[-121.99216000000001,47.74078],[-121.99185000000001,47.740950000000005],[-121.99128000000002,47.74106],[-121.99074,47.74094],[-121.989759,47.74082],[-121.9893,47.74091200000001],[-121.989,47.74112],[-121.98895,47.74121],[-121.98836999999999,47.742143000000006],[-121.98831,47.742354000000006],[-121.98817,47.74286],[-121.98796000000002,47.74329],[-121.98778,47.74365],[-121.98766000000002,47.74416],[-121.98688,47.746006],[-121.98686000000001,47.746252000000005],[-121.98683,47.746693],[-121.98712000000002,47.7478],[-121.98713000000001,47.74797],[-121.98678699999999,47.74798],[-121.98616000000001,47.747999],[-121.98595,47.746300000000005],[-121.98566300000002,47.745540000000005],[-121.98546999999999,47.745020000000004],[-121.98542,47.74519000000001],[-121.9854,47.745470000000005],[-121.98537999999999,47.74597000000001],[-121.98539,47.74636],[-121.98539,47.746562000000004],[-121.98536999999999,47.74673000000001],[-121.98531,47.747004],[-121.98529,47.74707],[-121.98519,47.747222],[-121.98509,47.747330000000005],[-121.98504,47.747370000000004],[-121.98498900000001,47.7474],[-121.98502,47.747896999999995],[-121.985573,47.747868],[-121.98558,47.74819000000001],[-121.985983,47.74816200000001],[-121.98602000000001,47.74851000000001],[-121.98603,47.74868],[-121.98602000000001,47.74888],[-121.986,47.749077],[-121.98597,47.74926],[-121.98592900000001,47.749457],[-121.98584,47.74974],[-121.98577999999999,47.749891],[-121.98564,47.75016],[-121.98556,47.750288],[-121.985355,47.75058],[-121.98514,47.75082],[-121.98496000000002,47.75101],[-121.98482000000001,47.75113],[-121.9847,47.75123],[-121.98109000000001,47.751218],[-121.98108,47.75109],[-121.98079,47.7488],[-121.98065000000001,47.747710000000005],[-121.98063,47.747710000000005],[-121.98062700000001,47.747656],[-121.98062700000001,47.74762],[-121.98063,47.747550000000004],[-121.97923,47.74754],[-121.97911,47.74754],[-121.978204,47.74754],[-121.97778,47.74754],[-121.97761000000001,47.74754],[-121.97751000000001,47.747550000000004],[-121.97741,47.747566],[-121.97725000000001,47.747609],[-121.97724000000001,47.74761],[-121.97720000000001,47.74756],[-121.9754,47.747558000000005],[-121.975051,47.74756],[-121.97501,47.74568],[-121.97498,47.743770000000005],[-121.97495,47.742160000000005],[-121.97493,47.740950000000005],[-121.97143,47.74092],[-121.97141,47.74082],[-121.9714,47.74069],[-121.97141,47.74047],[-121.97142000000001,47.73997000000001],[-121.97141500000001,47.73975000000001],[-121.97145,47.739560000000004],[-121.97147,47.73944],[-121.97148,47.73919000000001],[-121.97148,47.739000000000004],[-121.97147,47.738850000000006],[-121.97145,47.73875000000001],[-121.97141,47.73857000000001],[-121.97142000000001,47.73841000000001],[-121.97138,47.73774],[-121.97113,47.73774],[-121.97052000000001,47.737790000000004],[-121.96947,47.73788],[-121.96947,47.743747],[-121.96946000000001,47.747479999999996],[-121.96483300000001,47.746997],[-121.96495000000002,47.74311000000001],[-121.96008,47.742747],[-121.95962000000002,47.74270800000001],[-121.95952000000001,47.7409],[-121.95926000000001,47.740866999999994],[-121.95898799999999,47.74083],[-121.95865,47.740804],[-121.95214,47.740237],[-121.95022000000002,47.74008],[-121.94926000000002,47.73998],[-121.94933,47.7419],[-121.94904000000001,47.7419],[-121.94800000000001,47.741865999999995],[-121.94399000000001,47.74176],[-121.94390000000001,47.738124000000006],[-121.94664000000002,47.73818000000001],[-121.94664000000002,47.73771000000001],[-121.94663000000001,47.737168000000004],[-121.94659000000001,47.736850000000004],[-121.94658500000001,47.73671000000001],[-121.94655000000002,47.736201],[-121.94723000000002,47.736230000000006],[-121.94788000000001,47.73626],[-121.94774000000001,47.7344],[-121.94861000000002,47.734410000000004],[-121.94903000000001,47.734410000000004],[-121.94934,47.73443],[-121.94944000000001,47.734435000000005],[-121.95153,47.734609],[-121.95318,47.734750000000005],[-121.95542,47.73494],[-121.95656300000002,47.73503200000001],[-121.9567,47.735043000000005],[-121.95669000000001,47.73485],[-121.95667,47.73452],[-121.95665000000001,47.734100000000005],[-121.95924000000001,47.734300000000005],[-121.959218,47.73388],[-121.95921000000001,47.733647],[-121.95920000000001,47.73337000000001],[-121.95915000000001,47.73243000000001],[-121.95915000000001,47.73232000000001],[-121.95913,47.731957],[-121.9591,47.73152],[-121.9591,47.73098],[-121.95911000000001,47.730790000000006],[-121.95909,47.73059000000001],[-121.95907,47.730449],[-121.95904,47.73031700000001],[-121.95901,47.730197000000004],[-121.9589,47.72995000000001],[-121.95869,47.729586000000005],[-121.95811,47.72892500000001],[-121.95795000000001,47.728767000000005],[-121.95782700000001,47.728660000000005],[-121.95756000000002,47.728480000000005],[-121.95687,47.72802000000001],[-121.95664000000001,47.727854],[-121.95519,47.72683000000001],[-121.95441000000001,47.726282000000005],[-121.95421000000002,47.72614000000001],[-121.95475,47.72590000000001],[-121.95596,47.72540000000001],[-121.9563,47.72525800000001],[-121.95732000000001,47.72476],[-121.957232,47.72467],[-121.9567,47.72410000000001],[-121.9565,47.72390000000001],[-121.9564,47.723800000000004],[-121.95687,47.72359000000001],[-121.95786000000001,47.72315100000001],[-121.9588,47.72276000000001],[-121.95964000000001,47.72243800000001],[-121.95907,47.72164],[-121.95891,47.721430000000005],[-121.95882,47.72133000000001],[-121.95539,47.72272000000001],[-121.95449,47.721742000000006],[-121.95546999999999,47.72134200000001],[-121.95818,47.720240000000004],[-121.9583,47.72018500000001],[-121.95837999999999,47.720304000000006],[-121.95904,47.72119000000001],[-121.95912000000001,47.72129],[-121.95927,47.721500000000006],[-121.95917,47.721590000000006],[-121.959754,47.72239500000001],[-121.95999,47.72272000000001],[-121.96001000000001,47.72275000000001],[-121.96015400000002,47.72294600000001],[-121.96022000000002,47.723060000000004],[-121.96032000000001,47.723247],[-121.96035,47.72334000000001],[-121.96042000000001,47.72355500000001],[-121.96060000000001,47.72415000000001],[-121.96073,47.72442],[-121.96082900000002,47.72457000000001],[-121.96090000000001,47.724646],[-121.96094000000001,47.72469],[-121.96110000000002,47.724790000000006],[-121.96129000000002,47.72488],[-121.96153000000001,47.72500000000001],[-121.96180000000001,47.725077000000006],[-121.9621,47.72511700000001],[-121.96225000000001,47.725130000000014],[-121.96244,47.725130000000014],[-121.96271,47.72510000000001],[-121.96309000000001,47.72502600000001],[-121.96347,47.724894],[-121.96374,47.724790000000006],[-121.96403000000001,47.724700000000006],[-121.96431000000001,47.7246],[-121.96465000000002,47.72437000000001],[-121.96523,47.723670000000006],[-121.96525000000001,47.72338200000001],[-121.96692000000002,47.723380000000006],[-121.96688,47.72289000000001],[-121.96687,47.72275000000001],[-121.96819900000001,47.72275000000001],[-121.96822000000002,47.72466],[-121.96819900000001,47.724810000000005],[-121.968334,47.724824],[-121.96932000000001,47.72488],[-121.96958000000001,47.72489],[-121.96985000000001,47.72491000000001],[-121.970445,47.72491000000001],[-121.97101,47.724908000000006],[-121.971334,47.72490500000001],[-121.97168,47.724899],[-121.97332,47.72487],[-121.97351,47.72487],[-121.97555,47.72484],[-121.97616000000001,47.72484],[-121.9768,47.724830000000004],[-121.97832,47.72487],[-121.97846999999999,47.72488],[-121.97863,47.724900000000005],[-121.97874999999999,47.724920000000004],[-121.97892999999999,47.724970000000006],[-121.97907,47.72502000000001],[-121.97926000000001,47.725113000000015],[-121.97974099999999,47.72545000000001],[-121.98,47.72559700000001],[-121.98011000000001,47.72564500000001],[-121.98020000000001,47.725680000000004],[-121.98024000000001,47.72569000000001],[-121.98048,47.72572500000001],[-121.98061000000001,47.72573000000001],[-121.98068,47.72574000000001],[-121.98086,47.72574000000001],[-121.98093,47.72574000000001],[-121.98421000000002,47.72570000000001],[-121.98472000000001,47.72569000000001],[-121.9854,47.72567000000001],[-121.98565,47.725660000000005],[-121.98572999999999,47.725660000000005],[-121.98635,47.72563100000001],[-121.98659,47.725620000000006],[-121.986815,47.72557000000001],[-121.98684,47.72583000000001],[-121.98700000000001,47.72751000000001],[-121.98704000000001,47.727889],[-121.98709000000001,47.72839000000001],[-121.98710700000001,47.72853000000001],[-121.98715000000001,47.72896000000001],[-121.98723900000002,47.72896000000001],[-121.98751000000001,47.72895000000001],[-121.98791000000001,47.72896000000001],[-121.98788,47.72855000000001],[-121.99057,47.72860000000001],[-121.99056000000002,47.72862000000001],[-121.99101000000002,47.728640000000006],[-121.99144000000001,47.72863500000001],[-121.99682000000001,47.728640000000006],[-121.99728400000002,47.728640000000006],[-121.99730000000001,47.72966],[-121.99731000000001,47.73019600000001],[-121.99732000000002,47.730489],[-121.99732000000002,47.73071000000001]]]},"name":"Duvall"},{"boundary":{"type":"Polygon","coordinates":[[[-121.92587999999999,47.6443],[-121.924407,47.646349],[-121.92429000000001,47.64701],[-121.92403,47.647701],[-121.92408,47.648631],[-121.92416000000001,47.648691],[-121.92398,47.649210000000004],[-121.924,47.649353000000005],[-121.924046,47.649770000000004],[-121.92378,47.650459999999995],[-121.92385,47.65063],[-121.9238,47.650904],[-121.92360000000001,47.65155],[-121.92303,47.651543999999994],[-121.91747000000001,47.65153],[-121.91711000000002,47.65153],[-121.91482000000002,47.65145],[-121.91475000000001,47.65146299999999],[-121.91451000000002,47.65145],[-121.91404000000001,47.65145],[-121.91329000000002,47.65145999999999],[-121.91281000000001,47.65145],[-121.91267,47.65145],[-121.91194000000002,47.651439999999994],[-121.91188000000001,47.65179],[-121.91075000000001,47.65182399999999],[-121.9107,47.652034],[-121.91062000000002,47.65285],[-121.90749000000001,47.652879999999996],[-121.90749000000001,47.653324],[-121.90728300000002,47.653332000000006],[-121.90708000000001,47.653330000000004],[-121.90688,47.653330000000004],[-121.90451000000002,47.653310000000005],[-121.90235,47.65329],[-121.89822000000001,47.653321],[-121.89615,47.653326],[-121.89604,47.651513],[-121.89645,47.651506],[-121.90149000000001,47.651425999999994],[-121.90147,47.65131],[-121.90144000000001,47.649710000000006],[-121.90141000000001,47.648083],[-121.89923,47.648084],[-121.89722000000002,47.648700000000005],[-121.89681,47.648830000000004],[-121.89681,47.64893000000001],[-121.89681,47.64906],[-121.89554,47.64906],[-121.89556,47.64819000000001],[-121.89556999999999,47.647872],[-121.89549,47.647859999999994],[-121.8954,47.647833],[-121.89484,47.647679999999994],[-121.89429000000001,47.64754],[-121.89327,47.647279999999995],[-121.89246,47.647079999999995],[-121.89067,47.646657],[-121.89068,47.645900000000005],[-121.89065000000001,47.6452],[-121.89059,47.6449],[-121.8906,47.644819999999996],[-121.89061000000001,47.6448],[-121.89066000000001,47.644707],[-121.89068,47.64467],[-121.89066000000001,47.64445],[-121.89063,47.64421],[-121.89125000000001,47.64421],[-121.893,47.64422],[-121.89356000000001,47.644220999999995],[-121.894,47.64422],[-121.8961,47.64423],[-121.89611000000001,47.644631],[-121.89609,47.6449],[-121.89766000000002,47.644619999999996],[-121.89765000000001,47.644237],[-121.89762000000002,47.643737],[-121.89761000000001,47.64189],[-121.897681,47.64188599999999],[-121.89824,47.64189699999999],[-121.89842,47.6419],[-121.8986,47.64191],[-121.89885,47.641937],[-121.89917,47.641945],[-121.89961000000001,47.641980999999994],[-121.9,47.641988],[-121.90050000000001,47.642019000000005],[-121.90056000000001,47.642022000000004],[-121.90101000000001,47.642069],[-121.90148,47.642106000000005],[-121.90148,47.642010000000006],[-121.90148,47.6419],[-121.90148,47.64181],[-121.90147,47.640862],[-121.90147,47.64084],[-121.90146800000001,47.64045],[-121.90143,47.639764],[-121.90145000000001,47.63942],[-121.90143,47.63938],[-121.90141000000001,47.63936],[-121.901374,47.63927],[-121.90137,47.63906],[-121.90137,47.639030000000005],[-121.90142000000002,47.638940000000005],[-121.90145500000001,47.63890000000001],[-121.90153000000001,47.63884],[-121.90167000000001,47.63877900000001],[-121.90180000000001,47.63875000000001],[-121.90198000000001,47.638740000000006],[-121.90213,47.638740000000006],[-121.90291,47.638740000000006],[-121.90416000000002,47.638780000000004],[-121.90436000000001,47.63879000000001],[-121.904368,47.63897000000001],[-121.90444000000001,47.64008],[-121.90443,47.64036],[-121.90442000000002,47.640390000000004],[-121.90439,47.640423],[-121.90434,47.640454],[-121.90432000000001,47.641408],[-121.90432000000001,47.64153],[-121.905043,47.641408],[-121.90643,47.640910000000005],[-121.90694300000001,47.64082],[-121.90712000000002,47.640859999999996],[-121.90729000000002,47.64083],[-121.90738,47.64081],[-121.908461,47.639963],[-121.90868,47.63969],[-121.9087,47.63893900000001],[-121.90879,47.63862],[-121.90908,47.63826],[-121.9093,47.63811000000001],[-121.90978,47.637910000000005],[-121.91019000000001,47.637750000000004],[-121.91087,47.63766],[-121.91100000000002,47.637530000000005],[-121.91167000000002,47.63762],[-121.91415000000002,47.638044],[-121.91519000000001,47.637879999999996],[-121.91542000000001,47.63791500000001],[-121.9163,47.637856],[-121.9163,47.637879999999996],[-121.9163,47.638020000000004],[-121.91631000000001,47.638040000000004],[-121.91634,47.63888],[-121.91636000000001,47.63924],[-121.91636000000001,47.639509000000004],[-121.91635000000001,47.639583],[-121.91632000000001,47.63984],[-121.91628000000001,47.64002],[-121.91622100000002,47.64022],[-121.91616000000002,47.640387],[-121.91568000000001,47.641639999999995],[-121.91565000000001,47.64171],[-121.91547,47.64218],[-121.91525000000001,47.642830000000004],[-121.91522000000002,47.64294],[-121.91521000000002,47.643015000000005],[-121.91521000000002,47.643130000000006],[-121.91522000000002,47.6433],[-121.91542000000001,47.64394],[-121.91680000000001,47.643910000000005],[-121.91678,47.64417],[-121.91678,47.64427],[-121.91826000000002,47.64425],[-121.92049,47.644259999999996],[-121.921135,47.644259999999996],[-121.92173,47.644259999999996],[-121.92276,47.644259999999996],[-121.92456000000001,47.64426699999999],[-121.92536,47.64429],[-121.92587999999999,47.6443]]]},"name":"Carnation"},{"boundary":{"type":"Polygon","coordinates":[[[-122.39454300000001,47.77792],[-122.39332999999999,47.777930000000005],[-122.39319400000001,47.777929],[-122.39198,47.777930000000005],[-122.39013,47.777910000000006],[-122.38981,47.77789],[-122.38963,47.77789],[-122.38944,47.777896],[-122.38855,47.7779],[-122.38826999999999,47.777910000000006],[-122.38826,47.777910000000006],[-122.3882,47.777910000000006],[-122.38696,47.77792],[-122.38287999999999,47.77787],[-122.38273999999998,47.77787],[-122.38019,47.77784],[-122.38002999999999,47.777843],[-122.38001,47.777843],[-122.3791,47.77786],[-122.37893999999999,47.77786],[-122.37809999999999,47.77785],[-122.37747999999999,47.77785],[-122.37722000000001,47.777846],[-122.37554099999998,47.77783],[-122.37544999999999,47.77783],[-122.374413,47.77782],[-122.37429,47.77782],[-122.37424,47.77782],[-122.37312,47.77781],[-122.37212999999998,47.777801],[-122.37207999999998,47.777801],[-122.37058999999999,47.77783],[-122.37033999999998,47.77784],[-122.37003999999999,47.7779],[-122.37003999999999,47.77788699999999],[-122.37003999999999,47.77787],[-122.37001,47.77783],[-122.36998,47.777801],[-122.36994,47.77778],[-122.36989,47.777761],[-122.369841,47.77775200000001],[-122.36971,47.777753000000004],[-122.36958,47.777755000000006],[-122.36921000000001,47.777750000000005],[-122.36891,47.777764],[-122.36846999999999,47.777794],[-122.36822000000001,47.777798000000004],[-122.36799,47.777795000000005],[-122.367089,47.777750000000005],[-122.36691400000001,47.777750000000005],[-122.366685,47.77774],[-122.36519,47.77774],[-122.36496000000001,47.777730000000005],[-122.36462000000002,47.777716000000005],[-122.36439,47.777716000000005],[-122.36427,47.777721],[-122.36421400000002,47.777722000000004],[-122.36401000000001,47.77772],[-122.3631,47.777730000000005],[-122.36298,47.77774],[-122.36289,47.777750000000005],[-122.36162000000002,47.77778],[-122.36133,47.777764],[-122.36065,47.777767],[-122.35887999999998,47.777766],[-122.35866999999999,47.777766],[-122.358111,47.777766],[-122.35803999999999,47.777766],[-122.35652,47.777766],[-122.35646999999999,47.777766],[-122.356209,47.777766],[-122.35594999999999,47.777766],[-122.35548999999999,47.777770000000004],[-122.35542,47.777770000000004],[-122.35501,47.777770000000004],[-122.35495,47.777770000000004],[-122.35494,47.777770000000004],[-122.35486,47.777770000000004],[-122.35401,47.777770000000004],[-122.35367,47.777770000000004],[-122.35271999999999,47.77778],[-122.35265,47.77778],[-122.35262999999999,47.77778],[-122.35234999999999,47.77778],[-122.35181,47.77778],[-122.35149,47.77778],[-122.35091,47.77778],[-122.34994,47.777785],[-122.34986,47.777785],[-122.34778,47.77779],[-122.34771,47.77779],[-122.34764000000001,47.77779],[-122.34633,47.77779],[-122.34631,47.777794],[-122.34629000000001,47.777794],[-122.34622000000002,47.777794],[-122.34615000000001,47.777794],[-122.34189,47.77779],[-122.34183,47.77779],[-122.34176000000001,47.77779],[-122.34094,47.77779],[-122.34089,47.777785],[-122.34084,47.777785],[-122.34036,47.777785],[-122.340317,47.77778],[-122.3397,47.77778],[-122.33964,47.77778],[-122.33879999999999,47.77778],[-122.33824,47.77778],[-122.33823,47.77778],[-122.33815,47.77778],[-122.33784,47.77778],[-122.33760000000001,47.77778],[-122.337539,47.77778],[-122.33736,47.77778],[-122.3373,47.77778],[-122.33539999999999,47.777770000000004],[-122.33536,47.777770000000004],[-122.33532,47.777770000000004],[-122.33355,47.77767],[-122.33239999999999,47.77764],[-122.33,47.77762],[-122.32995,47.77762],[-122.32994,47.77762],[-122.32832999999998,47.777590000000004],[-122.32763,47.777632000000004],[-122.327334,47.77765],[-122.327304,47.77765],[-122.32683999999999,47.777679],[-122.3266,47.77768],[-122.32654,47.77768],[-122.32628,47.77769],[-122.32469999999999,47.777665],[-122.32463,47.777663999999994],[-122.324554,47.777663],[-122.32309,47.77765],[-122.32229999999998,47.77765],[-122.32185,47.77764],[-122.3214,47.77764],[-122.32128,47.77764],[-122.32121000000001,47.77764],[-122.31968,47.777631],[-122.31944,47.77763],[-122.31865,47.777625],[-122.31826000000001,47.777623],[-122.318,47.777622],[-122.31796000000001,47.777622],[-122.31790000000001,47.77762],[-122.31787,47.77762],[-122.31781000000001,47.77762],[-122.31765000000001,47.77762],[-122.31756000000001,47.77762],[-122.31746000000001,47.77762],[-122.31731,47.77762],[-122.31709900000001,47.77762],[-122.316887,47.77762],[-122.3168,47.77762],[-122.31669000000001,47.77762],[-122.31665000000001,47.77762],[-122.31578999999999,47.77762],[-122.31568,47.77762],[-122.31525,47.777619],[-122.31509,47.77761],[-122.31488,47.7776],[-122.31388,47.77758],[-122.313422,47.777570000000004],[-122.313328,47.777570000000004],[-122.31314,47.777570000000004],[-122.31313,47.777570000000004],[-122.313,47.77756],[-122.31267,47.77756],[-122.31265,47.77756],[-122.31232999999999,47.77756],[-122.31229,47.77756],[-122.31192000000001,47.777557],[-122.31190000000001,47.77756],[-122.31156000000001,47.777550000000005],[-122.31093,47.777546],[-122.31071,47.77754],[-122.31048,47.77754],[-122.30982999999999,47.77754],[-122.30955,47.777542000000004],[-122.30929,47.77754],[-122.30923,47.77754],[-122.30904,47.777539000000004],[-122.30896,47.77753800000001],[-122.30882999999999,47.77753500000001],[-122.30836,47.777533000000005],[-122.3081,47.777530000000006],[-122.30795,47.77752],[-122.30758,47.77751000000001],[-122.30669999999999,47.77751000000001],[-122.30632999999999,47.7775],[-122.30566999999999,47.777497],[-122.30523,47.77749],[-122.30512,47.77749],[-122.30498,47.777491],[-122.30421300000002,47.77748],[-122.30322000000001,47.77747],[-122.30301,47.77747],[-122.30297999999999,47.77747],[-122.30194,47.777463],[-122.30176,47.77746],[-122.30155,47.777459],[-122.301482,47.77746],[-122.30136,47.777456],[-122.3013,47.777456],[-122.301298,47.777456],[-122.29974,47.77744],[-122.29956000000001,47.77744],[-122.29938,47.77744],[-122.29934,47.77744],[-122.29916000000001,47.77743],[-122.29821000000001,47.77743],[-122.29812000000001,47.77742],[-122.29803,47.77742],[-122.29759000000001,47.77742],[-122.29757000000001,47.77653900000001],[-122.29755000000002,47.775600000000004],[-122.29754000000001,47.77531000000001],[-122.297531,47.7748],[-122.29753000000001,47.774719000000005],[-122.29751000000002,47.773782000000004],[-122.29750000000001,47.773230000000005],[-122.297478,47.772449],[-122.29746000000002,47.771510000000006],[-122.29746000000002,47.77146],[-122.29745000000001,47.771091],[-122.297437,47.77062],[-122.29742000000002,47.770100000000006],[-122.29950000000001,47.77011000000001],[-122.30009,47.77011000000001],[-122.30128,47.77011000000001],[-122.30188,47.77011000000001],[-122.30281,47.770120000000006],[-122.30546199999999,47.77013000000001],[-122.30547999999999,47.77068],[-122.30547999999999,47.77088],[-122.30566999999999,47.77088],[-122.30604,47.77089],[-122.30604699999999,47.771094],[-122.30636,47.771100000000004],[-122.30636999999999,47.77154],[-122.30726000000001,47.77154],[-122.30725000000001,47.77106],[-122.30725000000001,47.77082],[-122.30725000000001,47.77062],[-122.30805,47.770630000000004],[-122.30823,47.77075000000001],[-122.308602,47.77094],[-122.309236,47.771146],[-122.30941,47.7712],[-122.30971,47.771300000000004],[-122.30982999999999,47.771350000000005],[-122.31,47.77143],[-122.31019,47.771341],[-122.31053,47.7712],[-122.31058,47.771190000000004],[-122.31131,47.770951000000004],[-122.31152000000002,47.770874],[-122.31179,47.77078],[-122.313,47.770343000000004],[-122.313422,47.77015000000001],[-122.31394,47.76953100000001],[-122.31222000000001,47.76953000000001],[-122.31215,47.76988],[-122.31214,47.769923000000006],[-122.31118000000001,47.769920000000006],[-122.31114400000001,47.769783000000004],[-122.31033,47.769760000000005],[-122.310328,47.76964],[-122.31033,47.769200000000005],[-122.31021000000001,47.769200000000005],[-122.30976,47.769200000000005],[-122.30958,47.769200000000005],[-122.30954799999999,47.76906],[-122.30954,47.769020000000005],[-122.30949,47.76878800000001],[-122.30935,47.768084],[-122.30924,47.76755000000001],[-122.30924,47.767004],[-122.30812999999999,47.767],[-122.30814,47.766799000000006],[-122.30814,47.76668],[-122.30821,47.766625000000005],[-122.307587,47.76661500000001],[-122.30718,47.766600000000004],[-122.30676,47.76661300000001],[-122.30673999999999,47.76519000000001],[-122.30672,47.764390000000006],[-122.30645,47.764340000000004],[-122.30627,47.764320000000005],[-122.306244,47.76431000000001],[-122.30624,47.763326000000006],[-122.30624,47.763180000000006],[-122.30624,47.76290000000001],[-122.30627,47.76290000000001],[-122.30646999999999,47.762907000000006],[-122.30808999999999,47.76301000000001],[-122.30772,47.76223000000001],[-122.30764,47.762071000000006],[-122.30753,47.761842],[-122.30734,47.761465],[-122.30699,47.76128],[-122.30658,47.76106],[-122.30577999999998,47.760642000000004],[-122.30499999999999,47.76021300000001],[-122.30452000000001,47.759949],[-122.30385,47.75958],[-122.303724,47.75949],[-122.30364,47.75936],[-122.3036,47.75922],[-122.30354,47.758773000000005],[-122.30355,47.75871000000001],[-122.30355,47.75867],[-122.30372999999999,47.758387],[-122.30398,47.7584],[-122.30424000000001,47.758390000000006],[-122.30419,47.75829],[-122.30407,47.75815800000001],[-122.30348,47.75787],[-122.30284999999999,47.75757],[-122.30263,47.757433],[-122.3025,47.757309],[-122.30239999999999,47.757183],[-122.30232099999999,47.756997],[-122.30211,47.755614],[-122.30206999999999,47.75488599999999],[-122.30205,47.754138000000005],[-122.30238999999999,47.753102000000005],[-122.30332,47.75265],[-122.30262,47.751979],[-122.30302999999999,47.75107],[-122.303,47.750158000000006],[-122.30217999999999,47.74985],[-122.30171,47.74967],[-122.30153,47.74962],[-122.30134199999999,47.749598000000006],[-122.30115,47.74959500000001],[-122.30105,47.7496],[-122.30096,47.749610000000004],[-122.30069,47.74968],[-122.30053,47.749750000000006],[-122.300354,47.74958],[-122.29988,47.74982],[-122.29923400000001,47.74982],[-122.29911000000001,47.7498],[-122.29898,47.749700000000004],[-122.29895,47.74955500000001],[-122.29894,47.7494],[-122.29906000000001,47.74904],[-122.29904,47.748760000000004],[-122.29904,47.74831000000001],[-122.29734,47.748298000000005],[-122.297334,47.74673000000001],[-122.2973,47.74465],[-122.29715000000002,47.74465],[-122.2967,47.74464],[-122.29669000000001,47.74429],[-122.29604,47.74426],[-122.29602000000001,47.74313600000001],[-122.29603,47.742810000000006],[-122.29593,47.742810000000006],[-122.29536999999999,47.742810000000006],[-122.29501,47.742802000000005],[-122.29472000000001,47.74279800000001],[-122.29468000000001,47.74106],[-122.295,47.74106],[-122.29532999999999,47.74106],[-122.29663000000001,47.741046999999995],[-122.298595,47.74103],[-122.29933,47.74102],[-122.29931,47.73922],[-122.29929000000001,47.737410000000004],[-122.29919000000001,47.737390000000005],[-122.29798000000001,47.73736],[-122.29390000000001,47.737369],[-122.29389,47.73693900000001],[-122.2938,47.736940000000004],[-122.29312000000002,47.73693000000001],[-122.2929,47.73688],[-122.29276,47.7368],[-122.29219,47.73631000000001],[-122.29236999999999,47.735800000000005],[-122.29243,47.73554000000001],[-122.29246,47.735290000000006],[-122.29243,47.73375000000001],[-122.29381000000001,47.733760000000004],[-122.29469000000002,47.73377000000001],[-122.29516000000001,47.73377000000001],[-122.29531,47.73377000000001],[-122.29652000000002,47.733784],[-122.29788,47.733790000000006],[-122.29923000000001,47.733810000000005],[-122.30059,47.73382],[-122.30195,47.733830000000005],[-122.3036,47.733850000000004],[-122.304574,47.73386],[-122.304844,47.73386],[-122.30599,47.733874],[-122.30617,47.73388],[-122.30733,47.73389],[-122.30803999999999,47.733896],[-122.30869999999999,47.733900000000006],[-122.31005,47.73391900000001],[-122.31275,47.73395000000001],[-122.31544,47.73397000000001],[-122.31676,47.733990000000006],[-122.31812000000001,47.734001],[-122.32079999999999,47.734030000000004],[-122.32222,47.73404],[-122.32359,47.734052000000005],[-122.32446,47.73406],[-122.32452,47.73406],[-122.32473399999999,47.73406],[-122.32498,47.73406],[-122.32525,47.73406],[-122.32531999999999,47.734062],[-122.32565,47.73407],[-122.32622,47.73407],[-122.32637999999999,47.73407],[-122.32887999999998,47.73409],[-122.33023,47.734100000000005],[-122.33158,47.73411000000001],[-122.33303,47.734125000000006],[-122.33375,47.73413000000001],[-122.33428,47.73413000000001],[-122.33532999999998,47.734134000000005],[-122.33555,47.734134000000005],[-122.33592999999999,47.734134000000005],[-122.33698,47.73413300000001],[-122.33807999999999,47.73413000000001],[-122.33863,47.73413000000001],[-122.33969,47.734131000000005],[-122.34074,47.73413000000001],[-122.34103,47.73413000000001],[-122.34134,47.73413000000001],[-122.34239,47.73413000000001],[-122.34342000000001,47.734128000000005],[-122.34400000000001,47.734128000000005],[-122.34499000000001,47.734127],[-122.3451,47.734127],[-122.345211,47.734127],[-122.3463,47.734127],[-122.3478,47.734127],[-122.35051,47.734128000000005],[-122.35176999999999,47.734128000000005],[-122.35304,47.734128000000005],[-122.35426000000001,47.734128000000005],[-122.35556999999999,47.73413000000001],[-122.35916999999999,47.73413600000001],[-122.36093,47.734139000000006],[-122.36195900000001,47.734140000000004],[-122.37241999999999,47.73393000000001],[-122.37437599999998,47.733920000000005],[-122.37494,47.73391900000001],[-122.37768,47.74006],[-122.37848999999999,47.74147],[-122.38072999999999,47.744521],[-122.38266999999999,47.74783],[-122.38264,47.74911900000001],[-122.38168,47.7532],[-122.38073999999999,47.75531900000001],[-122.38024,47.75851900000001],[-122.38642,47.763324000000004],[-122.386541,47.763419000000006],[-122.38774,47.764520000000005],[-122.38744,47.76522000000001],[-122.38893999999999,47.767120000000006],[-122.39107,47.76910000000001],[-122.39166000000002,47.769650000000006],[-122.39175,47.769740000000006],[-122.39371,47.77154],[-122.394442,47.77221900000001],[-122.39354,47.77351900000001],[-122.39454300000001,47.77792]]]},"name":"Shoreline"},{"boundary":{"type":"Polygon","coordinates":[[[-122.37902999999999,47.452679999999994],[-122.37235999999999,47.45579],[-122.371815,47.456123],[-122.37158,47.456292],[-122.36993,47.457409999999996],[-122.36869,47.458310000000004],[-122.36864,47.45832],[-122.36859,47.45839],[-122.36804,47.459219999999995],[-122.36944,47.46412],[-122.36883999999999,47.465720000000005],[-122.36887999999999,47.465728000000006],[-122.36898,47.465945000000005],[-122.36905,47.46611000000001],[-122.36918,47.466330000000006],[-122.36916000000001,47.466345000000004],[-122.369237,47.466550000000005],[-122.3693,47.46667],[-122.36938099999999,47.46682],[-122.36944,47.466910000000006],[-122.36958,47.467168],[-122.36961000000001,47.467256],[-122.36966000000001,47.467382],[-122.36972,47.467510000000004],[-122.36972999999999,47.46758],[-122.36972999999999,47.46762],[-122.36972999999999,47.4677],[-122.36972999999999,47.467822],[-122.36972,47.46793],[-122.36971,47.46804],[-122.36967,47.468210000000006],[-122.36974,47.468421],[-122.36962000000001,47.46887],[-122.369369,47.46988699999999],[-122.36914,47.470819999999996],[-122.36809,47.472442],[-122.36667,47.474658],[-122.36644,47.47502],[-122.36382,47.477489999999996],[-122.36334,47.478221],[-122.36153,47.481108],[-122.36134,47.48142099999999],[-122.36329,47.484922],[-122.36524,47.48842],[-122.36556,47.48867],[-122.37443999999999,47.495430000000006],[-122.37565,47.496430000000004],[-122.37531199999998,47.49687],[-122.37521,47.49703],[-122.37506999999998,47.49725],[-122.37501999999999,47.49732],[-122.37492,47.4975],[-122.37485,47.497659],[-122.37427799999999,47.499221],[-122.37413,47.499617],[-122.37406,47.49992],[-122.37396999999999,47.50058],[-122.37392,47.5007],[-122.37379999999999,47.500834],[-122.37312,47.50138],[-122.37226,47.502010000000006],[-122.37204999999999,47.502182000000005],[-122.37141,47.502778000000006],[-122.37056,47.5028],[-122.37008999999999,47.5028],[-122.36834999999999,47.5028],[-122.36726000000002,47.502790000000005],[-122.36576,47.502794],[-122.36423,47.50278],[-122.36385,47.502790000000005],[-122.36228,47.502790000000005],[-122.36221,47.502787],[-122.36159,47.502790000000005],[-122.36045,47.502790000000005],[-122.35916,47.502786],[-122.35839999999999,47.50278],[-122.35802999999999,47.502784],[-122.35780199999999,47.502784],[-122.35647999999999,47.50278],[-122.35513999999999,47.50278],[-122.35378999999999,47.5028],[-122.35382999999999,47.50098],[-122.35247999999999,47.501006999999994],[-122.35076,47.50103],[-122.34979,47.501059999999995],[-122.34846,47.50108399999999],[-122.34711000000001,47.5011],[-122.34711000000001,47.50095],[-122.34710000000001,47.500150000000005],[-122.34713,47.499274],[-122.34443,47.499320000000004],[-122.34311000000001,47.499340000000004],[-122.3429,47.49933800000001],[-122.34171,47.49935500000001],[-122.33913,47.499390000000005],[-122.339065,47.499390000000005],[-122.33582,47.499449],[-122.33559,47.49945],[-122.33546,47.49946],[-122.33474,47.49947],[-122.33447,47.49947],[-122.3337,47.499488],[-122.33237999999999,47.499479],[-122.32974999999999,47.49947],[-122.32830999999999,47.49947],[-122.32652,47.49947],[-122.32534999999999,47.49946],[-122.32493,47.49947],[-122.32479,47.49947],[-122.32445,47.49946],[-122.32444,47.49954],[-122.32433999999999,47.49954],[-122.32433599999999,47.50028],[-122.32423,47.50028],[-122.32423,47.50057],[-122.32406999999999,47.50058],[-122.32392,47.500609],[-122.32377999999999,47.50065],[-122.32364,47.500706],[-122.32359,47.500659999999996],[-122.32346999999999,47.500730000000004],[-122.32342,47.50076],[-122.32336999999998,47.5008],[-122.323293,47.50089],[-122.32328,47.50167999999999],[-122.32333799999998,47.50167],[-122.32345,47.50165],[-122.32372,47.50216],[-122.3239,47.502500000000005],[-122.32394,47.50264],[-122.32386999999999,47.502790000000005],[-122.32368,47.50284],[-122.32332999999998,47.50298],[-122.3232,47.503035000000004],[-122.32315,47.503049],[-122.32266999999999,47.503045],[-122.32131,47.503035000000004],[-122.32054,47.50303],[-122.31789,47.50301],[-122.31708,47.503004],[-122.31666000000001,47.502953000000005],[-122.31654,47.502930000000006],[-122.31634,47.503001],[-122.31527,47.502998000000005],[-122.31335,47.502990000000004],[-122.31269999999999,47.502970000000005],[-122.31264,47.502970000000005],[-122.3126,47.50709],[-122.30995,47.50707],[-122.30864,47.507059],[-122.30736,47.50705],[-122.30736,47.5069],[-122.30736999999999,47.506730000000005],[-122.30734,47.50666],[-122.30729000000001,47.50662],[-122.30719,47.50661],[-122.30714,47.50661],[-122.305617,47.506591],[-122.30412000000001,47.506585],[-122.304041,47.50658],[-122.30402000000001,47.506530000000005],[-122.30316,47.506513000000005],[-122.30309,47.506513000000005],[-122.30301,47.506510000000006],[-122.30292,47.506510000000006],[-122.30283999999999,47.50651200000001],[-122.30276999999998,47.506508000000004],[-122.30266999999999,47.5065],[-122.30259,47.506504],[-122.30252,47.5065],[-122.30243999999999,47.50649],[-122.30236999999998,47.50649],[-122.302292,47.50649],[-122.30222,47.506496],[-122.30215,47.506509],[-122.30206,47.5065],[-122.30087999999999,47.506479999999996],[-122.30078999999999,47.50649],[-122.30076999999999,47.506408],[-122.30069999999999,47.50601],[-122.30052,47.505983],[-122.30029,47.50544],[-122.30017,47.505140000000004],[-122.300046,47.504839999999994],[-122.29992000000001,47.50454],[-122.29986000000001,47.50439],[-122.29978,47.504208],[-122.29972000000001,47.504039999999996],[-122.29969000000001,47.504039999999996],[-122.29962800000001,47.50381],[-122.299586,47.503643999999994],[-122.29954000000001,47.503479999999996],[-122.29947,47.50316],[-122.29944,47.50304],[-122.29913,47.50304],[-122.29874,47.502250000000004],[-122.29819,47.50123],[-122.29819,47.50107],[-122.29819,47.500952000000005],[-122.29809,47.500685999999995],[-122.29805,47.50058],[-122.29795000000001,47.5004],[-122.29723000000001,47.499010000000006],[-122.29752000000002,47.498926000000004],[-122.29744000000001,47.49882],[-122.29723000000001,47.49848],[-122.29703500000001,47.498160000000006],[-122.29686000000001,47.49794],[-122.29681000000001,47.497893],[-122.29672000000001,47.49781],[-122.29659000000001,47.49774],[-122.29645900000001,47.4977],[-122.29641000000001,47.497679999999995],[-122.29631,47.497665],[-122.29633,47.497350000000004],[-122.29654000000001,47.497350000000004],[-122.29676,47.497350000000004],[-122.29662000000002,47.497009],[-122.29634,47.49629],[-122.29635,47.49586],[-122.29641000000001,47.495837],[-122.29641000000001,47.495830000000005],[-122.29609,47.49535000000001],[-122.29443,47.49284],[-122.294075,47.49230000000001],[-122.29437,47.49231000000001],[-122.29442000000002,47.49142],[-122.29435000000001,47.491425],[-122.29432000000001,47.490625],[-122.29429200000001,47.490094],[-122.29281,47.490140000000004],[-122.29256000000001,47.489459999999994],[-122.29225000000001,47.48878],[-122.29424000000002,47.48876],[-122.29460000000002,47.48876],[-122.29469000000002,47.488710000000005],[-122.29479,47.48869],[-122.29545,47.488679999999995],[-122.29604,47.488665],[-122.29616000000001,47.48867],[-122.29626000000002,47.488668999999994],[-122.29681000000001,47.488679999999995],[-122.29706000000002,47.488695],[-122.29723000000001,47.488730000000004],[-122.297431,47.488734],[-122.29824,47.488730000000004],[-122.29996000000001,47.488710000000005],[-122.30211,47.48869],[-122.30342,47.488679999999995],[-122.30357,47.48867],[-122.304804,47.488659999999996],[-122.30612,47.48864],[-122.30745,47.48862],[-122.30845,47.488611],[-122.30870999999999,47.48861],[-122.31063,47.48858],[-122.31136000000001,47.488572000000005],[-122.31158,47.488569],[-122.31165000000001,47.488431],[-122.31201,47.487759999999994],[-122.3122,47.48741],[-122.3125,47.486836999999994],[-122.31302000000001,47.485859999999995],[-122.31303,47.485836],[-122.31316000000001,47.48555],[-122.31338,47.48495],[-122.313466,47.484649999999995],[-122.31358,47.484049999999996],[-122.31375,47.48312],[-122.31409000000001,47.481339999999996],[-122.314407,47.47963],[-122.31476,47.477726999999994],[-122.31512000000001,47.47576],[-122.31516,47.47552],[-122.31521000000001,47.475356000000005],[-122.31526600000001,47.475001],[-122.31532999999999,47.474639999999994],[-122.3154,47.47416],[-122.31551,47.4737],[-122.31559,47.4735],[-122.31573999999999,47.473197],[-122.31587999999999,47.472975000000005],[-122.31603,47.47281],[-122.31624000000001,47.472590000000004],[-122.31764000000001,47.471259999999994],[-122.31790000000001,47.471025],[-122.31811,47.47083],[-122.3188,47.470202],[-122.31892,47.47009],[-122.31944,47.4696],[-122.31964,47.46943],[-122.319722,47.469350000000006],[-122.31982,47.46926],[-122.31991000000001,47.469170000000005],[-122.31993,47.469156000000005],[-122.32234999999999,47.46692],[-122.323,47.46632],[-122.32321,47.46607],[-122.32329,47.46603],[-122.32342999999999,47.466010000000004],[-122.32343999999999,47.465092000000006],[-122.32343699999998,47.46459],[-122.32342999999999,47.46329],[-122.32342,47.46185],[-122.32342,47.46097],[-122.32341,47.460570000000004],[-122.32346,47.460243],[-122.32354,47.46003],[-122.32361,47.45985999999999],[-122.32377999999999,47.459649999999996],[-122.324028,47.45935],[-122.324484,47.458819999999996],[-122.32456,47.45872],[-122.32465,47.45858],[-122.32476,47.458379],[-122.32486999999999,47.458079999999995],[-122.32494,47.45784199999999],[-122.32518999999999,47.45703999999999],[-122.32525,47.45683999999999],[-122.32547999999998,47.456089999999996],[-122.32606,47.454118],[-122.32611,47.45392],[-122.32524,47.45243],[-122.32451,47.45119999999999],[-122.32342799999999,47.44933],[-122.32318,47.448932000000006],[-122.32236999999998,47.447575],[-122.32172,47.44647],[-122.321324,47.44574],[-122.32111,47.445330000000006],[-122.32104,47.445190000000004],[-122.32070999999999,47.444399999999995],[-122.32056999999999,47.443943],[-122.32055,47.44383],[-122.32054,47.44357],[-122.3205,47.443208],[-122.3205,47.443059999999996],[-122.3205,47.44301],[-122.3205,47.442862],[-122.32054,47.44247],[-122.32056,47.442383],[-122.320662,47.441869999999994],[-122.32073999999999,47.44159],[-122.32087999999999,47.4413],[-122.32099,47.4411],[-122.32112000000001,47.44093],[-122.32141,47.44058],[-122.32173999999999,47.440177],[-122.32207999999999,47.4398],[-122.32242999999998,47.439357],[-122.32245999999999,47.43932],[-122.32252999999999,47.43929],[-122.32258999999999,47.439271],[-122.32279999999999,47.43922],[-122.3236,47.43964],[-122.32409,47.43989],[-122.324107,47.43984],[-122.32414,47.439710000000005],[-122.32439,47.437219999999996],[-122.32445,47.43661],[-122.32446999999999,47.436445],[-122.32456,47.435566],[-122.32467,47.434259999999995],[-122.32476999999999,47.4334],[-122.32485,47.432500000000005],[-122.32489,47.43209],[-122.32497,47.43123],[-122.325193,47.43123],[-122.3261,47.43123],[-122.32718,47.43123],[-122.32847999999998,47.431239999999995],[-122.32977999999999,47.43124099999999],[-122.330362,47.431239999999995],[-122.33132,47.431248999999994],[-122.332467,47.431256],[-122.33371799999999,47.431259999999995],[-122.33451000000001,47.431259999999995],[-122.33579999999999,47.43127],[-122.33578999999999,47.43155],[-122.33563,47.432750000000006],[-122.33551,47.433710000000005],[-122.33546999999999,47.433977],[-122.33545,47.434146],[-122.33534699999998,47.43467],[-122.33532,47.434926],[-122.335244,47.435550000000006],[-122.33519,47.43593800000001],[-122.33512999999999,47.436350000000004],[-122.33512,47.43646],[-122.33496000000001,47.43767],[-122.334889,47.4382],[-122.33482000000001,47.43871000000001],[-122.33458,47.44045],[-122.33451000000001,47.44096],[-122.33442000000001,47.44163999999999],[-122.33418,47.443439999999995],[-122.33417,47.44354],[-122.33408,47.444208999999994],[-122.33401,47.44472],[-122.33398,47.444975],[-122.33396,47.445243],[-122.33396,47.447449999999996],[-122.33396,47.447489999999995],[-122.33397,47.44861],[-122.33392,47.449679999999994],[-122.33394,47.450289999999995],[-122.33393,47.45105999999999],[-122.33393,47.452110000000005],[-122.33393,47.452436],[-122.33393,47.453289999999996],[-122.33392,47.45381],[-122.33392,47.454215],[-122.33391,47.454699999999995],[-122.33391,47.454750999999995],[-122.333908,47.45541],[-122.33388,47.45716699999999],[-122.33388,47.457339],[-122.33646999999999,47.457339],[-122.33646,47.45635],[-122.33645,47.456098999999995],[-122.33760000000001,47.456089999999996],[-122.33931,47.45607999999999],[-122.33931,47.455999999999996],[-122.33933999999999,47.454829999999994],[-122.33936999999999,47.453738],[-122.33939,47.45292],[-122.33941,47.45238],[-122.3399,47.45239],[-122.34094,47.45239],[-122.34100000000001,47.45242],[-122.34101000000001,47.45242699999999],[-122.34209,47.45241],[-122.34351600000001,47.45238],[-122.34435,47.45237],[-122.34462000000002,47.45237],[-122.34481000000001,47.45238],[-122.34561000000001,47.452386],[-122.34609,47.45239],[-122.346396,47.452397],[-122.34665000000001,47.452382],[-122.34736000000001,47.452439999999996],[-122.34836999999999,47.452428],[-122.35005,47.45241],[-122.35042999999999,47.452402],[-122.35166000000001,47.452399],[-122.35271999999999,47.452393],[-122.35405,47.452391],[-122.35502999999999,47.45238],[-122.35529999999999,47.452373],[-122.35544999999999,47.45237],[-122.3579,47.452346],[-122.358449,47.452342],[-122.35844999999999,47.45207],[-122.35846,47.45166999999999],[-122.35849999999999,47.45063],[-122.35866,47.45063],[-122.35906999999999,47.45063],[-122.35978999999999,47.45062299999999],[-122.36004,47.45061],[-122.3602,47.45059],[-122.36031,47.45056699999999],[-122.36048,47.45051],[-122.36051,47.4505],[-122.36055,47.45048799999999],[-122.36067,47.450478],[-122.36106000000001,47.4505],[-122.36118,47.45048599999999],[-122.36125900000002,47.45045999999999],[-122.36137,47.450419999999994],[-122.36148,47.450356],[-122.36171,47.45019],[-122.36181,47.450136],[-122.36191000000001,47.45007999999999],[-122.36205,47.45004399999999],[-122.36223,47.450019999999995],[-122.36246,47.450019999999995],[-122.36232999999999,47.449614],[-122.36213,47.449614],[-122.36134,47.44874],[-122.35919,47.44874],[-122.35665,47.44874],[-122.35665,47.448310000000006],[-122.35595099999999,47.44839],[-122.35567999999999,47.448479999999996],[-122.35556999999999,47.448834],[-122.35544999999999,47.44883],[-122.35548999999999,47.44796899999999],[-122.35548999999999,47.447869999999995],[-122.35552,47.447089999999996],[-122.35555,47.446239999999996],[-122.35556999999999,47.445555000000006],[-122.355644,47.44547],[-122.35565,47.445370000000004],[-122.35566999999999,47.44522],[-122.35566999999999,47.445201],[-122.35571999999999,47.44512],[-122.35589999999999,47.44511000000001],[-122.35619,47.445121],[-122.35621,47.445114000000004],[-122.35745,47.445150000000005],[-122.35934999999999,47.445163],[-122.36015,47.44518],[-122.36184,47.44522],[-122.367036,47.44762099999999],[-122.37432,47.448932000000006],[-122.38314,47.45052],[-122.37902999999999,47.452679999999994]]]},"name":"Burien"},{"boundary":{"type":"Polygon","coordinates":[[[-122.28698,47.780730000000005],[-122.28696000000001,47.781839999999995],[-122.28694,47.78291900000001],[-122.28691,47.78437],[-122.28685,47.787819999999996],[-122.28684,47.78819000000001],[-122.28684,47.78882],[-122.28688,47.789536000000005],[-122.28693,47.79051400000001],[-122.28695,47.79090000000001],[-122.28632,47.79090000000001],[-122.28634,47.79155000000001],[-122.28622000000001,47.791843],[-122.28598,47.791830000000004],[-122.28584,47.79182],[-122.28576,47.79182],[-122.28527,47.79182],[-122.28524,47.79182],[-122.28502999999999,47.791810000000005],[-122.28476,47.791810000000005],[-122.28447,47.791810000000005],[-122.28420000000001,47.791810000000005],[-122.28412000000002,47.791810000000005],[-122.28413,47.792269000000005],[-122.28413,47.79255000000001],[-122.28416000000001,47.79364],[-122.28303,47.79363000000001],[-122.28225400000001,47.793623000000004],[-122.28146000000001,47.793617000000005],[-122.28148,47.794940000000004],[-122.28148,47.79550000000001],[-122.28150000000001,47.79586200000001],[-122.28150000000001,47.796850000000006],[-122.28151000000001,47.7978],[-122.28152000000001,47.798820000000006],[-122.28153,47.79916600000001],[-122.28324,47.79916600000001],[-122.28423000000001,47.79916600000001],[-122.2861,47.79916600000001],[-122.28611000000001,47.80006899999999],[-122.28513199999999,47.80006699999999],[-122.28516,47.80277],[-122.28241,47.80276],[-122.28156000000001,47.80275],[-122.28156000000001,47.802837],[-122.28157,47.80305],[-122.28158,47.80374],[-122.28158,47.803799999999995],[-122.28157,47.80412],[-122.28197,47.80415],[-122.28221,47.80419],[-122.28309,47.804306999999994],[-122.2833,47.80433],[-122.28351,47.804339999999996],[-122.28372,47.804339999999996],[-122.28413,47.80432],[-122.28423000000001,47.804322],[-122.284774,47.804289999999995],[-122.28489,47.80427999999999],[-122.28522000000001,47.80426799999999],[-122.28533999999999,47.80426299999999],[-122.28553,47.80426799999999],[-122.28556,47.80426899999999],[-122.28576999999999,47.804289999999995],[-122.28606,47.80433],[-122.28609,47.804339999999996],[-122.28616000000001,47.804355],[-122.28627,47.80439],[-122.28645,47.80441999999999],[-122.28647799999999,47.80441999999999],[-122.28667,47.804503999999994],[-122.28691,47.80455],[-122.286975,47.804559999999995],[-122.286971,47.80639],[-122.28544,47.80638],[-122.28502,47.80638],[-122.28412000000002,47.80637],[-122.28325000000001,47.80636],[-122.282656,47.806359],[-122.28208,47.806354],[-122.28092000000001,47.806345],[-122.28058,47.80635],[-122.28028,47.80636],[-122.28007,47.80636],[-122.27916,47.806385],[-122.27876999999998,47.80637],[-122.27825,47.80637],[-122.27812,47.80636],[-122.27654,47.806343],[-122.27632,47.80634],[-122.274309,47.806312000000005],[-122.27426000000001,47.806312000000005],[-122.27391,47.80631],[-122.27282999999998,47.80631],[-122.27086,47.80632],[-122.26939,47.80632],[-122.26938,47.80654],[-122.26981,47.80658],[-122.26979,47.806616],[-122.26938,47.806599999999996],[-122.26937,47.80695],[-122.26936,47.80703],[-122.26936,47.8071],[-122.26936,47.80723999999999],[-122.26946000000001,47.807649999999995],[-122.26957,47.80779999999999],[-122.26943,47.80797],[-122.26955000000001,47.808019],[-122.2698,47.80812],[-122.26978,47.80814],[-122.26953,47.80805],[-122.2694,47.808],[-122.2693,47.808082],[-122.26909,47.80824],[-122.268875,47.80846],[-122.26934,47.808819],[-122.26935,47.808997],[-122.26892000000001,47.809],[-122.26819,47.80901],[-122.26714000000001,47.80902],[-122.266436,47.80902],[-122.26638,47.8063],[-122.26463000000001,47.8063],[-122.26303,47.8063],[-122.26122000000002,47.8063],[-122.26017,47.806301],[-122.26015000000001,47.80496899999999],[-122.26013,47.80377],[-122.26012000000001,47.80347999999999],[-122.26012000000001,47.80312],[-122.26011300000002,47.80292],[-122.26011000000001,47.8027],[-122.26010000000001,47.802065],[-122.26010000000001,47.80181999999999],[-122.26010000000001,47.80152],[-122.26010000000001,47.801339999999996],[-122.26009,47.80074],[-122.26008,47.800508],[-122.26010000000001,47.80014],[-122.26007,47.799490000000006],[-122.26005,47.79850000000001],[-122.26005,47.7978],[-122.25988,47.79778],[-122.25985,47.79778],[-122.25976,47.797768000000005],[-122.25974,47.797765000000005],[-122.25941,47.79773000000001],[-122.2594,47.797731000000006],[-122.25852,47.79766],[-122.25837999999999,47.79764],[-122.25836,47.797638000000006],[-122.25802999999999,47.797604],[-122.25821,47.797540000000005],[-122.25846999999999,47.797450000000005],[-122.258547,47.7974],[-122.25872999999999,47.797294],[-122.2589,47.797200000000004],[-122.25906,47.797090000000004],[-122.25909,47.79706],[-122.25921000000001,47.796960000000006],[-122.25928,47.79690000000001],[-122.25934,47.796828000000005],[-122.25938,47.79675000000001],[-122.25942,47.796670000000006],[-122.25946,47.79651000000001],[-122.25945,47.796490000000006],[-122.25945,47.79635000000001],[-122.25943,47.796265000000005],[-122.25941,47.79612100000001],[-122.25938,47.79601000000001],[-122.2593,47.79565000000001],[-122.25925400000001,47.79550000000001],[-122.25919,47.79534800000001],[-122.25918,47.795330000000014],[-122.25914,47.79527000000001],[-122.25898799999999,47.79505200000001],[-122.25881,47.794850000000004],[-122.25856,47.79467],[-122.25849,47.79462],[-122.25826,47.79444],[-122.25778,47.79410000000001],[-122.25755000000001,47.79392000000001],[-122.25732,47.79373500000001],[-122.25720000000001,47.793620000000004],[-122.25678699999999,47.793226000000004],[-122.25663,47.793060000000004],[-122.25653,47.79295000000001],[-122.25628,47.79270000000001],[-122.2562,47.79261000000001],[-122.25617,47.792575000000014],[-122.25602,47.792404000000005],[-122.255933,47.79229000000001],[-122.2559,47.79225000000001],[-122.25578999999999,47.792110000000015],[-122.25574999999999,47.792046000000006],[-122.25572,47.791976000000005],[-122.25559,47.791742000000006],[-122.25556999999999,47.79171000000001],[-122.25562000000001,47.79171100000001],[-122.25754,47.79175000000001],[-122.25925000000001,47.79178],[-122.26012000000001,47.791801],[-122.26018,47.791801],[-122.26089,47.791830000000004],[-122.26144000000001,47.791830000000004],[-122.26356200000001,47.791877],[-122.26394,47.791889],[-122.26454000000001,47.791900000000005],[-122.26452000000002,47.791850000000004],[-122.26421000000002,47.79093800000001],[-122.26435000000001,47.79093800000001],[-122.26410000000001,47.790200000000006],[-122.26393,47.790200000000006],[-122.26382000000001,47.78989],[-122.26372,47.78974],[-122.26352000000001,47.789550000000006],[-122.26333,47.78943],[-122.26307,47.78931600000001],[-122.26262000000001,47.78918],[-122.26232999999999,47.789030000000004],[-122.26149000000001,47.78826],[-122.26126000000002,47.788090000000004],[-122.26149000000001,47.788090000000004],[-122.26157,47.788091],[-122.26187,47.788090000000004],[-122.2624,47.78810000000001],[-122.26458000000001,47.78813000000001],[-122.26520000000001,47.788140000000006],[-122.2653,47.788140000000006],[-122.2653,47.788500000000006],[-122.26662900000002,47.788491],[-122.26681,47.78849],[-122.26699,47.78849],[-122.26737,47.78848],[-122.26738,47.788681],[-122.26738,47.78873000000001],[-122.26663,47.788720000000005],[-122.26664000000001,47.78877000000001],[-122.26664000000001,47.788790000000006],[-122.26664000000001,47.78884],[-122.26664000000001,47.788889],[-122.26650000000001,47.788886],[-122.26619000000001,47.78889],[-122.26572999999999,47.788888],[-122.26524,47.788891],[-122.26524,47.788920000000005],[-122.265235,47.78909],[-122.26523,47.789317000000004],[-122.26523,47.789448],[-122.26523,47.789574],[-122.26523,47.789863999999994],[-122.26522900000002,47.789950000000005],[-122.26523,47.79008],[-122.26522600000001,47.79017000000001],[-122.26522000000001,47.790406000000004],[-122.26667,47.79041000000001],[-122.26667,47.79049500000001],[-122.26667,47.790580000000006],[-122.266676,47.790690000000005],[-122.26625000000001,47.79070300000001],[-122.26591,47.79071300000001],[-122.26592000000001,47.79091000000001],[-122.26591,47.79101000000001],[-122.26668000000001,47.79099000000001],[-122.26668000000001,47.79122],[-122.26669000000001,47.791290000000004],[-122.266395,47.791284],[-122.26596,47.791269],[-122.26552000000001,47.791267],[-122.26523,47.791266],[-122.26523,47.79139000000001],[-122.26523,47.79177000000001],[-122.26522900000002,47.791900000000005],[-122.26552000000001,47.79191000000001],[-122.26592000000001,47.791928000000006],[-122.2664,47.791940000000004],[-122.26655000000001,47.791940000000004],[-122.2667,47.79193000000001],[-122.26686000000001,47.79191600000001],[-122.26718000000001,47.791882],[-122.26723000000001,47.79188],[-122.26743,47.79188],[-122.26847,47.791900000000005],[-122.26884,47.791920000000005],[-122.26894,47.791934000000005],[-122.26936,47.79202000000001],[-122.26949,47.79205100000001],[-122.26960000000001,47.792060000000006],[-122.26961000000001,47.79205800000001],[-122.269856,47.79205700000001],[-122.27037999999999,47.79207000000001],[-122.27064,47.79207000000001],[-122.27064,47.79195000000001],[-122.27064,47.79170500000001],[-122.27029,47.791700000000006],[-122.26984,47.79170500000001],[-122.2694,47.79170500000001],[-122.2694,47.791567],[-122.26941000000001,47.79148],[-122.269405,47.791450000000005],[-122.2694,47.7912],[-122.26938,47.790622000000006],[-122.26937,47.79051000000001],[-122.26976,47.79050000000001],[-122.27029,47.79049800000001],[-122.27067,47.79050000000001],[-122.27067,47.790448000000005],[-122.27068,47.79019800000001],[-122.27032999999999,47.790200000000006],[-122.26971,47.79019200000001],[-122.269367,47.79019000000001],[-122.26936,47.79012000000001],[-122.26936,47.790090000000006],[-122.26936,47.789910000000006],[-122.26936,47.78984],[-122.26936,47.78982],[-122.26936,47.789767],[-122.26936,47.789749],[-122.26936,47.78961],[-122.26935,47.789390000000004],[-122.26935,47.78931000000001],[-122.27024,47.789300000000004],[-122.27022000000001,47.78871000000001],[-122.27015,47.78866],[-122.27006,47.788588000000004],[-122.27023,47.788485],[-122.26935,47.788494],[-122.26934,47.78819000000001],[-122.268,47.78817000000001],[-122.268,47.78627],[-122.26744000000001,47.78627],[-122.26531,47.78625],[-122.26531,47.785551000000005],[-122.26483,47.78555000000001],[-122.26483,47.785180000000004],[-122.26482000000001,47.78486399999999],[-122.26483,47.784292],[-122.26488,47.784292],[-122.26494000000001,47.784293],[-122.26496700000001,47.784293],[-122.26506,47.78429],[-122.26531,47.784298],[-122.26530699999999,47.78385],[-122.2653,47.783370000000005],[-122.2653,47.78293000000001],[-122.26531,47.78249],[-122.26469000000002,47.78249],[-122.26469700000001,47.78219000000001],[-122.26531,47.7822],[-122.26530699999999,47.780730000000005],[-122.26543,47.780730000000005],[-122.26542,47.77991900000001],[-122.26585,47.779621],[-122.26584,47.779070000000004],[-122.26617,47.779070000000004],[-122.26641000000001,47.779070000000004],[-122.26708,47.77906],[-122.26705000000001,47.77702],[-122.26831,47.77702],[-122.27096,47.77702],[-122.2738,47.77702],[-122.27711000000001,47.77702],[-122.27717,47.77702],[-122.27736999999999,47.77702],[-122.27744,47.77702],[-122.27767,47.77702],[-122.27836999999998,47.77702],[-122.27856,47.77702],[-122.2786,47.77702],[-122.27878999999999,47.77702],[-122.27935,47.77702],[-122.27954,47.77702],[-122.2796,47.77702],[-122.27978999999999,47.77702],[-122.27985,47.77702],[-122.2817,47.77727],[-122.281806,47.77727],[-122.28381,47.777277],[-122.28425000000001,47.777271],[-122.28433,47.77726],[-122.28645,47.77724],[-122.28686,47.777243],[-122.28704,47.777242],[-122.28698,47.780730000000005]]]},"name":"Brier"},{"boundary":{"type":"Polygon","coordinates":[[[-122.33339,47.78589],[-122.33339,47.785924],[-122.33336999999999,47.786100000000005],[-122.33336,47.78627],[-122.33333999999999,47.78652],[-122.33332999999999,47.78714],[-122.3333,47.78757],[-122.33327,47.788140000000006],[-122.3332,47.788520000000005],[-122.33318,47.788638000000006],[-122.33308,47.788979000000005],[-122.332964,47.789320000000004],[-122.332827,47.78964],[-122.33281699999999,47.78966],[-122.33266,47.78999],[-122.33246,47.790470000000006],[-122.33188,47.79191900000001],[-122.33184,47.79201800000001],[-122.33156000000001,47.79232000000001],[-122.33165000000001,47.792330000000014],[-122.33179,47.792330000000014],[-122.33191000000001,47.792330000000014],[-122.33199,47.79232000000001],[-122.33218,47.792330000000014],[-122.33272799999999,47.792330000000014],[-122.33279999999999,47.792330000000014],[-122.33282,47.79274000000001],[-122.33283999999999,47.794160000000005],[-122.33259,47.794160000000005],[-122.33232,47.79415000000001],[-122.33215,47.794160000000005],[-122.33189,47.79415000000001],[-122.33141,47.794140000000006],[-122.33119,47.794140000000006],[-122.33105,47.794140000000006],[-122.33067199999999,47.79413500000001],[-122.330574,47.79413400000001],[-122.33042,47.794547],[-122.33025,47.79502000000001],[-122.33015,47.79555000000001],[-122.33015,47.795660000000005],[-122.33014,47.79597000000001],[-122.33012000000001,47.79630000000001],[-122.330122,47.796890000000005],[-122.33013,47.79802000000001],[-122.33013,47.79857000000001],[-122.33014,47.79910000000001],[-122.33014,47.799627],[-122.33025,47.799628000000006],[-122.33036999999999,47.79963000000001],[-122.33051,47.799620000000004],[-122.33077999999999,47.79963000000001],[-122.33139,47.79965000000001],[-122.33161000000001,47.79965200000001],[-122.33173,47.79965000000001],[-122.32986999999999,47.80237],[-122.32972999999998,47.80232],[-122.32956,47.802274],[-122.32887999999998,47.80207],[-122.32874099999998,47.80222],[-122.3282,47.802412000000004],[-122.32657999999999,47.80299],[-122.32603999999999,47.803188],[-122.32574999999999,47.80332],[-122.32486999999999,47.80373],[-122.32458,47.803869999999996],[-122.32435,47.80395],[-122.323662,47.80418399999999],[-122.32342999999999,47.80425999999999],[-122.32312999999999,47.80437],[-122.322224,47.8047],[-122.32192,47.804809999999996],[-122.32129,47.805051],[-122.31939,47.80577],[-122.31876,47.80601],[-122.31842,47.80615],[-122.31741000000001,47.80655],[-122.31708,47.80667999999999],[-122.31683,47.80667999999999],[-122.316793,47.80667999999999],[-122.31629000000001,47.80667],[-122.31595,47.80667],[-122.31566600000001,47.80666399999999],[-122.31536999999999,47.806659999999994],[-122.3153,47.806658],[-122.31514,47.806656],[-122.31486000000001,47.80665],[-122.31466000000002,47.806653],[-122.31439,47.80667],[-122.31414000000001,47.80671],[-122.31399,47.806737],[-122.31385,47.806776],[-122.31364,47.80685],[-122.3134,47.80693],[-122.313,47.807089999999995],[-122.31286,47.80714],[-122.31271,47.80718099999999],[-122.31256,47.807219999999994],[-122.3124,47.807244999999995],[-122.31231,47.807255],[-122.31209,47.80727999999999],[-122.31176,47.807289999999995],[-122.31169000000001,47.80727999999999],[-122.31146000000001,47.807249999999996],[-122.31139,47.80723999999999],[-122.31132000000001,47.80723],[-122.31107,47.807178],[-122.31082,47.80709699999999],[-122.31044,47.806959],[-122.31018,47.80686299999999],[-122.30968,47.80671],[-122.30925,47.80663],[-122.30893999999999,47.80659],[-122.30852,47.80658],[-122.30822,47.806562],[-122.30753,47.80656],[-122.30654,47.80655],[-122.30579699999998,47.80653],[-122.30559,47.80654],[-122.30526,47.80654],[-122.30475,47.80654],[-122.30366000000001,47.80653],[-122.30321,47.80654],[-122.30276999999998,47.806529],[-122.30148,47.80651],[-122.30145,47.80651],[-122.30101,47.8065],[-122.30089,47.8065],[-122.30054,47.8065],[-122.30042999999999,47.8065],[-122.30031,47.8065],[-122.29996000000001,47.80649],[-122.29984,47.80649],[-122.29943,47.80649],[-122.29921000000002,47.80648699999999],[-122.298688,47.8065],[-122.29820000000001,47.80649],[-122.29779300000001,47.80649],[-122.29743,47.8065],[-122.29726000000002,47.80649],[-122.29690000000001,47.80649],[-122.29593,47.80649],[-122.29565000000001,47.80647999999999],[-122.29557,47.806472],[-122.29512000000001,47.806459999999994],[-122.29493000000001,47.80645],[-122.29488,47.80645],[-122.2947,47.80645],[-122.29344400000001,47.806442],[-122.29303,47.806439999999995],[-122.29289,47.806439],[-122.29247,47.806436],[-122.29234,47.806436],[-122.29234,47.80679],[-122.29234,47.80681],[-122.29232,47.80735],[-122.29231,47.807849999999995],[-122.29231,47.8082],[-122.29227,47.80824],[-122.29147,47.80823],[-122.28966000000001,47.80823],[-122.28967,47.80686899999999],[-122.28731,47.80685],[-122.28712000000002,47.80685],[-122.28698,47.806850999999995],[-122.28698,47.80654],[-122.286971,47.80639],[-122.286975,47.804559999999995],[-122.28696000000001,47.80440299999999],[-122.28689,47.804392],[-122.28689,47.80366099999999],[-122.286,47.803672],[-122.28555,47.80367999999999],[-122.28556,47.80426899999999],[-122.28553,47.80426799999999],[-122.28533999999999,47.80426299999999],[-122.28522000000001,47.80426799999999],[-122.28489,47.80427999999999],[-122.284774,47.804289999999995],[-122.28423000000001,47.804322],[-122.28413,47.80432],[-122.28372,47.804339999999996],[-122.28351,47.804339999999996],[-122.2833,47.80433],[-122.28309,47.804306999999994],[-122.28221,47.80419],[-122.28197,47.80415],[-122.28157,47.80412],[-122.28158,47.803799999999995],[-122.28158,47.80374],[-122.28157,47.80305],[-122.28156000000001,47.802837],[-122.28156000000001,47.80275],[-122.28241,47.80276],[-122.28516,47.80277],[-122.28513199999999,47.80006699999999],[-122.28611000000001,47.80006899999999],[-122.2861,47.79916600000001],[-122.28423000000001,47.79916600000001],[-122.28324,47.79916600000001],[-122.28153,47.79916600000001],[-122.28152000000001,47.798820000000006],[-122.28151000000001,47.7978],[-122.28150000000001,47.796850000000006],[-122.28150000000001,47.79586200000001],[-122.28148,47.79550000000001],[-122.28148,47.794940000000004],[-122.28146000000001,47.793617000000005],[-122.28225400000001,47.793623000000004],[-122.28303,47.79363000000001],[-122.28416000000001,47.79364],[-122.28413,47.79255000000001],[-122.28413,47.792269000000005],[-122.28412000000002,47.791810000000005],[-122.28420000000001,47.791810000000005],[-122.28447,47.791810000000005],[-122.28476,47.791810000000005],[-122.28502999999999,47.791810000000005],[-122.28524,47.79182],[-122.28527,47.79182],[-122.28576,47.79182],[-122.28584,47.79182],[-122.28598,47.791830000000004],[-122.28622000000001,47.791843],[-122.28634,47.79155000000001],[-122.28632,47.79090000000001],[-122.28695,47.79090000000001],[-122.28693,47.79051400000001],[-122.28688,47.789536000000005],[-122.28684,47.78882],[-122.28684,47.78819000000001],[-122.28685,47.787819999999996],[-122.28691,47.78437],[-122.28694,47.78291900000001],[-122.28696000000001,47.781839999999995],[-122.28698,47.780730000000005],[-122.28827,47.780957],[-122.28826400000001,47.7808],[-122.28838999999999,47.78068],[-122.28872,47.78028],[-122.28900300000001,47.78008],[-122.28902000000001,47.779969],[-122.28944,47.779976000000005],[-122.289849,47.779990000000005],[-122.289851,47.77991000000001],[-122.28985,47.77982],[-122.28986,47.77968],[-122.28986,47.77948],[-122.28986,47.779250000000005],[-122.28986,47.77910000000001],[-122.289892,47.778760000000005],[-122.2899,47.77855000000001],[-122.28992000000001,47.77830000000001],[-122.28993,47.77826],[-122.28971,47.77825000000001],[-122.28923,47.77825000000001],[-122.28915,47.77825000000001],[-122.28889,47.77825000000001],[-122.28894,47.777930000000005],[-122.288864,47.777930000000005],[-122.28826400000001,47.777933000000004],[-122.28824,47.77734],[-122.29009,47.77736],[-122.29013,47.77736],[-122.29036,47.777359000000004],[-122.29052000000001,47.777359000000004],[-122.290932,47.777367],[-122.29104000000001,47.777367],[-122.29163000000001,47.777366],[-122.29176000000001,47.777367],[-122.29222800000001,47.777370000000005],[-122.29232999999999,47.77737200000001],[-122.29242,47.777370000000005],[-122.29249,47.777370000000005],[-122.29367,47.77738],[-122.29378,47.77738],[-122.29391000000001,47.777390000000004],[-122.29575,47.777403],[-122.29592000000001,47.777404],[-122.296037,47.777406],[-122.29621000000002,47.777408],[-122.29759000000001,47.77742],[-122.29803,47.77742],[-122.29812000000001,47.77742],[-122.29821000000001,47.77743],[-122.29916000000001,47.77743],[-122.29934,47.77744],[-122.29938,47.77744],[-122.29956000000001,47.77744],[-122.29974,47.77744],[-122.301298,47.777456],[-122.3013,47.777456],[-122.30136,47.777456],[-122.301482,47.77746],[-122.30155,47.777459],[-122.30176,47.77746],[-122.30194,47.777463],[-122.30297999999999,47.77747],[-122.30301,47.77747],[-122.30322000000001,47.77747],[-122.30421300000002,47.77748],[-122.30498,47.777491],[-122.30512,47.77749],[-122.30523,47.77749],[-122.30566999999999,47.777497],[-122.30632999999999,47.7775],[-122.30669999999999,47.77751000000001],[-122.30758,47.77751000000001],[-122.30795,47.77752],[-122.3081,47.777530000000006],[-122.30836,47.777533000000005],[-122.30882999999999,47.77753500000001],[-122.30896,47.77753800000001],[-122.30904,47.777539000000004],[-122.30923,47.77754],[-122.30929,47.77754],[-122.30955,47.777542000000004],[-122.30982999999999,47.77754],[-122.31048,47.77754],[-122.31071,47.77754],[-122.31093,47.777546],[-122.31156000000001,47.777550000000005],[-122.31190000000001,47.77756],[-122.31192000000001,47.777557],[-122.31229,47.77756],[-122.31232999999999,47.77756],[-122.31265,47.77756],[-122.31267,47.77756],[-122.313,47.77756],[-122.31313,47.777570000000004],[-122.31314,47.777570000000004],[-122.313328,47.777570000000004],[-122.313422,47.777570000000004],[-122.31388,47.77758],[-122.31488,47.7776],[-122.31509,47.77761],[-122.31525,47.777619],[-122.31568,47.77762],[-122.31578999999999,47.77762],[-122.31665000000001,47.77762],[-122.31669000000001,47.77762],[-122.3168,47.77762],[-122.316887,47.77762],[-122.31709900000001,47.77762],[-122.31731,47.77762],[-122.31746000000001,47.77762],[-122.31756000000001,47.77762],[-122.31765000000001,47.77762],[-122.31781000000001,47.77762],[-122.31787,47.77762],[-122.31790000000001,47.77762],[-122.31796000000001,47.777622],[-122.318,47.777622],[-122.31826000000001,47.777623],[-122.31865,47.777625],[-122.31944,47.77763],[-122.31968,47.777631],[-122.32121000000001,47.77764],[-122.32128,47.77764],[-122.3214,47.77764],[-122.32185,47.77764],[-122.32229999999998,47.77765],[-122.32309,47.77765],[-122.324554,47.777663],[-122.32463,47.777663999999994],[-122.32469999999999,47.777665],[-122.32628,47.77769],[-122.32654,47.77768],[-122.3266,47.77768],[-122.32683999999999,47.777679],[-122.327304,47.77765],[-122.32694,47.77807000000001],[-122.32746999999999,47.778676000000004],[-122.32791,47.77919000000001],[-122.32797,47.77926],[-122.32802999999998,47.77933000000001],[-122.32858999999999,47.77998],[-122.32914,47.78062],[-122.32924,47.780743],[-122.32969,47.781259999999996],[-122.33028,47.78138],[-122.33072,47.78147],[-122.33116000000001,47.781703],[-122.331691,47.7822],[-122.33187099999999,47.78284],[-122.33183,47.78327],[-122.33165000000001,47.78354],[-122.331441,47.78372],[-122.33129300000002,47.78381],[-122.33116000000001,47.7839],[-122.33128,47.784150000000004],[-122.33142000000001,47.78447],[-122.33176,47.78522],[-122.33198,47.785700000000006],[-122.33243999999999,47.785700000000006],[-122.33341,47.78571000000001],[-122.33339,47.78589]],[[-122.29760400000002,47.77819500000001],[-122.29708000000001,47.77819500000001],[-122.29653,47.77819100000001],[-122.29654000000001,47.77839000000001],[-122.29712000000002,47.778400000000005],[-122.29760000000002,47.77840500000001],[-122.29760000000002,47.77833000000001],[-122.29760400000002,47.778225000000006],[-122.29760400000002,47.77819500000001]],[[-122.30372999999999,47.78053500000001],[-122.30335,47.780530000000006],[-122.30312,47.780530000000006],[-122.303,47.780525000000004],[-122.30301,47.78069],[-122.30301,47.78079],[-122.303007,47.78085],[-122.30315,47.78085],[-122.30372999999999,47.78086],[-122.30372999999999,47.780550000000005],[-122.30372999999999,47.78053500000001]],[[-122.29705000000001,47.781392000000004],[-122.29704000000001,47.781279999999995],[-122.29703,47.78106],[-122.29698,47.78105],[-122.29687,47.781042],[-122.29667,47.78104],[-122.29650400000001,47.78104],[-122.29649,47.781535000000005],[-122.29705000000001,47.78155],[-122.29705000000001,47.781392000000004]],[[-122.32994,47.794689],[-122.32936,47.794682],[-122.32894399999999,47.79468],[-122.32878999999998,47.79467],[-122.32878999999998,47.79478],[-122.32879999999999,47.79502000000001],[-122.32879999999999,47.795086000000005],[-122.32893999999999,47.79508800000001],[-122.32906999999999,47.79508800000001],[-122.32907999999999,47.795089000000004],[-122.32912999999999,47.79509000000001],[-122.32982999999999,47.79510000000001],[-122.32985,47.794961],[-122.32994,47.794689]]]},"name":"Mountlake Terrace"},{"boundary":{"type":"Polygon","coordinates":[[[-122.32606,47.454118],[-122.32547999999998,47.456089999999996],[-122.32525,47.45683999999999],[-122.32518999999999,47.45703999999999],[-122.32494,47.45784199999999],[-122.32486999999999,47.458079999999995],[-122.32476,47.458379],[-122.32465,47.45858],[-122.32456,47.45872],[-122.324484,47.458819999999996],[-122.324028,47.45935],[-122.32377999999999,47.459649999999996],[-122.32361,47.45985999999999],[-122.32354,47.46003],[-122.32346,47.460243],[-122.32341,47.460570000000004],[-122.32342,47.46097],[-122.32342,47.46185],[-122.32342999999999,47.46329],[-122.32343699999998,47.46459],[-122.32343999999999,47.465092000000006],[-122.32342999999999,47.466010000000004],[-122.32329,47.46603],[-122.32321,47.46607],[-122.323,47.46632],[-122.32234999999999,47.46692],[-122.31993,47.469156000000005],[-122.31991000000001,47.469170000000005],[-122.31982,47.46926],[-122.319722,47.469350000000006],[-122.31964,47.46943],[-122.31944,47.4696],[-122.31892,47.47009],[-122.3188,47.470202],[-122.31811,47.47083],[-122.31790000000001,47.471025],[-122.31764000000001,47.471259999999994],[-122.31624000000001,47.472590000000004],[-122.31603,47.47281],[-122.31587999999999,47.472975000000005],[-122.31573999999999,47.473197],[-122.31559,47.4735],[-122.31551,47.4737],[-122.3154,47.47416],[-122.31532999999999,47.474639999999994],[-122.31526600000001,47.475001],[-122.31521000000001,47.475356000000005],[-122.31516,47.47552],[-122.31512000000001,47.47576],[-122.31476,47.477726999999994],[-122.314407,47.47963],[-122.31409000000001,47.481339999999996],[-122.31375,47.48312],[-122.31358,47.484049999999996],[-122.313466,47.484649999999995],[-122.31338,47.48495],[-122.31316000000001,47.48555],[-122.31303,47.485836],[-122.31302000000001,47.485859999999995],[-122.3125,47.486836999999994],[-122.3122,47.48741],[-122.31201,47.487759999999994],[-122.31165000000001,47.488431],[-122.31158,47.488569],[-122.31136000000001,47.488572000000005],[-122.31063,47.48858],[-122.30870999999999,47.48861],[-122.30845,47.488611],[-122.30745,47.48862],[-122.30612,47.48864],[-122.304804,47.488659999999996],[-122.30357,47.48867],[-122.30342,47.488679999999995],[-122.30211,47.48869],[-122.29996000000001,47.488710000000005],[-122.29824,47.488730000000004],[-122.297431,47.488734],[-122.29723000000001,47.488730000000004],[-122.29718000000001,47.48862],[-122.29691000000001,47.48796399999999],[-122.29667300000001,47.48732],[-122.29643,47.486639999999994],[-122.29637,47.48649699999999],[-122.29629000000001,47.48625],[-122.29624000000001,47.486095],[-122.29613,47.485730000000004],[-122.29609,47.4856],[-122.29606000000001,47.485510000000005],[-122.29587,47.484899999999996],[-122.29566000000001,47.484199999999994],[-122.2954,47.48338],[-122.29528,47.48296],[-122.29514,47.48242],[-122.29497,47.481553],[-122.29471000000001,47.48031],[-122.29464000000002,47.480117],[-122.29445000000001,47.47976],[-122.29422000000002,47.47936],[-122.29343,47.477985999999994],[-122.29316000000001,47.477519],[-122.29171000000001,47.475397],[-122.29150000000001,47.47508],[-122.29100000000001,47.47434],[-122.28991500000001,47.472730000000006],[-122.289795,47.472530000000006],[-122.28976999999999,47.47247],[-122.2897,47.47225],[-122.28967,47.472120000000004],[-122.28965000000001,47.47205],[-122.28946,47.470639999999996],[-122.2894,47.470234],[-122.28919,47.4688],[-122.28909,47.46815600000001],[-122.28896,47.467452],[-122.2889,47.46724],[-122.28878999999999,47.46696],[-122.28811,47.46696],[-122.288506,47.466170000000005],[-122.28875,47.465700000000005],[-122.28882999999999,47.465540000000004],[-122.28902000000001,47.465140000000005],[-122.28944,47.464310000000005],[-122.28974,47.463730000000005],[-122.28976,47.46369],[-122.28983,47.463567],[-122.28989,47.46346],[-122.28997,47.463298],[-122.29016000000001,47.46294],[-122.29067,47.46201000000001],[-122.29090000000001,47.46153],[-122.29121000000002,47.460910000000005],[-122.29183,47.45965999999999],[-122.29173,47.45965999999999],[-122.29158100000001,47.459649999999996],[-122.29150000000001,47.459649999999996],[-122.289,47.459649999999996],[-122.28826000000001,47.459649999999996],[-122.28703,47.45963999999999],[-122.28672999999999,47.45963999999999],[-122.28658,47.459619999999994],[-122.28643799999999,47.459599999999995],[-122.28632999999999,47.459575],[-122.28617,47.459536],[-122.28604,47.459495],[-122.28595,47.459559999999996],[-122.28586,47.459599999999995],[-122.28577999999999,47.45963],[-122.28565,47.459649999999996],[-122.28327,47.45963999999999],[-122.28277999999999,47.459638],[-122.28216,47.45963999999999],[-122.28128000000001,47.459635],[-122.28118,47.45963999999999],[-122.28117,47.457939999999994],[-122.28117,47.457469999999994],[-122.28117,47.45706299999999],[-122.28118,47.456513],[-122.28119000000001,47.45643999999999],[-122.28119000000001,47.45637],[-122.28124000000001,47.456269999999996],[-122.27956,47.45627999999999],[-122.27892,47.45627999999999],[-122.27830999999999,47.45627999999999],[-122.27806,47.456269999999996],[-122.27748,47.456267999999994],[-122.277399,47.456269999999996],[-122.2771,47.45625999999999],[-122.27615,47.45625999999999],[-122.27525,47.45625999999999],[-122.274928,47.45625999999999],[-122.27444,47.456253999999994],[-122.27385,47.45625999999999],[-122.27349,47.45625999999999],[-122.27323,47.456253],[-122.27286999999998,47.456253999999994],[-122.27205,47.456255],[-122.27141,47.456253999999994],[-122.27132999999999,47.454499999999996],[-122.27051,47.454499999999996],[-122.26778,47.45448999999999],[-122.26780000000001,47.454179999999994],[-122.26781000000001,47.45392],[-122.26783,47.45233],[-122.26784,47.45225],[-122.26822000000001,47.45225],[-122.26891,47.452259],[-122.26889,47.45173],[-122.26793500000001,47.451719999999995],[-122.26791900000002,47.451132],[-122.26704000000001,47.45113],[-122.26702000000002,47.45062099999999],[-122.26701000000001,47.44993],[-122.26700000000001,47.449639999999995],[-122.26702000000002,47.44956],[-122.26709000000001,47.449419999999996],[-122.26714000000001,47.44934],[-122.26723000000001,47.44922],[-122.26726000000002,47.44918],[-122.26729500000002,47.449130000000004],[-122.26730300000001,47.449110000000005],[-122.26888,47.449130000000004],[-122.26886,47.447779999999995],[-122.26874,47.44779],[-122.26809,47.44779],[-122.26715000000002,47.447799999999994],[-122.26705000000001,47.447799999999994],[-122.26650000000001,47.44780299999999],[-122.26565000000001,47.44780899999999],[-122.26563,47.44780899999999],[-122.26507,47.44780599999999],[-122.26507,47.447542],[-122.26506,47.446999999999996],[-122.26512000000001,47.446819999999995],[-122.265319,47.44618],[-122.26562000000001,47.44511000000001],[-122.26668000000001,47.445121],[-122.26700000000001,47.443799999999996],[-122.26706000000001,47.44356],[-122.26581,47.44315],[-122.26558,47.44308699999999],[-122.2651,47.44294],[-122.26522900000002,47.43873000000001],[-122.26544,47.438431],[-122.26687,47.43775],[-122.26814,47.437148],[-122.26865000000001,47.43682],[-122.269367,47.43589],[-122.26943,47.43583],[-122.26957,47.435656],[-122.27005,47.43519200000001],[-122.27052,47.434689999999996],[-122.27089,47.4344],[-122.27096,47.433805],[-122.27105,47.432897],[-122.27085,47.432894],[-122.27025,47.43289],[-122.26993,47.432897],[-122.26968000000001,47.432900000000004],[-122.26969000000001,47.43283],[-122.26968000000001,47.43273000000001],[-122.26971,47.43253000000001],[-122.26969000000001,47.432327],[-122.26963,47.43207],[-122.26961000000001,47.4317],[-122.26963,47.431475],[-122.26964000000001,47.431425],[-122.26971,47.43127],[-122.26981,47.431154],[-122.26993,47.43109],[-122.27017,47.43103],[-122.27015,47.430930000000004],[-122.27019,47.43079],[-122.27029999999999,47.43068699999999],[-122.27036,47.430640999999994],[-122.27049,47.43054],[-122.27071,47.430383],[-122.27132999999999,47.42994],[-122.27251,47.429100000000005],[-122.27362000000001,47.42819000000001],[-122.27436,47.42758],[-122.27486,47.42716],[-122.27512,47.42691000000001],[-122.27532999999998,47.42667],[-122.27539999999999,47.42658],[-122.27574999999999,47.42684],[-122.27829,47.425070000000005],[-122.27846,47.425140000000006],[-122.28096000000001,47.423373000000005],[-122.28087,47.42331000000001],[-122.28120000000001,47.42309],[-122.28157,47.42282],[-122.28156000000001,47.422368000000006],[-122.28225400000001,47.42194],[-122.28237999999999,47.42185],[-122.28281,47.421503],[-122.28366000000001,47.422067],[-122.28417,47.42171200000001],[-122.28410600000001,47.4199],[-122.28408,47.41914],[-122.28225,47.419163],[-122.281451,47.41918],[-122.28076,47.419196],[-122.28086,47.41911],[-122.28095,47.41903],[-122.28111000000001,47.418839999999996],[-122.28125000000001,47.418651],[-122.28141000000001,47.41829],[-122.281496,47.418082],[-122.28179,47.41738699999999],[-122.28212,47.416602],[-122.28223,47.416354],[-122.28226000000001,47.41627999999999],[-122.28229,47.41607],[-122.28232999999999,47.4158],[-122.28236,47.415572000000004],[-122.28235,47.415517],[-122.28226000000001,47.41517],[-122.28215,47.414739999999995],[-122.28182000000001,47.4141],[-122.28154,47.41356],[-122.28098,47.41285],[-122.28093,47.412802],[-122.28082300000001,47.412718000000005],[-122.28081,47.4127],[-122.28093,47.412631],[-122.28099,47.41256],[-122.28101000000001,47.41247],[-122.28105000000001,47.412236],[-122.28105000000001,47.41216],[-122.28115000000001,47.411716],[-122.281316,47.41098099999999],[-122.28134,47.410758],[-122.28137,47.410371],[-122.28138,47.40925],[-122.28136,47.40894],[-122.28135,47.40886],[-122.281318,47.408761],[-122.28123000000001,47.408550000000005],[-122.28128000000001,47.408530000000006],[-122.28133,47.40851000000001],[-122.28136,47.40848],[-122.28138,47.40844],[-122.28138,47.4084],[-122.28137,47.40838],[-122.28163,47.40832],[-122.2818,47.40827],[-122.28187,47.40823],[-122.28198,47.40815200000001],[-122.28275,47.407502],[-122.28289,47.407392],[-122.28304,47.407301],[-122.28315,47.40724899999999],[-122.28331,47.40719],[-122.28346,47.407162],[-122.28355,47.40715],[-122.28376999999999,47.40714],[-122.28391,47.40714],[-122.28403,47.40716],[-122.28413,47.40718],[-122.28423000000001,47.407208],[-122.2844,47.40727],[-122.28446000000001,47.4073],[-122.28456000000001,47.40736],[-122.28463,47.40741],[-122.28475,47.407526999999995],[-122.28486000000001,47.40767999999999],[-122.28488,47.40771],[-122.28502,47.40792],[-122.285255,47.40826],[-122.28565,47.408254],[-122.28615,47.408249],[-122.286581,47.408243],[-122.28656000000001,47.40381],[-122.28656000000001,47.40374],[-122.28684,47.40373],[-122.28852,47.40367],[-122.28877999999999,47.403659999999995],[-122.288765,47.403459999999995],[-122.28875,47.40336],[-122.288714,47.403259],[-122.28846999999999,47.40291800000001],[-122.28846,47.40287],[-122.28847999999999,47.40284],[-122.28838999999999,47.402615000000004],[-122.28836999999999,47.40241],[-122.28833999999999,47.402131000000004],[-122.29009,47.402133000000006],[-122.29017,47.402141],[-122.29017,47.402100000000004],[-122.29017,47.40183],[-122.29018,47.40166299999999],[-122.29018,47.401316],[-122.29018,47.401019],[-122.29019000000001,47.400968],[-122.28655,47.40103],[-122.28563,47.40104699999999],[-122.28398,47.40107],[-122.28394,47.39925],[-122.28276999999999,47.39927],[-122.28276,47.39745],[-122.282945,47.397452],[-122.28347,47.397026],[-122.283466,47.396771],[-122.28412000000002,47.39664],[-122.28407,47.396556000000004],[-122.286568,47.39647],[-122.28655,47.39569],[-122.287315,47.395686],[-122.28732000000001,47.395848],[-122.289305,47.395810000000004],[-122.28934,47.39588],[-122.28993,47.395857],[-122.29027,47.395851],[-122.29035,47.396076],[-122.29132800000001,47.396065],[-122.29173,47.396065],[-122.29196000000002,47.39759],[-122.29208,47.39844],[-122.29225000000001,47.39959],[-122.292455,47.40094],[-122.29253,47.40145],[-122.29254,47.401605999999994],[-122.29256000000001,47.40177],[-122.29260000000001,47.402256],[-122.29266000000001,47.403568],[-122.29266000000001,47.40363],[-122.29266000000001,47.40376],[-122.29265000000001,47.404562],[-122.29265000000001,47.404588999999994],[-122.29261000000001,47.405283999999995],[-122.29255,47.405992000000005],[-122.29246,47.406645],[-122.29244,47.40687],[-122.292367,47.4073],[-122.29218300000001,47.408190000000005],[-122.29369000000001,47.408194],[-122.29452000000002,47.4082],[-122.29486000000001,47.408203],[-122.29518,47.408206],[-122.29602000000001,47.408210000000004],[-122.29657,47.408210000000004],[-122.29721000000002,47.40822],[-122.29813,47.40823],[-122.29821000000001,47.40823],[-122.29832,47.40823],[-122.29831,47.408390000000004],[-122.29803,47.410379999999996],[-122.29782900000002,47.41179999999999],[-122.30248999999999,47.41187999999999],[-122.30247999999999,47.413689999999995],[-122.30248999999999,47.41409899999999],[-122.3025,47.41493],[-122.30248999999999,47.41545],[-122.30248999999999,47.41552],[-122.302555,47.415527],[-122.30371,47.41554],[-122.30414,47.41554],[-122.30464,47.41554],[-122.30512999999999,47.41554],[-122.30604,47.415530000000004],[-122.30676999999999,47.415529],[-122.30711000000001,47.415527],[-122.30865,47.41552],[-122.3092,47.415510000000005],[-122.3093,47.415516000000004],[-122.30963799999999,47.41552],[-122.31048,47.415530000000004],[-122.31189,47.415561],[-122.312084,47.41556],[-122.31231,47.41554],[-122.31275,47.415530000000004],[-122.31309,47.41555],[-122.31389,47.41558],[-122.31444,47.4156],[-122.31557,47.41563],[-122.31659,47.41567],[-122.31821000000001,47.41575],[-122.31869,47.415773],[-122.31881,47.41578],[-122.318816,47.41585],[-122.31881,47.41599],[-122.31882999999999,47.416076999999994],[-122.3191,47.41607],[-122.320257,47.416038],[-122.32022,47.41617],[-122.32019,47.416349],[-122.32018,47.416489999999996],[-122.32016999999999,47.416802999999994],[-122.32019,47.41733],[-122.32022,47.41855],[-122.32022,47.41859],[-122.32024,47.41916],[-122.32025,47.419869999999996],[-122.32028,47.421116000000005],[-122.32029999999999,47.42163],[-122.32029,47.42184099999999],[-122.32027,47.421974],[-122.32019,47.422371000000005],[-122.32006,47.42293900000001],[-122.31983,47.423849],[-122.31978,47.424],[-122.31964,47.42439],[-122.31899,47.42589],[-122.31882999999999,47.42625],[-122.31852,47.426950000000005],[-122.31839,47.427337],[-122.31836,47.427478],[-122.31833999999999,47.42764],[-122.31831,47.42792],[-122.3183,47.42844],[-122.32006999999999,47.42846],[-122.321146,47.42848],[-122.32166000000001,47.428490000000004],[-122.32213099999998,47.42850000000001],[-122.32319,47.42851000000001],[-122.32491,47.42855000000001],[-122.32492,47.429002000000004],[-122.32493,47.429387],[-122.32494,47.42989],[-122.32496,47.43078],[-122.32497,47.43102],[-122.32497,47.43123],[-122.32489,47.43209],[-122.32485,47.432500000000005],[-122.32476999999999,47.4334],[-122.32467,47.434259999999995],[-122.32456,47.435566],[-122.32446999999999,47.436445],[-122.32445,47.43661],[-122.32439,47.437219999999996],[-122.32414,47.439710000000005],[-122.324107,47.43984],[-122.32409,47.43989],[-122.3236,47.43964],[-122.32279999999999,47.43922],[-122.32258999999999,47.439271],[-122.32252999999999,47.43929],[-122.32245999999999,47.43932],[-122.32242999999998,47.439357],[-122.32207999999999,47.4398],[-122.32173999999999,47.440177],[-122.32141,47.44058],[-122.32112000000001,47.44093],[-122.32099,47.4411],[-122.32087999999999,47.4413],[-122.32073999999999,47.44159],[-122.320662,47.441869999999994],[-122.32056,47.442383],[-122.32054,47.44247],[-122.3205,47.442862],[-122.3205,47.44301],[-122.3205,47.443059999999996],[-122.3205,47.443208],[-122.32054,47.44357],[-122.32055,47.44383],[-122.32056999999999,47.443943],[-122.32070999999999,47.444399999999995],[-122.32104,47.445190000000004],[-122.32111,47.445330000000006],[-122.321324,47.44574],[-122.32172,47.44647],[-122.32236999999998,47.447575],[-122.32318,47.448932000000006],[-122.32342799999999,47.44933],[-122.32451,47.45119999999999],[-122.32524,47.45243],[-122.32611,47.45392],[-122.32606,47.454118]]]},"name":"SeaTac"},{"boundary":{"type":"Polygon","coordinates":[[[-122.36246,47.450019999999995],[-122.36223,47.450019999999995],[-122.36205,47.45004399999999],[-122.36191000000001,47.45007999999999],[-122.36181,47.450136],[-122.36171,47.45019],[-122.36148,47.450356],[-122.36137,47.450419999999994],[-122.36125900000002,47.45045999999999],[-122.36118,47.45048599999999],[-122.36106000000001,47.4505],[-122.36067,47.450478],[-122.36055,47.45048799999999],[-122.36051,47.4505],[-122.36048,47.45051],[-122.36031,47.45056699999999],[-122.3602,47.45059],[-122.36004,47.45061],[-122.35978999999999,47.45062299999999],[-122.35906999999999,47.45063],[-122.35866,47.45063],[-122.35849999999999,47.45063],[-122.35846,47.45166999999999],[-122.35844999999999,47.45207],[-122.358449,47.452342],[-122.3579,47.452346],[-122.35544999999999,47.45237],[-122.35529999999999,47.452373],[-122.35502999999999,47.45238],[-122.35405,47.452391],[-122.35271999999999,47.452393],[-122.35166000000001,47.452399],[-122.35042999999999,47.452402],[-122.35005,47.45241],[-122.34836999999999,47.452428],[-122.34736000000001,47.452439999999996],[-122.34665000000001,47.452382],[-122.346396,47.452397],[-122.34609,47.45239],[-122.34561000000001,47.452386],[-122.34481000000001,47.45238],[-122.34462000000002,47.45237],[-122.34435,47.45237],[-122.34351600000001,47.45238],[-122.34209,47.45241],[-122.34101000000001,47.45242699999999],[-122.34100000000001,47.45242],[-122.34094,47.45239],[-122.3399,47.45239],[-122.33941,47.45238],[-122.33939,47.45292],[-122.33936999999999,47.453738],[-122.33933999999999,47.454829999999994],[-122.33931,47.455999999999996],[-122.33931,47.45607999999999],[-122.33760000000001,47.456089999999996],[-122.33645,47.456098999999995],[-122.33646,47.45635],[-122.33646999999999,47.457339],[-122.33388,47.457339],[-122.33388,47.45716699999999],[-122.333908,47.45541],[-122.33391,47.454750999999995],[-122.33391,47.454699999999995],[-122.33392,47.454215],[-122.33392,47.45381],[-122.33393,47.453289999999996],[-122.33393,47.452436],[-122.33393,47.452110000000005],[-122.33393,47.45105999999999],[-122.33394,47.450289999999995],[-122.33392,47.449679999999994],[-122.33397,47.44861],[-122.33396,47.447489999999995],[-122.33396,47.447449999999996],[-122.33396,47.445243],[-122.33398,47.444975],[-122.33401,47.44472],[-122.33408,47.444208999999994],[-122.33417,47.44354],[-122.33418,47.443439999999995],[-122.33442000000001,47.44163999999999],[-122.33451000000001,47.44096],[-122.33458,47.44045],[-122.33482000000001,47.43871000000001],[-122.334889,47.4382],[-122.33496000000001,47.43767],[-122.33512,47.43646],[-122.33512999999999,47.436350000000004],[-122.33519,47.43593800000001],[-122.335244,47.435550000000006],[-122.33532,47.434926],[-122.33534699999998,47.43467],[-122.33545,47.434146],[-122.33546999999999,47.433977],[-122.33551,47.433710000000005],[-122.33563,47.432750000000006],[-122.33578999999999,47.43155],[-122.33579999999999,47.43127],[-122.33579999999999,47.43113],[-122.33579999999999,47.431059999999995],[-122.33578999999999,47.430896999999995],[-122.33562,47.428630000000005],[-122.33559,47.428230000000006],[-122.33558,47.42803000000001],[-122.33556,47.4278],[-122.33552900000001,47.42732],[-122.33545,47.42634],[-122.33539199999998,47.4256],[-122.33536999999998,47.42531800000001],[-122.33536,47.42526],[-122.33534999999999,47.42517000000001],[-122.33529999999999,47.424510000000005],[-122.33526,47.42396],[-122.335212,47.42327],[-122.335204,47.42309],[-122.33524,47.421510000000005],[-122.33525,47.42109],[-122.33525300000001,47.420950000000005],[-122.33526,47.42046],[-122.33527,47.420183],[-122.33527,47.419819999999994],[-122.33527,47.41918],[-122.33527,47.418727],[-122.33527,47.41858],[-122.33527,47.417362999999995],[-122.33528,47.41659],[-122.33525,47.416039999999995],[-122.3352,47.41558],[-122.33481,47.413419999999995],[-122.33468,47.41273],[-122.33458,47.41217],[-122.33447,47.411579999999994],[-122.33441,47.41141999999999],[-122.334349,47.4113],[-122.33421000000001,47.411111],[-122.3341,47.41099],[-122.33403,47.410933],[-122.33361000000001,47.410639999999994],[-122.33352000000001,47.41057],[-122.33344,47.4105],[-122.33336999999999,47.41042099999999],[-122.33331,47.41034],[-122.33325,47.410259999999994],[-122.33321000000001,47.41017],[-122.33315,47.410015],[-122.33313,47.40992],[-122.33308,47.409648999999995],[-122.33291,47.40882],[-122.33286999999999,47.408730000000006],[-122.33282999999999,47.40866],[-122.33278199999998,47.40858],[-122.33268199999999,47.408462],[-122.3326,47.408390000000004],[-122.33251,47.40832],[-122.332422,47.40826],[-122.33241,47.408252000000005],[-122.33224,47.40816],[-122.33205099999999,47.40809],[-122.33185,47.408038000000005],[-122.33164000000001,47.408],[-122.331428,47.40799],[-122.33124000000001,47.40799],[-122.331,47.408027],[-122.33079,47.408072000000004],[-122.32976999999998,47.408324],[-122.32946999999999,47.408391],[-122.32929999999999,47.40844],[-122.32911,47.40847],[-122.32771,47.40856],[-122.3276,47.408570000000005],[-122.32753,47.408570000000005],[-122.32746,47.408570000000005],[-122.32725,47.408570000000005],[-122.32706,47.408570000000005],[-122.32691,47.408541],[-122.326893,47.40815200000001],[-122.32682,47.407948],[-122.32685,47.407669999999996],[-122.32708,47.40739],[-122.32743099999999,47.407301],[-122.32749,47.40728599999999],[-122.32748,47.40717],[-122.32742,47.40718],[-122.32741,47.40696],[-122.32826,47.406936],[-122.32822999999999,47.406388],[-122.32892,47.40637],[-122.32885999999999,47.405330000000006],[-122.32954799999999,47.40531000000001],[-122.32951,47.40455],[-122.32951,47.40439],[-122.32968,47.404388],[-122.33065,47.404379999999996],[-122.33097,47.40477],[-122.33136,47.404619999999994],[-122.33141,47.40461],[-122.33157,47.404556],[-122.33171,47.404959999999996],[-122.33173,47.40502],[-122.3319,47.405190000000005],[-122.33233999999999,47.40561],[-122.33369,47.40692],[-122.33523,47.408421],[-122.3365,47.409164],[-122.34026000000001,47.411359999999995],[-122.34349,47.413256999999994],[-122.34663,47.415099],[-122.34789,47.415839999999996],[-122.34803,47.415921],[-122.34892,47.41793],[-122.34952000000001,47.419373],[-122.349835,47.420120000000004],[-122.35072999999998,47.42322],[-122.35145,47.426770000000005],[-122.35195,47.429190000000006],[-122.35237999999998,47.43131],[-122.35323,47.435520000000004],[-122.3543,47.43912],[-122.35436999999999,47.43934],[-122.35512999999999,47.441919999999996],[-122.35585999999999,47.442302000000005],[-122.35704,47.44292],[-122.35837999999998,47.44356],[-122.35933999999999,47.44402699999999],[-122.36184,47.44522],[-122.36015,47.44518],[-122.35934999999999,47.445163],[-122.35745,47.445150000000005],[-122.35621,47.445114000000004],[-122.35619,47.445121],[-122.35589999999999,47.44511000000001],[-122.35571999999999,47.44512],[-122.35566999999999,47.445201],[-122.35566999999999,47.44522],[-122.35565,47.445370000000004],[-122.355644,47.44547],[-122.35556999999999,47.445555000000006],[-122.35555,47.446239999999996],[-122.35552,47.447089999999996],[-122.35548999999999,47.447869999999995],[-122.35548999999999,47.44796899999999],[-122.35544999999999,47.44883],[-122.35556999999999,47.448834],[-122.35567999999999,47.448479999999996],[-122.35595099999999,47.44839],[-122.35665,47.448310000000006],[-122.35665,47.44874],[-122.35919,47.44874],[-122.36134,47.44874],[-122.36213,47.449614],[-122.36232999999999,47.449614],[-122.36246,47.450019999999995]]]},"name":"Normandy Park"},{"boundary":{"type":"Polygon","coordinates":[[[-122.33776,47.831540999999994],[-122.33760000000001,47.831489999999995],[-122.337489,47.831469999999996],[-122.33718,47.83140099999999],[-122.33685799999999,47.83134],[-122.33676,47.831308],[-122.33668,47.831289999999996],[-122.3365,47.831253999999994],[-122.33641,47.83123],[-122.33621000000001,47.831179999999996],[-122.33609,47.83116],[-122.33595,47.83115],[-122.33586,47.831143],[-122.33556,47.831139],[-122.3355,47.83114],[-122.335443,47.83114],[-122.33518,47.83114],[-122.33512999999999,47.83114],[-122.33481,47.83113],[-122.3345,47.83107999999999],[-122.33429000000001,47.83103],[-122.33418,47.83101],[-122.334,47.83095],[-122.3339,47.830912000000005],[-122.333769,47.83085],[-122.33322000000001,47.83058],[-122.33297199999998,47.83045],[-122.33283999999999,47.8304],[-122.33274999999999,47.83036],[-122.33266,47.830330000000004],[-122.33242,47.830276],[-122.33232999999998,47.83026699999999],[-122.33223,47.830258],[-122.33212999999999,47.830258],[-122.33202999999999,47.830265],[-122.33195,47.83027],[-122.33186,47.83029],[-122.331799,47.8303],[-122.33163,47.83034],[-122.33161300000002,47.830345],[-122.33155000000001,47.83036],[-122.33153,47.83037],[-122.33145,47.830402],[-122.331351,47.83044099999999],[-122.33120600000001,47.830510000000004],[-122.33115000000001,47.83055],[-122.33106000000001,47.83061],[-122.33104,47.830619999999996],[-122.33095,47.830690999999995],[-122.33094,47.830706],[-122.33081,47.830839999999995],[-122.33068,47.83099],[-122.33059,47.831087999999994],[-122.33042,47.83132],[-122.33024,47.831559999999996],[-122.32997999999999,47.831919],[-122.32963,47.83238],[-122.32941,47.832653],[-122.329227,47.83285],[-122.32912999999999,47.832950000000004],[-122.32902999999999,47.83305],[-122.32869,47.83335],[-122.32857999999999,47.833439999999996],[-122.32852999999999,47.833479999999994],[-122.32829,47.83367],[-122.32802,47.83385],[-122.32796,47.83389],[-122.32785,47.83396],[-122.32762000000001,47.83411],[-122.32728,47.83429699999999],[-122.32725,47.83431],[-122.32697999999999,47.83445],[-122.32652,47.8347],[-122.32618,47.83487999999999],[-122.32616,47.8349],[-122.32588999999999,47.83505],[-122.32566,47.83522],[-122.32556,47.835300000000004],[-122.325514,47.835342000000004],[-122.32539999999999,47.835474],[-122.32529,47.83561],[-122.32516999999999,47.83583],[-122.32513999999999,47.835937],[-122.32512999999999,47.83597],[-122.32508999999999,47.836259999999996],[-122.32507999999999,47.836368],[-122.32506999999998,47.83665],[-122.32502999999998,47.837162],[-122.32500499999999,47.837455],[-122.32499999999999,47.83752],[-122.32498,47.83767999999999],[-122.324968,47.837979999999995],[-122.3249,47.83879],[-122.32487799999998,47.83896],[-122.32482999999999,47.839237],[-122.32464,47.839633],[-122.32454,47.83979],[-122.32425,47.840272],[-122.32403,47.84062899999999],[-122.32394,47.84080099999999],[-122.323924,47.840819999999994],[-122.32388999999999,47.84088899999999],[-122.32386999999999,47.84091],[-122.32377999999999,47.84105999999999],[-122.32369,47.84119999999999],[-122.32359,47.841319999999996],[-122.32354,47.841379999999994],[-122.32343699999998,47.841469999999994],[-122.32329999999999,47.84159],[-122.32306999999999,47.841792],[-122.32301,47.84184199999999],[-122.32292,47.84193],[-122.32279999999999,47.84205],[-122.32217899999999,47.842639999999996],[-122.32206999999998,47.842742],[-122.3219,47.8429],[-122.32163,47.84314],[-122.321495,47.843239999999994],[-122.32146999999999,47.843256999999994],[-122.32135,47.84333],[-122.32121000000001,47.843399999999995],[-122.32100799999999,47.843489999999996],[-122.32066999999999,47.843619999999994],[-122.32043999999999,47.84371],[-122.32028,47.84379],[-122.3202,47.84383],[-122.32006999999999,47.843914],[-122.32005,47.84393],[-122.31991000000001,47.844035],[-122.31983,47.8441],[-122.31968,47.844269999999995],[-122.31967,47.844289999999994],[-122.31962800000001,47.84435],[-122.31961500000001,47.844379999999994],[-122.31948,47.84459],[-122.31936,47.844759999999994],[-122.31967,47.844739999999994],[-122.32006,47.84475],[-122.32117,47.84475],[-122.32376999999998,47.84473],[-122.32485,47.844728999999994],[-122.32493,47.844742999999994],[-122.32524,47.844753999999995],[-122.32571999999999,47.84477],[-122.32646,47.844789999999996],[-122.32798,47.844832],[-122.328694,47.84483999999999],[-122.33044,47.84487999999999],[-122.33044,47.84516],[-122.33041,47.84647999999999],[-122.33041,47.84654],[-122.33048,47.84692],[-122.33046999999999,47.84715],[-122.33048,47.847289999999994],[-122.33046,47.84753],[-122.33049,47.847669999999994],[-122.33046999999999,47.847739999999995],[-122.33046999999999,47.847804999999994],[-122.33046999999999,47.847919999999995],[-122.33046999999999,47.84793],[-122.33046999999999,47.84797],[-122.33046999999999,47.84797999999999],[-122.33046999999999,47.848],[-122.33046999999999,47.848079999999996],[-122.33046999999999,47.848109],[-122.33046999999999,47.848279999999995],[-122.33046999999999,47.848372000000005],[-122.32766000000001,47.84834],[-122.32763,47.84923],[-122.32759,47.84985999999999],[-122.32758,47.84983],[-122.32756,47.84977],[-122.32755,47.84975],[-122.32754,47.84972],[-122.32751,47.849669999999996],[-122.32749,47.849633],[-122.32746999999999,47.849599999999995],[-122.32742,47.84954],[-122.32735,47.849489999999996],[-122.32726000000001,47.849439999999994],[-122.32717099999999,47.849399999999996],[-122.32706999999999,47.849379],[-122.32684799999998,47.849329],[-122.32663,47.84927999999999],[-122.32632999999998,47.849219999999995],[-122.32623,47.849193],[-122.32587999999998,47.84912],[-122.32580999999999,47.84911],[-122.32554999999999,47.849059999999994],[-122.32540999999999,47.849025],[-122.32513999999999,47.848966999999995],[-122.32497,47.848943],[-122.32482999999999,47.84892],[-122.32482,47.8491],[-122.3248,47.849619999999994],[-122.3248,47.84979],[-122.32479,47.849869999999996],[-122.32477999999999,47.850089999999994],[-122.32477999999999,47.850159999999995],[-122.32479,47.85021],[-122.32479,47.850339999999996],[-122.3248,47.85039],[-122.3248,47.85048999999999],[-122.32481,47.85059999999999],[-122.32482,47.850789999999996],[-122.324833,47.85089399999999],[-122.32484,47.850939999999994],[-122.32485,47.85108999999999],[-122.32485,47.85114299999999],[-122.32486,47.85123299999999],[-122.32488,47.85133999999999],[-122.32489,47.85150399999999],[-122.3249,47.851589999999995],[-122.32492,47.85184799999999],[-122.32492,47.85192099999999],[-122.32202,47.851989999999994],[-122.32217999999999,47.853829999999995],[-122.31976,47.853829999999995],[-122.31854,47.853838999999994],[-122.31552,47.85386999999999],[-122.31552,47.853899999999996],[-122.31441000000001,47.85391],[-122.31441600000001,47.853899999999996],[-122.31435,47.853899999999996],[-122.31435,47.853719999999996],[-122.31435,47.853469999999994],[-122.31435,47.85312],[-122.31434,47.85193399999999],[-122.3143,47.85193399999999],[-122.31429000000001,47.85171999999999],[-122.31429000000001,47.850752],[-122.31428500000001,47.850339999999996],[-122.31428000000001,47.85021999999999],[-122.31351000000001,47.85021],[-122.31171,47.850192],[-122.3117,47.850319999999996],[-122.31141000000001,47.850316],[-122.31111000000001,47.85036099999999],[-122.30902,47.85031],[-122.30904,47.849219999999995],[-122.30902999999999,47.84914],[-122.30902999999999,47.84901],[-122.30902999999999,47.84875],[-122.30904,47.84868099999999],[-122.30904,47.84865],[-122.30904,47.848551],[-122.30904,47.84852],[-122.30904,47.8484],[-122.30633999999999,47.84841],[-122.303627,47.8484],[-122.30352,47.8484],[-122.30352,47.84843],[-122.29812000000001,47.84839],[-122.29811400000001,47.848279999999995],[-122.29811000000001,47.848150000000004],[-122.298106,47.84794599999999],[-122.29809,47.847559999999994],[-122.29808,47.84743099999999],[-122.29808,47.84739999999999],[-122.29808,47.847319999999996],[-122.29808,47.84724299999999],[-122.29618900000001,47.84724799999999],[-122.29578,47.84724899999999],[-122.29542000000001,47.84779999999999],[-122.29493000000001,47.847812],[-122.29439,47.84781999999999],[-122.29351000000001,47.850089999999994],[-122.29346000000001,47.850179999999995],[-122.29343,47.850249999999996],[-122.29317,47.85031],[-122.29312000000002,47.850319999999996],[-122.29294,47.850359999999995],[-122.29288,47.850320999999994],[-122.29271,47.85023399999999],[-122.29232999999999,47.85003],[-122.29221000000001,47.849968999999994],[-122.29245,47.849779999999996],[-122.29245,47.8493],[-122.29086000000001,47.849725],[-122.29056600000001,47.849799999999995],[-122.29046000000001,47.84983],[-122.29037,47.849869999999996],[-122.29027,47.84991],[-122.29004,47.85003999999999],[-122.28935,47.850429999999996],[-122.28895,47.85065999999999],[-122.28887999999999,47.850699999999996],[-122.28876999999999,47.85075],[-122.28872999999999,47.85076699999999],[-122.28864,47.85079699999999],[-122.28853,47.850829999999995],[-122.28801,47.85098099999999],[-122.28774,47.851029999999994],[-122.287476,47.850899999999996],[-122.28731,47.850809999999996],[-122.28721000000002,47.850756],[-122.28710600000001,47.850699999999996],[-122.28675,47.85049399999999],[-122.28678,47.85043999999999],[-122.28655,47.850339999999996],[-122.28646,47.8503],[-122.28620000000001,47.85021],[-122.28612000000001,47.850179999999995],[-122.28586999999999,47.850179999999995],[-122.28524,47.85019],[-122.28488,47.85019],[-122.2847,47.850179999999995],[-122.28471,47.850049999999996],[-122.28474,47.84985999999999],[-122.28475,47.849819999999994],[-122.28478,47.849596],[-122.28481000000001,47.849399999999996],[-122.28485,47.849219999999995],[-122.28486000000001,47.84913],[-122.28410000000001,47.84913],[-122.28304,47.84913],[-122.28189,47.84914],[-122.28190000000001,47.848910000000004],[-122.28190000000001,47.848879999999994],[-122.28190000000001,47.84881],[-122.28190000000001,47.84878],[-122.28189,47.84856],[-122.28188,47.84805],[-122.28188,47.84791],[-122.28189,47.84768999999999],[-122.28165000000001,47.847699999999996],[-122.28154,47.847699999999996],[-122.28143,47.847699999999996],[-122.28122400000002,47.84767999999999],[-122.28106000000001,47.84768399999999],[-122.28095,47.84767599999999],[-122.28085,47.84765999999999],[-122.28075,47.84761999999999],[-122.28067,47.847579999999994],[-122.28062000000001,47.847539999999995],[-122.28054,47.84748999999999],[-122.28045,47.847449999999995],[-122.28038,47.84743999999999],[-122.28025000000001,47.84742599999999],[-122.28020000000001,47.84742599999999],[-122.27971,47.847429999999996],[-122.27932999999999,47.84741999999999],[-122.27906999999999,47.847424999999994],[-122.27896,47.84742099999999],[-122.27793,47.847429999999996],[-122.27788,47.847427999999994],[-122.27782,47.847429999999996],[-122.27774,47.84743099999999],[-122.27758,47.847429999999996],[-122.277588,47.847542999999995],[-122.27736999999999,47.84754399999999],[-122.27736999999999,47.847089999999994],[-122.27656,47.84653],[-122.276468,47.846526999999995],[-122.27495,47.84653],[-122.27495,47.84639],[-122.27507999999999,47.84639],[-122.27506,47.845710000000004],[-122.27506,47.84565],[-122.27506,47.8456],[-122.27506,47.84486399999999],[-122.27499,47.84486499999999],[-122.27484,47.84486999999999],[-122.274843,47.84459699999999],[-122.27484,47.844139999999996],[-122.2748,47.84307999999999],[-122.27457,47.84307999999999],[-122.27453,47.84277],[-122.27447,47.84234],[-122.27439,47.84212],[-122.27425000000001,47.84173],[-122.27406,47.84122899999999],[-122.27383999999999,47.84081],[-122.27385,47.84049699999999],[-122.27383999999999,47.84026899999999],[-122.27383999999999,47.84012],[-122.27383999999999,47.839999999999996],[-122.27383999999999,47.83992],[-122.27385,47.83929],[-122.2741,47.838879],[-122.273682,47.83806],[-122.27327,47.837661999999995],[-122.273196,47.837378],[-122.27299599999999,47.83665],[-122.27266,47.83676],[-122.27230999999999,47.836879999999994],[-122.27247999999999,47.83717],[-122.27262400000001,47.83753],[-122.27229,47.83767999999999],[-122.27134,47.83717],[-122.271292,47.83713],[-122.27086999999999,47.83685],[-122.27082,47.83680699999999],[-122.27072999999999,47.83672],[-122.27029999999999,47.836332000000006],[-122.27016,47.836202],[-122.27006,47.836116000000004],[-122.26977,47.83586],[-122.26967,47.835770000000004],[-122.26945,47.835636],[-122.2693,47.83554],[-122.26819,47.83483999999999],[-122.26782000000001,47.834599999999995],[-122.2677,47.834560999999994],[-122.26755000000001,47.83447999999999],[-122.26736000000001,47.83436],[-122.26616000000001,47.833619999999996],[-122.26512000000001,47.83297],[-122.26467000000001,47.83268699999999],[-122.26424000000002,47.832139000000005],[-122.26427000000001,47.831619999999994],[-122.26432000000001,47.83098],[-122.26519,47.830304],[-122.26624000000001,47.828990000000005],[-122.26666000000002,47.82848],[-122.26687,47.828210000000006],[-122.26699,47.828030000000005],[-122.26794000000001,47.82752],[-122.26807,47.827448],[-122.26838,47.82729],[-122.26836999999999,47.826923],[-122.26836,47.826501],[-122.26835,47.82574],[-122.26832,47.824459999999995],[-122.26835,47.824458],[-122.268324,47.82389],[-122.26832999999999,47.823543],[-122.26835,47.823543],[-122.26835,47.82262],[-122.26819,47.822614],[-122.26702000000002,47.822601],[-122.26702000000002,47.822164],[-122.26702700000001,47.822036000000004],[-122.267031,47.82135],[-122.26703,47.821303],[-122.26657,47.82129],[-122.26572999999999,47.82127],[-122.26572999999999,47.82123],[-122.26573099999999,47.821219],[-122.26572,47.82087],[-122.26572,47.820750000000004],[-122.2651,47.820729],[-122.26502,47.820730000000005],[-122.26494000000001,47.820727],[-122.26479,47.820730000000005],[-122.26447,47.82072],[-122.26423000000001,47.82072],[-122.26389,47.820710000000005],[-122.26294,47.820695],[-122.2627,47.820692],[-122.26251,47.82068399999999],[-122.2625,47.82041],[-122.262492,47.82007],[-122.26190000000001,47.82005],[-122.26187300000001,47.81883],[-122.26207,47.818833],[-122.26232,47.818839999999994],[-122.26304,47.81885],[-122.263031,47.817969999999995],[-122.26325000000001,47.817949999999996],[-122.26373,47.81795999999999],[-122.26411000000002,47.817972],[-122.26463000000001,47.81797999999999],[-122.26489000000001,47.817989999999995],[-122.26522000000001,47.817989999999995],[-122.26547,47.818],[-122.26571,47.817995999999994],[-122.26571,47.818059999999996],[-122.26572,47.818259999999995],[-122.26572,47.81833],[-122.26572,47.81835],[-122.26572,47.8184],[-122.26572,47.818419999999996],[-122.26572,47.81859],[-122.26572,47.81911],[-122.26572999999999,47.81927999999999],[-122.26573099999999,47.81945999999999],[-122.26574,47.81981],[-122.26677,47.819849999999995],[-122.26698,47.819849999999995],[-122.26785000000001,47.819872999999994],[-122.26835,47.819889999999994],[-122.269102,47.81991],[-122.27091,47.81995],[-122.27089,47.81983099999999],[-122.270879,47.81963],[-122.27087999999999,47.819531],[-122.27086999999999,47.819419999999994],[-122.27087999999999,47.819019999999995],[-122.27085,47.81903],[-122.27085,47.818963999999994],[-122.2709,47.818963999999994],[-122.27102000000001,47.818956],[-122.27199,47.81896],[-122.27215,47.81887],[-122.27216,47.818690999999994],[-122.27216999999999,47.818391],[-122.27226,47.818391],[-122.27256999999999,47.81839],[-122.27279999999999,47.818394],[-122.27302999999999,47.8184],[-122.27361,47.81841],[-122.27438,47.818419999999996],[-122.27485,47.818431],[-122.27484,47.81722599999999],[-122.27537999999998,47.817229999999995],[-122.27591,47.817229999999995],[-122.27591,47.817355],[-122.27619,47.81741999999999],[-122.27631,47.81755999999999],[-122.27685699999999,47.81756299999999],[-122.27686,47.81741999999999],[-122.27725000000001,47.81741999999999],[-122.27816999999999,47.81741999999999],[-122.278262,47.817449999999994],[-122.27835999999999,47.817486999999986],[-122.27879999999999,47.81785999999999],[-122.27923,47.81823],[-122.27931,47.818301],[-122.27937999999999,47.818259999999995],[-122.2803,47.81905999999999],[-122.28053,47.81922699999999],[-122.28079,47.819267999999994],[-122.28081,47.819269999999996],[-122.28116600000001,47.819199999999995],[-122.28149,47.81905999999999],[-122.28164000000001,47.819008],[-122.28183,47.81895],[-122.28202,47.81881],[-122.28276999999999,47.818239999999996],[-122.28371,47.81783999999999],[-122.283785,47.817809999999994],[-122.28451000000001,47.81741999999999],[-122.28404,47.81701999999999],[-122.28352000000001,47.816579999999995],[-122.28313,47.81625],[-122.28343,47.816089999999996],[-122.28371,47.81594],[-122.28423000000001,47.815656999999995],[-122.28472500000001,47.815396],[-122.2847,47.81362699999999],[-122.284817,47.813629999999996],[-122.28528,47.81363999999999],[-122.28546999999999,47.81363999999999],[-122.28571,47.81365999999999],[-122.28614,47.81367399999999],[-122.286246,47.813669999999995],[-122.28693,47.81368299999999],[-122.2885,47.81367999999999],[-122.28856999999999,47.81367999999999],[-122.28934,47.813689999999994],[-122.2897,47.8137],[-122.289712,47.8137],[-122.29029000000001,47.8137],[-122.29083,47.8137],[-122.29087,47.8137],[-122.29098,47.813689999999994],[-122.29120000000002,47.81365999999999],[-122.29148,47.81353],[-122.29155000000002,47.81347999999999],[-122.29161000000002,47.81343999999999],[-122.29167000000001,47.813379999999995],[-122.29174,47.813281999999994],[-122.29178,47.813179999999996],[-122.29180000000001,47.813039999999994],[-122.29180000000001,47.812928],[-122.29178,47.812719],[-122.291774,47.81257],[-122.29180000000001,47.81251],[-122.29185000000001,47.81246599999999],[-122.29189000000001,47.81245],[-122.29191000000002,47.812439999999995],[-122.29200300000001,47.81241],[-122.29212000000001,47.8124],[-122.29227,47.81243],[-122.29228,47.8123],[-122.2923,47.81212],[-122.292314,47.812039999999996],[-122.29232999999999,47.81193699999999],[-122.29232999999999,47.81191],[-122.29232999999999,47.81185999999999],[-122.29232999999999,47.81177999999999],[-122.29232999999999,47.81175699999999],[-122.29232999999999,47.81168999999999],[-122.29232999999999,47.811671999999994],[-122.29232,47.81147999999999],[-122.29232999999999,47.81134099999999],[-122.29232999999999,47.81124199999999],[-122.29232,47.810629999999996],[-122.29232,47.81035],[-122.29232,47.810019999999994],[-122.29229000000001,47.809659999999994],[-122.29224,47.80918],[-122.29226000000001,47.80859],[-122.29227,47.80824],[-122.29231,47.8082],[-122.29231,47.807849999999995],[-122.29232,47.80735],[-122.29234,47.80681],[-122.29234,47.80679],[-122.29234,47.806436],[-122.29247,47.806436],[-122.29289,47.806439],[-122.29303,47.806439999999995],[-122.29344400000001,47.806442],[-122.2947,47.80645],[-122.29488,47.80645],[-122.29493000000001,47.80645],[-122.29512000000001,47.806459999999994],[-122.29557,47.806472],[-122.29565000000001,47.80647999999999],[-122.29593,47.80649],[-122.29690000000001,47.80649],[-122.29726000000002,47.80649],[-122.29743,47.8065],[-122.29779300000001,47.80649],[-122.29820000000001,47.80649],[-122.298688,47.8065],[-122.29921000000002,47.80648699999999],[-122.29943,47.80649],[-122.29984,47.80649],[-122.29996000000001,47.80649],[-122.30031,47.8065],[-122.30042999999999,47.8065],[-122.30054,47.8065],[-122.30089,47.8065],[-122.30101,47.8065],[-122.30145,47.80651],[-122.30148,47.80651],[-122.30276999999998,47.806529],[-122.30321,47.80654],[-122.30366000000001,47.80653],[-122.30475,47.80654],[-122.30526,47.80654],[-122.30559,47.80654],[-122.30579699999998,47.80653],[-122.30654,47.80655],[-122.30753,47.80656],[-122.30822,47.806562],[-122.30852,47.80658],[-122.30893999999999,47.80659],[-122.30925,47.80663],[-122.30968,47.80671],[-122.31018,47.80686299999999],[-122.31044,47.806959],[-122.31082,47.80709699999999],[-122.31107,47.807178],[-122.31132000000001,47.80723],[-122.31139,47.80723999999999],[-122.31146000000001,47.807249999999996],[-122.31169000000001,47.80727999999999],[-122.31176,47.807289999999995],[-122.31209,47.80727999999999],[-122.31231,47.807255],[-122.3124,47.807244999999995],[-122.31256,47.807219999999994],[-122.31271,47.80718099999999],[-122.31286,47.80714],[-122.313,47.807089999999995],[-122.3134,47.80693],[-122.31364,47.80685],[-122.31385,47.806776],[-122.31399,47.806737],[-122.31414000000001,47.80671],[-122.31439,47.80667],[-122.31466000000002,47.806653],[-122.31486000000001,47.80665],[-122.31514,47.806656],[-122.3153,47.806658],[-122.31536999999999,47.806659999999994],[-122.31566600000001,47.80666399999999],[-122.31595,47.80667],[-122.31629000000001,47.80667],[-122.316793,47.80667999999999],[-122.31683,47.80667999999999],[-122.31708,47.80667999999999],[-122.31741000000001,47.80655],[-122.31842,47.80615],[-122.31876,47.80601],[-122.31939,47.80577],[-122.32129,47.805051],[-122.32192,47.804809999999996],[-122.322224,47.8047],[-122.32312999999999,47.80437],[-122.32342999999999,47.80425999999999],[-122.323662,47.80418399999999],[-122.32435,47.80395],[-122.32458,47.803869999999996],[-122.32486999999999,47.80373],[-122.32574999999999,47.80332],[-122.32603999999999,47.803188],[-122.32657999999999,47.80299],[-122.3282,47.802412000000004],[-122.32874099999998,47.80222],[-122.32887999999998,47.80207],[-122.32956,47.802274],[-122.32972999999998,47.80232],[-122.32986999999999,47.80237],[-122.32972999999998,47.802578000000004],[-122.32982999999999,47.80279],[-122.32926,47.80321],[-122.32909,47.803459999999994],[-122.32873999999998,47.80394],[-122.32717,47.80612],[-122.32665,47.806839999999994],[-122.326454,47.80712],[-122.326,47.80775],[-122.32585999999999,47.807939999999995],[-122.32566,47.808215000000004],[-122.32552999999999,47.808398000000004],[-122.32643999999999,47.80843],[-122.32643999999999,47.80912],[-122.32645,47.809419999999996],[-122.32669,47.809418],[-122.32716,47.809419],[-122.32745,47.809439999999995],[-122.32745,47.809239999999996],[-122.32744,47.80914],[-122.32744,47.80905],[-122.32742999999999,47.80878],[-122.32742999999999,47.80869],[-122.32762000000001,47.808688999999994],[-122.32800999999999,47.808685],[-122.32818999999999,47.80869],[-122.32828699999999,47.80869],[-122.32837999999998,47.808695],[-122.32845999999999,47.808695],[-122.32866999999999,47.808696],[-122.32874999999999,47.8087],[-122.32878399999998,47.80936],[-122.32879999999999,47.80959],[-122.32885999999999,47.80959],[-122.32922,47.809593],[-122.32922,47.80968599999999],[-122.32926,47.810539999999996],[-122.32934999999999,47.810539999999996],[-122.32961,47.810539999999996],[-122.32986999999999,47.810539999999996],[-122.329955,47.810539999999996],[-122.33021000000001,47.81053],[-122.33039,47.810539999999996],[-122.33045,47.810539999999996],[-122.33117,47.810545],[-122.33141,47.81054699999999],[-122.33159,47.81055],[-122.33212999999999,47.810555],[-122.33229999999999,47.810559999999995],[-122.33245,47.810558],[-122.33286999999999,47.81056099999999],[-122.33301,47.810562],[-122.33357,47.810562],[-122.333912,47.810559999999995],[-122.33426000000001,47.81057],[-122.33481,47.810579999999995],[-122.33518,47.81057],[-122.33523,47.81057],[-122.33578999999999,47.81057],[-122.33578999999999,47.81093],[-122.33578999999999,47.811152],[-122.33578999999999,47.811418999999994],[-122.33579999999999,47.81163299999999],[-122.33579999999999,47.812],[-122.33579999999999,47.812365],[-122.33581,47.81265],[-122.33581,47.81274],[-122.33581,47.813179999999996],[-122.33582,47.81353],[-122.335822,47.813869999999994],[-122.33582,47.814249999999994],[-122.33582,47.81435999999999],[-122.33582,47.81439999999999],[-122.33582999999999,47.814699999999995],[-122.33583999999999,47.814809999999994],[-122.33583999999999,47.81501],[-122.33585,47.815599999999996],[-122.33585,47.815799999999996],[-122.33585,47.815836],[-122.33585,47.81595],[-122.33585,47.81599],[-122.33585,47.81603],[-122.33585,47.816145],[-122.33585,47.816179999999996],[-122.33585,47.81634],[-122.33583999999999,47.816689999999994],[-122.33585,47.81681999999999],[-122.33586,47.816979999999994],[-122.33586,47.81704499999999],[-122.33586,47.81722599999999],[-122.33586,47.81728999999999],[-122.33586,47.81739699999999],[-122.33586999999999,47.81773],[-122.33586999999999,47.81783999999999],[-122.33586,47.81788999999999],[-122.33586,47.81807],[-122.33585,47.818130000000004],[-122.33586,47.81825],[-122.33586999999999,47.81861],[-122.33587999999999,47.81873],[-122.33613,47.81873],[-122.33633999999999,47.818734],[-122.33686,47.81873],[-122.33689,47.81873],[-122.33714,47.818734],[-122.33714,47.81963],[-122.33723,47.81963],[-122.33725000000001,47.821110000000004],[-122.33725000000001,47.82134],[-122.33729000000001,47.821472],[-122.33729000000001,47.82181],[-122.3359,47.821799999999996],[-122.3359,47.82185],[-122.3359,47.822072000000006],[-122.3359,47.82215000000001],[-122.33588999999999,47.82228],[-122.3359,47.82245],[-122.3359,47.822694],[-122.3359,47.82283],[-122.3359,47.823],[-122.3359,47.82305],[-122.3359,47.82349],[-122.3359,47.82366],[-122.3359,47.82396],[-122.3359,47.824],[-122.3359,47.82487],[-122.3359,47.825026],[-122.3359,47.82518],[-122.3359,47.8254],[-122.33591,47.82578],[-122.335912,47.82585],[-122.33592999999999,47.82592],[-122.33597999999999,47.82604],[-122.33599,47.82606],[-122.33605,47.826170000000005],[-122.3361,47.82625],[-122.33639,47.826496],[-122.3368,47.82677],[-122.33694,47.826859999999996],[-122.33711000000001,47.827],[-122.33717,47.82705],[-122.33724000000001,47.827101],[-122.33741,47.827296999999994],[-122.33749,47.82743],[-122.33759,47.82766099999999],[-122.33761200000001,47.82776],[-122.33765000000001,47.828100000000006],[-122.33769000000001,47.828450000000004],[-122.337711,47.82870200000001],[-122.337719,47.828798000000006],[-122.33773,47.829059],[-122.33773,47.829150000000006],[-122.33773,47.82932],[-122.33773,47.829934],[-122.33773,47.83002],[-122.33774,47.8301],[-122.33774,47.830255],[-122.33774,47.83038],[-122.33774,47.83056],[-122.33775,47.830738000000004],[-122.33774,47.831219],[-122.33774,47.83125],[-122.33776,47.831540999999994]]]},"name":"Lynnwood"},{"boundary":{"type":"Polygon","coordinates":[[[-122.30952,47.35794299999999],[-122.30909,47.35875],[-122.30892999999999,47.35903999999999],[-122.308602,47.35965999999999],[-122.30829,47.36026],[-122.30807999999999,47.36069],[-122.30779,47.36123],[-122.30761000000001,47.361537],[-122.30699,47.362730000000006],[-122.30566999999999,47.365259],[-122.30538999999999,47.3658],[-122.30506999999999,47.366398000000004],[-122.30486,47.36685],[-122.30475,47.36705],[-122.30467,47.36718],[-122.30451000000001,47.36743],[-122.30422000000002,47.368001],[-122.30385,47.368700000000004],[-122.30332,47.36977],[-122.3031,47.37017],[-122.30266999999999,47.37096],[-122.30232,47.371649999999995],[-122.30199,47.372280999999994],[-122.30163,47.37295],[-122.30138,47.37345],[-122.30107199999999,47.374013],[-122.30075,47.374635],[-122.30068,47.374779999999994],[-122.30066000000001,47.37481],[-122.30116000000001,47.37481999999999],[-122.30162000000001,47.374821999999995],[-122.30199,47.374829999999996],[-122.302369,47.374829999999996],[-122.30245,47.37483399999999],[-122.30256,47.374835],[-122.30256,47.375],[-122.30256,47.375149],[-122.30256,47.3752],[-122.30256,47.37535200000001],[-122.30256,47.37538],[-122.30256,47.37541],[-122.30256,47.375544],[-122.30256,47.3759],[-122.30273999999999,47.3759],[-122.30282999999999,47.3759],[-122.30337999999999,47.375910000000005],[-122.30469000000001,47.375930000000004],[-122.30574999999999,47.37595],[-122.30681,47.375965],[-122.30782,47.37598],[-122.30782,47.376079999999995],[-122.30786,47.377779999999994],[-122.3079,47.37956],[-122.30718,47.37955],[-122.30672,47.37955],[-122.30609,47.37953],[-122.30493,47.3795],[-122.3048,47.3795],[-122.30382999999999,47.37949],[-122.30376999999999,47.37948399999999],[-122.30372999999999,47.37947],[-122.30212,47.37943],[-122.30196000000001,47.37945],[-122.301826,47.379459999999995],[-122.30062000000001,47.37945],[-122.30053,47.37945],[-122.30054,47.380339],[-122.30053,47.381099999999996],[-122.29962000000002,47.381142999999994],[-122.29871,47.38115],[-122.29868,47.382166],[-122.29867,47.38259],[-122.29751000000002,47.38259],[-122.29743,47.382889999999996],[-122.29710000000001,47.384229999999995],[-122.29769000000002,47.38425999999999],[-122.29769000000002,47.384401999999994],[-122.29769000000002,47.38464999999999],[-122.29866000000001,47.38465999999999],[-122.29865000000001,47.38494399999999],[-122.29871,47.38623],[-122.29872999999999,47.38652],[-122.29863,47.38652],[-122.29816000000001,47.38652],[-122.29772000000001,47.38652],[-122.29729000000002,47.38653],[-122.29671,47.38652],[-122.29654000000001,47.38652],[-122.29648,47.386779999999995],[-122.29642000000001,47.387049999999995],[-122.29633,47.38737],[-122.29639,47.38737999999999],[-122.29724400000002,47.38737999999999],[-122.29723000000001,47.38838],[-122.29722000000002,47.38903],[-122.29592000000001,47.38901],[-122.29579,47.389576],[-122.29558,47.390403],[-122.29581,47.390432000000004],[-122.29720000000002,47.39043],[-122.29720000000002,47.39096],[-122.29627,47.390964],[-122.29553,47.390965],[-122.29544,47.390965],[-122.29536,47.391345],[-122.29519,47.392030000000005],[-122.29511000000001,47.392437],[-122.29521000000001,47.39244],[-122.29719000000001,47.39246],[-122.29719000000001,47.39372],[-122.29756000000002,47.393724],[-122.29775000000001,47.393724],[-122.29879,47.39374],[-122.29885,47.39553000000001],[-122.29853,47.39548],[-122.29788,47.39537800000001],[-122.29762000000002,47.395328000000006],[-122.297437,47.395286],[-122.29716000000002,47.395208000000004],[-122.29700000000001,47.395157000000005],[-122.29683,47.39509],[-122.29664000000001,47.395010000000006],[-122.29651000000001,47.394954],[-122.29624000000001,47.39481],[-122.29610000000001,47.394723],[-122.29596000000001,47.39463],[-122.29592000000001,47.394594],[-122.29548,47.39422],[-122.2954,47.394059999999996],[-122.29493000000001,47.39365],[-122.294739,47.39348],[-122.29389,47.392790000000005],[-122.29333,47.39246],[-122.292806,47.39221800000001],[-122.29264,47.392160000000004],[-122.292589,47.39227],[-122.29203,47.39363],[-122.29198000000001,47.393750000000004],[-122.29186000000001,47.394031],[-122.292,47.39497],[-122.29212000000001,47.396069],[-122.29186000000001,47.396066],[-122.29173,47.396065],[-122.29132800000001,47.396065],[-122.29035,47.396076],[-122.29027,47.395851],[-122.28993,47.395857],[-122.28934,47.39588],[-122.289305,47.395810000000004],[-122.28732000000001,47.395848],[-122.287315,47.395686],[-122.28655,47.39569],[-122.286568,47.39647],[-122.28407,47.396556000000004],[-122.28412000000002,47.39664],[-122.283466,47.396771],[-122.28347,47.397026],[-122.282945,47.397452],[-122.28276,47.39745],[-122.28276999999999,47.39927],[-122.28394,47.39925],[-122.28398,47.40107],[-122.28563,47.40104699999999],[-122.28655,47.40103],[-122.29019000000001,47.400968],[-122.29018,47.401019],[-122.29018,47.401316],[-122.29018,47.40166299999999],[-122.29017,47.40183],[-122.29017,47.402100000000004],[-122.29017,47.402141],[-122.29009,47.402133000000006],[-122.28833999999999,47.402131000000004],[-122.28836999999999,47.40241],[-122.28838999999999,47.402615000000004],[-122.28847999999999,47.40284],[-122.28846,47.40287],[-122.28846999999999,47.40291800000001],[-122.288714,47.403259],[-122.28875,47.40336],[-122.288765,47.403459999999995],[-122.28877999999999,47.403659999999995],[-122.28852,47.40367],[-122.28684,47.40373],[-122.28656000000001,47.40374],[-122.28656000000001,47.40381],[-122.286581,47.408243],[-122.28615,47.408249],[-122.28565,47.408254],[-122.285255,47.40826],[-122.28502,47.40792],[-122.28488,47.40771],[-122.28486000000001,47.40767999999999],[-122.28475,47.407526999999995],[-122.28463,47.40741],[-122.28456000000001,47.40736],[-122.28446000000001,47.4073],[-122.2844,47.40727],[-122.28423000000001,47.407208],[-122.28413,47.40718],[-122.28403,47.40716],[-122.28391,47.40714],[-122.28376999999999,47.40714],[-122.28355,47.40715],[-122.28346,47.407162],[-122.28331,47.40719],[-122.28315,47.40724899999999],[-122.28304,47.407301],[-122.28289,47.407392],[-122.28275,47.407502],[-122.28198,47.40815200000001],[-122.28187,47.40823],[-122.2818,47.40827],[-122.28163,47.40832],[-122.28137,47.40838],[-122.28138,47.4084],[-122.28138,47.40844],[-122.28136,47.40848],[-122.28133,47.40851000000001],[-122.28128000000001,47.408530000000006],[-122.28123000000001,47.408550000000005],[-122.281318,47.408761],[-122.28135,47.40886],[-122.28136,47.40894],[-122.28138,47.40925],[-122.28137,47.410371],[-122.28134,47.410758],[-122.281316,47.41098099999999],[-122.28115000000001,47.411716],[-122.28105000000001,47.41216],[-122.28105000000001,47.412236],[-122.28101000000001,47.41247],[-122.28099,47.41256],[-122.28093,47.412631],[-122.28081,47.4127],[-122.28082300000001,47.412718000000005],[-122.28093,47.412802],[-122.28098,47.41285],[-122.28154,47.41356],[-122.28182000000001,47.4141],[-122.28215,47.414739999999995],[-122.28226000000001,47.41517],[-122.28235,47.415517],[-122.28217,47.415516000000004],[-122.28133,47.415526],[-122.27956,47.41555],[-122.27661,47.41928299999999],[-122.27624,47.41929],[-122.27436,47.41931],[-122.27312,47.419332000000004],[-122.27076999999998,47.41937],[-122.26767000000001,47.419399999999996],[-122.26734,47.419395],[-122.26718000000001,47.419399999999996],[-122.2664,47.42036],[-122.26548,47.421330000000005],[-122.26525000000001,47.42182],[-122.26521000000001,47.42266],[-122.26536999999999,47.423054],[-122.26544,47.42322],[-122.26558,47.423300000000005],[-122.26597,47.42383],[-122.26706000000001,47.42489],[-122.26725800000001,47.425233000000006],[-122.26722000000002,47.425540000000005],[-122.26621000000002,47.42694],[-122.26576999999999,47.42812000000001],[-122.26532999999999,47.42866],[-122.26493,47.42888],[-122.26418000000001,47.42918],[-122.26352000000001,47.42931000000001],[-122.26262900000002,47.429410000000004],[-122.25979099999999,47.429435000000005],[-122.25903,47.42958],[-122.25863,47.429811],[-122.25832,47.430248],[-122.25829999999999,47.430330000000005],[-122.25825,47.43049],[-122.25823199999999,47.430566],[-122.25845,47.43135],[-122.25899,47.43174],[-122.2597,47.432010000000005],[-122.26075,47.43227],[-122.26184,47.43253500000001],[-122.26245,47.43269],[-122.26286999999999,47.432900000000004],[-122.26313,47.433032000000004],[-122.26319000000001,47.43318],[-122.26321000000002,47.43327],[-122.26322000000002,47.433350000000004],[-122.26323000000001,47.433443999999994],[-122.26322000000002,47.433530000000005],[-122.26321000000002,47.43362],[-122.26318,47.4337],[-122.26315000000001,47.43379],[-122.26311000000001,47.43387],[-122.26307,47.43395],[-122.26301000000001,47.43402699999999],[-122.26295,47.434101],[-122.26288,47.43417],[-122.2625,47.43417],[-122.26229000000001,47.43417],[-122.26208,47.43416],[-122.26172000000001,47.43416],[-122.26085,47.434154],[-122.25995,47.43414],[-122.25954,47.43414],[-122.25927,47.43414],[-122.25846999999999,47.433530000000005],[-122.25787,47.43327],[-122.2573,47.43323],[-122.25701000000001,47.433288],[-122.25685,47.43332],[-122.25645,47.43352],[-122.25628,47.43352],[-122.25593699999999,47.43352],[-122.2548,47.433506],[-122.2485,47.43343],[-122.2484,47.43379],[-122.24820000000001,47.43452],[-122.24779000000001,47.43603],[-122.24752000000002,47.437053],[-122.24740000000001,47.437355000000004],[-122.24729000000002,47.43755],[-122.24714000000002,47.43773],[-122.24697,47.4379],[-122.24677,47.43806],[-122.24657,47.4382],[-122.24616000000002,47.43844],[-122.24587,47.438657],[-122.24566000000002,47.43889],[-122.24563,47.43894],[-122.24551000000001,47.4391],[-122.24545,47.439226],[-122.24538,47.43941],[-122.24535,47.4396],[-122.245336,47.43979],[-122.24535,47.43992],[-122.24546000000001,47.440433],[-122.24563,47.44123],[-122.24442000000002,47.441219999999994],[-122.24369000000002,47.441216],[-122.24333,47.44121],[-122.24229000000001,47.44120699999999],[-122.24176000000001,47.441199999999995],[-122.24143000000001,47.441199999999995],[-122.24098000000001,47.441199999999995],[-122.23908,47.441176],[-122.239,47.441179999999996],[-122.23883,47.441179999999996],[-122.23648,47.441178],[-122.23525000000001,47.441175],[-122.2351,47.441174],[-122.23289,47.441168],[-122.22976999999999,47.441158],[-122.22779,47.44115],[-122.22703,47.44115],[-122.22395,47.441136],[-122.22382,47.44113],[-122.22146000000001,47.441123],[-122.21989,47.441122],[-122.21772000000001,47.44116399999999],[-122.21759000000002,47.44117],[-122.21746000000002,47.44117],[-122.21649900000001,47.44117],[-122.21584,47.44115],[-122.2155,47.44114],[-122.21553,47.441019999999995],[-122.21554,47.44097],[-122.21558,47.44077],[-122.21561200000001,47.440625999999995],[-122.21562000000002,47.44060399999999],[-122.21566000000001,47.440419999999996],[-122.21569000000001,47.440391],[-122.21594,47.44017],[-122.21605000000001,47.440082],[-122.21620000000001,47.43996],[-122.21633,47.43984],[-122.21657,47.43964],[-122.21664000000001,47.439574],[-122.21683,47.439170000000004],[-122.216856,47.43911000000001],[-122.21691000000001,47.439],[-122.21702000000002,47.43874],[-122.21731000000001,47.438406],[-122.21746000000002,47.438229],[-122.21786000000002,47.43776],[-122.21798000000001,47.437619999999995],[-122.21805,47.43754],[-122.21806000000001,47.4375],[-122.21819,47.43699],[-122.21829000000001,47.436623],[-122.21895,47.43403],[-122.21988,47.43039],[-122.21994000000001,47.43019],[-122.21976000000001,47.43018],[-122.21928000000001,47.43022],[-122.21896000000001,47.43024],[-122.21847,47.430251],[-122.21837,47.43025],[-122.21821000000001,47.430246],[-122.21822000000002,47.43007],[-122.21822000000002,47.429786],[-122.21821600000001,47.429750000000006],[-122.21818,47.42749],[-122.21812000000001,47.42646],[-122.21812000000001,47.42624],[-122.21811000000001,47.42598],[-122.2181,47.42533000000001],[-122.21809,47.425188000000006],[-122.21813,47.42456],[-122.21812000000001,47.42434],[-122.21812000000001,47.42422],[-122.21811000000001,47.423634],[-122.21797000000001,47.422984],[-122.21790000000001,47.423],[-122.21755000000002,47.42302],[-122.21626000000002,47.42304],[-122.21485000000001,47.423058000000005],[-122.21469000000002,47.42306],[-122.21343,47.423076],[-122.21273,47.42308],[-122.21239,47.42309],[-122.21151000000002,47.423097],[-122.20995300000001,47.42311300000001],[-122.20894,47.423120000000004],[-122.20757,47.42313000000001],[-122.20611000000001,47.42313000000001],[-122.20559,47.42313000000001],[-122.20503,47.42313000000001],[-122.20465000000002,47.42313000000001],[-122.20453,47.42313000000001],[-122.20443,47.42313000000001],[-122.20432000000001,47.423120000000004],[-122.20396000000001,47.423120000000004],[-122.20301,47.423120000000004],[-122.20262000000001,47.423120000000004],[-122.20232999999999,47.42311000000001],[-122.2018,47.42311300000001],[-122.20099,47.42311000000001],[-122.19988,47.42311000000001],[-122.19969,47.42311000000001],[-122.19877999999999,47.42310500000001],[-122.197944,47.42310200000001],[-122.19706000000001,47.423100000000005],[-122.19706000000001,47.42404],[-122.19708,47.42582],[-122.19708,47.42669],[-122.19708,47.42850000000001],[-122.19709,47.43023],[-122.19556,47.43023],[-122.19384,47.43023],[-122.19246999999999,47.43023],[-122.19171,47.43023],[-122.19129000000001,47.43023],[-122.190951,47.43023],[-122.18966,47.43022],[-122.18782999999999,47.430215000000004],[-122.18646,47.430217],[-122.18446,47.43023],[-122.18358699999999,47.43023],[-122.18114,47.430246],[-122.17916999999998,47.430257],[-122.17897999999998,47.43026],[-122.17721,47.43027],[-122.17666,47.43027],[-122.17609999999999,47.430279999999996],[-122.17579999999998,47.430279999999996],[-122.17580999999998,47.42938],[-122.17568299999999,47.42938],[-122.17472,47.429386],[-122.17457999999999,47.42906],[-122.17434999999999,47.428490000000004],[-122.174251,47.42825500000001],[-122.17397499999998,47.42759],[-122.17344999999999,47.426314000000005],[-122.17336999999998,47.42613000000001],[-122.17321,47.425740000000005],[-122.17282999999998,47.42482],[-122.17269999999998,47.42452],[-122.17267999999999,47.42447],[-122.17266999999998,47.42444],[-122.17262,47.424310000000006],[-122.17257999999998,47.424213],[-122.17256999999998,47.424197],[-122.17247999999998,47.42398],[-122.17237999999998,47.42399],[-122.17236999999997,47.423970000000004],[-122.17236999999997,47.42391000000001],[-122.17237099999997,47.42378],[-122.17236999999997,47.423730000000006],[-122.17237999999998,47.423362000000004],[-122.17237999999998,47.42313000000001],[-122.17237999999998,47.42308],[-122.17218999999999,47.42309],[-122.17210999999999,47.42309],[-122.17202999999998,47.42309],[-122.17183999999999,47.42309],[-122.170553,47.42309],[-122.17056,47.42269],[-122.17049999999999,47.42271100000001],[-122.17021,47.42271100000001],[-122.16993,47.42269],[-122.169667,47.42268],[-122.16924,47.422498000000004],[-122.16874999999999,47.422287],[-122.16816,47.42203000000001],[-122.16807999999999,47.42195],[-122.16802999999999,47.421910000000004],[-122.16802,47.42189],[-122.16799999999999,47.42188599999999],[-122.16785,47.42181],[-122.16782,47.42142],[-122.16781,47.421303],[-122.1664,47.42134],[-122.16529999999999,47.42136],[-122.165225,47.421363],[-122.16516999999999,47.421016],[-122.16499999999999,47.42096],[-122.16478,47.42087],[-122.164598,47.420676],[-122.16398,47.41999],[-122.16329999999999,47.41924099999999],[-122.16291,47.41881],[-122.16286999999998,47.41882],[-122.16266,47.41889],[-122.16195,47.41912],[-122.1619,47.41914],[-122.1619,47.418986999999994],[-122.1619,47.418839999999996],[-122.1614,47.41861],[-122.16121000000001,47.418521],[-122.161174,47.418503],[-122.16102000000001,47.41843],[-122.16093,47.418386],[-122.160904,47.418375000000005],[-122.16076999999999,47.418310000000005],[-122.16058,47.41822],[-122.16058799999999,47.418130000000005],[-122.16062000000001,47.41727999999999],[-122.16041,47.41727999999999],[-122.15983999999999,47.41727999999999],[-122.15991,47.41589],[-122.15875999999999,47.4159],[-122.15872999999998,47.414913],[-122.15859999999999,47.41491],[-122.15859999999999,47.414069999999995],[-122.15948999999999,47.41405999999999],[-122.1595,47.41352],[-122.15977999999998,47.41352],[-122.1599,47.41352],[-122.1599,47.413289999999996],[-122.15988999999999,47.41288599999999],[-122.15983999999999,47.41147999999999],[-122.16035,47.41147999999999],[-122.160369,47.408947],[-122.16036,47.40865],[-122.16012,47.40865],[-122.16008599999999,47.40865],[-122.16002999999999,47.40865],[-122.16006,47.408570000000005],[-122.16009,47.40851000000001],[-122.16011,47.40847],[-122.16022000000001,47.408210000000004],[-122.16017,47.40783999999999],[-122.16012,47.40751],[-122.16006,47.407],[-122.16008599999999,47.406639],[-122.15996999999999,47.40632],[-122.15993999999999,47.40623],[-122.15979999999999,47.40586],[-122.15962,47.40514],[-122.15965,47.40511000000001],[-122.15979999999999,47.40502],[-122.15983999999999,47.40502],[-122.15997999999999,47.40502],[-122.15997999999999,47.404889999999995],[-122.15999,47.40285],[-122.16,47.40173],[-122.15999,47.401669999999996],[-122.15999,47.401622999999994],[-122.15997999999999,47.401615],[-122.15993999999999,47.40159],[-122.15983999999999,47.40155],[-122.15983999999999,47.401525],[-122.15986099999999,47.401379999999996],[-122.15906999999999,47.401379999999996],[-122.15902,47.401379999999996],[-122.15868999999999,47.40137],[-122.15863999999999,47.40137],[-122.15863999999999,47.40123],[-122.15863999999999,47.401025999999995],[-122.158654,47.399414],[-122.158613,47.39777],[-122.15728,47.397768],[-122.15607999999999,47.39605],[-122.15586799999998,47.39574],[-122.15494,47.39441],[-122.15469,47.39405],[-122.15469,47.3937],[-122.15471,47.392300000000006],[-122.15467,47.3922],[-122.15465,47.392100000000006],[-122.15464,47.392],[-122.15302,47.391965],[-122.15298999999999,47.391110000000005],[-122.15296599999999,47.39053500000001],[-122.15230999999999,47.389489999999995],[-122.15142,47.389199999999995],[-122.15117,47.388510000000004],[-122.15036999999998,47.38843],[-122.15004799999998,47.3882],[-122.14938,47.38802],[-122.14941,47.3869],[-122.14792000000001,47.38693],[-122.1478,47.386919999999996],[-122.14768000000001,47.38691],[-122.14759000000001,47.38688799999999],[-122.14750000000001,47.386869999999995],[-122.1473,47.38681999999999],[-122.14720000000001,47.386779999999995],[-122.14709,47.38673],[-122.14699,47.38667999999999],[-122.14625000000001,47.38628799999999],[-122.14616000000001,47.38625],[-122.14603,47.386199999999995],[-122.14561,47.38612],[-122.14502,47.386039999999994],[-122.14452000000001,47.385932000000004],[-122.14411000000001,47.385757],[-122.14371,47.385585],[-122.14341,47.385417],[-122.14292,47.38479699999999],[-122.14264,47.38448999999999],[-122.14245,47.38405999999999],[-122.14238999999999,47.38385999999999],[-122.14227,47.38361],[-122.14209,47.383372],[-122.14216,47.383159],[-122.14234799999998,47.3827],[-122.14283999999999,47.38191],[-122.14325000000001,47.38131],[-122.14379,47.38041],[-122.14392000000001,47.380233999999994],[-122.144,47.38011],[-122.14402000000001,47.37958],[-122.14402000000001,47.37881],[-122.14403,47.37572],[-122.14403,47.37406299999999],[-122.14402000000001,47.37238],[-122.14403,47.372260999999995],[-122.144,47.37021],[-122.14399,47.369338000000006],[-122.1439,47.369110000000006],[-122.14376999999999,47.36876],[-122.14368,47.368524],[-122.14342,47.36786699999999],[-122.14306,47.366999],[-122.14256,47.367],[-122.14157,47.367003],[-122.14150000000001,47.365217],[-122.14111000000001,47.36522],[-122.13996999999999,47.365210000000005],[-122.13830999999999,47.365210000000005],[-122.13735,47.365221],[-122.13699,47.365223],[-122.13652400000001,47.365224],[-122.13628,47.365226],[-122.13628,47.36507],[-122.13628,47.36459],[-122.13628,47.364239999999995],[-122.13627,47.36413],[-122.13627,47.36395],[-122.13626000000001,47.362210000000005],[-122.136258,47.361978],[-122.13626000000001,47.36159],[-122.13429000000001,47.36155],[-122.13284999999999,47.361599999999996],[-122.13257999999999,47.36161],[-122.13256,47.36156],[-122.13255,47.361506],[-122.13253999999999,47.36148899999999],[-122.13252999999999,47.361458],[-122.13251,47.361418],[-122.13246999999998,47.36138],[-122.13243999999999,47.36135],[-122.13238999999999,47.3613],[-122.13234999999999,47.36127],[-122.13232999999998,47.36124099999999],[-122.13230999999999,47.3612],[-122.13229,47.36116],[-122.13228,47.361146],[-122.13222,47.36112],[-122.13206,47.36099],[-122.13197,47.36092],[-122.131948,47.360910000000004],[-122.13179,47.36077],[-122.13171,47.3607],[-122.13095,47.360710000000005],[-122.13095,47.35987999999999],[-122.13095,47.35892],[-122.12921,47.35893],[-122.12825799999999,47.35894],[-122.12822,47.35886399999999],[-122.12818999999999,47.35875],[-122.12816099999999,47.358639999999994],[-122.12813999999999,47.358517],[-122.12813999999999,47.358475],[-122.12812,47.358213],[-122.12913999999999,47.35818],[-122.12921,47.35809],[-122.12925,47.35801],[-122.12929999999999,47.35788999999999],[-122.12942,47.35775999999999],[-122.12943599999998,47.357749999999996],[-122.12957999999999,47.35762999999999],[-122.12982,47.35741699999999],[-122.12988999999999,47.35726999999999],[-122.12991,47.357139999999994],[-122.12992,47.35703999999999],[-122.12992999999999,47.3567],[-122.12996,47.35664799999999],[-122.12997999999999,47.356399999999994],[-122.13002,47.35627999999999],[-122.13002999999999,47.356199999999994],[-122.13071,47.35619],[-122.13113,47.356179999999995],[-122.13113,47.355906],[-122.13113,47.35579],[-122.13113,47.355308],[-122.1317,47.355301],[-122.1317,47.354989999999994],[-122.13055,47.354989999999994],[-122.13031099999999,47.35499399999999],[-122.130053,47.355],[-122.13006,47.35490599999999],[-122.130102,47.35457999999999],[-122.13012,47.354569999999995],[-122.13014,47.35441999999999],[-122.13006999999999,47.354409999999994],[-122.12931999999999,47.35441999999999],[-122.12830999999998,47.354429999999994],[-122.12831999999999,47.353955],[-122.12972999999998,47.353176],[-122.12986,47.35311],[-122.13280199999998,47.35153999999999],[-122.13287999999999,47.351589999999995],[-122.13316999999999,47.35161599999999],[-122.1332,47.35159999999999],[-122.13359,47.351409999999994],[-122.133697,47.35145999999999],[-122.13456000000001,47.350989999999996],[-122.13457,47.35084499999999],[-122.13472999999999,47.350609999999996],[-122.13454,47.35039999999999],[-122.13485,47.350238],[-122.13882999999998,47.348079999999996],[-122.14318,47.34572],[-122.14362000000001,47.345458],[-122.14386999999999,47.345293],[-122.14423000000001,47.34504],[-122.14458,47.344759999999994],[-122.14477,47.34459999999999],[-122.14495000000001,47.34443999999999],[-122.14525,47.344159999999995],[-122.14542999999999,47.343959],[-122.14559,47.343785],[-122.1458,47.34353],[-122.14626700000001,47.34353],[-122.14639,47.343516],[-122.14691,47.3435],[-122.14956000000001,47.343489999999996],[-122.15092,47.343489999999996],[-122.15342999999999,47.343489999999996],[-122.15485,47.34347999999999],[-122.15688999999999,47.343472],[-122.15737999999999,47.343472],[-122.15837999999998,47.343469999999996],[-122.15876999999998,47.343467999999994],[-122.1601,47.34345],[-122.16022000000001,47.34345],[-122.16071,47.34345999999999],[-122.16252999999999,47.343450999999995],[-122.1655,47.343439999999994],[-122.1655,47.344123999999994],[-122.1655,47.346163],[-122.16552999999999,47.34844],[-122.16554,47.34893],[-122.16838999999999,47.34892],[-122.171234,47.34883],[-122.17613799999998,47.348819999999996],[-122.17613799999998,47.34895],[-122.17685999999999,47.34894],[-122.17796999999999,47.34894],[-122.17860499999999,47.34894],[-122.17866999999998,47.34894],[-122.17939999999999,47.348929],[-122.17957999999999,47.348926],[-122.18077999999998,47.348908],[-122.18077999999998,47.34899],[-122.18078999999999,47.34961],[-122.18083999999999,47.34961],[-122.18091,47.34974],[-122.18099,47.34981],[-122.18145,47.34981],[-122.18145,47.350269999999995],[-122.18376999999998,47.35025999999999],[-122.18413,47.35025999999999],[-122.18454,47.35025999999999],[-122.18677299999999,47.35025999999999],[-122.18677299999999,47.350715],[-122.18838999999998,47.35073],[-122.1921,47.35077],[-122.19435,47.35081999999999],[-122.1952,47.35079999999999],[-122.19711000000001,47.35081999999999],[-122.20236,47.350829999999995],[-122.20239,47.34609],[-122.20416700000001,47.3461],[-122.20414000000001,47.35085999999999],[-122.208,47.35091],[-122.208549,47.350919999999995],[-122.20915400000001,47.350935],[-122.20903,47.35104699999999],[-122.20893,47.35108899999999],[-122.20869400000001,47.35115999999999],[-122.20859,47.351229999999994],[-122.20855,47.351313],[-122.20852000000001,47.35148999999999],[-122.20855,47.351609999999994],[-122.20846,47.35183399999999],[-122.20839,47.352],[-122.20837999999999,47.352139],[-122.20842,47.35238],[-122.20845,47.352639999999994],[-122.20845,47.353019999999994],[-122.20862000000001,47.353444999999994],[-122.20896,47.35371],[-122.209452,47.35409899999999],[-122.2095,47.35414099999999],[-122.21037,47.35464999999999],[-122.21071300000001,47.35486999999999],[-122.21060000000001,47.354949999999995],[-122.21041000000001,47.35508399999999],[-122.21033,47.355137],[-122.20985,47.35518],[-122.209,47.355258],[-122.20833999999999,47.35532],[-122.20746000000001,47.35493999999999],[-122.20742000000001,47.3563],[-122.2074,47.35685999999999],[-122.20735,47.35847999999999],[-122.20733,47.36075],[-122.20733,47.361272],[-122.20732000000001,47.361456],[-122.20732000000001,47.36167],[-122.21266000000001,47.36165],[-122.21276999999999,47.36752],[-122.212786,47.36799],[-122.21539,47.36799],[-122.21539,47.36889],[-122.21759100000001,47.36889],[-122.21806000000001,47.36888],[-122.21806000000001,47.36979],[-122.22113900000001,47.36979],[-122.22202999999999,47.36979],[-122.22202999999999,47.3694],[-122.22202999999999,47.36923],[-122.22192000000001,47.36811000000001],[-122.22191000000001,47.367948],[-122.22269,47.36824],[-122.22286,47.368500000000004],[-122.22331,47.36868],[-122.22349,47.36871000000001],[-122.22409,47.36883],[-122.22574999999999,47.36941],[-122.22627,47.36934],[-122.22668,47.369150000000005],[-122.22708,47.36892],[-122.22733,47.368611],[-122.22733,47.3682],[-122.22718,47.367850999999995],[-122.226608,47.367512000000005],[-122.22559,47.3671],[-122.22497,47.36685],[-122.22486,47.366783],[-122.22474,47.366710000000005],[-122.22436,47.36638],[-122.223642,47.36576],[-122.22346,47.365410000000004],[-122.22337999999999,47.36524],[-122.22368,47.364658],[-122.22481,47.36316],[-122.22568,47.36246],[-122.22699,47.36159],[-122.22775,47.36149399999999],[-122.22796000000001,47.36147],[-122.22843999999999,47.361470999999995],[-122.22852999999999,47.361439999999995],[-122.22876,47.36144699999999],[-122.22882999999999,47.361419999999995],[-122.22892999999999,47.361412],[-122.22908,47.361425999999994],[-122.22967,47.361278],[-122.22986,47.36123],[-122.230976,47.36098],[-122.23141000000001,47.360972000000004],[-122.232,47.36105],[-122.23268,47.36114],[-122.23277999999999,47.36116],[-122.23360000000001,47.361373],[-122.23392000000001,47.36135],[-122.23471,47.36167999999999],[-122.23568,47.36191],[-122.23582999999999,47.36196699999999],[-122.23646000000001,47.362210000000005],[-122.23703,47.36266],[-122.23714000000001,47.36294],[-122.23725900000002,47.36322],[-122.23718000000001,47.36447999999999],[-122.23735,47.36516],[-122.23721000000002,47.365750000000006],[-122.23709000000001,47.365997],[-122.23691000000001,47.36622],[-122.23658,47.36662],[-122.23589,47.367322],[-122.235068,47.36797],[-122.23444,47.368468],[-122.23435,47.368637],[-122.23437,47.369085],[-122.23447,47.369330000000005],[-122.23465700000001,47.36951800000001],[-122.23528,47.3697],[-122.23585,47.36967],[-122.23585,47.369738000000005],[-122.23585,47.37005],[-122.23697,47.37001],[-122.23759000000001,47.36998],[-122.23832,47.3699],[-122.23851,47.369898],[-122.23928000000001,47.36992],[-122.2397,47.369941],[-122.24001000000001,47.36996],[-122.239948,47.36952],[-122.23993,47.36932],[-122.23993,47.3693],[-122.23991000000001,47.369150000000005],[-122.23989,47.369037],[-122.23987,47.36889],[-122.23971,47.36795],[-122.23960000000001,47.3672],[-122.23950500000001,47.36632],[-122.239406,47.365370000000006],[-122.23928000000001,47.36456],[-122.23895,47.362331000000005],[-122.23881,47.361259999999994],[-122.23877999999999,47.35981999999999],[-122.23877999999999,47.35794599999999],[-122.23879,47.35746799999999],[-122.2388,47.356562999999994],[-122.23876999999999,47.353652],[-122.23903,47.35365099999999],[-122.23910000000001,47.35365699999999],[-122.23925000000001,47.35365699999999],[-122.23991000000001,47.353652999999994],[-122.24152000000002,47.353652999999994],[-122.24317,47.353652],[-122.24344,47.35365099999999],[-122.24461000000002,47.353649999999995],[-122.24487,47.353649999999995],[-122.24600000000001,47.353649999999995],[-122.24640000000001,47.353649999999995],[-122.24644,47.353649999999995],[-122.24784000000001,47.353652],[-122.24948,47.353652999999994],[-122.25153,47.353655999999994],[-122.25278999999999,47.35365999999999],[-122.25367,47.35365899999999],[-122.25351,47.35690699999999],[-122.25348,47.35803],[-122.25274999999999,47.35803],[-122.25211,47.35804099999999],[-122.25162900000002,47.35805],[-122.24931000000001,47.358079999999994],[-122.24922000000002,47.361065999999994],[-122.24920000000002,47.362927],[-122.24918000000001,47.36347],[-122.24916000000002,47.363837],[-122.24920000000002,47.3643],[-122.24940000000001,47.365431],[-122.24942000000001,47.365550000000006],[-122.24945000000001,47.36598],[-122.24933,47.36708699999999],[-122.24932000000001,47.36715],[-122.24922000000002,47.36771],[-122.24917,47.36794],[-122.24899,47.369150000000005],[-122.24886000000001,47.37015],[-122.24885,47.370219999999996],[-122.24884,47.37027],[-122.24920700000001,47.37115],[-122.24921000000002,47.371326999999994],[-122.24920000000002,47.37197],[-122.24919900000002,47.37222],[-122.24920000000002,47.37239],[-122.24920000000002,47.37249],[-122.24920000000002,47.372761],[-122.24920000000002,47.373025],[-122.24920300000002,47.373279999999994],[-122.24921000000002,47.373659999999994],[-122.24921000000002,47.373913],[-122.24921000000002,47.37396],[-122.24921000000002,47.37454399999999],[-122.24922000000002,47.376058],[-122.24921000000002,47.37614],[-122.24922000000002,47.376318000000005],[-122.24923000000001,47.37643],[-122.24953000000001,47.376495],[-122.249739,47.37654],[-122.25126000000002,47.37677],[-122.25273999999999,47.37652],[-122.25325400000001,47.376306],[-122.25355,47.376098],[-122.2538,47.375785],[-122.25412000000001,47.37508],[-122.25409,47.375],[-122.25422600000002,47.37440699999999],[-122.25432900000001,47.37396],[-122.25436,47.373676999999994],[-122.25445,47.37292],[-122.25458,47.3725],[-122.25461000000001,47.3724],[-122.25505,47.37185999999999],[-122.255449,47.37163999999999],[-122.25609,47.371469999999995],[-122.256607,47.37152],[-122.25676,47.371596],[-122.25729000000001,47.37183999999999],[-122.25750000000001,47.3721],[-122.25775,47.372710000000005],[-122.2578,47.37283],[-122.25766000000002,47.37354],[-122.25747,47.37387999999999],[-122.25741000000001,47.37407999999999],[-122.25731,47.37443],[-122.25742000000001,47.37481],[-122.25747,47.375],[-122.25749,47.375706],[-122.25743,47.375848],[-122.25686999999999,47.37715],[-122.25684,47.37725],[-122.25686,47.37748499999999],[-122.25702500000001,47.37802],[-122.25728400000001,47.37838],[-122.25746000000001,47.378530000000005],[-122.25764000000001,47.378596],[-122.25816,47.37877],[-122.25869,47.37874],[-122.259402,47.37861],[-122.25968,47.378478],[-122.25981,47.37845],[-122.26241,47.377939999999995],[-122.26304,47.37774699999999],[-122.26378,47.377413999999995],[-122.26403,47.377219999999994],[-122.26503,47.37672],[-122.26516000000001,47.376689999999996],[-122.26516000000001,47.37656],[-122.26516000000001,47.376276],[-122.26517,47.375823999999994],[-122.26553,47.37582],[-122.26576,47.37582],[-122.26599,47.37582],[-122.26851,47.375930000000004],[-122.26856000000001,47.37582],[-122.27037999999999,47.37582],[-122.27042999999999,47.37572],[-122.27059,47.37276],[-122.27063,47.372171],[-122.27052,47.372170000000004],[-122.26651000000001,47.37216],[-122.26613,47.37216],[-122.26518,47.372153000000004],[-122.265184,47.37127999999999],[-122.26519,47.37035],[-122.265198,47.365120000000005],[-122.26625000000001,47.36511000000001],[-122.267739,47.365099],[-122.26777,47.365749],[-122.26779,47.36664],[-122.26780000000001,47.36687],[-122.26942000000001,47.366859999999996],[-122.27041,47.36685],[-122.27056999999999,47.366852],[-122.27076999999998,47.36685],[-122.27076999999998,47.366668999999995],[-122.27076,47.36508],[-122.27125000000001,47.36508],[-122.27241,47.365086],[-122.27235999999999,47.36501200000001],[-122.27230999999999,47.3649],[-122.27226999999999,47.364802],[-122.27226,47.36477],[-122.272241,47.36463],[-122.27197,47.362610000000004],[-122.27193,47.36247],[-122.27185,47.361689999999996],[-122.27186,47.36155],[-122.27189,47.36131],[-122.27206999999999,47.36083],[-122.2721,47.360636],[-122.27211,47.36044],[-122.27208999999999,47.360246999999994],[-122.27203999999999,47.360059],[-122.27196,47.35976899999999],[-122.27146,47.35864599999999],[-122.27144,47.35857],[-122.27143,47.35846699999999],[-122.27143,47.35837],[-122.27145,47.358255],[-122.27147,47.358169],[-122.2716,47.35790699999999],[-122.27166000000001,47.357789999999994],[-122.273183,47.35778299999999],[-122.27377999999999,47.35777999999999],[-122.27398,47.357789999999994],[-122.27465000000001,47.357789999999994],[-122.27483,47.357789999999994],[-122.27597999999999,47.35779999999999],[-122.27642999999999,47.35779999999999],[-122.27651,47.35779999999999],[-122.27659,47.35779999999999],[-122.27783,47.35780799999999],[-122.27830999999999,47.35780999999999],[-122.27848999999999,47.35780999999999],[-122.28120000000001,47.35782999999999],[-122.28192000000001,47.35782999999999],[-122.28293,47.35782999999999],[-122.28388,47.35780999999999],[-122.28436,47.35780799999999],[-122.28452000000001,47.35780999999999],[-122.28601,47.35781099999999],[-122.28609,47.35781099999999],[-122.287,47.35779999999999],[-122.28789,47.357789999999994],[-122.288453,47.357789999999994],[-122.28858,47.357789999999994],[-122.29005000000001,47.357789999999994],[-122.29067,47.357789999999994],[-122.29142000000002,47.357789999999994],[-122.29209,47.35777999999999],[-122.29283,47.35779999999999],[-122.29318,47.35779999999999],[-122.29418000000001,47.35781599999999],[-122.29459000000001,47.35782099999999],[-122.29624000000001,47.35783999999999],[-122.29673,47.35783999999999],[-122.29763000000001,47.35786999999999],[-122.29771000000001,47.35786999999999],[-122.29798000000001,47.35786999999999],[-122.29865000000001,47.35787999999999],[-122.29977,47.357899999999994],[-122.30096999999999,47.35791999999999],[-122.3017,47.357929999999996],[-122.30256,47.357929999999996],[-122.304713,47.357941999999994],[-122.30639,47.357944999999994],[-122.30725000000001,47.35793999999999],[-122.30874999999999,47.35793999999999],[-122.30931,47.35794099999999],[-122.30942,47.357941999999994],[-122.30952,47.35794299999999]],[[-122.28651,47.38661],[-122.28467,47.38661],[-122.283826,47.38661],[-122.28379,47.38658099999999],[-122.28313,47.38625],[-122.28307,47.386316],[-122.28267,47.38667999999999],[-122.28217,47.387139999999995],[-122.28211,47.38721999999999],[-122.28206999999999,47.38732099999999],[-122.28206,47.38737],[-122.28206,47.388],[-122.28206,47.3882],[-122.28206,47.38839],[-122.28101000000001,47.388403],[-122.28102000000001,47.38786999999999],[-122.28102000000001,47.38743999999999],[-122.28020000000001,47.38741999999999],[-122.28022000000001,47.387359999999994],[-122.28036,47.386889999999994],[-122.2805,47.38645999999999],[-122.28054,47.38636699999999],[-122.28057,47.38633],[-122.280637,47.38625999999999],[-122.28074,47.386193],[-122.28084,47.38615],[-122.28086,47.38614],[-122.281,47.38612],[-122.28114000000001,47.3861],[-122.28116000000001,47.386102],[-122.28134,47.386109],[-122.28149,47.38613],[-122.28165000000001,47.386179999999996],[-122.28190000000001,47.385765],[-122.28232999999999,47.385859999999994],[-122.28231,47.385496999999994],[-122.282309,47.385445999999995],[-122.28241,47.385445999999995],[-122.28247999999999,47.385439999999996],[-122.28245,47.38535],[-122.28242999999999,47.385279999999995],[-122.28242,47.38503],[-122.28227,47.38503],[-122.28136900000001,47.38504],[-122.28137,47.38481999999999],[-122.28137,47.38475],[-122.28136,47.384299999999996],[-122.28135,47.38371],[-122.281349,47.383039999999994],[-122.280241,47.38305],[-122.27776,47.38305],[-122.27704,47.38305999999999],[-122.27722000000001,47.383799999999994],[-122.27742,47.384949999999996],[-122.27772999999999,47.38673],[-122.27792000000001,47.387359999999994],[-122.27814,47.388110000000005],[-122.27833999999999,47.388510000000004],[-122.27842,47.388510000000004],[-122.279863,47.388535000000005],[-122.281,47.38856],[-122.28094,47.390948],[-122.28160000000001,47.391059999999996],[-122.28202,47.39112],[-122.28246,47.39115],[-122.28309,47.39115],[-122.28365000000001,47.39107],[-122.28420000000001,47.39097],[-122.284952,47.3908],[-122.28569999999999,47.390659],[-122.2865,47.390550000000005],[-122.28649,47.39046],[-122.28648,47.39008],[-122.28651,47.388537],[-122.28651,47.38792399999999],[-122.28651,47.38661]]]},"name":"Kent"},{"boundary":{"type":"Polygon","coordinates":[[[-121.89368,47.543631],[-121.88731,47.54374],[-121.88736,47.547305],[-121.88696,47.5473],[-121.88677999999999,47.5473],[-121.88569,47.54729],[-121.88411,47.54727999999999],[-121.88193,47.547259999999994],[-121.88108,47.54725],[-121.87984999999999,47.547239],[-121.87830999999998,47.54723],[-121.87796999999999,47.54722399999999],[-121.87672999999998,47.54721],[-121.87666999999999,47.547219999999996],[-121.87638999999999,47.54721],[-121.86636999999999,47.54712],[-121.86556999999999,47.547122],[-121.86546999999999,47.547122],[-121.86546999999999,47.547104],[-121.86547999999999,47.543659999999996],[-121.86538999999999,47.54365],[-121.86451000000001,47.543633],[-121.86451000000001,47.54365],[-121.86452000000001,47.54377],[-121.86449,47.54387],[-121.86452000000001,47.54413],[-121.86463,47.544366999999994],[-121.8647,47.544599999999996],[-121.86461000000001,47.54475],[-121.86437,47.54479],[-121.86415000000001,47.54471],[-121.86405,47.54479],[-121.86398,47.544760999999994],[-121.864,47.54465],[-121.86376999999999,47.54445],[-121.86353,47.544419999999995],[-121.86329300000001,47.544545],[-121.86305,47.544439999999994],[-121.86278099999998,47.54443],[-121.86268,47.54453],[-121.86273899999999,47.544672],[-121.86292999999999,47.544819999999994],[-121.86286,47.54494],[-121.863,47.54508],[-121.86319,47.54509],[-121.8632,47.54527],[-121.86323,47.545243],[-121.86363,47.545350000000006],[-121.86377999999999,47.54548],[-121.86381,47.545770000000005],[-121.86319900000001,47.54596],[-121.862964,47.545788],[-121.86263,47.545628],[-121.8619,47.545320000000004],[-121.86172,47.54518],[-121.8615,47.545072000000005],[-121.86152000000001,47.545],[-121.8614,47.544979],[-121.86139,47.545026],[-121.86124000000001,47.545],[-121.86104,47.544934],[-121.860856,47.54483],[-121.86068,47.54474],[-121.8605,47.54461],[-121.86037999999999,47.54447],[-121.86021000000001,47.54436],[-121.86001,47.54421],[-121.85956,47.544019999999996],[-121.85923,47.54395],[-121.85916999999999,47.54394],[-121.85896999999999,47.54392],[-121.85882999999998,47.5439],[-121.85876999999998,47.543934],[-121.85866999999999,47.54396],[-121.85849999999999,47.544039999999995],[-121.85828,47.54409],[-121.85812,47.54405],[-121.85802,47.544013],[-121.8579,47.543974],[-121.85786,47.54387],[-121.85776999999999,47.54374],[-121.85764,47.54369],[-121.85752000000001,47.543679999999995],[-121.85737999999999,47.543636],[-121.8573,47.54352],[-121.85732999999999,47.54345],[-121.85484,47.54339],[-121.85111,47.54337],[-121.85082,47.543372000000005],[-121.85051,47.54287],[-121.8505,47.542814],[-121.85049,47.5428],[-121.85055,47.54107],[-121.85062300000001,47.539030000000004],[-121.84885,47.539030000000004],[-121.848642,47.539030000000004],[-121.84617,47.53906],[-121.84586,47.53906],[-121.84564,47.53906],[-121.845597,47.539059],[-121.84423000000001,47.53907],[-121.84419000000001,47.540253],[-121.84415000000001,47.541242999999994],[-121.84411100000001,47.542320000000004],[-121.84408,47.543350000000004],[-121.84401000000001,47.546],[-121.84383,47.54787999999999],[-121.843578,47.54791],[-121.84344,47.54791],[-121.84339,47.54792],[-121.84293,47.547926],[-121.8429,47.54772],[-121.84289,47.547079999999994],[-121.83975199999999,47.54703],[-121.8371,47.54699],[-121.83686,47.54699],[-121.83613,47.54698],[-121.83339,47.546959],[-121.83316,47.54696],[-121.83062000000001,47.54695],[-121.82862999999999,47.54694],[-121.82871999999999,47.54745],[-121.82880999999999,47.54809],[-121.82880999999999,47.54818],[-121.82880999999999,47.54829],[-121.82878699999998,47.5484],[-121.82875999999999,47.54848],[-121.82869999999998,47.548614],[-121.82864,47.54871000000001],[-121.828604,47.548750000000005],[-121.82840999999999,47.54866],[-121.82835999999999,47.54871000000001],[-121.8282,47.548639],[-121.82804099999998,47.548570000000005],[-121.82777099999998,47.54845],[-121.8275,47.54834],[-121.82728,47.54826],[-121.82705,47.548176000000005],[-121.82682,47.548097],[-121.82501599999999,47.5475],[-121.825013,47.547419999999995],[-121.82492,47.543198000000004],[-121.82328,47.54319],[-121.81846,47.5433],[-121.81793400000001,47.543310000000005],[-121.81785,47.543310000000005],[-121.81768000000001,47.543310000000005],[-121.81309,47.54336],[-121.8123,47.543365],[-121.81038,47.54338],[-121.81038,47.54316],[-121.81033199999999,47.54209],[-121.81031,47.541988999999994],[-121.810242,47.5417],[-121.81015000000001,47.541439999999994],[-121.81002000000001,47.54119],[-121.80909,47.53942],[-121.80906999999999,47.53936],[-121.80906999999999,47.539303000000004],[-121.80906999999999,47.53924],[-121.80908,47.539190000000005],[-121.80912000000001,47.53909],[-121.80916,47.539030000000004],[-121.80921000000001,47.538970000000006],[-121.80937999999999,47.53888],[-121.80961,47.53877000000001],[-121.80982999999999,47.53867],[-121.80987999999999,47.538630000000005],[-121.80996,47.538560000000004],[-121.81,47.53849],[-121.81003,47.538430000000005],[-121.81007,47.538086],[-121.81009,47.5379],[-121.81009,47.537559],[-121.81006000000001,47.537279999999996],[-121.8099,47.536621],[-121.80973999999999,47.535990000000005],[-121.80972,47.53593000000001],[-121.80955,47.53535000000001],[-121.80948,47.535120000000006],[-121.80957,47.53511000000001],[-121.810235,47.53511000000001],[-121.810292,47.53511000000001],[-121.81043,47.53510000000001],[-121.81072,47.53511000000001],[-121.809073,47.532920000000004],[-121.80896,47.532770000000006],[-121.80870999999999,47.53242],[-121.80845,47.532043],[-121.80822,47.5317],[-121.80733,47.53041],[-121.80722000000002,47.53023],[-121.80693,47.52985],[-121.80669999999999,47.529554000000005],[-121.80663,47.529461],[-121.80658,47.52947],[-121.80615,47.529450000000004],[-121.80608,47.52935000000001],[-121.80595,47.529180000000004],[-121.80582999999999,47.52901200000001],[-121.80574999999999,47.52895000000001],[-121.80568,47.528771000000006],[-121.80526,47.528774000000006],[-121.80503999999999,47.52877600000001],[-121.80498,47.528603000000004],[-121.80478,47.528020000000005],[-121.80472999999999,47.52787],[-121.8047,47.52784],[-121.80454,47.52768],[-121.80431,47.52724],[-121.80393,47.52686],[-121.80363,47.526651],[-121.80172,47.526],[-121.8013,47.52563000000001],[-121.80066000000001,47.525264],[-121.8002,47.52482],[-121.79975,47.524648],[-121.79876999999999,47.524798000000004],[-121.79773,47.52533000000001],[-121.79696000000001,47.525481],[-121.79624000000001,47.52533000000001],[-121.79601000000001,47.525290000000005],[-121.79535,47.52541000000001],[-121.79481000000001,47.525704000000005],[-121.79460000000002,47.52577900000001],[-121.79414000000001,47.525940000000006],[-121.79317,47.526129000000005],[-121.79213,47.526250000000005],[-121.79081000000001,47.52624],[-121.78936999999999,47.526070000000004],[-121.78676999999999,47.52547200000001],[-121.78607,47.52523000000001],[-121.78413,47.52489],[-121.78374,47.524736000000004],[-121.78356400000001,47.524581],[-121.783292,47.52444],[-121.78286999999999,47.524225],[-121.78253,47.523390000000006],[-121.78216,47.52295000000001],[-121.78228,47.522830000000006],[-121.78254,47.52273000000001],[-121.78273999999999,47.52261000000001],[-121.7829,47.52246],[-121.783076,47.522240000000004],[-121.78317,47.52203000000001],[-121.78321500000001,47.52187],[-121.78332,47.5214],[-121.78341,47.521235000000004],[-121.7836,47.52088],[-121.78370199999999,47.520700000000005],[-121.78382,47.52058],[-121.78391500000001,47.520500000000006],[-121.78407,47.520323000000005],[-121.78418,47.520154000000005],[-121.78421000000002,47.51999],[-121.78411000000001,47.51978],[-121.78401000000001,47.51956],[-121.78397,47.51935],[-121.783975,47.51917],[-121.78403,47.51897],[-121.78409,47.51882],[-121.78419000000001,47.51871200000001],[-121.78437,47.518585],[-121.78462000000002,47.518508000000004],[-121.78489,47.51848],[-121.78502999999999,47.518495],[-121.7852,47.518541],[-121.78535,47.51861],[-121.78568,47.51883],[-121.78582999999999,47.51896],[-121.78589,47.51897],[-121.785945,47.51898],[-121.78605,47.51899],[-121.78613,47.519],[-121.78616000000001,47.51901],[-121.78622000000001,47.519016],[-121.78636,47.519026],[-121.78643,47.51881],[-121.78664,47.51843],[-121.78671,47.5183],[-121.78738,47.517502],[-121.78776,47.516563],[-121.7882,47.516292],[-121.78854,47.51578],[-121.78856999999999,47.51532],[-121.78814,47.51438699999999],[-121.78827,47.514019999999995],[-121.78852,47.51371],[-121.78863,47.51343],[-121.7885,47.513110000000005],[-121.78764100000001,47.512750000000004],[-121.78758,47.51261],[-121.78794,47.51228],[-121.78961000000001,47.51192699999999],[-121.789855,47.51173],[-121.78977799999998,47.511399999999995],[-121.78984,47.51128099999999],[-121.78969000000001,47.511199999999995],[-121.7895,47.510856999999994],[-121.78924,47.510756],[-121.78889,47.510508],[-121.78912000000001,47.5105],[-121.789232,47.5105],[-121.79174400000001,47.510459999999995],[-121.79262000000001,47.51045],[-121.79414000000001,47.512510000000006],[-121.79418000000001,47.51256],[-121.795,47.512510000000006],[-121.79700500000001,47.512370000000004],[-121.79861000000001,47.512273],[-121.80694,47.511739999999996],[-121.80738,47.51173],[-121.8092,47.51127999999999],[-121.81024000000001,47.511015],[-121.80848999999999,47.5099],[-121.80771,47.50996],[-121.80686,47.510310000000004],[-121.80563,47.510058],[-121.80516,47.50926],[-121.80523,47.509170000000005],[-121.80532999999998,47.50905],[-121.80537999999999,47.508970000000005],[-121.805579,47.50888],[-121.80568,47.508700000000005],[-121.80564,47.508590000000005],[-121.80573999999999,47.5084],[-121.80605,47.508449],[-121.80686999999999,47.50818],[-121.80726000000001,47.50810800000001],[-121.80762000000001,47.507988],[-121.80833999999999,47.50787],[-121.80896,47.50811100000001],[-121.80946999999999,47.50752],[-121.80957,47.507259999999995],[-121.80935,47.506895],[-121.80954,47.5069],[-121.809658,47.506910000000005],[-121.80988099999999,47.506892],[-121.80995,47.506979],[-121.81028300000001,47.507425],[-121.81131,47.50882],[-121.81258,47.510510000000004],[-121.81265,47.5106],[-121.81285,47.5106],[-121.81301,47.51081],[-121.81333,47.51123],[-121.81424400000002,47.5124],[-121.81435,47.5124],[-121.8177,47.5124],[-121.82016,47.5124],[-121.82136999999999,47.512406],[-121.82301,47.51241],[-121.82305,47.51241],[-121.82305,47.512510000000006],[-121.82306,47.512605],[-121.82306,47.512710000000006],[-121.82306,47.512747],[-121.82307999999999,47.512879999999996],[-121.82309,47.512930000000004],[-121.82324,47.51289],[-121.82352,47.512834],[-121.82369,47.51281],[-121.82381,47.5128],[-121.82395,47.512786],[-121.82411,47.51278],[-121.82444,47.51279],[-121.82476,47.512819],[-121.82492,47.51284],[-121.82510599999999,47.51287],[-121.82529999999998,47.512916000000004],[-121.82557999999999,47.51299],[-121.82623,47.51314],[-121.82637999999999,47.513174],[-121.82656,47.51319],[-121.82667,47.51319],[-121.82677999999999,47.513169],[-121.82692,47.513131],[-121.82733999999999,47.51315],[-121.82816999999999,47.513175000000004],[-121.82829,47.51358],[-121.82836999999998,47.513693999999994],[-121.82840999999999,47.51387],[-121.82842999999998,47.514489999999995],[-121.82842999999998,47.515010000000004],[-121.82844099999998,47.5158],[-121.82844999999999,47.516023999999994],[-121.82846999999998,47.516514],[-121.82844999999999,47.516999],[-121.82844999999999,47.51723],[-121.82843999999999,47.517599],[-121.82842999999998,47.5177],[-121.82845999999999,47.51814],[-121.82851,47.518910000000005],[-121.828523,47.51969],[-121.82853099999998,47.519798],[-121.82856999999998,47.52291000000001],[-121.82856999999998,47.523590000000006],[-121.82858999999999,47.52434],[-121.82858999999999,47.524389],[-121.82857199999998,47.525000000000006],[-121.82854999999999,47.525649],[-121.82853999999999,47.526610000000005],[-121.82854999999999,47.52664],[-121.82853999999999,47.526685],[-121.82852999999999,47.526724],[-121.82851,47.52675000000001],[-121.82848999999999,47.527513000000006],[-121.82847999999998,47.52815000000001],[-121.82914,47.52814800000001],[-121.82941,47.52815000000001],[-121.82941,47.527992000000005],[-121.83066000000001,47.527997],[-121.83067,47.52837000000001],[-121.83067,47.52854000000001],[-121.83067,47.52873500000001],[-121.83073999999999,47.52873500000001],[-121.83093,47.52873800000001],[-121.83159,47.528740000000006],[-121.83233399999999,47.528740000000006],[-121.83236,47.528830000000006],[-121.83234999999999,47.529767],[-121.83233999999999,47.529849],[-121.83233599999998,47.529894],[-121.83232999999998,47.53],[-121.83233599999998,47.530074],[-121.83233999999999,47.53026],[-121.83233799999998,47.530330000000006],[-121.83233799999998,47.530733000000005],[-121.83283099999998,47.5309],[-121.83286,47.530910000000006],[-121.83324,47.531033],[-121.83359,47.53117],[-121.83389,47.531296],[-121.83441,47.531479999999995],[-121.83460000000001,47.531544],[-121.83476999999999,47.53159],[-121.83536,47.5317],[-121.83545,47.53172],[-121.83562,47.53176],[-121.83564,47.53177],[-121.83622000000001,47.53229],[-121.83639,47.532288],[-121.836554,47.532286],[-121.83667,47.53228],[-121.83756000000001,47.532270000000004],[-121.83765000000001,47.532271],[-121.83773,47.532271],[-121.83789,47.532270000000004],[-121.83799,47.532270000000004],[-121.83913,47.532250000000005],[-121.84011000000001,47.53223800000001],[-121.84061000000001,47.532230000000006],[-121.84165000000002,47.53222],[-121.84327,47.53219000000001],[-121.84359,47.532180000000004],[-121.84436000000001,47.53217000000001],[-121.84436000000001,47.532140000000005],[-121.84436000000001,47.53204],[-121.84437,47.531723],[-121.84437,47.53162],[-121.84437,47.531596],[-121.84437,47.531468],[-121.844377,47.53106],[-121.84439,47.53014],[-121.84441000000001,47.528620000000004],[-121.84637,47.52861800000001],[-121.84653,47.52864],[-121.84668,47.528670000000005],[-121.8468,47.528687],[-121.84697,47.52869200000001],[-121.84756000000002,47.52868],[-121.84762000000002,47.528690000000005],[-121.84767000000001,47.52870000000001],[-121.84854,47.528740000000006],[-121.85018,47.528800000000004],[-121.85106,47.52882],[-121.85172,47.528830000000006],[-121.85282999999998,47.52885500000001],[-121.85409,47.528890000000004],[-121.85504999999999,47.52891000000001],[-121.85756,47.52897000000001],[-121.85806999999998,47.52897000000001],[-121.85832999999998,47.528980000000004],[-121.85884999999999,47.52899000000001],[-121.85976,47.528946000000005],[-121.86027,47.528960000000005],[-121.86222000000001,47.529],[-121.86249,47.529],[-121.86259,47.529],[-121.86273999999999,47.52901000000001],[-121.86292,47.52901200000001],[-121.86296999999999,47.52901000000001],[-121.86465000000001,47.529050000000005],[-121.86491000000001,47.529050000000005],[-121.86496000000001,47.529050000000005],[-121.86509,47.52906],[-121.86525,47.52906],[-121.86556,47.529066],[-121.86556999999999,47.529050000000005],[-121.86556999999999,47.528984],[-121.865567,47.52897000000001],[-121.86556999999999,47.52884],[-121.86563,47.52631500000001],[-121.86512,47.526300000000006],[-121.86333599999999,47.52628],[-121.86296,47.526270000000004],[-121.86298,47.52534000000001],[-121.86227,47.52533000000001],[-121.86031,47.52531000000001],[-121.86026000000001,47.52404],[-121.8615,47.524179000000004],[-121.86241,47.52417500000001],[-121.86285,47.52416],[-121.86348,47.52411000000001],[-121.86362000000001,47.524100000000004],[-121.86432,47.52404],[-121.86507999999999,47.52382],[-121.86524,47.52375000000001],[-121.86552,47.523631],[-121.865519,47.52358],[-121.86569,47.523590000000006],[-121.86569999999999,47.5232],[-121.86570799999998,47.522670000000005],[-121.86572999999999,47.521770000000004],[-121.86572,47.5204],[-121.86572999999999,47.519659999999995],[-121.86569,47.51945],[-121.86569,47.51942],[-121.86569,47.51935],[-121.86575599999999,47.51781999999999],[-121.86576999999998,47.51747999999999],[-121.86577999999999,47.516855],[-121.86577999999999,47.516735000000004],[-121.86577999999999,47.516619999999996],[-121.86576999999998,47.51632],[-121.86576,47.51618],[-121.86566,47.51549],[-121.86564,47.51445],[-121.86612000000001,47.51445999999999],[-121.86622000000001,47.51445999999999],[-121.86725000000001,47.514469999999996],[-121.86819,47.514467999999994],[-121.86909,47.514469999999996],[-121.87070999999999,47.514489999999995],[-121.87231999999999,47.5145],[-121.87719,47.514517],[-121.87829999999998,47.51453],[-121.87953999999999,47.51454],[-121.87982,47.514559999999996],[-121.88007999999999,47.51460399999999],[-121.88044,47.51472],[-121.88081,47.51487399999999],[-121.88095,47.51473],[-121.8812,47.51447999999999],[-121.88235999999999,47.513259999999995],[-121.88251,47.513059999999996],[-121.88269999999999,47.51277],[-121.88278999999999,47.51254],[-121.88284999999999,47.51231800000001],[-121.88291,47.512042],[-121.88322000000001,47.51208],[-121.88336999999999,47.512086999999994],[-121.88353,47.51208],[-121.88382,47.51206],[-121.88398,47.51203],[-121.88404,47.51202],[-121.88434,47.511922],[-121.88482,47.51177],[-121.88492000000001,47.51174699999999],[-121.88512999999999,47.51171],[-121.88721000000001,47.511658],[-121.88718,47.513177],[-121.88718,47.514579999999995],[-121.88719,47.518170000000005],[-121.8872,47.52176],[-121.8873,47.529090000000004],[-121.88671,47.52902],[-121.88653,47.52901000000001],[-121.88588999999999,47.528980000000004],[-121.88560199999999,47.528980000000004],[-121.884173,47.528987],[-121.88367,47.52897000000001],[-121.88291,47.528967],[-121.88273999999998,47.52895200000001],[-121.88237999999998,47.52888],[-121.882239,47.528870000000005],[-121.88202999999999,47.528870000000005],[-121.88181,47.528870000000005],[-121.88187599999999,47.529547],[-121.8819,47.532862],[-121.88193,47.536370000000005],[-121.88230999999999,47.53638],[-121.88292999999999,47.53638],[-121.88416000000001,47.53638],[-121.88596999999999,47.53638],[-121.88743,47.53638],[-121.88866,47.53638],[-121.89012000000001,47.536353000000005],[-121.89026000000001,47.53636],[-121.8907,47.53635200000001],[-121.89072,47.536479],[-121.89072999999999,47.53656],[-121.89076,47.536798000000005],[-121.89077999999999,47.53691500000001],[-121.89081,47.536970000000004],[-121.890997,47.537219],[-121.89131,47.537527],[-121.89133,47.53755],[-121.89175,47.53793],[-121.89183,47.538042000000004],[-121.89195000000001,47.53833300000001],[-121.892,47.538520000000005],[-121.89206,47.538903000000005],[-121.89206,47.53915000000001],[-121.89202,47.539636],[-121.892,47.5401],[-121.89202,47.540695],[-121.8921,47.541551],[-121.89215,47.541819],[-121.89219,47.54197],[-121.892303,47.54218],[-121.89238999999999,47.54231000000001],[-121.89271,47.542645],[-121.89276999999998,47.54274],[-121.8929,47.542936000000005],[-121.89309,47.54314],[-121.89368,47.543631]]]},"name":"Snoqualmie"},{"boundary":{"type":"Polygon","coordinates":[[[-122.60343999999999,47.66764],[-122.60338999999999,47.667930000000005],[-122.60318,47.66831900000001],[-122.60175,47.67103],[-122.59904,47.673170000000006],[-122.59708,47.67472],[-122.59336,47.67656],[-122.58702000000001,47.679111000000006],[-122.58319,47.68159],[-122.58049,47.684689999999996],[-122.58,47.68524],[-122.57769,47.689750000000004],[-122.57711,47.69088],[-122.57598999999999,47.69621000000001],[-122.57401,47.70233000000001],[-122.57305,47.70403],[-122.57156,47.706790000000005],[-122.5688,47.70984],[-122.56754000000001,47.7109],[-122.566,47.7122],[-122.5658,47.71237000000001],[-122.56569999999999,47.712451],[-122.55942,47.71774],[-122.55667,47.72008],[-122.55542,47.721120000000006],[-122.55425000000001,47.722110000000015],[-122.55207999999999,47.72392000000001],[-122.55186,47.724070000000005],[-122.55115,47.72453000000001],[-122.55067,47.72478],[-122.55002,47.725110000000015],[-122.549068,47.72553000000001],[-122.54867300000001,47.725660000000005],[-122.54811000000001,47.725847],[-122.54716000000002,47.72610600000001],[-122.54664300000002,47.726237000000005],[-122.54536,47.72646],[-122.54385,47.72659600000001],[-122.53058,47.72654800000001],[-122.52901,47.726541000000005],[-122.51584,47.726490000000005],[-122.50388,47.72646],[-122.49713000000001,47.726440000000004],[-122.47735,47.72641600000001],[-122.44569000000001,47.726380000000006],[-122.43443,47.726366000000006],[-122.43476000000001,47.724920000000004],[-122.43516000000001,47.721540000000005],[-122.43588,47.71747],[-122.43658,47.71453],[-122.43859,47.71196],[-122.44119000000002,47.71007],[-122.44199000000002,47.70946],[-122.44416000000002,47.70784],[-122.44489000000002,47.707228],[-122.44534,47.70691000000001],[-122.44624000000002,47.70568],[-122.44797700000001,47.70271100000001],[-122.44987,47.69950000000001],[-122.450936,47.697500000000005],[-122.45286999999999,47.69389],[-122.4559,47.688246],[-122.45628,47.687642999999994],[-122.45716000000002,47.686063999999995],[-122.45818,47.684079999999994],[-122.45833999999999,47.68377],[-122.45864,47.683110000000006],[-122.4589,47.682320000000004],[-122.45904,47.68178399999999],[-122.45916000000001,47.68064],[-122.4593,47.679770000000005],[-122.45938,47.67871000000001],[-122.4595,47.67762],[-122.45968,47.676199000000004],[-122.4597,47.67427],[-122.45962000000002,47.672697],[-122.45962000000002,47.67112],[-122.45955900000001,47.66953900000001],[-122.459437,47.66848],[-122.45904,47.666470000000004],[-122.45858,47.664381],[-122.45825,47.663343000000005],[-122.4578,47.661970000000004],[-122.45709000000001,47.66001000000001],[-122.45645,47.65841],[-122.455927,47.657148],[-122.45546999999999,47.655836],[-122.45501,47.65461],[-122.45463000000001,47.65349],[-122.45445000000001,47.65265],[-122.45397,47.65082399999999],[-122.45359,47.64924],[-122.45325700000001,47.647310000000004],[-122.45310300000001,47.646268],[-122.453,47.64555000000001],[-122.45281,47.64402],[-122.45261,47.64208],[-122.45255,47.639790000000005],[-122.45255,47.63876500000001],[-122.45255,47.63811000000001],[-122.45273999999999,47.636351000000005],[-122.45293,47.634916000000004],[-122.45319500000001,47.63367],[-122.45345,47.632628000000004],[-122.45391000000001,47.63139],[-122.454171,47.630247],[-122.45435,47.629627],[-122.45449,47.62911000000001],[-122.45469000000001,47.627970000000005],[-122.45475,47.62709],[-122.45475,47.626204],[-122.45289,47.622656000000006],[-122.44953000000001,47.61765],[-122.44774000000001,47.614765],[-122.44680000000001,47.59813500000001],[-122.44770000000001,47.595560000000006],[-122.45016000000001,47.588522000000005],[-122.45373,47.577293],[-122.453818,47.57703],[-122.45395,47.576609],[-122.45667,47.575216000000005],[-122.46066000000002,47.57325],[-122.46474,47.57144099999999],[-122.47162000000002,47.56857000000001],[-122.47734,47.566500000000005],[-122.48476000000001,47.56398],[-122.48775,47.563052000000006],[-122.48913,47.562630000000006],[-122.49219000000001,47.561930000000004],[-122.49614700000001,47.561323],[-122.50166000000002,47.56102],[-122.50425000000001,47.560990000000004],[-122.50816999999999,47.56124],[-122.5126,47.56189],[-122.51473,47.562476000000004],[-122.51701000000001,47.563100000000006],[-122.52163,47.564703],[-122.5279,47.56739],[-122.52794,47.567411],[-122.52874999999999,47.56783],[-122.53036999999999,47.568678000000006],[-122.53122800000001,47.56913000000001],[-122.53162000000002,47.5694],[-122.53406000000001,47.57107],[-122.53462000000002,47.57157],[-122.5356,47.57247],[-122.53728000000001,47.574525],[-122.53829999999999,47.57631000000001],[-122.53921000000001,47.578851],[-122.53975,47.58043],[-122.54007,47.58136],[-122.5408,47.583679999999994],[-122.54165000000002,47.585695],[-122.54313,47.58845],[-122.54463000000001,47.59114],[-122.544977,47.59161],[-122.54555,47.59239000000001],[-122.54738,47.59363200000001],[-122.548568,47.59429],[-122.55086999999999,47.594590000000004],[-122.55178,47.594530000000006],[-122.553889,47.59438],[-122.55793,47.59277000000001],[-122.56126000000002,47.59118],[-122.56368,47.58992],[-122.56427000000001,47.58965],[-122.56598,47.58885],[-122.56776,47.58818],[-122.5684,47.58795],[-122.56976,47.587745],[-122.57045,47.58786599999999],[-122.5716,47.58807],[-122.57345,47.588645],[-122.57576999999998,47.58957],[-122.57589999999999,47.58963],[-122.57718,47.59025200000001],[-122.5782,47.59088],[-122.57957999999999,47.59174],[-122.58106000000001,47.59290000000001],[-122.58243499999999,47.59422],[-122.583516,47.595529000000006],[-122.5847,47.59746],[-122.58514,47.59851000000001],[-122.58542,47.599224],[-122.58586,47.601419],[-122.58679599999999,47.60781],[-122.58629,47.615950000000005],[-122.58593499999999,47.62148],[-122.58592999999999,47.62151000000001],[-122.58572999999998,47.6246],[-122.58565,47.625490000000006],[-122.58529,47.62771600000001],[-122.58528,47.62863000000001],[-122.58526,47.631295],[-122.58525,47.63236800000001],[-122.58512,47.635780000000004],[-122.585094,47.636410000000005],[-122.58506,47.637010000000004],[-122.58541,47.638250000000006],[-122.58586,47.63938],[-122.58663,47.64083],[-122.58874999999999,47.64237500000001],[-122.59169800000001,47.64448399999999],[-122.59206999999999,47.644748],[-122.593,47.64562],[-122.59405000000001,47.646612000000005],[-122.59432000000001,47.647009],[-122.59556,47.6488],[-122.59788,47.652190000000004],[-122.59836,47.65289],[-122.5988,47.65352],[-122.59975,47.65526],[-122.601749,47.658919000000004],[-122.60279999999999,47.66084],[-122.60372799999999,47.664550000000006],[-122.60343999999999,47.66764]]]},"name":"Bainbridge Island"},{"boundary":{"type":"Polygon","coordinates":[[[-122.44151000000002,47.79321600000001],[-122.44099000000001,47.79855000000001],[-122.44054000000001,47.80312],[-122.432311,47.803154],[-122.40761000000002,47.803259999999995],[-122.39938,47.8033],[-122.39849,47.803276999999994],[-122.39582,47.80321],[-122.39494,47.803191],[-122.39484,47.803197],[-122.39457,47.803215],[-122.39448,47.803219999999996],[-122.39396,47.80322399999999],[-122.39241,47.80323],[-122.39189,47.803232],[-122.39169900000002,47.803231],[-122.39161000000001,47.803228],[-122.39150000000001,47.80323],[-122.39138,47.80323],[-122.39120000000001,47.803225],[-122.39110000000001,47.80322699999999],[-122.39077999999999,47.803219999999996],[-122.3906,47.80323],[-122.39054,47.80322099999999],[-122.3905,47.803194],[-122.38982999999999,47.80321],[-122.38781,47.80325],[-122.38726000000001,47.80327],[-122.38715,47.80329],[-122.38692,47.80329],[-122.38677999999999,47.80329],[-122.38626000000001,47.80329],[-122.38604,47.803290999999994],[-122.38582,47.80329],[-122.38570999999999,47.80329],[-122.38555,47.80329],[-122.38539999999999,47.80329],[-122.38519,47.803279999999994],[-122.38472999999999,47.80328099999999],[-122.38442300000001,47.80327],[-122.3841,47.803276],[-122.38395,47.803276999999994],[-122.38382999999999,47.803273],[-122.38361,47.803276],[-122.38356999999999,47.803276999999994],[-122.38351,47.803276],[-122.38332999999999,47.803273],[-122.38327,47.803272],[-122.38292999999999,47.80327],[-122.38292999999999,47.80252],[-122.38292999999999,47.8021],[-122.38292,47.79984],[-122.38292,47.79966],[-122.38155,47.79965200000001],[-122.38149,47.79963000000001],[-122.38143,47.799620000000004],[-122.38132999999999,47.799620000000004],[-122.38129,47.799624],[-122.38125000000001,47.79963000000001],[-122.38054,47.79963000000001],[-122.3805,47.79963000000001],[-122.38023,47.79966],[-122.37995,47.799670000000006],[-122.37978999999999,47.799670000000006],[-122.37947999999999,47.799679000000005],[-122.37933999999998,47.799673000000006],[-122.3791,47.79968],[-122.37852,47.79967200000001],[-122.37827099999998,47.799670000000006],[-122.37805999999999,47.799670000000006],[-122.37774999999999,47.799670000000006],[-122.37753,47.79966],[-122.37753,47.79953000000001],[-122.37753,47.79951300000001],[-122.37753,47.799465000000005],[-122.37753,47.79945000000001],[-122.37752,47.79913000000001],[-122.37752,47.79854600000001],[-122.37753,47.79818300000001],[-122.37755,47.79787],[-122.37753,47.79569000000001],[-122.37752,47.79500200000001],[-122.37751,47.794050000000006],[-122.37745,47.79410000000001],[-122.37737999999999,47.794140000000006],[-122.37729999999999,47.794160000000005],[-122.37711300000001,47.794168000000006],[-122.37684999999999,47.794160000000005],[-122.37674999999999,47.794160000000005],[-122.37612,47.794160000000005],[-122.37590999999999,47.794160000000005],[-122.37537999999998,47.794160000000005],[-122.37485,47.794160000000005],[-122.37485,47.793788000000006],[-122.37482999999999,47.792680000000004],[-122.37482999999999,47.79231100000001],[-122.37504999999999,47.792310000000015],[-122.37565,47.792310000000015],[-122.37571999999999,47.79231100000001],[-122.37580999999999,47.792330000000014],[-122.37587999999998,47.792330000000014],[-122.37593999999999,47.79230800000001],[-122.37626,47.792330000000014],[-122.37722000000001,47.79238000000001],[-122.37754,47.79240000000001],[-122.37751,47.79223400000001],[-122.37751,47.792138000000016],[-122.3775,47.79207000000001],[-122.37749,47.791940000000004],[-122.37749,47.79175000000001],[-122.3775,47.791470000000004],[-122.37752,47.791340000000005],[-122.37752,47.79133000000001],[-122.37756,47.79123800000001],[-122.3776,47.791187],[-122.37766,47.79112000000001],[-122.37763,47.79070000000001],[-122.37755,47.7896],[-122.37754,47.789452000000004],[-122.37751,47.78904],[-122.3775,47.788340000000005],[-122.377466,47.78626],[-122.37745,47.785561],[-122.37745,47.78502],[-122.37743999999999,47.783398000000005],[-122.37743999999999,47.78286],[-122.37743999999999,47.781347],[-122.37747999999999,47.77785],[-122.37809999999999,47.77785],[-122.37893999999999,47.77786],[-122.3791,47.77786],[-122.38001,47.777843],[-122.38002999999999,47.777843],[-122.38019,47.77784],[-122.38273999999998,47.77787],[-122.38287999999999,47.77787],[-122.38696,47.77792],[-122.3882,47.777910000000006],[-122.38826,47.777910000000006],[-122.38826999999999,47.777910000000006],[-122.38855,47.7779],[-122.38944,47.777896],[-122.38963,47.77789],[-122.38981,47.77789],[-122.39013,47.777910000000006],[-122.39198,47.777930000000005],[-122.39319400000001,47.777929],[-122.39332999999999,47.777930000000005],[-122.39334,47.77801900000001],[-122.39347,47.77870000000001],[-122.393198,47.77870000000001],[-122.39321000000001,47.77877000000001],[-122.39287999999999,47.778802000000006],[-122.39292999999999,47.779452000000006],[-122.390494,47.77947],[-122.39047,47.78072],[-122.38826999999999,47.78069],[-122.38826999999999,47.782120000000006],[-122.38826999999999,47.7848],[-122.38826999999999,47.785216000000005],[-122.39079,47.785230000000006],[-122.39246,47.785243],[-122.39968,47.7854],[-122.40216000000001,47.78546],[-122.41159600000002,47.785500000000006],[-122.43222000000002,47.78558],[-122.44224000000001,47.78562],[-122.44151000000002,47.79321600000001]]]},"name":"Woodway"},{"boundary":{"type":"Polygon","coordinates":[[[-122.22796000000001,47.866730000000004],[-122.22795,47.867428999999994],[-122.22695,47.867439999999995],[-122.22698,47.867819999999995],[-122.22654,47.867819999999995],[-122.22653,47.868218000000006],[-122.22653,47.86834],[-122.22642,47.86833800000001],[-122.22642,47.868651],[-122.22657,47.868657],[-122.22656,47.869279999999996],[-122.22653,47.871039999999994],[-122.22375,47.871019999999994],[-122.22314,47.87101],[-122.22313,47.87193],[-122.22312000000001,47.87363],[-122.22326000000001,47.87363],[-122.22324,47.87431],[-122.22323,47.87463999999999],[-122.223107,47.87464099999999],[-122.22306999999999,47.878271],[-122.22287999999999,47.878268],[-122.21936000000001,47.878229],[-122.21922000000002,47.87823],[-122.21925000000002,47.878370000000004],[-122.21929000000002,47.878479999999996],[-122.21934,47.8786],[-122.21939,47.878660999999994],[-122.21946000000001,47.878730000000004],[-122.219874,47.87907],[-122.22019,47.879348],[-122.22027,47.879439999999995],[-122.22032,47.87949],[-122.22032999999999,47.879566],[-122.22032999999999,47.87963],[-122.220325,47.879690999999994],[-122.22031,47.87976],[-122.22027,47.879819999999995],[-122.22024,47.87987999999999],[-122.22017,47.87995],[-122.22009,47.88001],[-122.220017,47.88005999999999],[-122.21968000000001,47.88032],[-122.21966000000002,47.88032],[-122.21891000000001,47.879799999999996],[-122.21851000000001,47.87951],[-122.21803,47.87917],[-122.21781000000001,47.879014],[-122.21762000000003,47.878885],[-122.21752000000002,47.87883],[-122.21737,47.878749],[-122.21729000000002,47.878706],[-122.21716000000002,47.87863],[-122.21710000000002,47.878612000000004],[-122.21703000000001,47.87858],[-122.21685000000001,47.878510000000006],[-122.21665000000002,47.878448],[-122.21636000000001,47.87836],[-122.21604,47.8783],[-122.21583,47.87826],[-122.21558,47.87822],[-122.21511000000001,47.878189],[-122.21509,47.878190000000004],[-122.21321000000002,47.878181],[-122.21294,47.878181],[-122.21221000000001,47.878181],[-122.21174,47.878181],[-122.21101000000002,47.878181],[-122.21033,47.878181],[-122.20986,47.878181],[-122.20956000000001,47.87818],[-122.20746000000001,47.87821],[-122.20686,47.878178000000005],[-122.20681,47.878178000000005],[-122.20667,47.878170000000004],[-122.206,47.87816],[-122.20452000000002,47.878142000000004],[-122.20418000000001,47.878141],[-122.20398,47.87814],[-122.20351000000001,47.87813200000001],[-122.20339,47.878130000000006],[-122.20305,47.878126],[-122.20293,47.87812],[-122.20293,47.87828],[-122.19984,47.878277],[-122.19883999999999,47.878231],[-122.19881,47.87823],[-122.19779,47.8782],[-122.19681,47.87818],[-122.19572999999998,47.878190000000004],[-122.19537999999999,47.878183],[-122.19344,47.878150000000005],[-122.19336999999999,47.878150000000005],[-122.19269999999999,47.878150000000005],[-122.19198,47.87814],[-122.19172999999999,47.87814],[-122.18532999999998,47.878150000000005],[-122.18125,47.87797],[-122.18006,47.877979999999994],[-122.17874999999998,47.877976],[-122.17656999999998,47.87796099999999],[-122.17524999999999,47.877939999999995],[-122.17463,47.87795],[-122.17456999999999,47.87795],[-122.17326,47.87795],[-122.17320099999999,47.87795],[-122.17062999999999,47.87795],[-122.16951,47.877869999999994],[-122.16928,47.87791],[-122.16605,47.877889999999994],[-122.16596999999999,47.877758],[-122.16608,47.87743999999999],[-122.16614,47.87723],[-122.166174,47.877159999999996],[-122.16621,47.877098999999994],[-122.16631,47.876973],[-122.16642999999999,47.87685999999999],[-122.16651,47.87679],[-122.16659,47.876718000000004],[-122.16675,47.876615],[-122.16699,47.876532000000005],[-122.16712000000001,47.8765],[-122.16719,47.876479999999994],[-122.16758,47.87639],[-122.16768,47.87637],[-122.16791,47.876310000000004],[-122.16799999999999,47.876276],[-122.16808999999999,47.876242],[-122.1682,47.876191],[-122.16822,47.87617],[-122.16829,47.87613],[-122.16842999999999,47.876062],[-122.16856,47.87599],[-122.168641,47.87594],[-122.16896999999999,47.8757],[-122.16919,47.875530000000005],[-122.16946999999999,47.875319000000005],[-122.17005999999999,47.874869999999994],[-122.17030999999999,47.8747],[-122.17032999999998,47.87468299999999],[-122.17067999999999,47.87445999999999],[-122.17081999999999,47.874359999999996],[-122.17105099999999,47.874195],[-122.17174999999999,47.873753],[-122.17199699999999,47.87357],[-122.17227999999999,47.87341],[-122.17258499999998,47.87321],[-122.17286999999997,47.87302],[-122.17289999999998,47.873],[-122.17300999999999,47.872910000000005],[-122.17313999999999,47.8728],[-122.17349999999999,47.87245],[-122.17375999999999,47.872203],[-122.17396,47.87202],[-122.17414,47.871849999999995],[-122.17442,47.871635],[-122.17466,47.87145999999999],[-122.17469999999999,47.87143],[-122.17505999999999,47.871117],[-122.17522,47.87093],[-122.17526999999998,47.87085999999999],[-122.17533999999998,47.87075],[-122.17546099999998,47.87043],[-122.17550999999999,47.870262],[-122.17558999999999,47.86985],[-122.17566,47.869510000000005],[-122.17569999999998,47.86924],[-122.17579999999998,47.868900000000004],[-122.17586299999999,47.868730000000006],[-122.17592999999998,47.868549],[-122.17608999999999,47.868210000000005],[-122.17621,47.868010000000005],[-122.17629999999998,47.867889999999996],[-122.17647999999998,47.86767999999999],[-122.17678999999998,47.86734],[-122.17692999999998,47.86716],[-122.17703999999999,47.867011],[-122.17711,47.8669],[-122.17722,47.86676],[-122.17734999999999,47.866572000000005],[-122.17754,47.86661],[-122.17862999999998,47.86472],[-122.17907999999998,47.86416],[-122.17948999999999,47.86387],[-122.18083999999999,47.862750000000005],[-122.18092,47.86264],[-122.18122000000001,47.862170000000006],[-122.18154,47.86197],[-122.18408,47.86135],[-122.185159,47.86097],[-122.18556,47.860774],[-122.18579499999998,47.8607],[-122.18606999999999,47.860626999999994],[-122.186045,47.86044],[-122.18618,47.85998399999999],[-122.18625,47.859812],[-122.18632999999998,47.859629999999996],[-122.18647999999999,47.859303],[-122.18662,47.858959999999996],[-122.18676999999998,47.85863],[-122.18699,47.858121],[-122.18702,47.85805],[-122.18705,47.85793999999999],[-122.18708,47.85778499999999],[-122.18708,47.85762999999999],[-122.18705,47.857319999999994],[-122.18703,47.857189999999996],[-122.18701,47.856919999999995],[-122.18699,47.85633],[-122.18696999999999,47.85605999999999],[-122.18696999999999,47.85595],[-122.18696999999999,47.855799999999995],[-122.18699,47.855639999999994],[-122.18701,47.8555],[-122.18706,47.855239999999995],[-122.187101,47.85503],[-122.18711,47.85499899999999],[-122.18711,47.854969999999994],[-122.1872,47.854589999999995],[-122.18725,47.85433],[-122.18732,47.853939999999994],[-122.18742999999999,47.853429999999996],[-122.18752,47.853049999999996],[-122.187585,47.85273],[-122.1876,47.85259],[-122.18761,47.85255],[-122.18762000000001,47.85251],[-122.18763,47.85245],[-122.18773999999999,47.85193099999999],[-122.18778999999999,47.851769999999995],[-122.18778999999999,47.85161999999999],[-122.187799,47.85145999999999],[-122.1878,47.85115],[-122.18777999999999,47.85105599999999],[-122.18775,47.85091],[-122.18765,47.85057],[-122.18757,47.85035],[-122.1875,47.850199999999994],[-122.18742999999999,47.850049999999996],[-122.187216,47.84967999999999],[-122.187,47.849325],[-122.18691,47.84917],[-122.18682999999999,47.84901],[-122.19041,47.84916],[-122.19321000000001,47.84928599999999],[-122.1939,47.84931],[-122.194173,47.84931],[-122.19606,47.849399999999996],[-122.19655,47.84942399999999],[-122.19739,47.84943],[-122.19794,47.849439999999994],[-122.199,47.84945],[-122.19922000000001,47.84945],[-122.19936,47.84945],[-122.200112,47.84945],[-122.20045,47.849502],[-122.20067,47.849593],[-122.20089,47.849554],[-122.20194000000001,47.84937],[-122.20292,47.849399999999996],[-122.20332,47.84945999999999],[-122.20421200000001,47.84946099999999],[-122.20417,47.84794899999999],[-122.20416000000002,47.84779999999999],[-122.20411000000001,47.84572],[-122.20729000000001,47.845710000000004],[-122.20723000000001,47.843759999999996],[-122.20783,47.843759999999996],[-122.20785000000001,47.84415],[-122.20892,47.844139999999996],[-122.209,47.84516],[-122.20943,47.84535],[-122.20951000000001,47.845388],[-122.20962000000002,47.84543],[-122.20941,47.84554],[-122.20935,47.845566],[-122.20926000000001,47.84561],[-122.20923,47.84562],[-122.20915000000001,47.845659999999995],[-122.20913,47.84567],[-122.20933,47.84572],[-122.21088,47.846599999999995],[-122.21190000000001,47.84646699999999],[-122.21256000000001,47.84646399999999],[-122.21254,47.845599],[-122.21545,47.84568099999999],[-122.21557,47.845679999999994],[-122.21592000000001,47.84568699999999],[-122.21604,47.84568899999999],[-122.21603,47.84562],[-122.21600400000001,47.845425],[-122.21600000000001,47.84536],[-122.21597,47.845150000000004],[-122.21589,47.844539999999995],[-122.21587,47.844339999999995],[-122.21576,47.8435],[-122.21573,47.84319],[-122.2155,47.84112],[-122.21535,47.83994],[-122.21533,47.839859999999994],[-122.2153,47.839756],[-122.21528900000001,47.839712000000006],[-122.21521000000001,47.839479999999995],[-122.21514,47.839324],[-122.21506000000001,47.83917],[-122.21492300000003,47.838950000000004],[-122.21477,47.83874],[-122.21454000000001,47.838451],[-122.21442000000002,47.838330000000006],[-122.21439000000001,47.838300000000004],[-122.220818,47.83831800000001],[-122.22296999999999,47.838327],[-122.22296999999999,47.8387],[-122.22296999999999,47.83896],[-122.22296999999999,47.83902],[-122.22299,47.83999],[-122.22299,47.840259999999994],[-122.22299,47.84032],[-122.22306,47.84155],[-122.22306999999999,47.84163999999999],[-122.223158,47.84315],[-122.22317,47.84653],[-122.22317,47.847505],[-122.22264,47.847519999999996],[-122.22236999999998,47.847519999999996],[-122.22203999999999,47.847516],[-122.22206,47.847553],[-122.22209,47.847590999999994],[-122.22218,47.847739999999995],[-122.22221,47.84777999999999],[-122.22224,47.84785699999999],[-122.22229999999999,47.847987999999994],[-122.22229999999999,47.848479999999995],[-122.22227,47.84877],[-122.22221,47.848910000000004],[-122.22216,47.84901],[-122.22209,47.849148],[-122.22207999999999,47.84931],[-122.22206,47.84935],[-122.22205,47.84939],[-122.222394,47.849376],[-122.22243999999999,47.84937],[-122.22304,47.84936],[-122.2232,47.84936],[-122.22342400000001,47.84936],[-122.22376799999999,47.84935],[-122.22394,47.84935],[-122.22403,47.84935],[-122.22402000000001,47.85266699999999],[-122.22402000000001,47.852979999999995],[-122.22551,47.852959999999996],[-122.227835,47.852919],[-122.22784,47.852965],[-122.22782000000001,47.85318399999999],[-122.22782000000001,47.85325999999999],[-122.22785400000001,47.85388399999999],[-122.22786,47.85415999999999],[-122.22786,47.85423999999999],[-122.22559,47.854299999999995],[-122.22551,47.854299999999995],[-122.22502,47.854318],[-122.22502999999999,47.85522099999999],[-122.22552,47.855215],[-122.22561,47.85521],[-122.22787,47.85519],[-122.22787,47.85537],[-122.22787,47.855577],[-122.22795900000001,47.85603],[-122.22795,47.85621],[-122.22795,47.85637],[-122.22794,47.856519],[-122.22794,47.85656399999999],[-122.22794,47.85668799999999],[-122.22794,47.85719999999999],[-122.22794,47.857369999999996],[-122.22794,47.85775999999999],[-122.22795,47.858953],[-122.22795,47.85935],[-122.22795,47.86023],[-122.22796000000001,47.862885],[-122.22796000000001,47.86377],[-122.22796000000001,47.865063],[-122.22796000000001,47.866730000000004]]]},"name":"Mill Creek"},{"boundary":{"type":"Polygon","coordinates":[[[-122.42952000000001,47.82761],[-122.42295,47.83852],[-122.41205000000001,47.85061999999999],[-122.39845,47.86132],[-122.39805,47.861619999999995],[-122.38991,47.86132],[-122.36551,47.86042],[-122.35736999999999,47.860121],[-122.35336999999998,47.85997],[-122.34137,47.859532],[-122.33936999999999,47.85945999999999],[-122.33738,47.859379999999994],[-122.33701,47.85937],[-122.33591,47.859331],[-122.33554,47.859318],[-122.33534999999999,47.859318],[-122.33476999999999,47.859318],[-122.33457,47.85932],[-122.33384,47.859318],[-122.33382999999999,47.85932],[-122.33159,47.85923999999999],[-122.33084,47.85921999999999],[-122.32970999999999,47.85919],[-122.32704,47.85912],[-122.32638999999999,47.85876],[-122.32614,47.858619999999995],[-122.32553999999999,47.858019999999996],[-122.32543999999999,47.857699999999994],[-122.32524,47.857118],[-122.32527599999999,47.85673],[-122.32529999999998,47.856399999999994],[-122.32529,47.8563],[-122.32526999999999,47.856019999999994],[-122.32526,47.85592],[-122.32525,47.855869999999996],[-122.32524,47.855799999999995],[-122.32522999999999,47.855705],[-122.32522,47.85552],[-122.32521,47.85541],[-122.32518999999999,47.855308],[-122.325193,47.85527999999999],[-122.32518999999999,47.855228],[-122.32517999999999,47.855079999999994],[-122.32517999999999,47.85503],[-122.32516,47.85475999999999],[-122.32513999999999,47.85448499999999],[-122.32512,47.854209999999995],[-122.32509999999999,47.85399999999999],[-122.32507999999999,47.85374399999999],[-122.32506,47.853429999999996],[-122.32504999999999,47.853289999999994],[-122.32502,47.853049999999996],[-122.32499,47.852605999999994],[-122.32498,47.852528],[-122.32496,47.8523],[-122.32492,47.85192099999999],[-122.32492,47.85184799999999],[-122.3249,47.851589999999995],[-122.32489,47.85150399999999],[-122.32488,47.85133999999999],[-122.32486,47.85123299999999],[-122.32485,47.85114299999999],[-122.32485,47.85108999999999],[-122.32484,47.850939999999994],[-122.324833,47.85089399999999],[-122.32482,47.850789999999996],[-122.32481,47.85059999999999],[-122.3248,47.85048999999999],[-122.3248,47.85039],[-122.32479,47.850339999999996],[-122.32479,47.85021],[-122.32477999999999,47.850159999999995],[-122.32477999999999,47.850089999999994],[-122.32479,47.849869999999996],[-122.3248,47.84979],[-122.3248,47.849619999999994],[-122.32482,47.8491],[-122.32482999999999,47.84892],[-122.32497,47.848943],[-122.32513999999999,47.848966999999995],[-122.32540999999999,47.849025],[-122.32554999999999,47.849059999999994],[-122.32580999999999,47.84911],[-122.32587999999998,47.84912],[-122.32623,47.849193],[-122.32632999999998,47.849219999999995],[-122.32663,47.84927999999999],[-122.32684799999998,47.849329],[-122.32706999999999,47.849379],[-122.32717099999999,47.849399999999996],[-122.32726000000001,47.849439999999994],[-122.32735,47.849489999999996],[-122.32742,47.84954],[-122.32746999999999,47.849599999999995],[-122.32749,47.849633],[-122.32751,47.849669999999996],[-122.32754,47.84972],[-122.32755,47.84975],[-122.32756,47.84977],[-122.32758,47.84983],[-122.32759,47.84985999999999],[-122.32763,47.84923],[-122.32766000000001,47.84834],[-122.33046999999999,47.848372000000005],[-122.33046999999999,47.848279999999995],[-122.33046999999999,47.848109],[-122.33046999999999,47.848079999999996],[-122.33046999999999,47.848],[-122.33046999999999,47.84797999999999],[-122.33046999999999,47.84797],[-122.33046999999999,47.84793],[-122.33046999999999,47.847919999999995],[-122.33046999999999,47.847804999999994],[-122.33046999999999,47.847739999999995],[-122.33049,47.847669999999994],[-122.33046,47.84753],[-122.33048,47.847289999999994],[-122.33046999999999,47.84715],[-122.33048,47.84692],[-122.33041,47.84654],[-122.33041,47.84647999999999],[-122.33044,47.84516],[-122.33044,47.84487999999999],[-122.328694,47.84483999999999],[-122.32798,47.844832],[-122.32646,47.844789999999996],[-122.32571999999999,47.84477],[-122.32524,47.844753999999995],[-122.32493,47.844742999999994],[-122.32485,47.844728999999994],[-122.32376999999998,47.84473],[-122.32117,47.84475],[-122.32006,47.84475],[-122.31967,47.844739999999994],[-122.31936,47.844759999999994],[-122.31948,47.84459],[-122.31961500000001,47.844379999999994],[-122.31962800000001,47.84435],[-122.31967,47.844289999999994],[-122.31968,47.844269999999995],[-122.31983,47.8441],[-122.31991000000001,47.844035],[-122.32005,47.84393],[-122.32006999999999,47.843914],[-122.3202,47.84383],[-122.32028,47.84379],[-122.32043999999999,47.84371],[-122.32066999999999,47.843619999999994],[-122.32100799999999,47.843489999999996],[-122.32121000000001,47.843399999999995],[-122.32135,47.84333],[-122.32146999999999,47.843256999999994],[-122.321495,47.843239999999994],[-122.32163,47.84314],[-122.3219,47.8429],[-122.32206999999998,47.842742],[-122.32217899999999,47.842639999999996],[-122.32279999999999,47.84205],[-122.32292,47.84193],[-122.32301,47.84184199999999],[-122.32306999999999,47.841792],[-122.32329999999999,47.84159],[-122.32343699999998,47.841469999999994],[-122.32354,47.841379999999994],[-122.32359,47.841319999999996],[-122.32369,47.84119999999999],[-122.32377999999999,47.84105999999999],[-122.32386999999999,47.84091],[-122.32388999999999,47.84088899999999],[-122.323924,47.840819999999994],[-122.32394,47.84080099999999],[-122.32403,47.84062899999999],[-122.32425,47.840272],[-122.32454,47.83979],[-122.32464,47.839633],[-122.32482999999999,47.839237],[-122.32487799999998,47.83896],[-122.3249,47.83879],[-122.324968,47.837979999999995],[-122.32498,47.83767999999999],[-122.32499999999999,47.83752],[-122.32500499999999,47.837455],[-122.32502999999998,47.837162],[-122.32506999999998,47.83665],[-122.32507999999999,47.836368],[-122.32508999999999,47.836259999999996],[-122.32512999999999,47.83597],[-122.32513999999999,47.835937],[-122.32516999999999,47.83583],[-122.32529,47.83561],[-122.32539999999999,47.835474],[-122.325514,47.835342000000004],[-122.32556,47.835300000000004],[-122.32566,47.83522],[-122.32588999999999,47.83505],[-122.32616,47.8349],[-122.32618,47.83487999999999],[-122.32652,47.8347],[-122.32697999999999,47.83445],[-122.32725,47.83431],[-122.32728,47.83429699999999],[-122.32762000000001,47.83411],[-122.32785,47.83396],[-122.32796,47.83389],[-122.32802,47.83385],[-122.32829,47.83367],[-122.32852999999999,47.833479999999994],[-122.32857999999999,47.833439999999996],[-122.32869,47.83335],[-122.32902999999999,47.83305],[-122.32912999999999,47.832950000000004],[-122.329227,47.83285],[-122.32941,47.832653],[-122.32963,47.83238],[-122.32997999999999,47.831919],[-122.33024,47.831559999999996],[-122.33042,47.83132],[-122.33059,47.831087999999994],[-122.33068,47.83099],[-122.33081,47.830839999999995],[-122.33094,47.830706],[-122.33095,47.830690999999995],[-122.33104,47.830619999999996],[-122.33106000000001,47.83061],[-122.33115000000001,47.83055],[-122.33120600000001,47.830510000000004],[-122.331351,47.83044099999999],[-122.33145,47.830402],[-122.33153,47.83037],[-122.33155000000001,47.83036],[-122.33161300000002,47.830345],[-122.33163,47.83034],[-122.331799,47.8303],[-122.33186,47.83029],[-122.33195,47.83027],[-122.33202999999999,47.830265],[-122.33212999999999,47.830258],[-122.33223,47.830258],[-122.33232999999998,47.83026699999999],[-122.33242,47.830276],[-122.33266,47.830330000000004],[-122.33274999999999,47.83036],[-122.33283999999999,47.8304],[-122.33297199999998,47.83045],[-122.33322000000001,47.83058],[-122.333769,47.83085],[-122.3339,47.830912000000005],[-122.334,47.83095],[-122.33418,47.83101],[-122.33429000000001,47.83103],[-122.3345,47.83107999999999],[-122.33481,47.83113],[-122.33512999999999,47.83114],[-122.33518,47.83114],[-122.335443,47.83114],[-122.3355,47.83114],[-122.33556,47.831139],[-122.33586,47.831143],[-122.33595,47.83115],[-122.33609,47.83116],[-122.33621000000001,47.831179999999996],[-122.33641,47.83123],[-122.3365,47.831253999999994],[-122.33668,47.831289999999996],[-122.33676,47.831308],[-122.33685799999999,47.83134],[-122.33718,47.83140099999999],[-122.337489,47.831469999999996],[-122.33760000000001,47.831489999999995],[-122.33776,47.831540999999994],[-122.33774,47.83125],[-122.33774,47.831219],[-122.33775,47.830738000000004],[-122.33774,47.83056],[-122.33774,47.83038],[-122.33774,47.830255],[-122.33774,47.8301],[-122.33773,47.83002],[-122.33773,47.829934],[-122.33773,47.82932],[-122.33773,47.829150000000006],[-122.33773,47.829059],[-122.337719,47.828798000000006],[-122.337711,47.82870200000001],[-122.33769000000001,47.828450000000004],[-122.33765000000001,47.828100000000006],[-122.33761200000001,47.82776],[-122.33759,47.82766099999999],[-122.33749,47.82743],[-122.33741,47.827296999999994],[-122.33724000000001,47.827101],[-122.33717,47.82705],[-122.33711000000001,47.827],[-122.33694,47.826859999999996],[-122.3368,47.82677],[-122.33639,47.826496],[-122.3361,47.82625],[-122.33605,47.826170000000005],[-122.33599,47.82606],[-122.33597999999999,47.82604],[-122.33592999999999,47.82592],[-122.335912,47.82585],[-122.33591,47.82578],[-122.3359,47.8254],[-122.3359,47.82518],[-122.3359,47.825026],[-122.3359,47.82487],[-122.3359,47.824],[-122.3359,47.82396],[-122.3359,47.82366],[-122.3359,47.82349],[-122.3359,47.82305],[-122.3359,47.823],[-122.3359,47.82283],[-122.3359,47.822694],[-122.3359,47.82245],[-122.33588999999999,47.82228],[-122.3359,47.82215000000001],[-122.3359,47.822072000000006],[-122.3359,47.82185],[-122.3359,47.821799999999996],[-122.33729000000001,47.82181],[-122.33729000000001,47.821472],[-122.33725000000001,47.82134],[-122.33725000000001,47.821110000000004],[-122.33723,47.81963],[-122.33714,47.81963],[-122.33714,47.818734],[-122.33689,47.81873],[-122.33686,47.81873],[-122.33633999999999,47.818734],[-122.33613,47.81873],[-122.33587999999999,47.81873],[-122.33586999999999,47.81861],[-122.33586,47.81825],[-122.33585,47.818130000000004],[-122.33586,47.81807],[-122.33586,47.81788999999999],[-122.33586999999999,47.81783999999999],[-122.33586999999999,47.81773],[-122.33586,47.81739699999999],[-122.33586,47.81728999999999],[-122.33586,47.81722599999999],[-122.33586,47.81704499999999],[-122.33586,47.816979999999994],[-122.33585,47.81681999999999],[-122.33583999999999,47.816689999999994],[-122.33585,47.81634],[-122.33585,47.816179999999996],[-122.33585,47.816145],[-122.33585,47.81603],[-122.33585,47.81599],[-122.33585,47.81595],[-122.33585,47.815836],[-122.33585,47.815799999999996],[-122.33585,47.815599999999996],[-122.33583999999999,47.81501],[-122.33583999999999,47.814809999999994],[-122.33582999999999,47.814699999999995],[-122.33582,47.81439999999999],[-122.33582,47.81435999999999],[-122.33582,47.814249999999994],[-122.335822,47.813869999999994],[-122.33582,47.81353],[-122.33581,47.813179999999996],[-122.33581,47.81274],[-122.33581,47.81265],[-122.33579999999999,47.812365],[-122.33579999999999,47.812],[-122.33579999999999,47.81163299999999],[-122.33578999999999,47.811418999999994],[-122.33578999999999,47.811152],[-122.33578999999999,47.81093],[-122.33578999999999,47.81057],[-122.33523,47.81057],[-122.33518,47.81057],[-122.33481,47.810579999999995],[-122.33426000000001,47.81057],[-122.333912,47.810559999999995],[-122.33357,47.810562],[-122.33301,47.810562],[-122.33286999999999,47.81056099999999],[-122.33245,47.810558],[-122.33229999999999,47.810559999999995],[-122.33212999999999,47.810555],[-122.33159,47.81055],[-122.33141,47.81054699999999],[-122.33117,47.810545],[-122.33045,47.810539999999996],[-122.33039,47.810539999999996],[-122.33021000000001,47.81053],[-122.329955,47.810539999999996],[-122.32986999999999,47.810539999999996],[-122.32961,47.810539999999996],[-122.32934999999999,47.810539999999996],[-122.32926,47.810539999999996],[-122.32922,47.80968599999999],[-122.32922,47.809593],[-122.32885999999999,47.80959],[-122.32879999999999,47.80959],[-122.32878399999998,47.80936],[-122.32874999999999,47.8087],[-122.32866999999999,47.808696],[-122.32845999999999,47.808695],[-122.32837999999998,47.808695],[-122.32828699999999,47.80869],[-122.32818999999999,47.80869],[-122.32800999999999,47.808685],[-122.32762000000001,47.808688999999994],[-122.32742999999999,47.80869],[-122.32742999999999,47.80878],[-122.32744,47.80905],[-122.32744,47.80914],[-122.32745,47.809239999999996],[-122.32745,47.809439999999995],[-122.32716,47.809419],[-122.32669,47.809418],[-122.32645,47.809419999999996],[-122.32643999999999,47.80912],[-122.32643999999999,47.80843],[-122.32552999999999,47.808398000000004],[-122.32566,47.808215000000004],[-122.32585999999999,47.807939999999995],[-122.326,47.80775],[-122.326454,47.80712],[-122.32665,47.806839999999994],[-122.32717,47.80612],[-122.32873999999998,47.80394],[-122.32909,47.803459999999994],[-122.32926,47.80321],[-122.32982999999999,47.80279],[-122.32972999999998,47.802578000000004],[-122.32986999999999,47.80237],[-122.33173,47.79965000000001],[-122.33161000000001,47.79965200000001],[-122.33139,47.79965000000001],[-122.33077999999999,47.79963000000001],[-122.33051,47.799620000000004],[-122.33036999999999,47.79963000000001],[-122.33025,47.799628000000006],[-122.33014,47.799627],[-122.33014,47.79910000000001],[-122.33013,47.79857000000001],[-122.33013,47.79802000000001],[-122.330122,47.796890000000005],[-122.33012000000001,47.79630000000001],[-122.33014,47.79597000000001],[-122.33015,47.795660000000005],[-122.33015,47.79555000000001],[-122.33025,47.79502000000001],[-122.33042,47.794547],[-122.330574,47.79413400000001],[-122.33067199999999,47.79413500000001],[-122.33105,47.794140000000006],[-122.33119,47.794140000000006],[-122.33141,47.794140000000006],[-122.33189,47.79415000000001],[-122.33215,47.794160000000005],[-122.33232,47.79415000000001],[-122.33259,47.794160000000005],[-122.33283999999999,47.794160000000005],[-122.33282,47.79274000000001],[-122.33279999999999,47.792330000000014],[-122.33272799999999,47.792330000000014],[-122.33218,47.792330000000014],[-122.33199,47.79232000000001],[-122.33191000000001,47.792330000000014],[-122.33179,47.792330000000014],[-122.33165000000001,47.792330000000014],[-122.33156000000001,47.79232000000001],[-122.33184,47.79201800000001],[-122.33188,47.79191900000001],[-122.33246,47.790470000000006],[-122.33266,47.78999],[-122.33281699999999,47.78966],[-122.332827,47.78964],[-122.332964,47.789320000000004],[-122.33308,47.788979000000005],[-122.33318,47.788638000000006],[-122.3332,47.788520000000005],[-122.33327,47.788140000000006],[-122.3333,47.78757],[-122.33332999999999,47.78714],[-122.33333999999999,47.78652],[-122.33336,47.78627],[-122.33336999999999,47.786100000000005],[-122.33339,47.785924],[-122.33339,47.78589],[-122.33341,47.78571000000001],[-122.33243999999999,47.785700000000006],[-122.33198,47.785700000000006],[-122.33176,47.78522],[-122.33142000000001,47.78447],[-122.33128,47.784150000000004],[-122.33116000000001,47.7839],[-122.33129300000002,47.78381],[-122.331441,47.78372],[-122.33165000000001,47.78354],[-122.33183,47.78327],[-122.33187099999999,47.78284],[-122.331691,47.7822],[-122.33116000000001,47.781703],[-122.33072,47.78147],[-122.33028,47.78138],[-122.32969,47.781259999999996],[-122.32924,47.780743],[-122.32914,47.78062],[-122.32858999999999,47.77998],[-122.32802999999998,47.77933000000001],[-122.32797,47.77926],[-122.32791,47.77919000000001],[-122.32746999999999,47.778676000000004],[-122.32694,47.77807000000001],[-122.327304,47.77765],[-122.327334,47.77765],[-122.32763,47.777632000000004],[-122.32832999999998,47.777590000000004],[-122.32994,47.77762],[-122.32995,47.77762],[-122.33,47.77762],[-122.33239999999999,47.77764],[-122.33355,47.77767],[-122.33532,47.777770000000004],[-122.33536,47.777770000000004],[-122.33539999999999,47.777770000000004],[-122.3373,47.77778],[-122.33736,47.77778],[-122.337539,47.77778],[-122.33760000000001,47.77778],[-122.33784,47.77778],[-122.33815,47.77778],[-122.33823,47.77778],[-122.33824,47.77778],[-122.33879999999999,47.77778],[-122.33964,47.77778],[-122.3397,47.77778],[-122.340317,47.77778],[-122.34036,47.777785],[-122.34084,47.777785],[-122.34089,47.777785],[-122.34094,47.77779],[-122.34176000000001,47.77779],[-122.34183,47.77779],[-122.34189,47.77779],[-122.34615000000001,47.777794],[-122.34622000000002,47.777794],[-122.34629000000001,47.777794],[-122.34631,47.777794],[-122.34633,47.77779],[-122.34764000000001,47.77779],[-122.34771,47.77779],[-122.34778,47.77779],[-122.34986,47.777785],[-122.34994,47.777785],[-122.35091,47.77778],[-122.35149,47.77778],[-122.35181,47.77778],[-122.35234999999999,47.77778],[-122.35262999999999,47.77778],[-122.35265,47.77778],[-122.35271999999999,47.77778],[-122.35367,47.777770000000004],[-122.35401,47.777770000000004],[-122.35486,47.777770000000004],[-122.35494,47.777770000000004],[-122.35495,47.777770000000004],[-122.35501,47.777770000000004],[-122.35542,47.777770000000004],[-122.35548999999999,47.777770000000004],[-122.35594999999999,47.777766],[-122.356209,47.777766],[-122.35646999999999,47.777766],[-122.35652,47.777766],[-122.35803999999999,47.777766],[-122.358111,47.777766],[-122.35866999999999,47.777766],[-122.35887999999998,47.777766],[-122.36065,47.777767],[-122.36133,47.777764],[-122.36162000000002,47.77778],[-122.36289,47.777750000000005],[-122.36298,47.77774],[-122.3631,47.777730000000005],[-122.36401000000001,47.77772],[-122.36421400000002,47.777722000000004],[-122.36427,47.777721],[-122.36439,47.777716000000005],[-122.36462000000002,47.777716000000005],[-122.36496000000001,47.777730000000005],[-122.36519,47.77774],[-122.366685,47.77774],[-122.36691400000001,47.777750000000005],[-122.367089,47.777750000000005],[-122.36799,47.777795000000005],[-122.36822000000001,47.777798000000004],[-122.36846999999999,47.777794],[-122.36891,47.777764],[-122.36921000000001,47.777750000000005],[-122.36958,47.777755000000006],[-122.36971,47.777753000000004],[-122.369841,47.77775200000001],[-122.36989,47.777761],[-122.36994,47.77778],[-122.36998,47.777801],[-122.37001,47.77783],[-122.37003999999999,47.77787],[-122.37003999999999,47.77788699999999],[-122.37003999999999,47.7779],[-122.37033999999998,47.77784],[-122.37058999999999,47.77783],[-122.37207999999998,47.777801],[-122.37212999999998,47.777801],[-122.37312,47.77781],[-122.37424,47.77782],[-122.37429,47.77782],[-122.374413,47.77782],[-122.37544999999999,47.77783],[-122.37554099999998,47.77783],[-122.37722000000001,47.777846],[-122.37747999999999,47.77785],[-122.37743999999999,47.781347],[-122.37743999999999,47.78286],[-122.37743999999999,47.783398000000005],[-122.37745,47.78502],[-122.37745,47.785561],[-122.377466,47.78626],[-122.3775,47.788340000000005],[-122.37751,47.78904],[-122.37754,47.789452000000004],[-122.37755,47.7896],[-122.37763,47.79070000000001],[-122.37766,47.79112000000001],[-122.3776,47.791187],[-122.37756,47.79123800000001],[-122.37752,47.79133000000001],[-122.37752,47.791340000000005],[-122.3775,47.791470000000004],[-122.37749,47.79175000000001],[-122.37749,47.791940000000004],[-122.3775,47.79207000000001],[-122.37751,47.792138000000016],[-122.37751,47.79223400000001],[-122.37754,47.79240000000001],[-122.37722000000001,47.79238000000001],[-122.37626,47.792330000000014],[-122.37593999999999,47.79230800000001],[-122.37587999999998,47.792330000000014],[-122.37580999999999,47.792330000000014],[-122.37571999999999,47.79231100000001],[-122.37565,47.792310000000015],[-122.37504999999999,47.792310000000015],[-122.37482999999999,47.79231100000001],[-122.37482999999999,47.792680000000004],[-122.37485,47.793788000000006],[-122.37485,47.794160000000005],[-122.37537999999998,47.794160000000005],[-122.37590999999999,47.794160000000005],[-122.37612,47.794160000000005],[-122.37674999999999,47.794160000000005],[-122.37684999999999,47.794160000000005],[-122.37711300000001,47.794168000000006],[-122.37729999999999,47.794160000000005],[-122.37737999999999,47.794140000000006],[-122.37745,47.79410000000001],[-122.37751,47.794050000000006],[-122.37752,47.79500200000001],[-122.37753,47.79569000000001],[-122.37755,47.79787],[-122.37753,47.79818300000001],[-122.37752,47.79854600000001],[-122.37752,47.79913000000001],[-122.37753,47.79945000000001],[-122.37753,47.799465000000005],[-122.37753,47.79951300000001],[-122.37753,47.79953000000001],[-122.37753,47.79966],[-122.37774999999999,47.799670000000006],[-122.37805999999999,47.799670000000006],[-122.37827099999998,47.799670000000006],[-122.37852,47.79967200000001],[-122.3791,47.79968],[-122.37933999999998,47.799673000000006],[-122.37947999999999,47.799679000000005],[-122.37978999999999,47.799670000000006],[-122.37995,47.799670000000006],[-122.38023,47.79966],[-122.3805,47.79963000000001],[-122.38054,47.79963000000001],[-122.38125000000001,47.79963000000001],[-122.38129,47.799624],[-122.38132999999999,47.799620000000004],[-122.38143,47.799620000000004],[-122.38149,47.79963000000001],[-122.38155,47.79965200000001],[-122.38292,47.79966],[-122.38292,47.79984],[-122.38292999999999,47.8021],[-122.38292999999999,47.80252],[-122.38292999999999,47.80327],[-122.38327,47.803272],[-122.38332999999999,47.803273],[-122.38351,47.803276],[-122.38356999999999,47.803276999999994],[-122.38361,47.803276],[-122.38382999999999,47.803273],[-122.38395,47.803276999999994],[-122.3841,47.803276],[-122.38442300000001,47.80327],[-122.38472999999999,47.80328099999999],[-122.38519,47.803279999999994],[-122.38539999999999,47.80329],[-122.38555,47.80329],[-122.38570999999999,47.80329],[-122.38582,47.80329],[-122.38604,47.803290999999994],[-122.38626000000001,47.80329],[-122.38677999999999,47.80329],[-122.38692,47.80329],[-122.38715,47.80329],[-122.38726000000001,47.80327],[-122.38781,47.80325],[-122.38982999999999,47.80321],[-122.3905,47.803194],[-122.39054,47.80322099999999],[-122.3906,47.80323],[-122.39077999999999,47.803219999999996],[-122.39110000000001,47.80322699999999],[-122.39120000000001,47.803225],[-122.39138,47.80323],[-122.39150000000001,47.80323],[-122.39161000000001,47.803228],[-122.39169900000002,47.803231],[-122.39189,47.803232],[-122.39241,47.80323],[-122.39396,47.80322399999999],[-122.39448,47.803219999999996],[-122.39457,47.803215],[-122.39484,47.803197],[-122.39494,47.803191],[-122.39582,47.80321],[-122.39849,47.803276999999994],[-122.39938,47.8033],[-122.39869,47.803999999999995],[-122.39663,47.806110000000004],[-122.39594,47.806818],[-122.39415000000001,47.808458],[-122.39001,47.812239999999996],[-122.38876999999998,47.813379999999995],[-122.38698,47.81502],[-122.38641,47.81554],[-122.38471,47.817099999999996],[-122.38414,47.81761999999999],[-122.38542,47.817612],[-122.38926000000001,47.81759999999999],[-122.39054,47.817589999999996],[-122.39287999999999,47.81757999999999],[-122.39991,47.81755],[-122.40225000000001,47.81755],[-122.40867,47.817519999999995],[-122.42793,47.81744299999999],[-122.43435000000001,47.81741999999999],[-122.42952000000001,47.82761]],[[-122.34156000000002,47.84667999999999],[-122.34111000000001,47.846669999999996],[-122.34094,47.84665999999999],[-122.34073,47.84665999999999],[-122.34132000000001,47.84612],[-122.34132000000001,47.845819],[-122.34132000000001,47.84525],[-122.34110000000001,47.84543],[-122.34088,47.8456],[-122.34069000000001,47.84561],[-122.34044,47.845777],[-122.34002000000001,47.84623],[-122.3399,47.84623],[-122.33932,47.84623],[-122.33891,47.845904],[-122.33803999999999,47.845901],[-122.338029,47.845608],[-122.33802,47.84532],[-122.33802,47.845256],[-122.33802,47.8452],[-122.33802,47.84516],[-122.33735,47.84516],[-122.33715000000001,47.845166],[-122.33697,47.845168],[-122.33693,47.845123],[-122.336822,47.84501],[-122.33631799999999,47.844829999999995],[-122.33592,47.844677999999995],[-122.33592,47.84475],[-122.33592,47.844789999999996],[-122.33592,47.84479999999999],[-122.33671,47.8451],[-122.33677999999999,47.845164],[-122.33685,47.845259999999996],[-122.33693,47.84536],[-122.33697,47.84545],[-122.33701,47.845530000000004],[-122.33703,47.845605],[-122.33705,47.84567],[-122.33706000000001,47.84574],[-122.33706000000001,47.845879999999994],[-122.33704,47.845948],[-122.33702000000001,47.846],[-122.33702000000001,47.846059999999994],[-122.33703,47.84612],[-122.33704,47.846171],[-122.33707,47.84623],[-122.336965,47.84623],[-122.33744,47.84664399999999],[-122.33784,47.84664699999999],[-122.33927,47.846652],[-122.33919,47.846726999999994],[-122.33905,47.84685999999999],[-122.33901,47.84691],[-122.33941,47.84692],[-122.33976999999999,47.84692],[-122.34133,47.84693],[-122.34156000000002,47.84667999999999]],[[-122.3562,47.79936000000001],[-122.3562,47.79909000000001],[-122.35619,47.79861000000001],[-122.35619,47.79836000000001],[-122.35618199999999,47.79814000000001],[-122.35618099999999,47.79787],[-122.35618099999999,47.79751800000001],[-122.35618199999999,47.79708],[-122.35616999999999,47.79656000000001],[-122.35616999999999,47.796400000000006],[-122.35616999999999,47.79591000000001],[-122.35618,47.795564000000006],[-122.35616999999999,47.79534200000001],[-122.35616,47.79503500000001],[-122.35616,47.7944],[-122.35615,47.793980000000005],[-122.35615,47.793870000000005],[-122.35614,47.79377000000001],[-122.35614,47.79364],[-122.35614,47.79335500000001],[-122.35613,47.79307000000001],[-122.356128,47.79292600000001],[-122.35612,47.792680000000004],[-122.35612,47.792480000000005],[-122.35613,47.79234000000001],[-122.35612,47.79219000000001],[-122.35611,47.791940000000004],[-122.35612,47.791810000000005],[-122.35611,47.79177200000001],[-122.35608599999999,47.791630000000005],[-122.35606,47.79121000000001],[-122.35597999999999,47.78999],[-122.35596999999999,47.78974],[-122.35513999999999,47.78904],[-122.35497,47.78887],[-122.35482,47.78873000000001],[-122.3543,47.78828],[-122.35387999999999,47.78778],[-122.35379999999999,47.787665999999994],[-122.35367,47.78741],[-122.35358,47.787130000000005],[-122.35333999999999,47.78584],[-122.35324,47.785070000000005],[-122.35318,47.785075000000006],[-122.35312,47.785077],[-122.35292,47.78508],[-122.35272099999999,47.78508],[-122.35252,47.78508],[-122.35230999999999,47.785077],[-122.35211,47.785078000000006],[-122.35199999999999,47.78508],[-122.35167,47.785070000000005],[-122.35155,47.785070000000005],[-122.35158,47.785291],[-122.35159,47.785340000000005],[-122.35161000000001,47.785638000000006],[-122.35162000000001,47.785816000000004],[-122.3516,47.78616],[-122.35159,47.78644],[-122.35158,47.786619],[-122.35157,47.78701],[-122.35158,47.78716],[-122.35158,47.78734],[-122.35111,47.78735],[-122.350522,47.78735],[-122.35006999999999,47.78735],[-122.34851,47.78734],[-122.34735,47.78735],[-122.34693,47.78735],[-122.34666000000001,47.78734],[-122.346292,47.787341],[-122.34629000000001,47.787251],[-122.34629000000001,47.78698],[-122.34629000000001,47.786893],[-122.34612000000001,47.7869],[-122.346036,47.786902000000005],[-122.34535,47.7869],[-122.34527,47.786898],[-122.34502,47.786895],[-122.34489,47.786896],[-122.34478,47.786898],[-122.34451000000001,47.78688],[-122.34439,47.78689],[-122.34414000000001,47.786893],[-122.34407,47.786892],[-122.34386,47.78689],[-122.34367,47.7869],[-122.34309,47.7869],[-122.34276,47.7869],[-122.34269,47.786902000000005],[-122.34246999999999,47.7869],[-122.3424,47.7869],[-122.342349,47.786899],[-122.34224,47.786899],[-122.34224,47.787710000000004],[-122.34225900000001,47.78871300000001],[-122.34108,47.78871000000001],[-122.33956,47.788700000000006],[-122.33952000000001,47.78875000000001],[-122.33913,47.789288],[-122.33856,47.79008],[-122.33855,47.790090000000006],[-122.33825,47.79055000000001],[-122.337998,47.79091000000001],[-122.33788,47.79108],[-122.33759,47.79148],[-122.33724000000001,47.791990000000006],[-122.33699,47.792355000000015],[-122.33735,47.79236100000001],[-122.33739,47.79236200000001],[-122.33745,47.79235600000001],[-122.33843499999999,47.792352000000015],[-122.33847999999999,47.792352000000015],[-122.33879999999999,47.79236000000001],[-122.33911,47.79235000000001],[-122.33923,47.79235000000001],[-122.34055000000001,47.792352000000015],[-122.34099,47.79235300000001],[-122.34098,47.79248200000001],[-122.34096000000001,47.79269000000001],[-122.34097,47.792871000000005],[-122.34097,47.793001000000004],[-122.340728,47.79316000000001],[-122.34051000000001,47.79330300000001],[-122.34043,47.79335000000001],[-122.34035,47.793394000000006],[-122.34026000000001,47.79343000000001],[-122.34016000000001,47.79346],[-122.34006400000001,47.793485000000004],[-122.33993,47.79351000000001],[-122.33991,47.79351000000001],[-122.33964,47.79353000000001],[-122.33946999999999,47.79353500000001],[-122.33909,47.79355000000001],[-122.33896999999999,47.79355000000001],[-122.33881,47.79356000000001],[-122.33872999999998,47.793569000000005],[-122.33864,47.793580000000006],[-122.33855,47.793607],[-122.3385,47.793620000000004],[-122.33847999999999,47.79363000000001],[-122.33842999999999,47.793670000000006],[-122.33836,47.79377000000001],[-122.33834699999998,47.79384],[-122.33833999999999,47.79388],[-122.33832999999998,47.79399000000001],[-122.33832,47.794160000000005],[-122.33832,47.794430000000006],[-122.33832999999998,47.794630000000005],[-122.33832999999998,47.794909000000004],[-122.33832999999998,47.794923000000004],[-122.33832,47.795110000000015],[-122.33694,47.79510000000001],[-122.33559,47.795094000000006],[-122.33559,47.79513900000001],[-122.33559,47.79520000000001],[-122.33559,47.79555000000001],[-122.3356,47.79580000000001],[-122.33561,47.79601000000001],[-122.335606,47.796084],[-122.3356,47.796290000000006],[-122.3356,47.79636000000001],[-122.33659,47.796347000000004],[-122.33776999999999,47.79633000000001],[-122.33782000000001,47.79690600000001],[-122.3366,47.796940000000006],[-122.33561,47.796960000000006],[-122.33561,47.797140000000006],[-122.33561,47.79719000000001],[-122.33562,47.797360000000005],[-122.33561,47.797540000000005],[-122.33562,47.797670000000004],[-122.33562,47.797854],[-122.33562,47.79800000000001],[-122.33563,47.79844000000001],[-122.33563,47.798510000000014],[-122.33564,47.79859300000001],[-122.33564,47.79881000000001],[-122.33566,47.79931000000001],[-122.33566,47.799462000000005],[-122.33566,47.79968],[-122.33587999999999,47.799682000000004],[-122.33617,47.799685000000004],[-122.33642400000001,47.79968],[-122.33654,47.79968],[-122.33676,47.79967500000001],[-122.33686,47.799669],[-122.33697,47.799670000000006],[-122.33720000000001,47.79967200000001],[-122.33761000000001,47.799670000000006],[-122.33782000000001,47.79966],[-122.33814,47.79966],[-122.33828,47.79966],[-122.33851,47.79966],[-122.33908,47.79966],[-122.33939,47.79966],[-122.33972,47.79966],[-122.3398,47.79966],[-122.34033,47.79966],[-122.34069000000001,47.79966],[-122.34102000000001,47.79966],[-122.34143,47.79966],[-122.34156000000002,47.79966],[-122.34196000000001,47.79965000000001],[-122.34319900000001,47.79965000000001],[-122.34343,47.79965000000001],[-122.34374,47.799645000000005],[-122.34386,47.79965000000001],[-122.34423000000001,47.79965000000001],[-122.34435,47.79965000000001],[-122.34450000000001,47.79965000000001],[-122.34496000000001,47.799639000000006],[-122.34512000000001,47.799637000000004],[-122.34515,47.799640000000004],[-122.34525000000001,47.799640000000004],[-122.34528,47.799645000000005],[-122.34552000000001,47.799639000000006],[-122.3457,47.79963000000001],[-122.34622000000002,47.799640000000004],[-122.34647,47.799634000000005],[-122.34681,47.799640000000004],[-122.34693,47.799637000000004],[-122.347328,47.79963000000001],[-122.34791000000001,47.79963000000001],[-122.34821000000001,47.799620000000004],[-122.3483,47.799620000000004],[-122.34876,47.79963000000001],[-122.34897,47.799628000000006],[-122.34921000000001,47.79963000000001],[-122.34962000000002,47.799624],[-122.34984,47.799624],[-122.35025,47.799620000000004],[-122.35054,47.799620000000004],[-122.35112000000001,47.799620000000004],[-122.35148,47.79961000000001],[-122.35189,47.799600000000005],[-122.35206,47.799600000000005],[-122.35247999999999,47.799603000000005],[-122.35256999999999,47.79959900000001],[-122.35262999999999,47.799600000000005],[-122.35273999999998,47.799600000000005],[-122.35278999999998,47.799600000000005],[-122.35294999999999,47.799597000000006],[-122.35301,47.799600000000005],[-122.35311,47.799600000000005],[-122.35382,47.799594000000006],[-122.35409,47.79959000000001],[-122.35444,47.799600000000005],[-122.35451,47.799600000000005],[-122.3549,47.799600000000005],[-122.355053,47.799600000000005],[-122.35552999999999,47.799600000000005],[-122.35578999999998,47.799600000000005],[-122.35621,47.799600000000005],[-122.3562,47.79936000000001]]]},"name":"Edmonds"},{"boundary":{"type":"Polygon","coordinates":[[[-122.33579999999999,47.43127],[-122.33451000000001,47.431259999999995],[-122.33371799999999,47.431259999999995],[-122.332467,47.431256],[-122.33132,47.431248999999994],[-122.330362,47.431239999999995],[-122.32977999999999,47.43124099999999],[-122.32847999999998,47.431239999999995],[-122.32718,47.43123],[-122.3261,47.43123],[-122.325193,47.43123],[-122.32497,47.43123],[-122.32497,47.43102],[-122.32496,47.43078],[-122.32494,47.42989],[-122.32493,47.429387],[-122.32492,47.429002000000004],[-122.32491,47.42855000000001],[-122.32319,47.42851000000001],[-122.32213099999998,47.42850000000001],[-122.32166000000001,47.428490000000004],[-122.321146,47.42848],[-122.32006999999999,47.42846],[-122.3183,47.42844],[-122.31831,47.42792],[-122.31833999999999,47.42764],[-122.31836,47.427478],[-122.31839,47.427337],[-122.31852,47.426950000000005],[-122.31882999999999,47.42625],[-122.31899,47.42589],[-122.31964,47.42439],[-122.31978,47.424],[-122.31983,47.423849],[-122.32006,47.42293900000001],[-122.32019,47.422371000000005],[-122.32027,47.421974],[-122.32029,47.42184099999999],[-122.32029999999999,47.42163],[-122.32028,47.421116000000005],[-122.32025,47.419869999999996],[-122.32024,47.41916],[-122.32022,47.41859],[-122.32022,47.41855],[-122.32019,47.41733],[-122.32016999999999,47.416802999999994],[-122.32018,47.416489999999996],[-122.32019,47.416349],[-122.32022,47.41617],[-122.320257,47.416038],[-122.3191,47.41607],[-122.31882999999999,47.416076999999994],[-122.31881,47.41599],[-122.318816,47.41585],[-122.31881,47.41578],[-122.31869,47.415773],[-122.31821000000001,47.41575],[-122.31659,47.41567],[-122.31557,47.41563],[-122.31444,47.4156],[-122.31389,47.41558],[-122.31309,47.41555],[-122.31275,47.415530000000004],[-122.31231,47.41554],[-122.312084,47.41556],[-122.31189,47.415561],[-122.31048,47.415530000000004],[-122.30963799999999,47.41552],[-122.3093,47.415516000000004],[-122.3092,47.415510000000005],[-122.30865,47.41552],[-122.30711000000001,47.415527],[-122.30676999999999,47.415529],[-122.30604,47.415530000000004],[-122.30512999999999,47.41554],[-122.30464,47.41554],[-122.30414,47.41554],[-122.30371,47.41554],[-122.302555,47.415527],[-122.30248999999999,47.41552],[-122.30248999999999,47.41545],[-122.3025,47.41493],[-122.30248999999999,47.41409899999999],[-122.30247999999999,47.413689999999995],[-122.30248999999999,47.41187999999999],[-122.29782900000002,47.41179999999999],[-122.29803,47.410379999999996],[-122.29831,47.408390000000004],[-122.29832,47.40823],[-122.29821000000001,47.40823],[-122.29813,47.40823],[-122.29721000000002,47.40822],[-122.29657,47.408210000000004],[-122.29602000000001,47.408210000000004],[-122.29518,47.408206],[-122.29486000000001,47.408203],[-122.29452000000002,47.4082],[-122.29369000000001,47.408194],[-122.29218300000001,47.408190000000005],[-122.292367,47.4073],[-122.29244,47.40687],[-122.29246,47.406645],[-122.29255,47.405992000000005],[-122.29261000000001,47.405283999999995],[-122.29265000000001,47.404588999999994],[-122.29265000000001,47.404562],[-122.29266000000001,47.40376],[-122.29266000000001,47.40363],[-122.29266000000001,47.403568],[-122.29260000000001,47.402256],[-122.29256000000001,47.40177],[-122.29254,47.401605999999994],[-122.29253,47.40145],[-122.292455,47.40094],[-122.29225000000001,47.39959],[-122.29208,47.39844],[-122.29196000000002,47.39759],[-122.29173,47.396065],[-122.29186000000001,47.396066],[-122.29212000000001,47.396069],[-122.292,47.39497],[-122.29186000000001,47.394031],[-122.29198000000001,47.393750000000004],[-122.29203,47.39363],[-122.292589,47.39227],[-122.29264,47.392160000000004],[-122.292806,47.39221800000001],[-122.29333,47.39246],[-122.29389,47.392790000000005],[-122.294739,47.39348],[-122.29493000000001,47.39365],[-122.2954,47.394059999999996],[-122.29548,47.39422],[-122.29592000000001,47.394594],[-122.29596000000001,47.39463],[-122.29610000000001,47.394723],[-122.29624000000001,47.39481],[-122.29651000000001,47.394954],[-122.29664000000001,47.395010000000006],[-122.29683,47.39509],[-122.29700000000001,47.395157000000005],[-122.29716000000002,47.395208000000004],[-122.297437,47.395286],[-122.29762000000002,47.395328000000006],[-122.29788,47.39537800000001],[-122.29853,47.39548],[-122.29885,47.39553000000001],[-122.29879,47.39374],[-122.29775000000001,47.393724],[-122.29756000000002,47.393724],[-122.29719000000001,47.39372],[-122.29719000000001,47.39246],[-122.29521000000001,47.39244],[-122.29511000000001,47.392437],[-122.29519,47.392030000000005],[-122.29536,47.391345],[-122.29544,47.390965],[-122.29553,47.390965],[-122.29627,47.390964],[-122.29720000000002,47.39096],[-122.29720000000002,47.39043],[-122.29581,47.390432000000004],[-122.29558,47.390403],[-122.29579,47.389576],[-122.29592000000001,47.38901],[-122.29722000000002,47.38903],[-122.29723000000001,47.38838],[-122.29724400000002,47.38737999999999],[-122.29639,47.38737999999999],[-122.29633,47.38737],[-122.29642000000001,47.387049999999995],[-122.29648,47.386779999999995],[-122.29654000000001,47.38652],[-122.29671,47.38652],[-122.29729000000002,47.38653],[-122.29772000000001,47.38652],[-122.29816000000001,47.38652],[-122.29863,47.38652],[-122.29872999999999,47.38652],[-122.29871,47.38623],[-122.29865000000001,47.38494399999999],[-122.29866000000001,47.38465999999999],[-122.29769000000002,47.38464999999999],[-122.29769000000002,47.384401999999994],[-122.29769000000002,47.38425999999999],[-122.29710000000001,47.384229999999995],[-122.29743,47.382889999999996],[-122.29751000000002,47.38259],[-122.29867,47.38259],[-122.29868,47.382166],[-122.29871,47.38115],[-122.29962000000002,47.381142999999994],[-122.30053,47.381099999999996],[-122.30054,47.380339],[-122.30053,47.37945],[-122.30062000000001,47.37945],[-122.301826,47.379459999999995],[-122.30196000000001,47.37945],[-122.30212,47.37943],[-122.30372999999999,47.37947],[-122.30376999999999,47.37948399999999],[-122.30382999999999,47.37949],[-122.3048,47.3795],[-122.30493,47.3795],[-122.30609,47.37953],[-122.30672,47.37955],[-122.30718,47.37955],[-122.3079,47.37956],[-122.30786,47.377779999999994],[-122.30782,47.376079999999995],[-122.30782,47.37598],[-122.30681,47.375965],[-122.30574999999999,47.37595],[-122.30469000000001,47.375930000000004],[-122.30337999999999,47.375910000000005],[-122.30282999999999,47.3759],[-122.30273999999999,47.3759],[-122.30256,47.3759],[-122.30256,47.375544],[-122.30256,47.37541],[-122.30256,47.37538],[-122.30256,47.37535200000001],[-122.30256,47.3752],[-122.30256,47.375149],[-122.30256,47.375],[-122.30256,47.374835],[-122.30245,47.37483399999999],[-122.302369,47.374829999999996],[-122.30199,47.374829999999996],[-122.30162000000001,47.374821999999995],[-122.30116000000001,47.37481999999999],[-122.30066000000001,47.37481],[-122.30068,47.374779999999994],[-122.30075,47.374635],[-122.30107199999999,47.374013],[-122.30138,47.37345],[-122.30163,47.37295],[-122.30199,47.372280999999994],[-122.30232,47.371649999999995],[-122.30266999999999,47.37096],[-122.3031,47.37017],[-122.30332,47.36977],[-122.30385,47.368700000000004],[-122.30422000000002,47.368001],[-122.30451000000001,47.36743],[-122.30467,47.36718],[-122.30475,47.36705],[-122.30486,47.36685],[-122.30506999999999,47.366398000000004],[-122.30538999999999,47.3658],[-122.30566999999999,47.365259],[-122.30699,47.362730000000006],[-122.30761000000001,47.361537],[-122.30779,47.36123],[-122.30807999999999,47.36069],[-122.30829,47.36026],[-122.308602,47.35965999999999],[-122.30892999999999,47.35903999999999],[-122.30909,47.35875],[-122.30952,47.35794299999999],[-122.31048,47.35795099999999],[-122.31178,47.35795999999999],[-122.3123,47.35795999999999],[-122.31323,47.35794599999999],[-122.31323,47.357499999999995],[-122.31325000000001,47.35686499999999],[-122.31325000000001,47.35631],[-122.31324000000001,47.3553],[-122.31326000000001,47.354319999999994],[-122.31325500000001,47.353708],[-122.31325000000001,47.35344599999999],[-122.313241,47.35332],[-122.31322000000002,47.353199999999994],[-122.31318,47.35307999999999],[-122.31313,47.352958],[-122.31307,47.35283999999999],[-122.31303,47.35278699999999],[-122.31292,47.352636999999994],[-122.31284,47.35255],[-122.31277999999999,47.3525],[-122.31269,47.35239],[-122.31267,47.35236],[-122.31264,47.35232],[-122.31262000000001,47.352289999999996],[-122.3126,47.35227],[-122.31257,47.35223],[-122.31255,47.352199999999996],[-122.31253,47.35215],[-122.31248,47.35207],[-122.31244,47.351989999999994],[-122.31241,47.35189999999999],[-122.31239199999999,47.35185999999999],[-122.3124,47.35184999999999],[-122.31242,47.35184199999999],[-122.31244,47.35181999999999],[-122.31246999999999,47.35179999999999],[-122.3125,47.35178699999999],[-122.31231,47.35175399999999],[-122.31246,47.35174599999999],[-122.31253,47.351729999999996],[-122.31257,47.351729999999996],[-122.312656,47.351715],[-122.31286,47.35166999999999],[-122.31299,47.35162199999999],[-122.31317,47.351589999999995],[-122.31321000000001,47.351589999999995],[-122.3133,47.35158199999999],[-122.31338,47.35158199999999],[-122.31345,47.35158199999999],[-122.31344,47.35068999999999],[-122.31332,47.35067999999999],[-122.31327,47.34972],[-122.31328,47.34967999999999],[-122.31331,47.349599999999995],[-122.31336999999999,47.349533],[-122.31344,47.34945999999999],[-122.31352000000001,47.349408],[-122.31359,47.34936],[-122.31366000000001,47.349289999999996],[-122.31372,47.349219999999995],[-122.31374,47.349108],[-122.31376,47.34887],[-122.31585,47.34887],[-122.31584799999999,47.34875],[-122.31585,47.3484],[-122.31587999999999,47.347049999999996],[-122.31608,47.347049999999996],[-122.316705,47.347032],[-122.31771,47.34701],[-122.31774,47.34701],[-122.31782000000001,47.34701999999999],[-122.31851,47.34701999999999],[-122.31851,47.34668599999999],[-122.31861,47.34353],[-122.318726,47.34354],[-122.31886999999999,47.34354],[-122.318951,47.34397],[-122.31901,47.344199999999994],[-122.319484,47.345389],[-122.3197,47.345382],[-122.32030999999999,47.34537],[-122.32122000000001,47.345341],[-122.32121000000001,47.345659999999995],[-122.32157,47.3461],[-122.32179,47.34664699999999],[-122.32186999999999,47.34683999999999],[-122.32222999999999,47.346819999999994],[-122.32242,47.346816],[-122.32254999999999,47.346689999999995],[-122.32328,47.346858],[-122.3235,47.34665999999999],[-122.32355,47.346259999999994],[-122.32359,47.34613],[-122.32379999999999,47.34586699999999],[-122.32463,47.34619],[-122.32468,47.34615],[-122.32605,47.344719999999995],[-122.32629,47.34457],[-122.32621,47.3445],[-122.32601,47.34437],[-122.32687999999999,47.343849999999996],[-122.32827999999999,47.343598],[-122.32865,47.34337],[-122.33018,47.34339],[-122.33028,47.34315],[-122.33042999999999,47.34282399999999],[-122.33052,47.34252],[-122.33086,47.342085],[-122.33016,47.341939999999994],[-122.32969,47.34155],[-122.32952,47.34125999999999],[-122.32942999999999,47.34111],[-122.32918199999999,47.341103],[-122.32918,47.340399999999995],[-122.331801,47.340402],[-122.33414,47.34040699999999],[-122.33449,47.34041],[-122.3345,47.34063],[-122.3345,47.341525],[-122.33452000000001,47.34236],[-122.33458,47.342428],[-122.33448,47.34248399999999],[-122.334407,47.34241],[-122.33441,47.34297],[-122.33441,47.343059999999994],[-122.33416000000001,47.34314],[-122.33212999999999,47.34382099999999],[-122.324833,47.34852],[-122.32481,47.349183999999994],[-122.32457,47.35771999999999],[-122.32453,47.35892],[-122.325234,47.35972099999999],[-122.32512999999999,47.360321],[-122.32482,47.36139],[-122.325289,47.36421],[-122.32582999999998,47.36472],[-122.32572999999998,47.365100000000005],[-122.325694,47.36524],[-122.32482999999999,47.36842],[-122.32533399999998,47.368321],[-122.32682999999999,47.37202],[-122.32687999999999,47.372530000000005],[-122.32692999999999,47.373219999999996],[-122.325234,47.373819999999995],[-122.32527999999999,47.37572],[-122.32527999999999,47.37589],[-122.32533399999998,47.37812],[-122.32632999999998,47.38313],[-122.32666,47.38483999999999],[-122.32705,47.38677],[-122.32723,47.387719999999995],[-122.32724,47.38784299999999],[-122.32723,47.38802],[-122.32606999999999,47.39078],[-122.32596,47.39102],[-122.32576999999998,47.391239999999996],[-122.32574999999999,47.391373],[-122.32572999999998,47.391521],[-122.32732,47.395632000000006],[-122.32742999999999,47.395920000000004],[-122.32793,47.39792],[-122.32842999999998,47.400620999999994],[-122.33063,47.402921],[-122.331334,47.404019999999996],[-122.33152000000001,47.40441],[-122.33157,47.404556],[-122.33141,47.40461],[-122.33136,47.404619999999994],[-122.33097,47.40477],[-122.33065,47.404379999999996],[-122.32968,47.404388],[-122.32951,47.40439],[-122.32951,47.40455],[-122.32954799999999,47.40531000000001],[-122.32885999999999,47.405330000000006],[-122.32892,47.40637],[-122.32822999999999,47.406388],[-122.32826,47.406936],[-122.32741,47.40696],[-122.32742,47.40718],[-122.32748,47.40717],[-122.32749,47.40728599999999],[-122.32743099999999,47.407301],[-122.32708,47.40739],[-122.32685,47.407669999999996],[-122.32682,47.407948],[-122.326893,47.40815200000001],[-122.32691,47.408541],[-122.32706,47.408570000000005],[-122.32725,47.408570000000005],[-122.32746,47.408570000000005],[-122.32753,47.408570000000005],[-122.3276,47.408570000000005],[-122.32771,47.40856],[-122.32911,47.40847],[-122.32929999999999,47.40844],[-122.32946999999999,47.408391],[-122.32976999999998,47.408324],[-122.33079,47.408072000000004],[-122.331,47.408027],[-122.33124000000001,47.40799],[-122.331428,47.40799],[-122.33164000000001,47.408],[-122.33185,47.408038000000005],[-122.33205099999999,47.40809],[-122.33224,47.40816],[-122.33241,47.408252000000005],[-122.332422,47.40826],[-122.33251,47.40832],[-122.3326,47.408390000000004],[-122.33268199999999,47.408462],[-122.33278199999998,47.40858],[-122.33282999999999,47.40866],[-122.33286999999999,47.408730000000006],[-122.33291,47.40882],[-122.33308,47.409648999999995],[-122.33313,47.40992],[-122.33315,47.410015],[-122.33321000000001,47.41017],[-122.33325,47.410259999999994],[-122.33331,47.41034],[-122.33336999999999,47.41042099999999],[-122.33344,47.4105],[-122.33352000000001,47.41057],[-122.33361000000001,47.410639999999994],[-122.33403,47.410933],[-122.3341,47.41099],[-122.33421000000001,47.411111],[-122.334349,47.4113],[-122.33441,47.41141999999999],[-122.33447,47.411579999999994],[-122.33458,47.41217],[-122.33468,47.41273],[-122.33481,47.413419999999995],[-122.3352,47.41558],[-122.33525,47.416039999999995],[-122.33528,47.41659],[-122.33527,47.417362999999995],[-122.33527,47.41858],[-122.33527,47.418727],[-122.33527,47.41918],[-122.33527,47.419819999999994],[-122.33527,47.420183],[-122.33526,47.42046],[-122.33525300000001,47.420950000000005],[-122.33525,47.42109],[-122.33524,47.421510000000005],[-122.335204,47.42309],[-122.335212,47.42327],[-122.33526,47.42396],[-122.33529999999999,47.424510000000005],[-122.33534999999999,47.42517000000001],[-122.33536,47.42526],[-122.33536999999998,47.42531800000001],[-122.33539199999998,47.4256],[-122.33545,47.42634],[-122.33552900000001,47.42732],[-122.33556,47.4278],[-122.33558,47.42803000000001],[-122.33559,47.428230000000006],[-122.33562,47.428630000000005],[-122.33578999999999,47.430896999999995],[-122.33579999999999,47.431059999999995],[-122.33579999999999,47.43113],[-122.33579999999999,47.43127]]]},"name":"Des Moines"}]; +}); \ No newline at end of file diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-154-marker-fit.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-154-marker-fit.js new file mode 100644 index 0000000..6d1bdd1 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-154-marker-fit.js @@ -0,0 +1,174 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + markers: [ + { + id:1, + latitude: 45, + longitude: -74, + showWindow: false, + title: 'Marker 2' + }, + { + id:2, + latitude: 15, + longitude: 30, + showWindow: false, + title: 'Marker 2' + }, + { + id:3, + icon: 'assets/images/plane.png', + latitude: 37, + longitude: -122, + showWindow: false, + title: 'Plane' + } + ], + markers2: [ + { + id: 1, + latitude: 46, + longitude: -77, + title: '[46,-77]' + }, + { + id: 2, + latitude: 33, + longitude: -77, + title: '[33,-77]' + }, + { + id:3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -125, + title: '[35,-125]' + } + ], + dynamicMarkers: [], + refresh: function () { + $scope.map.control.refresh(origCenter); + } + } + }); + +// _.each($scope.map.markers, function (marker) { +// marker.closeClick = function () { +// marker.showWindow = false; +// $scope.$apply(); +// }; +// marker.onClicked = function () { +// $scope.onMarkerClicked(marker); +// }; +// }); +// +// _.each($scope.map.markers2, function (marker) { +// marker.closeClick = function () { +// marker.showWindow = false; +// $scope.$apply(); +// }; +// marker.onClicked = function () { +// $scope.onMarkerClicked(marker); +// }; +// }); + + $scope.removeMarkers = function () { + $log.info("Clearing markers. They should disappear from the map now"); + $scope.map.markers.length = 0; + $scope.map.markers2.length = 0; + $scope.map.dynamicMarkers.length = 0; + $scope.map.randomMarkers.length = 0; + $scope.map.mexiMarkers.length = 0; + $scope.map.polylines.length = 0; + $scope.map.clickedMarker = null; + $scope.searchLocationMarker = null; + $scope.map.infoWindow.show = false; + $scope.map.templatedInfoWindow.show = false; + // $scope.map.infoWindow.coords = null; + }; + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + }; + + var markerToClose = null; + + $scope.onMarkerClicked = function (marker) { +// if (markerToClose) { +// markerToClose.showWindow = false; +// } + markerToClose = marker; // for next go around + marker.showWindow = true; + $scope.$apply(); + //window.alert("Marker: lat: " + marker.latitude + ", lon: " + marker.longitude + " clicked!!") + }; + + $scope.onInsideWindowClick = function(){ + alert("Window hit!"); + }; + + $timeout(function () { + var dynamicMarkers = [ + { id: 1, + latitude: 46, + longitude: -79 + }, + { + id: 2, + latitude: 33, + longitude: -79 + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127 + } + ]; + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +}); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-157-ng-click.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-157-ng-click.js new file mode 100644 index 0000000..6d1bdd1 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-157-ng-click.js @@ -0,0 +1,174 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + markers: [ + { + id:1, + latitude: 45, + longitude: -74, + showWindow: false, + title: 'Marker 2' + }, + { + id:2, + latitude: 15, + longitude: 30, + showWindow: false, + title: 'Marker 2' + }, + { + id:3, + icon: 'assets/images/plane.png', + latitude: 37, + longitude: -122, + showWindow: false, + title: 'Plane' + } + ], + markers2: [ + { + id: 1, + latitude: 46, + longitude: -77, + title: '[46,-77]' + }, + { + id: 2, + latitude: 33, + longitude: -77, + title: '[33,-77]' + }, + { + id:3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -125, + title: '[35,-125]' + } + ], + dynamicMarkers: [], + refresh: function () { + $scope.map.control.refresh(origCenter); + } + } + }); + +// _.each($scope.map.markers, function (marker) { +// marker.closeClick = function () { +// marker.showWindow = false; +// $scope.$apply(); +// }; +// marker.onClicked = function () { +// $scope.onMarkerClicked(marker); +// }; +// }); +// +// _.each($scope.map.markers2, function (marker) { +// marker.closeClick = function () { +// marker.showWindow = false; +// $scope.$apply(); +// }; +// marker.onClicked = function () { +// $scope.onMarkerClicked(marker); +// }; +// }); + + $scope.removeMarkers = function () { + $log.info("Clearing markers. They should disappear from the map now"); + $scope.map.markers.length = 0; + $scope.map.markers2.length = 0; + $scope.map.dynamicMarkers.length = 0; + $scope.map.randomMarkers.length = 0; + $scope.map.mexiMarkers.length = 0; + $scope.map.polylines.length = 0; + $scope.map.clickedMarker = null; + $scope.searchLocationMarker = null; + $scope.map.infoWindow.show = false; + $scope.map.templatedInfoWindow.show = false; + // $scope.map.infoWindow.coords = null; + }; + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + }; + + var markerToClose = null; + + $scope.onMarkerClicked = function (marker) { +// if (markerToClose) { +// markerToClose.showWindow = false; +// } + markerToClose = marker; // for next go around + marker.showWindow = true; + $scope.$apply(); + //window.alert("Marker: lat: " + marker.latitude + ", lon: " + marker.longitude + " clicked!!") + }; + + $scope.onInsideWindowClick = function(){ + alert("Window hit!"); + }; + + $timeout(function () { + var dynamicMarkers = [ + { id: 1, + latitude: 46, + longitude: -79 + }, + { + id: 2, + latitude: 33, + longitude: -79 + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127 + } + ]; + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +}); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-322.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-322.js new file mode 100644 index 0000000..34a16da --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-322.js @@ -0,0 +1,170 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + markers: [ + { + id:1, + latitude: 45, + longitude: -74, + showWindow: false, + title: 'Marker 2' + }, + { + id:2, + latitude: 15, + longitude: 30, + showWindow: false, + title: 'Marker 2' + }, + { + id:3, + icon: 'assets/images/plane.png', + latitude: 37, + longitude: -122, + showWindow: false, + title: 'Plane' + } + ], + markers2: [ + { + id: 1, + latitude: 46, + longitude: -77, + title: '[46,-77]' + }, + { + id: 2, + latitude: 33, + longitude: -77, + title: '[33,-77]' + }, + { + id:3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -125, + title: '[35,-125]' + } + ], + dynamicMarkers: [], + refresh: function () { + $scope.map.control.refresh(origCenter); + } + } + }); + + _.each($scope.map.markers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + + _.each($scope.map.markers2, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + + $scope.removeMarkers = function () { + $log.info("Clearing markers. They should disappear from the map now"); + $scope.map.markers.length = 0; + $scope.map.markers2.length = 0; + $scope.map.dynamicMarkers.length = 0; + $scope.map.randomMarkers.length = 0; + $scope.map.mexiMarkers.length = 0; + $scope.map.polylines.length = 0; + $scope.map.clickedMarker = null; + $scope.searchLocationMarker = null; + $scope.map.infoWindow.show = false; + $scope.map.templatedInfoWindow.show = false; + // $scope.map.infoWindow.coords = null; + }; + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + }; + + var markerToClose = null; + + $scope.onMarkerClicked = function (marker) { + if (markerToClose) { + markerToClose.showWindow = false; + } + markerToClose = marker; // for next go around + marker.showWindow = true; + $scope.$apply(); + //window.alert("Marker: lat: " + marker.latitude + ", lon: " + marker.longitude + " clicked!!") + }; + + $timeout(function () { + var dynamicMarkers = [ + { id: 1, + latitude: 46, + longitude: -79 + }, + { + id: 2, + latitude: 33, + longitude: -79 + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127 + } + ]; + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +}); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-354-window-windows-rerender-content.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-354-window-windows-rerender-content.js new file mode 100644 index 0000000..45c46c4 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-354-window-windows-rerender-content.js @@ -0,0 +1,24 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon", function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}) + .controller( + "controller", ['$rootScope', '$scope', '$location', '$http', + function ($rootScope, $scope, $location, $http) { + $scope.map = { + center: { + latitude: 51.219053, + longitude: 4.404418 + }, + zoom: 15 + }; + $scope.windowOptions = {disableAutoPan : true}; + $scope.windows = [ + { + latitude: 51.229053, + longitude: 4.404418, + show:true + } + ] + $scope.test = {}; + $scope.test.name = true; + }]); \ No newline at end of file diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-371-polygon-ng-repeat.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-371-polygon-ng-repeat.js new file mode 100644 index 0000000..26ba28b --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-371-polygon-ng-repeat.js @@ -0,0 +1,85 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", ['$rootScope', '$scope', '$location', '$http', function ($rootScope, $scope, $location, $http) { + $scope.map = { + center: { + latitude: -22.912122112782, longitude: -43.233883213252 + }, + options: { + disableDefaultUI: false, + panControl: true, + navigationControl: true, + scrollwheel: true, + scaleControl: false + }, + zoom: 15, + polygons: [] + }; + + $scope.polys = [ + { + id: 1, + clickable: true, + draggable: false, + editable: false, + visible: true, + geodesic: false, + stroke: {weight: 1, color: "#000080", opacity: 1}, + fill: {color: "#FFCE00", opacity: 1}, + path: [ + {latitude: -22.840109991554, longitude: -43.604843616486}, + {latitude: -22.895785581504, longitude: -43.660461902618}, + {latitude: -22.923614814482, longitude: -43.480560779572} + ] + }, + { id: 2, + clickable: true, + draggable: false, + editable: false, + visible: true, + geodesic: false, + stroke: {weight: 1, color: "black", opacity: 1}, + fill: {color: "#FFCE00", opacity: "0.3"}, + path: [ + {latitude: -22.220105243267, longitude: -42.533525750041}, + {latitude: -22.221535457024, longitude: -42.510480210185}, + {latitude: -22.241159694484, longitude: -42.517046257854}, + {latitude: -22.237336361699, longitude: -42.531315609813}, + {latitude: -22.227633565887, longitude: -42.534770295024} + ] + }, + { id: 3, + clickable: true, + draggable: false, + editable: false, + visible: true, + geodesic: true, + stroke: {weight: 1, color: "red", opacity: 1}, + fill: {color: "#0A67A3", opacity: "0.3"}, + path: [ + {latitude: -22.912122112782, longitude: -43.233883213252}, + {latitude: -22.912658229953, longitude: -43.233333360404}, + {latitude: -22.913135051277, longitude: -43.232568930835}, + {latitude: -22.914049160006, longitude: -43.231040071696}, + {latitude: -22.915007732268, longitude: -43.229344915599}, + {latitude: -22.915096671619, longitude: -43.229065965861}, + {latitude: -22.914958321493, longitude: -43.228862117976}, + {latitude: -22.91306587523, longitude: -43.227215241641}, + {latitude: -22.911054813301, longitude: -43.225868772715}, + {latitude: -22.910516219169, longitude: -43.230718206614}, + {latitude: -22.910317334098, longitude: -43.231544326991}, + {latitude: -22.910335246119, longitude: -43.231946658343}, + {latitude: -22.910432218057, longitude: -43.2324991934}, + {latitude: -22.9106644563, longitude: -43.233437966555}, + {latitude: -22.910508807309, longitude: -43.233000766486}, + {latitude: -22.9113932865, longitude: -43.2337786071} + ] + } + ]; + + setTimeout(function () { + $scope.map.polygons = $scope.polys; + console.log('plota poly'); + $scope.$apply(); + }, 3600); + }]); \ No newline at end of file diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-382-window-windows-ng-show.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-382-window-windows-ng-show.js new file mode 100644 index 0000000..6bdc402 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-382-window-windows-ng-show.js @@ -0,0 +1,37 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + $scope.map = { + center: { + latitude: 51.219053, + longitude: 4.404418 + }, + zoom: 15 + }; + + $scope.markersClick = function (m) { + + }; + $scope.map.markers = [ + { + longitude: 4.404418, + latitude: 51.219053 + } + ]; +}) +.controller('winCtrl', ['$rootScope', '$scope', '$location', '$http', + function ($rootScope, $scope, $location, $http) { + $scope.showSpan = true; + }]); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-393-cluster-events-mapped.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-393-cluster-events-mapped.js new file mode 100644 index 0000000..e4f72e1 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-393-cluster-events-mapped.js @@ -0,0 +1,146 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + markers: [ + { + id: 1, + latitude: 45, + longitude: -74, + showWindow: false, + title: 'Marker 2' + }, + { + id: 2, + latitude: 15, + longitude: 30, + showWindow: false, + title: 'Marker 2' + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 37, + longitude: -122, + showWindow: false, + title: 'Plane' + } + ], + markers2: [ + { + id: 1, + latitude: 46, + longitude: -77, + title: '[46,-77]' + }, + { + id: 2, + latitude: 48, + longitude: -79, + title: '[33,-77]' + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 47, + longitude: -78, + title: '[35,-125]' + } + ], + dynamicMarkers: [], + refresh: function () { + $scope.map.control.refresh(origCenter); + } + } + }); + + $scope.clusterEvents = { + click: function (cluster, clusterModels) { + alert("Cluster Models: clusterModels: " + JSON.stringify(clusterModels)); + } + }; + + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + }; + + var markerToClose = null; + + $scope.onMarkerClicked = function (marker) { +// if (markerToClose) { +// markerToClose.showWindow = false; +// } + markerToClose = marker; // for next go around + marker.showWindow = true; + $scope.$apply(); + //window.alert("Marker: lat: " + marker.latitude + ", lon: " + marker.longitude + " clicked!!") + }; + + $scope.onInsideWindowClick = function () { + alert("Window hit!"); + }; + + $timeout(function () { + var dynamicMarkers = [ + { id: 1, + latitude: 46, + longitude: -79 + }, + { + id: 2, + latitude: 33, + longitude: -79 + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127 + } + ]; + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +}); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-432-markers-fit-clustering.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-432-markers-fit-clustering.js new file mode 100644 index 0000000..a01330a --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-432-markers-fit-clustering.js @@ -0,0 +1,152 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + markers: [ + { + id: 1, + latitude: 45, + longitude: -74, + showWindow: false, + title: 'Marker 2' + }, + { + id: 2, + latitude: 15, + longitude: 30, + showWindow: false, + title: 'Marker 2' + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 37, + longitude: -122, + showWindow: false, + title: 'Plane' + } + ], + markers2: [ + { + id: 1, + latitude: 46, + longitude: -77, + title: '[46,-77]' + }, + { + id: 2, + latitude: 48, + longitude: -79, + title: '[33,-77]' + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 47, + longitude: -78, + title: '[35,-125]' + } + ], + dynamicMarkers: [], + refresh: function () { + $scope.map.control.refresh(origCenter); + } + } + }); + + $scope.clusterEvents = { + click: function (cluster, clusterModels) { + alert("Cluster Models: clusterModels: " + JSON.stringify(clusterModels)); + } + }; + + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + }; + + var markerToClose = null; + + $scope.onMarkerClicked = function (marker) { +// if (markerToClose) { +// markerToClose.showWindow = false; +// } + markerToClose = marker; // for next go around + marker.showWindow = true; + $scope.$apply(); + //window.alert("Marker: lat: " + marker.latitude + ", lon: " + marker.longitude + " clicked!!") + }; + + $scope.onInsideWindowClick = function () { + alert("Window hit!"); + }; + + $timeout(function () { + var dynamicMarkers = [ + { id: 1, + latitude: 46, + longitude: -79 + }, + { + id: 2, + latitude: 33, + longitude: -79 + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127 + }, + { + id: 4, + icon: 'assets/images/plane.png', + latitude: 35.2, + longitude: -127.1 + } + ]; + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +}); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-482-markers-animation-clustering.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-482-markers-animation-clustering.js new file mode 100644 index 0000000..06478c2 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-482-markers-animation-clustering.js @@ -0,0 +1,105 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + dynamicMarkers: [], + refresh: function () { + $scope.map.control.refresh(origCenter); + } + } + }); + + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + }; + +// var oldMarker = null; + $scope.onMarkerClicked = function (marker) { +// if(oldMarker){ +//// oldMarker.options = {animation:google.maps.Animation.DROP}; // or 2 +// oldMarker.options = {animation:0}; //or null +// } + marker.showWindow = false; + if(marker.options) + marker.options = null; + else + marker.options = {animation:google.maps.Animation.BOUNCE}; //or 1 +// oldMarker = marker; + $scope.$apply(); + }; + + $scope.onInsideWindowClick = function () { + alert("Window hit!"); + }; + + $timeout(function () { + var dynamicMarkers = [ + { id: 1, + latitude: 46, + longitude: -79 + }, + { + id: 2, + latitude: 33, + longitude: -79 + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127 + }, + { + id: 4, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -128, + title: '[35,-125]' + } + ]; + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +}); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-482-markers-animation.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-482-markers-animation.js new file mode 100644 index 0000000..2f961d0 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-482-markers-animation.js @@ -0,0 +1,143 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + markers: [ + { + id: 1, + latitude: 45, + longitude: -74, + showWindow: false, + title: 'Marker 2' + }, + { + id: 2, + latitude: 15, + longitude: 30, + showWindow: false, + title: 'Marker 2' + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 37, + longitude: -122, + showWindow: false, + title: 'Plane' + } + ], + markers2: [ + { + id: 1, + latitude: 46, + longitude: -77, + title: '[46,-77]' + }, + { + id: 2, + latitude: 48, + longitude: -79, + title: '[33,-77]' + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 47, + longitude: -78, + title: '[35,-125]' + } + ], + dynamicMarkers: [], + refresh: function () { + $scope.map.control.refresh(origCenter); + } + } + }); + + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + }; + +// var oldMarker = null; + $scope.onMarkerClicked = function (marker) { +// if(oldMarker){ +//// oldMarker.options = {animation:google.maps.Animation.DROP}; // or 2 +// oldMarker.options = {animation:0}; //or null +// } + marker.showWindow = false; + if(marker.options) + marker.options = null; + else + marker.options = {animation:google.maps.Animation.BOUNCE}; //or 1 +// oldMarker = marker; + $scope.$apply(); + }; + + $scope.onInsideWindowClick = function () { + alert("Window hit!"); + }; + + $timeout(function () { + var dynamicMarkers = [ + { id: 1, + latitude: 46, + longitude: -79 + }, + { + id: 2, + latitude: 33, + longitude: -79 + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127 + } + ]; + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +}); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-484-markers-self.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-484-markers-self.js new file mode 100644 index 0000000..1d741e1 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-484-markers-self.js @@ -0,0 +1,109 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + + markers: [ + { + division: "US", + type: "US", + // coords:{ + latitude: 41.169444444444444, + longitude: -75.87777777777778, + driverCode: "GRIP", + trailer: " 5555", + status: "D", + orderNumber: "9999999", + destinationCityCode: "MT", + destinationStateCode: "PA", + contactCityCode: "PICK", + contactStateCode: "ON", + icon: undefined, + id: "7" + }, + { + division: "US", + type: "US", + // coords:{ + latitude: 43.169444444444444, + longitude: -75.87777777777778, + driverCode: "GRIP", + trailer: " 5555", + status: "D", + orderNumber: "9999999", + destinationCityCode: "MT", + destinationStateCode: "PA", + contactCityCode: "PICK", + contactStateCode: "ON", + icon: undefined, + id: "10" + } + ] + , + dynamicMarkers: [], + refresh: function () { + $scope.map.control.refresh(origCenter); + } + } + }); + + $log.info("Markers: " + $scope.map.markers); + $timeout(function () { + var dynamicMarkers = [ + { id: 1, + latitude: 46, + longitude: -79 + }, + { + id: 2, + latitude: 33, + longitude: -79 + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127 + } + ]; + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +}); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-495-polyline-fit.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-495-polyline-fit.js new file mode 100644 index 0000000..62293d7 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-495-polyline-fit.js @@ -0,0 +1,71 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1); +}).controller("controller", ['$rootScope', '$scope', '$location', '$http', function ($rootScope, $scope, $location, $http) { + $scope.map = { + // http://angular-google-maps.org/use + center: { + latitude: 17.38504400000000, + longitude: 78.48667100000000 + }, + zoom: 9, + lineStyle: { + color: '#333', + weight: 5, + opacity: 0.7 + } + }; + $scope.locations = [ + { + "createdAt": { + "date": "2014-06-13 15:00:33", + "timezone_type": 3, + "timezone": "Asia\/Kolkata" + }, + "source": "121", + "source_type": "entry_created", + "latitude": 17.485044, + "longitude": 79.386671, + "location": "Unknown", + "title": "1) Created entry on: One AAA at 2014-06-13 15:00:33 (Unknown)" + }, + { + "createdAt": { + "date": "2014-06-14 08:21:05", + "timezone_type": 3, + "timezone": "Asia\/Kolkata" + }, + "source": null, + "source_type": "login", + "latitude": 17.685044, + "longitude": 78.586671, + "location": "Unknown", + "title": "2) Login at 2014-06-14 08:21:05 (Unknown)" + }, + { + "createdAt": { + "date": "2014-06-14 08:30:30", + "timezone_type": 3, + "timezone": "Asia\/Kolkata" + }, + "source": null, + "source_type": "logout", + "latitude": 18.385044, + "longitude": 78.986671, + "location": "Unknown", + "title": "3) Logout at 2014-06-14 08:30:30 (Unknown)" + }, + { + "createdAt": { + "date": "2014-06-15 00:16:36", + "timezone_type": 3, + "timezone": "Asia\/Kolkata" + }, + "source": null, + "source_type": "login", + "latitude": 17.885044, + "longitude": 78.986671, + "location": "Unknown", + "title": "4) Login at 2014-06-15 00:16:36 (Unknown)" + } + ]; +}]); \ No newline at end of file diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-498-marker-move-loaded.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-498-marker-move-loaded.js new file mode 100644 index 0000000..59455e1 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-498-marker-move-loaded.js @@ -0,0 +1,58 @@ +angular.module('testApp', ['google-maps']).controller('TestController', ['$scope', function ($scope) { + $scope.map = { + center: { + latitude: 45, + longitude: -73 + }, + zoom: 3, + events: { + tilesloaded: function (map, eventName, originalEventArgs) { + //map is trueley ready then this callback is hit + }, + click: function (mapModel, eventName, originalEventArgs) { + var e = originalEventArgs[0]; + var lat = e.latLng.lat(), + lon = e.latLng.lng(); + $scope.map.clickedMarker = { + title: 'You clicked here ' + 'lat: ' + lat + ' lon: ' + lon, + latitude: lat, + longitude: lon + }; + //scope apply required because this event handler is outside of the angular domain + $scope.$apply(); + } + }, + markers: [ + { + id: 1, + latitude: 45, + longitude: -74, + showWindow: false, + title: 'Markers: 1' + }, + { + id: 2, + latitude: 15, + longitude: 30, + showWindow: false, + title: 'Markers: 2' + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 37, + longitude: -122, + showWindow: false, + title: 'Markers: 3' + } + ], + clickedMarker: { + title: '' + }, + onMarkerClicked: function (marker) { + marker.showWindow = true; + $scope.$apply(); + //window.alert("Marker: lat: " + marker.latitude + ", lon: " + marker.longitude + " clicked!!") + } + }; +}]); \ No newline at end of file diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-504-markers-flash.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-504-markers-flash.js new file mode 100644 index 0000000..e363f46 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-504-markers-flash.js @@ -0,0 +1,27 @@ +angular.module('testApp', ['google-maps']).controller('TestController', ['$scope', function ($scope) { + $scope.map = { + center: { + latitude: 45, + longitude: -73 + }, + zoom: 3 + }; + var ICONS = { + test: 'https://upload.wikimedia.org/wikipedia/commons/thumb/7/7c/Go-home.svg/48px-Go-home.svg.png', + test1: 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Go-up.svg/48px-Go-up.svg.png' + }; + $scope.sites = [ + {coords: {longitude: 25, latitude: -25}, site_id: 0, icon: ICONS['test']}, + {coords: {longitude: 25, latitude: 25}, site_id: 1, icon: ICONS['test']}, + {coords: {longitude: -25, latitude: -25}, site_id: 2, icon: ICONS['test']}, + {coords: {longitude: -25, latitude: 25}, site_id: 3, icon: ICONS['test']} + ].map(function (site) { + site.click = function () { + site.icon = ICONS['test1']; + $scope.$apply(); + }; + return site; + }); + + +}]); \ No newline at end of file diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-507-windows-show-w-marker-broken.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-507-windows-show-w-marker-broken.js new file mode 100644 index 0000000..33ca972 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-507-windows-show-w-marker-broken.js @@ -0,0 +1,43 @@ +angular.module('appMaps', ['google-maps']) + .controller('mainCtrl', function($scope) { + $scope.map = {center: {latitude: 40.1451, longitude: -99.6680 }, zoom: 4, bounds: {}}; + $scope.options = {scrollwheel: false}; + var createRandomMarker = function (i, bounds, idKey) { + var lat_min = bounds.southwest.latitude, + lat_range = bounds.northeast.latitude - lat_min, + lng_min = bounds.southwest.longitude, + lng_range = bounds.northeast.longitude - lng_min; + + if (idKey == null) { + idKey = "id"; + } + + var latitude = lat_min + (Math.random() * lat_range); + var longitude = lng_min + (Math.random() * lng_range); + var ret = { + latitude: latitude, + longitude: longitude, + title: 'm' + i, + show: false + }; + ret.onClick = function() { + console.log("Clicked!"); + ret.show = true; + $scope.$apply(); + }; + ret[idKey] = i; + return ret; + }; + $scope.randomMarkers = []; + // Get the bounds from the map once it's loaded + $scope.$watch(function() { return $scope.map.bounds; }, function(nv, ov) { + // Only need to regenerate once + if (!ov.southwest && nv.southwest) { + var markers = []; + for (var i = 0; i < 50; i++) { + markers.push(createRandomMarker(i, $scope.map.bounds)) + } + $scope.randomMarkers = markers; + } + }, true); + }); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-534-markers-child-destroy.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-534-markers-child-destroy.js new file mode 100644 index 0000000..4f1db9e --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-534-markers-child-destroy.js @@ -0,0 +1,108 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + markers: [ + { + id: 1, + latitude: 45, + longitude: -74, + showWindow: false, + title: 'Marker 2' + }, + { + id: 2, + latitude: 15, + longitude: 30, + showWindow: false, + title: 'Marker 2' + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 37, + longitude: -122, + showWindow: false, + title: 'Plane' + } + ], + dynamicMarkers: [], + refresh: function () { + $scope.map.control.refresh(origCenter); + } + } + }); + + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + }; + +// var oldMarker = null; + $scope.onMarkerClicked = function (marker) { + $scope.$apply(); + }; + + $scope.onInsideWindowClick = function () { + alert("Window hit!"); + }; + + $timeout(function () { + var markers = [ + { id: 1, + latitude: 46, + longitude: -79 + }, + { + id: 2, + latitude: 33, + longitude: -79 + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127 + } + ]; + _.each(markers, function (marker) { + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + $scope.map.markers = markers; + }, 2000); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +}); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-74-markers-events.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-74-markers-events.js new file mode 100644 index 0000000..57e8e74 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/issue-74-markers-events.js @@ -0,0 +1,149 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", function ($scope, $timeout, $log, $http, rndAddToLatLon) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + markers: [ + { + id: 1, + latitude: 45, + longitude: -74, + showWindow: false, + title: 'Marker 2' + }, + { + id: 2, + latitude: 15, + longitude: 30, + showWindow: false, + title: 'Marker 2' + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 37, + longitude: -122, + showWindow: false, + title: 'Plane' + } + ], + markers2: [ + { + id: 1, + latitude: 46, + longitude: -77, + title: '[46,-77]' + }, + { + id: 2, + latitude: 48, + longitude: -79, + title: '[33,-77]' + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 47, + longitude: -78, + title: '[35,-125]' + } + ], + dynamicMarkers: [], + refresh: function () { + $scope.map.control.refresh(origCenter); + } + } + }); + + $scope.markersEvents = { + click: function (gMarker, eventName, model) { + if(model.$id){ + model = model.coords;//use scope portion then + } + alert("Model: event:" + eventName + " " + JSON.stringify(model)); + } + }; + + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + }; + + var markerToClose = null; + + $scope.onMarkerClicked = function (marker) { +// if (markerToClose) { +// markerToClose.showWindow = false; +// } + markerToClose = marker; // for next go around + marker.showWindow = true; + $scope.$apply(); + //window.alert("Marker: lat: " + marker.latitude + ", lon: " + marker.longitude + " clicked!!") + }; + + $scope.onInsideWindowClick = function () { + alert("Window hit!"); + }; + + $timeout(function () { + var dynamicMarkers = [ + { id: 1, + latitude: 46, + longitude: -79 + }, + { + id: 2, + latitude: 33, + longitude: -79 + }, + { + id: 3, + icon: 'assets/images/plane.png', + latitude: 35, + longitude: -127 + } + ]; + _.each(dynamicMarkers, function (marker) { + marker.closeClick = function () { + marker.showWindow = false; + $scope.$apply(); + }; + marker.onClicked = function () { + $scope.onMarkerClicked(marker); + }; + }); + $scope.map.dynamicMarkers = dynamicMarkers; + }, 2000); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +}); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/shapes-lines.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/shapes-lines.js new file mode 100644 index 0000000..4221a1f --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/shapes-lines.js @@ -0,0 +1,262 @@ +angular.module("angular-google-maps-example", ["google-maps"]).value("rndAddToLatLon",function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +}).controller("controller", + function ($scope, $timeout, $log, $http, rndAddToLatLon, Logger) { + Logger.doLog = true + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + var versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + angular.extend($scope, { + example2: { + doRebuildAll: false + }, + clickWindow: function () { + $log.info('CLICK CLICK'); + Logger.info('CLICK CLICK'); + }, + map: { + control: {}, + version: "uknown", + + center: { + latitude: 45, + longitude: -73 + }, + options: { + streetViewControl: false, + panControl: false, + maxZoom: 20, + minZoom: 3 + }, + zoom: 3, + dragging: false, + bounds: {}, + + + events: { + tilesloaded: function (map, eventName, originalEventArgs) { + } + }, + circles: [ + { + id: 1, + center: { + latitude: 44, + longitude: -108 + }, + radius: 500000, + stroke: { + color: '#08B21F', + weight: 2, + opacity: 1 + }, + fill: { + color: '#08B21F', + opacity: 0.5 + }, + geodesic: true, // optional: defaults to false + draggable: true, // optional: defaults to false + clickable: true, // optional: defaults to true + editable: true, // optional: defaults to false + visible: true // optional: defaults to true + } + ], + polygons: [ + { + id: 1, + path: [ + { + latitude: 50, + longitude: -80 + }, + { + latitude: 30, + longitude: -120 + }, + { + latitude: 20, + longitude: -95 + } + ], + stroke: { + color: '#6060FB', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: false, + visible: true, + fill: { + color: '#ff0000', + opacity: 0.8 + } + }, + { + id: 2, + path: [ + { + latitude: 60, + longitude: -80 + }, + { + latitude: 40, + longitude: -120 + }, + { + latitude: 45, + longitude: -95 + } + ], + stroke: { + color: '#33CDDC', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: false, + visible: true, + fill: { + color: '#33CCCC', + opacity: 0.8 + } + } + ], + polylines: [ + { + id: 1, + path: [ + { + latitude: 45, + longitude: -74 + }, + { + latitude: 30, + longitude: -89 + }, + { + latitude: 37, + longitude: -122 + }, + { + latitude: 60, + longitude: -95 + } + ], + stroke: { + color: '#6060FB', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: true, + visible: true + }, + { + id: 2, + path: [ + { + latitude: 47, + longitude: -74 + }, + { + latitude: 32, + longitude: -89 + }, + { + latitude: 39, + longitude: -122 + }, + { + latitude: 62, + longitude: -95 + } + ], + stroke: { + color: '#6060FB', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: true, + visible: true + } + ] + }, + toggleColor: function (color) { + return color == 'red' ? '#6060FB' : 'red'; + } + + }); + + + $scope.refreshMap = function () { + //optional param if you want to refresh you can pass null undefined or false or empty arg + $scope.map.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map.control.getGMap().setZoom(11); + return; + }; + $scope.getMapInstance = function () { + alert("You have Map Instance of" + $scope.map.control.getGMap().toString()); + return; + } + + $timeout(function () { + + //go nuts // PLEASE KEEP THIS AS IT TESTS POLYLINES SPEED! +// var lastPolyline = $scope.map.polylines[1]; +// $scope.map.polylines = _.map(_.range(500), function (i) { +// var newPath = _.map(lastPolyline.path, function (p) { +// return { +// latitude: p.latitude, +// longitude: p.longitude + 1 +// }; +// }); +// var newPoly = _.clone(lastPolyline); +// newPoly.id = i + 1; +// newPoly.path = newPath; +// lastPolyline = newPoly; +// return newPoly; +// }); + + $scope.map.polylines.push({ + id: 3, + path: [ + { + latitude: 65, + longitude: -74 + }, + { + latitude: 50, + longitude: -89 + }, + { + latitude: 57, + longitude: -122 + }, + { + latitude: 20, + longitude: -95 + } + ], + stroke: { + color: '#FF0066', + weight: 3 + }, + editable: true, + draggable: true, + geodesic: true, + visible: true + }); + + }, 2000); + } +); diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/templates/info.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/templates/info.js new file mode 100644 index 0000000..eba1a29 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/templates/info.js @@ -0,0 +1,9 @@ +function InfoController($scope, $log) { + $scope.templateValue = 'hello from the template itself'; + $scope.clickedButtonInWindow = function () { + var msg = 'clicked a window in the template!'; + $log.info(msg); + alert(msg); + }; +}; + diff --git a/bower_components/angular-google-maps/example/assets/scripts/controllers/two-maps.js b/bower_components/angular-google-maps/example/assets/scripts/controllers/two-maps.js new file mode 100644 index 0000000..2c3731a --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/controllers/two-maps.js @@ -0,0 +1,115 @@ +(function () { + var module = angular.module("angular-google-maps-example", ["google-maps"]); +}()); + +var rndAddToLatLon = function () { + return Math.floor(((Math.random() < 0.5 ? -1 : 1) * 2) + 1) +} + +function DebugController($scope, $timeout, $log, $http) { + // Enable the new Google Maps visuals until it gets enabled by default. + // See http://googlegeodevelopers.blogspot.ca/2013/05/a-fresh-new-look-for-maps-api-for-all.html + google.maps.visualRefresh = true; + + versionUrl = window.location.host === "rawgithub.com" ? "http://rawgithub.com/nlaplante/angular-google-maps/master/package.json" : "/package.json"; + + $http.get(versionUrl).success(function (data) { + if (!data) + console.error("no version object found!!"); + $scope.version = data.version; + }); + + + angular.extend($scope, { + map: { + control: {}, + center: { + latitude: 45, + longitude: -74 + }, + marker: { + latitude: 45, + longitude: -74, + options: { + visible: false + } + }, + marker2: { + latitude: 45.2, + longitude: -74.5 + }, +// dragging:false, //appears to be required + zoom: 7, + options: { + disableDefaultUI: true, + panControl: false, + navigationControl: false, + scrollwheel: false, + scaleControl: false + }, + refresh: function () { + $scope.map.control.refresh(origCenter); + } + }, + map2: { + control: {}, + center: { + latitude: 52.2, + longitude: -80 + }, + showMap:true, + marker: { + latitude: 70, + longitude: -76, + options: { + visible: true + } + }, + marker2: { + latitude: 50.2, + longitude: -80.5 + }, + zoom: 4, + refresh: function () { + $scope.map2.control.refresh({latitude: 32.779680, longitude: -79.935493}); + $scope.map2.showMap = false; + _.defer(function(){ + $scope.map2.showMap = true; + }); + }, + markers3: [ + { + id: 1, + time: "12:00PM", + coords: { + latitude: 52.2, + longitude: -80.5 + }, + icon: "assets/images/plane.png", + lastSignal: "Never", + click:function(){ + this.show = true; + this.lastSignal = Math.round(Date.now()).toString(); + $scope.apply(); + }, + closeClick:function(){ + this.showWindow = false; + }, + show:false + } + ], + onMarkerClick: function (m) { + m.lastSignal = Math.round(Date.now()).toString(); + m.show = true; + $scope.map2.markers3.length = 0; + // _defer to allow the directive to clean up all old markers and windows + // since we know it is only the one marker just wipe and replace + // otherwise find it in the array or have a hashmap to array index to get it quick + _.defer(function(){$scope.map2.markers3 = [m];$scope.$apply();}); + + } + } + }); + + var origCenter = {latitude: $scope.map.center.latitude, longitude: $scope.map.center.longitude}; +} diff --git a/bower_components/angular-google-maps/example/assets/scripts/mocks/heat-layer.js b/bower_components/angular-google-maps/example/assets/scripts/mocks/heat-layer.js new file mode 100644 index 0000000..33aad56 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/scripts/mocks/heat-layer.js @@ -0,0 +1,541 @@ +//direct copy from here https://developers.google.com/maps/documentation/javascript/examples/layer-heatmap +//this function is only used to set the heatLayers data +MockHeatLayer = function (heatLayer) { + // Adding 500 Data Points + var map, pointarray, heatmap; + + var taxiData = [ + new google.maps.LatLng(37.782551, -122.445368), + new google.maps.LatLng(37.782745, -122.444586), + new google.maps.LatLng(37.782842, -122.443688), + new google.maps.LatLng(37.782919, -122.442815), + new google.maps.LatLng(37.782992, -122.442112), + new google.maps.LatLng(37.783100, -122.441461), + new google.maps.LatLng(37.783206, -122.440829), + new google.maps.LatLng(37.783273, -122.440324), + new google.maps.LatLng(37.783316, -122.440023), + new google.maps.LatLng(37.783357, -122.439794), + new google.maps.LatLng(37.783371, -122.439687), + new google.maps.LatLng(37.783368, -122.439666), + new google.maps.LatLng(37.783383, -122.439594), + new google.maps.LatLng(37.783508, -122.439525), + new google.maps.LatLng(37.783842, -122.439591), + new google.maps.LatLng(37.784147, -122.439668), + new google.maps.LatLng(37.784206, -122.439686), + new google.maps.LatLng(37.784386, -122.439790), + new google.maps.LatLng(37.784701, -122.439902), + new google.maps.LatLng(37.784965, -122.439938), + new google.maps.LatLng(37.785010, -122.439947), + new google.maps.LatLng(37.785360, -122.439952), + new google.maps.LatLng(37.785715, -122.440030), + new google.maps.LatLng(37.786117, -122.440119), + new google.maps.LatLng(37.786564, -122.440209), + new google.maps.LatLng(37.786905, -122.440270), + new google.maps.LatLng(37.786956, -122.440279), + new google.maps.LatLng(37.800224, -122.433520), + new google.maps.LatLng(37.800155, -122.434101), + new google.maps.LatLng(37.800160, -122.434430), + new google.maps.LatLng(37.800378, -122.434527), + new google.maps.LatLng(37.800738, -122.434598), + new google.maps.LatLng(37.800938, -122.434650), + new google.maps.LatLng(37.801024, -122.434889), + new google.maps.LatLng(37.800955, -122.435392), + new google.maps.LatLng(37.800886, -122.435959), + new google.maps.LatLng(37.800811, -122.436275), + new google.maps.LatLng(37.800788, -122.436299), + new google.maps.LatLng(37.800719, -122.436302), + new google.maps.LatLng(37.800702, -122.436298), + new google.maps.LatLng(37.800661, -122.436273), + new google.maps.LatLng(37.800395, -122.436172), + new google.maps.LatLng(37.800228, -122.436116), + new google.maps.LatLng(37.800169, -122.436130), + new google.maps.LatLng(37.800066, -122.436167), + new google.maps.LatLng(37.784345, -122.422922), + new google.maps.LatLng(37.784389, -122.422926), + new google.maps.LatLng(37.784437, -122.422924), + new google.maps.LatLng(37.784746, -122.422818), + new google.maps.LatLng(37.785436, -122.422959), + new google.maps.LatLng(37.786120, -122.423112), + new google.maps.LatLng(37.786433, -122.423029), + new google.maps.LatLng(37.786631, -122.421213), + new google.maps.LatLng(37.786660, -122.421033), + new google.maps.LatLng(37.786801, -122.420141), + new google.maps.LatLng(37.786823, -122.420034), + new google.maps.LatLng(37.786831, -122.419916), + new google.maps.LatLng(37.787034, -122.418208), + new google.maps.LatLng(37.787056, -122.418034), + new google.maps.LatLng(37.787169, -122.417145), + new google.maps.LatLng(37.787217, -122.416715), + new google.maps.LatLng(37.786144, -122.416403), + new google.maps.LatLng(37.785292, -122.416257), + new google.maps.LatLng(37.780666, -122.390374), + new google.maps.LatLng(37.780501, -122.391281), + new google.maps.LatLng(37.780148, -122.392052), + new google.maps.LatLng(37.780173, -122.391148), + new google.maps.LatLng(37.780693, -122.390592), + new google.maps.LatLng(37.781261, -122.391142), + new google.maps.LatLng(37.781808, -122.391730), + new google.maps.LatLng(37.782340, -122.392341), + new google.maps.LatLng(37.782812, -122.393022), + new google.maps.LatLng(37.783300, -122.393672), + new google.maps.LatLng(37.783809, -122.394275), + new google.maps.LatLng(37.784246, -122.394979), + new google.maps.LatLng(37.784791, -122.395958), + new google.maps.LatLng(37.785675, -122.396746), + new google.maps.LatLng(37.786262, -122.395780), + new google.maps.LatLng(37.786776, -122.395093), + new google.maps.LatLng(37.787282, -122.394426), + new google.maps.LatLng(37.787783, -122.393767), + new google.maps.LatLng(37.788343, -122.393184), + new google.maps.LatLng(37.788895, -122.392506), + new google.maps.LatLng(37.789371, -122.391701), + new google.maps.LatLng(37.789722, -122.390952), + new google.maps.LatLng(37.790315, -122.390305), + new google.maps.LatLng(37.790738, -122.389616), + new google.maps.LatLng(37.779448, -122.438702), + new google.maps.LatLng(37.779023, -122.438585), + new google.maps.LatLng(37.778542, -122.438492), + new google.maps.LatLng(37.778100, -122.438411), + new google.maps.LatLng(37.777986, -122.438376), + new google.maps.LatLng(37.777680, -122.438313), + new google.maps.LatLng(37.777316, -122.438273), + new google.maps.LatLng(37.777135, -122.438254), + new google.maps.LatLng(37.776987, -122.438303), + new google.maps.LatLng(37.776946, -122.438404), + new google.maps.LatLng(37.776944, -122.438467), + new google.maps.LatLng(37.776892, -122.438459), + new google.maps.LatLng(37.776842, -122.438442), + new google.maps.LatLng(37.776822, -122.438391), + new google.maps.LatLng(37.776814, -122.438412), + new google.maps.LatLng(37.776787, -122.438628), + new google.maps.LatLng(37.776729, -122.438650), + new google.maps.LatLng(37.776759, -122.438677), + new google.maps.LatLng(37.776772, -122.438498), + new google.maps.LatLng(37.776787, -122.438389), + new google.maps.LatLng(37.776848, -122.438283), + new google.maps.LatLng(37.776870, -122.438239), + new google.maps.LatLng(37.777015, -122.438198), + new google.maps.LatLng(37.777333, -122.438256), + new google.maps.LatLng(37.777595, -122.438308), + new google.maps.LatLng(37.777797, -122.438344), + new google.maps.LatLng(37.778160, -122.438442), + new google.maps.LatLng(37.778414, -122.438508), + new google.maps.LatLng(37.778445, -122.438516), + new google.maps.LatLng(37.778503, -122.438529), + new google.maps.LatLng(37.778607, -122.438549), + new google.maps.LatLng(37.778670, -122.438644), + new google.maps.LatLng(37.778847, -122.438706), + new google.maps.LatLng(37.779240, -122.438744), + new google.maps.LatLng(37.779738, -122.438822), + new google.maps.LatLng(37.780201, -122.438882), + new google.maps.LatLng(37.780400, -122.438905), + new google.maps.LatLng(37.780501, -122.438921), + new google.maps.LatLng(37.780892, -122.438986), + new google.maps.LatLng(37.781446, -122.439087), + new google.maps.LatLng(37.781985, -122.439199), + new google.maps.LatLng(37.782239, -122.439249), + new google.maps.LatLng(37.782286, -122.439266), + new google.maps.LatLng(37.797847, -122.429388), + new google.maps.LatLng(37.797874, -122.429180), + new google.maps.LatLng(37.797885, -122.429069), + new google.maps.LatLng(37.797887, -122.429050), + new google.maps.LatLng(37.797933, -122.428954), + new google.maps.LatLng(37.798242, -122.428990), + new google.maps.LatLng(37.798617, -122.429075), + new google.maps.LatLng(37.798719, -122.429092), + new google.maps.LatLng(37.798944, -122.429145), + new google.maps.LatLng(37.799320, -122.429251), + new google.maps.LatLng(37.799590, -122.429309), + new google.maps.LatLng(37.799677, -122.429324), + new google.maps.LatLng(37.799966, -122.429360), + new google.maps.LatLng(37.800288, -122.429430), + new google.maps.LatLng(37.800443, -122.429461), + new google.maps.LatLng(37.800465, -122.429474), + new google.maps.LatLng(37.800644, -122.429540), + new google.maps.LatLng(37.800948, -122.429620), + new google.maps.LatLng(37.801242, -122.429685), + new google.maps.LatLng(37.801375, -122.429702), + new google.maps.LatLng(37.801400, -122.429703), + new google.maps.LatLng(37.801453, -122.429707), + new google.maps.LatLng(37.801473, -122.429709), + new google.maps.LatLng(37.801532, -122.429707), + new google.maps.LatLng(37.801852, -122.429729), + new google.maps.LatLng(37.802173, -122.429789), + new google.maps.LatLng(37.802459, -122.429847), + new google.maps.LatLng(37.802554, -122.429825), + new google.maps.LatLng(37.802647, -122.429549), + new google.maps.LatLng(37.802693, -122.429179), + new google.maps.LatLng(37.802729, -122.428751), + new google.maps.LatLng(37.766104, -122.409291), + new google.maps.LatLng(37.766103, -122.409268), + new google.maps.LatLng(37.766138, -122.409229), + new google.maps.LatLng(37.766183, -122.409231), + new google.maps.LatLng(37.766153, -122.409276), + new google.maps.LatLng(37.766005, -122.409365), + new google.maps.LatLng(37.765897, -122.409570), + new google.maps.LatLng(37.765767, -122.409739), + new google.maps.LatLng(37.765693, -122.410389), + new google.maps.LatLng(37.765615, -122.411201), + new google.maps.LatLng(37.765533, -122.412121), + new google.maps.LatLng(37.765467, -122.412939), + new google.maps.LatLng(37.765444, -122.414821), + new google.maps.LatLng(37.765444, -122.414964), + new google.maps.LatLng(37.765318, -122.415424), + new google.maps.LatLng(37.763961, -122.415296), + new google.maps.LatLng(37.763115, -122.415196), + new google.maps.LatLng(37.762967, -122.415183), + new google.maps.LatLng(37.762278, -122.415127), + new google.maps.LatLng(37.761675, -122.415055), + new google.maps.LatLng(37.760932, -122.414988), + new google.maps.LatLng(37.759337, -122.414862), + new google.maps.LatLng(37.773187, -122.421922), + new google.maps.LatLng(37.773043, -122.422118), + new google.maps.LatLng(37.773007, -122.422165), + new google.maps.LatLng(37.772979, -122.422219), + new google.maps.LatLng(37.772865, -122.422394), + new google.maps.LatLng(37.772779, -122.422503), + new google.maps.LatLng(37.772676, -122.422701), + new google.maps.LatLng(37.772606, -122.422806), + new google.maps.LatLng(37.772566, -122.422840), + new google.maps.LatLng(37.772508, -122.422852), + new google.maps.LatLng(37.772387, -122.423011), + new google.maps.LatLng(37.772099, -122.423328), + new google.maps.LatLng(37.771704, -122.423783), + new google.maps.LatLng(37.771481, -122.424081), + new google.maps.LatLng(37.771400, -122.424179), + new google.maps.LatLng(37.771352, -122.424220), + new google.maps.LatLng(37.771248, -122.424327), + new google.maps.LatLng(37.770904, -122.424781), + new google.maps.LatLng(37.770520, -122.425283), + new google.maps.LatLng(37.770337, -122.425553), + new google.maps.LatLng(37.770128, -122.425832), + new google.maps.LatLng(37.769756, -122.426331), + new google.maps.LatLng(37.769300, -122.426902), + new google.maps.LatLng(37.769132, -122.427065), + new google.maps.LatLng(37.769092, -122.427103), + new google.maps.LatLng(37.768979, -122.427172), + new google.maps.LatLng(37.768595, -122.427634), + new google.maps.LatLng(37.768372, -122.427913), + new google.maps.LatLng(37.768337, -122.427961), + new google.maps.LatLng(37.768244, -122.428138), + new google.maps.LatLng(37.767942, -122.428581), + new google.maps.LatLng(37.767482, -122.429094), + new google.maps.LatLng(37.767031, -122.429606), + new google.maps.LatLng(37.766732, -122.429986), + new google.maps.LatLng(37.766680, -122.430058), + new google.maps.LatLng(37.766633, -122.430109), + new google.maps.LatLng(37.766580, -122.430211), + new google.maps.LatLng(37.766367, -122.430594), + new google.maps.LatLng(37.765910, -122.431137), + new google.maps.LatLng(37.765353, -122.431806), + new google.maps.LatLng(37.764962, -122.432298), + new google.maps.LatLng(37.764868, -122.432486), + new google.maps.LatLng(37.764518, -122.432913), + new google.maps.LatLng(37.763435, -122.434173), + new google.maps.LatLng(37.762847, -122.434953), + new google.maps.LatLng(37.762291, -122.435935), + new google.maps.LatLng(37.762224, -122.436074), + new google.maps.LatLng(37.761957, -122.436892), + new google.maps.LatLng(37.761652, -122.438886), + new google.maps.LatLng(37.761284, -122.439955), + new google.maps.LatLng(37.761210, -122.440068), + new google.maps.LatLng(37.761064, -122.440720), + new google.maps.LatLng(37.761040, -122.441411), + new google.maps.LatLng(37.761048, -122.442324), + new google.maps.LatLng(37.760851, -122.443118), + new google.maps.LatLng(37.759977, -122.444591), + new google.maps.LatLng(37.759913, -122.444698), + new google.maps.LatLng(37.759623, -122.445065), + new google.maps.LatLng(37.758902, -122.445158), + new google.maps.LatLng(37.758428, -122.444570), + new google.maps.LatLng(37.757687, -122.443340), + new google.maps.LatLng(37.757583, -122.443240), + new google.maps.LatLng(37.757019, -122.442787), + new google.maps.LatLng(37.756603, -122.442322), + new google.maps.LatLng(37.756380, -122.441602), + new google.maps.LatLng(37.755790, -122.441382), + new google.maps.LatLng(37.754493, -122.442133), + new google.maps.LatLng(37.754361, -122.442206), + new google.maps.LatLng(37.753719, -122.442650), + new google.maps.LatLng(37.753096, -122.442915), + new google.maps.LatLng(37.751617, -122.443211), + new google.maps.LatLng(37.751496, -122.443246), + new google.maps.LatLng(37.750733, -122.443428), + new google.maps.LatLng(37.750126, -122.443536), + new google.maps.LatLng(37.750103, -122.443784), + new google.maps.LatLng(37.750390, -122.444010), + new google.maps.LatLng(37.750448, -122.444013), + new google.maps.LatLng(37.750536, -122.444040), + new google.maps.LatLng(37.750493, -122.444141), + new google.maps.LatLng(37.790859, -122.402808), + new google.maps.LatLng(37.790864, -122.402768), + new google.maps.LatLng(37.790995, -122.402539), + new google.maps.LatLng(37.791148, -122.402172), + new google.maps.LatLng(37.791385, -122.401312), + new google.maps.LatLng(37.791405, -122.400776), + new google.maps.LatLng(37.791288, -122.400528), + new google.maps.LatLng(37.791113, -122.400441), + new google.maps.LatLng(37.791027, -122.400395), + new google.maps.LatLng(37.791094, -122.400311), + new google.maps.LatLng(37.791211, -122.400183), + new google.maps.LatLng(37.791060, -122.399334), + new google.maps.LatLng(37.790538, -122.398718), + new google.maps.LatLng(37.790095, -122.398086), + new google.maps.LatLng(37.789644, -122.397360), + new google.maps.LatLng(37.789254, -122.396844), + new google.maps.LatLng(37.788855, -122.396397), + new google.maps.LatLng(37.788483, -122.395963), + new google.maps.LatLng(37.788015, -122.395365), + new google.maps.LatLng(37.787558, -122.394735), + new google.maps.LatLng(37.787472, -122.394323), + new google.maps.LatLng(37.787630, -122.394025), + new google.maps.LatLng(37.787767, -122.393987), + new google.maps.LatLng(37.787486, -122.394452), + new google.maps.LatLng(37.786977, -122.395043), + new google.maps.LatLng(37.786583, -122.395552), + new google.maps.LatLng(37.786540, -122.395610), + new google.maps.LatLng(37.786516, -122.395659), + new google.maps.LatLng(37.786378, -122.395707), + new google.maps.LatLng(37.786044, -122.395362), + new google.maps.LatLng(37.785598, -122.394715), + new google.maps.LatLng(37.785321, -122.394361), + new google.maps.LatLng(37.785207, -122.394236), + new google.maps.LatLng(37.785751, -122.394062), + new google.maps.LatLng(37.785996, -122.393881), + new google.maps.LatLng(37.786092, -122.393830), + new google.maps.LatLng(37.785998, -122.393899), + new google.maps.LatLng(37.785114, -122.394365), + new google.maps.LatLng(37.785022, -122.394441), + new google.maps.LatLng(37.784823, -122.394635), + new google.maps.LatLng(37.784719, -122.394629), + new google.maps.LatLng(37.785069, -122.394176), + new google.maps.LatLng(37.785500, -122.393650), + new google.maps.LatLng(37.785770, -122.393291), + new google.maps.LatLng(37.785839, -122.393159), + new google.maps.LatLng(37.782651, -122.400628), + new google.maps.LatLng(37.782616, -122.400599), + new google.maps.LatLng(37.782702, -122.400470), + new google.maps.LatLng(37.782915, -122.400192), + new google.maps.LatLng(37.783137, -122.399887), + new google.maps.LatLng(37.783414, -122.399519), + new google.maps.LatLng(37.783629, -122.399237), + new google.maps.LatLng(37.783688, -122.399157), + new google.maps.LatLng(37.783716, -122.399106), + new google.maps.LatLng(37.783798, -122.399072), + new google.maps.LatLng(37.783997, -122.399186), + new google.maps.LatLng(37.784271, -122.399538), + new google.maps.LatLng(37.784577, -122.399948), + new google.maps.LatLng(37.784828, -122.400260), + new google.maps.LatLng(37.784999, -122.400477), + new google.maps.LatLng(37.785113, -122.400651), + new google.maps.LatLng(37.785155, -122.400703), + new google.maps.LatLng(37.785192, -122.400749), + new google.maps.LatLng(37.785278, -122.400839), + new google.maps.LatLng(37.785387, -122.400857), + new google.maps.LatLng(37.785478, -122.400890), + new google.maps.LatLng(37.785526, -122.401022), + new google.maps.LatLng(37.785598, -122.401148), + new google.maps.LatLng(37.785631, -122.401202), + new google.maps.LatLng(37.785660, -122.401267), + new google.maps.LatLng(37.803986, -122.426035), + new google.maps.LatLng(37.804102, -122.425089), + new google.maps.LatLng(37.804211, -122.424156), + new google.maps.LatLng(37.803861, -122.423385), + new google.maps.LatLng(37.803151, -122.423214), + new google.maps.LatLng(37.802439, -122.423077), + new google.maps.LatLng(37.801740, -122.422905), + new google.maps.LatLng(37.801069, -122.422785), + new google.maps.LatLng(37.800345, -122.422649), + new google.maps.LatLng(37.799633, -122.422603), + new google.maps.LatLng(37.799750, -122.421700), + new google.maps.LatLng(37.799885, -122.420854), + new google.maps.LatLng(37.799209, -122.420607), + new google.maps.LatLng(37.795656, -122.400395), + new google.maps.LatLng(37.795203, -122.400304), + new google.maps.LatLng(37.778738, -122.415584), + new google.maps.LatLng(37.778812, -122.415189), + new google.maps.LatLng(37.778824, -122.415092), + new google.maps.LatLng(37.778833, -122.414932), + new google.maps.LatLng(37.778834, -122.414898), + new google.maps.LatLng(37.778740, -122.414757), + new google.maps.LatLng(37.778501, -122.414433), + new google.maps.LatLng(37.778182, -122.414026), + new google.maps.LatLng(37.777851, -122.413623), + new google.maps.LatLng(37.777486, -122.413166), + new google.maps.LatLng(37.777109, -122.412674), + new google.maps.LatLng(37.776743, -122.412186), + new google.maps.LatLng(37.776440, -122.411800), + new google.maps.LatLng(37.776295, -122.411614), + new google.maps.LatLng(37.776158, -122.411440), + new google.maps.LatLng(37.775806, -122.410997), + new google.maps.LatLng(37.775422, -122.410484), + new google.maps.LatLng(37.775126, -122.410087), + new google.maps.LatLng(37.775012, -122.409854), + new google.maps.LatLng(37.775164, -122.409573), + new google.maps.LatLng(37.775498, -122.409180), + new google.maps.LatLng(37.775868, -122.408730), + new google.maps.LatLng(37.776256, -122.408240), + new google.maps.LatLng(37.776519, -122.407928), + new google.maps.LatLng(37.776539, -122.407904), + new google.maps.LatLng(37.776595, -122.407854), + new google.maps.LatLng(37.776853, -122.407547), + new google.maps.LatLng(37.777234, -122.407087), + new google.maps.LatLng(37.777644, -122.406558), + new google.maps.LatLng(37.778066, -122.406017), + new google.maps.LatLng(37.778468, -122.405499), + new google.maps.LatLng(37.778866, -122.404995), + new google.maps.LatLng(37.779295, -122.404455), + new google.maps.LatLng(37.779695, -122.403950), + new google.maps.LatLng(37.779982, -122.403584), + new google.maps.LatLng(37.780295, -122.403223), + new google.maps.LatLng(37.780664, -122.402766), + new google.maps.LatLng(37.781043, -122.402288), + new google.maps.LatLng(37.781399, -122.401823), + new google.maps.LatLng(37.781727, -122.401407), + new google.maps.LatLng(37.781853, -122.401247), + new google.maps.LatLng(37.781894, -122.401195), + new google.maps.LatLng(37.782076, -122.400977), + new google.maps.LatLng(37.782338, -122.400603), + new google.maps.LatLng(37.782666, -122.400133), + new google.maps.LatLng(37.783048, -122.399634), + new google.maps.LatLng(37.783450, -122.399198), + new google.maps.LatLng(37.783791, -122.398998), + new google.maps.LatLng(37.784177, -122.398959), + new google.maps.LatLng(37.784388, -122.398971), + new google.maps.LatLng(37.784404, -122.399128), + new google.maps.LatLng(37.784586, -122.399524), + new google.maps.LatLng(37.784835, -122.399927), + new google.maps.LatLng(37.785116, -122.400307), + new google.maps.LatLng(37.785282, -122.400539), + new google.maps.LatLng(37.785346, -122.400692), + new google.maps.LatLng(37.765769, -122.407201), + new google.maps.LatLng(37.765790, -122.407414), + new google.maps.LatLng(37.765802, -122.407755), + new google.maps.LatLng(37.765791, -122.408219), + new google.maps.LatLng(37.765763, -122.408759), + new google.maps.LatLng(37.765726, -122.409348), + new google.maps.LatLng(37.765716, -122.409882), + new google.maps.LatLng(37.765708, -122.410202), + new google.maps.LatLng(37.765705, -122.410253), + new google.maps.LatLng(37.765707, -122.410369), + new google.maps.LatLng(37.765692, -122.410720), + new google.maps.LatLng(37.765699, -122.411215), + new google.maps.LatLng(37.765687, -122.411789), + new google.maps.LatLng(37.765666, -122.412373), + new google.maps.LatLng(37.765598, -122.412883), + new google.maps.LatLng(37.765543, -122.413039), + new google.maps.LatLng(37.765532, -122.413125), + new google.maps.LatLng(37.765500, -122.413553), + new google.maps.LatLng(37.765448, -122.414053), + new google.maps.LatLng(37.765388, -122.414645), + new google.maps.LatLng(37.765323, -122.415250), + new google.maps.LatLng(37.765303, -122.415847), + new google.maps.LatLng(37.765251, -122.416439), + new google.maps.LatLng(37.765204, -122.417020), + new google.maps.LatLng(37.765172, -122.417556), + new google.maps.LatLng(37.765164, -122.418075), + new google.maps.LatLng(37.765153, -122.418618), + new google.maps.LatLng(37.765136, -122.419112), + new google.maps.LatLng(37.765129, -122.419378), + new google.maps.LatLng(37.765119, -122.419481), + new google.maps.LatLng(37.765100, -122.419852), + new google.maps.LatLng(37.765083, -122.420349), + new google.maps.LatLng(37.765045, -122.420930), + new google.maps.LatLng(37.764992, -122.421481), + new google.maps.LatLng(37.764980, -122.421695), + new google.maps.LatLng(37.764993, -122.421843), + new google.maps.LatLng(37.764986, -122.422255), + new google.maps.LatLng(37.764975, -122.422823), + new google.maps.LatLng(37.764939, -122.423411), + new google.maps.LatLng(37.764902, -122.424014), + new google.maps.LatLng(37.764853, -122.424576), + new google.maps.LatLng(37.764826, -122.424922), + new google.maps.LatLng(37.764796, -122.425375), + new google.maps.LatLng(37.764782, -122.425869), + new google.maps.LatLng(37.764768, -122.426089), + new google.maps.LatLng(37.764766, -122.426117), + new google.maps.LatLng(37.764723, -122.426276), + new google.maps.LatLng(37.764681, -122.426649), + new google.maps.LatLng(37.782012, -122.404200), + new google.maps.LatLng(37.781574, -122.404911), + new google.maps.LatLng(37.781055, -122.405597), + new google.maps.LatLng(37.780479, -122.406341), + new google.maps.LatLng(37.779996, -122.406939), + new google.maps.LatLng(37.779459, -122.407613), + new google.maps.LatLng(37.778953, -122.408228), + new google.maps.LatLng(37.778409, -122.408839), + new google.maps.LatLng(37.777842, -122.409501), + new google.maps.LatLng(37.777334, -122.410181), + new google.maps.LatLng(37.776809, -122.410836), + new google.maps.LatLng(37.776240, -122.411514), + new google.maps.LatLng(37.775725, -122.412145), + new google.maps.LatLng(37.775190, -122.412805), + new google.maps.LatLng(37.774672, -122.413464), + new google.maps.LatLng(37.774084, -122.414186), + new google.maps.LatLng(37.773533, -122.413636), + new google.maps.LatLng(37.773021, -122.413009), + new google.maps.LatLng(37.772501, -122.412371), + new google.maps.LatLng(37.771964, -122.411681), + new google.maps.LatLng(37.771479, -122.411078), + new google.maps.LatLng(37.770992, -122.410477), + new google.maps.LatLng(37.770467, -122.409801), + new google.maps.LatLng(37.770090, -122.408904), + new google.maps.LatLng(37.769657, -122.408103), + new google.maps.LatLng(37.769132, -122.407276), + new google.maps.LatLng(37.768564, -122.406469), + new google.maps.LatLng(37.767980, -122.405745), + new google.maps.LatLng(37.767380, -122.405299), + new google.maps.LatLng(37.766604, -122.405297), + new google.maps.LatLng(37.765838, -122.405200), + new google.maps.LatLng(37.765139, -122.405139), + new google.maps.LatLng(37.764457, -122.405094), + new google.maps.LatLng(37.763716, -122.405142), + new google.maps.LatLng(37.762932, -122.405398), + new google.maps.LatLng(37.762126, -122.405813), + new google.maps.LatLng(37.761344, -122.406215), + new google.maps.LatLng(37.760556, -122.406495), + new google.maps.LatLng(37.759732, -122.406484), + new google.maps.LatLng(37.758910, -122.406228), + new google.maps.LatLng(37.758182, -122.405695), + new google.maps.LatLng(37.757676, -122.405118), + new google.maps.LatLng(37.757039, -122.404346), + new google.maps.LatLng(37.756335, -122.403719), + new google.maps.LatLng(37.755503, -122.403406), + new google.maps.LatLng(37.754665, -122.403242), + new google.maps.LatLng(37.753837, -122.403172), + new google.maps.LatLng(37.752986, -122.403112), + new google.maps.LatLng(37.751266, -122.403355) + ]; + + + var pointArray = new google.maps.MVCArray(taxiData); + heatLayer.setData(pointArray); + +//function changeGradient() { +// var gradient = [ +// 'rgba(0, 255, 255, 0)', +// 'rgba(0, 255, 255, 1)', +// 'rgba(0, 191, 255, 1)', +// 'rgba(0, 127, 255, 1)', +// 'rgba(0, 63, 255, 1)', +// 'rgba(0, 0, 255, 1)', +// 'rgba(0, 0, 223, 1)', +// 'rgba(0, 0, 191, 1)', +// 'rgba(0, 0, 159, 1)', +// 'rgba(0, 0, 127, 1)', +// 'rgba(63, 0, 91, 1)', +// 'rgba(127, 0, 63, 1)', +// 'rgba(191, 0, 31, 1)', +// 'rgba(255, 0, 0, 1)' +// ] +// heatmap.set('gradient', heatmap.get('gradient') ? null : gradient); +//} +// +//function changeRadius() { +// heatmap.set('radius', heatmap.get('radius') ? null : 20); +//} +// +//function changeOpacity() { +// heatmap.set('opacity', heatmap.get('opacity') ? null : 0.2); +//} +}; \ No newline at end of file diff --git a/bower_components/angular-google-maps/example/assets/stylesheets/example.css b/bower_components/angular-google-maps/example/assets/stylesheets/example.css new file mode 100644 index 0000000..9e1b2ad --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/stylesheets/example.css @@ -0,0 +1,75 @@ +body { + font-family: 'Open Sans', sans-serif; +} + +.angular-google-map-container { + width: 100%; + height: 600px; +} + +/* fix for Twitter Bootstrap handling of responsive images */ +.angular-google-map img { + max-width: inherit; +} + +.angular-google-map { + top: 80px; + +} + +.shrink { + font-size: 8; +} + +.true:hover { + background-color: lightgreen; +} + +.false:hover { + background-color: lightpink; +} + +.marker-labels { + color: red; + background-color: white; + font-family: "Lucida Grande", "Arial", sans-serif; + font-size: 10px; + font-weight: bold; + text-align: center; + border: 1px solid black; + white-space: nowrap; +} + +/* uncomment this if you are using the element instead of a div +.angular-google-map { + display: block; + }*/ + +.custom-info-window{ + background: rgba(0,0,0,0.5); + color: white; + padding: 20px; + /*border: 1px solid white;*/ + box-shadow: 3px 3px 10px rgba(0,0,0,0.5); + border-radius: 5px; + width: 200px; + margin-top: 15px; + margin-left: -130px; +} +.custom-info-window a{ + color: white; + text-decoration: underline; +} +.custom-info-window:after +{ + content: ''; + position: absolute; + border-style: solid; + border-width: 0 15px 15px; + border-color: rgba(0,0,0,0.5) transparent; + display: block; + width: 0; + z-index: 1; + top: -15px; + left: 118px; +} \ No newline at end of file diff --git a/bower_components/angular-google-maps/example/assets/stylesheets/geojson.css b/bower_components/angular-google-maps/example/assets/stylesheets/geojson.css new file mode 100644 index 0000000..fdc2a6b --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/stylesheets/geojson.css @@ -0,0 +1,78 @@ +body { + font-family: 'Open Sans', sans-serif; +} + +.angular-google-map-container { + width: 100%; + height: 600px; +} + +/* fix for Twitter Bootstrap handling of responsive images */ +.angular-google-map img { + max-width: inherit; +} + +.angular-google-map { + top: 80px; + +} + +.shrink { + font-size: 8; +} + +.true:hover { + background-color: lightgreen; +} + +.false:hover { + background-color: lightpink; +} + +.marker-labels { + color: red; + background-color: white; + font-family: "Lucida Grande", "Arial", sans-serif; + font-size: 10px; + font-weight: bold; + text-align: center; + width: 40px; + border: 1px solid black; + white-space: nowrap; +} + +/* uncomment this if you are using the element instead of a div +.angular-google-map { + display: block; + }*/ + +.custom-info-window{ + background: rgba(0,0,0,0.5); + color: white; + padding: 20px; + /*border: 1px solid white;*/ + box-shadow: 3px 3px 10px rgba(0,0,0,0.5); + border-radius: 5px; + width: 200px; + margin-top: 15px; + margin-left: -130px; +} +.custom-info-window a{ + color: white; + text-decoration: underline; +} +.custom-info-window:after +{ + content: ''; + position: absolute; + border-style: solid; + border-width: 0 15px 15px; + border-color: rgba(0,0,0,0.5) transparent; + display: block; + width: 0; + z-index: 1; + top: -15px; + left: 118px; +} + + diff --git a/bower_components/angular-google-maps/example/assets/stylesheets/hugedata.css b/bower_components/angular-google-maps/example/assets/stylesheets/hugedata.css new file mode 100644 index 0000000..d4cb470 --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/stylesheets/hugedata.css @@ -0,0 +1,3 @@ +.buying-map .angular-google-map-container { + height: 560px; +} \ No newline at end of file diff --git a/bower_components/angular-google-maps/example/assets/templates/info.html b/bower_components/angular-google-maps/example/assets/templates/info.html new file mode 100644 index 0000000..3c9fcac --- /dev/null +++ b/bower_components/angular-google-maps/example/assets/templates/info.html @@ -0,0 +1,9 @@ +
        + I'm loaded from an external template, yay! +
        + Parameter from opener: {{ parameter.message }} +
        +
        + Template specific: {{ templateValue }} +
        +
        diff --git a/bower_components/angular-google-maps/example/example.html b/bower_components/angular-google-maps/example/example.html new file mode 100644 index 0000000..da85af8 --- /dev/null +++ b/bower_components/angular-google-maps/example/example.html @@ -0,0 +1,451 @@ + + + + + angular-google-maps example page + + + + + + + + + + + + +
        +
        +
        +

        angular-google-maps example {{version}}

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

        This is an info window at {{ map.infoWindow.coords.latitude | number:4 }}, {{ map.infoWindow.coords.longitude | number:4 }}!

        +
        + I should not be attached to a + marker. + + CLICK ME +
        +
        +
        + + + + I'm a window with a custom class set via options.boxClass. I only work when googles infoBox plugin is included. + + + + + + + + + + + + + +
        + I should not be attached to a + marker. +
        + CLICK ME +
        +

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        + +

        My marker will stay open when the window is popped up!

        +
        +
        +
        + + + + + + +
        +

        Dynamic Marker created via a delay!

        + +

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        + +

        My marker will stay open when the window is popped up!

        +
        +
        +
        + + + +
        +

        Mexi!

        +

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        +

        My marker will stay open when the window is popped up!

        +
        +
        +
        + + + + + + +

        This is an info window at {{ m.latitude | number:4 }}, {{ m.longitude | number:4 }}!

        +

        My marker will stay open when the window is popped up!

        +
        +
        + + + + + + + This is my clicked marker! +

        My marker will reappear when you close me.

        +
        +
        + + + + + + + + + + + + + +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Layers + + Traffic: + Bicycling: + Weather: + Heat: +
        Generate Random Markers +
        Do Cluster?:
        +
        Cluster Options: + +
        Use Ugly Cluster Image?:
        +
        +
        + + +
        +
        markers + +
        markers2 + +
        dynamicMarkers + +
        center +
        +						 
        +						
        +						{{ map.center.latitude | number:4 }} lat
        +						
        +   + {{ map.center.longitude | number:4 }} lng +
        +
        zoom{{ map.zoom }}
        bounds +
        +                    	north-east: {{ map.bounds.northeast.latitude | number:4 }},{{ map.bounds.northeast.longitude | number:4 }} 
        +                    	
        + south-west: {{ map.bounds.southwest.latitude | number:4 }},{{ map.bounds.southwest.longitude | number:4 }} +
        +
        dragging{{ map.dragging }}
        clicked position +
        +							 
        +							
        +							{{ map.clickedMarker.latitude | number:4 }} lat
        +							
        +   + {{ map.clickedMarker.longitude | number:4 }} lng +
        + Click the map to see +
        polylines + +
        circles + +
        polygons + +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/geojson.html b/bower_components/angular-google-maps/example/geojson.html new file mode 100644 index 0000000..e7b84cd --- /dev/null +++ b/bower_components/angular-google-maps/example/geojson.html @@ -0,0 +1,446 @@ + + + + + angular-google-maps example page + + + + + + + + + + + + +
        +
        +
        +

        angular-google-maps example {{version}}

        +
        + + + +
        +
        +
        + + + + + + + + + +
        + I should not be attached to a + marker. +

        This is an info window at {{ map.infoWindow.coords.coordinates[1] | number:4 }}, {{ map.infoWindow.coords.coordinates[0] | number:4 }}!

        + CLICK ME +
        +
        + + + + I'm a window with a custom class set via options.boxClass. I only work when googles infoBox plugin is included. + + + + + + + + + + + + + +
        +

        This is an info window at {{ geometry.coordinates[1] | number:4 }}, {{ geometry.coordinates[0] | number:4 }}!

        + +

        My marker will stay open when the window is popped up!

        +
        +
        +
        + + + + +
        +

        Dynamic Marker created via a delay!

        + +

        This is an info window at {{ geometry.coordinates[1] | number:4 }}, {{ geometry.coordinates[0] | number:4 }}!

        + +

        My marker will stay open when the window is popped up!

        +
        +
        +
        + + + +
        +

        Mexi!

        +

        This is an info window at {{ geometry.coordinates[1] | number:4 }}, {{ geometry.coordinates[0] | number:4 }}!

        +

        My marker will stay open when the window is popped up!

        +
        +
        +
        + + + + + + +

        This is an info window at {{ m.geometry.coordinates[1] | number:4 }}, {{ m.geometry.coordinates[0] | number:4 }}!

        +

        My marker will stay open when the window is popped up!

        +
        +
        + + +
        + + + This is my clicked marker! +

        My marker will reappear when you close me.

        +
        +
        +
        + + + + + + + + + + +
        +
        +
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        + Layers + + Traffic: + Bicycling: + Weather: + Heat: +
        Generate Random Markers +
        Do Cluster?:
        +
        Cluster Options: + +
        Use Ugly Cluster Image?:
        +
        +
        + + +
        +
        markers + +
        markers2 + +
        dynamicMarkers + +
        center +
        +						 
        +						
        +						{{ map.center.coordinates[1] | number:4 }} lat
        +						
        +   + {{ map.center.coordinates[0] | number:4 }} lng +
        +
        zoom{{ map.zoom }}
        bounds +
        +                    	north-east: {{ map.bounds.northeast.latitude | number:4 }},{{ map.bounds.northeast.longitude | number:4 }} 
        +                    	
        + south-west: {{ map.bounds.southwest.latitude | number:4 }},{{ map.bounds.southwest.longitude | number:4 }} +
        +
        dragging{{ map.dragging }}
        clicked position +
        +							 
        +							
        +							{{ map.clickedMarker.geometry.coordinates[1] | number:4 }} lat
        +							
        +   + {{ map.clickedMarker.geometry.coordinates[1] | number:4 }} lng +
        + Click the map to see +
        polylines + +
        circles + +
        polygons + +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/hugedata.html b/bower_components/angular-google-maps/example/hugedata.html new file mode 100644 index 0000000..91d85e4 --- /dev/null +++ b/bower_components/angular-google-maps/example/hugedata.html @@ -0,0 +1,30 @@ + + + + + angular-google-maps huge data demo + + + + + + + + + +
        +
        +
        +

        angular-google-maps huge data demo

        +
        +
        +
        +
        + + + +
        +
        +
        + + \ No newline at end of file diff --git a/bower_components/angular-google-maps/example/issue-154-marker-fit.html b/bower_components/angular-google-maps/example/issue-154-marker-fit.html new file mode 100644 index 0000000..65f141f --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-154-marker-fit.html @@ -0,0 +1,80 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + +
        +
        + + + +
        + hit me +

        This is an info window at {{ m.latitude | number:4 }}, {{ m.longitude | number:4 }}!

        +

        My marker will stay open when the window is popped up!

        +
        +
        +
        + + + + + + + + + + + +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-157-ng-click.html b/bower_components/angular-google-maps/example/issue-157-ng-click.html new file mode 100644 index 0000000..1260a95 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-157-ng-click.html @@ -0,0 +1,80 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + +
        +
        + + + +
        + hit me +

        This is an info window at {{ m.latitude | number:4 }}, {{ m.longitude | number:4 }}!

        +

        My marker will stay open when the window is popped up!

        +
        +
        +
        + + + + + + + + + + + +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-322.html b/bower_components/angular-google-maps/example/issue-322.html new file mode 100644 index 0000000..d7b5830 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-322.html @@ -0,0 +1,79 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + +
        +
        + + + +

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        + +

        My marker will stay open when the window is popped up!

        +
        +
        + + + + +

        Dynamic Marker created via a delay!

        + +

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        + +

        My marker will stay open when the window is popped up!

        +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-354-window-windows-rerender-content.html b/bower_components/angular-google-maps/example/issue-354-window-windows-rerender-content.html new file mode 100644 index 0000000..b285a98 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-354-window-windows-rerender-content.html @@ -0,0 +1,72 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + + + test = {{ test.name }} +
        +
        + + + + + +
        +

        test = {{test.name}}

        +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-371-polygon-ng-repeat.html b/bower_components/angular-google-maps/example/issue-371-polygon-ng-repeat.html new file mode 100644 index 0000000..3ba51f8 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-371-polygon-ng-repeat.html @@ -0,0 +1,65 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + + + + + + + +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-382-window-windows-ng-show.html b/bower_components/angular-google-maps/example/issue-382-window-windows-ng-show.html new file mode 100644 index 0000000..cea9604 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-382-window-windows-ng-show.html @@ -0,0 +1,71 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + +
        +
        + + + + + + + +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-393-cluster-events-mapped.html b/bower_components/angular-google-maps/example/issue-393-cluster-events-mapped.html new file mode 100644 index 0000000..31110b4 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-393-cluster-events-mapped.html @@ -0,0 +1,74 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + +
        +
        + + + + +

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        +
        +
        + + + +

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-432-markers-fit-clustering.html b/bower_components/angular-google-maps/example/issue-432-markers-fit-clustering.html new file mode 100644 index 0000000..d62984b --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-432-markers-fit-clustering.html @@ -0,0 +1,74 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

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

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-482-markers-animation-clustering.html b/bower_components/angular-google-maps/example/issue-482-markers-animation-clustering.html new file mode 100644 index 0000000..022bb00 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-482-markers-animation-clustering.html @@ -0,0 +1,56 @@ + + + + + angular-google-maps example page + + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + +
        +
        + + + + +

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-482-markers-animation.html b/bower_components/angular-google-maps/example/issue-482-markers-animation.html new file mode 100644 index 0000000..546cd25 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-482-markers-animation.html @@ -0,0 +1,55 @@ + + + + + angular-google-maps example page + + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + +
        +
        + + + + +

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-484-markers-self.html b/bower_components/angular-google-maps/example/issue-484-markers-self.html new file mode 100644 index 0000000..0a25af0 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-484-markers-self.html @@ -0,0 +1,67 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + +
        +
        + + + + + + + + +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-495-polyline-fit.html b/bower_components/angular-google-maps/example/issue-495-polyline-fit.html new file mode 100644 index 0000000..7b3fd97 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-495-polyline-fit.html @@ -0,0 +1,53 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + + + + +
        +
        + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-498-marker-move-loaded.html b/bower_components/angular-google-maps/example/issue-498-marker-move-loaded.html new file mode 100644 index 0000000..9ab2e24 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-498-marker-move-loaded.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + {{sites}} + + + + + + + + + + +
        + + + This is my clicked marker! +

        My marker will reappear when you close me.

        +
        +
        +
        +
        + + + diff --git a/bower_components/angular-google-maps/example/issue-504-markers-flash.html b/bower_components/angular-google-maps/example/issue-504-markers-flash.html new file mode 100644 index 0000000..3d5de81 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-504-markers-flash.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + {{sites}} + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-507-windows-show-w-marker-broken.html b/bower_components/angular-google-maps/example/issue-507-windows-show-w-marker-broken.html new file mode 100644 index 0000000..6940808 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-507-windows-show-w-marker-broken.html @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + +
        + + + +
        {{title}}
        +
        +
        +
        +
        + + + + diff --git a/bower_components/angular-google-maps/example/issue-534-markers-child-destroy.html b/bower_components/angular-google-maps/example/issue-534-markers-child-destroy.html new file mode 100644 index 0000000..9bbd804 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-534-markers-child-destroy.html @@ -0,0 +1,55 @@ + + + + + angular-google-maps example page + + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

        +
        +
        +
        + +
        +
        + + + + +

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/issue-74-markers-events.html b/bower_components/angular-google-maps/example/issue-74-markers-events.html new file mode 100644 index 0000000..2bde7a5 --- /dev/null +++ b/bower_components/angular-google-maps/example/issue-74-markers-events.html @@ -0,0 +1,69 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

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

        This is an info window at {{ latitude | number:4 }}, {{ longitude | number:4 }}!

        +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/shapes-lines.html b/bower_components/angular-google-maps/example/shapes-lines.html new file mode 100644 index 0000000..f9ae2c2 --- /dev/null +++ b/bower_components/angular-google-maps/example/shapes-lines.html @@ -0,0 +1,252 @@ + + + + + angular-google-maps example page + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/example/two-maps.html b/bower_components/angular-google-maps/example/two-maps.html new file mode 100644 index 0000000..235f378 --- /dev/null +++ b/bower_components/angular-google-maps/example/two-maps.html @@ -0,0 +1,110 @@ + + + + + angular-google-maps example page + + + + + + + + + + + +
        +

        angular-google-maps example {{version}}

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

        Marker , MAP 1 Clicked on me2!

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

        Marker 2Clicked on me2!

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

        + Latitude: {{m.coords.latitude}}
        + Longitude: {{m.coords.longitude}}
        + Last signal: {{m.lastSignal}} +

        +
        +
        +
        +
        +
        + + + + + + + + + + + + + diff --git a/bower_components/angular-google-maps/jasmineSettings.coffee b/bower_components/angular-google-maps/jasmineSettings.coffee new file mode 100644 index 0000000..f33f898 --- /dev/null +++ b/bower_components/angular-google-maps/jasmineSettings.coffee @@ -0,0 +1,56 @@ +_ = require('lodash') +log = require('util').log + +doCover = false #clean cheap way to disable coverage so you can debug the darn code.. thank you blanket + +requireConfig = + paths: + "lodash": "bower_components/lodash/dist/lodash.underscore" + deps: ["lodash"] + callback: (_) -> + +log("jasmineSettings: past requireConfig") + +spec = + src: ["bower_components/lodash-amd/main.js", "dist/angular-google-maps.js"] + options: + keepRunner: true + vendor: ["http://maps.googleapis.com/maps/api/js?sensor=false&language=en", + "bower_components/jquery/jquery.js", + "bower_components/angular/angular.js", + "bower_components/angular-mocks/angular-mocks.js"] + specs: ["tmp/spec/js/bootstrap.js", "tmp/spec/js/**/*spec.js"] + helpers: ["tmp/spec/js/helpers/helpers.js"] + template: require "grunt-template-jasmine-requirejs" + templateOptions: + requireConfig: requireConfig + +log("jasmineSettings: past spec") + +coverage = undefined + +if doCover + coverage = _.clone spec + coverage.options = _.extend coverage.options, + template: require "grunt-template-jasmine-istanbul" + templateOptions: + template: require "grunt-template-jasmine-requirejs" + templateOptions: + requireConfig: + requireConfig + coverage: "spec/coverage/coverage.json" + report: "spec/coverage" + thresholds: + lines: 25 + statements: 25 + branches: 5 + functions: 25 + +log("jasmineSettings: past coverage") + +toExport = + spec: spec +toExport["coverage"] = coverage if coverage + +log("jasmineSettings: past toExport") +module.exports = toExport diff --git a/bower_components/angular-google-maps/lib/infobox.js b/bower_components/angular-google-maps/lib/infobox.js new file mode 100644 index 0000000..85e6221 --- /dev/null +++ b/bower_components/angular-google-maps/lib/infobox.js @@ -0,0 +1,812 @@ +/** + * @name InfoBox + * @version 1.1.12 [December 11, 2012] + * @author Gary Little (inspired by proof-of-concept code from Pamela Fox of Google) + * @copyright Copyright 2010 Gary Little [gary at luxcentral.com] + * @fileoverview InfoBox extends the Google Maps JavaScript API V3 OverlayView class. + *

        + * An InfoBox behaves like a google.maps.InfoWindow, but it supports several + * additional properties for advanced styling. An InfoBox can also be used as a map label. + *

        + * An InfoBox also fires the same events as a google.maps.InfoWindow. + */ + +/*! + * + * 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. + */ + +/*jslint browser:true */ +/*global google */ + +/** + * @name InfoBoxOptions + * @class This class represents the optional parameter passed to the {@link InfoBox} constructor. + * @property {string|Node} content The content of the InfoBox (plain text or an HTML DOM node). + * @property {boolean} [disableAutoPan=false] Disable auto-pan on open. + * @property {number} maxWidth The maximum width (in pixels) of the InfoBox. Set to 0 if no maximum. + * @property {Size} pixelOffset The offset (in pixels) from the top left corner of the InfoBox + * (or the bottom left corner if the alignBottom property is true) + * to the map pixel corresponding to position. + * @property {LatLng} position The geographic location at which to display the InfoBox. + * @property {number} zIndex The CSS z-index style value for the InfoBox. + * Note: This value overrides a zIndex setting specified in the boxStyle property. + * @property {string} [boxClass="infoBox"] The name of the CSS class defining the styles for the InfoBox container. + * @property {Object} [boxStyle] An object literal whose properties define specific CSS + * style values to be applied to the InfoBox. Style values defined here override those that may + * be defined in the boxClass style sheet. If this property is changed after the + * InfoBox has been created, all previously set styles (except those defined in the style sheet) + * are removed from the InfoBox before the new style values are applied. + * @property {string} closeBoxMargin The CSS margin style value for the close box. + * The default is "2px" (a 2-pixel margin on all sides). + * @property {string} closeBoxURL The URL of the image representing the close box. + * Note: The default is the URL for Google's standard close box. + * Set this property to "" if no close box is required. + * @property {Size} infoBoxClearance Minimum offset (in pixels) from the InfoBox to the + * map edge after an auto-pan. + * @property {boolean} [isHidden=false] Hide the InfoBox on open. + * [Deprecated in favor of the visible property.] + * @property {boolean} [visible=true] Show the InfoBox on open. + * @property {boolean} alignBottom Align the bottom left corner of the InfoBox to the position + * location (default is false which means that the top left corner of the InfoBox is aligned). + * @property {string} pane The pane where the InfoBox is to appear (default is "floatPane"). + * Set the pane to "mapPane" if the InfoBox is being used as a map label. + * Valid pane names are the property names for the google.maps.MapPanes object. + * @property {boolean} enableEventPropagation Propagate mousedown, mousemove, mouseover, mouseout, + * mouseup, click, dblclick, touchstart, touchend, touchmove, and contextmenu events in the InfoBox + * (default is false to mimic the behavior of a google.maps.InfoWindow). Set + * this property to true if the InfoBox is being used as a map label. + */ + +/** + * Creates an InfoBox with the options specified in {@link InfoBoxOptions}. + * Call InfoBox.open to add the box to the map. + * @constructor + * @param {InfoBoxOptions} [opt_opts] + */ +function InfoBox(opt_opts) { + + opt_opts = opt_opts || {}; + + google.maps.OverlayView.apply(this, arguments); + + // Standard options (in common with google.maps.InfoWindow): + // + this.content_ = opt_opts.content || ""; + this.disableAutoPan_ = opt_opts.disableAutoPan || false; + this.maxWidth_ = opt_opts.maxWidth || 0; + this.pixelOffset_ = opt_opts.pixelOffset || new google.maps.Size(0, 0); + this.position_ = opt_opts.position || new google.maps.LatLng(0, 0); + this.zIndex_ = opt_opts.zIndex || null; + + // Additional options (unique to InfoBox): + // + this.boxClass_ = opt_opts.boxClass || "infoBox"; + this.boxStyle_ = opt_opts.boxStyle || {}; + this.closeBoxMargin_ = opt_opts.closeBoxMargin || "2px"; + this.closeBoxURL_ = opt_opts.closeBoxURL || "http://www.google.com/intl/en_us/mapfiles/close.gif"; + if (opt_opts.closeBoxURL === "") { + this.closeBoxURL_ = ""; + } + this.infoBoxClearance_ = opt_opts.infoBoxClearance || new google.maps.Size(1, 1); + + if (typeof opt_opts.visible === "undefined") { + if (typeof opt_opts.isHidden === "undefined") { + opt_opts.visible = true; + } else { + opt_opts.visible = !opt_opts.isHidden; + } + } + this.isHidden_ = !opt_opts.visible; + + this.alignBottom_ = opt_opts.alignBottom || false; + this.pane_ = opt_opts.pane || "floatPane"; + this.enableEventPropagation_ = opt_opts.enableEventPropagation || false; + + this.div_ = null; + this.closeListener_ = null; + this.moveListener_ = null; + this.contextListener_ = null; + this.eventListeners_ = null; + this.fixedWidthSet_ = null; +} + +/* InfoBox extends OverlayView in the Google Maps API v3. + */ +InfoBox.prototype = new google.maps.OverlayView(); + +/** + * Creates the DIV representing the InfoBox. + * @private + */ +InfoBox.prototype.createInfoBoxDiv_ = function () { + + var i; + var events; + var bw; + var me = this; + + // This handler prevents an event in the InfoBox from being passed on to the map. + // + var cancelHandler = function (e) { + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + }; + + // This handler ignores the current event in the InfoBox and conditionally prevents + // the event from being passed on to the map. It is used for the contextmenu event. + // + var ignoreHandler = function (e) { + + e.returnValue = false; + + if (e.preventDefault) { + + e.preventDefault(); + } + + if (!me.enableEventPropagation_) { + + cancelHandler(e); + } + }; + + if (!this.div_) { + + this.div_ = document.createElement("div"); + + this.setBoxStyle_(); + + if (typeof this.content_.nodeType === "undefined") { + this.div_.innerHTML = this.getCloseBoxImg_() + this.content_; + } else { + this.div_.innerHTML = this.getCloseBoxImg_(); + this.div_.appendChild(this.content_); + } + + // Add the InfoBox DIV to the DOM + this.getPanes()[this.pane_].appendChild(this.div_); + + this.addClickHandler_(); + + if (this.div_.style.width) { + + this.fixedWidthSet_ = true; + + } else { + + if (this.maxWidth_ !== 0 && this.div_.offsetWidth > this.maxWidth_) { + + this.div_.style.width = this.maxWidth_; + this.div_.style.overflow = "auto"; + this.fixedWidthSet_ = true; + + } else { // The following code is needed to overcome problems with MSIE + + bw = this.getBoxWidths_(); + + this.div_.style.width = (this.div_.offsetWidth - bw.left - bw.right) + "px"; + this.fixedWidthSet_ = false; + } + } + + this.panBox_(this.disableAutoPan_); + + if (!this.enableEventPropagation_) { + + this.eventListeners_ = []; + + // Cancel event propagation. + // + // Note: mousemove not included (to resolve Issue 152) + events = ["mousedown", "mouseover", "mouseout", "mouseup", + "click", "dblclick", "touchstart", "touchend", "touchmove"]; + + for (i = 0; i < events.length; i++) { + + this.eventListeners_.push(google.maps.event.addDomListener(this.div_, events[i], cancelHandler)); + } + + // Workaround for Google bug that causes the cursor to change to a pointer + // when the mouse moves over a marker underneath InfoBox. + this.eventListeners_.push(google.maps.event.addDomListener(this.div_, "mouseover", function (e) { + this.style.cursor = "default"; + })); + } + + this.contextListener_ = google.maps.event.addDomListener(this.div_, "contextmenu", ignoreHandler); + + /** + * This event is fired when the DIV containing the InfoBox's content is attached to the DOM. + * @name InfoBox#domready + * @event + */ + google.maps.event.trigger(this, "domready"); + } +}; + +/** + * Returns the HTML tag for the close box. + * @private + */ +InfoBox.prototype.getCloseBoxImg_ = function () { + + var img = ""; + + if (this.closeBoxURL_ !== "") { + + img = " mapWidth) { + xOffset = pixPosition.x + iwWidth + iwOffsetX + padX - mapWidth; + } + if (this.alignBottom_) { + if (pixPosition.y < (-iwOffsetY + padY + iwHeight)) { + yOffset = pixPosition.y + iwOffsetY - padY - iwHeight; + } else if ((pixPosition.y + iwOffsetY + padY) > mapHeight) { + yOffset = pixPosition.y + iwOffsetY + padY - mapHeight; + } + } else { + if (pixPosition.y < (-iwOffsetY + padY)) { + yOffset = pixPosition.y + iwOffsetY - padY; + } else if ((pixPosition.y + iwHeight + iwOffsetY + padY) > mapHeight) { + yOffset = pixPosition.y + iwHeight + iwOffsetY + padY - mapHeight; + } + } + + if (!(xOffset === 0 && yOffset === 0)) { + + // Move the map to the shifted center. + // + var c = map.getCenter(); + map.panBy(xOffset, yOffset); + } + } + } +}; + +/** + * Sets the style of the InfoBox by setting the style sheet and applying + * other specific styles requested. + * @private + */ +InfoBox.prototype.setBoxStyle_ = function () { + + var i, boxStyle; + + if (this.div_) { + + // Apply style values from the style sheet defined in the boxClass parameter: + this.div_.className = this.boxClass_; + + // Clear existing inline style values: + this.div_.style.cssText = ""; + + // Apply style values defined in the boxStyle parameter: + boxStyle = this.boxStyle_; + for (i in boxStyle) { + + if (boxStyle.hasOwnProperty(i)) { + + this.div_.style[i] = boxStyle[i]; + } + } + + // Fix up opacity style for benefit of MSIE: + // + if (typeof this.div_.style.opacity !== "undefined" && this.div_.style.opacity !== "") { + + this.div_.style.filter = "alpha(opacity=" + (this.div_.style.opacity * 100) + ")"; + } + + // Apply required styles: + // + this.div_.style.position = "absolute"; + this.div_.style.visibility = 'hidden'; + if (this.zIndex_ !== null) { + + this.div_.style.zIndex = this.zIndex_; + } + } +}; + +/** + * Get the widths of the borders of the InfoBox. + * @private + * @return {Object} widths object (top, bottom left, right) + */ +InfoBox.prototype.getBoxWidths_ = function () { + + var computedStyle; + var bw = {top: 0, bottom: 0, left: 0, right: 0}; + var box = this.div_; + + if (document.defaultView && document.defaultView.getComputedStyle) { + + computedStyle = box.ownerDocument.defaultView.getComputedStyle(box, ""); + + if (computedStyle) { + + // The computed styles are always in pixel units (good!) + bw.top = parseInt(computedStyle.borderTopWidth, 10) || 0; + bw.bottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; + bw.left = parseInt(computedStyle.borderLeftWidth, 10) || 0; + bw.right = parseInt(computedStyle.borderRightWidth, 10) || 0; + } + + } else if (document.documentElement.currentStyle) { // MSIE + + if (box.currentStyle) { + + // The current styles may not be in pixel units, but assume they are (bad!) + bw.top = parseInt(box.currentStyle.borderTopWidth, 10) || 0; + bw.bottom = parseInt(box.currentStyle.borderBottomWidth, 10) || 0; + bw.left = parseInt(box.currentStyle.borderLeftWidth, 10) || 0; + bw.right = parseInt(box.currentStyle.borderRightWidth, 10) || 0; + } + } + + return bw; +}; + +/** + * Invoked when close is called. Do not call it directly. + */ +InfoBox.prototype.onRemove = function () { + + if (this.div_) { + + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + } +}; + +/** + * Draws the InfoBox based on the current map projection and zoom level. + */ +InfoBox.prototype.draw = function () { + + this.createInfoBoxDiv_(); + + var pixPosition = this.getProjection().fromLatLngToDivPixel(this.position_); + + this.div_.style.left = (pixPosition.x + this.pixelOffset_.width) + "px"; + + if (this.alignBottom_) { + this.div_.style.bottom = -(pixPosition.y + this.pixelOffset_.height) + "px"; + } else { + this.div_.style.top = (pixPosition.y + this.pixelOffset_.height) + "px"; + } + + if (this.isHidden_) { + + this.div_.style.visibility = 'hidden'; + + } else { + + this.div_.style.visibility = "visible"; + } +}; + +/** + * Sets the options for the InfoBox. Note that changes to the maxWidth, + * closeBoxMargin, closeBoxURL, and enableEventPropagation + * properties have no affect until the current InfoBox is closed and a new one + * is opened. + * @param {InfoBoxOptions} opt_opts + */ +InfoBox.prototype.setOptions = function (opt_opts) { + if (typeof opt_opts.boxClass !== "undefined") { // Must be first + + this.boxClass_ = opt_opts.boxClass; + this.setBoxStyle_(); + } + if (typeof opt_opts.boxStyle !== "undefined") { // Must be second + + this.boxStyle_ = opt_opts.boxStyle; + this.setBoxStyle_(); + } + if (typeof opt_opts.content !== "undefined") { + + this.setContent(opt_opts.content); + } + if (typeof opt_opts.disableAutoPan !== "undefined") { + + this.disableAutoPan_ = opt_opts.disableAutoPan; + } + if (typeof opt_opts.maxWidth !== "undefined") { + + this.maxWidth_ = opt_opts.maxWidth; + } + if (typeof opt_opts.pixelOffset !== "undefined") { + + this.pixelOffset_ = opt_opts.pixelOffset; + } + if (typeof opt_opts.alignBottom !== "undefined") { + + this.alignBottom_ = opt_opts.alignBottom; + } + if (typeof opt_opts.position !== "undefined") { + + this.setPosition(opt_opts.position); + } + if (typeof opt_opts.zIndex !== "undefined") { + + this.setZIndex(opt_opts.zIndex); + } + if (typeof opt_opts.closeBoxMargin !== "undefined") { + + this.closeBoxMargin_ = opt_opts.closeBoxMargin; + } + if (typeof opt_opts.closeBoxURL !== "undefined") { + + this.closeBoxURL_ = opt_opts.closeBoxURL; + } + if (typeof opt_opts.infoBoxClearance !== "undefined") { + + this.infoBoxClearance_ = opt_opts.infoBoxClearance; + } + if (typeof opt_opts.isHidden !== "undefined") { + + this.isHidden_ = opt_opts.isHidden; + } + if (typeof opt_opts.visible !== "undefined") { + + this.isHidden_ = !opt_opts.visible; + } + if (typeof opt_opts.enableEventPropagation !== "undefined") { + + this.enableEventPropagation_ = opt_opts.enableEventPropagation; + } + + if (this.div_) { + + this.draw(); + } +}; + +/** + * Sets the content of the InfoBox. + * The content can be plain text or an HTML DOM node. + * @param {string|Node} content + */ +InfoBox.prototype.setContent = function (content) { + this.content_ = content; + + if (this.div_) { + + if (this.closeListener_) { + + google.maps.event.removeListener(this.closeListener_); + this.closeListener_ = null; + } + + // Odd code required to make things work with MSIE. + // + if (!this.fixedWidthSet_) { + + this.div_.style.width = ""; + } + + if (typeof content.nodeType === "undefined") { + this.div_.innerHTML = this.getCloseBoxImg_() + content; + } else { + this.div_.innerHTML = this.getCloseBoxImg_(); + this.div_.appendChild(content); + } + + // Perverse code required to make things work with MSIE. + // (Ensures the close box does, in fact, float to the right.) + // + if (!this.fixedWidthSet_) { + this.div_.style.width = this.div_.offsetWidth + "px"; + if (typeof content.nodeType === "undefined") { + this.div_.innerHTML = this.getCloseBoxImg_() + content; + } else { + this.div_.innerHTML = this.getCloseBoxImg_(); + this.div_.appendChild(content); + } + } + + this.addClickHandler_(); + } + + /** + * This event is fired when the content of the InfoBox changes. + * @name InfoBox#content_changed + * @event + */ + google.maps.event.trigger(this, "content_changed"); +}; + +/** + * Sets the geographic location of the InfoBox. + * @param {LatLng} latlng + */ +InfoBox.prototype.setPosition = function (latlng) { + + this.position_ = latlng; + + if (this.div_) { + + this.draw(); + } + + /** + * This event is fired when the position of the InfoBox changes. + * @name InfoBox#position_changed + * @event + */ + google.maps.event.trigger(this, "position_changed"); +}; + +/** + * Sets the zIndex style for the InfoBox. + * @param {number} index + */ +InfoBox.prototype.setZIndex = function (index) { + + this.zIndex_ = index; + + if (this.div_) { + + this.div_.style.zIndex = index; + } + + /** + * This event is fired when the zIndex of the InfoBox changes. + * @name InfoBox#zindex_changed + * @event + */ + google.maps.event.trigger(this, "zindex_changed"); +}; + +/** + * Sets the visibility of the InfoBox. + * @param {boolean} isVisible + */ +InfoBox.prototype.setVisible = function (isVisible) { + + this.isHidden_ = !isVisible; + if (this.div_) { + this.div_.style.visibility = (this.isHidden_ ? "hidden" : "visible"); + } +}; + +/** + * Returns the content of the InfoBox. + * @returns {string} + */ +InfoBox.prototype.getContent = function () { + + return this.content_; +}; + +/** + * Returns the geographic location of the InfoBox. + * @returns {LatLng} + */ +InfoBox.prototype.getPosition = function () { + + return this.position_; +}; + +/** + * Returns the zIndex for the InfoBox. + * @returns {number} + */ +InfoBox.prototype.getZIndex = function () { + + return this.zIndex_; +}; + +/** + * Returns a flag indicating whether the InfoBox is visible. + * @returns {boolean} + */ +InfoBox.prototype.getVisible = function () { + + var isVisible; + + if ((typeof this.getMap() === "undefined") || (this.getMap() === null)) { + isVisible = false; + } else { + isVisible = !this.isHidden_; + } + return isVisible; +}; + +/** + * Shows the InfoBox. [Deprecated; use setVisible instead.] + */ +InfoBox.prototype.show = function () { + + this.isHidden_ = false; + if (this.div_) { + this.div_.style.visibility = "visible"; + } +}; + +/** + * Hides the InfoBox. [Deprecated; use setVisible instead.] + */ +InfoBox.prototype.hide = function () { + + this.isHidden_ = true; + if (this.div_) { + this.div_.style.visibility = "hidden"; + } +}; + +/** + * Adds the InfoBox to the specified map or Street View panorama. If anchor + * (usually a google.maps.Marker) is specified, the position + * of the InfoBox is set to the position of the anchor. If the + * anchor is dragged to a new location, the InfoBox moves as well. + * @param {Map|StreetViewPanorama} map + * @param {MVCObject} [anchor] + */ +InfoBox.prototype.open = function (map, anchor) { + + var me = this; + + if (anchor) { + + this.position_ = anchor.getPosition(); + this.moveListener_ = google.maps.event.addListener(anchor, "position_changed", function () { + me.setPosition(this.getPosition()); + }); + } + + this.setMap(map); + + if (this.div_) { + + this.panBox_(); + } +}; + +/** + * Removes the InfoBox from the map. + */ +InfoBox.prototype.close = function () { + + var i; + + if (this.closeListener_) { + + google.maps.event.removeListener(this.closeListener_); + this.closeListener_ = null; + } + + if (this.eventListeners_) { + + for (i = 0; i < this.eventListeners_.length; i++) { + + google.maps.event.removeListener(this.eventListeners_[i]); + } + this.eventListeners_ = null; + } + + if (this.moveListener_) { + + google.maps.event.removeListener(this.moveListener_); + this.moveListener_ = null; + } + + if (this.contextListener_) { + + google.maps.event.removeListener(this.contextListener_); + this.contextListener_ = null; + } + + this.setMap(null); +}; \ No newline at end of file diff --git a/bower_components/angular-google-maps/lib/markerclusterer.js b/bower_components/angular-google-maps/lib/markerclusterer.js new file mode 100644 index 0000000..0e79ead --- /dev/null +++ b/bower_components/angular-google-maps/lib/markerclusterer.js @@ -0,0 +1,1643 @@ +/** + * @name MarkerClustererPlus for Google Maps V3 + * @version 2.1.1 [November 4, 2013] + * @author Gary Little + * @fileoverview + * The library creates and manages per-zoom-level clusters for large amounts of markers. + *

        + * This is an enhanced V3 implementation of the + * V2 MarkerClusterer by Xiaoxi Wu. It is based on the + * V3 MarkerClusterer port by Luke Mahe. MarkerClustererPlus was created by Gary Little. + *

        + * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It + * adds support for the ignoreHidden, title, batchSizeIE, + * and calculator properties as well as support for four more events. It also allows + * greater control over the styling of the text that appears on the cluster marker. The + * documentation has been significantly improved and the overall code has been simplified and + * polished. Very large numbers of markers can now be managed without causing Javascript timeout + * errors on Internet Explorer. Note that the name of the clusterclick event has been + * deprecated. The new name is click, so please change your application code now. + */ + +/** + * 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. + */ + + +/** + * @name ClusterIconStyle + * @class This class represents the object for values in the styles array passed + * to the {@link MarkerClusterer} constructor. The element in this array that is used to + * style the cluster icon is determined by calling the calculator function. + * + * @property {string} url The URL of the cluster icon image file. Required. + * @property {number} height The display height (in pixels) of the cluster icon. Required. + * @property {number} width The display width (in pixels) of the cluster icon. Required. + * @property {Array} [anchorText] The position (in pixels) from the center of the cluster icon to + * where the text label is to be centered and drawn. The format is [yoffset, xoffset] + * where yoffset increases as you go down from center and xoffset + * increases to the right of center. The default is [0, 0]. + * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the + * spot on the cluster icon that is to be aligned with the cluster position. The format is + * [yoffset, xoffset] where yoffset increases as you go down and + * xoffset increases to the right of the top-left corner of the icon. The default + * anchor position is the center of the cluster icon. + * @property {string} [textColor="black"] The color of the label text shown on the + * cluster icon. + * @property {number} [textSize=11] The size (in pixels) of the label text shown on the + * cluster icon. + * @property {string} [textDecoration="none"] The value of the CSS text-decoration + * property for the label text shown on the cluster icon. + * @property {string} [fontWeight="bold"] The value of the CSS font-weight + * property for the label text shown on the cluster icon. + * @property {string} [fontStyle="normal"] The value of the CSS font-style + * property for the label text shown on the cluster icon. + * @property {string} [fontFamily="Arial,sans-serif"] The value of the CSS font-family + * property for the label text shown on the cluster icon. + * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image + * within the image defined by url. The format is "xpos ypos" + * (the same format as for the CSS background-position property). You must set + * this property appropriately when the image defined by url represents a sprite + * containing multiple images. Note that the position must be specified in px units. + */ +/** + * @name ClusterIconInfo + * @class This class is an object containing general information about a cluster icon. This is + * the object that a calculator function returns. + * + * @property {string} text The text of the label to be shown on the cluster icon. + * @property {number} index The index plus 1 of the element in the styles + * array to be used to style the cluster icon. + * @property {string} title The tooltip to display when the mouse moves over the cluster icon. + * If this value is undefined or "", title is set to the + * value of the title property passed to the MarkerClusterer. + */ +/** + * A cluster icon. + * + * @constructor + * @extends google.maps.OverlayView + * @param {Cluster} cluster The cluster with which the icon is to be associated. + * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons + * to use for various cluster sizes. + * @private + */ +function ClusterIcon(cluster, styles) { + cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); + + this.cluster_ = cluster; + this.className_ = cluster.getMarkerClusterer().getClusterClass(); + this.styles_ = styles; + this.center_ = null; + this.div_ = null; + this.sums_ = null; + this.visible_ = false; + + this.setMap(cluster.getMap()); // Note: this causes onAdd to be called +} + + +/** + * Adds the icon to the DOM. + */ +ClusterIcon.prototype.onAdd = function () { + var cClusterIcon = this; + var cMouseDownInCluster; + var cDraggingMapByCluster; + + this.div_ = document.createElement("div"); + this.div_.className = this.className_; + if (this.visible_) { + this.show(); + } + + this.getPanes().overlayMouseTarget.appendChild(this.div_); + + // Fix for Issue 157 + this.boundsChangedListener_ = google.maps.event.addListener(this.getMap(), "bounds_changed", function () { + cDraggingMapByCluster = cMouseDownInCluster; + }); + + google.maps.event.addDomListener(this.div_, "mousedown", function () { + cMouseDownInCluster = true; + cDraggingMapByCluster = false; + }); + + google.maps.event.addDomListener(this.div_, "click", function (e) { + cMouseDownInCluster = false; + if (!cDraggingMapByCluster) { + var theBounds; + var mz; + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when a cluster marker is clicked. + * @name MarkerClusterer#click + * @param {Cluster} c The cluster that was clicked. + * @event + */ + google.maps.event.trigger(mc, "click", cClusterIcon.cluster_); + google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name + + // The default click handler follows. Disable it by setting + // the zoomOnClick property to false. + if (mc.getZoomOnClick()) { + // Zoom into the cluster. + mz = mc.getMaxZoom(); + theBounds = cClusterIcon.cluster_.getBounds(); + mc.getMap().fitBounds(theBounds); + // There is a fix for Issue 170 here: + setTimeout(function () { + mc.getMap().fitBounds(theBounds); + // Don't zoom beyond the max zoom level + if (mz !== null && (mc.getMap().getZoom() > mz)) { + mc.getMap().setZoom(mz + 1); + } + }, 100); + } + + // Prevent event propagation to the map: + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + } + }); + + google.maps.event.addDomListener(this.div_, "mouseover", function () { + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when the mouse moves over a cluster marker. + * @name MarkerClusterer#mouseover + * @param {Cluster} c The cluster that the mouse moved over. + * @event + */ + google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_); + }); + + google.maps.event.addDomListener(this.div_, "mouseout", function () { + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when the mouse moves out of a cluster marker. + * @name MarkerClusterer#mouseout + * @param {Cluster} c The cluster that the mouse moved out of. + * @event + */ + google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_); + }); +}; + + +/** + * Removes the icon from the DOM. + */ +ClusterIcon.prototype.onRemove = function () { + if (this.div_ && this.div_.parentNode) { + this.hide(); + google.maps.event.removeListener(this.boundsChangedListener_); + google.maps.event.clearInstanceListeners(this.div_); + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + } +}; + + +/** + * Draws the icon. + */ +ClusterIcon.prototype.draw = function () { + if (this.visible_) { + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.top = pos.y + "px"; + this.div_.style.left = pos.x + "px"; + } +}; + + +/** + * Hides the icon. + */ +ClusterIcon.prototype.hide = function () { + if (this.div_) { + this.div_.style.display = "none"; + } + this.visible_ = false; +}; + + +/** + * Positions and shows the icon. + */ +ClusterIcon.prototype.show = function () { + if (this.div_) { + var img = ""; + // NOTE: values must be specified in px units + var bp = this.backgroundPosition_.split(" "); + var spriteH = parseInt(bp[0].trim(), 10); + var spriteV = parseInt(bp[1].trim(), 10); + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.cssText = this.createCss(pos); + img = ""; + this.div_.innerHTML = img + "

        " + this.sums_.text + "
        "; + if (typeof this.sums_.title === "undefined" || this.sums_.title === "") { + this.div_.title = this.cluster_.getMarkerClusterer().getTitle(); + } else { + this.div_.title = this.sums_.title; + } + this.div_.style.display = ""; + } + this.visible_ = true; +}; + + +/** + * Sets the icon styles to the appropriate element in the styles array. + * + * @param {ClusterIconInfo} sums The icon label text and styles index. + */ +ClusterIcon.prototype.useStyle = function (sums) { + this.sums_ = sums; + var index = Math.max(0, sums.index - 1); + index = Math.min(this.styles_.length - 1, index); + var style = this.styles_[index]; + this.url_ = style.url; + this.height_ = style.height; + this.width_ = style.width; + this.anchorText_ = style.anchorText || [0, 0]; + this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)]; + this.textColor_ = style.textColor || "black"; + this.textSize_ = style.textSize || 11; + this.textDecoration_ = style.textDecoration || "none"; + this.fontWeight_ = style.fontWeight || "bold"; + this.fontStyle_ = style.fontStyle || "normal"; + this.fontFamily_ = style.fontFamily || "Arial,sans-serif"; + this.backgroundPosition_ = style.backgroundPosition || "0 0"; +}; + + +/** + * Sets the position at which to center the icon. + * + * @param {google.maps.LatLng} center The latlng to set as the center. + */ +ClusterIcon.prototype.setCenter = function (center) { + this.center_ = center; +}; + + +/** + * Creates the cssText style parameter based on the position of the icon. + * + * @param {google.maps.Point} pos The position of the icon. + * @return {string} The CSS style text. + */ +ClusterIcon.prototype.createCss = function (pos) { + var style = []; + style.push("cursor: pointer;"); + style.push("position: absolute; top: " + pos.y + "px; left: " + pos.x + "px;"); + style.push("width: " + this.width_ + "px; height: " + this.height_ + "px;"); + return style.join(""); +}; + + +/** + * Returns the position at which to place the DIV depending on the latlng. + * + * @param {google.maps.LatLng} latlng The position in latlng. + * @return {google.maps.Point} The position in pixels. + */ +ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) { + var pos = this.getProjection().fromLatLngToDivPixel(latlng); + pos.x -= this.anchorIcon_[1]; + pos.y -= this.anchorIcon_[0]; + pos.x = parseInt(pos.x, 10); + pos.y = parseInt(pos.y, 10); + return pos; +}; + + +/** + * Creates a single cluster that manages a group of proximate markers. + * Used internally, do not call this constructor directly. + * @constructor + * @param {MarkerClusterer} mc The MarkerClusterer object with which this + * cluster is associated. + */ +function Cluster(mc) { + this.markerClusterer_ = mc; + this.map_ = mc.getMap(); + this.gridSize_ = mc.getGridSize(); + this.minClusterSize_ = mc.getMinimumClusterSize(); + this.averageCenter_ = mc.getAverageCenter(); + this.markers_ = []; + this.center_ = null; + this.bounds_ = null; + this.clusterIcon_ = new ClusterIcon(this, mc.getStyles()); +} + + +/** + * Returns the number of markers managed by the cluster. You can call this from + * a click, mouseover, or mouseout event handler + * for the MarkerClusterer object. + * + * @return {number} The number of markers in the cluster. + */ +Cluster.prototype.getSize = function () { + return this.markers_.length; +}; + + +/** + * Returns the array of markers managed by the cluster. You can call this from + * a click, mouseover, or mouseout event handler + * for the MarkerClusterer object. + * + * @return {Array} The array of markers in the cluster. + */ +Cluster.prototype.getMarkers = function () { + return this.markers_; +}; + + +/** + * Returns the center of the cluster. You can call this from + * a click, mouseover, or mouseout event handler + * for the MarkerClusterer object. + * + * @return {google.maps.LatLng} The center of the cluster. + */ +Cluster.prototype.getCenter = function () { + return this.center_; +}; + + +/** + * Returns the map with which the cluster is associated. + * + * @return {google.maps.Map} The map. + * @ignore + */ +Cluster.prototype.getMap = function () { + return this.map_; +}; + + +/** + * Returns the MarkerClusterer object with which the cluster is associated. + * + * @return {MarkerClusterer} The associated marker clusterer. + * @ignore + */ +Cluster.prototype.getMarkerClusterer = function () { + return this.markerClusterer_; +}; + + +/** + * Returns the bounds of the cluster. + * + * @return {google.maps.LatLngBounds} the cluster bounds. + * @ignore + */ +Cluster.prototype.getBounds = function () { + var i; + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); + var markers = this.getMarkers(); + for (i = 0; i < markers.length; i++) { + bounds.extend(markers[i].getPosition()); + } + return bounds; +}; + + +/** + * Removes the cluster from the map. + * + * @ignore + */ +Cluster.prototype.remove = function () { + this.clusterIcon_.setMap(null); + this.markers_ = []; + delete this.markers_; +}; + + +/** + * Adds a marker to the cluster. + * + * @param {google.maps.Marker} marker The marker to be added. + * @return {boolean} True if the marker was added. + * @ignore + */ +Cluster.prototype.addMarker = function (marker) { + var i; + var mCount; + var mz; + + if (this.isMarkerAlreadyAdded_(marker)) { + return false; + } + + if (!this.center_) { + this.center_ = marker.getPosition(); + this.calculateBounds_(); + } else { + if (this.averageCenter_) { + var l = this.markers_.length + 1; + var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l; + var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l; + this.center_ = new google.maps.LatLng(lat, lng); + this.calculateBounds_(); + } + } + + marker.isAdded = true; + this.markers_.push(marker); + + mCount = this.markers_.length; + mz = this.markerClusterer_.getMaxZoom(); + if (mz !== null && this.map_.getZoom() > mz) { + // Zoomed in past max zoom, so show the marker. + if (marker.getMap() !== this.map_) { + marker.setMap(this.map_); + } + } else if (mCount < this.minClusterSize_) { + // Min cluster size not reached so show the marker. + if (marker.getMap() !== this.map_) { + marker.setMap(this.map_); + } + } else if (mCount === this.minClusterSize_) { + // Hide the markers that were showing. + for (i = 0; i < mCount; i++) { + this.markers_[i].setMap(null); + } + } else { + marker.setMap(null); + } + + this.updateIcon_(); + return true; +}; + + +/** + * Determines if a marker lies within the cluster's bounds. + * + * @param {google.maps.Marker} marker The marker to check. + * @return {boolean} True if the marker lies in the bounds. + * @ignore + */ +Cluster.prototype.isMarkerInClusterBounds = function (marker) { + return this.bounds_.contains(marker.getPosition()); +}; + + +/** + * Calculates the extended bounds of the cluster with the grid. + */ +Cluster.prototype.calculateBounds_ = function () { + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); + this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); +}; + + +/** + * Updates the cluster icon. + */ +Cluster.prototype.updateIcon_ = function () { + var mCount = this.markers_.length; + var mz = this.markerClusterer_.getMaxZoom(); + + if (mz !== null && this.map_.getZoom() > mz) { + this.clusterIcon_.hide(); + return; + } + + if (mCount < this.minClusterSize_) { + // Min cluster size not yet reached. + this.clusterIcon_.hide(); + return; + } + + var numStyles = this.markerClusterer_.getStyles().length; + var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); + this.clusterIcon_.setCenter(this.center_); + this.clusterIcon_.useStyle(sums); + this.clusterIcon_.show(); +}; + + +/** + * Determines if a marker has already been added to the cluster. + * + * @param {google.maps.Marker} marker The marker to check. + * @return {boolean} True if the marker has already been added. + */ +Cluster.prototype.isMarkerAlreadyAdded_ = function (marker) { + var i; + if (this.markers_.indexOf) { + return this.markers_.indexOf(marker) !== -1; + } else { + for (i = 0; i < this.markers_.length; i++) { + if (marker === this.markers_[i]) { + return true; + } + } + } + return false; +}; + + +/** + * @name MarkerClustererOptions + * @class This class represents the optional parameter passed to + * the {@link MarkerClusterer} constructor. + * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square. + * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or + * null if clustering is to be enabled at all zoom levels. + * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is + * clicked. You may want to set this to false if you have installed a handler + * for the click event and it deals with zooming on its own. + * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be + * the average position of all markers in the cluster. If set to false, the + * cluster marker is positioned at the location of the first marker added to the cluster. + * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster + * before the markers are hidden and a cluster marker appears. + * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You + * may want to set this to true to ensure that hidden markers are not included + * in the marker count that appears on a cluster marker (this count is the value of the + * text property of the result returned by the default calculator). + * If set to true and you change the visibility of a marker being clustered, be + * sure to also call MarkerClusterer.repaint(). + * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster + * marker. (Alternatively, you can use a custom calculator function to specify a + * different tooltip for each cluster marker.) + * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine + * the text to be displayed on a cluster marker and the index indicating which style to use + * for the cluster marker. The input parameters for the function are (1) the array of markers + * represented by a cluster marker and (2) the number of cluster icon styles. It returns a + * {@link ClusterIconInfo} object. The default calculator returns a + * text property which is the number of markers in the cluster and an + * index property which is one higher than the lowest integer such that + * 10^i exceeds the number of markers in the cluster, or the size of the styles + * array, whichever is less. The styles array element used has an index of + * index minus 1. For example, the default calculator returns a + * text value of "125" and an index of 3 + * for a cluster icon representing 125 markers so the element used in the styles + * array is 2. A calculator may also return a title + * property that contains the text of the tooltip to be used for the cluster marker. If + * title is not defined, the tooltip is set to the value of the title + * property for the MarkerClusterer. + * @property {string} [clusterClass="cluster"] The name of the CSS class defining general styles + * for the cluster markers. Use this class to define CSS styles that are not set up by the code + * that processes the styles array. + * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles + * of the cluster markers to be used. The element to be used to style a given cluster marker + * is determined by the function defined by the calculator property. + * The default is an array of {@link ClusterIconStyle} elements whose properties are derived + * from the values for imagePath, imageExtension, and + * imageSizes. + * @property {boolean} [enableRetinaIcons=false] Whether to allow the use of cluster icons that + * have sizes that are some multiple (typically double) of their actual display size. Icons such + * as these look better when viewed on high-resolution monitors such as Apple's Retina displays. + * Note: if this property is true, sprites cannot be used as cluster icons. + * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the + * number of markers to be processed in a single batch when using a browser other than + * Internet Explorer (for Internet Explorer, use the batchSizeIE property instead). + * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is + * being used, markers are processed in several batches with a small delay inserted between + * each batch in an attempt to avoid Javascript timeout errors. Set this property to the + * number of markers to be processed in a single batch; select as high a number as you can + * without causing a timeout error in the browser. This number might need to be as low as 100 + * if 15,000 markers are being managed, for example. + * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH] + * The full URL of the root name of the group of image files to use for cluster icons. + * The complete file name is of the form imagePathn.imageExtension + * where n is the image file number (1, 2, etc.). + * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION] + * The extension name for the cluster icon image files (e.g., "png" or + * "jpg"). + * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES] + * An array of numbers containing the widths of the group of + * imagePathn.imageExtension image files. + * (The images are assumed to be square.) + */ +/** + * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}. + * @constructor + * @extends google.maps.OverlayView + * @param {google.maps.Map} map The Google map to attach to. + * @param {Array.} [opt_markers] The markers to be added to the cluster. + * @param {MarkerClustererOptions} [opt_options] The optional parameters. + */ +function MarkerClusterer(map, opt_markers, opt_options) { + // MarkerClusterer implements google.maps.OverlayView interface. We use the + // extend function to extend MarkerClusterer with google.maps.OverlayView + // because it might not always be available when the code is defined so we + // look for it at the last possible moment. If it doesn't exist now then + // there is no point going ahead :) + this.extend(MarkerClusterer, google.maps.OverlayView); + + opt_markers = opt_markers || []; + opt_options = opt_options || {}; + + this.markers_ = []; + this.clusters_ = []; + this.listeners_ = []; + this.activeMap_ = null; + this.ready_ = false; + + this.gridSize_ = opt_options.gridSize || 60; + this.minClusterSize_ = opt_options.minimumClusterSize || 2; + this.maxZoom_ = opt_options.maxZoom || null; + this.styles_ = opt_options.styles || []; + this.title_ = opt_options.title || ""; + this.zoomOnClick_ = true; + if (opt_options.zoomOnClick !== undefined) { + this.zoomOnClick_ = opt_options.zoomOnClick; + } + this.averageCenter_ = false; + if (opt_options.averageCenter !== undefined) { + this.averageCenter_ = opt_options.averageCenter; + } + this.ignoreHidden_ = false; + if (opt_options.ignoreHidden !== undefined) { + this.ignoreHidden_ = opt_options.ignoreHidden; + } + this.enableRetinaIcons_ = false; + if (opt_options.enableRetinaIcons !== undefined) { + this.enableRetinaIcons_ = opt_options.enableRetinaIcons; + } + this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH; + this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION; + this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES; + this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR; + this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE; + this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE; + this.clusterClass_ = opt_options.clusterClass || "cluster"; + + if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) { + // Try to avoid IE timeout when processing a huge number of markers: + this.batchSize_ = this.batchSizeIE_; + } + + this.setupStyles_(); + + this.addMarkers(opt_markers, true); + this.setMap(map); // Note: this causes onAdd to be called +} + + +/** + * Implementation of the onAdd interface method. + * @ignore + */ +MarkerClusterer.prototype.onAdd = function () { + var cMarkerClusterer = this; + + this.activeMap_ = this.getMap(); + this.ready_ = true; + + this.repaint(); + + // Add the map event listeners + this.listeners_ = [ + google.maps.event.addListener(this.getMap(), "zoom_changed", function () { + cMarkerClusterer.resetViewport_(false); + // Workaround for this Google bug: when map is at level 0 and "-" of + // zoom slider is clicked, a "zoom_changed" event is fired even though + // the map doesn't zoom out any further. In this situation, no "idle" + // event is triggered so the cluster markers that have been removed + // do not get redrawn. Same goes for a zoom in at maxZoom. + if (this.getZoom() === (this.get("minZoom") || 0) || this.getZoom() === this.get("maxZoom")) { + google.maps.event.trigger(this, "idle"); + } + }), + google.maps.event.addListener(this.getMap(), "idle", function () { + cMarkerClusterer.redraw_(); + }) + ]; +}; + + +/** + * Implementation of the onRemove interface method. + * Removes map event listeners and all cluster icons from the DOM. + * All managed markers are also put back on the map. + * @ignore + */ +MarkerClusterer.prototype.onRemove = function () { + var i; + + // Put all the managed markers back on the map: + for (i = 0; i < this.markers_.length; i++) { + if (this.markers_[i].getMap() !== this.activeMap_) { + this.markers_[i].setMap(this.activeMap_); + } + } + + // Remove all clusters: + for (i = 0; i < this.clusters_.length; i++) { + this.clusters_[i].remove(); + } + this.clusters_ = []; + + // Remove map event listeners: + for (i = 0; i < this.listeners_.length; i++) { + google.maps.event.removeListener(this.listeners_[i]); + } + this.listeners_ = []; + + this.activeMap_ = null; + this.ready_ = false; +}; + + +/** + * Implementation of the draw interface method. + * @ignore + */ +MarkerClusterer.prototype.draw = function () {}; + + +/** + * Sets up the styles object. + */ +MarkerClusterer.prototype.setupStyles_ = function () { + var i, size; + if (this.styles_.length > 0) { + return; + } + + for (i = 0; i < this.imageSizes_.length; i++) { + size = this.imageSizes_[i]; + this.styles_.push({ + url: this.imagePath_ + (i + 1) + "." + this.imageExtension_, + height: size, + width: size + }); + } +}; + + +/** + * Fits the map to the bounds of the markers managed by the clusterer. + */ +MarkerClusterer.prototype.fitMapToMarkers = function () { + var i; + var markers = this.getMarkers(); + var bounds = new google.maps.LatLngBounds(); + for (i = 0; i < markers.length; i++) { + bounds.extend(markers[i].getPosition()); + } + + this.getMap().fitBounds(bounds); +}; + + +/** + * Returns the value of the gridSize property. + * + * @return {number} The grid size. + */ +MarkerClusterer.prototype.getGridSize = function () { + return this.gridSize_; +}; + + +/** + * Sets the value of the gridSize property. + * + * @param {number} gridSize The grid size. + */ +MarkerClusterer.prototype.setGridSize = function (gridSize) { + this.gridSize_ = gridSize; +}; + + +/** + * Returns the value of the minimumClusterSize property. + * + * @return {number} The minimum cluster size. + */ +MarkerClusterer.prototype.getMinimumClusterSize = function () { + return this.minClusterSize_; +}; + +/** + * Sets the value of the minimumClusterSize property. + * + * @param {number} minimumClusterSize The minimum cluster size. + */ +MarkerClusterer.prototype.setMinimumClusterSize = function (minimumClusterSize) { + this.minClusterSize_ = minimumClusterSize; +}; + + +/** + * Returns the value of the maxZoom property. + * + * @return {number} The maximum zoom level. + */ +MarkerClusterer.prototype.getMaxZoom = function () { + return this.maxZoom_; +}; + + +/** + * Sets the value of the maxZoom property. + * + * @param {number} maxZoom The maximum zoom level. + */ +MarkerClusterer.prototype.setMaxZoom = function (maxZoom) { + this.maxZoom_ = maxZoom; +}; + + +/** + * Returns the value of the styles property. + * + * @return {Array} The array of styles defining the cluster markers to be used. + */ +MarkerClusterer.prototype.getStyles = function () { + return this.styles_; +}; + + +/** + * Sets the value of the styles property. + * + * @param {Array.} styles The array of styles to use. + */ +MarkerClusterer.prototype.setStyles = function (styles) { + this.styles_ = styles; +}; + + +/** + * Returns the value of the title property. + * + * @return {string} The content of the title text. + */ +MarkerClusterer.prototype.getTitle = function () { + return this.title_; +}; + + +/** + * Sets the value of the title property. + * + * @param {string} title The value of the title property. + */ +MarkerClusterer.prototype.setTitle = function (title) { + this.title_ = title; +}; + + +/** + * Returns the value of the zoomOnClick property. + * + * @return {boolean} True if zoomOnClick property is set. + */ +MarkerClusterer.prototype.getZoomOnClick = function () { + return this.zoomOnClick_; +}; + + +/** + * Sets the value of the zoomOnClick property. + * + * @param {boolean} zoomOnClick The value of the zoomOnClick property. + */ +MarkerClusterer.prototype.setZoomOnClick = function (zoomOnClick) { + this.zoomOnClick_ = zoomOnClick; +}; + + +/** + * Returns the value of the averageCenter property. + * + * @return {boolean} True if averageCenter property is set. + */ +MarkerClusterer.prototype.getAverageCenter = function () { + return this.averageCenter_; +}; + + +/** + * Sets the value of the averageCenter property. + * + * @param {boolean} averageCenter The value of the averageCenter property. + */ +MarkerClusterer.prototype.setAverageCenter = function (averageCenter) { + this.averageCenter_ = averageCenter; +}; + + +/** + * Returns the value of the ignoreHidden property. + * + * @return {boolean} True if ignoreHidden property is set. + */ +MarkerClusterer.prototype.getIgnoreHidden = function () { + return this.ignoreHidden_; +}; + + +/** + * Sets the value of the ignoreHidden property. + * + * @param {boolean} ignoreHidden The value of the ignoreHidden property. + */ +MarkerClusterer.prototype.setIgnoreHidden = function (ignoreHidden) { + this.ignoreHidden_ = ignoreHidden; +}; + + +/** + * Returns the value of the enableRetinaIcons property. + * + * @return {boolean} True if enableRetinaIcons property is set. + */ +MarkerClusterer.prototype.getEnableRetinaIcons = function () { + return this.enableRetinaIcons_; +}; + + +/** + * Sets the value of the enableRetinaIcons property. + * + * @param {boolean} enableRetinaIcons The value of the enableRetinaIcons property. + */ +MarkerClusterer.prototype.setEnableRetinaIcons = function (enableRetinaIcons) { + this.enableRetinaIcons_ = enableRetinaIcons; +}; + + +/** + * Returns the value of the imageExtension property. + * + * @return {string} The value of the imageExtension property. + */ +MarkerClusterer.prototype.getImageExtension = function () { + return this.imageExtension_; +}; + + +/** + * Sets the value of the imageExtension property. + * + * @param {string} imageExtension The value of the imageExtension property. + */ +MarkerClusterer.prototype.setImageExtension = function (imageExtension) { + this.imageExtension_ = imageExtension; +}; + + +/** + * Returns the value of the imagePath property. + * + * @return {string} The value of the imagePath property. + */ +MarkerClusterer.prototype.getImagePath = function () { + return this.imagePath_; +}; + + +/** + * Sets the value of the imagePath property. + * + * @param {string} imagePath The value of the imagePath property. + */ +MarkerClusterer.prototype.setImagePath = function (imagePath) { + this.imagePath_ = imagePath; +}; + + +/** + * Returns the value of the imageSizes property. + * + * @return {Array} The value of the imageSizes property. + */ +MarkerClusterer.prototype.getImageSizes = function () { + return this.imageSizes_; +}; + + +/** + * Sets the value of the imageSizes property. + * + * @param {Array} imageSizes The value of the imageSizes property. + */ +MarkerClusterer.prototype.setImageSizes = function (imageSizes) { + this.imageSizes_ = imageSizes; +}; + + +/** + * Returns the value of the calculator property. + * + * @return {function} the value of the calculator property. + */ +MarkerClusterer.prototype.getCalculator = function () { + return this.calculator_; +}; + + +/** + * Sets the value of the calculator property. + * + * @param {function(Array., number)} calculator The value + * of the calculator property. + */ +MarkerClusterer.prototype.setCalculator = function (calculator) { + this.calculator_ = calculator; +}; + + +/** + * Returns the value of the batchSizeIE property. + * + * @return {number} the value of the batchSizeIE property. + */ +MarkerClusterer.prototype.getBatchSizeIE = function () { + return this.batchSizeIE_; +}; + + +/** + * Sets the value of the batchSizeIE property. + * + * @param {number} batchSizeIE The value of the batchSizeIE property. + */ +MarkerClusterer.prototype.setBatchSizeIE = function (batchSizeIE) { + this.batchSizeIE_ = batchSizeIE; +}; + + +/** + * Returns the value of the clusterClass property. + * + * @return {string} the value of the clusterClass property. + */ +MarkerClusterer.prototype.getClusterClass = function () { + return this.clusterClass_; +}; + + +/** + * Sets the value of the clusterClass property. + * + * @param {string} clusterClass The value of the clusterClass property. + */ +MarkerClusterer.prototype.setClusterClass = function (clusterClass) { + this.clusterClass_ = clusterClass; +}; + + +/** + * Returns the array of markers managed by the clusterer. + * + * @return {Array} The array of markers managed by the clusterer. + */ +MarkerClusterer.prototype.getMarkers = function () { + return this.markers_; +}; + + +/** + * Returns the number of markers managed by the clusterer. + * + * @return {number} The number of markers. + */ +MarkerClusterer.prototype.getTotalMarkers = function () { + return this.markers_.length; +}; + + +/** + * Returns the current array of clusters formed by the clusterer. + * + * @return {Array} The array of clusters formed by the clusterer. + */ +MarkerClusterer.prototype.getClusters = function () { + return this.clusters_; +}; + + +/** + * Returns the number of clusters formed by the clusterer. + * + * @return {number} The number of clusters formed by the clusterer. + */ +MarkerClusterer.prototype.getTotalClusters = function () { + return this.clusters_.length; +}; + + +/** + * Adds a marker to the clusterer. The clusters are redrawn unless + * opt_nodraw is set to true. + * + * @param {google.maps.Marker} marker The marker to add. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + */ +MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) { + this.pushMarkerTo_(marker); + if (!opt_nodraw) { + this.redraw_(); + } +}; + + +/** + * Adds an array of markers to the clusterer. The clusters are redrawn unless + * opt_nodraw is set to true. + * + * @param {Array.} markers The markers to add. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + */ +MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) { + var key; + for (key in markers) { + if (markers.hasOwnProperty(key)) { + this.pushMarkerTo_(markers[key]); + } + } + if (!opt_nodraw) { + this.redraw_(); + } +}; + + +/** + * Pushes a marker to the clusterer. + * + * @param {google.maps.Marker} marker The marker to add. + */ +MarkerClusterer.prototype.pushMarkerTo_ = function (marker) { + // If the marker is draggable add a listener so we can update the clusters on the dragend: + if (marker.getDraggable()) { + var cMarkerClusterer = this; + google.maps.event.addListener(marker, "dragend", function () { + if (cMarkerClusterer.ready_) { + this.isAdded = false; + cMarkerClusterer.repaint(); + } + }); + } + marker.isAdded = false; + this.markers_.push(marker); +}; + + +/** + * Removes a marker from the cluster. The clusters are redrawn unless + * opt_nodraw is set to true. Returns true if the + * marker was removed from the clusterer. + * + * @param {google.maps.Marker} marker The marker to remove. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + * @return {boolean} True if the marker was removed from the clusterer. + */ +MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) { + var removed = this.removeMarker_(marker); + + if (!opt_nodraw && removed) { + this.repaint(); + } + + return removed; +}; + + +/** + * Removes an array of markers from the cluster. The clusters are redrawn unless + * opt_nodraw is set to true. Returns true if markers + * were removed from the clusterer. + * + * @param {Array.} markers The markers to remove. + * @param {boolean} [opt_nodraw] Set to true to prevent redrawing. + * @return {boolean} True if markers were removed from the clusterer. + */ +MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) { + var i, r; + var removed = false; + + for (i = 0; i < markers.length; i++) { + r = this.removeMarker_(markers[i]); + removed = removed || r; + } + + if (!opt_nodraw && removed) { + this.repaint(); + } + + return removed; +}; + + +/** + * Removes a marker and returns true if removed, false if not. + * + * @param {google.maps.Marker} marker The marker to remove + * @return {boolean} Whether the marker was removed or not + */ +MarkerClusterer.prototype.removeMarker_ = function (marker) { + var i; + var index = -1; + if (this.markers_.indexOf) { + index = this.markers_.indexOf(marker); + } else { + for (i = 0; i < this.markers_.length; i++) { + if (marker === this.markers_[i]) { + index = i; + break; + } + } + } + + if (index === -1) { + // Marker is not in our list of markers, so do nothing: + return false; + } + + marker.setMap(null); + this.markers_.splice(index, 1); // Remove the marker from the list of managed markers + return true; +}; + + +/** + * Removes all clusters and markers from the map and also removes all markers + * managed by the clusterer. + */ +MarkerClusterer.prototype.clearMarkers = function () { + this.resetViewport_(true); + this.markers_ = []; +}; + + +/** + * Recalculates and redraws all the marker clusters from scratch. + * Call this after changing any properties. + */ +MarkerClusterer.prototype.repaint = function () { + var oldClusters = this.clusters_.slice(); + this.clusters_ = []; + this.resetViewport_(false); + this.redraw_(); + + // Remove the old clusters. + // Do it in a timeout to prevent blinking effect. + setTimeout(function () { + var i; + for (i = 0; i < oldClusters.length; i++) { + oldClusters[i].remove(); + } + }, 0); +}; + + +/** + * Returns the current bounds extended by the grid size. + * + * @param {google.maps.LatLngBounds} bounds The bounds to extend. + * @return {google.maps.LatLngBounds} The extended bounds. + * @ignore + */ +MarkerClusterer.prototype.getExtendedBounds = function (bounds) { + var projection = this.getProjection(); + + // Turn the bounds into latlng. + var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), + bounds.getNorthEast().lng()); + var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), + bounds.getSouthWest().lng()); + + // Convert the points to pixels and the extend out by the grid size. + var trPix = projection.fromLatLngToDivPixel(tr); + trPix.x += this.gridSize_; + trPix.y -= this.gridSize_; + + var blPix = projection.fromLatLngToDivPixel(bl); + blPix.x -= this.gridSize_; + blPix.y += this.gridSize_; + + // Convert the pixel points back to LatLng + var ne = projection.fromDivPixelToLatLng(trPix); + var sw = projection.fromDivPixelToLatLng(blPix); + + // Extend the bounds to contain the new bounds. + bounds.extend(ne); + bounds.extend(sw); + + return bounds; +}; + + +/** + * Redraws all the clusters. + */ +MarkerClusterer.prototype.redraw_ = function () { + this.createClusters_(0); +}; + + +/** + * Removes all clusters from the map. The markers are also removed from the map + * if opt_hide is set to true. + * + * @param {boolean} [opt_hide] Set to true to also remove the markers + * from the map. + */ +MarkerClusterer.prototype.resetViewport_ = function (opt_hide) { + var i, marker; + // Remove all the clusters + for (i = 0; i < this.clusters_.length; i++) { + this.clusters_[i].remove(); + } + this.clusters_ = []; + + // Reset the markers to not be added and to be removed from the map. + for (i = 0; i < this.markers_.length; i++) { + marker = this.markers_[i]; + marker.isAdded = false; + if (opt_hide) { + marker.setMap(null); + } + } +}; + + +/** + * Calculates the distance between two latlng locations in km. + * + * @param {google.maps.LatLng} p1 The first lat lng point. + * @param {google.maps.LatLng} p2 The second lat lng point. + * @return {number} The distance between the two points in km. + * @see http://www.movable-type.co.uk/scripts/latlong.html + */ +MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) { + var R = 6371; // Radius of the Earth in km + var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; + var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; + var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + var d = R * c; + return d; +}; + + +/** + * Determines if a marker is contained in a bounds. + * + * @param {google.maps.Marker} marker The marker to check. + * @param {google.maps.LatLngBounds} bounds The bounds to check against. + * @return {boolean} True if the marker is in the bounds. + */ +MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) { + return bounds.contains(marker.getPosition()); +}; + + +/** + * Adds a marker to a cluster, or creates a new cluster. + * + * @param {google.maps.Marker} marker The marker to add. + */ +MarkerClusterer.prototype.addToClosestCluster_ = function (marker) { + var i, d, cluster, center; + var distance = 40000; // Some large number + var clusterToAddTo = null; + for (i = 0; i < this.clusters_.length; i++) { + cluster = this.clusters_[i]; + center = cluster.getCenter(); + if (center) { + d = this.distanceBetweenPoints_(center, marker.getPosition()); + if (d < distance) { + distance = d; + clusterToAddTo = cluster; + } + } + } + + if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { + clusterToAddTo.addMarker(marker); + } else { + cluster = new Cluster(this); + cluster.addMarker(marker); + this.clusters_.push(cluster); + } +}; + + +/** + * Creates the clusters. This is done in batches to avoid timeout errors + * in some browsers when there is a huge number of markers. + * + * @param {number} iFirst The index of the first marker in the batch of + * markers to be added to clusters. + */ +MarkerClusterer.prototype.createClusters_ = function (iFirst) { + var i, marker; + var mapBounds; + var cMarkerClusterer = this; + if (!this.ready_) { + return; + } + + // Cancel previous batch processing if we're working on the first batch: + if (iFirst === 0) { + /** + * This event is fired when the MarkerClusterer begins + * clustering markers. + * @name MarkerClusterer#clusteringbegin + * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. + * @event + */ + google.maps.event.trigger(this, "clusteringbegin", this); + + if (typeof this.timerRefStatic !== "undefined") { + clearTimeout(this.timerRefStatic); + delete this.timerRefStatic; + } + } + + // Get our current map view bounds. + // Create a new bounds object so we don't affect the map. + // + // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug: + if (this.getMap().getZoom() > 3) { + mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(), + this.getMap().getBounds().getNorthEast()); + } else { + mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625)); + } + var bounds = this.getExtendedBounds(mapBounds); + + var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length); + + for (i = iFirst; i < iLast; i++) { + marker = this.markers_[i]; + if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { + if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) { + this.addToClosestCluster_(marker); + } + } + } + + if (iLast < this.markers_.length) { + this.timerRefStatic = setTimeout(function () { + cMarkerClusterer.createClusters_(iLast); + }, 0); + } else { + delete this.timerRefStatic; + + /** + * This event is fired when the MarkerClusterer stops + * clustering markers. + * @name MarkerClusterer#clusteringend + * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. + * @event + */ + google.maps.event.trigger(this, "clusteringend", this); + } +}; + + +/** + * Extends an object's prototype by another's. + * + * @param {Object} obj1 The object to be extended. + * @param {Object} obj2 The object to extend with. + * @return {Object} The new extended object. + * @ignore + */ +MarkerClusterer.prototype.extend = function (obj1, obj2) { + return (function (object) { + var property; + for (property in object.prototype) { + this.prototype[property] = object.prototype[property]; + } + return this; + }).apply(obj1, [obj2]); +}; + + +/** + * The default function for determining the label text and style + * for a cluster icon. + * + * @param {Array.} markers The array of markers represented by the cluster. + * @param {number} numStyles The number of marker styles available. + * @return {ClusterIconInfo} The information resource for the cluster. + * @constant + * @ignore + */ +MarkerClusterer.CALCULATOR = function (markers, numStyles) { + var index = 0; + var title = ""; + var count = markers.length.toString(); + + var dv = count; + while (dv !== 0) { + dv = parseInt(dv / 10, 10); + index++; + } + + index = Math.min(index, numStyles); + return { + text: count, + index: index, + title: title + }; +}; + + +/** + * The number of markers to process in one batch. + * + * @type {number} + * @constant + */ +MarkerClusterer.BATCH_SIZE = 2000; + + +/** + * The number of markers to process in one batch (IE only). + * + * @type {number} + * @constant + */ +MarkerClusterer.BATCH_SIZE_IE = 500; + + +/** + * The default root name for the marker cluster images. + * + * @type {string} + * @constant + */ +MarkerClusterer.IMAGE_PATH = "http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m"; + + +/** + * The default extension name for the marker cluster images. + * + * @type {string} + * @constant + */ +MarkerClusterer.IMAGE_EXTENSION = "png"; + + +/** + * The default array of sizes for the marker cluster images. + * + * @type {Array.} + * @constant + */ +MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90]; + +if (typeof String.prototype.trim !== 'function') { + /** + * IE hack since trim() doesn't exist in all browsers + * @return {string} The string with removed whitespace + */ + String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g, ''); + } +} + diff --git a/bower_components/angular-google-maps/lib/markerwithlabel.js b/bower_components/angular-google-maps/lib/markerwithlabel.js new file mode 100644 index 0000000..1a05c8e --- /dev/null +++ b/bower_components/angular-google-maps/lib/markerwithlabel.js @@ -0,0 +1,581 @@ +/** + * 1.1.9-patched + * @name MarkerWithLabel for V3 + * @version 1.1.8 [February 26, 2013] + * @author Gary Little (inspired by code from Marc Ridey of Google). + * @copyright Copyright 2012 Gary Little [gary at luxcentral.com] + * @fileoverview MarkerWithLabel extends the Google Maps JavaScript API V3 + * google.maps.Marker class. + *

        + * MarkerWithLabel allows you to define markers with associated labels. As you would expect, + * if the marker is draggable, so too will be the label. In addition, a marker with a label + * responds to all mouse events in the same manner as a regular marker. It also fires mouse + * events and "property changed" events just as a regular marker would. Version 1.1 adds + * support for the raiseOnDrag feature introduced in API V3.3. + *

        + * If you drag a marker by its label, you can cancel the drag and return the marker to its + * original position by pressing the Esc key. This doesn't work if you drag the marker + * itself because this feature is not (yet) supported in the google.maps.Marker class. + */ + +/*! + * + * 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. + */ + +/*jslint browser:true */ +/*global document,google */ + +/** + * @param {Function} childCtor Child class. + * @param {Function} parentCtor Parent class. + */ +function inherits(childCtor, parentCtor) { + /** @constructor */ + function tempCtor() {} + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor(); + /** @override */ + childCtor.prototype.constructor = childCtor; +} + +/** + * This constructor creates a label and associates it with a marker. + * It is for the private use of the MarkerWithLabel class. + * @constructor + * @param {Marker} marker The marker with which the label is to be associated. + * @param {string} crossURL The URL of the cross image =. + * @param {string} handCursor The URL of the hand cursor. + * @private + */ +function MarkerLabel_(marker, crossURL, handCursorURL) { + this.marker_ = marker; + this.handCursorURL_ = marker.handCursorURL; + + this.labelDiv_ = document.createElement("div"); + this.labelDiv_.style.cssText = "position: absolute; overflow: hidden;"; + + // Set up the DIV for handling mouse events in the label. This DIV forms a transparent veil + // in the "overlayMouseTarget" pane, a veil that covers just the label. This is done so that + // events can be captured even if the label is in the shadow of a google.maps.InfoWindow. + // Code is included here to ensure the veil is always exactly the same size as the label. + this.eventDiv_ = document.createElement("div"); + this.eventDiv_.style.cssText = this.labelDiv_.style.cssText; + + // This is needed for proper behavior on MSIE: + this.eventDiv_.setAttribute("onselectstart", "return false;"); + this.eventDiv_.setAttribute("ondragstart", "return false;"); + + // Get the DIV for the "X" to be displayed when the marker is raised. + this.crossDiv_ = MarkerLabel_.getSharedCross(crossURL); +} +inherits(MarkerLabel_, google.maps.OverlayView); + +/** + * Returns the DIV for the cross used when dragging a marker when the + * raiseOnDrag parameter set to true. One cross is shared with all markers. + * @param {string} crossURL The URL of the cross image =. + * @private + */ +MarkerLabel_.getSharedCross = function (crossURL) { + var div; + if (typeof MarkerLabel_.getSharedCross.crossDiv === "undefined") { + div = document.createElement("img"); + div.style.cssText = "position: absolute; z-index: 1000002; display: none;"; + // Hopefully Google never changes the standard "X" attributes: + div.style.marginLeft = "-8px"; + div.style.marginTop = "-9px"; + div.src = crossURL; + MarkerLabel_.getSharedCross.crossDiv = div; + } + return MarkerLabel_.getSharedCross.crossDiv; +}; + +/** + * Adds the DIV representing the label to the DOM. This method is called + * automatically when the marker's setMap method is called. + * @private + */ +MarkerLabel_.prototype.onAdd = function () { + var me = this; + var cMouseIsDown = false; + var cDraggingLabel = false; + var cSavedZIndex; + var cLatOffset, cLngOffset; + var cIgnoreClick; + var cRaiseEnabled; + var cStartPosition; + var cStartCenter; + // Constants: + var cRaiseOffset = 20; + var cDraggingCursor = "url(" + this.handCursorURL_ + ")"; + + // Stops all processing of an event. + // + var cAbortEvent = function (e) { + if (e.preventDefault) { + e.preventDefault(); + } + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + }; + + var cStopBounce = function () { + me.marker_.setAnimation(null); + }; + + this.getPanes().overlayImage.appendChild(this.labelDiv_); + this.getPanes().overlayMouseTarget.appendChild(this.eventDiv_); + // One cross is shared with all markers, so only add it once: + if (typeof MarkerLabel_.getSharedCross.processed === "undefined") { + this.getPanes().overlayImage.appendChild(this.crossDiv_); + MarkerLabel_.getSharedCross.processed = true; + } + + this.listeners_ = [ + google.maps.event.addDomListener(this.eventDiv_, "mouseover", function (e) { + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + this.style.cursor = "pointer"; + google.maps.event.trigger(me.marker_, "mouseover", e); + } + }), + google.maps.event.addDomListener(this.eventDiv_, "mouseout", function (e) { + if ((me.marker_.getDraggable() || me.marker_.getClickable()) && !cDraggingLabel) { + this.style.cursor = me.marker_.getCursor(); + google.maps.event.trigger(me.marker_, "mouseout", e); + } + }), + google.maps.event.addDomListener(this.eventDiv_, "mousedown", function (e) { + cDraggingLabel = false; + if (me.marker_.getDraggable()) { + cMouseIsDown = true; + this.style.cursor = cDraggingCursor; + } + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + google.maps.event.trigger(me.marker_, "mousedown", e); + cAbortEvent(e); // Prevent map pan when starting a drag on a label + } + }), + google.maps.event.addDomListener(document, "mouseup", function (mEvent) { + var position; + if (cMouseIsDown) { + cMouseIsDown = false; + me.eventDiv_.style.cursor = "pointer"; + google.maps.event.trigger(me.marker_, "mouseup", mEvent); + } + if (cDraggingLabel) { + if (cRaiseEnabled) { // Lower the marker & label + position = me.getProjection().fromLatLngToDivPixel(me.marker_.getPosition()); + position.y += cRaiseOffset; + me.marker_.setPosition(me.getProjection().fromDivPixelToLatLng(position)); + // This is not the same bouncing style as when the marker portion is dragged, + // but it will have to do: + try { // Will fail if running Google Maps API earlier than V3.3 + me.marker_.setAnimation(google.maps.Animation.BOUNCE); + setTimeout(cStopBounce, 1406); + } catch (e) {} + } + me.crossDiv_.style.display = "none"; + me.marker_.setZIndex(cSavedZIndex); + cIgnoreClick = true; // Set flag to ignore the click event reported after a label drag + cDraggingLabel = false; + mEvent.latLng = me.marker_.getPosition(); + google.maps.event.trigger(me.marker_, "dragend", mEvent); + } + }), + google.maps.event.addListener(me.marker_.getMap(), "mousemove", function (mEvent) { + var position; + if (cMouseIsDown) { + if (cDraggingLabel) { + // Change the reported location from the mouse position to the marker position: + mEvent.latLng = new google.maps.LatLng(mEvent.latLng.lat() - cLatOffset, mEvent.latLng.lng() - cLngOffset); + position = me.getProjection().fromLatLngToDivPixel(mEvent.latLng); + if (cRaiseEnabled) { + me.crossDiv_.style.left = position.x + "px"; + me.crossDiv_.style.top = position.y + "px"; + me.crossDiv_.style.display = ""; + position.y -= cRaiseOffset; + } + me.marker_.setPosition(me.getProjection().fromDivPixelToLatLng(position)); + if (cRaiseEnabled) { // Don't raise the veil; this hack needed to make MSIE act properly + me.eventDiv_.style.top = (position.y + cRaiseOffset) + "px"; + } + google.maps.event.trigger(me.marker_, "drag", mEvent); + } else { + // Calculate offsets from the click point to the marker position: + cLatOffset = mEvent.latLng.lat() - me.marker_.getPosition().lat(); + cLngOffset = mEvent.latLng.lng() - me.marker_.getPosition().lng(); + cSavedZIndex = me.marker_.getZIndex(); + cStartPosition = me.marker_.getPosition(); + cStartCenter = me.marker_.getMap().getCenter(); + cRaiseEnabled = me.marker_.get("raiseOnDrag"); + cDraggingLabel = true; + me.marker_.setZIndex(1000000); // Moves the marker & label to the foreground during a drag + mEvent.latLng = me.marker_.getPosition(); + google.maps.event.trigger(me.marker_, "dragstart", mEvent); + } + } + }), + google.maps.event.addDomListener(document, "keydown", function (e) { + if (cDraggingLabel) { + if (e.keyCode === 27) { // Esc key + cRaiseEnabled = false; + me.marker_.setPosition(cStartPosition); + me.marker_.getMap().setCenter(cStartCenter); + google.maps.event.trigger(document, "mouseup", e); + } + } + }), + google.maps.event.addDomListener(this.eventDiv_, "click", function (e) { + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + if (cIgnoreClick) { // Ignore the click reported when a label drag ends + cIgnoreClick = false; + } else { + google.maps.event.trigger(me.marker_, "click", e); + cAbortEvent(e); // Prevent click from being passed on to map + } + } + }), + google.maps.event.addDomListener(this.eventDiv_, "dblclick", function (e) { + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + google.maps.event.trigger(me.marker_, "dblclick", e); + cAbortEvent(e); // Prevent map zoom when double-clicking on a label + } + }), + google.maps.event.addListener(this.marker_, "dragstart", function (mEvent) { + if (!cDraggingLabel) { + cRaiseEnabled = this.get("raiseOnDrag"); + } + }), + google.maps.event.addListener(this.marker_, "drag", function (mEvent) { + if (!cDraggingLabel) { + if (cRaiseEnabled) { + me.setPosition(cRaiseOffset); + // During a drag, the marker's z-index is temporarily set to 1000000 to + // ensure it appears above all other markers. Also set the label's z-index + // to 1000000 (plus or minus 1 depending on whether the label is supposed + // to be above or below the marker). + me.labelDiv_.style.zIndex = 1000000 + (this.get("labelInBackground") ? -1 : +1); + } + } + }), + google.maps.event.addListener(this.marker_, "dragend", function (mEvent) { + if (!cDraggingLabel) { + if (cRaiseEnabled) { + me.setPosition(0); // Also restores z-index of label + } + } + }), + google.maps.event.addListener(this.marker_, "position_changed", function () { + me.setPosition(); + }), + google.maps.event.addListener(this.marker_, "zindex_changed", function () { + me.setZIndex(); + }), + google.maps.event.addListener(this.marker_, "visible_changed", function () { + me.setVisible(); + }), + google.maps.event.addListener(this.marker_, "labelvisible_changed", function () { + me.setVisible(); + }), + google.maps.event.addListener(this.marker_, "title_changed", function () { + me.setTitle(); + }), + google.maps.event.addListener(this.marker_, "labelcontent_changed", function () { + me.setContent(); + }), + google.maps.event.addListener(this.marker_, "labelanchor_changed", function () { + me.setAnchor(); + }), + google.maps.event.addListener(this.marker_, "labelclass_changed", function () { + me.setStyles(); + }), + google.maps.event.addListener(this.marker_, "labelstyle_changed", function () { + me.setStyles(); + }) + ]; +}; + +/** + * Removes the DIV for the label from the DOM. It also removes all event handlers. + * This method is called automatically when the marker's setMap(null) + * method is called. + * @private + */ +MarkerLabel_.prototype.onRemove = function () { + var i; + if (this.labelDiv_.parentNode !== null) + this.labelDiv_.parentNode.removeChild(this.labelDiv_); + if (this.eventDiv_.parentNode !== null) + this.eventDiv_.parentNode.removeChild(this.eventDiv_); + + // Remove event listeners: + for (i = 0; i < this.listeners_.length; i++) { + google.maps.event.removeListener(this.listeners_[i]); + } +}; + +/** + * Draws the label on the map. + * @private + */ +MarkerLabel_.prototype.draw = function () { + this.setContent(); + this.setTitle(); + this.setStyles(); +}; + +/** + * Sets the content of the label. + * The content can be plain text or an HTML DOM node. + * @private + */ +MarkerLabel_.prototype.setContent = function () { + var content = this.marker_.get("labelContent"); + if (typeof content.nodeType === "undefined") { + this.labelDiv_.innerHTML = content; + this.eventDiv_.innerHTML = this.labelDiv_.innerHTML; + } else { + this.labelDiv_.innerHTML = ""; // Remove current content + this.labelDiv_.appendChild(content); + content = content.cloneNode(true); + this.eventDiv_.appendChild(content); + } +}; + +/** + * Sets the content of the tool tip for the label. It is + * always set to be the same as for the marker itself. + * @private + */ +MarkerLabel_.prototype.setTitle = function () { + this.eventDiv_.title = this.marker_.getTitle() || ""; +}; + +/** + * Sets the style of the label by setting the style sheet and applying + * other specific styles requested. + * @private + */ +MarkerLabel_.prototype.setStyles = function () { + var i, labelStyle; + + // Apply style values from the style sheet defined in the labelClass parameter: + this.labelDiv_.className = this.marker_.get("labelClass"); + this.eventDiv_.className = this.labelDiv_.className; + + // Clear existing inline style values: + this.labelDiv_.style.cssText = ""; + this.eventDiv_.style.cssText = ""; + // Apply style values defined in the labelStyle parameter: + labelStyle = this.marker_.get("labelStyle"); + for (i in labelStyle) { + if (labelStyle.hasOwnProperty(i)) { + this.labelDiv_.style[i] = labelStyle[i]; + this.eventDiv_.style[i] = labelStyle[i]; + } + } + this.setMandatoryStyles(); +}; + +/** + * Sets the mandatory styles to the DIV representing the label as well as to the + * associated event DIV. This includes setting the DIV position, z-index, and visibility. + * @private + */ +MarkerLabel_.prototype.setMandatoryStyles = function () { + this.labelDiv_.style.position = "absolute"; + this.labelDiv_.style.overflow = "hidden"; + // Make sure the opacity setting causes the desired effect on MSIE: + if (typeof this.labelDiv_.style.opacity !== "undefined" && this.labelDiv_.style.opacity !== "") { + this.labelDiv_.style.MsFilter = "\"progid:DXImageTransform.Microsoft.Alpha(opacity=" + (this.labelDiv_.style.opacity * 100) + ")\""; + this.labelDiv_.style.filter = "alpha(opacity=" + (this.labelDiv_.style.opacity * 100) + ")"; + } + + this.eventDiv_.style.position = this.labelDiv_.style.position; + this.eventDiv_.style.overflow = this.labelDiv_.style.overflow; + this.eventDiv_.style.opacity = 0.01; // Don't use 0; DIV won't be clickable on MSIE + this.eventDiv_.style.MsFilter = "\"progid:DXImageTransform.Microsoft.Alpha(opacity=1)\""; + this.eventDiv_.style.filter = "alpha(opacity=1)"; // For MSIE + + this.setAnchor(); + this.setPosition(); // This also updates z-index, if necessary. + this.setVisible(); +}; + +/** + * Sets the anchor point of the label. + * @private + */ +MarkerLabel_.prototype.setAnchor = function () { + var anchor = this.marker_.get("labelAnchor"); + this.labelDiv_.style.marginLeft = -anchor.x + "px"; + this.labelDiv_.style.marginTop = -anchor.y + "px"; + this.eventDiv_.style.marginLeft = -anchor.x + "px"; + this.eventDiv_.style.marginTop = -anchor.y + "px"; +}; + +/** + * Sets the position of the label. The z-index is also updated, if necessary. + * @private + */ +MarkerLabel_.prototype.setPosition = function (yOffset) { + var position = this.getProjection().fromLatLngToDivPixel(this.marker_.getPosition()); + if (typeof yOffset === "undefined") { + yOffset = 0; + } + this.labelDiv_.style.left = Math.round(position.x) + "px"; + this.labelDiv_.style.top = Math.round(position.y - yOffset) + "px"; + this.eventDiv_.style.left = this.labelDiv_.style.left; + this.eventDiv_.style.top = this.labelDiv_.style.top; + + this.setZIndex(); +}; + +/** + * Sets the z-index of the label. If the marker's z-index property has not been defined, the z-index + * of the label is set to the vertical coordinate of the label. This is in keeping with the default + * stacking order for Google Maps: markers to the south are in front of markers to the north. + * @private + */ +MarkerLabel_.prototype.setZIndex = function () { + var zAdjust = (this.marker_.get("labelInBackground") ? -1 : +1); + if (typeof this.marker_.getZIndex() === "undefined") { + this.labelDiv_.style.zIndex = parseInt(this.labelDiv_.style.top, 10) + zAdjust; + this.eventDiv_.style.zIndex = this.labelDiv_.style.zIndex; + } else { + this.labelDiv_.style.zIndex = this.marker_.getZIndex() + zAdjust; + this.eventDiv_.style.zIndex = this.labelDiv_.style.zIndex; + } +}; + +/** + * Sets the visibility of the label. The label is visible only if the marker itself is + * visible (i.e., its visible property is true) and the labelVisible property is true. + * @private + */ +MarkerLabel_.prototype.setVisible = function () { + if (this.marker_.get("labelVisible")) { + this.labelDiv_.style.display = this.marker_.getVisible() ? "block" : "none"; + } else { + this.labelDiv_.style.display = "none"; + } + this.eventDiv_.style.display = this.labelDiv_.style.display; +}; + +/** + * @name MarkerWithLabelOptions + * @class This class represents the optional parameter passed to the {@link MarkerWithLabel} constructor. + * The properties available are the same as for google.maps.Marker with the addition + * of the properties listed below. To change any of these additional properties after the labeled + * marker has been created, call google.maps.Marker.set(propertyName, propertyValue). + *

        + * When any of these properties changes, a property changed event is fired. The names of these + * events are derived from the name of the property and are of the form propertyname_changed. + * For example, if the content of the label changes, a labelcontent_changed event + * is fired. + *

        + * @property {string|Node} [labelContent] The content of the label (plain text or an HTML DOM node). + * @property {Point} [labelAnchor] By default, a label is drawn with its anchor point at (0,0) so + * that its top left corner is positioned at the anchor point of the associated marker. Use this + * property to change the anchor point of the label. For example, to center a 50px-wide label + * beneath a marker, specify a labelAnchor of google.maps.Point(25, 0). + * (Note: x-values increase to the right and y-values increase to the top.) + * @property {string} [labelClass] The name of the CSS class defining the styles for the label. + * Note that style values for position, overflow, top, + * left, zIndex, display, marginLeft, and + * marginTop are ignored; these styles are for internal use only. + * @property {Object} [labelStyle] An object literal whose properties define specific CSS + * style values to be applied to the label. Style values defined here override those that may + * be defined in the labelClass style sheet. If this property is changed after the + * label has been created, all previously set styles (except those defined in the style sheet) + * are removed from the label before the new style values are applied. + * Note that style values for position, overflow, top, + * left, zIndex, display, marginLeft, and + * marginTop are ignored; these styles are for internal use only. + * @property {boolean} [labelInBackground] A flag indicating whether a label that overlaps its + * associated marker should appear in the background (i.e., in a plane below the marker). + * The default is false, which causes the label to appear in the foreground. + * @property {boolean} [labelVisible] A flag indicating whether the label is to be visible. + * The default is true. Note that even if labelVisible is + * true, the label will not be visible unless the associated marker is also + * visible (i.e., unless the marker's visible property is true). + * @property {boolean} [raiseOnDrag] A flag indicating whether the label and marker are to be + * raised when the marker is dragged. The default is true. If a draggable marker is + * being created and a version of Google Maps API earlier than V3.3 is being used, this property + * must be set to false. + * @property {boolean} [optimized] A flag indicating whether rendering is to be optimized for the + * marker. Important: The optimized rendering technique is not supported by MarkerWithLabel, + * so the value of this parameter is always forced to false. + * @property {string} [crossImage="http://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"] + * The URL of the cross image to be displayed while dragging a marker. + * @property {string} [handCursor="http://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"] + * The URL of the cursor to be displayed while dragging a marker. + */ +/** + * Creates a MarkerWithLabel with the options specified in {@link MarkerWithLabelOptions}. + * @constructor + * @param {MarkerWithLabelOptions} [opt_options] The optional parameters. + */ +function MarkerWithLabel(opt_options) { + opt_options = opt_options || {}; + opt_options.labelContent = opt_options.labelContent || ""; + opt_options.labelAnchor = opt_options.labelAnchor || new google.maps.Point(0, 0); + opt_options.labelClass = opt_options.labelClass || "markerLabels"; + opt_options.labelStyle = opt_options.labelStyle || {}; + opt_options.labelInBackground = opt_options.labelInBackground || false; + if (typeof opt_options.labelVisible === "undefined") { + opt_options.labelVisible = true; + } + if (typeof opt_options.raiseOnDrag === "undefined") { + opt_options.raiseOnDrag = true; + } + if (typeof opt_options.clickable === "undefined") { + opt_options.clickable = true; + } + if (typeof opt_options.draggable === "undefined") { + opt_options.draggable = false; + } + if (typeof opt_options.optimized === "undefined") { + opt_options.optimized = false; + } + opt_options.crossImage = opt_options.crossImage || "http" + (document.location.protocol === "https:" ? "s" : "") + "://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"; + opt_options.handCursor = opt_options.handCursor || "http" + (document.location.protocol === "https:" ? "s" : "") + "://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"; + opt_options.optimized = false; // Optimized rendering is not supported + + this.label = new MarkerLabel_(this, opt_options.crossImage, opt_options.handCursor); // Bind the label to the marker + + // Call the parent constructor. It calls Marker.setValues to initialize, so all + // the new parameters are conveniently saved and can be accessed with get/set. + // Marker.set triggers a property changed event (called "propertyname_changed") + // that the marker label listens for in order to react to state changes. + google.maps.Marker.apply(this, arguments); +} +inherits(MarkerWithLabel, google.maps.Marker); + +/** + * Overrides the standard Marker setMap function. + * @param {Map} theMap The map to which the marker is to be added. + * @private + */ +MarkerWithLabel.prototype.setMap = function (theMap) { + + // Call the inherited function... + google.maps.Marker.prototype.setMap.apply(this, arguments); + + // ... then deal with the label: + this.label.setMap(theMap); +}; \ No newline at end of file diff --git a/bower_components/angular-google-maps/package.json b/bower_components/angular-google-maps/package.json new file mode 100644 index 0000000..4937631 --- /dev/null +++ b/bower_components/angular-google-maps/package.json @@ -0,0 +1,46 @@ +{ + "name": "angular-google-maps", + "version": "1.1.13", + "description": "AngularJS directives for Google Maps", + "repository": { + "type": "git", + "url": "https://github.com/nlaplante/angular-google-maps.git" + }, + "scripts": { + "build": "grunt", + "example": "grunt example" + }, + "keywords": [ + "angularjs", + "googlemaps", + "directives", + "webcomponent" + ], + "license": "MIT", + "devDependencies": { + "bower": "~1.2.8", + "grunt-bump": "0.0.15", + "grunt-contrib-clean": "~0.5.0", + "grunt-contrib-coffee": "~0.7.0", + "grunt-contrib-compress": "~0.5.3", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-connect": "~0.5.0", + "grunt-contrib-copy": "~0.4.1", + "grunt-contrib-jasmine": "~0.6.4", + "grunt-contrib-jshint": "~0.7.2", + "grunt-contrib-uglify": "~0.2.6", + "grunt-contrib-watch": "~0.5.3", + "grunt-conventional-changelog": "~1.1.0", + "grunt-mkdir": "~0.1.1", + "grunt-open": "~0.2.2", + "grunt-template-jasmine-istanbul": "~0.3.1", + "grunt-template-jasmine-requirejs": "~0.2.0", + "lodash": "~2.4.1", + "mocha": "~1.9.0", + "should": "~1.2.0" + }, + "dependencies": { + "grunt-cli": "~0.1.11", + "grunt": "~0.4.2" + } +} diff --git a/bower_components/angular-google-maps/spec/coffee/bootstrap.coffee b/bower_components/angular-google-maps/spec/coffee/bootstrap.coffee new file mode 100644 index 0000000..85d35cc --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/bootstrap.coffee @@ -0,0 +1,5 @@ +#global jasmine protects +beforeEach -> + @googleTemp = window.google +afterEach -> + window.google = @googleTemp \ No newline at end of file diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/markers.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/markers.spec.coffee new file mode 100644 index 0000000..71def26 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/markers.spec.coffee @@ -0,0 +1,49 @@ +describe "markers directive test", -> + beforeEach -> + #TODO: These modules really need dependencies setup properly + module("google-maps.mocks") + module("google-maps") + module("google-maps.directives.api.utils") + + inject ($rootScope, $timeout, $compile, GoogleApiMock, $q) => + @rootScope = $rootScope + @timeout = $timeout + @compile = $compile + @apiMock = new GoogleApiMock() + @apiMock.mockAPI() + @apiMock.mockMap() + @markerCount = 0 + @marker = (opts) => @markerCount++ + @marker.prototype = @apiMock.getMarker().prototype + @apiMock.mockMarker(@marker) + + it "should add markers for each object in model", -> + #TODO: We ought to be able to make this test pass, just need to figure _async I think -MDB. + html = """ + + + + """ + scope = @rootScope.$new() + scope.items = [] + scope.map = {} + scope.map.zoom = 12 + scope.map.center = {longitude: 47, latitude: -27} + + + scope.$watch 'items', (nv) -> + console.log(nv) + + element = @compile(html)(scope) + scope.$apply() + expect(@markerCount).toEqual(0) + @timeout -> + toPush = {} + toPush.id = 0 + toPush.latitude = 47 + toPush.longitude = -27 + scope.items.push(toPush) + @timeout.flush() + scope.$apply() + expect(@markerCount).toEqual(1) + diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/models/child/marker-child-model.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/models/child/marker-child-model.spec.coffee new file mode 100644 index 0000000..e1ece69 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/models/child/marker-child-model.spec.coffee @@ -0,0 +1,97 @@ +describe "MarkerChildModel", -> + beforeEach -> + #define / inject values into the item we are testing... not a controller but it allows us to inject + angular.module('mockModule', ["google-maps"]) + module('mockModule') + module("google-maps.mocks") + inject (GoogleApiMock) => + @gmap = new GoogleApiMock() + @gmap.mockAPI() + @gmap.mockAnimation() + @gmap.mockLatLng() + @gmap.mockMarker() + @gmap.mockEvent() + #comparison variables + @index = 0 + @model = + icon: 'icon.png' + coords: + latitude: 90 + longitude: 90 + options: + animation: google.maps.Animation.BOUNCE + @iconKey = 'icon' + @coordsKey = 'coords' + @optionsKey = 'options' + + inject ($timeout, $rootScope, $controller, MarkerChildModel, MarkerManager) => + scope = $rootScope.$new() + scope.click = -> + scope.icon = @iconKey + scope.coords = @coordsKey + scope.options = @optionsKey + @subject = new MarkerChildModel(@model, scope, document.gMap, $timeout, defaults = {}, + doClick = (()->), + new MarkerManager(document.gMap, undefined, undefined)) + + it 'can be created', -> + expect(@subject).toBeDefined() + expect(@subject.scope).toBeDefined() + + it 'parentScope keys are set correctly', -> + expect(@subject.iconKey).toEqual(@iconKey) + expect(@subject.coordsKey).toEqual(@coordsKey) + expect(@subject.optionsKey).toEqual(@optionsKey) + + describe 'evalModelHandle()', -> + it 'scope values are equal to the model values by key', -> + #since evalHModelHandle does not use => and uses -> + #it is a prototype function which is more static, and kinda private.. as in not obvious to find + #2 ways to get to it instance.__proto___.function or classType.prototype.function + # equates to @subject.__proto__.evalModelHandle or directives.api.model.child.MarkerChildModel.prototype.evalModelHandle + expect(@subject.__proto__.evalModelHandle(@model, @iconKey)).toEqual(@model.icon) + expect(@subject.__proto__.evalModelHandle(@model, @coordsKey)).toEqual(@model.coords) + expect(@subject.__proto__.evalModelHandle(@model, @optionsKey)).toEqual(@model.options) + it 'updates an existing models properties via watch, icon', -> + @model.icon = 'test.png' + expect(@subject.__proto__.evalModelHandle(@model, @iconKey)).toEqual(@model.icon) + it 'updates an existing models properties via watch, coords', -> + @model.coords.latitude = 91 + expect(@subject.__proto__.evalModelHandle(@model, @coordsKey)).toEqual(@model.coords) + it 'updates an existing models properties via watch, options', -> + @model.options = 'options2' + expect(@subject.__proto__.evalModelHandle(@model, @optionsKey)).toEqual(@model.options) + it 'undefined model returns undefined', -> + expect(@subject.__proto__.evalModelHandle(undefined, @optionsKey)).toEqual(undefined) + + it 'modelKey of self returns model', -> + expect(@subject.__proto__.evalModelHandle(@model, 'self')).toEqual(@model) + it 'modelKey of undefined returns undefined', -> + expect(@subject.__proto__.evalModelHandle(@model, undefined)).toEqual(undefined) + + describe 'maybeSetScopeValue()', -> + beforeEach(-> + @gSetterCalled = false + @isInit = false + @gSetter = (scope)=> + @gSetterCalled = true + ) + it "oldModel undefined, isInit false - changes scope's models value, and calls gSetter ", -> + newModel = + icon: 'someIcon' + @subject.scope.icon = 'junk' + @subject.maybeSetScopeValue('icon', newModel, undefined, @iconKey, + @subject.__proto__.evalModelHandle, @isInit, @gSetter) + expect(@gSetterCalled).toEqual(true) + expect(@subject.scope.icon).toEqual(newModel.icon) + + + describe 'destroy()', -> + it 'wipes internal scope', -> + @subject.destroy() + expect(@subject.scope.$$destroyed).toEqual(true) + + it 'wipes gMarker', -> + @subject.destroy() + expect(@subject.gMarker).toEqual(undefined) + expect(@subject.gMarkerManager.gMarkers.length).toEqual(0) \ No newline at end of file diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/models/child/window-child-model.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/models/child/window-child-model.spec.coffee new file mode 100644 index 0000000..b7c3985 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/models/child/window-child-model.spec.coffee @@ -0,0 +1,53 @@ +describe "WindowChildModel", -> + beforeEach -> + if window.InfoBox + @infoBoxRealTemp = window.InfoBox + else + window.InfoBox = (opt_opts) -> + opt_opts = opt_opts || {} + @boxClass_ = opt_opts.boxClass || "infoBox" + @content_ = opt_opts.content || ""; + @div_ = document.createElement("div") + @div_.className = @boxClass_ + + @scope = + coords: + latitude: 90.0 + longitude: 89.0 + show: true + @commonOpts = + position: new google.maps.LatLng(@scope.coords.latitude, @scope.coords.longitude) + @windowOpts = _.extend(@commonOpts, content: 'content') + @gMarker = new google.maps.Marker(@commonOpts) + #define / inject values into the item we are testing... not a controller but it allows us to inject + angular.module('mockModule', ["google-maps"]) + .value('isIconVisibleOnClick', true) + .value('model', @scope) + .value('mapCtrl', document.gMap) + .value('markerCtrl', @gMarker) + .value('opts', @windowOpts) + .value('element', 'hi') + .value('needToManualDestroy', false) + .value('markerIsVisibleAfterWindowClose', true) + .controller 'childModel', (WindowChildModel) -> + WindowChildModel + + angular.mock.module('mockModule') + + it 'can be created', -> + inject(($http, $rootScope, $templateCache, $compile, $controller) => + scope = $rootScope.$new() + _.extend(@scope, scope) + @subject = $controller('childModel', scope: scope) + ) + expect(@subject != undefined).toEqual(true) + expect(@subject.index).toEqual(@index) + + it 'can be created with the infoBoxplugin', -> + inject(($http, $rootScope, $templateCache, $compile, $controller) => + scope = $rootScope.$new() + _.extend(@scope, scope) + @subject = $controller('childModel', scope: scope) + ) + expect(@subject != undefined).toEqual(true) + expect(@subject.index).toEqual(@index) \ No newline at end of file diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/i-marker-parent.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/i-marker-parent.spec.coffee new file mode 100644 index 0000000..2cf243e --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/i-marker-parent.spec.coffee @@ -0,0 +1,64 @@ +describe "IMarkerParentModel", -> + beforeEach -> + angular.mock.module("google-maps.directives.api.models.parent") + + @clickCount = 0 + inject ($rootScope, $timeout, $compile, $http, $templateCache, $interpolate, IMarkerParentModel) => + @rootScope = $rootScope + @scope = $rootScope.$new() + @ele = $compile('')(@scope) + @attrs = {click: @click} + @IMarkerParentModel = IMarkerParentModel + @$timeout = $timeout + @scope.click = () => + @clickCount++ + + it "should instantiate", -> + @scope.coords = { + latitude: 47, + longitude: -27 + } + subject = new @IMarkerParentModel(@scope, @ele, @attrs, null, @$timeout) + expect(subject?).toEqual(true) + + it "should validate a scope correctly", -> + try + @subject = new @IMarkerParentModel(@scope, @ele, @attrs, null, @$timeout) + expect(false).toEqual(true) + catch e + expect(e).toEqual("Unable to construct IMarkerParentModel due to invalid scope") + + @scope.coords = { + latitude: 47, + longitude: -27 + } + @subject = new @IMarkerParentModel(@scope, @ele, @attrs, null, @$timeout) + expect(@subject.validateScope(@scope)).toEqual(true) + + it "should call watch on timeout for correct properties", -> + props = [] + expectedProps = 'coords icon options'.split(' ') + @IMarkerParentModel.prototype.watch = (prop, scope) => + props.push(prop) + + @scope.coords = { + latitude: 47, + longitude: -27 + } + + @subject = new @IMarkerParentModel(@scope, @ele, @attrs, null, @$timeout) + expect(props[i]).toEqual(prop) for prop, i in expectedProps + + describe "IMarkerParentModel method tests", -> + beforeEach -> + @scope.coords = { + latitude: 47, + longitude: -27 + } + @subject = new @IMarkerParentModel(@scope, @ele, @attrs, null, @$timeout) + + it "should throw onWatch", -> + expect(@subject.onWatch).toThrow() + + it "should throw onDestroy", -> + expect(@subject.onDestroy).toThrow() \ No newline at end of file diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/layer-parent-model.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/layer-parent-model.spec.coffee new file mode 100644 index 0000000..1d279d0 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/layer-parent-model.spec.coffee @@ -0,0 +1,51 @@ +describe "LayerParentModelSpec", -> + beforeEach -> + module("google-maps.mocks") + angular.module('mockModule', ["google-maps"]) + .value('mapCtrl', {}) + .value('element', {}) + .value('attrs', {}) + .value('model', {}) + .value('scope', @scope) + + module "mockModule" + inject (GoogleApiMock) -> + mock = new GoogleApiMock() + mock.mockAPI() + + @scope = + options : + blah:true + $watch:()-> + $on:()-> + @attrs = + type:"testLayer" + options:"someBoundAttr" + self = @ + @setOpts + @tempMaps = google.maps + google.maps.testLayer = (opts)=> + self.setOpts = opts + setMap:()-> + + @mapCtrl = {} + + @timeout = (fnc,time) => + fnc() + + inject ($rootScope, LayerParentModel) => + scope = $rootScope.$new() + @constructor = LayerParentModel + @scope = _.extend @scope, scope + @subject = new @constructor(@scope,{},@attrs,@mapCtrl) + + afterEach -> + google.map = @tempMaps + + it "constructor is defined", -> + expect(@constructor).toBeDefined() + it "subject is defined", -> + expect(@subject).toBeDefined() + + it "options set", -> + expect(@setOpts.blah).toBe(@scope.options.blah) \ No newline at end of file diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/marker-parent-model.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/marker-parent-model.spec.coffee new file mode 100644 index 0000000..0f42626 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/marker-parent-model.spec.coffee @@ -0,0 +1,106 @@ +describe "MarkerParentModel", -> + afterEach -> + window.google.maps = @gMapsTemp + beforeEach -> + module "google-maps.mocks" + #define / inject values into the item we are testing... not a controller but it allows us to inject + angular.module('mockModule', ["google-maps"]) + .value('mapCtrl', + getMap: ()-> + document.gMap) + .value('element', {}) + .value('attrs', click:true) + .value('model', {}) + .value('scope', @scope) + + module "mockModule" + inject (GoogleApiMock) => + @gmap = new GoogleApiMock() + @gmap.mockAPI() + @gmap.mockAnimation() + @gmap.mockLatLng() + @gmap.mockMarker() + #@gmap.mockInfoWindow() + @gmap.mockEvent() + @gMapsTemp = window.google.maps + #comparison variables + @index = 0 + @clicked = false + self = @ + @scope = + icon: 'icon.png' + coords: + latitude: 90 + longitude: 90 + options: + animation: google.maps.Animation.BOUNCE + events: + click: (marker, eventName, args) -> + self.clicked = true + self.gMarkerSetEvent = marker + + #mocking google maps event listener + @googleMapListeners = [] + window.google.maps.event.addListener = (thing, eventName, callBack) => + found = _.find @googleMapListeners, (obj)-> + obj.obj == thing + + unless found? + toPush = {} + toPush.obj = thing + toPush.events = {} + toPush.events[eventName] = callBack + @googleMapListeners.push toPush + + else + found.events[eventName] = callBack + + @fireListener = (thing, eventName) => + found = _.find @googleMapListeners, (obj)-> + obj.obj == thing + + found.events[eventName](found.obj) if found? + inject ($rootScope, $timeout, element, attrs, mapCtrl, MarkerParentModel, MarkerManager) => + scope = $rootScope.$new() + @scope = _.extend @scope, scope + @testCtor = MarkerParentModel + @subject = new MarkerParentModel(@scope, element, attrs, mapCtrl, $timeout, new MarkerManager(mapCtrl.getMap()), false) + + @subject.setEvents(@, @scope) + + it 'constructor exist', -> + expect(@testCtor?).toEqual(true) + + it 'can be created', -> + expect(@subject?).toEqual(true) + + describe "validateScope", -> + it 'returns fals with scope undefined', -> + expect(@subject.validateScope(undefined)).toEqual(false) + it 'returns fals with scope.coords undefined', -> + expect(@subject.validateScope({coords: undefined})).toEqual(false) + it 'returns fals with scope.coords,latitude undefined', -> + expect(@subject.validateScope({coords: {latitude: undefined, longitude: {}}})).toEqual(true) + it 'returns fals with scope.coords.longtitude undefined', -> + expect(@subject.validateScope({coords: {latitude: {}, longitude: undefined }})).toEqual(true) + + + it 'fake googleMapListeners can be fired - to prove mocke of google.maps.event.addListener works', -> + testPass = false + window.google.maps.event.addListener @, "junk", ()=> + testPass = true + @fireListener(@, "junk") + expect(testPass).toBeTruthy() + + it "googleMapListeners is fired through MarkerParentModel's scope.events", -> + expect(@clicked).toBeFalsy() + @fireListener(@, "click") + expect(@clicked).toBeTruthy() + + #TODO: This test is failing because subject.scope.gmarker is not the same as the googleMapListener object + # When the obj.obj is added to the googleMapListeners it is the same, but gets modified later, somehow + xit "googleMapListeners is fired through MarkerParentModel's scope.events with an optional marker", -> + expect(@gMarkerSetEvent).toBeUndefined() + @fireListener(@subject.scope.gMarker, "click") + expect(@gMarkerSetEvent).toBeDefined() + expect(@gMarkerSetEvent.position).toBeDefined() \ No newline at end of file diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/markers-parent-model.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/markers-parent-model.spec.coffee new file mode 100644 index 0000000..9473ba9 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/markers-parent-model.spec.coffee @@ -0,0 +1,100 @@ +# TODO: These tests are failing because something is not mocked correctly +# in the clusterermanager. Likely google.maps.overlayview. +describe "MarkersParentModel - Clusterer Event Extensions", -> + afterEach -> + self.markerModelsCluster = undefined + beforeEach -> + @clusterTest = + getMarkers: -> + [ + {key: 1}, + {key: 2} + ] + @index = 0 + @clicked = false + self = @ + @scope = + icon: 'icon.png' + coords: + latitude: 90 + longitude: 90 + events: + click: (marker, eventName, args) -> + self.clicked = true + self.gMarkerSetEvent = marker + clusterOptions: {} + clusterEvents: + click: (cluster, markerModelsCluster) -> + self.markerModelsCluster = markerModelsCluster + mouseout: (cluster, markerModelsCluster) -> + self.markerModelsCluster = markerModelsCluster + mouseover: (cluster, markerModelsCluster) -> + self.markerModelsCluster = markerModelsCluster + crap: -> + self.markerModelsCluster = "crap" + doCluster: "true" + models: [] + + #define / inject values into the item we are testing... not a controller but it allows us to inject + angular.module('mockModule', ["google-maps","google-maps.mocks"]) + .value('map', document.gMap) + .value('element', {}) + .value('attrs', click: true) + .value('model', {}) + .value('scope', @scope) + + module "mockModule" + inject (GoogleApiMock) => + @gmap = new GoogleApiMock(false) + @gmap.mockEvent() + + inject ($rootScope, element, attrs, map, MarkersParentModel) => + scope = $rootScope.$new() + $timeout = ((fn)-> + fn()) + @scope = _.extend @scope, scope + @scope.options = + animation: google.maps.Animation.BOUNCE + @testCtor = MarkersParentModel + @fireListener = window.google.maps.event.fireListener + @subject = new @testCtor(@scope, element, attrs, map, $timeout) + @subject + + it 'constructor exist', -> + expect(@testCtor).toBeDefined() + + it 'can be created', -> + expect(@subject?).toBeDefined() + + describe "clusterEvents", -> + describe "basic event handling", -> + describe "is fired", -> + describe "mapped extension", -> + it 'click - ', -> + @subject.scope.markerModels.put 1, model: "test1" + @subject.scope.markerModels.put 2, model: "test2" + @subject.clusterInternalOptions.click @clusterTest + expect(_.all(@markerModelsCluster, (entity, i)=> + entity == @subject.scope.markerModels[i+1].model + )).toBeTruthy() + it 'mouseout - ', -> + @subject.scope.markerModels.put 1, model: "test1" + @subject.scope.markerModels.put 2, model: "test2" + @subject.clusterInternalOptions.mouseout @clusterTest + expect(_.all(@markerModelsCluster, (entity, i)=> + entity == @subject.scope.markerModels[i+1].model + )).toBeTruthy() + it 'mouseover - ', -> + @subject.scope.markerModels.put 1, model: "test1" + @subject.scope.markerModels.put 2, model: "test2" + @subject.clusterInternalOptions.mouseover @clusterTest + expect(_.all(@markerModelsCluster, (entity, i)=> + entity == @subject.scope.markerModels[i+1].model + )).toBeTruthy() + describe "some legacy event", => + it 'crap - ', -> #not a real event but shows that any existing function can be fired + @subject.gMarkerManager.opt_events.crap() + expect(@markerModelsCluster).toBe("crap") + describe "not fired", -> + it 'click - ', -> + diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/markers-parent.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/markers-parent.spec.coffee new file mode 100644 index 0000000..06d189a --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/markers-parent.spec.coffee @@ -0,0 +1,228 @@ +xdescribe "MarkersParentModel", -> + beforeEach -> + angular.mock.module "google-maps.directives.api.models.parent", ($provide) => + @provide = $provide + class MarkerManager + constructor: (args...) -> + @args = args + @constructed = true + @drawCalled = 0 + @clearCalled = 0 + @fitCalled = 0 + draw: () -> + @drawCalled++ + clear: () -> + @clearCalled++ + fit:() -> + @fitCalled++ + + class ClustererMarkerManager extends MarkerManager + constructor: (args...) -> + super(args) + @constructed = true + @childConstructed = true + + class MarkerChildModel + constructor: (@args...) -> + @constructed = true + + @clustererMarkerManager = ClustererMarkerManager + @markerManager = MarkerManager + @markerChildModel = MarkerChildModel + @provide.decorator 'ClustererMarkerManager', () => @clustererMarkerManager + @provide.decorator 'MarkerManager', () => @markerManager + @provide.decorator 'MarkerChildModel', () => @markerChildModel + + @clickCount = 0 + inject ($rootScope, $timeout, $compile, $http, $templateCache, $interpolate, MarkersParentModel) => + @rootScope = $rootScope + @scope = $rootScope.$new() + @ele = $compile('')(@scope) + @attrs = {click: @click} + @MarkersParentModel = MarkersParentModel + @$timeout = $timeout + @scope.click = () => + @clickCount++ + + @map = jasmine.createSpyObj('map') + @subject = new @MarkersParentModel(@scope, @ele, @attrs, @map, @$timeout) + + it "should instantiate", -> + expect(@subject?).toEqual(true) + + it "should validate a scope correctly", -> + #XXX: Should this really validate as true if no models is set? + expect(@subject.validateScope(@scope)).toEqual(true) + @scope.models = [{ + latitude: 47, + longitude: -27 + }] + expect(@subject.validateScope(@scope)).toEqual(false) + @scope.coords = { + latitude: 47, + longitude: -27 + } + expect(@subject.validateScope(@scope)).toEqual(true) + + it "should watch the appropriate properties on timeout", -> + props = [] + expectedProps = 'models doCluster clusterOptions clusterEvents fit idKey'.split(' ') + @scope.$watch = (propName, func) => + props.push(propName) + + spyOn(@subject, 'createMarkersFromScratch') + @subject.onTimeOut(@scope) + #expect(props[i]).toEqual(prop) for prop, i in expectedProps + expect(@subject.createMarkersFromScratch).toHaveBeenCalled() + + describe "watch tests", -> + beforeEach -> + spyOn(@subject, 'reBuildMarkers') + spyOn(@subject, 'pieceMealMarkers') + @idKey = @subject.idKey + + it "should watch rebuild markers and not change idKey", -> + @subject.onWatch('foo', @scope, 'baz', 'bar') + expect(@subject.idKey).toEqual(@idKey) + expect(@subject.pieceMealMarkers).not.toHaveBeenCalled() + expect(@subject.reBuildMarkers).toHaveBeenCalled() + + it "should watch and rebuild markers, but change idKey", -> + @subject.onWatch('idKey', @scope, 'foo', @idKey) + expect(@subject.idKey).toEqual('foo') + expect(@subject.pieceMealMarkers).not.toHaveBeenCalled() + expect(@subject.reBuildMarkers).toHaveBeenCalled() + + it "should watch and build piecemeal and not change idKey", -> + @subject.doRebuildAll = false + @subject.onWatch('foo', @scope, 'foo', @idKey) + expect(@subject.idKey).toEqual(@idKey) + expect(@subject.pieceMealMarkers).toHaveBeenCalled() + expect(@subject.reBuildMarkers).not.toHaveBeenCalled() + + it "should watch and build piecemeal and change idKey", -> + @subject.doRebuildAll = false + @subject.onWatch('idKey', @scope, 'foo', @idKey) + expect(@subject.idKey).toEqual('foo') + expect(@subject.pieceMealMarkers).toHaveBeenCalled() + expect(@subject.reBuildMarkers).not.toHaveBeenCalled() + + #TODO: need to get some negative testing in here and validate parameters a bit more + describe "createMarkers from scratch tests", -> + beforeEach -> + spyOn(@subject, 'newChildMarker') + # create a ClustererMarkerManager so that we fall into the else + @subject.gMarkerManager = new @clustererMarkerManager() + + it "should call ClustererMarkerManager", -> + @scope.doCluster = true + @scope.clusterOptions = {} + @subject.createMarkersFromScratch(@scope) + expect(@subject.gMarkerManager.childConstructed).toEqual(true) + + it "should call not call ClustererMarkerManager when markerManager is set and options are the same as scope options", -> + @scope.doCluster = true + @scope.clusterOptions = {} + @subject.gMarkerManager.opt_options = @scope.clusterOptions + # create a ClustererMarkerManager so that we fall into the else + # Set mock value to false, so we can verify if it gets called or not + @subject.gMarkerManager.childConstructed = false + @subject.createMarkersFromScratch(@scope) + expect(@subject.gMarkerManager.childConstructed).toEqual(false) + + it "should call ClustererMarkerManager when markerManager is set and options options are not the same", -> + @scope.doCluster = true + @scope.clusterOptions = {} + # Set mock value to false, so we can verify if it gets called or not + @subject.gMarkerManager.childConstructed = false + @subject.createMarkersFromScratch(@scope) + expect(@subject.gMarkerManager.childConstructed).toEqual(true) + + it "should call clustererMarkerManager when doCluster is true and no clusterOptions set", -> + @scope.doCluster = true + @subject.createMarkersFromScratch(@scope) + expect(@subject.gMarkerManager.childConstructed).toEqual(true) + expect(@subject.gMarkerManager.args.length).toEqual(1) + + it "should call generic MarkerManager when no cluster options are set", -> + @subject.createMarkersFromScratch(@scope) + expect(@subject.gMarkerManager.childConstructed).toEqual(undefined) + + it "should call newChildMarker for each model and fit should not be called", -> + @scope.models = [ + {}, {} + ] + @subject.createMarkersFromScratch(@scope) + expect(@subject.newChildMarker.calls.length).toEqual(2) + expect(@subject.gMarkerManager.fitCalled).toBe(0) + + #TODO: Should flesh out these tests a bit more so that the async loop is tested, right now _async is not very testable + # because the timeout is not able to made synchronous. Passing $timeout could fix this. + + describe "Rebuild markers", -> + @beforeEach -> + spyOn(@subject, 'onDestroy') + spyOn(@subject, 'createMarkersFromScratch') + + it "should return without doRebuild", -> + @scope.doRebuild = false + @subject.reBuildMarkers(@scope) + expect(@subject.onDestroy).not.toHaveBeenCalled() + + it "should call on destroy and createMarkersFromScratch", -> + @subject.reBuildMarkers(@scope) + expect(@subject.onDestroy).toHaveBeenCalled() + expect(@subject.createMarkersFromScratch).toHaveBeenCalled() + + # TODO: This needs to be fleshed out significantly once _async.each is testable + describe "pieceMealMarkers", -> + @beforeEach -> + spyOn(@subject, 'figureOutState') + spyOn(@subject, 'reBuildMarkers') + spyOn(@subject, 'newChildMarker') + + it "should call reBuildMarkers", -> + @subject.pieceMealMarkers(@scope) + expect(@subject.reBuildMarkers).toHaveBeenCalled() + + it "should call figureOutState", -> + @scope.models = [ + {} + ] + + # What is markerModels? + @scope.markerModels = [ + {} + ] + @subject.pieceMealMarkers(@scope) + expect(@subject.figureOutState).toHaveBeenCalled() + + describe "newChildMarker", -> + it "should return undefined, but call constructor", -> + expect(@subject.newChildMarker({}, @scope)).toEqual(undefined) + + it "should return a new childMarker", -> + model = {} + model[@subject.idKey] = "foo" + @scope.markerModels = {put :->} + spyOn(@scope.markerModels, 'put') + child = @subject.newChildMarker(model, @scope) + expect(child.constructed).toEqual(true) + expect(@scope.markerModels.put.calls[0].args[0]).toEqual('foo') + expect(@scope.markerModels.put.calls[0].args[1]).toEqual(child) + + #TODO: need to figure out what to test here, looks like some refactoring in the method + # is in order anyway, so won't put too much in here. + describe "onDestroy", -> + it "should succeed", -> + @subject.onDestroy(@scope) + + #TODO: This needs to be fleshed out a lot once _async is testable + describe "fit", -> + it "should succeed", -> + @subject.gMarkerManager = new @clustererMarkerManager() + @subject.gMarkerManager.fit() + + + + diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/windows-parent.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/windows-parent.spec.coffee new file mode 100644 index 0000000..cc75428 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/models/parent/windows-parent.spec.coffee @@ -0,0 +1,7 @@ +describe "WindowsParentModel", -> + beforeEach -> + angular.mock.module("google-maps.directives.api.models.parent") + + inject($rootScope, $timeout, $compile, $http, $templateCache, $interpolate) => + @scope = $rootScope.$new() + @subject = new WindowsParentModel(@scope, ) diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/utils/_async.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/utils/_async.spec.coffee new file mode 100644 index 0000000..afa2406 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/utils/_async.spec.coffee @@ -0,0 +1,60 @@ +describe "_async", -> + beforeEach -> + @subject = _async + + it "handle callback passes an index", (done) -> + _async.each [1], (thing,index)-> + expect(thing).toEqual 1 + expect(index).toEqual 0 + done() + + it "handle array of 101 outputs 101 elements equal to the original, with 1 pauses", (done) -> + known = _.range(101) + test = [] + pauses = 1 + @subject.each(known,((num) -> test.push(num)),()-> + done() + expect(pauses).toEqual(2) + expect(test.length).toEqual(known.length) + expect(test).toEqual(known) + ,(()-> pauses++),100) + + it "handle array of 200 outputs 200 elements equal to the original, with 2 pauses", (done) -> + known = _.range(200) + test = [] + pauses = 1 + running = true + @subject.each(known,((num) -> test.push(num)),()-> + done() + expect(pauses).toEqual(2) + expect(test.length).toEqual(known.length) + expect(test).toEqual(known) + ,(()-> pauses++),100) + + it "handle array of 1000 outputs 1000 elements equal to the original, with 10 pauses", (done) -> + known = _.range(1000) + test = [] + pauses = 1 + @subject.each(known,((num) -> test.push(num)),()-> + done() + expect(pauses).toEqual(10) + expect(test.length).toEqual(known.length) + expect(test).toEqual(known) + ,(()-> pauses++),100) + + + it "handle map of 1000 outputs 1000 elements equal to the original, with 10 pauses", (done) -> + known = _.range(1000) + test = [] + pauses = 1 + running = true + @subject.map(known,((num) -> + num += 1 + "$#{num.toString()}"),(mapped)-> + test = mapped + done() + expect(pauses).toEqual(10) + expect(test[999]).toEqual("$1000") + expect(test.length).toEqual(known.length) + expect(test).toEqual(_.map(known,((n)-> n+=1; "$#{n.toString()}"))) + ,(()-> pauses++),100) diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/utils/base-object.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/utils/base-object.spec.coffee new file mode 100644 index 0000000..0736709 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/utils/base-object.spec.coffee @@ -0,0 +1,49 @@ +describe "oo.BaseObject", -> + beforeEach -> + module "google-maps.directives.api.utils" + inject (BaseObject) => + @subject = BaseObject + PersonModule = + changePersonName: (person, name)-> + person.name = name + person + killPersonsName: (person)-> + delete person.name + person + PersonAttributes = + p_name: "no_name" + state: "no_state" + @PersonAttributes = PersonAttributes + class Person extends BaseObject + @include PersonModule + @extend PersonAttributes + constructor: (name, state)-> + @name = if name? then name else Person.p_name + @state = if state? then state else Person.state + @name = "nick" + @state = "fl" + @defaultUsage = new Person() + @usage = new Person(@name, @state) + + it "exists ~ you loaded the script!", -> + expect(@subject?).toEqual(true) + + describe "include specs", -> + it "defaults attributes exist", -> + expect(@defaultUsage.name?).toEqual(true) + expect(@defaultUsage.name?).toEqual(true) + it "defaults attributes are correct", -> + expect(@defaultUsage.name).toEqual(@PersonAttributes.p_name) + expect(@defaultUsage.state).toEqual(@PersonAttributes.state) + it "subject attributes are correct ", -> + expect(@usage.name).toEqual(@name) + expect(@usage.state).toEqual(@state) + describe "extend specs", -> + it "defaults functions exist", -> + expect(@defaultUsage.changePersonName?).toEqual(true) + expect(@defaultUsage.killPersonsName?).toEqual(true) + it "subject functions act correctly", -> + p = @defaultUsage.changePersonName(angular.copy(@defaultUsage), "john") + p2 = @defaultUsage.killPersonsName(@defaultUsage) + expect(p.name).toEqual("john") + expect(p2.name).toEqual(undefined) \ No newline at end of file diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/utils/gmap-util.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/utils/gmap-util.spec.coffee new file mode 100644 index 0000000..86c9b32 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/utils/gmap-util.spec.coffee @@ -0,0 +1,104 @@ +describe "utils.gmap-util", -> + beforeEach -> + module "google-maps.directives.api.utils" + module "google-maps.mocks" + inject (GmapUtil, GoogleApiMock) => + @subject = GmapUtil + @gmap = new GoogleApiMock() + @gmap.mockAPI() + @gmap.mockMVCArray() + @gmap.mockPoint() + @gmap.mockLatLng() + @gmap.mockLatLngBounds() + + describe "should validate the path correctly", -> + it "latlong", -> + latlong = {longitude: 45, latitude: -27} + expect(@subject.validatePath([latlong, latlong])).toEqual(true) + expect(@subject.validatePath([latlong])).toEqual(false) + it "empty array", -> + expect(@subject.validatePath([])).toEqual(false) + it "array of invalid objects", -> + expect(@subject.validatePath([ + {}, + {} + ])).toEqual(false) + it "Polygon", -> + expect(@subject.validatePath({type: "Polygon"})).toEqual(false) + expect(@subject.validatePath({type: "Polygon", coordinates: [[1, 2] for [1..4]]})).toEqual(true) + expect(@subject.validatePath({type: "Polygon", coordinates: [[1, 2] for [1..1]]})).toEqual(false) + it "Polygon", -> + expect(@subject.validatePath({type: "LineString", coordinates: [1, 2] for [1..2]})).toEqual(true) + expect(@subject.validatePath({type: "LineString", coordinates: [1, 2] for [1..1]})).toEqual(false) + expect(@subject.validatePath({type: "LineString", coordinates: [] for [1..2]})).toEqual(false) + it "foo", -> + expect(@subject.validatePath({type: "foo", coordinates: []})).toEqual(false) + + describe "should validate coordinates correctly", -> + it "basic", -> + expect(@subject.validateCoords()).toEqual(false) + expect(@subject.validateCoords([1, 2])).toEqual(true) + expect(@subject.validateCoords([])).toEqual(false) + + it "type:Point", -> + expect(@subject.validateCoords({type: "Point", coordinates: [1, 2]})).toEqual(true) + expect(@subject.validateCoords({type: "Point", coordinates: []})).toEqual(false) + it "type:foo, no lat lon", -> + expect(@subject.validateCoords({type: "foo", coordinates: []})).toEqual(false) + it "type:foo, w lat lon", -> + expect(@subject.validateCoords( type: "foo", latitude: 45, longitude:150 )).toEqual true + + it "should evaluate truthiness correctly", -> + expect(@subject.isTrue(true)).toEqual(true) + expect(@subject.isTrue("true")).toEqual(true) + expect(@subject.isTrue("1")).toEqual(true) + expect(@subject.isTrue("y")).toEqual(true) + + expect(@subject.isTrue()).toEqual(false) + expect(@subject.isTrue(null)).toEqual(false) + + it "should evaluate falsiness correctly", -> + expect(@subject.isFalse('false')).toEqual(true) + expect(@subject.isFalse('FALSE')).toEqual(true) + expect(@subject.isFalse(0)).toEqual(true) + expect(@subject.isFalse('n')).toEqual(true) + expect(@subject.isFalse('N')).toEqual(true) + expect(@subject.isFalse('no')).toEqual(true) + expect(@subject.isFalse('NO')).toEqual(true) + + # XXX: Is this really true? + expect(@subject.isFalse(false)).toEqual(false) + + it "should convert path points correctly", -> + latlong = {longitude: 45, latitude: -27} + expect(@subject.convertPathPoints([]).getLength()).toEqual(0) + expect(@subject.convertPathPoints([latlong]).getLength()).toEqual(1) + expect(@subject.convertPathPoints({type: "Polygon", coordinates: [[1, 2] for [1..4]]}).getLength()).toEqual(4) + expect(@subject.convertPathPoints({type: "LineString", coordinates: [1, 2] for [1..4]}).getLength()).toEqual(4) + + it "should increase coverage", -> + latlong = {longitude: 45, latitude: -27} + @subject.getCoords(latlong) + @subject.getLabelPositionPoint("0 1") + @subject.extendMapBounds({fitBounds: (bounds) -> return undefined}, []) + + it "(getLabelPositionPoint) should convert decimal coordinates separated by a space into a map Point object", -> + testCases = [ + { input: '22 0', expected: { x: 22, y: 0 } } + { input: '1 2', expected: { x: 1, y: 2 } } + { input: '1.0 2.3', expected: { x: 1.0, y: 2.3 } } + { input: '-1 -2', expected: { x: -1, y: -2 } } + ] + testCases.forEach (testCase ) => + result = @subject.getLabelPositionPoint(testCase.input) + expect(result.x).toEqual(testCase.expected.x) + expect(result.y).toEqual(testCase.expected.y) + + it "(getLabelPositionPoint) should ignore coordinate strings not following the format", -> + testCases = [ + ' 1 2 ' + 'a b' + '1,2' + ] + testCases.forEach (testCase)=> + result = @subject.getLabelPositionPoint(testCase.input) diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/utils/model-key.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/utils/model-key.spec.coffee new file mode 100644 index 0000000..b1d2d5f --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/utils/model-key.spec.coffee @@ -0,0 +1,28 @@ +describe "ModelKey Tests", -> + beforeEach -> + angular.mock.module("google-maps.directives.api.utils") + inject ($rootScope, ModelKey) => + @scope = $rootScope.$new() + @subject = new ModelKey(@scope) + + it "should eval model handle correctly", -> + model = {key: 'key'} + expect(@subject.evalModelHandle()).toEqual(undefined) + expect(@subject.evalModelHandle(model, 'self')).toEqual(model) + expect(@subject.evalModelHandle(model, 'key')).toEqual('key') + expect(@subject.evalModelHandle(model, 'foo')).toEqual(undefined) + + it "should properly compare models", -> + model1 = {coords: {latitude: 41, longitude: -27}} + model2 = {coords: {latitude: 40, longitude: -27}} + expect(@subject.modelKeyComparison).toThrow("No scope or parentScope set!") + @scope.coords = 'coords' + expect(@subject.modelKeyComparison(model1, model1)).toEqual(true) + expect(@subject.modelKeyComparison(model1, model2)).toEqual(false) + + it "should properly set id key", -> + expect(@subject.idKey).toEqual(undefined) + expect(@subject.setIdKey(@scope)).toEqual('id') + @scope.idKey = 'foo' + expect(@subject.setIdKey(@scope)).toEqual('foo') + diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/utils/prop-map.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/utils/prop-map.spec.coffee new file mode 100644 index 0000000..0098653 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/utils/prop-map.spec.coffee @@ -0,0 +1,44 @@ +describe "PropMap tests", -> + beforeEach -> + angular.mock.module('google-maps.directives.api.models.parent') + inject (PropMap) => + @PropMap = PropMap + + it "should construct", -> + propMap = new @PropMap() + expect(propMap?).toEqual(true) + + describe "PropMap method tests", -> + beforeEach -> + @propMap = new @PropMap() + + it "should have initial length 0", -> + expect(@propMap.length).toEqual(0) + + it "should return undefined initially", -> + expect(@propMap.get('foo')).toEqual(undefined) + + it "should return what is put", -> + @propMap.put('foo', 'bar') + expect(@propMap.get('foo')).toEqual('bar') + + it "should remove and reflect length", -> + @propMap.put('foo', 'bar') + expect(@propMap.length).toEqual(1) + @propMap.remove('foo') + expect(@propMap.get('foo')).toEqual(undefined) + expect(@propMap.length).toEqual(0) + + it "should return all put values", -> + @propMap.put('foo', 'bar') + @propMap.put('baz', 'biz') + values = @propMap.values() + expected = ['bar', 'biz'] + expect(values[i]).toEqual(item) for item, i in expected + + it "should return all put keys", -> + @propMap.put('foo', 'bar') + @propMap.put('baz', 'biz') + keys = @propMap.keys() + expected = ['foo', 'baz'] + expect(keys[i]).toEqual(item) for item, i in expected diff --git a/bower_components/angular-google-maps/spec/coffee/directives/api/window.spec.coffee b/bower_components/angular-google-maps/spec/coffee/directives/api/window.spec.coffee new file mode 100644 index 0000000..8797890 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/directives/api/window.spec.coffee @@ -0,0 +1,85 @@ +describe "directives.api.Window", -> + beforeEach -> + window.google + module "google-maps" + module "google-maps.mocks" + inject (GoogleApiMock) => + @gmap = new GoogleApiMock() + @gmap.mockAPI() + @gmap.mockLatLng() + @gmap.mockMarker() + @gmap.mockInfoWindow() + @gmap.mockEvent() + ### Possible Attributes + coords: '=coords', + show: '=show', + templateUrl: '=templateurl', + templateParameter: '=templateparameter', + isIconVisibleOnClick: '=isiconvisibleonclick', + closeClick: '&closeclick', #scope glue to gmap InfoWindow closeclick + options: '=options' + ### + @mocks = + scope: + coords: + latitude: 90.0 + longitude: 89.0 + show: true + $watch:()-> + $on:()-> + element: + html: -> + "

        test html

        " + attrs: { + isiconvisibleonclick:true + } + ctrls: [ + { + getMap:()-> + {} + } + ] + + @timeOutNoW = (fnc,time) => + fnc() + @gMarker = new google.maps.Marker(@commonOpts) + inject (_$rootScope_,$q, $compile, $http, $templateCache, $injector, Window) => + @$rootScope = _$rootScope_ + d = $q.defer() + d.resolve @gmap + + @$rootScope.deferred = d + @mocks.ctrls[0].getScope = => + @$rootScope + @windowScope = _.extend @$rootScope.$new(), @mocks.scope + + + @subject = new Window(@timeOutNoW,$compile, $http, $templateCache) + @subject.onChildCreation = (child) => + @childWindow = child + + @prom = d.promise + return + + it "should test receive the fulfilled promise!!", -> + result = undefined + @prom.then (returnFromPromise) -> + result = returnFromPromise + @$rootScope.$apply() # promises are resolved/dispatched only on next $digest cycle + expect(result).toBe @gmap + + it 'can be created', -> + expect(@subject).toBeDefined() + expect(@subject.index).toEqual(@index) + + it 'link creates window options and a childWindow', -> + + @subject.link(@windowScope, @mocks.element, @mocks.attrs, @mocks.ctrls) + crap = null + @prom.then -> + crap = "set" + #holy crap $rootScope.$apply() must come after all promises!!!!! + @$rootScope.$apply() #force $q to resolve + expect(crap).toBe 'set' + expect(@childWindow).toBeDefined() + expect(@childWindow.opts).toBeDefined() \ No newline at end of file diff --git a/bower_components/angular-google-maps/spec/coffee/helpers/google-api-mock.coffee b/bower_components/angular-google-maps/spec/coffee/helpers/google-api-mock.coffee new file mode 100644 index 0000000..0cec3df --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/helpers/google-api-mock.coffee @@ -0,0 +1,128 @@ +angular.module("google-maps.mocks", []) +.factory "GoogleApiMock", -> + class _Map + constructor:()-> + @lat = 47 + @lon = -27 + @zoom = 0 + @getCoords = -> return {latitude: @lat, longitude: @lon} + @center = new window.google.maps.LatLng @lat,@lon + @setCenter = (coords) => + @center = coords + @setZoom = (@zoom) -> + class GoogleApiMock + constructor: -> + mockAPI: -> + window.google = {} + window.google.maps = {} + + # To make debugging easier, mock everything with exceptions + unmocked = (api) => () => throw new String("Unmocked API " + api) + window.google.maps.Marker = unmocked("Marker") + window.google.maps.event = + clearListeners: unmocked("event.clearListeners") + addListener: unmocked("event.addListener") + window.google.maps.OverlayView = unmocked("OverlayView") + window.google.maps.InfoWindow = unmocked("InfoWindow") + window.google.maps.LatLng = unmocked("LatLng") + window.google.maps.MVCArray = unmocked("MVCArray") + window.google.maps.Point = unmocked("Point") + window.google.maps.LatLngBounds = unmocked("LatLngBounds") + + #http://gis.stackexchange.com/questions/11626/does-y-mean-latitude-and-x-mean-longitude-in-every-gis-software + mockLatLng: (LatLng = (y, x) -> + lat: () -> + y + lng: () -> + x + ) -> + window.google.maps.LatLng = LatLng + + mockLatLngBounds: (LatLngBounds = () -> return) -> + if not (LatLngBounds.extend?) + LatLngBounds.prototype.extend = () -> return + + window.google.maps.LatLngBounds = LatLngBounds + + mockMap:(map) -> + @mockMapTypeId() + @mockLatLng() + @mockOverlayView() + @mockEvent() + map = _Map unless map + window.google.maps.Map = map + + mockAnimation: (Animation = {BOUNCE: "bounce"}) -> + window.google.maps.Animation = Animation + + mockMapTypeId: (MapTypeId = {ROADMAP: "roadmap"}) -> + window.google.maps.MapTypeId = MapTypeId + + mockOverlayView: ( + OverlayView = class OverlayView + setMap:() -> + ) -> + window.google.maps.OverlayView = OverlayView + + mockEvent: (event = {}) -> + listeners = [] + #mocking google maps event listener + if not event.addListener + event.addListener = (thing, eventName, callBack) -> + found = _.find listeners, (obj)-> + obj.obj == thing + unless found? + toPush = {} + toPush.obj = thing + toPush.events = {} + toPush.events[eventName] = callBack + listeners.push toPush + else + found.events[eventName] = callBack + + if not event.clearListeners + event.clearListeners = () -> + listeners.length = 0 + + unless event.fireListener + event.fireListener = (thing, eventName) => + found = _.find listeners, (obj)-> + obj.obj == thing + found.events[eventName](found.obj) if found? + + + window.google.maps.event = event + + mockInfoWindow: (InfoWindow = () -> return) -> + window.google.maps.InfoWindow = InfoWindow + + mockMarker: (Marker = @getMarker()) -> + window.google.maps.Marker = Marker + + mockMVCArray: () -> + MVCArray = () -> + this.values = [] + return + + if not (MVCArray.getLength?) + MVCArray.prototype.getLength = () -> + return this.values.length + + if not (MVCArray.push?) + MVCArray.prototype.push = (value) -> + this.values.push(value) + + window.google.maps.MVCArray = MVCArray + + mockPoint: (Point = (x,y) -> return {x: x, y:y}) -> + window.google.maps.Point = Point + + getMarker: -> + Marker = (opts) -> return + Marker.prototype.setMap = (map) -> + return + + return Marker + + GoogleApiMock + diff --git a/bower_components/angular-google-maps/spec/coffee/helpers/google-map-spec-controller.coffee b/bower_components/angular-google-maps/spec/coffee/helpers/google-map-spec-controller.coffee new file mode 100644 index 0000000..788f57d --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/helpers/google-map-spec-controller.coffee @@ -0,0 +1,22 @@ +(-> + module = angular.module("angular-google-maps-specs", ["google-maps"]) + .controller('GoogleMapSpecController', ($scope, $timeout, $log) -> + self = @ + @hasRun = false + @map = {} + google.maps.visualRefresh = true + angular.extend($scope, + showTraffic: true, + center: + latitude: 45 + longitude: -73 + zoom: 3, + events: #direct hook to google maps sdk events + tilesloaded: (map, eventName, originalEventArgs) -> + if !self.hasRun + self.map = map + document.gMap = map + self.hasRun = true + ) + ) +)() \ No newline at end of file diff --git a/bower_components/angular-google-maps/spec/coffee/usage/underscore.difference.spec.coffee b/bower_components/angular-google-maps/spec/coffee/usage/underscore.difference.spec.coffee new file mode 100644 index 0000000..e61de0b --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/usage/underscore.difference.spec.coffee @@ -0,0 +1,128 @@ +#SPEC set is to make sure Underscore is being understood and used correctly +describe "_.differenceObjects", -> + beforeEach -> + @objArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + ] + describe "Comparing Arrays of Objects", -> + describe "difference", -> + describe "0 length", -> + it "when two arrays are identical - same reference", -> + interArray = _.difference(@objArray, @objArray) + expect(interArray.length).toEqual(0) + + describe "different length - not identical", -> + it "diff reference, diff values", -> + difArray = [ + a: 1 + b: 2 + , + a: 2 + b: 3 + , + a: 3 + b: 4 + ] + interArray = _.difference(@objArray, difArray) + expect(interArray.length).toEqual(3) + + it "diff reference, 1 val identical", -> + difArray = [ + a: 1 + b: 1 + ] + interArray = _.difference(@objArray, difArray) + expect(interArray.length).toEqual(3) + + it "diff reference, same values", -> + difArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + ] + diffArray = @objArray + index = @objArray.indexOf {a: 1, b: 1} + expect(index).toBe(-1) + interArray = _.difference(@objArray, difArray) + expect(interArray.length).toEqual(3) + + describe "_.differenceObjects - extension", -> + describe "same length", -> + it "when two arrays are identical - same reference", -> + interArray = _.differenceObjects @objArray, @objArray + expect(interArray.length).toEqual(0) + + it "diff reference, same values", -> + difArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + ] + diffArray = @objArray + interArray = _.differenceObjects difArray, @objArray + expect(interArray.length).toEqual(0) + + it "diff reference one added (new), same values (intersected)", -> + difArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + , + a: 4 + b: 4 + ] + diffArray = @objArray + interArray = _.differenceObjects difArray, @objArray + expect(interArray.length).toEqual(1) + describe "different length - not identical", -> + it "diff reference, diff values", -> + difArray = [ + a: 1 + b: 2 + , + a: 2 + b: 3 + , + a: 3 + b: 4 + ] + interArray = _.differenceObjects difArray, @objArray + expect(interArray.length).toEqual(3) + + it "diff reference, 1 val identical", -> + difArray = [ + a: 1 + b: 1 + ] + interArray = _.differenceObjects difArray,@objArray + expect(interArray.length).toEqual(0) + describe "removal", -> + it "diff reference, 1 val identical", -> + difArray = [ + a: 1 + b: 1 + ] + interArray = _.withoutObjects @objArray, difArray + expect(interArray.length).toEqual(2) \ No newline at end of file diff --git a/bower_components/angular-google-maps/spec/coffee/usage/underscore.indexOfObject.spec.coffee b/bower_components/angular-google-maps/spec/coffee/usage/underscore.indexOfObject.spec.coffee new file mode 100644 index 0000000..85af475 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/usage/underscore.indexOfObject.spec.coffee @@ -0,0 +1,33 @@ +describe "_.indexOfObject", -> + beforeEach -> + @objArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a:5 + b:3 + , + a: 3 + b: 3 + ] + it "finds first element", -> + i = _.indexOfObject(@objArray, {a:1,b:1}) + expect(i).toEqual(0) + + it "finds element beginning", -> + i = _.indexOfObject(@objArray, {a:2,b:2}) + expect(i).toEqual(1) + it "finds last element", -> + i = _.indexOfObject(@objArray, {a:5,b:3}) + expect(i).toEqual(2) + + it "finds last element", -> + i = _.indexOfObject(@objArray, {a:3,b:3}) + expect(i).toEqual(3) + + it "find no element", -> + i = _.indexOfObject(@objArray, {a:4,b:3}) + expect(i).toEqual(-1) diff --git a/bower_components/angular-google-maps/spec/coffee/usage/underscore.intersection.spec.coffee b/bower_components/angular-google-maps/spec/coffee/usage/underscore.intersection.spec.coffee new file mode 100644 index 0000000..3314aa2 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/usage/underscore.intersection.spec.coffee @@ -0,0 +1,120 @@ +#SPEC set is to make sure Underscore is being understood and used correctly +describe "_.intersectionObjects", -> + beforeEach -> + @objArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + ] + describe "Comparing Arrays of Objects", -> + describe "intersection", -> + describe "same length", -> + it "when two arrays are identical - same reference", -> + interArray = _.intersection(@objArray, @objArray) + expect(interArray.length).toEqual(@objArray.length) + + describe "different length - not identical", -> + it "diff reference, diff values", -> + difArray = [ + a: 1 + b: 2 + , + a: 2 + b: 3 + , + a: 3 + b: 4 + ] + interArray = _.intersection(@objArray, difArray) + expect(interArray.length).toEqual(0) + + it "diff reference, 1 val identical", -> + difArray = [ + a: 1 + b: 1 + ] + interArray = _.intersection(@objArray, difArray) + expect(interArray.length).toEqual(0) + + it "diff reference, same values", -> + difArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + ] + diffArray = @objArray + index = @objArray.indexOf {a:1,b:1} + expect(index).toBe(-1) + interArray = _.intersection(@objArray, difArray) + expect(interArray.length).toEqual(0) + + describe "_.intersectionObjects - extension", -> + describe "same length", -> + it "when two arrays are identical - same reference", -> + interArray = _.intersectionObjects @objArray,@objArray + expect(interArray.length).toEqual(@objArray.length) + it "diff reference, same values", -> + difArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + ] + diffArray = @objArray + interArray = _.intersectionObjects @objArray,difArray + expect(interArray.length).toEqual(@objArray.length) + + it "diff reference one added (new), same values (intersected)", -> + difArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + , + a:4 + b:4 + ] + diffArray = @objArray + interArray = _.intersectionObjects @objArray,difArray + expect(interArray.length).toEqual(@objArray.length) + describe "different length - not identical", -> + it "diff reference, diff values", -> + difArray = [ + a: 1 + b: 2 + , + a: 2 + b: 3 + , + a: 3 + b: 4 + ] + interArray = _.intersectionObjects @objArray,difArray + expect(interArray.length).toEqual(0) + + it "diff reference, 1 val identical", -> + difArray = [ + a: 1 + b: 1 + ] + interArray = _.intersectionObjects @objArray,difArray + expect(interArray.length).toEqual(1) + expect(interArray.length).not.toEqual(@objArray.length) diff --git a/bower_components/angular-google-maps/spec/coffee/usage/underscore.isEqual.spec.coffee b/bower_components/angular-google-maps/spec/coffee/usage/underscore.isEqual.spec.coffee new file mode 100644 index 0000000..46c5818 --- /dev/null +++ b/bower_components/angular-google-maps/spec/coffee/usage/underscore.isEqual.spec.coffee @@ -0,0 +1,133 @@ +#SPEC set is to make sure Underscore is being understood and used correctly +describe "_.isEqual", -> + beforeEach -> + @objArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + ] + describe "Comparing Arrays of Objects", -> + describe "isEqual", -> + describe "same length", -> + it "when two arrays are identical - same reference", -> + expect(_.isEqual(@objArray, @objArray)).toBeTruthy + + it "diff reference, same values", -> + difArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + ] + expect(_.isEqual(@objArray, difArray)).toBeTruthy + + it "diff reference one added (new), same values (intersected)", -> + difArray = [ + a: 1 + b: 1 + , + a: 2 + b: 2 + , + a: 3 + b: 3 + , + a: 4 + b: 4 + ] + expect(_.isEqual(@objArray, difArray)).toBeFalsy + + describe "different length - not identical", -> + it "diff reference, diff values", -> + difArray = [ + a: 1 + b: 2 + , + a: 2 + b: 3 + , + a: 3 + b: 4 + ] + expect(_.isEqual(@objArray, difArray)).toBeFalsy + + it "diff reference, 1 val identical", -> + difArray = [ + a: 1 + b: 1 + ] + expect(_.isEqual(@objArray, difArray)).toBeFalsy + describe "array of nested objects", -> + beforeEach -> + @objArray = [ + a: 1 + b: + n: 1 + m: 2 + o: "hi!" + , + a: 2 + b: + n: 4 + m: 5 + o: "hi!" + , + a: + n: 2 + m: 3 + o: "hi!" + b: 3 + ] + it "same - reference should be equal", -> + expect(_.isEqual(@objArray, @objArray)).toBeTruthy + it "same - dif reference same values should be ==", -> + difArray = [ + a: 1 + b: + n: 1 + m: 2 + o: "hi!" + , + a: 2 + b: + n: 4 + m: 5 + o: "hi!" + , + a: + n: 2 + m: 3 + o: "hi!" + b: 3 + ] + expect(_.isEqual(@objArray, difArray)).toBeTruthy + it "dif reference diff values should be !=", -> + difArray = [ + a: 1 + b: + n: 1 + m: 2 + o: "hi!" + , + a: 2 + b: + n: 4 + m: 5 + o: "hi!!" + , + a: + n: 2 + m: 3 + o: "hi!" + b: 3 + ] + expect(_.isEqual(@objArray, difArray)).toBeFalsy \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/i-label.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/i-label.coffee new file mode 100644 index 0000000..12a2609 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/i-label.coffee @@ -0,0 +1,30 @@ +### + - interface for all labels to derrive from + - to enforce a minimum set of requirements + - attributes + - content + - anchor + - implementation needed on watches +### +angular.module("google-maps.directives.api") +.factory "ILabel", [ "BaseObject", "Logger", (BaseObject, Logger) -> + class ILabel extends BaseObject + constructor: ($timeout) -> + self = @ + @restrict = 'ECMA' + @replace = true + @template = undefined + @require = undefined + @transclude = true + @priority = -100 + @scope = { + labelContent: '=content', + labelAnchor: '@anchor', + labelClass: '@class', + labelStyle: '=style' + } + @$log = Logger + @$timeout = $timeout + link: (scope, element, attrs, ctrl) => + throw new Exception("Not Implemented!!") +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/i-marker.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/i-marker.coffee new file mode 100644 index 0000000..7312cee --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/i-marker.coffee @@ -0,0 +1,38 @@ +### + - interface for all markers to derrive from + - to enforce a minimum set of requirements + - attributes + - coords + - icon + - implementation needed on watches +### +angular.module("google-maps.directives.api") +.factory "IMarker", [ "Logger", "BaseObject", "$q", (Logger, BaseObject, $q)-> + class IMarker extends BaseObject + constructor: ($timeout) -> + self = @ + @$log = Logger + @$timeout = $timeout + @restrict = 'ECMA' + @require = '^googleMap' + @priority = -1 + @transclude = true + @replace = true + @scope = + coords: '=coords', + icon: '=icon', + click: '&click', + options: '=options', + events: '=events', + fit: '=fit' + + link: (scope, element, attrs, ctrl) => + throw new Error "No Map Control! Marker Directive Must be inside the map!" unless ctrl + + mapPromise: (scope, ctrl) -> + mapScope = ctrl.getScope() + mapScope.deferred.promise.then (map) -> + scope.map = map + mapScope.deferred.promise +] + diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/i-polyline.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/i-polyline.coffee new file mode 100644 index 0000000..79a764a --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/i-polyline.coffee @@ -0,0 +1,24 @@ +angular.module("google-maps.directives.api") +.factory "IPolyline", ["GmapUtil", "BaseObject", "Logger", (GmapUtil, BaseObject, Logger) -> + class IPolyline extends BaseObject + @include GmapUtil + constructor: ()-> + self = @ + restrict: "ECA" + replace: true + require: "^googleMap" + scope: + path: "=" + stroke: "=" + clickable: "=" + draggable: "=" + editable: "=" + geodesic: "=" + icons: "=" + visible: "=" + static: "=" + fit: "=" + + DEFAULTS: {} + $log: Logger +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/i-window.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/i-window.coffee new file mode 100644 index 0000000..715ba8c --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/i-window.coffee @@ -0,0 +1,29 @@ +### + - interface directive for all window(s) to derrive from +### +angular.module("google-maps.directives.api") +.factory "IWindow", [ "BaseObject", "ChildEvents", "Logger", (BaseObject, ChildEvents, Logger) -> + class IWindow extends BaseObject + @include ChildEvents + constructor: (@$timeout, @$compile, @$http, @$templateCache) -> + self = @ + @restrict = 'ECMA' + @template = undefined + @transclude = true + @priority = -100 + @require = undefined + @replace = true + @scope = { + coords: '=coords', + show: '=show', + templateUrl: '=templateurl', + templateParameter: '=templateparameter', + isIconVisibleOnClick: '=isiconvisibleonclick', + closeClick: '&closeclick', #scope glue to gmap InfoWindow closeclick + options: '=options' + } + @$log = Logger + + link: (scope, element, attrs, ctrls) => + throw new Exception("Not Implemented!!") +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/managers/clusterer-marker-manager.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/managers/clusterer-marker-manager.coffee new file mode 100644 index 0000000..6188edd --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/managers/clusterer-marker-manager.coffee @@ -0,0 +1,57 @@ +angular.module("google-maps.directives.api.managers") +.factory "ClustererMarkerManager", ["Logger", "FitHelper", ($log, FitHelper) -> + class ClustererMarkerManager extends FitHelper + constructor: (gMap, opt_markers, opt_options, @opt_events) -> + super() + self = @ + @opt_options = opt_options + if opt_options? and opt_markers == undefined + @clusterer = new MarkerClusterer(gMap, undefined, opt_options) + else if opt_options? and opt_markers? + @clusterer = new MarkerClusterer(gMap, opt_markers, opt_options) + else + @clusterer = new MarkerClusterer(gMap) + + @attachEvents @opt_events, "opt_events" + + @clusterer.setIgnoreHidden(true) + @noDrawOnSingleAddRemoves = true + $log.info(@) + add: (gMarker)=> + @clusterer.addMarker(gMarker, @noDrawOnSingleAddRemoves) + addMany: (gMarkers)=> + @clusterer.addMarkers(gMarkers) + remove: (gMarker)=> + @clusterer.removeMarker(gMarker, @noDrawOnSingleAddRemoves) + removeMany: (gMarkers)=> + @clusterer.addMarkers(gMarkers) + draw: ()=> + @clusterer.repaint() + clear: ()=> + @clusterer.clearMarkers() + @clusterer.repaint() + + attachEvents:(options, optionsName) -> + if angular.isDefined(options) and options? and angular.isObject(options) + for eventName, eventHandler of options + if options.hasOwnProperty(eventName) and angular.isFunction(options[eventName]) + $log.info "#{optionsName}: Attaching event: #{eventName} to clusterer" + google.maps.event.addListener @clusterer, eventName, options[eventName] + + clearEvents:(options) -> + if angular.isDefined(options) and options? and angular.isObject(options) + for eventName, eventHandler of options + if options.hasOwnProperty(eventName) and angular.isFunction(options[eventName]) + $log.info "#{optionsName}: Clearing event: #{eventName} to clusterer" + google.maps.event.clearListeners @clusterer, eventName + + destroy: => + @clearEvents @opt_events + @clearEvents @opt_internal_events + @clear() + + fit: ()=> + super @clusterer.getMarkers() , @clusterer.getMap() + + ClustererMarkerManager + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/managers/marker-manager.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/managers/marker-manager.coffee new file mode 100644 index 0000000..14c59d9 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/managers/marker-manager.coffee @@ -0,0 +1,72 @@ +angular.module("google-maps.directives.api.managers") +.factory "MarkerManager", ["Logger", "FitHelper", (Logger, FitHelper) -> + class MarkerManager extends FitHelper + @include FitHelper + constructor: (gMap, opt_markers, opt_options) -> + super() + self = @ + @gMap = gMap + @gMarkers = [] + @$log = Logger + @$log.info(@) + + add: (gMarker, optDraw, redraw=true)=> + @handleOptDraw(gMarker, optDraw, redraw) + @gMarkers.push(gMarker) + + addMany: (gMarkers)=> + @add(gMarker) for gMarker in gMarkers + + remove: (gMarker, optDraw)=> + @handleOptDraw(gMarker, optDraw, false) + unless optDraw + return + index = undefined + if @gMarkers.indexOf? + index = @gMarkers.indexOf(gMarker) + else + tempIndex = 0 + _.find @gMarkers, (marker) -> + tempIndex += 1 + if marker == gMarker + index = tempIndex + return + if index? + @gMarkers.splice(index, 1) + + removeMany: (gMarkers)=> + @gMarkers.forEach (marker) => + @remove(marker) + + draw: => + deletes = [] + @gMarkers.forEach (gMarker) => + unless gMarker.isDrawn + if gMarker.doAdd + gMarker.setMap(@gMap) + gMarker.isDrawn = true + else + deletes.push(gMarker) + + deletes.forEach (gMarker) => + gMarker.isDrawn = false + @remove(gMarker, true) + + clear: => + gMarker.setMap(null) for gMarker in @gMarkers + delete @gMarkers + @gMarkers = [] + + handleOptDraw: (gMarker, optDraw, doAdd)=> + if optDraw == true + if doAdd then gMarker.setMap(@gMap) else gMarker.setMap(null) + gMarker.isDrawn = true + else + gMarker.isDrawn = false + gMarker.doAdd = doAdd + + fit: ()=> + super @gMarkers,@gMap + + MarkerManager + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/map.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/map.coffee new file mode 100644 index 0000000..2c31280 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/map.coffee @@ -0,0 +1,218 @@ +angular.module("google-maps.directives.api") +.factory "Map", ["$timeout", '$q','Logger', "GmapUtil", "BaseObject", "ExtendGWin", "CtrlHandle", + ($timeout,$q, Logger, GmapUtil, BaseObject,ExtendGWin, CtrlHandle) -> + "use strict" + $log = Logger + + DEFAULTS = + mapTypeId: google.maps.MapTypeId.ROADMAP + + class Map extends BaseObject + @include GmapUtil + constructor:()-> + self = @ + restrict: "ECMA" + transclude: true + replace: false + #priority: 100, + template: "
        " + + scope: + center: "=center" # required + zoom: "=zoom" # required + dragging: "=dragging" # optional + control: "=" # optional + windows: "=windows" # optional TODO is this still needed looks like dead code + options: "=options" # optional + events: "=events" # optional + styles: "=styles" # optional + bounds: "=bounds" + + controller: ["$scope", ($scope) -> + ctrlObj = CtrlHandle.handle $scope + $scope.ctrlType = 'Map' + $scope.deferred.promise.then -> + ExtendGWin.init() + _.extend ctrlObj, getMap: -> + $scope.map + ] + + ### + @param scope + @param element + @param attrs + ### + link: (scope, element, attrs) => + # Center property must be specified and provide lat & + # lng properties + if not @validateCoords(scope.center) + $log.error "angular-google-maps: could not find a valid center property" + return + unless angular.isDefined(scope.zoom) + $log.error "angular-google-maps: map zoom property not set" + return + el = angular.element(element) + el.addClass "angular-google-map" + + # Parse options + opts = + options: {} + opts.options = scope.options if attrs.options + + opts.styles = scope.styles if attrs.styles + if attrs.type + type = attrs.type.toUpperCase() + if google.maps.MapTypeId.hasOwnProperty(type) + opts.mapTypeId = google.maps.MapTypeId[attrs.type.toUpperCase()] + else + $log.error "angular-google-maps: invalid map type \"" + attrs.type + "\"" + + # Create the map + _m = new google.maps.Map(el.find("div")[1], angular.extend({}, DEFAULTS, opts, + center: @getCoords(scope.center) + draggable: @isTrue(attrs.draggable) + zoom: scope.zoom + bounds: scope.bounds + )) + dragging = false + if not _m + google.maps.event.addListener _m, 'tilesloaded ', (map) -> + scope.deferred.resolve map + else + scope.deferred.resolve _m + + google.maps.event.addListener _m, "dragstart", -> + dragging = true + _.defer -> + scope.$apply (s) -> + s.dragging = dragging if s.dragging? + + google.maps.event.addListener _m, "dragend", -> + dragging = false + _.defer -> + scope.$apply (s) -> + s.dragging = dragging if s.dragging? + + + google.maps.event.addListener _m, "drag", -> + c = _m.center + _.defer -> + scope.$apply (s) -> + if angular.isDefined(s.center.type) + s.center.coordinates[1] = c.lat() + s.center.coordinates[0] = c.lng() + else + s.center.latitude = c.lat() + s.center.longitude = c.lng() + + + google.maps.event.addListener _m, "zoom_changed", -> + if scope.zoom isnt _m.zoom + _.defer -> + scope.$apply (s) -> + s.zoom = _m.zoom + + + settingCenterFromScope = false + google.maps.event.addListener _m, "center_changed", -> + c = _m.center + return if settingCenterFromScope #if the scope notified this change then there is no reason to update scope otherwise infinite loop + _.defer -> + scope.$apply (s) -> + unless _m.dragging + if angular.isDefined(s.center.type) + s.center.coordinates[1] = c.lat() if s.center.coordinates[1] isnt c.lat() + s.center.coordinates[0] = c.lng() if s.center.coordinates[0] isnt c.lng() + else + s.center.latitude = c.lat() if s.center.latitude isnt c.lat() + s.center.longitude = c.lng() if s.center.longitude isnt c.lng() + + + google.maps.event.addListener _m, "idle", -> + b = _m.getBounds() + ne = b.getNorthEast() + sw = b.getSouthWest() + _.defer -> + scope.$apply (s) -> + if s.bounds isnt null and s.bounds isnt `undefined` and s.bounds isnt undefined + s.bounds.northeast = + latitude: ne.lat() + longitude: ne.lng() + + s.bounds.southwest = + latitude: sw.lat() + longitude: sw.lng() + + if angular.isDefined(scope.events) and scope.events isnt null and angular.isObject(scope.events) + getEventHandler = (eventName) -> + -> + scope.events[eventName].apply scope, [_m, eventName, arguments] + + #TODO: Need to keep track of listeners and call removeListener on each + for eventName of scope.events + google.maps.event.addListener _m, eventName, getEventHandler(eventName) if scope.events.hasOwnProperty(eventName) and angular.isFunction(scope.events[eventName]) + + # Put the map into the scope + scope.map = _m + # google.maps.event.trigger _m, "resize" + + # check if have an external control hook to direct us manually without watches + #this will normally be an empty object that we extend and slap functionality onto with this directive + if attrs.control? and scope.control? + scope.control.refresh = (maybeCoords) => + return unless _m? + google.maps.event.trigger _m, "resize" #actually refresh + if maybeCoords?.latitude? and maybeCoords?.latitude? + coords = @getCoords(maybeCoords) + if @isTrue(attrs.pan) + _m.panTo coords + else + _m.setCenter coords + ### + I am sure you all will love this. You want the instance here you go.. BOOM! + ### + scope.control.getGMap = ()=> + _m + + # Update map when center coordinates change + scope.$watch "center", ((newValue, oldValue) => + coords = @getCoords newValue + return if coords.lat() is _m.center.lat() and coords.lng() is _m.center.lng() + settingCenterFromScope = true + unless dragging + if !@validateCoords(newValue) + $log.error("Invalid center for newValue: #{JSON.stringify newValue}") + if @isTrue(attrs.pan) and scope.zoom is _m.zoom + _m.panTo coords + else + _m.setCenter coords + + settingCenterFromScope = false + ), true + scope.$watch "zoom", (newValue, oldValue) -> + return if newValue is _m.zoom + _.defer -> + _m.setZoom newValue + + scope.$watch "bounds", (newValue, oldValue) -> + return if newValue is oldValue + if !newValue.northeast.latitude? or !newValue.northeast.longitude? or !newValue.southwest.latitude? or !newValue.southwest.longitude? + $log.error "Invalid map bounds for new value: #{JSON.stringify newValue}" + return + ne = new google.maps.LatLng(newValue.northeast.latitude, newValue.northeast.longitude) + sw = new google.maps.LatLng(newValue.southwest.latitude, newValue.southwest.longitude) + bounds = new google.maps.LatLngBounds(sw, ne) + _m.fitBounds bounds + + scope.$watch "options", (newValue,oldValue) => + unless _.isEqual(newValue,oldValue) + opts.options = newValue + _m.setOptions opts if _m? + ,true + + scope.$watch "styles", (newValue,oldValue) => + unless _.isEqual(newValue,oldValue) + opts.styles = newValue + _m.setOptions opts if _m? + ,true + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/marker.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/marker.coffee new file mode 100644 index 0000000..1c11f0d --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/marker.coffee @@ -0,0 +1,20 @@ +angular.module("google-maps.directives.api") +.factory "Marker", ["IMarker", "MarkerParentModel", "MarkerManager", "CtrlHandle", (IMarker, MarkerParentModel,MarkerManager, CtrlHandle) -> + class Marker extends IMarker + constructor: ($timeout) -> + super($timeout) + @template = '' + @$log.info(@) + + controller: ['$scope', '$element', ($scope, $element) => + $scope.ctrlType = 'Marker' + CtrlHandle.handle $scope,$element + ] + link: (scope, element, attrs, ctrl) => + super scope, element, attrs, ctrl + doFit = true if scope.fit + @mapPromise(scope,ctrl).then (map) => + @gMarkerManager = new MarkerManager map unless @gMarkerManager + new MarkerParentModel scope, element, attrs, map, @$timeout, @gMarkerManager, doFit + scope.deferred.resolve() + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/markers.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/markers.coffee new file mode 100644 index 0000000..b20cd5a --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/markers.coffee @@ -0,0 +1,31 @@ +angular.module("google-maps.directives.api") +.factory "Markers", ["IMarker", "MarkersParentModel", "CtrlHandle", (IMarker, MarkersParentModel, CtrlHandle) -> + class Markers extends IMarker + constructor: ($timeout) -> + super($timeout) + @template = '' + @scope = _.extend @scope or {}, + idKey: '=idkey' #id key to bind to that makes a model unique, if it does not exist default to rebuilding all markers + doRebuildAll: '=dorebuildall' #root level directive attribute not a model level, should default to false + models: '=models' + doCluster: '=docluster' + clusterOptions: '=clusteroptions' + clusterEvents: '=clusterevents' + labelContent: '=labelcontent' + labelAnchor: '@labelanchor' + labelClass: '@labelclass' + + @$timeout = $timeout + self = @ + @$log.info @ + + controller: ['$scope', '$element', ($scope, $element) -> + $scope.ctrlType = 'Markers' + CtrlHandle.handle $scope,$element + ] + + link: (scope, element, attrs, ctrl) => + @mapPromise(scope, ctrl).then (map) => + new MarkersParentModel(scope, element, attrs, map, @$timeout) + scope.deferred.resolve() +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/child/label-child-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/child/label-child-model.coffee new file mode 100644 index 0000000..1ad9bdf --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/child/label-child-model.coffee @@ -0,0 +1,61 @@ +angular.module("google-maps.directives.api.models.child") +.factory "MarkerLabelChildModel", [ "BaseObject", "GmapUtil", (BaseObject, GmapUtil) -> + class MarkerLabelChildModel extends BaseObject + @include GmapUtil + constructor: (gMarker, options, map) -> + super() + self = @ + @gMarker = gMarker + @setOptions options + @gMarkerLabel = new MarkerLabel_(@gMarker, options.crossImage, options.handCursor) + + @gMarker.set "setMap", (theMap) -> + self.gMarkerLabel.setMap theMap + google.maps.Marker.prototype.setMap.apply @, arguments + + @gMarker.setMap map + + setOption:(optStr,content) -> + ### + COMENTED CODE SHOWS AWFUL CHROME BUG in Google Maps SDK 3, still happens in version 3.16 + any animation will cause markers to disappear + ### +# oldAnim = @gMarker.getAnimation() +# @gMarker.setAnimation null + @gMarker.set optStr, content +# _.delay => +# @gMarker.setAnimation oldAnim +# , 1000 + + setOptions:(options) => + @gMarker.set("labelContent", options.labelContent) if options?.labelContent + @gMarker.set("labelAnchor", @getLabelPositionPoint(options.labelAnchor)) if options?.labelAnchor + @gMarker.set("labelClass", options.labelClass || 'labels') + @gMarker.set("labelStyle", options.labelStyle || { opacity: 100 }) + @gMarker.set("labelInBackground", options.labelInBackground || false;) + + if !options.labelVisible + @gMarker.set("labelVisible", true) + + if !options.raiseOnDrag + @gMarker.set("raiseOnDrag", true) + + if !options.clickable + @gMarker.set("clickable", true) + + if !options.draggable + @gMarker.set("draggable", false) + + if !options.optimized + @gMarker.set("optimized", false) + + #TODO: This should be overrideable and only gets set as a default if nothing is defined + options.crossImage = options.crossImage ? document.location.protocol + "//maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"; + options.handCursor = options.handCursor ? document.location.protocol + "//maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"; + + destroy: ()=> + #bug in MarkerLabel_ so we will check it here and maybe submit a patch + @gMarkerLabel.onRemove() if @gMarkerLabel.labelDiv_.parentNode? and @gMarkerLabel.eventDiv_.parentNode? + + MarkerLabelChildModel +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/child/marker-child-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/child/marker-child-model.coffee new file mode 100644 index 0000000..69200cc --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/child/marker-child-model.coffee @@ -0,0 +1,142 @@ +angular.module("google-maps.directives.api.models.child") +.factory "MarkerChildModel", [ "ModelKey", "GmapUtil", "Logger", "$injector", "EventsHelper", + (ModelKey, GmapUtil, Logger, $injector, EventsHelper) -> + class MarkerChildModel extends ModelKey + @include GmapUtil + @include EventsHelper + constructor: (@model, @parentScope, @gMap, @$timeout, @defaults, @doClick, @gMarkerManager, @idKey)-> + self = @ + @id = @model[@idKey] if @model[@idKey] + @iconKey = @parentScope.icon + @coordsKey = @parentScope.coords + @clickKey = @parentScope.click() + @labelContentKey = @parentScope.labelContent + @optionsKey = @parentScope.options + @labelOptionsKey = @parentScope.labelOptions + + super(@parentScope.$new(false)) + + @scope.model = @model + @setMyScope(@model, undefined, true) + @createMarker(@model) + + @scope.$watch 'model', (newValue, oldValue) => + if (newValue != oldValue) + @setMyScope(newValue, oldValue) + , true + + @$log = Logger + @$log.info(self) + @watchDestroy(@scope) + + setMyScope: (model, oldModel = undefined, isInit = false) => + @maybeSetScopeValue('icon', model, oldModel, @iconKey, @evalModelHandle, isInit, @setIcon) + @maybeSetScopeValue('coords', model, oldModel, @coordsKey, @evalModelHandle, isInit, @setCoords) + @maybeSetScopeValue('labelContent', model, oldModel, @labelContentKey, @evalModelHandle, isInit) + if _.isFunction(@clickKey) and $injector + @scope.click = () => + $injector.invoke(@clickKey, undefined, {"$markerModel": model}) + else + @maybeSetScopeValue('click', model, oldModel, @clickKey, @evalModelHandle, isInit) + @createMarker(model, oldModel, isInit) + + createMarker: (model, oldModel = undefined, isInit = false)=> + @maybeSetScopeValue 'options', model, oldModel, @optionsKey, (lModel, lModelKey) => + if lModel == undefined + return undefined + value = if lModelKey == 'self' then lModel else lModel[lModelKey] + if value == undefined # we still dont have a value see if this is something up the tree or default it + value = if lModelKey == undefined then @defaults else @scope.options + else + value + , isInit, @setOptions + + + maybeSetScopeValue: (scopePropName, model, oldModel, modelKey, evaluate, isInit, gSetter = undefined) => + if oldModel == undefined + @scope[scopePropName] = evaluate(model, modelKey) + unless isInit + gSetter(@scope) if gSetter? + return + + oldVal = evaluate(oldModel, modelKey) + newValue = evaluate(model, modelKey) + if(newValue != oldVal and @scope[scopePropName] != newValue) + @scope[scopePropName] = newValue + unless isInit + gSetter(@scope) if gSetter? + @gMarkerManager.draw() + + destroy: () => + @scope.$destroy() + + setCoords: (scope) => + if(scope.$id != @scope.$id or @gMarker == undefined) + return + if (scope.coords?) + if !@validateCoords(@scope.coords) + @$log.error "MarkerChildMarker cannot render marker as scope.coords as no position on marker: #{JSON.stringify @model}" + return + @gMarker.setPosition(@getCoords(scope.coords)) + @gMarker.setVisible(@validateCoords(scope.coords)) + @gMarkerManager.remove(@gMarker) + @gMarkerManager.add(@gMarker) + else + @gMarkerManager.remove(@gMarker) + + setIcon: (scope) => + if(scope.$id != @scope.$id or @gMarker == undefined) + return + @gMarkerManager.remove(@gMarker) + @gMarker.setIcon(scope.icon) + @gMarkerManager.add(@gMarker) + @gMarker.setPosition(@getCoords(scope.coords)) + @gMarker.setVisible(@validateCoords(scope.coords)) + + setOptions: (scope) => + if(scope.$id != @scope.$id) + return + + if @gMarker? + @gMarkerManager.remove(@gMarker) + delete @gMarker + unless scope.coords ? scope.icon? scope.options? + return + @opts = @createMarkerOptions(scope.coords, scope.icon, scope.options) + + delete @gMarker + if @isLabelDefined(scope) + @gMarker = new MarkerWithLabel(@setLabelOptions(@opts, scope)) + else + @gMarker = new google.maps.Marker(@opts) + + @setEvents @gMarker, @parentScope, @model + + @gMarker.key = @id if @id + + @gMarkerManager.add(@gMarker) + google.maps.event.addListener @gMarker, 'click', => + if @doClick and @scope.click? + @scope.click() + + isLabelDefined: (scope) => + scope.labelContent? + + setLabelOptions: (opts, scope) => + opts.labelAnchor = @getLabelPositionPoint(scope.labelAnchor) + opts.labelClass = scope.labelClass + opts.labelContent = scope.labelContent + opts + + watchDestroy: (scope)=> + scope.$on "$destroy", => + if @gMarker? #this is possible due to _async in that we created some Children but no gMarker yet + google.maps.event.clearListeners @gMarker, 'click' + if @parentScope?.events and _.isArray @parentScope.events + @parentScope.events.forEach (event, eventName) -> + google.maps.event.clearListeners @gMarker, eventName + @gMarkerManager.remove @gMarker, true + delete @gMarker + self = undefined + MarkerChildModel + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/child/polyline-child-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/child/polyline-child-model.coffee new file mode 100644 index 0000000..eaebf24 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/child/polyline-child-model.coffee @@ -0,0 +1,86 @@ +angular.module("google-maps.directives.api") +.factory "PolylineChildModel", ["BaseObject", "Logger", "$timeout", "array-sync", "GmapUtil", (BaseObject, Logger, $timeout, arraySync, GmapUtil) -> + $log = Logger + class PolylineChildModel extends BaseObject + @include GmapUtil + constructor: (@scope, @attrs, @map, @defaults, @model) -> + pathPoints = @convertPathPoints scope.path + @polyline = new google.maps.Polyline @buildOpts pathPoints + GmapUtil.extendMapBounds map, pathPoints if scope.fit + if !scope.static and angular.isDefined(scope.editable) + scope.$watch "editable", (newValue, oldValue) => + @polyline.setEditable newValue if newValue != oldValue + + if angular.isDefined scope.draggable + scope.$watch "draggable", (newValue, oldValue) => + @polyline.setDraggable newValue if newValue != oldValue + + if angular.isDefined scope.visible + scope.$watch "visible", (newValue, oldValue) => + @polyline.setVisible newValue if newValue != oldValue + + if angular.isDefined scope.geodesic + scope.$watch "geodesic", (newValue, oldValue) => + @polyline.setOptions @buildOpts(@polyline.getPath()) if newValue != oldValue + + if angular.isDefined(scope.stroke) and angular.isDefined(scope.stroke.weight) + scope.$watch "stroke.weight", (newValue, oldValue) => + @polyline.setOptions @buildOpts(@polyline.getPath()) if newValue != oldValue + + if angular.isDefined(scope.stroke) and angular.isDefined(scope.stroke.color) + scope.$watch "stroke.color", (newValue, oldValue) => + @polyline.setOptions @buildOpts(@polyline.getPath()) if newValue != oldValue + + if angular.isDefined(scope.stroke) and angular.isDefined(scope.stroke.opacity) + scope.$watch "stroke.opacity", (newValue, oldValue) => + @polyline.setOptions @buildOpts(@polyline.getPath()) if newValue != oldValue + + if angular.isDefined(scope.icons) + scope.$watch "icons", (newValue, oldValue) => + @polyline.setOptions @buildOpts(@polyline.getPath()) if newValue != oldValue + + # To properly support the fit attribute, + # array-sync needs to be upgraded to support an optional pathChanged callback + # function that is called with the path points whenever they have been changed. + arraySyncer = arraySync @polyline.getPath(), scope, "path", (pathPoints) => + GmapUtil.extendMapBounds map, pathPoints if scope.fit + + # Remove @polyline on scope $destroy + scope.$on "$destroy", => + @polyline.setMap null + @polyline = null + @scope = null + if arraySyncer + arraySyncer() + arraySyncer = null + + $log.info @ + + buildOpts:(pathPoints) => + opts = angular.extend({}, @defaults, + map: @map + path: pathPoints + icons: @scope.icons + strokeColor: @scope.stroke and @scope.stroke.color + strokeOpacity: @scope.stroke and @scope.stroke.opacity + strokeWeight: @scope.stroke and @scope.stroke.weight + ) + angular.forEach + clickable: true + draggable: false + editable: false + geodesic: false + visible: true + static: false + fit: false + , (defaultValue, key) => + if angular.isUndefined(@scope[key]) or @scope[key] is null + opts[key] = defaultValue + else + opts[key] = @scope[key] + opts.editable = false if opts.static + opts + + destroy:() -> + @scope.$destroy() +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/child/window-child-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/child/window-child-model.coffee new file mode 100644 index 0000000..5f962dc --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/child/window-child-model.coffee @@ -0,0 +1,152 @@ +angular.module("google-maps.directives.api.models.child") +.factory "WindowChildModel", [ "BaseObject", "GmapUtil", "Logger", "$compile", "$http", "$templateCache", + (BaseObject, GmapUtil, Logger, $compile, $http, $templateCache) -> + class WindowChildModel extends BaseObject + @include GmapUtil + constructor: (@model, @scope, @opts, @isIconVisibleOnClick, @mapCtrl, @markerCtrl, + @element, @needToManualDestroy = false, @markerIsVisibleAfterWindowClose = true)-> + @googleMapsHandles = [] + @$log = Logger + @createGWin() + # Open window on click + @markerCtrl.setClickable(true) if @markerCtrl? + + @handleClick() + @watchElement() + @watchShow() + @watchCoords() + @$log.info(@) + + watchElement:=> + @scope.$watch => + return if not @element or not @html + if @html != @element.html() #has content changed? + if @gWin + @opts?.content = undefined + @remove() + @createGWin() + @showHide() + + createGWin:() => + if !@gWin? + defaults = {} + if @opts? + #being double careful for race condition on @opts.position via watch coords (if element and coords change at same time) + @opts.position = @getCoords @scope.coords if @scope.coords + defaults = @opts + if @element + @html = if _.isObject(@element) then @element.html() else @element + @opts = @createWindowOptions(@markerCtrl, @scope, @html, defaults) + + if @opts? and !@gWin + if @opts.boxClass and (window.InfoBox && typeof window.InfoBox == 'function') + @gWin = new window.InfoBox(@opts) + else + @gWin = new google.maps.InfoWindow(@opts) + + # Set visibility of marker back to what it was before opening the window + @googleMapsHandles.push google.maps.event.addListener @gWin, 'closeclick', => + if @markerCtrl + @markerCtrl.setAnimation @oldMarkerAnimation + if @markerIsVisibleAfterWindowClose + _.delay => #appears to help animation chrome bug + @markerCtrl.setVisible false + @markerCtrl.setVisible @markerIsVisibleAfterWindowClose + ,250 + @gWin.isOpen(false) + @scope.closeClick() if @scope.closeClick? + + watchShow: () => + @scope.$watch('show', (newValue, oldValue) => + if (newValue != oldValue) + if (newValue) + @showWindow() + else + @hideWindow() + else + if @gWin? + if (newValue and !@gWin.getMap()) + # If we're initially showing the marker and it's not yet visible, show it. + @showWindow() + , true) + + watchCoords: ()=> + scope = if @markerCtrl? then @scope.$parent else @scope + scope.$watch('coords', (newValue, oldValue) => + if (newValue != oldValue) + unless newValue? + @hideWindow() + else + if !@validateCoords(newValue) + @$log.error "WindowChildMarker cannot render marker as scope.coords as no position on marker: #{JSON.stringify @model}" + return + pos = @getCoords(newValue) + @gWin.setPosition pos + @opts.position = pos if @opts + , true) + + handleClick: (forceClick)=> + # Show the window and hide the marker on click + click = => + @createGWin() unless @gWin? + pos = @markerCtrl.getPosition() + if @gWin? + @gWin.setPosition(pos) + @opts.position = pos if @opts + @showWindow() + @initialMarkerVisibility = @markerCtrl.getVisible() + @oldMarkerAnimation = @markerCtrl.getAnimation() + @markerCtrl.setVisible(@isIconVisibleOnClick) + + if @markerCtrl? + click() if forceClick + @googleMapsHandles.push google.maps.event.addListener @markerCtrl, 'click', click + + + showWindow: () => + show = () => + if @gWin + if (@scope.show || !@scope.show?) and !@gWin.isOpen() #only show if we have no show defined yet or if show is really true + @gWin.open(@mapCtrl) + if @scope.templateUrl + if @gWin + $http.get(@scope.templateUrl, { cache: $templateCache }).then (content) => + templateScope = @scope.$new() + if angular.isDefined(@scope.templateParameter) + templateScope.parameter = @scope.templateParameter + compiled = $compile(content.data)(templateScope) + @gWin.setContent(compiled[0]) + show() + else + show() + + showHide: -> + if @scope.show + @showWindow() + else + @hideWindow() + + getLatestPosition: (overridePos) => + if @gWin? and @markerCtrl? and not overridePos + @gWin.setPosition @markerCtrl.getPosition() + else + @gWin.setPosition overridePos if overridePos + + hideWindow: () => + @gWin.close() if @gWin? and @gWin.isOpen() + + remove: => + @hideWindow() + _.each @googleMapsHandles, (h) -> + google.maps.event.removeListener h + @googleMapsHandles.length = 0 + delete @gWin + + destroy: (manualOverride = false)=> + @remove() + if @scope? and (@needToManualDestroy or manualOverride) + @scope.$destroy() + self = undefined + + WindowChildModel + ] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/i-marker-parent-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/i-marker-parent-model.coffee new file mode 100644 index 0000000..462e7f6 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/i-marker-parent-model.coffee @@ -0,0 +1,52 @@ +### + - interface for all markers to derrive from + - to enforce a minimum set of requirements + - attributes + - coords + - icon + - implementation needed on watches +### +angular.module("google-maps.directives.api.models.parent") +.factory "IMarkerParentModel", ["ModelKey","Logger", (ModelKey, Logger) -> + class IMarkerParentModel extends ModelKey + DEFAULTS: {} + constructor: (@scope, @element, @attrs, @map, @$timeout) -> + super(@scope) + self = @ + @$log = Logger + # Validate required properties + throw new String("Unable to construct IMarkerParentModel due to invalid scope") unless @validateScope scope + @doClick = angular.isDefined attrs.click + if scope.options? + @DEFAULTS = scope.options + # Wrap marker initialization inside a $timeout() call to make sure the map is created already + @watch 'coords', @scope + @watch 'icon', @scope + @watch 'options', @scope + scope.$on "$destroy", => + @onDestroy(scope) + + validateScope: (scope)=> + unless scope? + @$log.error(@constructor.name + ": invalid scope used") + return false + ret = scope.coords? + unless ret + @$log.error(@constructor.name + ": no valid coords attribute found") + return false + ret + + watch: (propNameToWatch, scope) => + scope.$watch propNameToWatch, (newValue, oldValue) => + if ! _.isEqual newValue,oldValue + @onWatch(propNameToWatch, scope, newValue, oldValue) + , true + + onWatch: (propNameToWatch, scope, newValue, oldValue) => + throw new String("OnWatch Not Implemented!!") + + onDestroy: (scope) => + throw new String("OnDestroy Not Implemented!!") + + return IMarkerParentModel +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/i-window-parent-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/i-window-parent-model.coffee new file mode 100644 index 0000000..f960c93 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/i-window-parent-model.coffee @@ -0,0 +1,22 @@ +### + - interface directive for all window(s) to derrive from +### +angular.module("google-maps.directives.api.models.parent") +.factory "IWindowParentModel", ["ModelKey", "GmapUtil", "Logger",(ModelKey, GmapUtil,Logger) -> + class IWindowParentModel extends ModelKey + @include GmapUtil + # Animation is enabled by default + DEFAULTS: {} + + constructor: (scope, element, attrs, ctrls, $timeout, $compile, $http, $templateCache) -> + super(scope) + self = @ + @$log = Logger + @$timeout = $timeout + @$compile = $compile + @$http = $http + @$templateCache = $templateCache + if scope.options? + @DEFAULTS = scope.options + IWindowParentModel +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/layer-parent-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/layer-parent-model.coffee new file mode 100644 index 0000000..e074048 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/layer-parent-model.coffee @@ -0,0 +1,43 @@ +angular.module("google-maps.directives.api.models.parent") +.factory "LayerParentModel", ["BaseObject", "Logger", '$timeout',(BaseObject, Logger,$timeout) -> + class LayerParentModel extends BaseObject + constructor: (@scope, @element, @attrs, @gMap, @onLayerCreated = undefined, @$log = Logger) -> + unless @attrs.type? + @$log.info("type attribute for the layer directive is mandatory. Layer creation aborted!!") + return + @createGoogleLayer() + @doShow = true + + @doShow = @scope.show if angular.isDefined(@attrs.show) + @layer.setMap @gMap if @doShow and @gMap? + @scope.$watch("show", (newValue, oldValue) => + if newValue isnt oldValue + @doShow = newValue + if newValue + @layer.setMap @gMap + else + @layer.setMap null + , true) + @scope.$watch("options", (newValue, oldValue) => + if newValue isnt oldValue + @layer.setMap null + @layer = null + @createGoogleLayer() + , true) + @scope.$on "$destroy", => + @layer.setMap null + + createGoogleLayer: ()=> + unless @attrs.options? + @layer = if @attrs.namespace == undefined then new google.maps[@attrs.type]() + else new google.maps[@attrs.namespace][@attrs.type]() + else + @layer = if@attrs.namespace == undefined then new google.maps[@attrs.type](@scope.options) + else new google.maps[@attrs.namespace][@attrs.type](@scope.options) + + if @layer? and @onLayerCreated? + fn = @onLayerCreated(@scope, @layer) + if fn + fn(@layer) + LayerParentModel +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/marker-parent-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/marker-parent-model.coffee new file mode 100644 index 0000000..9757cc6 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/marker-parent-model.coffee @@ -0,0 +1,78 @@ +### + Basic Directive api for a marker. Basic in the sense that this directive contains 1:1 on scope and model. + Thus there will be one html element per marker within the directive. +### +angular.module("google-maps.directives.api.models.parent") +.factory "MarkerParentModel", ["IMarkerParentModel", "GmapUtil", "EventsHelper", + (IMarkerParentModel, GmapUtil, EventsHelper) -> + # TODO: Eventually this directive should be using marker-child-model (for this to happen something will need to be done with parentScope) + # currently this Parent directive is acting as a child where Marker is creating MarkerParentModel which directly creates a Marker (MarkerChild does the same) + class MarkerParentModel extends IMarkerParentModel + @include GmapUtil + @include EventsHelper + constructor: (scope, element, attrs, map, $timeout, @gMarkerManager, @doFit) -> + super(scope, element, attrs, map, $timeout) + self = @ + opts = @createMarkerOptions scope.coords, scope.icon, scope.options, @map + #using scope.$id as the identifier for a marker as scope.$id should be unique, no need for an index (as it is the index) + @setGMarker new google.maps.Marker(opts) + + @listener = google.maps.event.addListener @scope.gMarker, 'click', => + if @doClick and scope.click? + @$timeout => + @scope.click() + + @setEvents @scope.gMarker, scope, scope + @$log.info(@) + + + onWatch: (propNameToWatch, scope) => + switch propNameToWatch + when 'coords' + if (@validateCoords(scope.coords) and @scope.gMarker?) + pos = @scope.gMarker?.getPosition() + return if(pos.lat() == @scope.coords.latitude and @scope.coords.longitude == pos.lng()) + old = @scope.gMarker.getAnimation() + @scope.gMarker.setMap @map + @scope.gMarker.setPosition @getCoords(scope.coords) + @scope.gMarker.setVisible @validateCoords(scope.coords) + @scope.gMarker.setAnimation(old) + else + # Remove marker + @scope.gMarker.setMap null + when 'icon' + if (scope.icon? and @validateCoords(scope.coords) and @scope.gMarker?) + @scope.gMarker.setIcon scope.icon + @scope.gMarker.setMap null + @scope.gMarker.setMap @map + @scope.gMarker.setPosition @getCoords(scope.coords) + @scope.gMarker.setVisible @validateCoords(scope.coords) + when 'options' + if @validateCoords(scope.coords) and scope.icon? and scope.options + if @scope.gMarker? + @onDestroy(scope) + @onTimeOut(scope) + + setGMarker: (gMarker) => + if @scope.gMarker + delete @scope.gMarker + @gMarkerManager.remove @scope.gMarker, false + @scope.gMarker = gMarker + if @scope.gMarker + @gMarkerManager.add @scope.gMarker, false + @gMarkerManager.fit() if @doFit + + onDestroy: (scope)=> + unless @scope.gMarker + self = undefined + return + #remove from gMaps and then free resources + @scope.gMarker.setMap null + google.maps.event.removeListener @listener + @listener = null + @gMarkerManager.remove @scope.gMarker, false + delete @scope.gMarker + self = undefined + + return MarkerParentModel + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/markers-parent-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/markers-parent-model.coffee new file mode 100644 index 0000000..dcbb745 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/markers-parent-model.coffee @@ -0,0 +1,151 @@ +angular.module("google-maps.directives.api.models.parent") +.factory "MarkersParentModel", ["IMarkerParentModel", "ModelsWatcher", "PropMap", "MarkerChildModel", "ClustererMarkerManager", "MarkerManager", + (IMarkerParentModel, ModelsWatcher, PropMap, MarkerChildModel, ClustererMarkerManager, MarkerManager) -> + class MarkersParentModel extends IMarkerParentModel + @include ModelsWatcher + constructor: (scope, element, attrs, map, $timeout) -> + super(scope, element, attrs, map, $timeout) + self = @ + @scope.markerModels = new PropMap() + + @$timeout = $timeout + @$log.info @ + #assume do rebuild all is false and were lookging for a modelKey prop of id + @doRebuildAll = if @scope.doRebuildAll? then @scope.doRebuildAll else false + @setIdKey scope + @scope.$watch 'doRebuildAll', (newValue, oldValue) => + if (newValue != oldValue) + @doRebuildAll = newValue + + #watch all the below properties with end up being processed by onWatch below + @watch('models', scope) + @watch('doCluster', scope) + @watch('clusterOptions', scope) + @watch('clusterEvents', scope) + @watch('fit', scope) + @watch('idKey', scope) + @gMarkerManager = undefined + @createMarkersFromScratch(scope) + + + onWatch: (propNameToWatch, scope, newValue, oldValue) => + if propNameToWatch == "idKey" and newValue != oldValue + @idKey = newValue + if @doRebuildAll + @reBuildMarkers(scope) + else + @pieceMealMarkers(scope) + + validateScope: (scope)=> + modelsNotDefined = angular.isUndefined(scope.models) or scope.models == undefined + if(modelsNotDefined) + @$log.error(@constructor.name + ": no valid models attribute found") + + super(scope) or modelsNotDefined + + createMarkersFromScratch: (scope) => + if scope.doCluster + if scope.clusterEvents + @clusterInternalOptions = do _.once => + self = @ + unless @origClusterEvents + @origClusterEvents = + click: scope.clusterEvents?.click + mouseout: scope.clusterEvents?.mouseout + mouseover: scope.clusterEvents?.mouseover + _.extend scope.clusterEvents, + click:(cluster) -> + self.maybeExecMappedEvent cluster, 'click' + mouseout:(cluster) -> + self.maybeExecMappedEvent cluster, 'mouseout' + mouseover:(cluster) -> + self.maybeExecMappedEvent cluster, 'mouseover' + + if scope.clusterOptions or scope.clusterEvents + if @gMarkerManager == undefined + @gMarkerManager = new ClustererMarkerManager @map, + undefined, + scope.clusterOptions, + @clusterInternalOptions + else + if @gMarkerManager.opt_options != scope.clusterOptions + @gMarkerManager = new ClustererMarkerManager @map, + undefined, + scope.clusterOptions, + @clusterInternalOptions + else + @gMarkerManager = new ClustererMarkerManager @map + else + @gMarkerManager = new MarkerManager @map + + _async.each scope.models, (model) => + @newChildMarker(model, scope) + , () => #handle done callBack + @gMarkerManager.draw() + @gMarkerManager.fit() if scope.fit + + + reBuildMarkers: (scope) => + if(!scope.doRebuild and scope.doRebuild != undefined) + return + @onDestroy(scope) #clean @scope.markerModels + @createMarkersFromScratch(scope) + + pieceMealMarkers: (scope)=> + if @scope.models? and @scope.models.length > 0 and @scope.markerModels.length > 0 #and @scope.models.length == @scope.markerModels.length + #find the current state, async operation that calls back + @figureOutState @idKey, scope, @scope.markerModels, @modelKeyComparison, (state) => + payload = state + #payload contains added, removals and flattened (existing models with their gProp appended) + #remove all removals clean up scope (destroy removes itself from markerManger), finally remove from @scope.markerModels + _async.each payload.removals, (child)=> + if child? + child.destroy() if child.destroy? + @scope.markerModels.remove(child.id) + , () => + #add all adds via creating new ChildMarkers which are appended to @scope.markerModels + _async.each payload.adds, (modelToAdd) => + @newChildMarker(modelToAdd, scope) + , () => + #finally redraw if something has changed + if(payload.adds.length > 0 or payload.removals.length > 0) + @gMarkerManager.draw() + scope.markerModels = @scope.markerModels #for other directives like windows + else + @reBuildMarkers(scope) + + newChildMarker: (model, scope)=> + unless model[@idKey]? + @$log.error("Marker model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key.") + return + @$log.info('child', child, 'markers', @scope.markerModels) + child = new MarkerChildModel(model, scope, @map, @$timeout, @DEFAULTS, + @doClick, @gMarkerManager, @idKey) + @scope.markerModels.put(model[@idKey], child) #major change this makes model.id a requirement + child + + onDestroy: (scope)=> + #need to figure out how to handle individual destroys + #slap index to the external model so that when they pass external back + #for destroy we have a lookup? + #this will require another attribute for destroySingle(marker) + _.each @scope.markerModels.values(), (model)-> + model.destroy() if model? + delete @scope.markerModels + @scope.markerModels = new PropMap() + @gMarkerManager.clear() if @gMarkerManager? + + maybeExecMappedEvent:(cluster, fnName) -> + if _.isFunction @scope.clusterEvents?[fnName] + pair = @mapClusterToMarkerModels cluster + @origClusterEvents[fnName](pair.cluster,pair.mapped) if @origClusterEvents[fnName] + + mapClusterToMarkerModels:(cluster) -> + gMarkers = cluster.getMarkers() + mapped = gMarkers.map (g) => + @scope.markerModels[g.key].model + cluster: cluster + mapped: mapped + + return MarkersParentModel +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/polylines-parent-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/polylines-parent-model.coffee new file mode 100644 index 0000000..2b4a3d8 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/polylines-parent-model.coffee @@ -0,0 +1,153 @@ +### + Windows directive where many windows map to the models property +### +angular.module("google-maps.directives.api.models.parent") +.factory "PolylinesParentModel", + ["$timeout","Logger","ModelKey", "ModelsWatcher", "PropMap", "PolylineChildModel", + ($timeout,Logger,ModelKey,ModelsWatcher,PropMap,PolylineChildModel) -> + class PolylinesParentModel extends ModelKey + @include ModelsWatcher + constructor: (@scope, @element, @attrs, @gMap, @defaults) -> + super(scope) + self = @ + @$log = Logger + @plurals = new PropMap() + @scopePropNames = [ + 'path' + 'stroke' + 'clickable' + 'draggable' + 'editable' + 'geodesic' + 'icons' + 'visible' + ] + #setting up local references to propety keys IE: @pathKey + _.each @scopePropNames, (name) => + @[name + 'Key'] = undefined + @models = undefined + @firstTime = true + @$log.info @ + + @watchOurScope(scope) + @createChildScopes() + + #watch this scope(Parent to all Models), these updates reflect expression / Key changes + #thus they need to be pushed to all the children models so that they are bound to the correct objects / keys + watch: (scope, name, nameKey) => + scope.$watch name, (newValue, oldValue) => + if (newValue != oldValue) + @[nameKey] = if typeof newValue == 'function' then newValue() else newValue + _async.each _.values(@plurals), (model) => + model.scope[name] = if @[nameKey] == 'self' then model else model[@[nameKey]] + , () => + + watchModels: (scope) => + scope.$watch 'models', (newValue, oldValue) => + #check to make sure that the newValue Array is really a set of new objects + unless _.isEqual(newValue, oldValue) + if @doINeedToWipe(newValue) + @rebuildAll(scope, true, true) + else + @createChildScopes(false) + ,true + + doINeedToWipe: (newValue) => + newValueIsEmpty = if newValue? then newValue.length == 0 else true + @plurals.length > 0 and newValueIsEmpty + + rebuildAll: (scope, doCreate, doDelete) => + _async.each @plurals.values(), (model) => + model.destroy() + , () => #handle done callBack + delete @plurals if doDelete + @plurals = new PropMap() + @createChildScopes() if doCreate + + watchDestroy: (scope)=> + scope.$on "$destroy", => + @rebuildAll(scope, false, true) + + watchOurScope: (scope) => + _.each @scopePropNames, (name) => + nameKey = name + 'Key' + @[nameKey] = if typeof scope[name] == 'function' then scope[name]() else scope[name] + @watch(scope, name, nameKey) + + createChildScopes: (isCreatingFromScratch = true) => + if angular.isUndefined(@scope.models) + @$log.error("No models to create polylines from! I Need direct models!") + return + + if @gMap? + #at the very least we need a Map, the marker is optional as we can create Windows without markers + if @scope.models? + @watchIdKey @scope + if isCreatingFromScratch + @createAllNew @scope, false + else + @pieceMeal @scope, false + + watchIdKey: (scope)=> + @setIdKey scope + scope.$watch 'idKey', (newValue, oldValue) => + if (newValue != oldValue and !newValue?) + @idKey = newValue + @rebuildAll(scope, true, true) + + createAllNew: (scope, isArray = false) => + @models = scope.models + if @firstTime + @watchModels scope + @watchDestroy scope + _async.each scope.models, (model) => + @createChild(model, @gMap) + , () => #handle done callBack + @firstTime = false + + pieceMeal: (scope, isArray = true)=> + @models = scope.models + if scope? and scope.models? and scope.models.length > 0 and @plurals.length > 0 + @figureOutState @idKey, scope, @plurals, @modelKeyComparison, (state) => + payload = state + _async.each payload.removals, (id)=> + child = @plurals[id] + if child? + child.destroy() + @plurals.remove(id) + , () => + #add all adds via creating new ChildMarkers which are appended to @markers + _async.each payload.adds, (modelToAdd) => + @createChild(modelToAdd, @gMap) + , ()=> + else + @rebuildAll(@scope, true, true) + + createChild: (model, gMap)=> + childScope = @scope.$new(false) + @setChildScope(childScope, model) + + childScope.$watch('model', (newValue, oldValue) => + if(newValue != oldValue) + @setChildScope(childScope, newValue) + , true) + childScope.static = @scope.static + child = new PolylineChildModel childScope, @attrs, gMap, @defaults, model + + unless model[@idKey]? + @$log.error("Polyline model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key.") + return + @plurals.put(model[@idKey], child) + child + + setChildScope: (childScope, model) => + _.each @scopePropNames, (name) => + nameKey = name + 'Key' + newValue = if @[nameKey] == 'self' then model else model[@[nameKey]] + if(newValue != childScope[name]) + childScope[name] = newValue + childScope.model = model + + modelKeyComparison: (model1, model2) => + _.isEqual @evalModelHandle(model1, @scope.path), @evalModelHandle(model2,@scope.path) + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/windows-parent-model.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/windows-parent-model.coffee new file mode 100644 index 0000000..0cdaa35 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/models/parent/windows-parent-model.coffee @@ -0,0 +1,205 @@ +### + Windows directive where many windows map to the models property +### +angular.module("google-maps.directives.api.models.parent") +.factory "WindowsParentModel", + ["IWindowParentModel", "ModelsWatcher", "PropMap", "WindowChildModel", "Linked", + (IWindowParentModel, ModelsWatcher, PropMap, WindowChildModel, Linked) -> + class WindowsParentModel extends IWindowParentModel + @include ModelsWatcher + constructor: (scope, element, attrs, ctrls, $timeout, $compile, $http, $templateCache, @$interpolate) -> + super(scope, element, attrs, ctrls, $timeout, $compile, $http, $templateCache) + self = @ + @windows = new PropMap() + + @scopePropNames = ['show', 'coords', 'templateUrl', 'templateParameter', + 'isIconVisibleOnClick', 'closeClick'] + #setting up local references to propety keys IE: @coordsKey + _.each @scopePropNames, (name) => + @[name + 'Key'] = undefined + @linked = new Linked(scope, element, attrs, ctrls) + @models = undefined + @contentKeys = undefined #model keys to parse html angular content + @isIconVisibleOnClick = undefined + @firstTime = true + @$log.info(self) + @parentScope = undefined + + mapScope = ctrls[0].getScope() + mapScope.deferred.promise.then (map) => + @gMap = map + markerCtrl = if ctrls.length > 1 and ctrls[1]? then ctrls[1] else undefined + if not markerCtrl + @go(scope) + return + markerCtrl.getScope().deferred.promise.then => + @markerScope = markerCtrl.getScope() + @go(scope) + + go:(scope) => + @watchOurScope(scope) + @doRebuildAll = if @scope.doRebuildAll? then @scope.doRebuildAll else false + scope.$watch 'doRebuildAll', (newValue, oldValue) => + if (newValue != oldValue) + @doRebuildAll = newValue + + @createChildScopesWindows() + #watch this scope(Parent to all WindowModels), these updates reflect expression / Key changes + #thus they need to be pushed to all the children models so that they are bound to the correct objects / keys + watch: (scope, name, nameKey) => + scope.$watch name, (newValue, oldValue) => + if (newValue != oldValue) + @[nameKey] = if typeof newValue == 'function' then newValue() else newValue + _async.each _.values(@windows), (model) => + model.scope[name] = if @[nameKey] == 'self' then model else model[@[nameKey]] + , () => + + watchModels: (scope) => + scope.$watch 'models', (newValue, oldValue) => + #check to make sure that the newValue Array is really a set of new objects + unless _.isEqual(newValue, oldValue) + if @doRebuildAll or @doINeedToWipe(newValue) + @rebuildAll(scope, true, true) + else + @createChildScopesWindows(false) + + doINeedToWipe: (newValue) => + newValueIsEmpty = if newValue? then newValue.length == 0 else true + @windows.length > 0 and newValueIsEmpty + + rebuildAll: (scope, doCreate, doDelete) => + _async.each @windows.values(), (model) => + model.destroy() + , () => #handle done callBack + delete @windows if doDelete + @windows = new PropMap() + @createChildScopesWindows() if doCreate + + watchDestroy: (scope)=> + scope.$on "$destroy", => + @rebuildAll(scope, false, true) + + watchOurScope: (scope) => + _.each @scopePropNames, (name) => + nameKey = name + 'Key' + @[nameKey] = if typeof scope[name] == 'function' then scope[name]() else scope[name] + @watch(scope, name, nameKey) + + createChildScopesWindows: (isCreatingFromScratch = true) => + ### + being that we cannot tell the difference in Key String vs. a normal value string (TemplateUrl) + we will assume that all scope values are string expressions either pointing to a key (propName) or using + 'self' to point the model as container/object of interest. + + This may force redundant information into the model, but this appears to be the most flexible approach. + ### + @isIconVisibleOnClick = true + if angular.isDefined(@linked.attrs.isiconvisibleonclick) + @isIconVisibleOnClick = @linked.scope.isIconVisibleOnClick + + markersScope = @markerScope + modelsNotDefined = angular.isUndefined @linked.scope.models + + if modelsNotDefined and (markersScope == undefined or (markersScope.markerModels == undefined or markersScope.models == undefined)) + @$log.error("No models to create windows from! Need direct models or models derrived from markers!") + return + if @gMap? + #at the very least we need a Map, the marker is optional as we can create Windows without markers + if @linked.scope.models? + #we are creating windows with no markers + @watchIdKey @linked.scope + if isCreatingFromScratch + @createAllNewWindows @linked.scope, false + else + @pieceMealWindows @linked.scope, false + else + #creating windows with parent markers + @parentScope = markersScope + @watchIdKey @parentScope + if isCreatingFromScratch + @createAllNewWindows markersScope, true, 'markerModels', false + else + @pieceMealWindows markersScope, true, 'markerModels', false + + watchIdKey: (scope)=> + @setIdKey scope + scope.$watch 'idKey', (newValue, oldValue) => + if (newValue != oldValue and !newValue?) + @idKey = newValue + @rebuildAll(scope, true, true) + + + createAllNewWindows: (scope, hasGMarker, modelsPropToIterate = 'models', isArray = false) => + @models = scope.models + if @firstTime + @watchModels scope + @watchDestroy scope + @setContentKeys(scope.models) #only setting content keys once per model array + _async.each scope.models, (model) => + gMarker = if hasGMarker + then scope[modelsPropToIterate][[model[@idKey]]].gMarker else undefined + @createWindow(model, gMarker, @gMap) + , () => #handle done callBack + @firstTime = false + + + + pieceMealWindows: (scope, hasGMarker, modelsPropToIterate = 'models', isArray = true)=> + @models = scope.models + if scope? and scope.models? and scope.models.length > 0 and @windows.length > 0 + @figureOutState @idKey, scope, @windows, @modelKeyComparison, (state) => + payload = state + _async.each payload.removals, (child)=> + if child? + child.destroy() if child.destroy? + @windows.remove(child.id) + , () => + #add all adds via creating new ChildMarkers which are appended to @markers + _async.each payload.adds, (modelToAdd) => + gMarker = scope[modelsPropToIterate][modelToAdd[@idKey]].gMarker + @createWindow(modelToAdd, gMarker, @gMap) + , ()=> + else + @rebuildAll(@scope, true, true) + + setContentKeys: (models)=> + if(models.length > 0) + @contentKeys = Object.keys(models[0]) + + createWindow: (model, gMarker, gMap)=> + childScope = @linked.scope.$new(false) + @setChildScope(childScope, model) + childScope.$watch('model', (newValue, oldValue) => + if(newValue != oldValue) + @setChildScope(childScope, newValue) + , true) + fakeElement = + html: => + @interpolateContent(@linked.element.html(), model) + opts = @createWindowOptions gMarker, childScope, fakeElement.html(), @DEFAULTS + child = new WindowChildModel model, childScope, opts, @isIconVisibleOnClick, gMap, gMarker, fakeElement, true, true + + unless model[@idKey]? + @$log.error("Window model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key.") + return + @windows.put(model[@idKey], child) + child + + setChildScope: (childScope, model) => + _.each @scopePropNames, (name) => + nameKey = name + 'Key' + newValue = if @[nameKey] == 'self' then model else model[@[nameKey]] + if(newValue != childScope[name]) + childScope[name] = newValue + childScope.model = model + + interpolateContent: (content, model) => + if @contentKeys == undefined or @contentKeys.length == 0 + return + exp = @$interpolate(content) + interpModel = {} + interpModel[key] = model[key] for key in @contentKeys + exp(interpModel) + + WindowsParentModel + ] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/polyline.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/polyline.coffee new file mode 100644 index 0000000..fb70fac --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/polyline.coffee @@ -0,0 +1,15 @@ +angular.module("google-maps.directives.api") +.factory "Polyline", ["IPolyline", "$timeout", "array-sync", "PolylineChildModel", + (IPolyline, $timeout, arraySync, PolylineChildModel) -> + class Polyline extends IPolyline + link: (scope, element, attrs, mapCtrl) => + # Validate required properties + if angular.isUndefined(scope.path) or scope.path is null or + not @validatePath(scope.path) + @$log.error "polyline: no valid path attribute found" + return + + # Wrap polyline initialization inside a $timeout() call to make sure the map is created already + mapCtrl.getScope().deferred.promise.then (map) => + new PolylineChildModel scope, attrs, map, @DEFAULTS + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/polylines.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/polylines.coffee new file mode 100644 index 0000000..c93ed83 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/polylines.coffee @@ -0,0 +1,25 @@ +angular.module("google-maps.directives.api") +.factory "Polylines", ["IPolyline", "$timeout", "array-sync", "PolylinesParentModel", + (IPolyline, $timeout, arraySync, PolylinesParentModel) -> + class Polylines extends IPolyline + constructor:() -> + super() + @scope.idKey = '=idkey' #id key to bind to that makes a model unique, if it does not exist default to rebuilding all markers + @scope.models = '=models' #if undefined it will try get a markers models + #deprecating doRebuildAll, not even going to add it to polylines + @$log.info @ + + link: (scope, element, attrs, mapCtrl) => + # Validate required properties + if angular.isUndefined(scope.path) or scope.path is null + @$log.error "polylines: no valid path attribute found" + return + + unless scope.models + @$log.error "polylines: no models found to create from" + return + + # Wrap polyline initialization inside a $timeout() call to make sure the map is created already + mapCtrl.getScope().deferred.promise.then (map) => + new PolylinesParentModel scope, element, attrs, map, @DEFAULTS + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/_async.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/_async.coffee new file mode 100644 index 0000000..a6899da --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/_async.coffee @@ -0,0 +1,52 @@ +### + Author: Nicholas McCready & jfriend00 + _async handles things asynchronous-like :), to allow the UI to be free'd to do other things + Code taken from http://stackoverflow.com/questions/10344498/best-way-to-iterate-over-an-array-without-blocking-the-ui + + The design of any funcitonality of _async is to be like lodash/underscore and replicate it but call things + asynchronously underneath. Each should be sufficient for most things to be derrived from. + + TODO: Handle Object iteration like underscore and lodash as well.. not that important right now +### +async = + each: (array, callback, doneCallBack, pausedCallBack, chunk = 20, index = 0, pause = 1) -> + unless pause + throw "pause (delay) must be set from _async!" + return + if array == undefined or array?.length <= 0 + doneCallBack() + return + # set this to whatever number of items you can process at once + doChunk = () -> + cnt = chunk + i = index + + while cnt-- and i < (if array then array.length else i + 1) + # process array[index] here + callback(array[i],i) + ++i + + if array + if i < array.length + index = i + pausedCallBack() if pausedCallBack? + setTimeout(doChunk, pause) + else + doneCallBack() if doneCallBack + doChunk() + +#copied from underscore but w/ async each above + map: (objs, iterator, doneCallBack, pausedCallBack, chunk) -> + results = [] + return results unless objs? + _async.each objs, (o) -> + results.push iterator o + , () -> + doneCallBack(results) + , pausedCallBack, chunk + +window._async = async + +angular.module("google-maps.directives.api.utils") +.factory "async", -> + window._async diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/base-object.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/base-object.coffee new file mode 100644 index 0000000..ebc36b8 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/base-object.coffee @@ -0,0 +1,16 @@ +angular.module("google-maps.directives.api.utils") +.factory "BaseObject", -> + baseObjectKeywords = ['extended', 'included'] + class BaseObject + @extend: (obj) -> + for key, value of obj when key not in baseObjectKeywords + @[key] = value + obj.extended?.apply(@) + this + @include: (obj) -> + for key, value of obj when key not in baseObjectKeywords + #Assign properties to the prototype + @::[key] = value + obj.included?.apply(@) + this + BaseObject \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/child-events.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/child-events.coffee new file mode 100644 index 0000000..00210bf --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/child-events.coffee @@ -0,0 +1,10 @@ +### + Useful function callbacks that should be defined at later time. + Mainly to be used for specs to verify creation / linking. + + This is to lead a common design in notifying child stuff. +### +angular.module("google-maps.directives.api.utils") +.factory "ChildEvents", -> + onChildCreation: (child) -> + #doing nothing but can be hooked / overriden later \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/ctrl_handle.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/ctrl_handle.coffee new file mode 100644 index 0000000..0062a93 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/ctrl_handle.coffee @@ -0,0 +1,8 @@ +angular.module("google-maps.directives.api.utils") +.service 'CtrlHandle', ['$q', ($q) -> + CtrlHandle = + handle: ($scope, $element) -> + $scope.deferred = $q.defer() + getScope: -> + $scope +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/events-helper.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/events-helper.coffee new file mode 100644 index 0000000..4b0cb6b --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/events-helper.coffee @@ -0,0 +1,11 @@ +angular.module("google-maps.directives.api.utils") +.service "EventsHelper", ["Logger", ($log) -> + setEvents: (marker, scope, model) -> + if angular.isDefined(scope.events) and scope.events? and angular.isObject(scope.events) + _.compact _.map scope.events, (eventHandler, eventName) -> + if scope.events.hasOwnProperty(eventName) and angular.isFunction(scope.events[eventName]) + google.maps.event.addListener(marker, eventName, -> + eventHandler.apply(scope, [marker, eventName, model, arguments])) + else + $log.info "MarkerEventHelper: invalid event listener #{eventName}" + ] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/fit-helper.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/fit-helper.coffee new file mode 100644 index 0000000..2637688 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/fit-helper.coffee @@ -0,0 +1,14 @@ +angular.module("google-maps.directives.api.utils") +.factory "FitHelper", ["BaseObject", "Logger", (BaseObject,$log) -> + class FitHelper extends BaseObject + fit: (gMarkers, gMap) -> + if gMap and gMarkers and gMarkers.length > 0 + bounds = new google.maps.LatLngBounds(); + everSet = false + _async.each gMarkers, (gMarker) => + if gMarker + everSet = true unless everSet + bounds.extend(gMarker.getPosition()) + , () => + gMap.fitBounds(bounds) if everSet + ] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/gmap-util.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/gmap-util.coffee new file mode 100644 index 0000000..7379d00 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/gmap-util.coffee @@ -0,0 +1,179 @@ +angular.module("google-maps.directives.api.utils") +.service "GmapUtil",["Logger", "$compile", (Logger, $compile) -> + getCoords = (value) -> + return unless value + if Array.isArray(value) and value.length is 2 + new google.maps.LatLng(value[1], value[0]) + else if angular.isDefined(value.type) and value.type is "Point" + new google.maps.LatLng(value.coordinates[1], value.coordinates[0]) + else + new google.maps.LatLng(value.latitude, value.longitude) + + validateCoords = (coords) -> + return false if angular.isUndefined coords + + if _.isArray(coords) + return true if coords.length is 2 + else if coords? and coords?.type + return true if coords.type is "Point" and _.isArray(coords.coordinates) and coords.coordinates.length is 2 + return true if coords and angular.isDefined coords?.latitude and angular.isDefined coords?.longitude + false + + getLabelPositionPoint: (anchor) -> + if anchor == undefined + return undefined + + anchor = /^([-\d\.]+)\s([-\d\.]+)$/.exec(anchor) + xPos = parseFloat(anchor[1]) + yPos = parseFloat(anchor[2]) + if xPos? && yPos? + new google.maps.Point(xPos, yPos) + + createMarkerOptions: (coords, icon, defaults, map = undefined) -> + defaults = {} unless defaults? + + opts = angular.extend {}, defaults, + position: if defaults.position? then defaults.position + else getCoords(coords), + icon: if defaults.icon? then defaults.icon else icon, + visible: if defaults.visible? then defaults.visible else validateCoords(coords) + opts.map = map if map? + opts + + createWindowOptions: (gMarker, scope, content, defaults) -> + if content? and defaults? and $compile? + angular.extend {}, defaults, + content: @buildContent(scope,defaults,content), + position: if defaults.position? + then defaults.position else if angular.isObject(gMarker) + then gMarker.getPosition() else getCoords(scope.coords) + else + unless defaults + Logger.error "infoWindow defaults not defined" + Logger.error "infoWindow content not defined" unless content + else + return defaults + + buildContent:(scope,defaults,content) -> + if defaults.content? + ret = defaults.content + else + if $compile? + parsed =$compile(content)(scope) + if parsed.length > 0 + ret = parsed[0] #must be one element with children or angular bindings get lost + else + ret = content + ret + + defaultDelay: 50 + + isTrue:(val) -> + angular.isDefined(val) and val isnt null and val is true or val is "1" or val is "y" or val is "true" + + isFalse: (value) -> + ['false', 'FALSE', 0, 'n', 'N', 'no', 'NO'].indexOf(value) != -1 + + getCoords: getCoords + + validateCoords: validateCoords + + validatePath: (path) -> + i = 0 + if angular.isUndefined(path.type) + if not Array.isArray(path) or path.length < 2 + return false + + #Arrays of latitude/longitude objects or Google Maps LatLng objects are allowed + while i < path.length + if not ((angular.isDefined(path[i].latitude) and angular.isDefined(path[i].longitude)) or (typeof path[i].lat == "function" and typeof path[i].lng == "function")) + return false + + i++ + + true + else + return false if angular.isUndefined(path.coordinates) + + if path.type is "Polygon" + return false if path.coordinates[0].length < 4 + #Note: At this time, we only support the outer polygon and ignore the inner 'holes' + array = path.coordinates[0] + else if path.type is "MultiPolygon" + #Note: At this time, we will display the polygon with the most vertices + trackMaxVertices = { max: 0, index: 0 } + _.forEach(path.coordinates, (polygon, index) -> + if polygon[0].length > this.max + this.max = polygon[0].length + this.index = index + , trackMaxVertices); + + #TODO: Properly support MultiPolygons + polygon = path.coordinates[trackMaxVertices.index] + #Note: At this time, we only support the outer polygon and ignore the inner 'holes' + array = polygon[0] + + return false if array.length < 4 + else if path.type is "LineString" + return false if path.coordinates.length < 2 + array = path.coordinates + else + return false + + while i < array.length + return false if array[i].length != 2 + i++ + + true + + convertPathPoints: (path) -> + i = 0 + result = new google.maps.MVCArray() + + if angular.isUndefined(path.type) + # TODO: optimize to detect if array contains LatLng and directly pass array to MVCArray constructor + # CONTRIBUTIONS WELCOMED + # TODO: remove while loop it is the same as maping, either array.map or _.map + while i < path.length + latlng + if angular.isDefined(path[i].latitude) and angular.isDefined(path[i].longitude) # latitude/longitude object + latlng = new google.maps.LatLng(path[i].latitude, path[i].longitude) + else if typeof path[i].lat == "function" and typeof path[i].lng == "function" # LatLng object + latlng = path[i] + + result.push latlng + i++ + else + array + if path.type is "Polygon" + #Note: At this time, we only support the outer polygon and ignore the inner 'holes' + array = path.coordinates[0] + else if path.type is "MultiPolygon" + #Note: At this time we will display the polygon with the most vertices + trackMaxVertices = { max: 0, index: 0 } + _.forEach(path.coordinates, (polygon, index) -> + if polygon[0].length > this.max + this.max = polygon[0].length + this.index = index + , trackMaxVertices); + + #TODO: Properly support MultiPolygons + array = path.coordinates[trackMaxVertices.index][0] + else if path.type is "LineString" + array = path.coordinates + + while i < array.length + result.push new google.maps.LatLng(array[i][1], array[i][0]) + i++ + + result + + extendMapBounds:(map, points) -> + bounds = new google.maps.LatLngBounds() + i = 0 + + while i < points.length + bounds.extend points.getAt(i) + i++ + map.fitBounds bounds +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/linked.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/linked.coffee new file mode 100644 index 0000000..78eb06a --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/linked.coffee @@ -0,0 +1,10 @@ +angular.module("google-maps.directives.api.utils") +.factory "Linked", [ "BaseObject", (BaseObject) -> + class Linked extends BaseObject + constructor: (scope, element, attrs, ctrls)-> + @scope = scope + @element = element + @attrs = attrs + @ctrls = ctrls + Linked +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/logger.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/logger.coffee new file mode 100644 index 0000000..47fd449 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/logger.coffee @@ -0,0 +1,23 @@ +angular.module("google-maps.directives.api.utils") +.service "Logger", [ "$log", ($log)-> + logger: $log + doLog: false + info: (msg) -> + if(@doLog) + if @logger? + @logger.info(msg) + else + console.info(msg) + error: (msg) -> + if(@doLog) + if @logger? + @logger.error(msg) + else + console.error(msg) + warn: (msg) -> + if(@doLog) + if @logger? + @logger.warn(msg) + else + console.warn(msg) +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/model-key.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/model-key.coffee new file mode 100644 index 0000000..f925538 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/model-key.coffee @@ -0,0 +1,27 @@ +angular.module("google-maps.directives.api.utils") +.factory "ModelKey", ["BaseObject", (BaseObject) -> + class ModelKey extends BaseObject + constructor: (@scope) -> + super() + @defaultIdKey = "id" + @idKey = undefined + + evalModelHandle: (model, modelKey) -> + if model == undefined + return undefined + if modelKey == 'self' + model + else + model[modelKey] + + modelKeyComparison: (model1, model2) => + scope = if @scope.coords? then @scope else @parentScope + if not scope? then throw "No scope or parentScope set!" + @evalModelHandle(model1, scope.coords).latitude == @evalModelHandle(model2, + scope.coords).latitude and + @evalModelHandle(model1, scope.coords).longitude == @evalModelHandle(model2, + scope.coords).longitude + + setIdKey:(scope) => + @idKey = if scope.idKey? then scope.idKey else @defaultIdKey +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/models-watcher.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/models-watcher.coffee new file mode 100644 index 0000000..21a09b0 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/models-watcher.coffee @@ -0,0 +1,36 @@ +angular.module("google-maps.directives.api.utils") +.factory "ModelsWatcher",[ "Logger", (Logger) -> + #putting a payload together in order to not have to flatten twice, and to not have to flatten again later + figureOutState: (idKey, scope, childObjects, comparison, callBack)-> + adds = [] #models to add or update + mappedScopeModelIds = {} + removals = [] #childModels to remove + _async.each scope.models, (m) -> + if m[idKey]? + mappedScopeModelIds[m[idKey]] = {} + unless childObjects[m[idKey]]? + adds.push m + else + child = childObjects[m[idKey]] + #we're UPDATE in this case + unless comparison m, child.model + adds.push m + removals.push child + else + Logger.error("id missing for model #{m.toString()}, can not use do comparison/insertion") + , () => + _async.each childObjects.values(), (c) -> + unless c? + Logger.error("child undefined in ModelsWatcher.") + return + unless c.model? + Logger.error("child.model undefined in ModelsWatcher.") + return + id = c.model[idKey] + #if we do not have the object we can remove it, this case is when it no longer exists and should be removed + removals.push c unless mappedScopeModelIds[id]? + , () => + callBack + adds: adds + removals: removals +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/prop-map.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/prop-map.coffee new file mode 100644 index 0000000..ac032bc --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/prop-map.coffee @@ -0,0 +1,33 @@ +### + Simple Object Map with a lenght property to make it easy to track length/size +### +angular.module("google-maps.directives.api.utils") +.factory "PropMap", -> + propsToPop = ['get', 'put', 'remove', 'values', 'keys', 'length'] + class PropMap + constructor: () -> + @length = 0 + get: (key)=> + @[key] + #modify map through put or remove to keep track of length , otherwise the state will be incorrect + put: (key, value)=> + unless @[key]? #if we are adding something new increment length + @length++ + @[key] = value + remove: (key)=> + delete @[key] + @length-- + + values: ()=> + all = [] + keys = _.keys @ + _.each keys, (value) => + all.push(@[value]) if _.indexOf(propsToPop, value) == -1 + all + keys: ()=> + keys = _.keys @ + all = [] + _.each keys, (prop)=> + all.push(prop) if _.indexOf(propsToPop, prop) == -1 + all + PropMap \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/utils/property-action.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/utils/property-action.coffee new file mode 100644 index 0000000..186406c --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/utils/property-action.coffee @@ -0,0 +1,11 @@ +angular.module("google-maps.directives.api.utils") +.factory "nggmap-PropertyAction", ["Logger", (Logger) -> + PropertyAction = (setterFn, isFirstSet) -> + @setIfChange = (newVal,oldVal) -> + if not _.isEqual oldVal, newVal or isFirstSet + setterFn newVal + @sic = (oldVal, newVal) => + @setIfChange oldVal, newVal + @ + PropertyAction +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/window.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/window.coffee new file mode 100644 index 0000000..f1a50e6 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/window.coffee @@ -0,0 +1,50 @@ +angular.module("google-maps.directives.api") +.factory "Window", [ "IWindow", "GmapUtil", "WindowChildModel", "$q",(IWindow, GmapUtil, WindowChildModel,$q) -> + class Window extends IWindow + @include GmapUtil + constructor: ($timeout, $compile, $http, $templateCache) -> + super($timeout, $compile, $http, $templateCache) + self = @ + @require = ['^googleMap', '^?marker'] + @template = '' + @$log.info(self) + + link: (scope, element, attrs, ctrls) => + mapScope = ctrls[0].getScope() + mapScope.deferred.promise.then (mapCtrl) => + isIconVisibleOnClick = true + if angular.isDefined attrs.isiconvisibleonclick + isIconVisibleOnClick = scope.isIconVisibleOnClick + markerCtrl = if ctrls.length > 1 and ctrls[1]? then ctrls[1] else undefined + if not markerCtrl + @init scope,element,isIconVisibleOnClick,mapCtrl + return + markerScope = markerCtrl.getScope() + markerScope.deferred.promise.then => + @init scope,element,isIconVisibleOnClick ,mapCtrl, markerScope + + init: (scope,element,isIconVisibleOnClick ,mapCtrl,markerScope) => + defaults = if scope.options? then scope.options else {} + hasScopeCoords = scope? and @validateCoords(scope.coords) + if markerScope? + gMarker = markerScope.gMarker + markerScope.$watch 'coords', (newValue, oldValue) => + if markerScope.gMarker? and !window.markerCtrl + window.markerCtrl = markerScope.gMarker + window.handleClick(true) + return window.hideWindow() unless @validateCoords(newValue) + + if !angular.equals(newValue, oldValue) + window.getLatestPosition @getCoords newValue + , true + + opts = if hasScopeCoords then @createWindowOptions(gMarker, scope, element.html(), defaults) else defaults + + if mapCtrl? #at the very least we need a Map, the marker is optional as we can create Windows without markers + window = new WindowChildModel {}, scope, opts, isIconVisibleOnClick, mapCtrl, + gMarker, element + scope.$on "$destroy", => + window?.destroy() + + @onChildCreation window if @onChildCreation? and window? +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/api/windows.coffee b/bower_components/angular-google-maps/src/coffee/directives/api/windows.coffee new file mode 100644 index 0000000..a6e38c4 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/api/windows.coffee @@ -0,0 +1,22 @@ +angular.module("google-maps.directives.api") +.factory "Windows", ["IWindow", "WindowsParentModel", (IWindow, WindowsParentModel) -> + ### + Windows directive where many windows map to the models property + ### + class Windows extends IWindow + constructor: ($timeout, $compile, $http, $templateCache, $interpolate) -> + super($timeout, $compile, $http, $templateCache) + self = @ + @$interpolate = $interpolate + @require = ['^googleMap', '^?markers'] + @template = '' + @scope.idKey = '=idkey' #id key to bind to that makes a model unique, if it does not exist default to rebuilding all markers + @scope.doRebuildAll = '=dorebuildall' #root level directive attribute not a model level + @scope.models = '=models' #if undefined it will try get a markers models + + @$log.info @ + + link: (scope, element, attrs, ctrls) => + new WindowsParentModel(scope, element, attrs, ctrls, @$timeout, + @$compile, @$http, @$templateCache, @$interpolate) + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/circle.coffee b/bower_components/angular-google-maps/src/coffee/directives/circle.coffee new file mode 100644 index 0000000..3e86593 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/circle.coffee @@ -0,0 +1,142 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@authors +Julian Popescu - https://github.com/jpopesculian +Rick Huizinga - https://plus.google.com/+RickHuizinga +### +angular.module("google-maps") +.directive "circle", ["$log", "$timeout", "GmapUtil", "EventsHelper", ($log, $timeout, GmapUtil, EventsHelper) -> + "use strict" + DEFAULTS = {} + restrict: "ECA" + replace: true + require: "^googleMap" + scope: + center: "=center" + radius: "=radius" + stroke: "=stroke" + fill: "=fill" + clickable: "=" + draggable: "=" + editable: "=" + geodesic: "=" + icons: "=icons" + visible: "=" + events: "=" + + link: (scope, element, attrs, mapCtrl) -> + + # Wrap circle initialization inside a $timeout() call to make sure the map is created already + mapCtrl.getScope().deferred.promise.then (map) => + buildOpts = -> + # Validate required properties + if !GmapUtil.validateCoords(scope.center) + $log.error "circle: no valid center attribute found" + return + opts = angular.extend({}, DEFAULTS, + map: map + center: GmapUtil.getCoords(scope.center) + radius: scope.radius + strokeColor: scope.stroke and scope.stroke.color + strokeOpacity: scope.stroke and scope.stroke.opacity + strokeWeight: scope.stroke and scope.stroke.weight + fillColor: scope.fill and scope.fill.color + fillOpacity: scope.fill and scope.fill.opacity + ) + angular.forEach + clickable: true + draggable: false + editable: false + geodesic: false + visible: true + , (defaultValue, key) -> + if angular.isUndefined(scope[key]) or scope[key] is null + opts[key] = defaultValue + else + opts[key] = scope[key] + + opts + + circle = new google.maps.Circle(buildOpts()) + + scope.$watchCollection 'center', (newVals, oldVals) -> + if newVals isnt oldVals + circle.setOptions buildOpts() + + scope.$watchCollection 'stroke', (newVals, oldVals) -> + if newVals isnt oldVals + circle.setOptions buildOpts() + + scope.$watchCollection 'fill', (newVals, oldVals) -> + if newVals isnt oldVals + circle.setOptions buildOpts() + + scope.$watch 'radius', (newVal, oldVal) -> + if newVal isnt oldVal + circle.setOptions buildOpts() + + scope.$watch 'clickable', (newVal, oldVal) -> + if newVal isnt oldVal + circle.setOptions buildOpts() + + scope.$watch 'editable', (newVal, oldVal) -> + if newVal isnt oldVal + circle.setOptions buildOpts() + + scope.$watch 'draggable', (newVal, oldVal) -> + if newVal isnt oldVal + circle.setOptions buildOpts() + + scope.$watch 'visible', (newVal, oldVal) -> + if newVal isnt oldVal + circle.setOptions buildOpts() + + scope.$watch 'geodesic', (newVal, oldVal) -> + if newVal isnt oldVal + circle.setOptions buildOpts() + + EventsHelper.setEvents circle, scope, scope + + google.maps.event.addListener circle, 'radius_changed', -> + scope.radius = circle.getRadius() + $timeout -> + scope.$apply() + + google.maps.event.addListener circle, 'center_changed', -> + if angular.isDefined(scope.center.type) + scope.center.coordinates[1] = circle.getCenter().lat() + scope.center.coordinates[0] = circle.getCenter().lng() + else + scope.center.latitude = circle.getCenter().lat() + scope.center.longitude = circle.getCenter().lng() + + $timeout -> + scope.$apply() + + # Remove circle on scope $destroy + scope.$on "$destroy", -> + circle.setMap null + +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/controllers/polyline-display.coffee b/bower_components/angular-google-maps/src/coffee/directives/controllers/polyline-display.coffee new file mode 100644 index 0000000..db5c066 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/controllers/polyline-display.coffee @@ -0,0 +1,5 @@ +angular.module("google-maps") +.controller "PolylineDisplayController", ["$scope", ($scope) -> + $scope.toggleStrokeColor = -> + $scope.stroke.color = (if ($scope.stroke.color is "#6060FB") then "red" else "#6060FB") +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/label.coffee b/bower_components/angular-google-maps/src/coffee/directives/label.coffee new file mode 100644 index 0000000..92272fb --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/label.coffee @@ -0,0 +1,100 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors Bruno Queiroz, creativelikeadog@gmail.com +### + +### +Marker label directive + +This directive is used to create a marker label on an existing map. + +{attribute content required} content of the label +{attribute anchor required} string that contains the x and y point position of the label +{attribute class optional} class to DOM object +{attribute style optional} style for the label +### +### +Basic Directive api for a label. Basic in the sense that this directive contains 1:1 on scope and model. +Thus there will be one html element per marker within the directive. +### +angular.module("google-maps") +.directive "markerLabel", ["$timeout", "ILabel", "MarkerLabelChildModel", "GmapUtil", "nggmap-PropertyAction" + ($timeout, ILabel, MarkerLabelChildModel, GmapUtil, PropertyAction) -> + class Label extends ILabel + constructor: ($timeout) -> + super($timeout) + self = @ + @require = '^marker' + @template = '' + @$log.info(@) + + link: (scope, element, attrs, ctrl) => + markerScope = ctrl.getScope() + if markerScope + markerScope.deferred.promise.then => + @init markerScope, scope + + + init: (markerScope, scope)=> + label = null + createLabel = -> + unless label + label = new MarkerLabelChildModel markerScope.gMarker, scope, markerScope.map + + isFirstSet = true + markerScope.$watch 'gMarker', (newVal, oldVal) -> + if markerScope.gMarker? + contentAction = new PropertyAction (newVal) -> + createLabel() + if scope.labelContent + label?.setOption 'labelContent', scope.labelContent + , isFirstSet + anchorAction = new PropertyAction (newVal) -> + createLabel() + label?.setOption 'labelAnchor', scope.labelAnchor + , isFirstSet + classAction = new PropertyAction (newVal) -> + createLabel() + label?.setOption 'labelClass', scope.labelClass + , isFirstSet + styleAction = new PropertyAction (newVal) -> + createLabel() + label?.setOption 'labelStyle', scope.labelStyle + , isFirstSet + + scope.$watch 'labelContent', contentAction.sic + scope.$watch 'labelAnchor', anchorAction.sic + scope.$watch 'labelClass', classAction.sic + scope.$watch 'labelStyle', styleAction.sic + isFirstSet = false + + scope.$on '$destroy', -> + label?.destroy() + + new Label($timeout) +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/layer.coffee b/bower_components/angular-google-maps/src/coffee/directives/layer.coffee new file mode 100644 index 0000000..12431e7 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/layer.coffee @@ -0,0 +1,67 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors: +- Nicolas Laplante https://plus.google.com/108189012221374960701 +- Nicholas McCready - https://twitter.com/nmccready +### + +### +Map Layer directive + +This directive is used to create any type of Layer from the google maps sdk. +This directive creates a new scope. + +{attribute show optional} true (default) shows the trafficlayer otherwise it is hidden +### +angular.module("google-maps") +.directive "layer", ["$timeout", "Logger", "LayerParentModel", + ($timeout, Logger, LayerParentModel) -> + class Layer + constructor: -> + @$log = Logger + @restrict = "ECMA" + @require = "^googleMap" + @priority = -1 + @transclude = true + @template = '' + @replace = true + @scope = + show: "=show" + type: "=type" + namespace: "=namespace" + options: '=options' + onCreated: '&oncreated' + + link: (scope, element, attrs, mapCtrl) => + mapCtrl.getScope().deferred.promise.then (map) => + if scope.onCreated? + new LayerParentModel(scope, element, attrs, map, scope.onCreated) + else + new LayerParentModel(scope, element, attrs, map) + new Layer() +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/map.coffee b/bower_components/angular-google-maps/src/coffee/directives/map.coffee new file mode 100644 index 0000000..522921f --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/map.coffee @@ -0,0 +1,40 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +Nick Baugh - https://github.com/niftylettuce +### + +#jshint indent:4 + +#globals directives,google +angular.module("google-maps") +.directive "googleMap", ["Map", (Map) -> + new Map() +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/marker.coffee b/bower_components/angular-google-maps/src/coffee/directives/marker.coffee new file mode 100644 index 0000000..33ef1ea --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/marker.coffee @@ -0,0 +1,46 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +### + +### +Map marker directive + +This directive is used to create a marker on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute icon optional} string url to image used for marker icon +{attribute animate optional} if set to false, the marker won't be animated (on by default) +### +angular.module("google-maps") +.directive "marker", ["$timeout", "Marker", ($timeout, Marker) -> + new Marker($timeout) +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/markers.coffee b/bower_components/angular-google-maps/src/coffee/directives/markers.coffee new file mode 100644 index 0000000..89973a9 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/markers.coffee @@ -0,0 +1,46 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +### + +### +Map marker directive + +This directive is used to create a marker on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute icon optional} string url to image used for marker icon +{attribute animate optional} if set to false, the marker won't be animated (on by default) +### +angular.module("google-maps") +.directive "markers", ["$timeout", "Markers", ($timeout, Markers) -> + new Markers($timeout) + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/polygon.coffee b/bower_components/angular-google-maps/src/coffee/directives/polygon.coffee new file mode 100644 index 0000000..c35d5a6 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/polygon.coffee @@ -0,0 +1,168 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +Rick Huizinga - https://plus.google.com/+RickHuizinga +### +angular.module("google-maps") +.directive "polygon", ["$log", "$timeout", "array-sync", "GmapUtil", ($log, $timeout, arraySync, GmapUtil) -> + # + # * Utility functions + # + + ### + Check if a value is true + ### + isTrue = (val) -> + angular.isDefined(val) and val isnt null and val is true or val is "1" or val is "y" or val is "true" + "use strict" + DEFAULTS = {} + restrict: "ECA" + replace: true + require: "^googleMap" + scope: + path: "=path" + stroke: "=stroke" + clickable: "=" + draggable: "=" + editable: "=" + geodesic: "=" + fill: "=" + icons: "=icons" + visible: "=" + static: "=" + events: "=" + zIndex: "=zindex" + fit: "=" + + link: (scope, element, attrs, mapCtrl) -> + + # Validate required properties + if angular.isUndefined(scope.path) or scope.path is null or not GmapUtil.validatePath(scope.path) + $log.error "polygon: no valid path attribute found" + return + + # Wrap polygon initialization inside a $timeout() call to make sure the map is created already + mapCtrl.getScope().deferred.promise.then (map) => + buildOpts = (pathPoints) -> + opts = angular.extend({}, DEFAULTS, + map: map + path: pathPoints + strokeColor: scope.stroke and scope.stroke.color + strokeOpacity: scope.stroke and scope.stroke.opacity + strokeWeight: scope.stroke and scope.stroke.weight + fillColor: scope.fill and scope.fill.color + fillOpacity: scope.fill and scope.fill.opacity + ) + angular.forEach + clickable: true + draggable: false + editable: false + geodesic: false + visible: true + static: false + fit: false + zIndex: 0 + , (defaultValue, key) -> + if angular.isUndefined(scope[key]) or scope[key] is null + opts[key] = defaultValue + else + opts[key] = scope[key] + + opts.editable = false if opts.static + opts + + pathPoints = GmapUtil.convertPathPoints(scope.path) + polygon = new google.maps.Polygon(buildOpts(pathPoints)) + # The fit attribute is undocumented as it currently does not + # properly work when changes to the path are made. + GmapUtil.extendMapBounds map, pathPoints if scope.fit + + if !scope.static and angular.isDefined(scope.editable) + scope.$watch "editable", (newValue, oldValue) -> + polygon.setEditable newValue if newValue != oldValue + + if angular.isDefined(scope.draggable) + scope.$watch "draggable", (newValue, oldValue) -> + polygon.setDraggable newValue if newValue != oldValue + + if angular.isDefined(scope.visible) + scope.$watch "visible", (newValue, oldValue) -> + polygon.setVisible newValue if newValue != oldValue + + if angular.isDefined(scope.geodesic) + scope.$watch "geodesic", (newValue, oldValue) -> + polygon.setOptions buildOpts(polygon.getPath()) if newValue != oldValue + + if angular.isDefined(scope.stroke) and angular.isDefined(scope.stroke.opacity) + scope.$watch "stroke.opacity", (newValue, oldValue) -> + polygon.setOptions buildOpts(polygon.getPath()) + + if angular.isDefined(scope.stroke) and angular.isDefined(scope.stroke.weight) + scope.$watch "stroke.weight", (newValue, oldValue) -> + polygon.setOptions buildOpts(polygon.getPath()) if newValue != oldValue + + if angular.isDefined(scope.stroke) and angular.isDefined(scope.stroke.color) + scope.$watch "stroke.color", (newValue, oldValue) -> + polygon.setOptions buildOpts(polygon.getPath()) if newValue != oldValue + + if angular.isDefined(scope.fill) and angular.isDefined(scope.fill.color) + scope.$watch "fill.color", (newValue, oldValue) -> + polygon.setOptions buildOpts(polygon.getPath()) if newValue != oldValue + + if angular.isDefined(scope.fill) and angular.isDefined(scope.fill.opacity) + scope.$watch "fill.opacity", (newValue, oldValue) -> + polygon.setOptions buildOpts(polygon.getPath()) if newValue != oldValue + + if angular.isDefined(scope.zIndex) + scope.$watch "zIndex", (newValue, oldValue) -> + polygon.setOptions buildOpts(polygon.getPath()) if newValue != oldValue + + if angular.isDefined(scope.events) and scope.events isnt null and angular.isObject(scope.events) + getEventHandler = (eventName) -> + -> + scope.events[eventName].apply scope, [polygon, eventName, arguments] + + #TODO: Need to keep track of listeners and call removeListener on each + for eventName of scope.events + polygon.addListener eventName, getEventHandler(eventName) if scope.events.hasOwnProperty(eventName) and angular.isFunction(scope.events[eventName]) + + # To properly support the undocumented fit attribute, + # array-sync needs to be upgraded to support an optional pathChanged callback + # function that is called with the path points whenever they have been changed. + arraySyncer = arraySync polygon.getPath(), scope, "path", (pathPoints) -> + GmapUtil.extendMapBounds map, pathPoints if scope.fit + + # Remove polygon on scope $destroy + scope.$on "$destroy", -> + polygon.setMap null + if arraySyncer + arraySyncer() + arraySyncer = null +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/polyline.coffee b/bower_components/angular-google-maps/src/coffee/directives/polyline.coffee new file mode 100644 index 0000000..2774e0d --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/polyline.coffee @@ -0,0 +1,32 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +### +angular.module("google-maps").directive "polyline", [ "Polyline",(Polyline) -> new Polyline() ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/polylines.coffee b/bower_components/angular-google-maps/src/coffee/directives/polylines.coffee new file mode 100644 index 0000000..ae5c1ac --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/polylines.coffee @@ -0,0 +1,32 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +### +angular.module("google-maps").directive "polylines", [ "Polylines",(Polylines) -> new Polylines() ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/directives/rectangle.coffee b/bower_components/angular-google-maps/src/coffee/directives/rectangle.coffee new file mode 100644 index 0000000..9d03b38 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/rectangle.coffee @@ -0,0 +1,194 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +Chentsu Lin - https://github.com/ChenTsuLin +### +angular.module("google-maps").directive "rectangle", ["$log", "$timeout", ($log, $timeout) -> + validateBoundPoints = (bounds) -> + return false if angular.isUndefined(bounds.sw.latitude) or angular.isUndefined(bounds.sw.longitude) or angular.isUndefined(bounds.ne.latitude) or angular.isUndefined(bounds.ne.longitude) + + true + convertBoundPoints = (bounds) -> + result = new google.maps.LatLngBounds(new google.maps.LatLng(bounds.sw.latitude, bounds.sw.longitude), new google.maps.LatLng(bounds.ne.latitude, bounds.ne.longitude)) + + result + fitMapBounds = (map, bounds) -> + map.fitBounds bounds + + # + # * Utility functions + # + + ### + Check if a value is true + ### + isTrue = (val) -> + angular.isDefined(val) and val isnt null and val is true or val is "1" or val is "y" or val is "true" + "use strict" + DEFAULTS = {} + restrict: "ECA" + require: "^googleMap" + replace: true + scope: + bounds: "=" + stroke: "=" + clickable: "=" + draggable: "=" + editable: "=" + fill: "=" + visible: "=" + + link: (scope, element, attrs, mapCtrl) -> + + # Validate required properties + if angular.isUndefined(scope.bounds) or scope.bounds is null or angular.isUndefined(scope.bounds.sw) or scope.bounds.sw is null or angular.isUndefined(scope.bounds.ne) or scope.bounds.ne is null or not validateBoundPoints(scope.bounds) + $log.error "rectangle: no valid bound attribute found" + return + + # Wrap rectangle initialization inside a $timeout() call to make sure the map is created already + mapCtrl.getScope().deferred.promise.then (map) => + buildOpts = (bounds) -> + opts = angular.extend({}, DEFAULTS, + map: map + bounds: bounds + strokeColor: scope.stroke and scope.stroke.color + strokeOpacity: scope.stroke and scope.stroke.opacity + strokeWeight: scope.stroke and scope.stroke.weight + fillColor: scope.fill and scope.fill.color + fillOpacity: scope.fill and scope.fill.opacity + ) + angular.forEach + clickable: true + draggable: false + editable: false + visible: true + , (defaultValue, key) -> + if angular.isUndefined(scope[key]) or scope[key] is null + opts[key] = defaultValue + else + opts[key] = scope[key] + + opts + + rectangle = new google.maps.Rectangle(buildOpts(convertBoundPoints(scope.bounds))) + fitMapBounds map, bounds if isTrue(attrs.fit) + + dragging = false + google.maps.event.addListener rectangle, "mousedown", -> + google.maps.event.addListener rectangle, "mousemove", -> + dragging = true + _.defer -> + scope.$apply (s) -> + s.dragging = dragging if s.dragging? + + google.maps.event.addListener rectangle, "mouseup", -> + google.maps.event.clearListeners(rectangle, 'mousemove'); + google.maps.event.clearListeners(rectangle, 'mouseup'); + dragging = false + _.defer -> + scope.$apply (s) -> + s.dragging = dragging if s.dragging? + return + + settingBoundsFromScope = false + google.maps.event.addListener rectangle , "bounds_changed", -> + b = rectangle.getBounds() + ne = b.getNorthEast() + sw = b.getSouthWest() + return if settingBoundsFromScope #if the scope notified this change then there is no reason to update scope otherwise infinite loop + _.defer -> + scope.$apply (s) -> + unless rectangle.dragging + if s.bounds isnt null and s.bounds isnt `undefined` and s.bounds isnt undefined + s.bounds.ne = + latitude: ne.lat() + longitude: ne.lng() + + s.bounds.sw = + latitude: sw.lat() + longitude: sw.lng() + return + + # Update map when center coordinates change + scope.$watch "bounds", ((newValue, oldValue) -> + return if _.isEqual(newValue, oldValue) + settingBoundsFromScope = true + unless dragging + if !newValue.sw.latitude? or !newValue.sw.longitude? or !newValue.ne.latitude? or !newValue.ne.longitude? + $log.error("Invalid bounds for newValue: #{JSON.stringify newValue}") + bounds = new google.maps.LatLngBounds(new google.maps.LatLng(newValue.sw.latitude, newValue.sw.longitude), new google.maps.LatLng(newValue.ne.latitude, newValue.ne.longitude)) + rectangle.setBounds bounds + + settingBoundsFromScope = false + ), true + + + if angular.isDefined(scope.editable) + scope.$watch "editable", (newValue, oldValue) -> + rectangle.setEditable newValue + + if angular.isDefined(scope.draggable) + scope.$watch "draggable", (newValue, oldValue) -> + rectangle.setDraggable newValue + + if angular.isDefined(scope.visible) + scope.$watch "visible", (newValue, oldValue) -> + rectangle.setVisible newValue + + if angular.isDefined(scope.stroke) + if angular.isDefined(scope.stroke.color) + scope.$watch "stroke.color", (newValue, oldValue) -> + rectangle.setOptions buildOpts(rectangle.getBounds()) + + if angular.isDefined(scope.stroke.weight) + scope.$watch "stroke.weight", (newValue, oldValue) -> + rectangle.setOptions buildOpts(rectangle.getBounds()) + + if angular.isDefined(scope.stroke.opacity) + scope.$watch "stroke.opacity", (newValue, oldValue) -> + rectangle.setOptions buildOpts(rectangle.getBounds()) + + if angular.isDefined(scope.fill) + if angular.isDefined(scope.fill.color) + scope.$watch "fill.color", (newValue, oldValue) -> + rectangle.setOptions buildOpts(rectangle.getBounds()) + + if angular.isDefined(scope.fill.opacity) + scope.$watch "fill.opacity", (newValue, oldValue) -> + rectangle.setOptions buildOpts(rectangle.getBounds()) + + + + # Remove rectangle on scope $destroy + scope.$on "$destroy", -> + rectangle.setMap null + + +] diff --git a/bower_components/angular-google-maps/src/coffee/directives/window.coffee b/bower_components/angular-google-maps/src/coffee/directives/window.coffee new file mode 100644 index 0000000..bbfc6df --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/window.coffee @@ -0,0 +1,48 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +### + +### +Map info window directive + +This directive is used to create an info window on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute show optional} map will show when this expression returns true +### +angular.module("google-maps") +.directive("window", + ["$timeout", "$compile", "$http", "$templateCache", "Window", + ($timeout, $compile, $http, $templateCache, Window) -> + new Window($timeout, $compile, $http, $templateCache) + ]) + diff --git a/bower_components/angular-google-maps/src/coffee/directives/windows.coffee b/bower_components/angular-google-maps/src/coffee/directives/windows.coffee new file mode 100644 index 0000000..4a0f4cc --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/directives/windows.coffee @@ -0,0 +1,46 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +### + +### +Map info window directive + +This directive is used to create an info window on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute show optional} map will show when this expression returns true +### +angular.module("google-maps") +.directive "windows", ["$timeout", "$compile", "$http", "$templateCache", "$interpolate", "Windows", + ($timeout, $compile, $http, $templateCache, $interpolate, Windows) -> + new Windows($timeout, $compile, $http, $templateCache, $interpolate) + ] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/extensions/google.maps.coffee b/bower_components/angular-google-maps/src/coffee/extensions/google.maps.coffee new file mode 100644 index 0000000..e74a1e3 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/extensions/google.maps.coffee @@ -0,0 +1,91 @@ +#boot strap angular and extend google maps +angular.module("google-maps.extensions") +.service('ExtendGWin', -> + init: _.once -> + return unless google or google?.maps or google.maps.InfoWindow? + #Taken from : http://stackoverflow.com/questions/12410062/check-if-infowindow-is-opened-google-maps-v3 + # + # + # modify the prototype for google.maps.Infowindow so that it is capable of tracking + # the opened state of the window. we track the state via boolean which is set when + # open() or close() are called. in addition to these, the closeclick event is + # monitored so that the value of _openedState can be set when the close button is + # clicked (see code at bottom of this file). + # + google.maps.InfoWindow::_open = google.maps.InfoWindow::open + google.maps.InfoWindow::_close = google.maps.InfoWindow::close + google.maps.InfoWindow::_isOpen = false + + google.maps.InfoWindow::open = (map, anchor) -> + @_isOpen = true + @_open map, anchor + return + + google.maps.InfoWindow::close = -> + @_isOpen = false + @_close() + return + + google.maps.InfoWindow::isOpen = (val = undefined ) -> + unless val? + return @_isOpen + else + @_isOpen = val + + ### + Do the same for InfoBox + TODO: Clean this up so the logic is defined once, wait until develop becomes master as this will be easier + ### + return unless window.InfoBox + + window.InfoBox::_open = window.InfoBox::open + window.InfoBox::_close = window.InfoBox::close + window.InfoBox::_isOpen = false + + window.InfoBox::open = (map, anchor) -> + @_isOpen = true + @_open map, anchor + return + + window.InfoBox::close = -> + @_isOpen = false + @_close() + return + + window.InfoBox::isOpen = (val = undefined ) -> + unless val? + return @_isOpen + else + @_isOpen = val + + MarkerLabel_::setContent = -> + content = @marker_.get("labelContent") + return if !content or _.isEqual @oldContent, content + if typeof content?.nodeType is "undefined" + @labelDiv_.innerHTML = content + @eventDiv_.innerHTML = @labelDiv_.innerHTML + @oldContent = content + else + @labelDiv_.innerHTML = "" # Remove current content + @labelDiv_.appendChild content + content = content.cloneNode(true) + @eventDiv_.appendChild content + @oldContent = content + return + + ### + Removes the DIV for the label from the DOM. It also removes all event handlers. + This method is called automatically when the marker's setMap(null) + method is called. + @private + ### + MarkerLabel_::onRemove = -> + @labelDiv_.parentNode.removeChild @labelDiv_ if @labelDiv_.parentNode? + @eventDiv_.parentNode.removeChild @eventDiv_ if @eventDiv_.parentNode? + # Remove event listeners: + return unless @listeners_ + return unless @listeners_.length + @listeners_.forEach (l) -> + google.maps.event.removeListener l + return +) \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/extensions/lodash.coffee b/bower_components/angular-google-maps/src/coffee/extensions/lodash.coffee new file mode 100644 index 0000000..1c59c51 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/extensions/lodash.coffee @@ -0,0 +1,60 @@ +### + Author Nick McCready + Intersection of Objects if the arrays have something in common each intersecting object will be returned + in an new array. +### +_.intersectionObjects = (array1, array2, comparison = undefined) -> + res = _.map array1, (obj1) => + _.find array2, (obj2) => + if comparison? + comparison(obj1, obj2) + else + _.isEqual(obj1, obj2) + _.filter res, (o) -> + o? + +# Determine if the array or object contains a given value (using `===`). +#Aliased as `include`. +_.containsObject = _.includeObject = (obj, target, comparison = undefined) -> + if (obj == null) + return false + # if (nativeIndexOf && obj.indexOf == nativeIndexOf) + # return obj.indexOf(target) != -1 + _.any obj, (value) => + if comparison? + comparison value, target + else + _.isEqual value, target + + +_.differenceObjects = (array1, array2, comparison = undefined) -> + _.filter array1, (value) -> + !_.containsObject array2, value + +#alias to differenceObjects +_.withoutObjects = (array, array2) -> + _.differenceObjects(array, array2) + +_.indexOfObject = (array, item, comparison, isSorted) -> + return -1 unless array? + i = 0 + length = array.length + if isSorted + if typeof isSorted is "number" + i = ((if isSorted < 0 then Math.max(0, length + isSorted) else isSorted)) + else + i = _.sortedIndex(array, item) + return (if array[i] is item then i else -1) + while i < length + if comparison? + return i if comparison array[i], item + else + return i if _.isEqual array[i], item + i++ + -1 + +#to easily inherit multiple objects +_.extends=(arrayOfObjectsToCombine)-> + _.reduce arrayOfObjectsToCombine,(combined,toAdd)-> + _.extend(combined,toAdd) + ,{}#starting point empty object \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/module.coffee b/bower_components/angular-google-maps/src/coffee/module.coffee new file mode 100644 index 0000000..7039825 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/module.coffee @@ -0,0 +1,55 @@ +### +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +### +#define application wide modules +angular.module("google-maps.extensions", []) +angular.module("google-maps.directives.api.utils", ['google-maps.extensions']) +angular.module("google-maps.directives.api.managers", []) +angular.module("google-maps.directives.api.models.child", [ + "google-maps.directives.api.utils"]) +angular.module("google-maps.directives.api.models.parent", [ + "google-maps.directives.api.managers", + "google-maps.directives.api.models.child" +]) +angular.module("google-maps.directives.api", [ "google-maps.directives.api.models.parent"]) +angular.module("google-maps", [ "google-maps.directives.api"]) +.factory("debounce", ["$timeout", ($timeout) -> + (fn) -> # debounce fn + nthCall = 0 + -> # intercepting fn + that = this + argz = arguments + nthCall++ + later = ((version) -> + -> + fn.apply that, argz if version is nthCall)(nthCall) + $timeout later, 0, true +]) \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/utils/LatLngArraySync.coffee b/bower_components/angular-google-maps/src/coffee/utils/LatLngArraySync.coffee new file mode 100644 index 0000000..2efddb1 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/utils/LatLngArraySync.coffee @@ -0,0 +1,146 @@ +angular.module("google-maps").factory "array-sync", ["add-events", (mapEvents) -> + # pathChangedFn is an optional callback that is called whenever a change to the path + # is detected. The first parameter contains the internal array of Google LatLng objects. + (mapArray, scope, pathEval, pathChangedFn) -> + isSetFromScope = false + scopePath = scope.$eval(pathEval) + if !scope.static + legacyHandlers = + #listeners / handles to changes of the points from the map direction to update back to our scope (two way) + set_at: (index) -> + return if isSetFromScope #important to avoid cyclic forever change loop watch to map event change and back + value = mapArray.getAt(index) + return unless value + if not value.lng or not value.lat # LatLng object + scopePath[index] = value + else + scopePath[index].latitude = value.lat() + scopePath[index].longitude = value.lng() + + insert_at: (index) -> + return if isSetFromScope #important to avoid cyclic forever change loop watch to map event change and back + value = mapArray.getAt(index) + return unless value + #check to make sure we are not inserting something that is already there + if not value.lng or not value.lat # LatLng object + scopePath.splice index, 0, value + else + scopePath.splice index, 0, + latitude: value.lat() + longitude: value.lng() + + remove_at: (index) -> + return if isSetFromScope #important to avoid cyclic forever change loop watch to map event change and back + scopePath.splice index, 1 + + geojsonArray + if scopePath.type == "Polygon" + #Note: we only support display of the outer Polygon ring, not internal holes + geojsonArray = scopePath.coordinates[0] + else if scopePath.type == "LineString" + geojsonArray = scopePath.coordinates + + geojsonHandlers = + set_at: (index) -> + return if isSetFromScope #important to avoid cyclic forever change loop watch to map event change and back + value = mapArray.getAt(index) + return unless value + return if not value.lng or not value.lat + geojsonArray[index][1] = value.lat() + geojsonArray[index][0] = value.lng() + + insert_at: (index) -> + return if isSetFromScope #important to avoid cyclic forever change loop watch to map event change and back + value = mapArray.getAt(index) + return unless value + return if not value.lng or not value.lat + geojsonArray.splice index, 0, [ value.lng(), value.lat() ] + + remove_at: (index) -> + return if isSetFromScope #important to avoid cyclic forever change loop watch to map event change and back + geojsonArray.splice index, 1 + + mapArrayListener = mapEvents mapArray, + if angular.isUndefined scopePath.type then legacyHandlers else geojsonHandlers + + legacyWatcher = (newPath) -> + isSetFromScope = true + oldArray = mapArray + if newPath + i = 0 + oldLength = oldArray.getLength() + newLength = newPath.length + l = Math.min(oldLength, newLength) + newValue = undefined + #update existing points if different + while i < l + oldValue = oldArray.getAt(i) + newValue = newPath[i] + if typeof newValue.equals == "function" #LatLng object + if not newValue.equals(oldValue) + oldArray.setAt i, newValue + else # latitude/longitude object + if (oldValue.lat() isnt newValue.latitude) or (oldValue.lng() isnt newValue.longitude) + oldArray.setAt i, new google.maps.LatLng(newValue.latitude, newValue.longitude) + + i++ + #add new points + while i < newLength + newValue = newPath[i] + if typeof newValue.lat == "function" and typeof newValue.lng == "function" + oldArray.push newValue + else + oldArray.push new google.maps.LatLng(newValue.latitude, newValue.longitude) + + i++ + #remove old no longer there + while i < oldLength + oldArray.pop() + i++ + isSetFromScope = false + + geojsonWatcher = (newPath) -> + isSetFromScope = true + oldArray = mapArray + if newPath + array + if scopePath.type == "Polygon" + array = newPath.coordinates[0] + else if scopePath.type == "LineString" + array = newPath.coordinates + + i = 0 + oldLength = oldArray.getLength() + newLength = array.length + l = Math.min(oldLength, newLength) + newValue = undefined + while i < l + oldValue = oldArray.getAt(i) + newValue = array[i] + oldArray.setAt i, new google.maps.LatLng(newValue[1], + newValue[0]) if (oldValue.lat() isnt newValue[1]) or (oldValue.lng() isnt newValue[0]) + i++ + while i < newLength + newValue = array[i] + oldArray.push new google.maps.LatLng(newValue[1], newValue[0]) + i++ + while i < oldLength + oldArray.pop() + i++ + isSetFromScope = false + + watchListener + if !scope.static + if angular.isUndefined(scopePath.type) + watchListener = scope.$watchCollection pathEval, legacyWatcher + else + watchListener = scope.$watch pathEval, geojsonWatcher, true + + -> + if mapArrayListener + mapArrayListener() + mapArrayListener = null + if watchListener + watchListener() # call the watch deregistration function + watchListener = null +] \ No newline at end of file diff --git a/bower_components/angular-google-maps/src/coffee/utils/MapEvents.coffee b/bower_components/angular-google-maps/src/coffee/utils/MapEvents.coffee new file mode 100644 index 0000000..d6636b7 --- /dev/null +++ b/bower_components/angular-google-maps/src/coffee/utils/MapEvents.coffee @@ -0,0 +1,23 @@ +angular.module("google-maps") +.factory "add-events", ["$timeout", ($timeout) -> + addEvent = (target, eventName, handler) -> + google.maps.event.addListener target, eventName, -> + handler.apply this, arguments + $timeout (-> + ), true + + addEvents = (target, eventName, handler) -> + return addEvent(target, eventName, handler) if handler + remove = [] + angular.forEach eventName, (_handler, key) -> + + #console.log('adding listener: ' + key + ": " + _handler.toString() + " to : " + target); + remove.push addEvent(target, key, _handler) + + -> + angular.forEach remove, (listener) -> + google.maps.event.removeListener listener + + remove = null + addEvents +] diff --git a/bower_components/angular-loading-bar/CHANGELOG.md b/bower_components/angular-loading-bar/CHANGELOG.md new file mode 100644 index 0000000..a60f6b9 --- /dev/null +++ b/bower_components/angular-loading-bar/CHANGELOG.md @@ -0,0 +1,44 @@ +Changelog +========== + +## 0.5.2: +Fixes for Angular 1.3 breaking changes: +- Circular dependencies: ([#98](https://github.com/chieffancypants/angular-loading-bar/issues/98)), ([#101](https://github.com/chieffancypants/angular-loading-bar/pull/101)) +- $animate no longer accepts callbacks: ([#102](https://github.com/chieffancypants/angular-loading-bar/pull/102)) + +## 0.5.1 +- Reworked cache logic to allow cache:true ([#96](https://github.com/chieffancypants/angular-loading-bar/pull/96)) + +## 0.5.0 +- Added spinner template configuration ([#82](https://github.com/chieffancypants/angular-loading-bar/pull/82)) +- $timeout was not canceled properly ([#79](https://github.com/chieffancypants/angular-loading-bar/pull/79)) + +## 0.4.3 +- update z-index to work with other css frameworks ([#69](https://github.com/chieffancypants/angular-loading-bar/pull/69)) +- ignoreLoadingBar not ignored when calculating percentage complete ([#70](https://github.com/chieffancypants/angular-loading-bar/pull/70)) + +## 0.4.2 +- Split loading bar into different modules so they can be included separately ([#46](https://github.com/chieffancypants/angular-loading-bar/issues/46)) + +## 0.4.1 +- Fix for route views defined on body where loading bar is also attached ([#56](https://github.com/chieffancypants/angular-loading-bar/issues/56)) + +## 0.4.0 +- Initial load percentage is now configurable ([#47](https://github.com/chieffancypants/angular-loading-bar/issues/47)) +- Peg graphic reworked so the loadingbar does not require CSS changes when not at the very top of the page ([#42](https://github.com/chieffancypants/angular-loading-bar/issues/42), [#45](https://github.com/chieffancypants/angular-loading-bar/issues/45), [#10](https://github.com/chieffancypants/angular-loading-bar/issues/10)) +- z-index of spinner increased to work with Bootstrap 3 z-indexes ([#53](https://github.com/chieffancypants/angular-loading-bar/issues/53)) + +## 0.3.0 +- Loading bar only appears on XHR requests with high latency ([#27](https://github.com/chieffancypants/angular-loading-bar/issues/27)) + +## 0.2.0 +- Progression bar not calculated correctly for consecutive calls within the 500ms delay ([#29](https://github.com/chieffancypants/angular-loading-bar/issues/29), [#32](https://github.com/chieffancypants/angular-loading-bar/issues/32)) +- Event broadcasts when loading (#31) + +## 0.1.1 +- Alias chieffancypants.loadingbar to angular-loading-bar (#25, #19) + +## 0.1.0 +- Fixed issues with Angular 1.2-rc3+ +- Ability to ignore particular XHR requests (#21) +- Broadcasting of events (#18) diff --git a/bower_components/angular-loading-bar/Gruntfile-29c016da8d.js b/bower_components/angular-loading-bar/Gruntfile-29c016da8d.js new file mode 100644 index 0000000..d412686 --- /dev/null +++ b/bower_components/angular-loading-bar/Gruntfile-29c016da8d.js @@ -0,0 +1,94 @@ +/*global module:false*/ +module.exports = function(grunt) { + + grunt.initConfig({ + + // Metadata. + pkg: grunt.file.readJSON('package.json'), + banner: '/*! \n * <%= pkg.title || pkg.name %> v<%= pkg.version %>\n' + + ' * <%= pkg.homepage %>\n' + + ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' + + ' * License: <%= pkg.license %>\n' + + ' */\n', + + // Task configuration. + uglify: { + options: { + banner: '<%= banner %>', + report: 'gzip' + }, + build: { + src: 'src/loading-bar.js', + dest: 'build/loading-bar.min.js' + } + }, + + cssmin: { + options: { + banner: '<%= banner %>', + report: 'gzip' + }, + minify: { + src: 'src/loading-bar.css', + dest: 'build/loading-bar.min.css' + } + }, + + karma: { + unit: { + configFile: 'test/karma-angular-1.2.conf.js', + singleRun: true, + coverageReporter: { + type: 'text', + dir: 'coverage/' + } + }, + unit13: { + configFile: 'test/karma-angular-1.3.conf.js', + singleRun: true, + coverageReporter: { + type: 'text', + dir: 'coverage/' + } + }, + watch: { + configFile: 'test/karma-angular-1.2.conf.js', + singleRun: false, + reporters: ['progress'] // Don't display coverage + } + }, + + jshint: { + jshintrc: '.jshintrc', + gruntfile: { + src: 'Gruntfile.js' + }, + src: { + src: ['src/*.js'] + } + }, + + concat: { + build: { + options: { + banner: '<%= banner %>' + }, + files: { + 'build/loading-bar.css': 'src/loading-bar.css', + 'build/loading-bar.js': 'src/loading-bar.js', + } + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-karma'); + + grunt.registerTask('default', ['jshint', 'karma:unit', 'karma:unit13', 'uglify', 'cssmin', 'concat:build']); + grunt.registerTask('test', ['karma:watch']); + grunt.registerTask('build', ['default']); + +}; diff --git a/bower_components/angular-loading-bar/Gruntfile.js b/bower_components/angular-loading-bar/Gruntfile.js new file mode 100644 index 0000000..d412686 --- /dev/null +++ b/bower_components/angular-loading-bar/Gruntfile.js @@ -0,0 +1,94 @@ +/*global module:false*/ +module.exports = function(grunt) { + + grunt.initConfig({ + + // Metadata. + pkg: grunt.file.readJSON('package.json'), + banner: '/*! \n * <%= pkg.title || pkg.name %> v<%= pkg.version %>\n' + + ' * <%= pkg.homepage %>\n' + + ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' + + ' * License: <%= pkg.license %>\n' + + ' */\n', + + // Task configuration. + uglify: { + options: { + banner: '<%= banner %>', + report: 'gzip' + }, + build: { + src: 'src/loading-bar.js', + dest: 'build/loading-bar.min.js' + } + }, + + cssmin: { + options: { + banner: '<%= banner %>', + report: 'gzip' + }, + minify: { + src: 'src/loading-bar.css', + dest: 'build/loading-bar.min.css' + } + }, + + karma: { + unit: { + configFile: 'test/karma-angular-1.2.conf.js', + singleRun: true, + coverageReporter: { + type: 'text', + dir: 'coverage/' + } + }, + unit13: { + configFile: 'test/karma-angular-1.3.conf.js', + singleRun: true, + coverageReporter: { + type: 'text', + dir: 'coverage/' + } + }, + watch: { + configFile: 'test/karma-angular-1.2.conf.js', + singleRun: false, + reporters: ['progress'] // Don't display coverage + } + }, + + jshint: { + jshintrc: '.jshintrc', + gruntfile: { + src: 'Gruntfile.js' + }, + src: { + src: ['src/*.js'] + } + }, + + concat: { + build: { + options: { + banner: '<%= banner %>' + }, + files: { + 'build/loading-bar.css': 'src/loading-bar.css', + 'build/loading-bar.js': 'src/loading-bar.js', + } + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-karma'); + + grunt.registerTask('default', ['jshint', 'karma:unit', 'karma:unit13', 'uglify', 'cssmin', 'concat:build']); + grunt.registerTask('test', ['karma:watch']); + grunt.registerTask('build', ['default']); + +}; diff --git a/bower_components/angular-loading-bar/LICENSE b/bower_components/angular-loading-bar/LICENSE new file mode 100644 index 0000000..252c23a --- /dev/null +++ b/bower_components/angular-loading-bar/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013-2014 Wes Cruver + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bower_components/angular-loading-bar/README.md b/bower_components/angular-loading-bar/README.md new file mode 100644 index 0000000..b439b4b --- /dev/null +++ b/bower_components/angular-loading-bar/README.md @@ -0,0 +1,163 @@ +angular-loading-bar +=================== + +The idea is simple: Add a loading bar / progress bar whenever an XHR request goes out in angular. Multiple requests within the same time period get bundled together such that each response increments the progress bar by the appropriate amount. + +This is mostly cool because you simply include it in your app, and it works. There's no complicated setup, and no need to maintain the state of the loading bar; it's all handled automatically by the interceptor. + +**Requirements:** AngularJS 1.2+ + +**File Size:** 2.4Kb minified, 0.5Kb gzipped + + +## Usage: + +1. include the loading bar as a dependency for your app. If you want animations, include `ngAnimate` as well. *note: ngAnimate is optional* + + ```js + angular.module('myApp', ['angular-loading-bar', 'ngAnimate']) + ``` + +2. include the supplied CSS file (or create your own). +3. That's it -- you're done! + +#### via bower: +``` +$ bower install angular-loading-bar +``` +#### via npm: +``` +$ npm install angular-loading-bar +``` + + +## Why I created this +There are a couple projects similar to this out there, but none were ideal for me. All implementations I've seen require that you maintain state on behalf of the loading bar. In other words, you're setting the value of the loading/progress bar manually from potentially many different locations. This becomes complicated when you have a very large application with several services all making independant XHR requests. It becomes even more complicated if you want these services to be loosly coupled. + +Additionally, Angular was created as a highly testable framework, so it pains me to see Angular modules without tests. That is not the case here as this loading bar ships with 100% code coverage. + + +**Goals for this project:** + +1. Make it automatic +2. Unit tests, 100% coverage +3. Must work well with ngAnimate +4. Must be styled via external CSS (not inline) +5. No jQuery dependencies + + +## Configuration + +#### Turn the spinner on or off: +The insertion of the spinner can be controlled through configuration. It's on by default, but if you'd like to turn it off, simply configure the service: + +```js +angular.module('myApp', ['angular-loading-bar']) + .config(['cfpLoadingBarProvider', function(cfpLoadingBarProvider) { + cfpLoadingBarProvider.includeSpinner = false; + }]) +``` + +#### Turn the loading bar on or off: +Like the spinner configuration above, the loading bar can also be turned off for cases where you only want the spinner: + +```js +angular.module('myApp', ['angular-loading-bar']) + .config(['cfpLoadingBarProvider', function(cfpLoadingBarProvider) { + cfpLoadingBarProvider.includeBar = false; + }]) +``` + +#### Latency Threshold +By default, the loading bar will only display after it has been waiting for a response for over 100ms. This helps keep things feeling snappy, and avoids the annoyingness of showing a loading bar every few seconds on really chatty applications. This threshold is totally configurable: + +```js +angular.module('myApp', ['angular-loading-bar']) + .config(['cfpLoadingBarProvider', function(cfpLoadingBarProvider) { + cfpLoadingBarProvider.latencyThreshold = 500; + }]) +``` + +#### Ignoring particular XHR requests: +The loading bar can also be forced to ignore certain requests, for example, when long-polling or periodically sending debugging information back to the server. + +```js +// ignore particular $http requests: +$http.get('/status', { + ignoreLoadingBar: true +}); + +``` + + +```js +// ignore particular $resource requests: +.factory('Restaurant', function($resource) { + return $resource('/api/restaurant/:id', {id: '@id'}, { + query: { + method: 'GET', + isArray: true, + ignoreLoadingBar: true + } + }); +}); + +``` + + + + +## How it works: +This library is split into two modules, an $http `interceptor`, and a `service`: + +**Interceptor** +The interceptor simply listens for all outgoing XHR requests, and then instructs the loadingBar service to start, stop, and increment accordingly. There is no public API for the interceptor. It can be used stand-alone by including `cfp.loadingBarInterceptor` as a dependency for your module. + +**Service** +The service is responsible for the presentation of the loading bar. It injects the loading bar into the DOM, adjusts the width whenever `set()` is called, and `complete()`s the whole show by removing the loading bar from the DOM. + +## Service API (advanced usage) +Under normal circumstances you won't need to use this. However, if you wish to use the loading bar without the interceptor, you can do that as well. Simply include the loading bar service as a dependency instead of the main `angular-loading-bar` module: + +```js +angular.module('myApp', ['cfp.loadingBar']) +``` + + +```js + +cfpLoadingBar.start(); +// will insert the loading bar into the DOM, and display its progress at 1%. +// It will automatically call `inc()` repeatedly to give the illusion that the page load is progressing. + +cfpLoadingBar.inc(); +// increments the loading bar by a random amount. +// It is important to note that the auto incrementing will begin to slow down as +// the progress increases. This is to prevent the loading bar from appearing +// completed (or almost complete) before the XHR request has responded. + +cfpLoadingBar.set(0.3) // Set the loading bar to 30% +cfpLoadingBar.status() // Returns the loading bar's progress. +// -> 0.3 + +cfpLoadingBar.complete() +// Set the loading bar's progress to 100%, and then remove it from the DOM. + +``` + +## Events +The loading bar broadcasts the following events over $rootScope allowing further customization: + +**`cfpLoadingBar:loading`** triggered upon each XHR request that is not already cached + +**`cfpLoadingBar:loaded`** triggered each time an XHR request recieves a response (either successful or error) + +**`cfpLoadingBar:started`** triggered once upon the first XHR request. Will trigger again if another request goes out after `cfpLoadingBar:completed` has triggered. + +**`cfpLoadingBar:completed`** triggered once when the all XHR requests have returned (either successfully or not) + +## Credits: +Credit goes to [rstacruz](https://github.com/rstacruz) for his excellent [nProgress](https://github.com/rstacruz/nprogress). + +## License: +Licensed under the MIT license diff --git a/bower_components/angular-loading-bar/bower.json b/bower_components/angular-loading-bar/bower.json new file mode 100644 index 0000000..7777de5 --- /dev/null +++ b/bower_components/angular-loading-bar/bower.json @@ -0,0 +1,26 @@ +{ + "name": "angular-loading-bar", + "version": "0.5.2", + "main": [ + "build/loading-bar.js", + "build/loading-bar.css" + ], + "ignore": [ + "**/.*", + "node_modules", + "components", + "test", + "example" + ], + "devDependencies": { + "angular": "^1.2.9", + "angular-1.3": "angular#1.3", + "angular-mocks": "^1.2.9", + "angular-mocks-1.3": "angular-mocks#1.3", + "angular-animate": "^1.2.9", + "angular-animate-1.3": "angular-animate#1.3" + }, + "resolutions": { + "angular": "1.2.23" + } +} diff --git a/bower_components/angular-loading-bar/build/loading-bar.css b/bower_components/angular-loading-bar/build/loading-bar.css new file mode 100644 index 0000000..c98a11b --- /dev/null +++ b/bower_components/angular-loading-bar/build/loading-bar.css @@ -0,0 +1,110 @@ +/*! + * angular-loading-bar v0.5.2 + * https://chieffancypants.github.io/angular-loading-bar + * Copyright (c) 2014 Wes Cruver + * License: MIT + */ + +/* Make clicks pass-through */ +#loading-bar, +#loading-bar-spinner { + pointer-events: none; + -webkit-pointer-events: none; + -webkit-transition: 350ms linear all; + -moz-transition: 350ms linear all; + -o-transition: 350ms linear all; + transition: 350ms linear all; +} + +#loading-bar.ng-enter, +#loading-bar.ng-leave.ng-leave-active, +#loading-bar-spinner.ng-enter, +#loading-bar-spinner.ng-leave.ng-leave-active { + opacity: 0; +} + +#loading-bar.ng-enter.ng-enter-active, +#loading-bar.ng-leave, +#loading-bar-spinner.ng-enter.ng-enter-active, +#loading-bar-spinner.ng-leave { + opacity: 1; +} + +#loading-bar .bar { + -webkit-transition: width 350ms; + -moz-transition: width 350ms; + -o-transition: width 350ms; + transition: width 350ms; + + background: #29d; + position: fixed; + z-index: 10002; + top: 0; + left: 0; + width: 100%; + height: 2px; + border-bottom-right-radius: 1px; + border-top-right-radius: 1px; +} + +/* Fancy blur effect */ +#loading-bar .peg { + position: absolute; + width: 70px; + right: 0; + top: 0; + height: 2px; + opacity: .45; + -moz-box-shadow: #29d 1px 0 6px 1px; + -ms-box-shadow: #29d 1px 0 6px 1px; + -webkit-box-shadow: #29d 1px 0 6px 1px; + box-shadow: #29d 1px 0 6px 1px; + -moz-border-radius: 100%; + -webkit-border-radius: 100%; + border-radius: 100%; +} + +#loading-bar-spinner { + display: block; + position: fixed; + z-index: 10002; + top: 10px; + left: 10px; +} + +#loading-bar-spinner .spinner-icon { + width: 14px; + height: 14px; + + border: solid 2px transparent; + border-top-color: #29d; + border-left-color: #29d; + border-radius: 10px; + + -webkit-animation: loading-bar-spinner 400ms linear infinite; + -moz-animation: loading-bar-spinner 400ms linear infinite; + -ms-animation: loading-bar-spinner 400ms linear infinite; + -o-animation: loading-bar-spinner 400ms linear infinite; + animation: loading-bar-spinner 400ms linear infinite; +} + +@-webkit-keyframes loading-bar-spinner { + 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } +} +@-moz-keyframes loading-bar-spinner { + 0% { -moz-transform: rotate(0deg); transform: rotate(0deg); } + 100% { -moz-transform: rotate(360deg); transform: rotate(360deg); } +} +@-o-keyframes loading-bar-spinner { + 0% { -o-transform: rotate(0deg); transform: rotate(0deg); } + 100% { -o-transform: rotate(360deg); transform: rotate(360deg); } +} +@-ms-keyframes loading-bar-spinner { + 0% { -ms-transform: rotate(0deg); transform: rotate(0deg); } + 100% { -ms-transform: rotate(360deg); transform: rotate(360deg); } +} +@keyframes loading-bar-spinner { + 0% { transform: rotate(0deg); transform: rotate(0deg); } + 100% { transform: rotate(360deg); transform: rotate(360deg); } +} diff --git a/bower_components/angular-loading-bar/build/loading-bar.js b/bower_components/angular-loading-bar/build/loading-bar.js new file mode 100644 index 0000000..6259151 --- /dev/null +++ b/bower_components/angular-loading-bar/build/loading-bar.js @@ -0,0 +1,313 @@ +/*! + * angular-loading-bar v0.5.2 + * https://chieffancypants.github.io/angular-loading-bar + * Copyright (c) 2014 Wes Cruver + * License: MIT + */ +/* + * angular-loading-bar + * + * intercepts XHR requests and creates a loading bar. + * Based on the excellent nprogress work by rstacruz (more info in readme) + * + * (c) 2013 Wes Cruver + * License: MIT + */ + + +(function() { + +'use strict'; + +// Alias the loading bar for various backwards compatibilities since the project has matured: +angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']); +angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']); + + +/** + * loadingBarInterceptor service + * + * Registers itself as an Angular interceptor and listens for XHR requests. + */ +angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar']) + .config(['$httpProvider', function ($httpProvider) { + + var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, cfpLoadingBar) { + + /** + * The total number of requests made + */ + var reqsTotal = 0; + + /** + * The number of requests completed (either successfully or not) + */ + var reqsCompleted = 0; + + /** + * The amount of time spent fetching before showing the loading bar + */ + var latencyThreshold = cfpLoadingBar.latencyThreshold; + + /** + * $timeout handle for latencyThreshold + */ + var startTimeout; + + + /** + * calls cfpLoadingBar.complete() which removes the + * loading bar from the DOM. + */ + function setComplete() { + $timeout.cancel(startTimeout); + cfpLoadingBar.complete(); + reqsCompleted = 0; + reqsTotal = 0; + } + + /** + * Determine if the response has already been cached + * @param {Object} config the config option from the request + * @return {Boolean} retrns true if cached, otherwise false + */ + function isCached(config) { + var cache; + var defaultCache = $cacheFactory.get('$http'); + var defaults = $httpProvider.defaults; + + // Choose the proper cache source. Borrowed from angular: $http service + if ((config.cache || defaults.cache) && config.cache !== false && + (config.method === 'GET' || config.method === 'JSONP')) { + cache = angular.isObject(config.cache) ? config.cache + : angular.isObject(defaults.cache) ? defaults.cache + : defaultCache; + } + + var cached = cache !== undefined ? + cache.get(config.url) !== undefined : false; + + if (config.cached !== undefined && cached !== config.cached) { + return config.cached; + } + config.cached = cached; + return cached; + } + + + return { + 'request': function(config) { + // Check to make sure this request hasn't already been cached and that + // the requester didn't explicitly ask us to ignore this request: + if (!config.ignoreLoadingBar && !isCached(config)) { + $rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url}); + if (reqsTotal === 0) { + startTimeout = $timeout(function() { + cfpLoadingBar.start(); + }, latencyThreshold); + } + reqsTotal++; + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + return config; + }, + + 'response': function(response) { + if (!response.config.ignoreLoadingBar && !isCached(response.config)) { + reqsCompleted++; + $rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url}); + if (reqsCompleted >= reqsTotal) { + setComplete(); + } else { + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + } + return response; + }, + + 'responseError': function(rejection) { + if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) { + reqsCompleted++; + $rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url}); + if (reqsCompleted >= reqsTotal) { + setComplete(); + } else { + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + } + return $q.reject(rejection); + } + }; + }]; + + $httpProvider.interceptors.push(interceptor); + }]); + + +/** + * Loading Bar + * + * This service handles adding and removing the actual element in the DOM. + * Generally, best practices for DOM manipulation is to take place in a + * directive, but because the element itself is injected in the DOM only upon + * XHR requests, and it's likely needed on every view, the best option is to + * use a service. + */ +angular.module('cfp.loadingBar', []) + .provider('cfpLoadingBar', function() { + + this.includeSpinner = true; + this.includeBar = true; + this.latencyThreshold = 100; + this.startSize = 0.02; + this.parentSelector = 'body'; + this.spinnerTemplate = '
        '; + + this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) { + var $animate; + var $parentSelector = this.parentSelector, + loadingBarContainer = angular.element('
        '), + loadingBar = loadingBarContainer.find('div').eq(0), + spinner = angular.element(this.spinnerTemplate); + + var incTimeout, + completeTimeout, + started = false, + status = 0; + + var includeSpinner = this.includeSpinner; + var includeBar = this.includeBar; + var startSize = this.startSize; + + /** + * Inserts the loading bar element into the dom, and sets it to 2% + */ + function _start() { + if (!$animate) { + $animate = $injector.get('$animate'); + } + + var $parent = $document.find($parentSelector); + $timeout.cancel(completeTimeout); + + // do not continually broadcast the started event: + if (started) { + return; + } + + $rootScope.$broadcast('cfpLoadingBar:started'); + started = true; + + if (includeBar) { + $animate.enter(loadingBarContainer, $parent); + } + + if (includeSpinner) { + $animate.enter(spinner, $parent); + } + + _set(startSize); + } + + /** + * Set the loading bar's width to a certain percent. + * + * @param n any value between 0 and 1 + */ + function _set(n) { + if (!started) { + return; + } + var pct = (n * 100) + '%'; + loadingBar.css('width', pct); + status = n; + + // increment loadingbar to give the illusion that there is always + // progress but make sure to cancel the previous timeouts so we don't + // have multiple incs running at the same time. + $timeout.cancel(incTimeout); + incTimeout = $timeout(function() { + _inc(); + }, 250); + } + + /** + * Increments the loading bar by a random amount + * but slows down as it progresses + */ + function _inc() { + if (_status() >= 1) { + return; + } + + var rnd = 0; + + // TODO: do this mathmatically instead of through conditions + + var stat = _status(); + if (stat >= 0 && stat < 0.25) { + // Start out between 3 - 6% increments + rnd = (Math.random() * (5 - 3 + 1) + 3) / 100; + } else if (stat >= 0.25 && stat < 0.65) { + // increment between 0 - 3% + rnd = (Math.random() * 3) / 100; + } else if (stat >= 0.65 && stat < 0.9) { + // increment between 0 - 2% + rnd = (Math.random() * 2) / 100; + } else if (stat >= 0.9 && stat < 0.99) { + // finally, increment it .5 % + rnd = 0.005; + } else { + // after 99%, don't increment: + rnd = 0; + } + + var pct = _status() + rnd; + _set(pct); + } + + function _status() { + return status; + } + + function _completeAnimation() { + status = 0; + started = false; + } + + function _complete() { + if (!$animate) { + $animate = $injector.get('$animate'); + } + + $rootScope.$broadcast('cfpLoadingBar:completed'); + _set(1); + + $timeout.cancel(completeTimeout); + + // Attempt to aggregate any start/complete calls within 500ms: + completeTimeout = $timeout(function() { + var promise = $animate.leave(loadingBarContainer, _completeAnimation); + if (promise && promise.then) { + promise.then(_completeAnimation); + } + $animate.leave(spinner); + }, 500); + } + + return { + start : _start, + set : _set, + status : _status, + inc : _inc, + complete : _complete, + includeSpinner : this.includeSpinner, + latencyThreshold : this.latencyThreshold, + parentSelector : this.parentSelector, + startSize : this.startSize + }; + + + }]; // + }); // wtf javascript. srsly +})(); // diff --git a/bower_components/angular-loading-bar/build/loading-bar.min.css b/bower_components/angular-loading-bar/build/loading-bar.min.css new file mode 100644 index 0000000..e92ccde --- /dev/null +++ b/bower_components/angular-loading-bar/build/loading-bar.min.css @@ -0,0 +1,8 @@ +/*! + * angular-loading-bar v0.5.2 + * https://chieffancypants.github.io/angular-loading-bar + * Copyright (c) 2014 Wes Cruver + * License: MIT + */ + +#loading-bar,#loading-bar-spinner{pointer-events:none;-webkit-pointer-events:none;-webkit-transition:350ms linear all;-moz-transition:350ms linear all;-o-transition:350ms linear all;transition:350ms linear all}#loading-bar.ng-enter,#loading-bar.ng-leave.ng-leave-active,#loading-bar-spinner.ng-enter,#loading-bar-spinner.ng-leave.ng-leave-active{opacity:0}#loading-bar.ng-enter.ng-enter-active,#loading-bar.ng-leave,#loading-bar-spinner.ng-enter.ng-enter-active,#loading-bar-spinner.ng-leave{opacity:1}#loading-bar .bar{-webkit-transition:width 350ms;-moz-transition:width 350ms;-o-transition:width 350ms;transition:width 350ms;background:#29d;position:fixed;z-index:10002;top:0;left:0;width:100%;height:2px;border-bottom-right-radius:1px;border-top-right-radius:1px}#loading-bar .peg{position:absolute;width:70px;right:0;top:0;height:2px;opacity:.45;-moz-box-shadow:#29d 1px 0 6px 1px;-ms-box-shadow:#29d 1px 0 6px 1px;-webkit-box-shadow:#29d 1px 0 6px 1px;box-shadow:#29d 1px 0 6px 1px;-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%}#loading-bar-spinner{display:block;position:fixed;z-index:10002;top:10px;left:10px}#loading-bar-spinner .spinner-icon{width:14px;height:14px;border:solid 2px transparent;border-top-color:#29d;border-left-color:#29d;border-radius:10px;-webkit-animation:loading-bar-spinner 400ms linear infinite;-moz-animation:loading-bar-spinner 400ms linear infinite;-ms-animation:loading-bar-spinner 400ms linear infinite;-o-animation:loading-bar-spinner 400ms linear infinite;animation:loading-bar-spinner 400ms linear infinite}@-webkit-keyframes loading-bar-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes loading-bar-spinner{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes loading-bar-spinner{0%{-o-transform:rotate(0deg);transform:rotate(0deg)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes loading-bar-spinner{0%{-ms-transform:rotate(0deg);transform:rotate(0deg)}100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-bar-spinner{0%{transform:rotate(0deg);transform:rotate(0deg)}100%{transform:rotate(360deg);transform:rotate(360deg)}} \ No newline at end of file diff --git a/bower_components/angular-loading-bar/build/loading-bar.min.js b/bower_components/angular-loading-bar/build/loading-bar.min.js new file mode 100644 index 0000000..52576bc --- /dev/null +++ b/bower_components/angular-loading-bar/build/loading-bar.min.js @@ -0,0 +1,7 @@ +/*! + * angular-loading-bar v0.5.2 + * https://chieffancypants.github.io/angular-loading-bar + * Copyright (c) 2014 Wes Cruver + * License: MIT + */ +!function(){"use strict";angular.module("angular-loading-bar",["cfp.loadingBarInterceptor"]),angular.module("chieffancypants.loadingBar",["cfp.loadingBarInterceptor"]),angular.module("cfp.loadingBarInterceptor",["cfp.loadingBar"]).config(["$httpProvider",function(a){var b=["$q","$cacheFactory","$timeout","$rootScope","cfpLoadingBar",function(b,c,d,e,f){function g(){d.cancel(i),f.complete(),k=0,j=0}function h(b){var d,e=c.get("$http"),f=a.defaults;!b.cache&&!f.cache||b.cache===!1||"GET"!==b.method&&"JSONP"!==b.method||(d=angular.isObject(b.cache)?b.cache:angular.isObject(f.cache)?f.cache:e);var g=void 0!==d?void 0!==d.get(b.url):!1;return void 0!==b.cached&&g!==b.cached?b.cached:(b.cached=g,g)}var i,j=0,k=0,l=f.latencyThreshold;return{request:function(a){return a.ignoreLoadingBar||h(a)||(e.$broadcast("cfpLoadingBar:loading",{url:a.url}),0===j&&(i=d(function(){f.start()},l)),j++,f.set(k/j)),a},response:function(a){return a.config.ignoreLoadingBar||h(a.config)||(k++,e.$broadcast("cfpLoadingBar:loaded",{url:a.config.url}),k>=j?g():f.set(k/j)),a},responseError:function(a){return a.config.ignoreLoadingBar||h(a.config)||(k++,e.$broadcast("cfpLoadingBar:loaded",{url:a.config.url}),k>=j?g():f.set(k/j)),b.reject(a)}}}];a.interceptors.push(b)}]),angular.module("cfp.loadingBar",[]).provider("cfpLoadingBar",function(){this.includeSpinner=!0,this.includeBar=!0,this.latencyThreshold=100,this.startSize=.02,this.parentSelector="body",this.spinnerTemplate='
        ',this.$get=["$injector","$document","$timeout","$rootScope",function(a,b,c,d){function e(){k||(k=a.get("$animate"));var e=b.find(n);c.cancel(m),r||(d.$broadcast("cfpLoadingBar:started"),r=!0,u&&k.enter(o,e),t&&k.enter(q,e),f(v))}function f(a){if(r){var b=100*a+"%";p.css("width",b),s=a,c.cancel(l),l=c(function(){g()},250)}}function g(){if(!(h()>=1)){var a=0,b=h();a=b>=0&&.25>b?(3*Math.random()+3)/100:b>=.25&&.65>b?3*Math.random()/100:b>=.65&&.9>b?2*Math.random()/100:b>=.9&&.99>b?.005:0;var c=h()+a;f(c)}}function h(){return s}function i(){s=0,r=!1}function j(){k||(k=a.get("$animate")),d.$broadcast("cfpLoadingBar:completed"),f(1),c.cancel(m),m=c(function(){var a=k.leave(o,i);a&&a.then&&a.then(i),k.leave(q)},500)}var k,l,m,n=this.parentSelector,o=angular.element('
        '),p=o.find("div").eq(0),q=angular.element(this.spinnerTemplate),r=!1,s=0,t=this.includeSpinner,u=this.includeBar,v=this.startSize;return{start:e,set:f,status:h,inc:g,complete:j,includeSpinner:this.includeSpinner,latencyThreshold:this.latencyThreshold,parentSelector:this.parentSelector,startSize:this.startSize}}]})}(); \ No newline at end of file diff --git a/bower_components/angular-loading-bar/package.json b/bower_components/angular-loading-bar/package.json new file mode 100644 index 0000000..5c58371 --- /dev/null +++ b/bower_components/angular-loading-bar/package.json @@ -0,0 +1,45 @@ +{ + "name": "angular-loading-bar", + "version": "0.5.2", + "description": "An automatic loading bar for AngularJS", + "main": "src/loading-bar.js", + "directories": { + "example": "example", + "test": "test" + }, + "repository": { + "type": "git", + "url": "git://github.com/chieffancypants/angular-loading-bar.git" + }, + "keywords": [ + "angular", + "angularjs", + "loading", + "loadingbar", + "progress", + "progressbar" + ], + "author": "Wes Cruver", + "license": "MIT", + "bugs": { + "url": "https://github.com/chieffancypants/angular-loading-bar/issues" + }, + "homepage": "https://chieffancypants.github.io/angular-loading-bar", + "devDependencies": { + "karma-script-launcher": "~0.1.0", + "karma-chrome-launcher": "~0.1.0", + "karma-firefox-launcher": "~0.1.0", + "karma-html2js-preprocessor": "~0.1.0", + "karma-jasmine": "~0.1.3", + "karma-coffee-preprocessor": "~0.1.0", + "karma-phantomjs-launcher": "~0.1.0", + "karma": "~0.10.2", + "karma-coverage": "~0.1.0", + "grunt": "~0.4.1", + "grunt-contrib-jshint": "~0.6.4", + "grunt-contrib-uglify": "~0.2.4", + "grunt-contrib-cssmin": "~0.6.1", + "grunt-karma": "~0.6.2", + "grunt-contrib-concat": "~0.3.0" + } +} diff --git a/bower_components/angular-loading-bar/src/loading-bar.css b/bower_components/angular-loading-bar/src/loading-bar.css new file mode 100644 index 0000000..d854570 --- /dev/null +++ b/bower_components/angular-loading-bar/src/loading-bar.css @@ -0,0 +1,104 @@ + +/* Make clicks pass-through */ +#loading-bar, +#loading-bar-spinner { + pointer-events: none; + -webkit-pointer-events: none; + -webkit-transition: 350ms linear all; + -moz-transition: 350ms linear all; + -o-transition: 350ms linear all; + transition: 350ms linear all; +} + +#loading-bar.ng-enter, +#loading-bar.ng-leave.ng-leave-active, +#loading-bar-spinner.ng-enter, +#loading-bar-spinner.ng-leave.ng-leave-active { + opacity: 0; +} + +#loading-bar.ng-enter.ng-enter-active, +#loading-bar.ng-leave, +#loading-bar-spinner.ng-enter.ng-enter-active, +#loading-bar-spinner.ng-leave { + opacity: 1; +} + +#loading-bar .bar { + -webkit-transition: width 350ms; + -moz-transition: width 350ms; + -o-transition: width 350ms; + transition: width 350ms; + + background: #29d; + position: fixed; + z-index: 10002; + top: 0; + left: 0; + width: 100%; + height: 2px; + border-bottom-right-radius: 1px; + border-top-right-radius: 1px; +} + +/* Fancy blur effect */ +#loading-bar .peg { + position: absolute; + width: 70px; + right: 0; + top: 0; + height: 2px; + opacity: .45; + -moz-box-shadow: #29d 1px 0 6px 1px; + -ms-box-shadow: #29d 1px 0 6px 1px; + -webkit-box-shadow: #29d 1px 0 6px 1px; + box-shadow: #29d 1px 0 6px 1px; + -moz-border-radius: 100%; + -webkit-border-radius: 100%; + border-radius: 100%; +} + +#loading-bar-spinner { + display: block; + position: fixed; + z-index: 10002; + top: 10px; + left: 10px; +} + +#loading-bar-spinner .spinner-icon { + width: 14px; + height: 14px; + + border: solid 2px transparent; + border-top-color: #29d; + border-left-color: #29d; + border-radius: 10px; + + -webkit-animation: loading-bar-spinner 400ms linear infinite; + -moz-animation: loading-bar-spinner 400ms linear infinite; + -ms-animation: loading-bar-spinner 400ms linear infinite; + -o-animation: loading-bar-spinner 400ms linear infinite; + animation: loading-bar-spinner 400ms linear infinite; +} + +@-webkit-keyframes loading-bar-spinner { + 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } +} +@-moz-keyframes loading-bar-spinner { + 0% { -moz-transform: rotate(0deg); transform: rotate(0deg); } + 100% { -moz-transform: rotate(360deg); transform: rotate(360deg); } +} +@-o-keyframes loading-bar-spinner { + 0% { -o-transform: rotate(0deg); transform: rotate(0deg); } + 100% { -o-transform: rotate(360deg); transform: rotate(360deg); } +} +@-ms-keyframes loading-bar-spinner { + 0% { -ms-transform: rotate(0deg); transform: rotate(0deg); } + 100% { -ms-transform: rotate(360deg); transform: rotate(360deg); } +} +@keyframes loading-bar-spinner { + 0% { transform: rotate(0deg); transform: rotate(0deg); } + 100% { transform: rotate(360deg); transform: rotate(360deg); } +} diff --git a/bower_components/angular-loading-bar/src/loading-bar.js b/bower_components/angular-loading-bar/src/loading-bar.js new file mode 100644 index 0000000..b5a823c --- /dev/null +++ b/bower_components/angular-loading-bar/src/loading-bar.js @@ -0,0 +1,307 @@ +/* + * angular-loading-bar + * + * intercepts XHR requests and creates a loading bar. + * Based on the excellent nprogress work by rstacruz (more info in readme) + * + * (c) 2013 Wes Cruver + * License: MIT + */ + + +(function() { + +'use strict'; + +// Alias the loading bar for various backwards compatibilities since the project has matured: +angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']); +angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']); + + +/** + * loadingBarInterceptor service + * + * Registers itself as an Angular interceptor and listens for XHR requests. + */ +angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar']) + .config(['$httpProvider', function ($httpProvider) { + + var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, cfpLoadingBar) { + + /** + * The total number of requests made + */ + var reqsTotal = 0; + + /** + * The number of requests completed (either successfully or not) + */ + var reqsCompleted = 0; + + /** + * The amount of time spent fetching before showing the loading bar + */ + var latencyThreshold = cfpLoadingBar.latencyThreshold; + + /** + * $timeout handle for latencyThreshold + */ + var startTimeout; + + + /** + * calls cfpLoadingBar.complete() which removes the + * loading bar from the DOM. + */ + function setComplete() { + $timeout.cancel(startTimeout); + cfpLoadingBar.complete(); + reqsCompleted = 0; + reqsTotal = 0; + } + + /** + * Determine if the response has already been cached + * @param {Object} config the config option from the request + * @return {Boolean} retrns true if cached, otherwise false + */ + function isCached(config) { + var cache; + var defaultCache = $cacheFactory.get('$http'); + var defaults = $httpProvider.defaults; + + // Choose the proper cache source. Borrowed from angular: $http service + if ((config.cache || defaults.cache) && config.cache !== false && + (config.method === 'GET' || config.method === 'JSONP')) { + cache = angular.isObject(config.cache) ? config.cache + : angular.isObject(defaults.cache) ? defaults.cache + : defaultCache; + } + + var cached = cache !== undefined ? + cache.get(config.url) !== undefined : false; + + if (config.cached !== undefined && cached !== config.cached) { + return config.cached; + } + config.cached = cached; + return cached; + } + + + return { + 'request': function(config) { + // Check to make sure this request hasn't already been cached and that + // the requester didn't explicitly ask us to ignore this request: + if (!config.ignoreLoadingBar && !isCached(config)) { + $rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url}); + if (reqsTotal === 0) { + startTimeout = $timeout(function() { + cfpLoadingBar.start(); + }, latencyThreshold); + } + reqsTotal++; + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + return config; + }, + + 'response': function(response) { + if (!response.config.ignoreLoadingBar && !isCached(response.config)) { + reqsCompleted++; + $rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url}); + if (reqsCompleted >= reqsTotal) { + setComplete(); + } else { + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + } + return response; + }, + + 'responseError': function(rejection) { + if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) { + reqsCompleted++; + $rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url}); + if (reqsCompleted >= reqsTotal) { + setComplete(); + } else { + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + } + return $q.reject(rejection); + } + }; + }]; + + $httpProvider.interceptors.push(interceptor); + }]); + + +/** + * Loading Bar + * + * This service handles adding and removing the actual element in the DOM. + * Generally, best practices for DOM manipulation is to take place in a + * directive, but because the element itself is injected in the DOM only upon + * XHR requests, and it's likely needed on every view, the best option is to + * use a service. + */ +angular.module('cfp.loadingBar', []) + .provider('cfpLoadingBar', function() { + + this.includeSpinner = true; + this.includeBar = true; + this.latencyThreshold = 100; + this.startSize = 0.02; + this.parentSelector = 'body'; + this.spinnerTemplate = '
        '; + + this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) { + var $animate; + var $parentSelector = this.parentSelector, + loadingBarContainer = angular.element('
        '), + loadingBar = loadingBarContainer.find('div').eq(0), + spinner = angular.element(this.spinnerTemplate); + + var incTimeout, + completeTimeout, + started = false, + status = 0; + + var includeSpinner = this.includeSpinner; + var includeBar = this.includeBar; + var startSize = this.startSize; + + /** + * Inserts the loading bar element into the dom, and sets it to 2% + */ + function _start() { + if (!$animate) { + $animate = $injector.get('$animate'); + } + + var $parent = $document.find($parentSelector); + $timeout.cancel(completeTimeout); + + // do not continually broadcast the started event: + if (started) { + return; + } + + $rootScope.$broadcast('cfpLoadingBar:started'); + started = true; + + if (includeBar) { + $animate.enter(loadingBarContainer, $parent); + } + + if (includeSpinner) { + $animate.enter(spinner, $parent); + } + + _set(startSize); + } + + /** + * Set the loading bar's width to a certain percent. + * + * @param n any value between 0 and 1 + */ + function _set(n) { + if (!started) { + return; + } + var pct = (n * 100) + '%'; + loadingBar.css('width', pct); + status = n; + + // increment loadingbar to give the illusion that there is always + // progress but make sure to cancel the previous timeouts so we don't + // have multiple incs running at the same time. + $timeout.cancel(incTimeout); + incTimeout = $timeout(function() { + _inc(); + }, 250); + } + + /** + * Increments the loading bar by a random amount + * but slows down as it progresses + */ + function _inc() { + if (_status() >= 1) { + return; + } + + var rnd = 0; + + // TODO: do this mathmatically instead of through conditions + + var stat = _status(); + if (stat >= 0 && stat < 0.25) { + // Start out between 3 - 6% increments + rnd = (Math.random() * (5 - 3 + 1) + 3) / 100; + } else if (stat >= 0.25 && stat < 0.65) { + // increment between 0 - 3% + rnd = (Math.random() * 3) / 100; + } else if (stat >= 0.65 && stat < 0.9) { + // increment between 0 - 2% + rnd = (Math.random() * 2) / 100; + } else if (stat >= 0.9 && stat < 0.99) { + // finally, increment it .5 % + rnd = 0.005; + } else { + // after 99%, don't increment: + rnd = 0; + } + + var pct = _status() + rnd; + _set(pct); + } + + function _status() { + return status; + } + + function _completeAnimation() { + status = 0; + started = false; + } + + function _complete() { + if (!$animate) { + $animate = $injector.get('$animate'); + } + + $rootScope.$broadcast('cfpLoadingBar:completed'); + _set(1); + + $timeout.cancel(completeTimeout); + + // Attempt to aggregate any start/complete calls within 500ms: + completeTimeout = $timeout(function() { + var promise = $animate.leave(loadingBarContainer, _completeAnimation); + if (promise && promise.then) { + promise.then(_completeAnimation); + } + $animate.leave(spinner); + }, 500); + } + + return { + start : _start, + set : _set, + status : _status, + inc : _inc, + complete : _complete, + includeSpinner : this.includeSpinner, + latencyThreshold : this.latencyThreshold, + parentSelector : this.parentSelector, + startSize : this.startSize + }; + + + }]; // + }); // wtf javascript. srsly +})(); // diff --git a/bower_components/angular-mocks/README.md b/bower_components/angular-mocks/README.md new file mode 100644 index 0000000..69bc520 --- /dev/null +++ b/bower_components/angular-mocks/README.md @@ -0,0 +1,4 @@ +bower-angular-mocks +=================== + +angular-mocks.js bower repo \ No newline at end of file diff --git a/bower_components/angular-mocks/angular-mocks-c0ae134c58.js b/bower_components/angular-mocks/angular-mocks-c0ae134c58.js new file mode 100755 index 0000000..2626caa --- /dev/null +++ b/bower_components/angular-mocks/angular-mocks-c0ae134c58.js @@ -0,0 +1,1805 @@ +/** + * @license AngularJS v1.0.8 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + * + * TODO(vojta): wrap whole file into closure during build + */ + +/** + * @ngdoc overview + * @name angular.mock + * @description + * + * Namespace from 'angular-mocks.js' which contains testing related code. + */ +angular.mock = {}; + +/** + * ! This is a private undocumented service ! + * + * @name ngMock.$browser + * + * @description + * This service is a mock implementation of {@link ng.$browser}. It provides fake + * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, + * cookies, etc... + * + * The api of this service is the same as that of the real {@link ng.$browser $browser}, except + * that there are several helper methods available which can be used in tests. + */ +angular.mock.$BrowserProvider = function() { + this.$get = function() { + return new angular.mock.$Browser(); + }; +}; + +angular.mock.$Browser = function() { + var self = this; + + this.isMock = true; + self.$$url = "http://server/"; + self.$$lastUrl = self.$$url; // used by url polling fn + self.pollFns = []; + + // TODO(vojta): remove this temporary api + self.$$completeOutstandingRequest = angular.noop; + self.$$incOutstandingRequestCount = angular.noop; + + + // register url polling fn + + self.onUrlChange = function(listener) { + self.pollFns.push( + function() { + if (self.$$lastUrl != self.$$url) { + self.$$lastUrl = self.$$url; + listener(self.$$url); + } + } + ); + + return listener; + }; + + self.cookieHash = {}; + self.lastCookieHash = {}; + self.deferredFns = []; + self.deferredNextId = 0; + + self.defer = function(fn, delay) { + delay = delay || 0; + self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); + self.deferredFns.sort(function(a,b){ return a.time - b.time;}); + return self.deferredNextId++; + }; + + + self.defer.now = 0; + + + self.defer.cancel = function(deferId) { + var fnIndex; + + angular.forEach(self.deferredFns, function(fn, index) { + if (fn.id === deferId) fnIndex = index; + }); + + if (fnIndex !== undefined) { + self.deferredFns.splice(fnIndex, 1); + return true; + } + + return false; + }; + + + /** + * @name ngMock.$browser#defer.flush + * @methodOf ngMock.$browser + * + * @description + * Flushes all pending requests and executes the defer callbacks. + * + * @param {number=} number of milliseconds to flush. See {@link #defer.now} + */ + self.defer.flush = function(delay) { + if (angular.isDefined(delay)) { + self.defer.now += delay; + } else { + if (self.deferredFns.length) { + self.defer.now = self.deferredFns[self.deferredFns.length-1].time; + } else { + throw Error('No deferred tasks to be flushed'); + } + } + + while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { + self.deferredFns.shift().fn(); + } + }; + /** + * @name ngMock.$browser#defer.now + * @propertyOf ngMock.$browser + * + * @description + * Current milliseconds mock time. + */ + + self.$$baseHref = ''; + self.baseHref = function() { + return this.$$baseHref; + }; +}; +angular.mock.$Browser.prototype = { + +/** + * @name ngMock.$browser#poll + * @methodOf ngMock.$browser + * + * @description + * run all fns in pollFns + */ + poll: function poll() { + angular.forEach(this.pollFns, function(pollFn){ + pollFn(); + }); + }, + + addPollFn: function(pollFn) { + this.pollFns.push(pollFn); + return pollFn; + }, + + url: function(url, replace) { + if (url) { + this.$$url = url; + return this; + } + + return this.$$url; + }, + + cookies: function(name, value) { + if (name) { + if (value == undefined) { + delete this.cookieHash[name]; + } else { + if (angular.isString(value) && //strings only + value.length <= 4096) { //strict cookie storage limits + this.cookieHash[name] = value; + } + } + } else { + if (!angular.equals(this.cookieHash, this.lastCookieHash)) { + this.lastCookieHash = angular.copy(this.cookieHash); + this.cookieHash = angular.copy(this.cookieHash); + } + return this.cookieHash; + } + }, + + notifyWhenNoOutstandingRequests: function(fn) { + fn(); + } +}; + + +/** + * @ngdoc object + * @name ngMock.$exceptionHandlerProvider + * + * @description + * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed + * into the `$exceptionHandler`. + */ + +/** + * @ngdoc object + * @name ngMock.$exceptionHandler + * + * @description + * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed + * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration + * information. + * + * + *
        + *   describe('$exceptionHandlerProvider', function() {
        + *
        + *     it('should capture log messages and exceptions', function() {
        + *
        + *       module(function($exceptionHandlerProvider) {
        + *         $exceptionHandlerProvider.mode('log');
        + *       });
        + *
        + *       inject(function($log, $exceptionHandler, $timeout) {
        + *         $timeout(function() { $log.log(1); });
        + *         $timeout(function() { $log.log(2); throw 'banana peel'; });
        + *         $timeout(function() { $log.log(3); });
        + *         expect($exceptionHandler.errors).toEqual([]);
        + *         expect($log.assertEmpty());
        + *         $timeout.flush();
        + *         expect($exceptionHandler.errors).toEqual(['banana peel']);
        + *         expect($log.log.logs).toEqual([[1], [2], [3]]);
        + *       });
        + *     });
        + *   });
        + * 
        + */ + +angular.mock.$ExceptionHandlerProvider = function() { + var handler; + + /** + * @ngdoc method + * @name ngMock.$exceptionHandlerProvider#mode + * @methodOf ngMock.$exceptionHandlerProvider + * + * @description + * Sets the logging mode. + * + * @param {string} mode Mode of operation, defaults to `rethrow`. + * + * - `rethrow`: If any errors are passed into the handler in tests, it typically + * means that there is a bug in the application or test, so this mock will + * make these tests fail. + * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an + * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. + * See {@link ngMock.$log#assertEmpty assertEmpty()} and + * {@link ngMock.$log#reset reset()} + */ + this.mode = function(mode) { + switch(mode) { + case 'rethrow': + handler = function(e) { + throw e; + }; + break; + case 'log': + var errors = []; + + handler = function(e) { + if (arguments.length == 1) { + errors.push(e); + } else { + errors.push([].slice.call(arguments, 0)); + } + }; + + handler.errors = errors; + break; + default: + throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); + } + }; + + this.$get = function() { + return handler; + }; + + this.mode('rethrow'); +}; + + +/** + * @ngdoc service + * @name ngMock.$log + * + * @description + * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays + * (one array per logging level). These arrays are exposed as `logs` property of each of the + * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. + * + */ +angular.mock.$LogProvider = function() { + + function concat(array1, array2, index) { + return array1.concat(Array.prototype.slice.call(array2, index)); + } + + + this.$get = function () { + var $log = { + log: function() { $log.log.logs.push(concat([], arguments, 0)); }, + warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, + info: function() { $log.info.logs.push(concat([], arguments, 0)); }, + error: function() { $log.error.logs.push(concat([], arguments, 0)); } + }; + + /** + * @ngdoc method + * @name ngMock.$log#reset + * @methodOf ngMock.$log + * + * @description + * Reset all of the logging arrays to empty. + */ + $log.reset = function () { + /** + * @ngdoc property + * @name ngMock.$log#log.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#log}. + * + * @example + *
        +       * $log.log('Some Log');
        +       * var first = $log.log.logs.unshift();
        +       * 
        + */ + $log.log.logs = []; + /** + * @ngdoc property + * @name ngMock.$log#warn.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#warn}. + * + * @example + *
        +       * $log.warn('Some Warning');
        +       * var first = $log.warn.logs.unshift();
        +       * 
        + */ + $log.warn.logs = []; + /** + * @ngdoc property + * @name ngMock.$log#info.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#info}. + * + * @example + *
        +       * $log.info('Some Info');
        +       * var first = $log.info.logs.unshift();
        +       * 
        + */ + $log.info.logs = []; + /** + * @ngdoc property + * @name ngMock.$log#error.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#error}. + * + * @example + *
        +       * $log.log('Some Error');
        +       * var first = $log.error.logs.unshift();
        +       * 
        + */ + $log.error.logs = []; + }; + + /** + * @ngdoc method + * @name ngMock.$log#assertEmpty + * @methodOf ngMock.$log + * + * @description + * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. + */ + $log.assertEmpty = function() { + var errors = []; + angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { + angular.forEach($log[logLevel].logs, function(log) { + angular.forEach(log, function (logItem) { + errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); + }); + }); + }); + if (errors.length) { + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + + "log message was not checked and removed:"); + errors.push(''); + throw new Error(errors.join('\n---------\n')); + } + }; + + $log.reset(); + return $log; + }; +}; + + +(function() { + var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; + + function jsonStringToDate(string) { + var match; + if (match = string.match(R_ISO8061_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0; + if (match[9]) { + tzHour = int(match[9] + match[10]); + tzMin = int(match[9] + match[11]); + } + date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); + date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); + return date; + } + return string; + } + + function int(str) { + return parseInt(str, 10); + } + + function padNumber(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while(num.length < digits) num = '0' + num; + if (trim) + num = num.substr(num.length - digits); + return neg + num; + } + + + /** + * @ngdoc object + * @name angular.mock.TzDate + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. + * + * Mock of the Date type which has its timezone specified via constructor arg. + * + * The main purpose is to create Date-like instances with timezone fixed to the specified timezone + * offset, so that we can test code that depends on local timezone settings without dependency on + * the time zone settings of the machine where the code is running. + * + * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) + * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* + * + * @example + * !!!! WARNING !!!!! + * This is not a complete Date object so only methods that were implemented can be called safely. + * To make matters worse, TzDate instances inherit stuff from Date via a prototype. + * + * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is + * incomplete we might be missing some non-standard methods. This can result in errors like: + * "Date.prototype.foo called on incompatible Object". + * + *
        +   * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
        +   * newYearInBratislava.getTimezoneOffset() => -60;
        +   * newYearInBratislava.getFullYear() => 2010;
        +   * newYearInBratislava.getMonth() => 0;
        +   * newYearInBratislava.getDate() => 1;
        +   * newYearInBratislava.getHours() => 0;
        +   * newYearInBratislava.getMinutes() => 0;
        +   * 
        + * + */ + angular.mock.TzDate = function (offset, timestamp) { + var self = new Date(0); + if (angular.isString(timestamp)) { + var tsStr = timestamp; + + self.origDate = jsonStringToDate(timestamp); + + timestamp = self.origDate.getTime(); + if (isNaN(timestamp)) + throw { + name: "Illegal Argument", + message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" + }; + } else { + self.origDate = new Date(timestamp); + } + + var localOffset = new Date(timestamp).getTimezoneOffset(); + self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; + self.date = new Date(timestamp + self.offsetDiff); + + self.getTime = function() { + return self.date.getTime() - self.offsetDiff; + }; + + self.toLocaleDateString = function() { + return self.date.toLocaleDateString(); + }; + + self.getFullYear = function() { + return self.date.getFullYear(); + }; + + self.getMonth = function() { + return self.date.getMonth(); + }; + + self.getDate = function() { + return self.date.getDate(); + }; + + self.getHours = function() { + return self.date.getHours(); + }; + + self.getMinutes = function() { + return self.date.getMinutes(); + }; + + self.getSeconds = function() { + return self.date.getSeconds(); + }; + + self.getTimezoneOffset = function() { + return offset * 60; + }; + + self.getUTCFullYear = function() { + return self.origDate.getUTCFullYear(); + }; + + self.getUTCMonth = function() { + return self.origDate.getUTCMonth(); + }; + + self.getUTCDate = function() { + return self.origDate.getUTCDate(); + }; + + self.getUTCHours = function() { + return self.origDate.getUTCHours(); + }; + + self.getUTCMinutes = function() { + return self.origDate.getUTCMinutes(); + }; + + self.getUTCSeconds = function() { + return self.origDate.getUTCSeconds(); + }; + + self.getUTCMilliseconds = function() { + return self.origDate.getUTCMilliseconds(); + }; + + self.getDay = function() { + return self.date.getDay(); + }; + + // provide this method only on browsers that already have it + if (self.toISOString) { + self.toISOString = function() { + return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + + padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + + padNumber(self.origDate.getUTCDate(), 2) + 'T' + + padNumber(self.origDate.getUTCHours(), 2) + ':' + + padNumber(self.origDate.getUTCMinutes(), 2) + ':' + + padNumber(self.origDate.getUTCSeconds(), 2) + '.' + + padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' + } + } + + //hide all methods not implemented in this mock that the Date prototype exposes + var unimplementedMethods = ['getMilliseconds', 'getUTCDay', + 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', + 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', + 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', + 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', + 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; + + angular.forEach(unimplementedMethods, function(methodName) { + self[methodName] = function() { + throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); + }; + }); + + return self; + }; + + //make "tzDateInstance instanceof Date" return true + angular.mock.TzDate.prototype = Date.prototype; +})(); + + +/** + * @ngdoc function + * @name angular.mock.dump + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available function. + * + * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging. + * + * This method is also available on window, where it can be used to display objects on debug console. + * + * @param {*} object - any object to turn into string. + * @return {string} a serialized string of the argument + */ +angular.mock.dump = function(object) { + return serialize(object); + + function serialize(object) { + var out; + + if (angular.isElement(object)) { + object = angular.element(object); + out = angular.element('
        '); + angular.forEach(object, function(element) { + out.append(angular.element(element).clone()); + }); + out = out.html(); + } else if (angular.isArray(object)) { + out = []; + angular.forEach(object, function(o) { + out.push(serialize(o)); + }); + out = '[ ' + out.join(', ') + ' ]'; + } else if (angular.isObject(object)) { + if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { + out = serializeScope(object); + } else if (object instanceof Error) { + out = object.stack || ('' + object.name + ': ' + object.message); + } else { + out = angular.toJson(object, true); + } + } else { + out = String(object); + } + + return out; + } + + function serializeScope(scope, offset) { + offset = offset || ' '; + var log = [offset + 'Scope(' + scope.$id + '): {']; + for ( var key in scope ) { + if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { + log.push(' ' + key + ': ' + angular.toJson(scope[key])); + } + } + var child = scope.$$childHead; + while(child) { + log.push(serializeScope(child, offset + ' ')); + child = child.$$nextSibling; + } + log.push('}'); + return log.join('\n' + offset); + } +}; + +/** + * @ngdoc object + * @name ngMock.$httpBackend + * @description + * Fake HTTP backend implementation suitable for unit testing applications that use the + * {@link ng.$http $http service}. + * + * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less + * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. + * + * During unit testing, we want our unit tests to run quickly and have no external dependencies so + * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or + * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is + * to verify whether a certain request has been sent or not, or alternatively just let the + * application make requests, respond with pre-trained responses and assert that the end result is + * what we expect it to be. + * + * This mock implementation can be used to respond with static or dynamic responses via the + * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). + * + * When an Angular application needs some data from a server, it calls the $http service, which + * sends the request to a real server using $httpBackend service. With dependency injection, it is + * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify + * the requests and respond with some testing data without sending a request to real server. + * + * There are two ways to specify what test data should be returned as http responses by the mock + * backend when the code under test makes http requests: + * + * - `$httpBackend.expect` - specifies a request expectation + * - `$httpBackend.when` - specifies a backend definition + * + * + * # Request Expectations vs Backend Definitions + * + * Request expectations provide a way to make assertions about requests made by the application and + * to define responses for those requests. The test will fail if the expected requests are not made + * or they are made in the wrong order. + * + * Backend definitions allow you to define a fake backend for your application which doesn't assert + * if a particular request was made or not, it just returns a trained response if a request is made. + * The test will pass whether or not the request gets made during testing. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
        Request expectationsBackend definitions
        Syntax.expect(...).respond(...).when(...).respond(...)
        Typical usagestrict unit testsloose (black-box) unit testing
        Fulfills multiple requestsNOYES
        Order of requests mattersYESNO
        Request requiredYESNO
        Response requiredoptional (see below)YES
        + * + * In cases where both backend definitions and request expectations are specified during unit + * testing, the request expectations are evaluated first. + * + * If a request expectation has no response specified, the algorithm will search your backend + * definitions for an appropriate response. + * + * If a request didn't match any expectation or if the expectation doesn't have the response + * defined, the backend definitions are evaluated in sequential order to see if any of them match + * the request. The response from the first matched definition is returned. + * + * + * # Flushing HTTP requests + * + * The $httpBackend used in production, always responds to requests with responses asynchronously. + * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are + * hard to write, follow and maintain. At the same time the testing mock, can't respond + * synchronously because that would change the execution of the code under test. For this reason the + * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending + * requests and thus preserving the async api of the backend, while allowing the test to execute + * synchronously. + * + * + * # Unit testing with mock $httpBackend + * The following code shows how to setup and use the mock backend in unit testing a controller. + * First we create the controller under test + * +
        +  // The controller code
        +  function MyController($scope, $http) {
        +    var authToken;
        +
        +    $http.get('/auth.py').success(function(data, status, headers) {
        +      authToken = headers('A-Token');
        +      $scope.user = data;
        +    });
        +
        +    $scope.saveMessage = function(message) {
        +      var headers = { 'Authorization': authToken };
        +      $scope.status = 'Saving...';
        +
        +      $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
        +        $scope.status = '';
        +      }).error(function() {
        +        $scope.status = 'ERROR!';
        +      });
        +    };
        +  }
        +  
        + * + * Now we setup the mock backend and create the test specs. + * +
        +    // testing controller
        +    describe('MyController', function() {
        +       var $httpBackend, $rootScope, createController;
        +
        +       beforeEach(inject(function($injector) {
        +         // Set up the mock http service responses
        +         $httpBackend = $injector.get('$httpBackend');
        +         // backend definition common for all tests
        +         $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
        +
        +         // Get hold of a scope (i.e. the root scope)
        +         $rootScope = $injector.get('$rootScope');
        +         // The $controller service is used to create instances of controllers
        +         var $controller = $injector.get('$controller');
        +
        +         createController = function() {
        +           return $controller('MyController', {'$scope' : $rootScope });
        +         };
        +       }));
        +
        +
        +       afterEach(function() {
        +         $httpBackend.verifyNoOutstandingExpectation();
        +         $httpBackend.verifyNoOutstandingRequest();
        +       });
        +
        +
        +       it('should fetch authentication token', function() {
        +         $httpBackend.expectGET('/auth.py');
        +         var controller = createController();
        +         $httpBackend.flush();
        +       });
        +
        +
        +       it('should send msg to server', function() {
        +         var controller = createController();
        +         $httpBackend.flush();
        +
        +         // now you don’t care about the authentication, but
        +         // the controller will still send the request and
        +         // $httpBackend will respond without you having to
        +         // specify the expectation and response for this request
        +
        +         $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
        +         $rootScope.saveMessage('message content');
        +         expect($rootScope.status).toBe('Saving...');
        +         $httpBackend.flush();
        +         expect($rootScope.status).toBe('');
        +       });
        +
        +
        +       it('should send auth header', function() {
        +         var controller = createController();
        +         $httpBackend.flush();
        +
        +         $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
        +           // check if the header was send, if it wasn't the expectation won't
        +           // match the request and the test will fail
        +           return headers['Authorization'] == 'xxx';
        +         }).respond(201, '');
        +
        +         $rootScope.saveMessage('whatever');
        +         $httpBackend.flush();
        +       });
        +    });
        +   
        + */ +angular.mock.$HttpBackendProvider = function() { + this.$get = [createHttpBackendMock]; +}; + +/** + * General factory function for $httpBackend mock. + * Returns instance for unit testing (when no arguments specified): + * - passing through is disabled + * - auto flushing is disabled + * + * Returns instance for e2e testing (when `$delegate` and `$browser` specified): + * - passing through (delegating request to real backend) is enabled + * - auto flushing is enabled + * + * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) + * @param {Object=} $browser Auto-flushing enabled if specified + * @return {Object} Instance of $httpBackend mock + */ +function createHttpBackendMock($delegate, $browser) { + var definitions = [], + expectations = [], + responses = [], + responsesPush = angular.bind(responses, responses.push); + + function createResponse(status, data, headers) { + if (angular.isFunction(status)) return status; + + return function() { + return angular.isNumber(status) + ? [status, data, headers] + : [200, status, data]; + }; + } + + // TODO(vojta): change params to: method, url, data, headers, callback + function $httpBackend(method, url, data, callback, headers) { + var xhr = new MockXhr(), + expectation = expectations[0], + wasExpected = false; + + function prettyPrint(data) { + return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) + ? data + : angular.toJson(data); + } + + if (expectation && expectation.match(method, url)) { + if (!expectation.matchData(data)) + throw Error('Expected ' + expectation + ' with different data\n' + + 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); + + if (!expectation.matchHeaders(headers)) + throw Error('Expected ' + expectation + ' with different headers\n' + + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + + prettyPrint(headers)); + + expectations.shift(); + + if (expectation.response) { + responses.push(function() { + var response = expectation.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(response[0], response[1], xhr.getAllResponseHeaders()); + }); + return; + } + wasExpected = true; + } + + var i = -1, definition; + while ((definition = definitions[++i])) { + if (definition.match(method, url, data, headers || {})) { + if (definition.response) { + // if $browser specified, we do auto flush all requests + ($browser ? $browser.defer : responsesPush)(function() { + var response = definition.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(response[0], response[1], xhr.getAllResponseHeaders()); + }); + } else if (definition.passThrough) { + $delegate(method, url, data, callback, headers); + } else throw Error('No response defined !'); + return; + } + } + throw wasExpected ? + Error('No response defined !') : + Error('Unexpected request: ' + method + ' ' + url + '\n' + + (expectation ? 'Expected ' + expectation : 'No more request expected')); + } + + /** + * @ngdoc method + * @name ngMock.$httpBackend#when + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + */ + $httpBackend.when = function(method, url, data, headers) { + var definition = new MockHttpExpectation(method, url, data, headers), + chain = { + respond: function(status, data, headers) { + definition.response = createResponse(status, data, headers); + } + }; + + if ($browser) { + chain.passThrough = function() { + definition.passThrough = true; + }; + } + + definitions.push(definition); + return chain; + }; + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenGET + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenHEAD + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenDELETE + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenPOST + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenPUT + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenJSONP + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + createShortMethods('when'); + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expect + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current expectation. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + */ + $httpBackend.expect = function(method, url, data, headers) { + var expectation = new MockHttpExpectation(method, url, data, headers); + expectations.push(expectation); + return { + respond: function(status, data, headers) { + expectation.response = createResponse(status, data, headers); + } + }; + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectGET + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for GET requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. See #expect for more info. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectHEAD + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for HEAD requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectDELETE + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for DELETE requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectPOST + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for POST requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectPUT + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for PUT requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectPATCH + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for PATCH requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectJSONP + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for JSONP requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + createShortMethods('expect'); + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#flush + * @methodOf ngMock.$httpBackend + * @description + * Flushes all pending requests using the trained responses. + * + * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, + * all pending requests will be flushed. If there are no pending requests when the flush method + * is called an exception is thrown (as this typically a sign of programming error). + */ + $httpBackend.flush = function(count) { + if (!responses.length) throw Error('No pending request to flush !'); + + if (angular.isDefined(count)) { + while (count--) { + if (!responses.length) throw Error('No more pending request to flush !'); + responses.shift()(); + } + } else { + while (responses.length) { + responses.shift()(); + } + } + $httpBackend.verifyNoOutstandingExpectation(); + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#verifyNoOutstandingExpectation + * @methodOf ngMock.$httpBackend + * @description + * Verifies that all of the requests defined via the `expect` api were made. If any of the + * requests were not made, verifyNoOutstandingExpectation throws an exception. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + *
        +   *   afterEach($httpBackend.verifyNoOutstandingExpectation);
        +   * 
        + */ + $httpBackend.verifyNoOutstandingExpectation = function() { + if (expectations.length) { + throw Error('Unsatisfied requests: ' + expectations.join(', ')); + } + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#verifyNoOutstandingRequest + * @methodOf ngMock.$httpBackend + * @description + * Verifies that there are no outstanding requests that need to be flushed. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + *
        +   *   afterEach($httpBackend.verifyNoOutstandingRequest);
        +   * 
        + */ + $httpBackend.verifyNoOutstandingRequest = function() { + if (responses.length) { + throw Error('Unflushed requests: ' + responses.length); + } + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#resetExpectations + * @methodOf ngMock.$httpBackend + * @description + * Resets all request expectations, but preserves all backend definitions. Typically, you would + * call resetExpectations during a multiple-phase test when you want to reuse the same instance of + * $httpBackend mock. + */ + $httpBackend.resetExpectations = function() { + expectations.length = 0; + responses.length = 0; + }; + + return $httpBackend; + + + function createShortMethods(prefix) { + angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { + $httpBackend[prefix + method] = function(url, headers) { + return $httpBackend[prefix](method, url, undefined, headers) + } + }); + + angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { + $httpBackend[prefix + method] = function(url, data, headers) { + return $httpBackend[prefix](method, url, data, headers) + } + }); + } +} + +function MockHttpExpectation(method, url, data, headers) { + + this.data = data; + this.headers = headers; + + this.match = function(m, u, d, h) { + if (method != m) return false; + if (!this.matchUrl(u)) return false; + if (angular.isDefined(d) && !this.matchData(d)) return false; + if (angular.isDefined(h) && !this.matchHeaders(h)) return false; + return true; + }; + + this.matchUrl = function(u) { + if (!url) return true; + if (angular.isFunction(url.test)) return url.test(u); + return url == u; + }; + + this.matchHeaders = function(h) { + if (angular.isUndefined(headers)) return true; + if (angular.isFunction(headers)) return headers(h); + return angular.equals(headers, h); + }; + + this.matchData = function(d) { + if (angular.isUndefined(data)) return true; + if (data && angular.isFunction(data.test)) return data.test(d); + if (data && !angular.isString(data)) return angular.toJson(data) == d; + return data == d; + }; + + this.toString = function() { + return method + ' ' + url; + }; +} + +function MockXhr() { + + // hack for testing $http, $httpBackend + MockXhr.$$lastInstance = this; + + this.open = function(method, url, async) { + this.$$method = method; + this.$$url = url; + this.$$async = async; + this.$$reqHeaders = {}; + this.$$respHeaders = {}; + }; + + this.send = function(data) { + this.$$data = data; + }; + + this.setRequestHeader = function(key, value) { + this.$$reqHeaders[key] = value; + }; + + this.getResponseHeader = function(name) { + // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last + var header = this.$$respHeaders[name]; + if (header) return header; + + name = angular.lowercase(name); + header = this.$$respHeaders[name]; + if (header) return header; + + header = undefined; + angular.forEach(this.$$respHeaders, function(headerVal, headerName) { + if (!header && angular.lowercase(headerName) == name) header = headerVal; + }); + return header; + }; + + this.getAllResponseHeaders = function() { + var lines = []; + + angular.forEach(this.$$respHeaders, function(value, key) { + lines.push(key + ': ' + value); + }); + return lines.join('\n'); + }; + + this.abort = angular.noop; +} + + +/** + * @ngdoc function + * @name ngMock.$timeout + * @description + * + * This service is just a simple decorator for {@link ng.$timeout $timeout} service + * that adds a "flush" method. + */ + +/** + * @ngdoc method + * @name ngMock.$timeout#flush + * @methodOf ngMock.$timeout + * @description + * + * Flushes the queue of pending tasks. + */ + +/** + * + */ +angular.mock.$RootElementProvider = function() { + this.$get = function() { + return angular.element('
        '); + } +}; + +/** + * @ngdoc overview + * @name ngMock + * @description + * + * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful + * mocks to the {@link AUTO.$injector $injector}. + */ +angular.module('ngMock', ['ng']).provider({ + $browser: angular.mock.$BrowserProvider, + $exceptionHandler: angular.mock.$ExceptionHandlerProvider, + $log: angular.mock.$LogProvider, + $httpBackend: angular.mock.$HttpBackendProvider, + $rootElement: angular.mock.$RootElementProvider +}).config(function($provide) { + $provide.decorator('$timeout', function($delegate, $browser) { + $delegate.flush = function(delay) { + $browser.defer.flush(delay); + }; + return $delegate; + }); +}); + + +/** + * @ngdoc overview + * @name ngMockE2E + * @description + * + * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. + * Currently there is only one mock present in this module - + * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. + */ +angular.module('ngMockE2E', ['ng']).config(function($provide) { + $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); +}); + +/** + * @ngdoc object + * @name ngMockE2E.$httpBackend + * @description + * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of + * applications that use the {@link ng.$http $http service}. + * + * *Note*: For fake http backend implementation suitable for unit testing please see + * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. + * + * This implementation can be used to respond with static or dynamic responses via the `when` api + * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the + * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch + * templates from a webserver). + * + * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application + * is being developed with the real backend api replaced with a mock, it is often desirable for + * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch + * templates or static files from the webserver). To configure the backend with this behavior + * use the `passThrough` request handler of `when` instead of `respond`. + * + * Additionally, we don't want to manually have to flush mocked out requests like we do during unit + * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests + * automatically, closely simulating the behavior of the XMLHttpRequest object. + * + * To setup the application to run with this http backend, you have to create a module that depends + * on the `ngMockE2E` and your application modules and defines the fake backend: + * + *
        + *   myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
        + *   myAppDev.run(function($httpBackend) {
        + *     phones = [{name: 'phone1'}, {name: 'phone2'}];
        + *
        + *     // returns the current list of phones
        + *     $httpBackend.whenGET('/phones').respond(phones);
        + *
        + *     // adds a new phone to the phones array
        + *     $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
        + *       phones.push(angular.fromJson(data));
        + *     });
        + *     $httpBackend.whenGET(/^\/templates\//).passThrough();
        + *     //...
        + *   });
        + * 
        + * + * Afterwards, bootstrap your app with this new module. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#when + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` + * handler, will be pass through to the real backend (an XHR request will be made to the + * server. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenGET + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenHEAD + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenDELETE + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenPOST + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenPUT + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenPATCH + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for PATCH requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenJSONP + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ +angular.mock.e2e = {}; +angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock]; + + +angular.mock.clearDataCache = function() { + var key, + cache = angular.element.cache; + + for(key in cache) { + if (cache.hasOwnProperty(key)) { + var handle = cache[key].handle; + + handle && angular.element(handle.elem).unbind(); + delete cache[key]; + } + } +}; + + + +window.jasmine && (function(window) { + + afterEach(function() { + var spec = getCurrentSpec(); + var injector = spec.$injector; + + spec.$injector = null; + spec.$modules = null; + + if (injector) { + injector.get('$rootElement').unbind(); + injector.get('$browser').pollFns.length = 0; + } + + angular.mock.clearDataCache(); + + // clean up jquery's fragment cache + angular.forEach(angular.element.fragments, function(val, key) { + delete angular.element.fragments[key]; + }); + + MockXhr.$$lastInstance = null; + + angular.forEach(angular.callbacks, function(val, key) { + delete angular.callbacks[key]; + }); + angular.callbacks.counter = 0; + }); + + function getCurrentSpec() { + return jasmine.getEnv().currentSpec; + } + + function isSpecRunning() { + var spec = getCurrentSpec(); + return spec && spec.queue.running; + } + + /** + * @ngdoc function + * @name angular.mock.module + * @description + * + * *NOTE*: This function is also published on window for easy access.
        + * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. + * + * This function registers a module configuration code. It collects the configuration information + * which will be used when the injector is created by {@link angular.mock.inject inject}. + * + * See {@link angular.mock.inject inject} for usage example + * + * @param {...(string|Function)} fns any number of modules which are represented as string + * aliases or as anonymous module initialization functions. The modules are used to + * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. + */ + window.module = angular.mock.module = function() { + var moduleFns = Array.prototype.slice.call(arguments, 0); + return isSpecRunning() ? workFn() : workFn; + ///////////////////// + function workFn() { + var spec = getCurrentSpec(); + if (spec.$injector) { + throw Error('Injector already created, can not register a module!'); + } else { + var modules = spec.$modules || (spec.$modules = []); + angular.forEach(moduleFns, function(module) { + modules.push(module); + }); + } + } + }; + + /** + * @ngdoc function + * @name angular.mock.inject + * @description + * + * *NOTE*: This function is also published on window for easy access.
        + * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. + * + * The inject function wraps a function into an injectable function. The inject() creates new + * instance of {@link AUTO.$injector $injector} per test, which is then used for + * resolving references. + * + * See also {@link angular.mock.module module} + * + * Example of what a typical jasmine tests looks like with the inject method. + *
        +   *
        +   *   angular.module('myApplicationModule', [])
        +   *       .value('mode', 'app')
        +   *       .value('version', 'v1.0.1');
        +   *
        +   *
        +   *   describe('MyApp', function() {
        +   *
        +   *     // You need to load modules that you want to test,
        +   *     // it loads only the "ng" module by default.
        +   *     beforeEach(module('myApplicationModule'));
        +   *
        +   *
        +   *     // inject() is used to inject arguments of all given functions
        +   *     it('should provide a version', inject(function(mode, version) {
        +   *       expect(version).toEqual('v1.0.1');
        +   *       expect(mode).toEqual('app');
        +   *     }));
        +   *
        +   *
        +   *     // The inject and module method can also be used inside of the it or beforeEach
        +   *     it('should override a version and test the new version is injected', function() {
        +   *       // module() takes functions or strings (module aliases)
        +   *       module(function($provide) {
        +   *         $provide.value('version', 'overridden'); // override version here
        +   *       });
        +   *
        +   *       inject(function(version) {
        +   *         expect(version).toEqual('overridden');
        +   *       });
        +   *     ));
        +   *   });
        +   *
        +   * 
        + * + * @param {...Function} fns any number of functions which will be injected using the injector. + */ + window.inject = angular.mock.inject = function() { + var blockFns = Array.prototype.slice.call(arguments, 0); + var errorForStack = new Error('Declaration Location'); + return isSpecRunning() ? workFn() : workFn; + ///////////////////// + function workFn() { + var spec = getCurrentSpec(); + var modules = spec.$modules || []; + modules.unshift('ngMock'); + modules.unshift('ng'); + var injector = spec.$injector; + if (!injector) { + injector = spec.$injector = angular.injector(modules); + } + for(var i = 0, ii = blockFns.length; i < ii; i++) { + try { + injector.invoke(blockFns[i] || angular.noop, this); + } catch (e) { + if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; + throw e; + } finally { + errorForStack = null; + } + } + } + }; +})(window); diff --git a/bower_components/angular-mocks/angular-mocks.js b/bower_components/angular-mocks/angular-mocks.js new file mode 100755 index 0000000..2626caa --- /dev/null +++ b/bower_components/angular-mocks/angular-mocks.js @@ -0,0 +1,1805 @@ +/** + * @license AngularJS v1.0.8 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + * + * TODO(vojta): wrap whole file into closure during build + */ + +/** + * @ngdoc overview + * @name angular.mock + * @description + * + * Namespace from 'angular-mocks.js' which contains testing related code. + */ +angular.mock = {}; + +/** + * ! This is a private undocumented service ! + * + * @name ngMock.$browser + * + * @description + * This service is a mock implementation of {@link ng.$browser}. It provides fake + * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, + * cookies, etc... + * + * The api of this service is the same as that of the real {@link ng.$browser $browser}, except + * that there are several helper methods available which can be used in tests. + */ +angular.mock.$BrowserProvider = function() { + this.$get = function() { + return new angular.mock.$Browser(); + }; +}; + +angular.mock.$Browser = function() { + var self = this; + + this.isMock = true; + self.$$url = "http://server/"; + self.$$lastUrl = self.$$url; // used by url polling fn + self.pollFns = []; + + // TODO(vojta): remove this temporary api + self.$$completeOutstandingRequest = angular.noop; + self.$$incOutstandingRequestCount = angular.noop; + + + // register url polling fn + + self.onUrlChange = function(listener) { + self.pollFns.push( + function() { + if (self.$$lastUrl != self.$$url) { + self.$$lastUrl = self.$$url; + listener(self.$$url); + } + } + ); + + return listener; + }; + + self.cookieHash = {}; + self.lastCookieHash = {}; + self.deferredFns = []; + self.deferredNextId = 0; + + self.defer = function(fn, delay) { + delay = delay || 0; + self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); + self.deferredFns.sort(function(a,b){ return a.time - b.time;}); + return self.deferredNextId++; + }; + + + self.defer.now = 0; + + + self.defer.cancel = function(deferId) { + var fnIndex; + + angular.forEach(self.deferredFns, function(fn, index) { + if (fn.id === deferId) fnIndex = index; + }); + + if (fnIndex !== undefined) { + self.deferredFns.splice(fnIndex, 1); + return true; + } + + return false; + }; + + + /** + * @name ngMock.$browser#defer.flush + * @methodOf ngMock.$browser + * + * @description + * Flushes all pending requests and executes the defer callbacks. + * + * @param {number=} number of milliseconds to flush. See {@link #defer.now} + */ + self.defer.flush = function(delay) { + if (angular.isDefined(delay)) { + self.defer.now += delay; + } else { + if (self.deferredFns.length) { + self.defer.now = self.deferredFns[self.deferredFns.length-1].time; + } else { + throw Error('No deferred tasks to be flushed'); + } + } + + while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { + self.deferredFns.shift().fn(); + } + }; + /** + * @name ngMock.$browser#defer.now + * @propertyOf ngMock.$browser + * + * @description + * Current milliseconds mock time. + */ + + self.$$baseHref = ''; + self.baseHref = function() { + return this.$$baseHref; + }; +}; +angular.mock.$Browser.prototype = { + +/** + * @name ngMock.$browser#poll + * @methodOf ngMock.$browser + * + * @description + * run all fns in pollFns + */ + poll: function poll() { + angular.forEach(this.pollFns, function(pollFn){ + pollFn(); + }); + }, + + addPollFn: function(pollFn) { + this.pollFns.push(pollFn); + return pollFn; + }, + + url: function(url, replace) { + if (url) { + this.$$url = url; + return this; + } + + return this.$$url; + }, + + cookies: function(name, value) { + if (name) { + if (value == undefined) { + delete this.cookieHash[name]; + } else { + if (angular.isString(value) && //strings only + value.length <= 4096) { //strict cookie storage limits + this.cookieHash[name] = value; + } + } + } else { + if (!angular.equals(this.cookieHash, this.lastCookieHash)) { + this.lastCookieHash = angular.copy(this.cookieHash); + this.cookieHash = angular.copy(this.cookieHash); + } + return this.cookieHash; + } + }, + + notifyWhenNoOutstandingRequests: function(fn) { + fn(); + } +}; + + +/** + * @ngdoc object + * @name ngMock.$exceptionHandlerProvider + * + * @description + * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed + * into the `$exceptionHandler`. + */ + +/** + * @ngdoc object + * @name ngMock.$exceptionHandler + * + * @description + * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed + * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration + * information. + * + * + *
        + *   describe('$exceptionHandlerProvider', function() {
        + *
        + *     it('should capture log messages and exceptions', function() {
        + *
        + *       module(function($exceptionHandlerProvider) {
        + *         $exceptionHandlerProvider.mode('log');
        + *       });
        + *
        + *       inject(function($log, $exceptionHandler, $timeout) {
        + *         $timeout(function() { $log.log(1); });
        + *         $timeout(function() { $log.log(2); throw 'banana peel'; });
        + *         $timeout(function() { $log.log(3); });
        + *         expect($exceptionHandler.errors).toEqual([]);
        + *         expect($log.assertEmpty());
        + *         $timeout.flush();
        + *         expect($exceptionHandler.errors).toEqual(['banana peel']);
        + *         expect($log.log.logs).toEqual([[1], [2], [3]]);
        + *       });
        + *     });
        + *   });
        + * 
        + */ + +angular.mock.$ExceptionHandlerProvider = function() { + var handler; + + /** + * @ngdoc method + * @name ngMock.$exceptionHandlerProvider#mode + * @methodOf ngMock.$exceptionHandlerProvider + * + * @description + * Sets the logging mode. + * + * @param {string} mode Mode of operation, defaults to `rethrow`. + * + * - `rethrow`: If any errors are passed into the handler in tests, it typically + * means that there is a bug in the application or test, so this mock will + * make these tests fail. + * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an + * array of errors in `$exceptionHandler.errors`, to allow later assertion of them. + * See {@link ngMock.$log#assertEmpty assertEmpty()} and + * {@link ngMock.$log#reset reset()} + */ + this.mode = function(mode) { + switch(mode) { + case 'rethrow': + handler = function(e) { + throw e; + }; + break; + case 'log': + var errors = []; + + handler = function(e) { + if (arguments.length == 1) { + errors.push(e); + } else { + errors.push([].slice.call(arguments, 0)); + } + }; + + handler.errors = errors; + break; + default: + throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); + } + }; + + this.$get = function() { + return handler; + }; + + this.mode('rethrow'); +}; + + +/** + * @ngdoc service + * @name ngMock.$log + * + * @description + * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays + * (one array per logging level). These arrays are exposed as `logs` property of each of the + * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. + * + */ +angular.mock.$LogProvider = function() { + + function concat(array1, array2, index) { + return array1.concat(Array.prototype.slice.call(array2, index)); + } + + + this.$get = function () { + var $log = { + log: function() { $log.log.logs.push(concat([], arguments, 0)); }, + warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, + info: function() { $log.info.logs.push(concat([], arguments, 0)); }, + error: function() { $log.error.logs.push(concat([], arguments, 0)); } + }; + + /** + * @ngdoc method + * @name ngMock.$log#reset + * @methodOf ngMock.$log + * + * @description + * Reset all of the logging arrays to empty. + */ + $log.reset = function () { + /** + * @ngdoc property + * @name ngMock.$log#log.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#log}. + * + * @example + *
        +       * $log.log('Some Log');
        +       * var first = $log.log.logs.unshift();
        +       * 
        + */ + $log.log.logs = []; + /** + * @ngdoc property + * @name ngMock.$log#warn.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#warn}. + * + * @example + *
        +       * $log.warn('Some Warning');
        +       * var first = $log.warn.logs.unshift();
        +       * 
        + */ + $log.warn.logs = []; + /** + * @ngdoc property + * @name ngMock.$log#info.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#info}. + * + * @example + *
        +       * $log.info('Some Info');
        +       * var first = $log.info.logs.unshift();
        +       * 
        + */ + $log.info.logs = []; + /** + * @ngdoc property + * @name ngMock.$log#error.logs + * @propertyOf ngMock.$log + * + * @description + * Array of messages logged using {@link ngMock.$log#error}. + * + * @example + *
        +       * $log.log('Some Error');
        +       * var first = $log.error.logs.unshift();
        +       * 
        + */ + $log.error.logs = []; + }; + + /** + * @ngdoc method + * @name ngMock.$log#assertEmpty + * @methodOf ngMock.$log + * + * @description + * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown. + */ + $log.assertEmpty = function() { + var errors = []; + angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { + angular.forEach($log[logLevel].logs, function(log) { + angular.forEach(log, function (logItem) { + errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); + }); + }); + }); + if (errors.length) { + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " + + "log message was not checked and removed:"); + errors.push(''); + throw new Error(errors.join('\n---------\n')); + } + }; + + $log.reset(); + return $log; + }; +}; + + +(function() { + var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; + + function jsonStringToDate(string) { + var match; + if (match = string.match(R_ISO8061_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0; + if (match[9]) { + tzHour = int(match[9] + match[10]); + tzMin = int(match[9] + match[11]); + } + date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); + date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); + return date; + } + return string; + } + + function int(str) { + return parseInt(str, 10); + } + + function padNumber(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while(num.length < digits) num = '0' + num; + if (trim) + num = num.substr(num.length - digits); + return neg + num; + } + + + /** + * @ngdoc object + * @name angular.mock.TzDate + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. + * + * Mock of the Date type which has its timezone specified via constructor arg. + * + * The main purpose is to create Date-like instances with timezone fixed to the specified timezone + * offset, so that we can test code that depends on local timezone settings without dependency on + * the time zone settings of the machine where the code is running. + * + * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) + * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* + * + * @example + * !!!! WARNING !!!!! + * This is not a complete Date object so only methods that were implemented can be called safely. + * To make matters worse, TzDate instances inherit stuff from Date via a prototype. + * + * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is + * incomplete we might be missing some non-standard methods. This can result in errors like: + * "Date.prototype.foo called on incompatible Object". + * + *
        +   * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
        +   * newYearInBratislava.getTimezoneOffset() => -60;
        +   * newYearInBratislava.getFullYear() => 2010;
        +   * newYearInBratislava.getMonth() => 0;
        +   * newYearInBratislava.getDate() => 1;
        +   * newYearInBratislava.getHours() => 0;
        +   * newYearInBratislava.getMinutes() => 0;
        +   * 
        + * + */ + angular.mock.TzDate = function (offset, timestamp) { + var self = new Date(0); + if (angular.isString(timestamp)) { + var tsStr = timestamp; + + self.origDate = jsonStringToDate(timestamp); + + timestamp = self.origDate.getTime(); + if (isNaN(timestamp)) + throw { + name: "Illegal Argument", + message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" + }; + } else { + self.origDate = new Date(timestamp); + } + + var localOffset = new Date(timestamp).getTimezoneOffset(); + self.offsetDiff = localOffset*60*1000 - offset*1000*60*60; + self.date = new Date(timestamp + self.offsetDiff); + + self.getTime = function() { + return self.date.getTime() - self.offsetDiff; + }; + + self.toLocaleDateString = function() { + return self.date.toLocaleDateString(); + }; + + self.getFullYear = function() { + return self.date.getFullYear(); + }; + + self.getMonth = function() { + return self.date.getMonth(); + }; + + self.getDate = function() { + return self.date.getDate(); + }; + + self.getHours = function() { + return self.date.getHours(); + }; + + self.getMinutes = function() { + return self.date.getMinutes(); + }; + + self.getSeconds = function() { + return self.date.getSeconds(); + }; + + self.getTimezoneOffset = function() { + return offset * 60; + }; + + self.getUTCFullYear = function() { + return self.origDate.getUTCFullYear(); + }; + + self.getUTCMonth = function() { + return self.origDate.getUTCMonth(); + }; + + self.getUTCDate = function() { + return self.origDate.getUTCDate(); + }; + + self.getUTCHours = function() { + return self.origDate.getUTCHours(); + }; + + self.getUTCMinutes = function() { + return self.origDate.getUTCMinutes(); + }; + + self.getUTCSeconds = function() { + return self.origDate.getUTCSeconds(); + }; + + self.getUTCMilliseconds = function() { + return self.origDate.getUTCMilliseconds(); + }; + + self.getDay = function() { + return self.date.getDay(); + }; + + // provide this method only on browsers that already have it + if (self.toISOString) { + self.toISOString = function() { + return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + + padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + + padNumber(self.origDate.getUTCDate(), 2) + 'T' + + padNumber(self.origDate.getUTCHours(), 2) + ':' + + padNumber(self.origDate.getUTCMinutes(), 2) + ':' + + padNumber(self.origDate.getUTCSeconds(), 2) + '.' + + padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z' + } + } + + //hide all methods not implemented in this mock that the Date prototype exposes + var unimplementedMethods = ['getMilliseconds', 'getUTCDay', + 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', + 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', + 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', + 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', + 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; + + angular.forEach(unimplementedMethods, function(methodName) { + self[methodName] = function() { + throw Error("Method '" + methodName + "' is not implemented in the TzDate mock"); + }; + }); + + return self; + }; + + //make "tzDateInstance instanceof Date" return true + angular.mock.TzDate.prototype = Date.prototype; +})(); + + +/** + * @ngdoc function + * @name angular.mock.dump + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available function. + * + * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging. + * + * This method is also available on window, where it can be used to display objects on debug console. + * + * @param {*} object - any object to turn into string. + * @return {string} a serialized string of the argument + */ +angular.mock.dump = function(object) { + return serialize(object); + + function serialize(object) { + var out; + + if (angular.isElement(object)) { + object = angular.element(object); + out = angular.element('
        '); + angular.forEach(object, function(element) { + out.append(angular.element(element).clone()); + }); + out = out.html(); + } else if (angular.isArray(object)) { + out = []; + angular.forEach(object, function(o) { + out.push(serialize(o)); + }); + out = '[ ' + out.join(', ') + ' ]'; + } else if (angular.isObject(object)) { + if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { + out = serializeScope(object); + } else if (object instanceof Error) { + out = object.stack || ('' + object.name + ': ' + object.message); + } else { + out = angular.toJson(object, true); + } + } else { + out = String(object); + } + + return out; + } + + function serializeScope(scope, offset) { + offset = offset || ' '; + var log = [offset + 'Scope(' + scope.$id + '): {']; + for ( var key in scope ) { + if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { + log.push(' ' + key + ': ' + angular.toJson(scope[key])); + } + } + var child = scope.$$childHead; + while(child) { + log.push(serializeScope(child, offset + ' ')); + child = child.$$nextSibling; + } + log.push('}'); + return log.join('\n' + offset); + } +}; + +/** + * @ngdoc object + * @name ngMock.$httpBackend + * @description + * Fake HTTP backend implementation suitable for unit testing applications that use the + * {@link ng.$http $http service}. + * + * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less + * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. + * + * During unit testing, we want our unit tests to run quickly and have no external dependencies so + * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or + * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is + * to verify whether a certain request has been sent or not, or alternatively just let the + * application make requests, respond with pre-trained responses and assert that the end result is + * what we expect it to be. + * + * This mock implementation can be used to respond with static or dynamic responses via the + * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). + * + * When an Angular application needs some data from a server, it calls the $http service, which + * sends the request to a real server using $httpBackend service. With dependency injection, it is + * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify + * the requests and respond with some testing data without sending a request to real server. + * + * There are two ways to specify what test data should be returned as http responses by the mock + * backend when the code under test makes http requests: + * + * - `$httpBackend.expect` - specifies a request expectation + * - `$httpBackend.when` - specifies a backend definition + * + * + * # Request Expectations vs Backend Definitions + * + * Request expectations provide a way to make assertions about requests made by the application and + * to define responses for those requests. The test will fail if the expected requests are not made + * or they are made in the wrong order. + * + * Backend definitions allow you to define a fake backend for your application which doesn't assert + * if a particular request was made or not, it just returns a trained response if a request is made. + * The test will pass whether or not the request gets made during testing. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
        Request expectationsBackend definitions
        Syntax.expect(...).respond(...).when(...).respond(...)
        Typical usagestrict unit testsloose (black-box) unit testing
        Fulfills multiple requestsNOYES
        Order of requests mattersYESNO
        Request requiredYESNO
        Response requiredoptional (see below)YES
        + * + * In cases where both backend definitions and request expectations are specified during unit + * testing, the request expectations are evaluated first. + * + * If a request expectation has no response specified, the algorithm will search your backend + * definitions for an appropriate response. + * + * If a request didn't match any expectation or if the expectation doesn't have the response + * defined, the backend definitions are evaluated in sequential order to see if any of them match + * the request. The response from the first matched definition is returned. + * + * + * # Flushing HTTP requests + * + * The $httpBackend used in production, always responds to requests with responses asynchronously. + * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are + * hard to write, follow and maintain. At the same time the testing mock, can't respond + * synchronously because that would change the execution of the code under test. For this reason the + * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending + * requests and thus preserving the async api of the backend, while allowing the test to execute + * synchronously. + * + * + * # Unit testing with mock $httpBackend + * The following code shows how to setup and use the mock backend in unit testing a controller. + * First we create the controller under test + * +
        +  // The controller code
        +  function MyController($scope, $http) {
        +    var authToken;
        +
        +    $http.get('/auth.py').success(function(data, status, headers) {
        +      authToken = headers('A-Token');
        +      $scope.user = data;
        +    });
        +
        +    $scope.saveMessage = function(message) {
        +      var headers = { 'Authorization': authToken };
        +      $scope.status = 'Saving...';
        +
        +      $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) {
        +        $scope.status = '';
        +      }).error(function() {
        +        $scope.status = 'ERROR!';
        +      });
        +    };
        +  }
        +  
        + * + * Now we setup the mock backend and create the test specs. + * +
        +    // testing controller
        +    describe('MyController', function() {
        +       var $httpBackend, $rootScope, createController;
        +
        +       beforeEach(inject(function($injector) {
        +         // Set up the mock http service responses
        +         $httpBackend = $injector.get('$httpBackend');
        +         // backend definition common for all tests
        +         $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
        +
        +         // Get hold of a scope (i.e. the root scope)
        +         $rootScope = $injector.get('$rootScope');
        +         // The $controller service is used to create instances of controllers
        +         var $controller = $injector.get('$controller');
        +
        +         createController = function() {
        +           return $controller('MyController', {'$scope' : $rootScope });
        +         };
        +       }));
        +
        +
        +       afterEach(function() {
        +         $httpBackend.verifyNoOutstandingExpectation();
        +         $httpBackend.verifyNoOutstandingRequest();
        +       });
        +
        +
        +       it('should fetch authentication token', function() {
        +         $httpBackend.expectGET('/auth.py');
        +         var controller = createController();
        +         $httpBackend.flush();
        +       });
        +
        +
        +       it('should send msg to server', function() {
        +         var controller = createController();
        +         $httpBackend.flush();
        +
        +         // now you don’t care about the authentication, but
        +         // the controller will still send the request and
        +         // $httpBackend will respond without you having to
        +         // specify the expectation and response for this request
        +
        +         $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
        +         $rootScope.saveMessage('message content');
        +         expect($rootScope.status).toBe('Saving...');
        +         $httpBackend.flush();
        +         expect($rootScope.status).toBe('');
        +       });
        +
        +
        +       it('should send auth header', function() {
        +         var controller = createController();
        +         $httpBackend.flush();
        +
        +         $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
        +           // check if the header was send, if it wasn't the expectation won't
        +           // match the request and the test will fail
        +           return headers['Authorization'] == 'xxx';
        +         }).respond(201, '');
        +
        +         $rootScope.saveMessage('whatever');
        +         $httpBackend.flush();
        +       });
        +    });
        +   
        + */ +angular.mock.$HttpBackendProvider = function() { + this.$get = [createHttpBackendMock]; +}; + +/** + * General factory function for $httpBackend mock. + * Returns instance for unit testing (when no arguments specified): + * - passing through is disabled + * - auto flushing is disabled + * + * Returns instance for e2e testing (when `$delegate` and `$browser` specified): + * - passing through (delegating request to real backend) is enabled + * - auto flushing is enabled + * + * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) + * @param {Object=} $browser Auto-flushing enabled if specified + * @return {Object} Instance of $httpBackend mock + */ +function createHttpBackendMock($delegate, $browser) { + var definitions = [], + expectations = [], + responses = [], + responsesPush = angular.bind(responses, responses.push); + + function createResponse(status, data, headers) { + if (angular.isFunction(status)) return status; + + return function() { + return angular.isNumber(status) + ? [status, data, headers] + : [200, status, data]; + }; + } + + // TODO(vojta): change params to: method, url, data, headers, callback + function $httpBackend(method, url, data, callback, headers) { + var xhr = new MockXhr(), + expectation = expectations[0], + wasExpected = false; + + function prettyPrint(data) { + return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) + ? data + : angular.toJson(data); + } + + if (expectation && expectation.match(method, url)) { + if (!expectation.matchData(data)) + throw Error('Expected ' + expectation + ' with different data\n' + + 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); + + if (!expectation.matchHeaders(headers)) + throw Error('Expected ' + expectation + ' with different headers\n' + + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + + prettyPrint(headers)); + + expectations.shift(); + + if (expectation.response) { + responses.push(function() { + var response = expectation.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(response[0], response[1], xhr.getAllResponseHeaders()); + }); + return; + } + wasExpected = true; + } + + var i = -1, definition; + while ((definition = definitions[++i])) { + if (definition.match(method, url, data, headers || {})) { + if (definition.response) { + // if $browser specified, we do auto flush all requests + ($browser ? $browser.defer : responsesPush)(function() { + var response = definition.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(response[0], response[1], xhr.getAllResponseHeaders()); + }); + } else if (definition.passThrough) { + $delegate(method, url, data, callback, headers); + } else throw Error('No response defined !'); + return; + } + } + throw wasExpected ? + Error('No response defined !') : + Error('Unexpected request: ' + method + ' ' + url + '\n' + + (expectation ? 'Expected ' + expectation : 'No more request expected')); + } + + /** + * @ngdoc method + * @name ngMock.$httpBackend#when + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + */ + $httpBackend.when = function(method, url, data, headers) { + var definition = new MockHttpExpectation(method, url, data, headers), + chain = { + respond: function(status, data, headers) { + definition.response = createResponse(status, data, headers); + } + }; + + if ($browser) { + chain.passThrough = function() { + definition.passThrough = true; + }; + } + + definitions.push(definition); + return chain; + }; + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenGET + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenHEAD + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenDELETE + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenPOST + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenPUT + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#whenJSONP + * @methodOf ngMock.$httpBackend + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + createShortMethods('when'); + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expect + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current expectation. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + */ + $httpBackend.expect = function(method, url, data, headers) { + var expectation = new MockHttpExpectation(method, url, data, headers); + expectations.push(expectation); + return { + respond: function(status, data, headers) { + expectation.response = createResponse(status, data, headers); + } + }; + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectGET + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for GET requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. See #expect for more info. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectHEAD + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for HEAD requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectDELETE + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for DELETE requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectPOST + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for POST requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectPUT + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for PUT requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectPATCH + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for PATCH requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name ngMock.$httpBackend#expectJSONP + * @methodOf ngMock.$httpBackend + * @description + * Creates a new request expectation for JSONP requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + createShortMethods('expect'); + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#flush + * @methodOf ngMock.$httpBackend + * @description + * Flushes all pending requests using the trained responses. + * + * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, + * all pending requests will be flushed. If there are no pending requests when the flush method + * is called an exception is thrown (as this typically a sign of programming error). + */ + $httpBackend.flush = function(count) { + if (!responses.length) throw Error('No pending request to flush !'); + + if (angular.isDefined(count)) { + while (count--) { + if (!responses.length) throw Error('No more pending request to flush !'); + responses.shift()(); + } + } else { + while (responses.length) { + responses.shift()(); + } + } + $httpBackend.verifyNoOutstandingExpectation(); + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#verifyNoOutstandingExpectation + * @methodOf ngMock.$httpBackend + * @description + * Verifies that all of the requests defined via the `expect` api were made. If any of the + * requests were not made, verifyNoOutstandingExpectation throws an exception. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + *
        +   *   afterEach($httpBackend.verifyNoOutstandingExpectation);
        +   * 
        + */ + $httpBackend.verifyNoOutstandingExpectation = function() { + if (expectations.length) { + throw Error('Unsatisfied requests: ' + expectations.join(', ')); + } + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#verifyNoOutstandingRequest + * @methodOf ngMock.$httpBackend + * @description + * Verifies that there are no outstanding requests that need to be flushed. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + *
        +   *   afterEach($httpBackend.verifyNoOutstandingRequest);
        +   * 
        + */ + $httpBackend.verifyNoOutstandingRequest = function() { + if (responses.length) { + throw Error('Unflushed requests: ' + responses.length); + } + }; + + + /** + * @ngdoc method + * @name ngMock.$httpBackend#resetExpectations + * @methodOf ngMock.$httpBackend + * @description + * Resets all request expectations, but preserves all backend definitions. Typically, you would + * call resetExpectations during a multiple-phase test when you want to reuse the same instance of + * $httpBackend mock. + */ + $httpBackend.resetExpectations = function() { + expectations.length = 0; + responses.length = 0; + }; + + return $httpBackend; + + + function createShortMethods(prefix) { + angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) { + $httpBackend[prefix + method] = function(url, headers) { + return $httpBackend[prefix](method, url, undefined, headers) + } + }); + + angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { + $httpBackend[prefix + method] = function(url, data, headers) { + return $httpBackend[prefix](method, url, data, headers) + } + }); + } +} + +function MockHttpExpectation(method, url, data, headers) { + + this.data = data; + this.headers = headers; + + this.match = function(m, u, d, h) { + if (method != m) return false; + if (!this.matchUrl(u)) return false; + if (angular.isDefined(d) && !this.matchData(d)) return false; + if (angular.isDefined(h) && !this.matchHeaders(h)) return false; + return true; + }; + + this.matchUrl = function(u) { + if (!url) return true; + if (angular.isFunction(url.test)) return url.test(u); + return url == u; + }; + + this.matchHeaders = function(h) { + if (angular.isUndefined(headers)) return true; + if (angular.isFunction(headers)) return headers(h); + return angular.equals(headers, h); + }; + + this.matchData = function(d) { + if (angular.isUndefined(data)) return true; + if (data && angular.isFunction(data.test)) return data.test(d); + if (data && !angular.isString(data)) return angular.toJson(data) == d; + return data == d; + }; + + this.toString = function() { + return method + ' ' + url; + }; +} + +function MockXhr() { + + // hack for testing $http, $httpBackend + MockXhr.$$lastInstance = this; + + this.open = function(method, url, async) { + this.$$method = method; + this.$$url = url; + this.$$async = async; + this.$$reqHeaders = {}; + this.$$respHeaders = {}; + }; + + this.send = function(data) { + this.$$data = data; + }; + + this.setRequestHeader = function(key, value) { + this.$$reqHeaders[key] = value; + }; + + this.getResponseHeader = function(name) { + // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last + var header = this.$$respHeaders[name]; + if (header) return header; + + name = angular.lowercase(name); + header = this.$$respHeaders[name]; + if (header) return header; + + header = undefined; + angular.forEach(this.$$respHeaders, function(headerVal, headerName) { + if (!header && angular.lowercase(headerName) == name) header = headerVal; + }); + return header; + }; + + this.getAllResponseHeaders = function() { + var lines = []; + + angular.forEach(this.$$respHeaders, function(value, key) { + lines.push(key + ': ' + value); + }); + return lines.join('\n'); + }; + + this.abort = angular.noop; +} + + +/** + * @ngdoc function + * @name ngMock.$timeout + * @description + * + * This service is just a simple decorator for {@link ng.$timeout $timeout} service + * that adds a "flush" method. + */ + +/** + * @ngdoc method + * @name ngMock.$timeout#flush + * @methodOf ngMock.$timeout + * @description + * + * Flushes the queue of pending tasks. + */ + +/** + * + */ +angular.mock.$RootElementProvider = function() { + this.$get = function() { + return angular.element('
        '); + } +}; + +/** + * @ngdoc overview + * @name ngMock + * @description + * + * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful + * mocks to the {@link AUTO.$injector $injector}. + */ +angular.module('ngMock', ['ng']).provider({ + $browser: angular.mock.$BrowserProvider, + $exceptionHandler: angular.mock.$ExceptionHandlerProvider, + $log: angular.mock.$LogProvider, + $httpBackend: angular.mock.$HttpBackendProvider, + $rootElement: angular.mock.$RootElementProvider +}).config(function($provide) { + $provide.decorator('$timeout', function($delegate, $browser) { + $delegate.flush = function(delay) { + $browser.defer.flush(delay); + }; + return $delegate; + }); +}); + + +/** + * @ngdoc overview + * @name ngMockE2E + * @description + * + * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. + * Currently there is only one mock present in this module - + * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. + */ +angular.module('ngMockE2E', ['ng']).config(function($provide) { + $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); +}); + +/** + * @ngdoc object + * @name ngMockE2E.$httpBackend + * @description + * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of + * applications that use the {@link ng.$http $http service}. + * + * *Note*: For fake http backend implementation suitable for unit testing please see + * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. + * + * This implementation can be used to respond with static or dynamic responses via the `when` api + * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the + * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch + * templates from a webserver). + * + * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application + * is being developed with the real backend api replaced with a mock, it is often desirable for + * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch + * templates or static files from the webserver). To configure the backend with this behavior + * use the `passThrough` request handler of `when` instead of `respond`. + * + * Additionally, we don't want to manually have to flush mocked out requests like we do during unit + * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests + * automatically, closely simulating the behavior of the XMLHttpRequest object. + * + * To setup the application to run with this http backend, you have to create a module that depends + * on the `ngMockE2E` and your application modules and defines the fake backend: + * + *
        + *   myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
        + *   myAppDev.run(function($httpBackend) {
        + *     phones = [{name: 'phone1'}, {name: 'phone2'}];
        + *
        + *     // returns the current list of phones
        + *     $httpBackend.whenGET('/phones').respond(phones);
        + *
        + *     // adds a new phone to the phones array
        + *     $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
        + *       phones.push(angular.fromJson(data));
        + *     });
        + *     $httpBackend.whenGET(/^\/templates\//).passThrough();
        + *     //...
        + *   });
        + * 
        + * + * Afterwards, bootstrap your app with this new module. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#when + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` + * handler, will be pass through to the real backend (an XHR request will be made to the + * server. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenGET + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenHEAD + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenDELETE + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenPOST + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenPUT + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenPATCH + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for PATCH requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name ngMockE2E.$httpBackend#whenJSONP + * @methodOf ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ +angular.mock.e2e = {}; +angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock]; + + +angular.mock.clearDataCache = function() { + var key, + cache = angular.element.cache; + + for(key in cache) { + if (cache.hasOwnProperty(key)) { + var handle = cache[key].handle; + + handle && angular.element(handle.elem).unbind(); + delete cache[key]; + } + } +}; + + + +window.jasmine && (function(window) { + + afterEach(function() { + var spec = getCurrentSpec(); + var injector = spec.$injector; + + spec.$injector = null; + spec.$modules = null; + + if (injector) { + injector.get('$rootElement').unbind(); + injector.get('$browser').pollFns.length = 0; + } + + angular.mock.clearDataCache(); + + // clean up jquery's fragment cache + angular.forEach(angular.element.fragments, function(val, key) { + delete angular.element.fragments[key]; + }); + + MockXhr.$$lastInstance = null; + + angular.forEach(angular.callbacks, function(val, key) { + delete angular.callbacks[key]; + }); + angular.callbacks.counter = 0; + }); + + function getCurrentSpec() { + return jasmine.getEnv().currentSpec; + } + + function isSpecRunning() { + var spec = getCurrentSpec(); + return spec && spec.queue.running; + } + + /** + * @ngdoc function + * @name angular.mock.module + * @description + * + * *NOTE*: This function is also published on window for easy access.
        + * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. + * + * This function registers a module configuration code. It collects the configuration information + * which will be used when the injector is created by {@link angular.mock.inject inject}. + * + * See {@link angular.mock.inject inject} for usage example + * + * @param {...(string|Function)} fns any number of modules which are represented as string + * aliases or as anonymous module initialization functions. The modules are used to + * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. + */ + window.module = angular.mock.module = function() { + var moduleFns = Array.prototype.slice.call(arguments, 0); + return isSpecRunning() ? workFn() : workFn; + ///////////////////// + function workFn() { + var spec = getCurrentSpec(); + if (spec.$injector) { + throw Error('Injector already created, can not register a module!'); + } else { + var modules = spec.$modules || (spec.$modules = []); + angular.forEach(moduleFns, function(module) { + modules.push(module); + }); + } + } + }; + + /** + * @ngdoc function + * @name angular.mock.inject + * @description + * + * *NOTE*: This function is also published on window for easy access.
        + * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}. + * + * The inject function wraps a function into an injectable function. The inject() creates new + * instance of {@link AUTO.$injector $injector} per test, which is then used for + * resolving references. + * + * See also {@link angular.mock.module module} + * + * Example of what a typical jasmine tests looks like with the inject method. + *
        +   *
        +   *   angular.module('myApplicationModule', [])
        +   *       .value('mode', 'app')
        +   *       .value('version', 'v1.0.1');
        +   *
        +   *
        +   *   describe('MyApp', function() {
        +   *
        +   *     // You need to load modules that you want to test,
        +   *     // it loads only the "ng" module by default.
        +   *     beforeEach(module('myApplicationModule'));
        +   *
        +   *
        +   *     // inject() is used to inject arguments of all given functions
        +   *     it('should provide a version', inject(function(mode, version) {
        +   *       expect(version).toEqual('v1.0.1');
        +   *       expect(mode).toEqual('app');
        +   *     }));
        +   *
        +   *
        +   *     // The inject and module method can also be used inside of the it or beforeEach
        +   *     it('should override a version and test the new version is injected', function() {
        +   *       // module() takes functions or strings (module aliases)
        +   *       module(function($provide) {
        +   *         $provide.value('version', 'overridden'); // override version here
        +   *       });
        +   *
        +   *       inject(function(version) {
        +   *         expect(version).toEqual('overridden');
        +   *       });
        +   *     ));
        +   *   });
        +   *
        +   * 
        + * + * @param {...Function} fns any number of functions which will be injected using the injector. + */ + window.inject = angular.mock.inject = function() { + var blockFns = Array.prototype.slice.call(arguments, 0); + var errorForStack = new Error('Declaration Location'); + return isSpecRunning() ? workFn() : workFn; + ///////////////////// + function workFn() { + var spec = getCurrentSpec(); + var modules = spec.$modules || []; + modules.unshift('ngMock'); + modules.unshift('ng'); + var injector = spec.$injector; + if (!injector) { + injector = spec.$injector = angular.injector(modules); + } + for(var i = 0, ii = blockFns.length; i < ii; i++) { + try { + injector.invoke(blockFns[i] || angular.noop, this); + } catch (e) { + if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack; + throw e; + } finally { + errorForStack = null; + } + } + } + }; +})(window); diff --git a/bower_components/angular-mocks/bower.json b/bower_components/angular-mocks/bower.json new file mode 100644 index 0000000..6e729b6 --- /dev/null +++ b/bower_components/angular-mocks/bower.json @@ -0,0 +1,8 @@ +{ + "name": "angular-mocks", + "version": "1.0.8", + "main": "./angular-mocks.js", + "dependencies": { + "angular": "1.0.8" + } +} diff --git a/bower_components/angular-resource/README.md b/bower_components/angular-resource/README.md new file mode 100644 index 0000000..fa67e70 --- /dev/null +++ b/bower_components/angular-resource/README.md @@ -0,0 +1,4 @@ +bower-angular-resource +====================== + +angular-resource bower repo \ No newline at end of file diff --git a/bower_components/angular-resource/angular-resource.js b/bower_components/angular-resource/angular-resource.js new file mode 100755 index 0000000..f29b379 --- /dev/null +++ b/bower_components/angular-resource/angular-resource.js @@ -0,0 +1,457 @@ +/** + * @license AngularJS v1.0.8 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) { +'use strict'; + +/** + * @ngdoc overview + * @name ngResource + * @description + */ + +/** + * @ngdoc object + * @name ngResource.$resource + * @requires $http + * + * @description + * A factory which creates a resource object that lets you interact with + * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. + * + * The returned resource object has action methods which provide high-level behaviors without + * the need to interact with the low level {@link ng.$http $http} service. + * + * # Installation + * To use $resource make sure you have included the `angular-resource.js` that comes in Angular + * package. You can also find this file on Google CDN, bower as well as at + * {@link http://code.angularjs.org/ code.angularjs.org}. + * + * Finally load the module in your application: + * + * angular.module('app', ['ngResource']); + * + * and you are ready to get started! + * + * @param {string} url A parameterized URL template with parameters prefixed by `:` as in + * `/user/:username`. If you are using a URL with a port number (e.g. + * `http://example.com:8080/api`), you'll need to escape the colon character before the port + * number, like this: `$resource('http://example.com\\:8080/api')`. + * + * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in + * `actions` methods. + * + * Each key value in the parameter object is first bound to url template if present and then any + * excess keys are appended to the url search query after the `?`. + * + * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in + * URL `/path/greet?salutation=Hello`. + * + * If the parameter value is prefixed with `@` then the value of that parameter is extracted from + * the data object (useful for non-GET operations). + * + * @param {Object.=} actions Hash with declaration of custom action that should extend the + * default set of resource actions. The declaration should be created in the following format: + * + * {action1: {method:?, params:?, isArray:?}, + * action2: {method:?, params:?, isArray:?}, + * ...} + * + * Where: + * + * - `action` – {string} – The name of action. This name becomes the name of the method on your + * resource object. + * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, + * and `JSONP` + * - `params` – {object=} – Optional set of pre-bound parameters for this action. + * - isArray – {boolean=} – If true then the returned object for this action is an array, see + * `returns` section. + * + * @returns {Object} A resource "class" object with methods for the default set of resource actions + * optionally extended with custom `actions`. The default set contains these actions: + * + * { 'get': {method:'GET'}, + * 'save': {method:'POST'}, + * 'query': {method:'GET', isArray:true}, + * 'remove': {method:'DELETE'}, + * 'delete': {method:'DELETE'} }; + * + * Calling these methods invoke an {@link ng.$http} with the specified http method, + * destination and parameters. When the data is returned from the server then the object is an + * instance of the resource class. The actions `save`, `remove` and `delete` are available on it + * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, + * read, update, delete) on server-side data like this: + *
        +        var User = $resource('/user/:userId', {userId:'@id'});
        +        var user = User.get({userId:123}, function() {
        +          user.abc = true;
        +          user.$save();
        +        });
        +     
        + * + * It is important to realize that invoking a $resource object method immediately returns an + * empty reference (object or array depending on `isArray`). Once the data is returned from the + * server the existing reference is populated with the actual data. This is a useful trick since + * usually the resource is assigned to a model which is then rendered by the view. Having an empty + * object results in no rendering, once the data arrives from the server then the object is + * populated with the data and the view automatically re-renders itself showing the new data. This + * means that in most case one never has to write a callback function for the action methods. + * + * The action methods on the class object or instance object can be invoked with the following + * parameters: + * + * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` + * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` + * - non-GET instance actions: `instance.$action([parameters], [success], [error])` + * + * + * @example + * + * # Credit card resource + * + *
        +     // Define CreditCard class
        +     var CreditCard = $resource('/user/:userId/card/:cardId',
        +      {userId:123, cardId:'@id'}, {
        +       charge: {method:'POST', params:{charge:true}}
        +      });
        +
        +     // We can retrieve a collection from the server
        +     var cards = CreditCard.query(function() {
        +       // GET: /user/123/card
        +       // server returns: [ {id:456, number:'1234', name:'Smith'} ];
        +
        +       var card = cards[0];
        +       // each item is an instance of CreditCard
        +       expect(card instanceof CreditCard).toEqual(true);
        +       card.name = "J. Smith";
        +       // non GET methods are mapped onto the instances
        +       card.$save();
        +       // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
        +       // server returns: {id:456, number:'1234', name: 'J. Smith'};
        +
        +       // our custom method is mapped as well.
        +       card.$charge({amount:9.99});
        +       // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
        +     });
        +
        +     // we can create an instance as well
        +     var newCard = new CreditCard({number:'0123'});
        +     newCard.name = "Mike Smith";
        +     newCard.$save();
        +     // POST: /user/123/card {number:'0123', name:'Mike Smith'}
        +     // server returns: {id:789, number:'01234', name: 'Mike Smith'};
        +     expect(newCard.id).toEqual(789);
        + * 
        + * + * The object returned from this function execution is a resource "class" which has "static" method + * for each action in the definition. + * + * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`. + * When the data is returned from the server then the object is an instance of the resource type and + * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD + * operations (create, read, update, delete) on server-side data. + +
        +     var User = $resource('/user/:userId', {userId:'@id'});
        +     var user = User.get({userId:123}, function() {
        +       user.abc = true;
        +       user.$save();
        +     });
        +   
        + * + * It's worth noting that the success callback for `get`, `query` and other method gets passed + * in the response that came from the server as well as $http header getter function, so one + * could rewrite the above example and get access to http headers as: + * +
        +     var User = $resource('/user/:userId', {userId:'@id'});
        +     User.get({userId:123}, function(u, getResponseHeaders){
        +       u.abc = true;
        +       u.$save(function(u, putResponseHeaders) {
        +         //u => saved user object
        +         //putResponseHeaders => $http header getter
        +       });
        +     });
        +   
        + + * # Buzz client + + Let's look at what a buzz client created with the `$resource` service looks like: + + + + +
        + + +
        +
        +

        + + {{item.actor.name}} + Expand replies: {{item.links.replies[0].count}} +

        + {{item.object.content | html}} +
        + + {{reply.actor.name}}: {{reply.content | html}} +
        +
        +
        +
        + + +
        + */ +angular.module('ngResource', ['ng']). + factory('$resource', ['$http', '$parse', function($http, $parse) { + var DEFAULT_ACTIONS = { + 'get': {method:'GET'}, + 'save': {method:'POST'}, + 'query': {method:'GET', isArray:true}, + 'remove': {method:'DELETE'}, + 'delete': {method:'DELETE'} + }; + var noop = angular.noop, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + isFunction = angular.isFunction, + getter = function(obj, path) { + return $parse(path)(obj); + }; + + /** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path + * segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); + } + + + /** + * This method is intended for encoding *key* or *value* parts of query component. We need a custom + * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be + * encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } + + function Route(template, defaults) { + this.template = template = template + '#'; + this.defaults = defaults || {}; + var urlParams = this.urlParams = {}; + forEach(template.split(/\W/), function(param){ + if (param && (new RegExp("(^|[^\\\\]):" + param + "\\W").test(template))) { + urlParams[param] = true; + } + }); + this.template = template.replace(/\\:/g, ':'); + } + + Route.prototype = { + url: function(params) { + var self = this, + url = this.template, + val, + encodedVal; + + params = params || {}; + forEach(this.urlParams, function(_, urlParam){ + val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; + if (angular.isDefined(val) && val !== null) { + encodedVal = encodeUriSegment(val); + url = url.replace(new RegExp(":" + urlParam + "(\\W)", "g"), encodedVal + "$1"); + } else { + url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W)", "g"), function(match, + leadingSlashes, tail) { + if (tail.charAt(0) == '/') { + return tail; + } else { + return leadingSlashes + tail; + } + }); + } + }); + url = url.replace(/\/?#$/, ''); + var query = []; + forEach(params, function(value, key){ + if (!self.urlParams[key]) { + query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value)); + } + }); + query.sort(); + url = url.replace(/\/*$/, ''); + return url + (query.length ? '?' + query.join('&') : ''); + } + }; + + + function ResourceFactory(url, paramDefaults, actions) { + var route = new Route(url); + + actions = extend({}, DEFAULT_ACTIONS, actions); + + function extractParams(data, actionParams){ + var ids = {}; + actionParams = extend({}, paramDefaults, actionParams); + forEach(actionParams, function(value, key){ + ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value; + }); + return ids; + } + + function Resource(value){ + copy(value || {}, this); + } + + forEach(actions, function(action, name) { + action.method = angular.uppercase(action.method); + var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH'; + Resource[name] = function(a1, a2, a3, a4) { + var params = {}; + var data; + var success = noop; + var error = null; + switch(arguments.length) { + case 4: + error = a4; + success = a3; + //fallthrough + case 3: + case 2: + if (isFunction(a2)) { + if (isFunction(a1)) { + success = a1; + error = a2; + break; + } + + success = a2; + error = a3; + //fallthrough + } else { + params = a1; + data = a2; + success = a3; + break; + } + case 1: + if (isFunction(a1)) success = a1; + else if (hasBody) data = a1; + else params = a1; + break; + case 0: break; + default: + throw "Expected between 0-4 arguments [params, data, success, error], got " + + arguments.length + " arguments."; + } + + var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data)); + $http({ + method: action.method, + url: route.url(extend({}, extractParams(data, action.params || {}), params)), + data: data + }).then(function(response) { + var data = response.data; + + if (data) { + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + value.push(new Resource(item)); + }); + } else { + copy(data, value); + } + } + (success||noop)(value, response.headers); + }, error); + + return value; + }; + + + Resource.prototype['$' + name] = function(a1, a2, a3) { + var params = extractParams(this), + success = noop, + error; + + switch(arguments.length) { + case 3: params = a1; success = a2; error = a3; break; + case 2: + case 1: + if (isFunction(a1)) { + success = a1; + error = a2; + } else { + params = a1; + success = a2 || noop; + } + case 0: break; + default: + throw "Expected between 1-3 arguments [params, success, error], got " + + arguments.length + " arguments."; + } + var data = hasBody ? this : undefined; + Resource[name].call(this, params, data, success, error); + }; + }); + + Resource.bind = function(additionalParamDefaults){ + return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); + }; + + return Resource; + } + + return ResourceFactory; + }]); + + +})(window, window.angular); diff --git a/bower_components/angular-resource/angular-resource.min.js b/bower_components/angular-resource/angular-resource.min.js new file mode 100755 index 0000000..d0223d9 --- /dev/null +++ b/bower_components/angular-resource/angular-resource.min.js @@ -0,0 +1,10 @@ +/* + AngularJS v1.0.8 + (c) 2010-2012 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(C,d,w){'use strict';d.module("ngResource",["ng"]).factory("$resource",["$http","$parse",function(x,y){function s(b,e){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,e?"%20":"+")}function t(b,e){this.template=b+="#";this.defaults=e||{};var a=this.urlParams={};h(b.split(/\W/),function(f){f&&RegExp("(^|[^\\\\]):"+f+"\\W").test(b)&&(a[f]=!0)});this.template=b.replace(/\\:/g,":")}function u(b,e,a){function f(m,a){var b= +{},a=o({},e,a);h(a,function(a,z){var c;a.charAt&&a.charAt(0)=="@"?(c=a.substr(1),c=y(c)(m)):c=a;b[z]=c});return b}function g(a){v(a||{},this)}var k=new t(b),a=o({},A,a);h(a,function(a,b){a.method=d.uppercase(a.method);var e=a.method=="POST"||a.method=="PUT"||a.method=="PATCH";g[b]=function(b,c,d,B){var j={},i,l=p,q=null;switch(arguments.length){case 4:q=B,l=d;case 3:case 2:if(r(c)){if(r(b)){l=b;q=c;break}l=c;q=d}else{j=b;i=c;l=d;break}case 1:r(b)?l=b:e?i=b:j=b;break;case 0:break;default:throw"Expected between 0-4 arguments [params, data, success, error], got "+ +arguments.length+" arguments.";}var n=this instanceof g?this:a.isArray?[]:new g(i);x({method:a.method,url:k.url(o({},f(i,a.params||{}),j)),data:i}).then(function(b){var c=b.data;if(c)a.isArray?(n.length=0,h(c,function(a){n.push(new g(a))})):v(c,n);(l||p)(n,b.headers)},q);return n};g.prototype["$"+b]=function(a,d,h){var m=f(this),j=p,i;switch(arguments.length){case 3:m=a;j=d;i=h;break;case 2:case 1:r(a)?(j=a,i=d):(m=a,j=d||p);case 0:break;default:throw"Expected between 1-3 arguments [params, success, error], got "+ +arguments.length+" arguments.";}g[b].call(this,m,e?this:w,j,i)}});g.bind=function(d){return u(b,o({},e,d),a)};return g}var A={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}},p=d.noop,h=d.forEach,o=d.extend,v=d.copy,r=d.isFunction;t.prototype={url:function(b){var e=this,a=this.template,f,g,b=b||{};h(this.urlParams,function(h,c){f=b.hasOwnProperty(c)?b[c]:e.defaults[c];d.isDefined(f)&&f!==null?(g=s(f,!0).replace(/%26/gi,"&").replace(/%3D/gi, +"=").replace(/%2B/gi,"+"),a=a.replace(RegExp(":"+c+"(\\W)","g"),g+"$1")):a=a.replace(RegExp("(/?):"+c+"(\\W)","g"),function(a,b,c){return c.charAt(0)=="/"?c:b+c})});var a=a.replace(/\/?#$/,""),k=[];h(b,function(a,b){e.urlParams[b]||k.push(s(b)+"="+s(a))});k.sort();a=a.replace(/\/*$/,"");return a+(k.length?"?"+k.join("&"):"")}};return u}])})(window,window.angular); diff --git a/bower_components/angular-resource/bower.json b/bower_components/angular-resource/bower.json new file mode 100644 index 0000000..291e13a --- /dev/null +++ b/bower_components/angular-resource/bower.json @@ -0,0 +1,8 @@ +{ + "name": "angular-resource", + "version": "1.0.8", + "main": "./angular-resource.js", + "dependencies": { + "angular": "1.0.8" + } +} diff --git a/bower_components/angular-sanitize/README.md b/bower_components/angular-sanitize/README.md new file mode 100644 index 0000000..25d9565 --- /dev/null +++ b/bower_components/angular-sanitize/README.md @@ -0,0 +1,4 @@ +bower-angular-sanitize +====================== + +angular-sanitize bower repo \ No newline at end of file diff --git a/bower_components/angular-sanitize/angular-sanitize.js b/bower_components/angular-sanitize/angular-sanitize.js new file mode 100755 index 0000000..06f4c22 --- /dev/null +++ b/bower_components/angular-sanitize/angular-sanitize.js @@ -0,0 +1,556 @@ +/** + * @license AngularJS v1.0.8 + * (c) 2010-2012 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) { +'use strict'; + +/** + * @ngdoc overview + * @name ngSanitize + * @description + * + * The `ngSanitize` module provides functionality to sanitize HTML. + * + * # Installation + * As a separate module, it must be loaded after Angular core is loaded; otherwise, an 'Uncaught Error: + * No module: ngSanitize' runtime error will occur. + * + *
        + *   
        + *   
        + * 
        + * + * # Usage + * To make sure the module is available to your application, declare it as a dependency of you application + * module. + * + *
        + *   angular.module('app', ['ngSanitize']);
        + * 
        + */ + +/* + * HTML Parser By Misko Hevery (misko@hevery.com) + * based on: HTML Parser By John Resig (ejohn.org) + * Original code by Erik Arvidsson, Mozilla Public License + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + * + * // Use like so: + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + */ + + +/** + * @ngdoc service + * @name ngSanitize.$sanitize + * @function + * + * @description + * The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are + * then serialized back to properly escaped html string. This means that no unsafe input can make + * it into the returned string, however, since our parser is more strict than a typical browser + * parser, it's possible that some obscure input, which would be recognized as valid HTML by a + * browser, won't make it through the sanitizer. + * + * @param {string} html Html input. + * @returns {string} Sanitized html. + * + * @example + + + +
        + Snippet: + + + + + + + + + + + + + + + + + + + + + +
        FilterSourceRendered
        html filter +
        <div ng-bind-html="snippet">
        </div>
        +
        +
        +
        no filter
        <div ng-bind="snippet">
        </div>
        unsafe html filter
        <div ng-bind-html-unsafe="snippet">
        </div>
        +
        +
        + + it('should sanitize the html snippet ', function() { + expect(using('#html-filter').element('div').html()). + toBe('

        an html\nclick here\nsnippet

        '); + }); + + it('should escape snippet without any filter', function() { + expect(using('#escaped-html').element('div').html()). + toBe("<p style=\"color:blue\">an html\n" + + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + + "snippet</p>"); + }); + + it('should inline raw snippet if filtered as unsafe', function() { + expect(using('#html-unsafe-filter').element("div").html()). + toBe("

        an html\n" + + "click here\n" + + "snippet

        "); + }); + + it('should update', function() { + input('snippet').enter('new text'); + expect(using('#html-filter').binding('snippet')).toBe('new text'); + expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>"); + expect(using('#html-unsafe-filter').binding("snippet")).toBe('new text'); + }); +
        +
        + */ +var $sanitize = function(html) { + var buf = []; + htmlParser(html, htmlSanitizeWriter(buf)); + return buf.join(''); +}; + + +// Regular Expressions for parsing tags and attributes +var START_TAG_REGEXP = /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, + END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, + ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, + BEGIN_TAG_REGEXP = /^/g, + CDATA_REGEXP = //g, + URI_REGEXP = /^((ftp|https?):\/\/|mailto:|#)/i, + NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; // Match everything outside of normal chars and " (quote character) + + +// Good source of info about elements and attributes +// http://dev.w3.org/html5/spec/Overview.html#semantics +// http://simon.html5.org/html-elements + +// Safe Void Elements - HTML5 +// http://dev.w3.org/html5/spec/Overview.html#void-elements +var voidElements = makeMap("area,br,col,hr,img,wbr"); + +// Elements that you can, intentionally, leave open (and which close themselves) +// http://dev.w3.org/html5/spec/Overview.html#optional-tags +var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), + optionalEndTagInlineElements = makeMap("rp,rt"), + optionalEndTagElements = angular.extend({}, optionalEndTagInlineElements, optionalEndTagBlockElements); + +// Safe Block Elements - HTML5 +var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,aside," + + "blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6," + + "header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); + +// Inline Elements - HTML5 +var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,bdi,bdo," + + "big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small," + + "span,strike,strong,sub,sup,time,tt,u,var")); + + +// Special Elements (can contain anything) +var specialElements = makeMap("script,style"); + +var validElements = angular.extend({}, voidElements, blockElements, inlineElements, optionalEndTagElements); + +//Attributes that have href and hence need to be sanitized +var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); +var validAttrs = angular.extend({}, uriAttrs, makeMap( + 'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ + 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ + 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ + 'scope,scrolling,shape,span,start,summary,target,title,type,'+ + 'valign,value,vspace,width')); + +function makeMap(str) { + var obj = {}, items = str.split(','), i; + for (i = 0; i < items.length; i++) obj[items[i]] = true; + return obj; +} + + +/** + * @example + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + * @param {string} html string + * @param {object} handler + */ +function htmlParser( html, handler ) { + var index, chars, match, stack = [], last = html; + stack.last = function() { return stack[ stack.length - 1 ]; }; + + while ( html ) { + chars = true; + + // Make sure we're not in a script or style element + if ( !stack.last() || !specialElements[ stack.last() ] ) { + + // Comment + if ( html.indexOf(""); + + if ( index >= 0 ) { + if (handler.comment) handler.comment( html.substring( 4, index ) ); + html = html.substring( index + 3 ); + chars = false; + } + + // end tag + } else if ( BEGING_END_TAGE_REGEXP.test(html) ) { + match = html.match( END_TAG_REGEXP ); + + if ( match ) { + html = html.substring( match[0].length ); + match[0].replace( END_TAG_REGEXP, parseEndTag ); + chars = false; + } + + // start tag + } else if ( BEGIN_TAG_REGEXP.test(html) ) { + match = html.match( START_TAG_REGEXP ); + + if ( match ) { + html = html.substring( match[0].length ); + match[0].replace( START_TAG_REGEXP, parseStartTag ); + chars = false; + } + } + + if ( chars ) { + index = html.indexOf("<"); + + var text = index < 0 ? html : html.substring( 0, index ); + html = index < 0 ? "" : html.substring( index ); + + if (handler.chars) handler.chars( decodeEntities(text) ); + } + + } else { + html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), function(all, text){ + text = text. + replace(COMMENT_REGEXP, "$1"). + replace(CDATA_REGEXP, "$1"); + + if (handler.chars) handler.chars( decodeEntities(text) ); + + return ""; + }); + + parseEndTag( "", stack.last() ); + } + + if ( html == last ) { + throw "Parse Error: " + html; + } + last = html; + } + + // Clean up any remaining tags + parseEndTag(); + + function parseStartTag( tag, tagName, rest, unary ) { + tagName = angular.lowercase(tagName); + if ( blockElements[ tagName ] ) { + while ( stack.last() && inlineElements[ stack.last() ] ) { + parseEndTag( "", stack.last() ); + } + } + + if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { + parseEndTag( "", tagName ); + } + + unary = voidElements[ tagName ] || !!unary; + + if ( !unary ) + stack.push( tagName ); + + var attrs = {}; + + rest.replace(ATTR_REGEXP, function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { + var value = doubleQuotedValue + || singleQuotedValue + || unquotedValue + || ''; + + attrs[name] = decodeEntities(value); + }); + if (handler.start) handler.start( tagName, attrs, unary ); + } + + function parseEndTag( tag, tagName ) { + var pos = 0, i; + tagName = angular.lowercase(tagName); + if ( tagName ) + // Find the closest opened tag of the same type + for ( pos = stack.length - 1; pos >= 0; pos-- ) + if ( stack[ pos ] == tagName ) + break; + + if ( pos >= 0 ) { + // Close all the open elements, up the stack + for ( i = stack.length - 1; i >= pos; i-- ) + if (handler.end) handler.end( stack[ i ] ); + + // Remove the open elements from the stack + stack.length = pos; + } + } +} + +/** + * decodes all entities into regular string + * @param value + * @returns {string} A string with decoded entities. + */ +var hiddenPre=document.createElement("pre"); +function decodeEntities(value) { + hiddenPre.innerHTML=value.replace(//g, '>'); +} + +/** + * create an HTML/XML writer which writes to buffer + * @param {Array} buf use buf.jain('') to get out sanitized html string + * @returns {object} in the form of { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * } + */ +function htmlSanitizeWriter(buf){ + var ignore = false; + var out = angular.bind(buf, buf.push); + return { + start: function(tag, attrs, unary){ + tag = angular.lowercase(tag); + if (!ignore && specialElements[tag]) { + ignore = tag; + } + if (!ignore && validElements[tag] == true) { + out('<'); + out(tag); + angular.forEach(attrs, function(value, key){ + var lkey=angular.lowercase(key); + if (validAttrs[lkey]==true && (uriAttrs[lkey]!==true || value.match(URI_REGEXP))) { + out(' '); + out(key); + out('="'); + out(encodeEntities(value)); + out('"'); + } + }); + out(unary ? '/>' : '>'); + } + }, + end: function(tag){ + tag = angular.lowercase(tag); + if (!ignore && validElements[tag] == true) { + out(''); + } + if (tag == ignore) { + ignore = false; + } + }, + chars: function(chars){ + if (!ignore) { + out(encodeEntities(chars)); + } + } + }; +} + + +// define ngSanitize module and register $sanitize service +angular.module('ngSanitize', []).value('$sanitize', $sanitize); + +/** + * @ngdoc directive + * @name ngSanitize.directive:ngBindHtml + * + * @description + * Creates a binding that will sanitize the result of evaluating the `expression` with the + * {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element. + * + * See {@link ngSanitize.$sanitize $sanitize} docs for examples. + * + * @element ANY + * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. + */ +angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) { + return function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.ngBindHtml); + scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) { + value = $sanitize(value); + element.html(value || ''); + }); + }; +}]); + +/** + * @ngdoc filter + * @name ngSanitize.filter:linky + * @function + * + * @description + * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and + * plain email address links. + * + * @param {string} text Input text. + * @returns {string} Html-linkified text. + * + * @usage + + * + * @example + + + +
        + Snippet: + + + + + + + + + + + + + + + + +
        FilterSourceRendered
        linky filter +
        <div ng-bind-html="snippet | linky">
        </div>
        +
        +
        +
        no filter
        <div ng-bind="snippet">
        </div>
        + + + it('should linkify the snippet with urls', function() { + expect(using('#linky-filter').binding('snippet | linky')). + toBe('Pretty text with some links: ' + + 'http://angularjs.org/, ' + + 'us@somewhere.org, ' + + 'another@somewhere.org, ' + + 'and one more: ftp://127.0.0.1/.'); + }); + + it ('should not linkify snippet without the linky filter', function() { + expect(using('#escaped-html').binding('snippet')). + toBe("Pretty text with some links:\n" + + "http://angularjs.org/,\n" + + "mailto:us@somewhere.org,\n" + + "another@somewhere.org,\n" + + "and one more: ftp://127.0.0.1/."); + }); + + it('should update', function() { + input('snippet').enter('new http://link.'); + expect(using('#linky-filter').binding('snippet | linky')). + toBe('new http://link.'); + expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); + }); + + + */ +angular.module('ngSanitize').filter('linky', function() { + var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/, + MAILTO_REGEXP = /^mailto:/; + + return function(text) { + if (!text) return text; + var match; + var raw = text; + var html = []; + // TODO(vojta): use $sanitize instead + var writer = htmlSanitizeWriter(html); + var url; + var i; + while ((match = raw.match(LINKY_URL_REGEXP))) { + // We can not end in these as they are sometimes found at the end of the sentence + url = match[0]; + // if we did not match ftp/http/mailto then assume mailto + if (match[2] == match[3]) url = 'mailto:' + url; + i = match.index; + writer.chars(raw.substr(0, i)); + writer.start('a', {href:url}); + writer.chars(match[0].replace(MAILTO_REGEXP, '')); + writer.end('a'); + raw = raw.substring(i + match[0].length); + } + writer.chars(raw); + return html.join(''); + }; +}); + + +})(window, window.angular); diff --git a/bower_components/angular-sanitize/angular-sanitize.min.js b/bower_components/angular-sanitize/angular-sanitize.min.js new file mode 100755 index 0000000..6f8dcf1 --- /dev/null +++ b/bower_components/angular-sanitize/angular-sanitize.min.js @@ -0,0 +1,13 @@ +/* + AngularJS v1.0.8 + (c) 2010-2012 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(I,g){'use strict';function i(a){var d={},a=a.split(","),b;for(b=0;b=0;e--)if(f[e]==b)break;if(e>=0){for(c=f.length-1;c>=e;c--)d.end&&d.end(f[c]);f.length= +e}}var c,h,f=[],j=a;for(f.last=function(){return f[f.length-1]};a;){h=!0;if(!f.last()||!q[f.last()]){if(a.indexOf("<\!--")===0)c=a.indexOf("--\>"),c>=0&&(d.comment&&d.comment(a.substring(4,c)),a=a.substring(c+3),h=!1);else if(B.test(a)){if(c=a.match(r))a=a.substring(c[0].length),c[0].replace(r,e),h=!1}else if(C.test(a)&&(c=a.match(s)))a=a.substring(c[0].length),c[0].replace(s,b),h=!1;h&&(c=a.indexOf("<"),h=c<0?a:a.substring(0,c),a=c<0?"":a.substring(c),d.chars&&d.chars(k(h)))}else a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+ +f.last()+"[^>]*>","i"),function(b,a){a=a.replace(D,"$1").replace(E,"$1");d.chars&&d.chars(k(a));return""}),e("",f.last());if(a==j)throw"Parse Error: "+a;j=a}e()}function k(a){l.innerHTML=a.replace(//g,">")}function u(a){var d=!1,b=g.bind(a,a.push);return{start:function(a,c,h){a=g.lowercase(a);!d&&q[a]&&(d=a);!d&&v[a]== +!0&&(b("<"),b(a),g.forEach(c,function(a,c){var e=g.lowercase(c);if(G[e]==!0&&(w[e]!==!0||a.match(H)))b(" "),b(c),b('="'),b(t(a)),b('"')}),b(h?"/>":">"))},end:function(a){a=g.lowercase(a);!d&&v[a]==!0&&(b(""));a==d&&(d=!1)},chars:function(a){d||b(t(a))}}}var s=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,r=/^<\s*\/\s*([\w:-]+)[^>]*>/,A=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,C=/^/g, +E=//g,H=/^((ftp|https?):\/\/|mailto:|#)/i,F=/([^\#-~| |!])/g,p=i("area,br,col,hr,img,wbr"),x=i("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),y=i("rp,rt"),o=g.extend({},y,x),m=g.extend({},x,i("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),n=g.extend({},y,i("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")), +q=i("script,style"),v=g.extend({},p,m,n,o),w=i("background,cite,href,longdesc,src,usemap"),G=g.extend({},w,i("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")),l=document.createElement("pre");g.module("ngSanitize",[]).value("$sanitize",function(a){var d=[]; +z(a,u(d));return d.join("")});g.module("ngSanitize").directive("ngBindHtml",["$sanitize",function(a){return function(d,b,e){b.addClass("ng-binding").data("$binding",e.ngBindHtml);d.$watch(e.ngBindHtml,function(c){c=a(c);b.html(c||"")})}}]);g.module("ngSanitize").filter("linky",function(){var a=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,d=/^mailto:/;return function(b){if(!b)return b;for(var e=b,c=[],h=u(c),f,g;b=e.match(a);)f=b[0],b[2]==b[3]&&(f="mailto:"+f),g=b.index, +h.chars(e.substr(0,g)),h.start("a",{href:f}),h.chars(b[0].replace(d,"")),h.end("a"),e=e.substring(g+b[0].length);h.chars(e);return c.join("")}})})(window,window.angular); diff --git a/bower_components/angular-sanitize/bower.json b/bower_components/angular-sanitize/bower.json new file mode 100644 index 0000000..398e773 --- /dev/null +++ b/bower_components/angular-sanitize/bower.json @@ -0,0 +1,8 @@ +{ + "name": "angular-sanitize", + "version": "1.0.8", + "main": "./angular-sanitize.js", + "dependencies": { + "angular": "1.0.8" + } +} diff --git a/bower_components/angular-scenario/README.md b/bower_components/angular-scenario/README.md new file mode 100644 index 0000000..1bcf564 --- /dev/null +++ b/bower_components/angular-scenario/README.md @@ -0,0 +1,4 @@ +bower-angular-scenario +====================== + +bower repo for angular-scenario.js \ No newline at end of file diff --git a/bower_components/angular-scenario/angular-scenario.js b/bower_components/angular-scenario/angular-scenario.js new file mode 100755 index 0000000..577093f --- /dev/null +++ b/bower_components/angular-scenario/angular-scenario.js @@ -0,0 +1,26621 @@ +/*! + * jQuery JavaScript Library v1.7.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Wed Mar 21 12:46:34 2012 -0700 + */ +(function( window, undefined ) { +'use strict'; + +// Use the correct document accordingly with window argument (sandbox) +var document = window.document, + navigator = window.navigator, + location = window.location; +var jQuery = (function() { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + trimLeft = /^\s+/, + trimRight = /\s+$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, + rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + + // Useragent RegExp + rwebkit = /(webkit)[ \/]([\w.]+)/, + ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, + rmsie = /(msie) ([\w.]+)/, + rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, + + // Matches dashed string for camelizing + rdashAlpha = /-([a-z]|[0-9])/ig, + rmsPrefix = /^-ms-/, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return ( letter + "" ).toUpperCase(); + }, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // The deferred used on DOM ready + readyList, + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + trim = String.prototype.trim, + indexOf = Array.prototype.indexOf, + + // [[Class]] -> type pairs + class2type = {}; + +jQuery.fn = jQuery.prototype = { + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context && document.body ) { + this.context = document; + this[0] = document.body; + this.selector = selector; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = quickExpr.exec( selector ); + } + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.7.2", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = this.constructor(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // Add the callback + readyList.add( fn ); + + return this; + }, + + eq: function( i ) { + i = +i; + return i === -1 ? + this.slice( i ) : + this.slice( i, i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + // Either a released hold or an DOMready/load event and not yet ready + if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 1 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.fireWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).off( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyList ) { + return; + } + + readyList = jQuery.Callbacks( "once memory" ); + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + return setTimeout( jQuery.ready, 1 ); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + return obj == null ? + String( obj ) : + class2type[ toString.call(obj) ] || "object"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !hasOwn.call(obj, "constructor") && + !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + + } + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + var xml, tmp; + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && rnotwhite.test( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction( object ); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { + break; + } + } + } + } + + return object; + }, + + // Use native String.trim function wherever possible + trim: trim ? + function( text ) { + return text == null ? + "" : + trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 + var type = jQuery.type( array ); + + if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array, i ) { + var len; + + if ( array ) { + if ( indexOf ) { + return indexOf.call( array, elem, i ); + } + + len = array.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in array && array[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, + j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = [], retVal; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, key, ret = [], + i = 0, + length = elems.length, + // jquery objects are treated as arrays + isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( key in elems ) { + value = callback( elems[ key ], key, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + if ( typeof context === "string" ) { + var tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + var args = slice.call( arguments, 2 ), + proxy = function() { + return fn.apply( context, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + + return proxy; + }, + + // Mutifunctional method to get and set values to a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, pass ) { + var exec, + bulk = key == null, + i = 0, + length = elems.length; + + // Sets many values + if ( key && typeof key === "object" ) { + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); + } + chainable = 1; + + // Sets one value + } else if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = pass === undefined && jQuery.isFunction( value ); + + if ( bulk ) { + // Bulk operations only iterate when executing function values + if ( exec ) { + exec = fn; + fn = function( elem, key, value ) { + return exec.call( jQuery( elem ), value ); + }; + + // Otherwise they run against the entire set + } else { + fn.call( elems, value ); + fn = null; + } + } + + if ( fn ) { + for (; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + } + + chainable = 1; + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = rwebkit.exec( ua ) || + ropera.exec( ua ) || + rmsie.exec( ua ) || + ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + sub: function() { + function jQuerySub( selector, context ) { + return new jQuerySub.fn.init( selector, context ); + } + jQuery.extend( true, jQuerySub, this ); + jQuerySub.superclass = this; + jQuerySub.fn = jQuerySub.prototype = this(); + jQuerySub.fn.constructor = jQuerySub; + jQuerySub.sub = this.sub; + jQuerySub.fn.init = function init( selector, context ) { + if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { + context = jQuerySub( context ); + } + + return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); + }; + jQuerySub.fn.init.prototype = jQuerySub.fn; + var rootjQuerySub = jQuerySub(document); + return jQuerySub; + }, + + browser: {} +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +// IE doesn't match non-breaking spaces with \s +if ( rnotwhite.test( "\xA0" ) ) { + trimLeft = /^[\s\xA0]+/; + trimRight = /[\s\xA0]+$/; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch(e) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +return jQuery; + +})(); + + +// String to Object flags format cache +var flagsCache = {}; + +// Convert String-formatted flags into Object-formatted ones and store in cache +function createFlags( flags ) { + var object = flagsCache[ flags ] = {}, + i, length; + flags = flags.split( /\s+/ ); + for ( i = 0, length = flags.length; i < length; i++ ) { + object[ flags[i] ] = true; + } + return object; +} + +/* + * Create a callback list using the following parameters: + * + * flags: an optional list of space-separated flags that will change how + * the callback list behaves + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible flags: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( flags ) { + + // Convert flags from String-formatted to Object-formatted + // (we check in cache first) + flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; + + var // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = [], + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Add one or several callbacks to the list + add = function( args ) { + var i, + length, + elem, + type, + actual; + for ( i = 0, length = args.length; i < length; i++ ) { + elem = args[ i ]; + type = jQuery.type( elem ); + if ( type === "array" ) { + // Inspect recursively + add( elem ); + } else if ( type === "function" ) { + // Add if not in unique mode and callback is not in + if ( !flags.unique || !self.has( elem ) ) { + list.push( elem ); + } + } + } + }, + // Fire callbacks + fire = function( context, args ) { + args = args || []; + memory = !flags.memory || [ context, args ]; + fired = true; + firing = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { + memory = true; // Mark as halted + break; + } + } + firing = false; + if ( list ) { + if ( !flags.once ) { + if ( stack && stack.length ) { + memory = stack.shift(); + self.fireWith( memory[ 0 ], memory[ 1 ] ); + } + } else if ( memory === true ) { + self.disable(); + } else { + list = []; + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + var length = list.length; + add( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away, unless previous + // firing was halted (stopOnFalse) + } else if ( memory && memory !== true ) { + firingStart = length; + fire( memory[ 0 ], memory[ 1 ] ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + var args = arguments, + argIndex = 0, + argLength = args.length; + for ( ; argIndex < argLength ; argIndex++ ) { + for ( var i = 0; i < list.length; i++ ) { + if ( args[ argIndex ] === list[ i ] ) { + // Handle firingIndex and firingLength + if ( firing ) { + if ( i <= firingLength ) { + firingLength--; + if ( i <= firingIndex ) { + firingIndex--; + } + } + } + // Remove the element + list.splice( i--, 1 ); + // If we have some unicity property then + // we only need to do this once + if ( flags.unique ) { + break; + } + } + } + } + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + if ( list ) { + var i = 0, + length = list.length; + for ( ; i < length; i++ ) { + if ( fn === list[ i ] ) { + return true; + } + } + } + return false; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory || memory === true ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( stack ) { + if ( firing ) { + if ( !flags.once ) { + stack.push( [ context, args ] ); + } + } else if ( !( flags.once && memory ) ) { + fire( context, args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + + + +var // Static reference to slice + sliceDeferred = [].slice; + +jQuery.extend({ + + Deferred: function( func ) { + var doneList = jQuery.Callbacks( "once memory" ), + failList = jQuery.Callbacks( "once memory" ), + progressList = jQuery.Callbacks( "memory" ), + state = "pending", + lists = { + resolve: doneList, + reject: failList, + notify: progressList + }, + promise = { + done: doneList.add, + fail: failList.add, + progress: progressList.add, + + state: function() { + return state; + }, + + // Deprecated + isResolved: doneList.fired, + isRejected: failList.fired, + + then: function( doneCallbacks, failCallbacks, progressCallbacks ) { + deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); + return this; + }, + always: function() { + deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); + return this; + }, + pipe: function( fnDone, fnFail, fnProgress ) { + return jQuery.Deferred(function( newDefer ) { + jQuery.each( { + done: [ fnDone, "resolve" ], + fail: [ fnFail, "reject" ], + progress: [ fnProgress, "notify" ] + }, function( handler, data ) { + var fn = data[ 0 ], + action = data[ 1 ], + returned; + if ( jQuery.isFunction( fn ) ) { + deferred[ handler ](function() { + returned = fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); + } + }); + } else { + deferred[ handler ]( newDefer[ action ] ); + } + }); + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + if ( obj == null ) { + obj = promise; + } else { + for ( var key in promise ) { + obj[ key ] = promise[ key ]; + } + } + return obj; + } + }, + deferred = promise.promise({}), + key; + + for ( key in lists ) { + deferred[ key ] = lists[ key ].fire; + deferred[ key + "With" ] = lists[ key ].fireWith; + } + + // Handle state + deferred.done( function() { + state = "resolved"; + }, failList.disable, progressList.lock ).fail( function() { + state = "rejected"; + }, doneList.disable, progressList.lock ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( firstParam ) { + var args = sliceDeferred.call( arguments, 0 ), + i = 0, + length = args.length, + pValues = new Array( length ), + count = length, + pCount = length, + deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? + firstParam : + jQuery.Deferred(), + promise = deferred.promise(); + function resolveFunc( i ) { + return function( value ) { + args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + if ( !( --count ) ) { + deferred.resolveWith( deferred, args ); + } + }; + } + function progressFunc( i ) { + return function( value ) { + pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; + deferred.notifyWith( promise, pValues ); + }; + } + if ( length > 1 ) { + for ( ; i < length; i++ ) { + if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { + args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); + } else { + --count; + } + } + if ( !count ) { + deferred.resolveWith( deferred, args ); + } + } else if ( deferred !== firstParam ) { + deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); + } + return promise; + } +}); + + + + +jQuery.support = (function() { + + var support, + all, + a, + select, + opt, + input, + fragment, + tds, + events, + eventName, + i, + isSupported, + div = document.createElement( "div" ), + documentElement = document.documentElement; + + // Preliminary tests + div.setAttribute("className", "t"); + div.innerHTML = "
        a"; + + all = div.getElementsByTagName( "*" ); + a = div.getElementsByTagName( "a" )[ 0 ]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return {}; + } + + // First batch of supports tests + select = document.createElement( "select" ); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName( "input" )[ 0 ]; + + support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: ( div.firstChild.nodeType === 3 ), + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: ( a.getAttribute("href") === "/a" ), + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: ( input.value === "on" ), + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // Tests for enctype support on a form(#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // Will be defined later + submitBubbles: true, + changeBubbles: true, + focusinBubbles: false, + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + pixelMargin: true + }; + + // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead + jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { + div.attachEvent( "onclick", function() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + support.noCloneEvent = false; + }); + div.cloneNode( true ).fireEvent( "onclick" ); + } + + // Check if a radio maintains its value + // after being appended to the DOM + input = document.createElement("input"); + input.value = "t"; + input.setAttribute("type", "radio"); + support.radioValue = input.value === "t"; + + input.setAttribute("checked", "checked"); + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + fragment = document.createDocumentFragment(); + fragment.appendChild( div.lastChild ); + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + fragment.removeChild( input ); + fragment.appendChild( div ); + + // Technique from Juriy Zaytsev + // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ + // We only care about the case where non-standard event systems + // are used, namely in IE. Short-circuiting here helps us to + // avoid an eval call (in setAttribute) which can cause CSP + // to go haywire. See: https://developer.mozilla.org/en/Security/CSP + if ( div.attachEvent ) { + for ( i in { + submit: 1, + change: 1, + focusin: 1 + }) { + eventName = "on" + i; + isSupported = ( eventName in div ); + if ( !isSupported ) { + div.setAttribute( eventName, "return;" ); + isSupported = ( typeof div[ eventName ] === "function" ); + } + support[ i + "Bubbles" ] = isSupported; + } + } + + fragment.removeChild( div ); + + // Null elements to avoid leaks in IE + fragment = select = opt = div = input = null; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, outer, inner, table, td, offsetSupport, + marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, + paddingMarginBorderVisibility, paddingMarginBorder, + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + conMarginTop = 1; + paddingMarginBorder = "padding:0;margin:0;border:"; + positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; + paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; + style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; + html = "
        " + + "" + + "
        "; + + container = document.createElement("div"); + container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + body.insertBefore( container, body.firstChild ); + + // Construct the test element + div = document.createElement("div"); + container.appendChild( div ); + + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + // (only IE 8 fails this test) + div.innerHTML = "
        t
        "; + tds = div.getElementsByTagName( "td" ); + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Check if empty table cells still have offsetWidth/Height + // (IE <= 8 fail this test) + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( window.getComputedStyle ) { + div.innerHTML = ""; + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } + + if ( typeof div.style.zoom !== "undefined" ) { + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + // (IE < 8 does this) + div.innerHTML = ""; + div.style.width = div.style.padding = "1px"; + div.style.border = 0; + div.style.overflow = "hidden"; + div.style.display = "inline"; + div.style.zoom = 1; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Check if elements with layout shrink-wrap their children + // (IE 6 does this) + div.style.display = "block"; + div.style.overflow = "visible"; + div.innerHTML = "
        "; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + } + + div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility; + div.innerHTML = html; + + outer = div.firstChild; + inner = outer.firstChild; + td = outer.nextSibling.firstChild.firstChild; + + offsetSupport = { + doesNotAddBorder: ( inner.offsetTop !== 5 ), + doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) + }; + + inner.style.position = "fixed"; + inner.style.top = "20px"; + + // safari subtracts parent border width here which is 5px + offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); + inner.style.position = inner.style.top = ""; + + outer.style.overflow = "hidden"; + outer.style.position = "relative"; + + offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); + offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); + + if ( window.getComputedStyle ) { + div.style.marginTop = "1%"; + support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%"; + } + + if ( typeof container.style.zoom !== "undefined" ) { + container.style.zoom = 1; + } + + body.removeChild( container ); + marginDiv = div = container = null; + + jQuery.extend( support, offsetSupport ); + }); + + return support; +})(); + + + + +var rbrace = /^(?:\{.*\}|\[.*\])$/, + rmultiDash = /([A-Z])/g; + +jQuery.extend({ + cache: {}, + + // Please use with caution + uuid: 0, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var privateCache, thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, + isEvents = name === "events"; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = ++jQuery.uuid; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + privateCache = thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Users should not attempt to inspect the internal events object using jQuery.data, + // it is undocumented and subject to change. But does anyone listen? No. + if ( isEvents && !thisCache[ name ] ) { + return privateCache.events; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; + }, + + removeData: function( elem, name, pvt /* Internal Use Only */ ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + // Reference to internal data cache key + internalKey = jQuery.expando, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + + // See jQuery.data for more information + id = isNode ? elem[ internalKey ] : internalKey; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split( " " ); + } + } + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject(cache[ id ]) ) { + return; + } + } + + // Browsers that fail expando deletion also refuse to delete expandos on + // the window, but it will allow it on all other JS objects; other browsers + // don't care + // Ensure that `cache` is not a window object #10080 + if ( jQuery.support.deleteExpando || !cache.setInterval ) { + delete cache[ id ]; + } else { + cache[ id ] = null; + } + + // We destroyed the cache and need to eliminate the expando on the node to avoid + // false lookups in the cache for entries that no longer exist + if ( isNode ) { + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( jQuery.support.deleteExpando ) { + delete elem[ internalKey ]; + } else if ( elem.removeAttribute ) { + elem.removeAttribute( internalKey ); + } else { + elem[ internalKey ] = null; + } + } + }, + + // For internal use only. + _data: function( elem, name, data ) { + return jQuery.data( elem, name, data, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + if ( elem.nodeName ) { + var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; + + if ( match ) { + return !(match === true || elem.getAttribute("classid") !== match); + } + } + + return true; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var parts, part, attr, name, l, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attr = elem.attributes; + for ( l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + parts = key.split( ".", 2 ); + parts[1] = parts[1] ? "." + parts[1] : ""; + part = parts[1] + "!"; + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + data = this.triggerHandler( "getData" + part, [ parts[0] ] ); + + // Try to fetch any internally stored data first + if ( data === undefined && elem ) { + data = jQuery.data( elem, key ); + data = dataAttr( elem, key, data ); + } + + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } + + parts[1] = value; + this.each(function() { + var self = jQuery( this ); + + self.triggerHandler( "setData" + part, parts ); + jQuery.data( this, key, value ); + self.triggerHandler( "changeData" + part, parts ); + }); + }, null, value, arguments.length > 1, null, false ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + jQuery.isNumeric( data ) ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + for ( var name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} + + + + +function handleQueueMarkDefer( elem, type, src ) { + var deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + defer = jQuery._data( elem, deferDataKey ); + if ( defer && + ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && + ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { + // Give room for hard-coded callbacks to fire first + // and eventually mark/queue something else on the element + setTimeout( function() { + if ( !jQuery._data( elem, queueDataKey ) && + !jQuery._data( elem, markDataKey ) ) { + jQuery.removeData( elem, deferDataKey, true ); + defer.fire(); + } + }, 0 ); + } +} + +jQuery.extend({ + + _mark: function( elem, type ) { + if ( elem ) { + type = ( type || "fx" ) + "mark"; + jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); + } + }, + + _unmark: function( force, elem, type ) { + if ( force !== true ) { + type = elem; + elem = force; + force = false; + } + if ( elem ) { + type = type || "fx"; + var key = type + "mark", + count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); + if ( count ) { + jQuery._data( elem, key, count ); + } else { + jQuery.removeData( elem, key, true ); + handleQueueMarkDefer( elem, type, "mark" ); + } + } + }, + + queue: function( elem, type, data ) { + var q; + if ( elem ) { + type = ( type || "fx" ) + "queue"; + q = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !q || jQuery.isArray(data) ) { + q = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + q.push( data ); + } + } + return q || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + fn = queue.shift(), + hooks = {}; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + jQuery._data( elem, type + ".run", hooks ); + fn.call( elem, function() { + jQuery.dequeue( elem, type ); + }, hooks ); + } + + if ( !queue.length ) { + jQuery.removeData( elem, type + "queue " + type + ".run", true ); + handleQueueMarkDefer( elem, type, "queue" ); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, object ) { + if ( typeof type !== "string" ) { + object = type; + type = undefined; + } + type = type || "fx"; + var defer = jQuery.Deferred(), + elements = this, + i = elements.length, + count = 1, + deferDataKey = type + "defer", + queueDataKey = type + "queue", + markDataKey = type + "mark", + tmp; + function resolve() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + } + while( i-- ) { + if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || + ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || + jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && + jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { + count++; + tmp.add( resolve ); + } + } + resolve(); + return defer.promise( object ); + } +}); + + + + +var rclass = /[\n\t\r]/g, + rspace = /\s+/, + rreturn = /\r/g, + rtype = /^(?:button|input)$/i, + rfocusable = /^(?:button|input|object|select|textarea)$/i, + rclickable = /^a(?:rea)?$/i, + rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + nodeHook, boolHook, fixSpecified; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classNames, i, l, elem, + setClass, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call(this, j, this.className) ); + }); + } + + if ( value && typeof value === "string" ) { + classNames = value.split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className && classNames.length === 1 ) { + elem.className = value; + + } else { + setClass = " " + elem.className + " "; + + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { + setClass += classNames[ c ] + " "; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classNames, i, l, elem, className, c, cl; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call(this, j, this.className) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + classNames = ( value || "" ).split( rspace ); + + for ( i = 0, l = this.length; i < l; i++ ) { + elem = this[ i ]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + className = (" " + elem.className + " ").replace( rclass, " " ); + for ( c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[ c ] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var self = jQuery(this), val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, i, max, option, + index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + i = one ? index : 0; + max = one ? index + 1 : options.length; + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Don't return options that are disabled or in a disabled optgroup + if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && + (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + // Fixes Bug #2551 -- select.val() broken in IE after form.reset() + if ( one && !values.length && options.length ) { + return jQuery( options[ index ] ).val(); + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery( elem )[ name ]( value ); + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + + } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, "" + value ); + return value; + } + + } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + ret = elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return ret === null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var propName, attrNames, name, l, isBool, + i = 0; + + if ( value && elem.nodeType === 1 ) { + attrNames = value.toLowerCase().split( rspace ); + l = attrNames.length; + + for ( ; i < l; i++ ) { + name = attrNames[ i ]; + + if ( name ) { + propName = jQuery.propFix[ name ] || name; + isBool = rboolean.test( name ); + + // See #9699 for explanation of this approach (setting first, then removal) + // Do not do this for boolean attributes (see #10870) + if ( !isBool ) { + jQuery.attr( elem, name, "" ); + } + elem.removeAttribute( getSetAttribute ? name : propName ); + + // Set corresponding property to false for boolean attributes + if ( isBool && propName in elem ) { + elem[ propName ] = false; + } + } + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to it's default in case type is set after value + // This is for element creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + }, + // Use the value property for back compat + // Use the nodeHook for button elements in IE6/7 (#1954) + value: { + get: function( elem, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.get( elem, name ); + } + return name in elem ? + elem.value : + null; + }, + set: function( elem, value, name ) { + if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { + return nodeHook.set( elem, value, name ); + } + // Does not return so that setAttribute is also used + elem.value = value; + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) +jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + // Align boolean attributes with corresponding properties + // Fall back to attribute presence where some booleans are not supported + var attrNode, + property = jQuery.prop( elem, name ); + return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + var propName; + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + // value is true since we know at this point it's type boolean and not false + // Set boolean attributes to the same name and set the DOM property + propName = jQuery.propFix[ name ] || name; + if ( propName in elem ) { + // Only set the IDL specifically if it already exists on the element + elem[ propName ] = true; + } + + elem.setAttribute( name, name.toLowerCase() ); + } + return name; + } +}; + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + fixSpecified = { + name: true, + id: true, + coords: true + }; + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret; + ret = elem.getAttributeNode( name ); + return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? + ret.nodeValue : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + ret = document.createAttribute( name ); + elem.setAttributeNode( ret ); + } + return ( ret.nodeValue = value + "" ); + } + }; + + // Apply the nodeHook to tabindex + jQuery.attrHooks.tabindex.set = nodeHook.set; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + if ( value === "" ) { + value = "false"; + } + nodeHook.set( elem, value, name ); + } + }; +} + + +// Some attributes require a special call on IE +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret === null ? undefined : ret; + } + }); + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Normalize to lowercase since IE uppercases css property names + return elem.style.cssText.toLowerCase() || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = "" + value ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); + + + + +var rformElems = /^(?:textarea|input|select)$/i, + rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, + rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, + quickParse = function( selector ) { + var quick = rquickIs.exec( selector ); + if ( quick ) { + // 0 1 2 3 + // [ _, tag, id, class ] + quick[1] = ( quick[1] || "" ).toLowerCase(); + quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); + } + return quick; + }, + quickIs = function( elem, m ) { + var attrs = elem.attributes || {}; + return ( + (!m[1] || elem.nodeName.toLowerCase() === m[1]) && + (!m[2] || (attrs.id || {}).value === m[2]) && + (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) + ); + }, + hoverHack = function( events ) { + return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); + }; + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + add: function( elem, types, handler, data, selector ) { + + var elemData, eventHandle, events, + t, tns, type, namespaces, handleObj, + handleObjIn, quick, handlers, special; + + // Don't attach events to noData or text/comment nodes (allow plain objects tho) + if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + events = elemData.events; + if ( !events ) { + elemData.events = events = {}; + } + eventHandle = elemData.handle; + if ( !eventHandle ) { + elemData.handle = eventHandle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = jQuery.trim( hoverHack(types) ).split( " " ); + for ( t = 0; t < types.length; t++ ) { + + tns = rtypenamespace.exec( types[t] ) || []; + type = tns[1]; + namespaces = ( tns[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: tns[1], + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + quick: selector && quickParse( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + handlers = events[ type ]; + if ( !handlers ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), + t, tns, type, origType, namespaces, origCount, + j, events, special, handle, eventType, handleObj; + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = jQuery.trim( hoverHack( types || "" ) ).split(" "); + for ( t = 0; t < types.length; t++ ) { + tns = rtypenamespace.exec( types[t] ) || []; + type = origType = tns[1]; + namespaces = tns[2]; + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector? special.delegateType : special.bindType ) || type; + eventType = events[ type ] || []; + origCount = eventType.length; + namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + + // Remove matching events + for ( j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !namespaces || namespaces.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + eventType.splice( j--, 1 ); + + if ( handleObj.selector ) { + eventType.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( eventType.length === 0 && origCount !== eventType.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery.removeData( elem, [ "events", "handle" ], true ); + } + }, + + // Events that are safe to short-circuit if no handlers are attached. + // Native DOM events should not be added, they may have inline handlers. + customEvent: { + "getData": true, + "setData": true, + "changeData": true + }, + + trigger: function( event, data, elem, onlyHandlers ) { + // Don't do events on text and comment nodes + if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { + return; + } + + // Event object or event type + var type = event.type || event, + namespaces = [], + cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "!" ) >= 0 ) { + // Exclusive events trigger only for the exact event (no namespaces) + type = type.slice(0, -1); + exclusive = true; + } + + if ( type.indexOf( "." ) >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + + if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { + // No jQuery handlers for this event type, and it can't have inline handlers + return; + } + + // Caller can pass in an Event, Object, or just an event type string + event = typeof event === "object" ? + // jQuery.Event object + event[ jQuery.expando ] ? event : + // Object literal + new jQuery.Event( type, event ) : + // Just the event type (string) + new jQuery.Event( type ); + + event.type = type; + event.isTrigger = true; + event.exclusive = exclusive; + event.namespace = namespaces.join( "." ); + event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; + + // Handle a global trigger + if ( !elem ) { + + // TODO: Stop taunting the data cache; remove global events and always attach to document + cache = jQuery.cache; + for ( i in cache ) { + if ( cache[ i ].events && cache[ i ].events[ type ] ) { + jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); + } + } + return; + } + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data != null ? jQuery.makeArray( data ) : []; + data.unshift( event ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + eventPath = [[ elem, special.bindType || type ]]; + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; + old = null; + for ( ; cur; cur = cur.parentNode ) { + eventPath.push([ cur, bubbleType ]); + old = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( old && old === elem.ownerDocument ) { + eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); + } + } + + // Fire handlers on the event path + for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { + + cur = eventPath[i][0]; + event.type = eventPath[i][1]; + + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + // Note that this is a bare JS function and not a jQuery handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + // IE<9 dies on focus/blur to hidden element (#1486) + if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + old = elem[ ontype ]; + + if ( old ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( old ) { + elem[ ontype ] = old; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event || window.event ); + + var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), + delegateCount = handlers.delegateCount, + args = [].slice.call( arguments, 0 ), + run_all = !event.exclusive && !event.namespace, + special = jQuery.event.special[ event.type ] || {}, + handlerQueue = [], + i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers that should run if there are delegated events + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && !(event.button && event.type === "click") ) { + + // Pregenerate a single jQuery object for reuse with .is() + jqcur = jQuery(this); + jqcur.context = this.ownerDocument || this; + + for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { + + // Don't process events on disabled elements (#6911, #8165) + if ( cur.disabled !== true ) { + selMatch = {}; + matches = []; + jqcur[0] = cur; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + sel = handleObj.selector; + + if ( selMatch[ sel ] === undefined ) { + selMatch[ sel ] = ( + handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) + ); + } + if ( selMatch[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, matches: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( handlers.length > delegateCount ) { + handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); + } + + // Run delegates first; they may want to stop propagation beneath us + for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { + matched = handlerQueue[ i ]; + event.currentTarget = matched.elem; + + for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { + handleObj = matched.matches[ j ]; + + // Triggered event must either 1) be non-exclusive and have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { + + event.data = handleObj.data; + event.handleObj = handleObj; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 *** + props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = jQuery.Event( originalEvent ); + + for ( i = copy.length; i; ) { + prop = copy[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Target should not be a text node (#504, Safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8) + if ( event.metaKey === undefined ) { + event.metaKey = event.ctrlKey; + } + + return fixHook.filter? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady + }, + + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + + focus: { + delegateType: "focusin" + }, + blur: { + delegateType: "focusout" + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( jQuery.isWindow( this ) ) { + this.onbeforeunload = eventHandle; + } + }, + + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +// Some plugins are using, but it's undocumented/deprecated and will be removed. +// The 1.7 special event interface should provide all the hooks needed now. +jQuery.event.handle = jQuery.event.dispatch; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + if ( elem.detachEvent ) { + elem.detachEvent( "on" + type, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // otherwise set the returnValue property of the original event to false (IE) + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var target = this, + related = event.relatedTarget, + handleObj = event.handleObj, + selector = handleObj.selector, + ret; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !form._submit_attached ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + form._submit_attached = true; + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + jQuery.event.simulate( "change", this, event, true ); + } + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + elem._change_attached = true; + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { // && selector != null + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + var handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( var type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + live: function( types, data, fn ) { + jQuery( this.context ).on( types, this.selector, data, fn ); + return this; + }, + die: function( types, fn ) { + jQuery( this.context ).off( types, this.selector || "**", fn ); + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + if ( this[0] ) { + return jQuery.event.trigger( type, data, this[0], true ); + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, + guid = fn.guid || jQuery.guid++, + i = 0, + toggler = function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + }; + + // link all the functions, so any of them can unbind this click handler + toggler.guid = guid; + while ( i < args.length ) { + args[ i++ ].guid = guid; + } + + return this.click( toggler ); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + if ( fn == null ) { + fn = data; + data = null; + } + + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); + + + +/*! + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + expando = "sizcache" + (Math.random() + '').replace('.', ''), + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true, + rBackslash = /\\/g, + rReturn = /\r\n/g, + rNonWord = /\W/; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function() { + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function( selector, context, results, seed ) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec( "" ); + m = chunker.exec( soFar ); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context, seed ); + + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set, seed ); + } + } + + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray( set ); + + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function( results ) { + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); + } + } + } + } + + return results; +}; + +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); +}; + +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; +}; + +Sizzle.find = function( expr, context, isXML ) { + var set, i, len, match, type, left; + + if ( !expr ) { + return []; + } + + for ( i = 0, len = Expr.order.length; i < len; i++ ) { + type = Expr.order[i]; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + left = match[1]; + match.splice( 1, 1 ); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace( rBackslash, "" ); + set = Expr.find[ type ]( match, context, isXML ); + + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = typeof context.getElementsByTagName !== "undefined" ? + context.getElementsByTagName( "*" ) : + []; + } + + return { set: set, expr: expr }; +}; + +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + type, found, item, filter, left, + i, pass, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); + + while ( expr && set.length ) { + for ( type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + filter = Expr.filter[ type ]; + left = match[1]; + + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + pass = not ^ found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + + } else { + curLoop[i] = false; + } + + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Utility function for retreiving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +var getText = Sizzle.getText = function( elem ) { + var i, node, + nodeType = elem.nodeType, + ret = ""; + + if ( nodeType ) { + if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent || innerText for elements + if ( typeof elem.textContent === 'string' ) { + return elem.textContent; + } else if ( typeof elem.innerText === 'string' ) { + // Replace IE's carriage returns + return elem.innerText.replace( rReturn, '' ); + } else { + // Traverse it's children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + } else { + + // If no nodeType, this is expected to be an array + for ( i = 0; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + if ( node.nodeType !== 8 ) { + ret += getText( node ); + } + } + } + return ret; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + + leftMatch: {}, + + attrMap: { + "class": "className", + "for": "htmlFor" + }, + + attrHandle: { + href: function( elem ) { + return elem.getAttribute( "href" ); + }, + type: function( elem ) { + return elem.getAttribute( "type" ); + } + }, + + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !rNonWord.test( part ), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + + "": function(checkSet, part, isXML){ + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); + }, + + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !rNonWord.test( part ) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); + } + }, + + find: { + ID: function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }, + + NAME: function( match, context ) { + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], + results = context.getElementsByName( match[1] ); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + + TAG: function( match, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( match[1] ); + } + } + }, + preFilter: { + CLASS: function( match, curLoop, inplace, result, not, isXML ) { + match = " " + match[1].replace( rBackslash, "" ) + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + + ID: function( match ) { + return match[1].replace( rBackslash, "" ); + }, + + TAG: function( match, curLoop ) { + return match[1].replace( rBackslash, "" ).toLowerCase(); + }, + + CHILD: function( match ) { + if ( match[1] === "nth" ) { + if ( !match[2] ) { + Sizzle.error( match[0] ); + } + + match[2] = match[2].replace(/^\+|\s*/g, ''); + + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + else if ( match[2] ) { + Sizzle.error( match[0] ); + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { + var name = match[1] = match[1].replace( rBackslash, "" ); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + // Handle if an un-quoted value was used + match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + + PSEUDO: function( match, curLoop, inplace, result, not ) { + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + + if ( !inplace ) { + result.push.apply( result, ret ); + } + + return false; + } + + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + + POS: function( match ) { + match.unshift( true ); + + return match; + } + }, + + filters: { + enabled: function( elem ) { + return elem.disabled === false && elem.type !== "hidden"; + }, + + disabled: function( elem ) { + return elem.disabled === true; + }, + + checked: function( elem ) { + return elem.checked === true; + }, + + selected: function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + parent: function( elem ) { + return !!elem.firstChild; + }, + + empty: function( elem ) { + return !elem.firstChild; + }, + + has: function( elem, i, match ) { + return !!Sizzle( match[3], elem ).length; + }, + + header: function( elem ) { + return (/h\d/i).test( elem.nodeName ); + }, + + text: function( elem ) { + var attr = elem.getAttribute( "type" ), type = elem.type; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); + }, + + radio: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; + }, + + checkbox: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; + }, + + file: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; + }, + + password: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; + }, + + submit: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "submit" === elem.type; + }, + + image: function( elem ) { + return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; + }, + + reset: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && "reset" === elem.type; + }, + + button: function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && "button" === elem.type || name === "button"; + }, + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); + }, + + focus: function( elem ) { + return elem === elem.ownerDocument.activeElement; + } + }, + setFilters: { + first: function( elem, i ) { + return i === 0; + }, + + last: function( elem, i, match, array ) { + return i === array.length - 1; + }, + + even: function( elem, i ) { + return i % 2 === 0; + }, + + odd: function( elem, i ) { + return i % 2 === 1; + }, + + lt: function( elem, i, match ) { + return i < match[3] - 0; + }, + + gt: function( elem, i, match ) { + return i > match[3] - 0; + }, + + nth: function( elem, i, match ) { + return match[3] - 0 === i; + }, + + eq: function( elem, i, match ) { + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + + } else { + Sizzle.error( name ); + } + }, + + CHILD: function( elem, match ) { + var first, last, + doneName, parent, cache, + count, diff, + type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + if ( type === "first" ) { + return true; + } + + node = elem; + + /* falls through */ + case "last": + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + + return true; + + case "nth": + first = match[2]; + last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + doneName = match[0]; + parent = elem.parentNode; + + if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) { + count = 0; + + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + + parent[ expando ] = doneName; + } + + diff = elem.nodeIndex - last; + + if ( first === 0 ) { + return diff === 0; + + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + + ID: function( elem, match ) { + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + + TAG: function( elem, match ) { + return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match; + }, + + CLASS: function( elem, match ) { + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + + ATTR: function( elem, match ) { + var name = match[1], + result = Sizzle.attr ? + Sizzle.attr( elem, name ) : + Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + !type && Sizzle.attr ? + result != null : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} +// Expose origPOS +// "global" as in regardless of relation to brackets/parens +Expr.match.globalPOS = origPOS; + +var makeArray = function( array, results ) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder, siblingCheck; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + return a.compareDocumentPosition ? -1 : 1; + } + + return a.compareDocumentPosition(b) & 4 ? -1 : 1; + }; + +} else { + sortOrder = function( a, b ) { + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return a.sourceIndex - b.sourceIndex; + } + + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; + + // If the nodes are siblings (or identical) we can do a quick check + if ( aup === bup ) { + return siblingCheck( a, b ); + + // If no parents were found then the nodes are disconnected + } else if ( !aup ) { + return -1; + + } else if ( !bup ) { + return 1; + } + + // Otherwise they're somewhere else in the tree so we need + // to build up a full list of the parentNodes for comparison + while ( cur ) { + ap.unshift( cur ); + cur = cur.parentNode; + } + + cur = bup; + + while ( cur ) { + bp.unshift( cur ); + cur = cur.parentNode; + } + + al = ap.length; + bl = bp.length; + + // Start walking down the tree looking for a discrepancy + for ( var i = 0; i < al && i < bl; i++ ) { + if ( ap[i] !== bp[i] ) { + return siblingCheck( ap[i], bp[i] ); + } + } + + // We ended someplace up the tree so do a sibling check + return i === al ? + siblingCheck( a, bp[i], -1 ) : + siblingCheck( ap[i], b, 1 ); + }; + + siblingCheck = function( a, b, ret ) { + if ( a === b ) { + return ret; + } + + var cur = a.nextSibling; + + while ( cur ) { + if ( cur === b ) { + return -1; + } + + cur = cur.nextSibling; + } + + return 1; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(), + root = document.documentElement; + + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function( match, context, isXML ) { + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; + } + }; + + Expr.filter.ID = function( elem, match ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + + // release memory in IE + root = form = null; +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); + }; + } + + // release memory in IE + div = null; +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + + div.innerHTML = "

        "; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function( query, context, extra, seed ) { + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && !Sizzle.isXML(context) ) { + // See if we find a selector to speed up + var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); + + if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { + // Speed-up: Sizzle("TAG") + if ( match[1] ) { + return makeArray( context.getElementsByTagName( query ), extra ); + + // Speed-up: Sizzle(".CLASS") + } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { + return makeArray( context.getElementsByClassName( match[2] ), extra ); + } + } + + if ( context.nodeType === 9 ) { + // Speed-up: Sizzle("body") + // The body element only exists once, optimize finding it + if ( query === "body" && context.body ) { + return makeArray( [ context.body ], extra ); + + // Speed-up: Sizzle("#ID") + } else if ( match && match[3] ) { + var elem = context.getElementById( match[3] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id === match[3] ) { + return makeArray( [ elem ], extra ); + } + + } else { + return makeArray( [], extra ); + } + } + + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(qsaError) {} + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + var oldContext = context, + old = context.getAttribute( "id" ), + nid = old || id, + hasParent = context.parentNode, + relativeHierarchySelector = /^\s*[+~]/.test( query ); + + if ( !old ) { + context.setAttribute( "id", nid ); + } else { + nid = nid.replace( /'/g, "\\$&" ); + } + if ( relativeHierarchySelector && hasParent ) { + context = context.parentNode; + } + + try { + if ( !relativeHierarchySelector || hasParent ) { + return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); + } + + } catch(pseudoError) { + } finally { + if ( !old ) { + oldContext.removeAttribute( "id" ); + } + } + } + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + // release memory in IE + div = null; + })(); +} + +(function(){ + var html = document.documentElement, + matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; + + if ( matches ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9 fails this) + var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), + pseudoWorks = false; + + try { + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( document.documentElement, "[test!='']:sizzle" ); + + } catch( pseudoError ) { + pseudoWorks = true; + } + + Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { + try { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { + var ret = matches.call( node, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || !disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9, so check for that + node.document && node.document.nodeType !== 11 ) { + return ret; + } + } + } catch(e) {} + } + + return Sizzle(expr, null, null, [node]).length > 0; + }; + } +})(); + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
        "; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function( match, context, isXML ) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + // release memory in IE + div = null; +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + + if ( elem ) { + var match = false; + + elem = elem[dir]; + + while ( elem ) { + if ( elem[ expando ] === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem[ expando ] = doneName; + elem.sizset = i; + } + + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; + +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function( selector, context, seed ) { + var match, + tmpSet = [], + later = "", + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet, seed ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +Sizzle.selectors.attrMap = {}; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})(); + + +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + isSimple = /^.[^:#\[\.,]*$/, + slice = Array.prototype.slice, + POS = jQuery.expr.match.globalPOS, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var self = this, + i, l; + + if ( typeof selector !== "string" ) { + return jQuery( selector ).filter(function() { + for ( i = 0, l = self.length; i < l; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }); + } + + var ret = this.pushStack( "", "find", selector ), + length, n, r; + + for ( i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( n = length; n < ret.length; n++ ) { + for ( r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + POS.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var ret = [], i, l, cur = this[0]; + + // Array (deprecated as of jQuery 1.7) + if ( jQuery.isArray( selectors ) ) { + var level = 1; + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( i = 0; i < selectors.length; i++ ) { + + if ( jQuery( cur ).is( selectors[ i ] ) ) { + ret.push({ selector: selectors[ i ], elem: cur, level: level }); + } + } + + cur = cur.parentNode; + level++; + } + + return ret; + } + + // String + var pos = POS.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( i = 0, l = this.length; i < l; i++ ) { + cur = this[i]; + + while ( cur ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + + } else { + cur = cur.parentNode; + if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { + break; + } + } + } + } + + ret = ret.length > 1 ? jQuery.unique( ret ) : ret; + + return this.pushStack( ret, "closest", selectors ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call( arguments ).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} + + + + +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, + rtagName = /<([\w:]+)/, + rtbody = /]", "i"), + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /\/(java|ecma)script/i, + rcleanScript = /^\s*", "" ], + legend: [ 1, "
        ", "
        " ], + thead: [ 1, "", "
        " ], + tr: [ 2, "", "
        " ], + td: [ 3, "", "
        " ], + col: [ 2, "", "
        " ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }, + safeFragment = createSafeFragment( document ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and + * + * ... + * + * + * + * **Note:** the `script` tag containing the template does not need to be included in the `head` of the document, but + * it must be below the `ng-app` definition. + * + * Adding via the $templateCache service: + * + *
        + * var myApp = angular.module('myApp', []);
        + * myApp.run(function($templateCache) {
        + *   $templateCache.put('templateId.html', 'This is the content of the template');
        + * });
        + * 
        + * + * To retrieve the template later, simply use it in your HTML: + *
        + * 
        + *
        + * + * or get it via Javascript: + *
        + * $templateCache.get('templateId.html')
        + * 
        + * + * See {@link ng.$cacheFactory $cacheFactory}. + * + */ +function $TemplateCacheProvider() { + this.$get = ['$cacheFactory', function($cacheFactory) { + return $cacheFactory('templates'); + }]; +} + +/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! + * + * DOM-related variables: + * + * - "node" - DOM Node + * - "element" - DOM Element or Node + * - "$node" or "$element" - jqLite-wrapped node or element + * + * + * Compiler related stuff: + * + * - "linkFn" - linking fn of a single directive + * - "nodeLinkFn" - function that aggregates all linking fns for a particular node + * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node + * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) + */ + + +var NON_ASSIGNABLE_MODEL_EXPRESSION = 'Non-assignable model expression: '; + + +/** + * @ngdoc function + * @name ng.$compile + * @function + * + * @description + * Compiles a piece of HTML string or DOM into a template and produces a template function, which + * can then be used to link {@link ng.$rootScope.Scope scope} and the template together. + * + * The compilation is a process of walking the DOM tree and trying to match DOM elements to + * {@link ng.$compileProvider#directive directives}. For each match it + * executes corresponding template function and collects the + * instance functions into a single template function which is then returned. + * + * The template function can then be used once to produce the view or as it is the case with + * {@link ng.directive:ngRepeat repeater} many-times, in which + * case each call results in a view that is a DOM clone of the original template. + * + + + +
        +
        +
        +
        +
        +
        + + it('should auto compile', function() { + expect(element('div[compile]').text()).toBe('Hello Angular'); + input('html').enter('{{name}}!'); + expect(element('div[compile]').text()).toBe('Angular!'); + }); + +
        + + * + * + * @param {string|DOMElement} element Element or HTML string to compile into a template function. + * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives. + * @param {number} maxPriority only apply directives lower then given priority (Only effects the + * root element(s), not their children) + * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template + * (a DOM element/tree) to a scope. Where: + * + * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. + * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the + * `template` and call the `cloneAttachFn` function allowing the caller to attach the + * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is + * called as:
        `cloneAttachFn(clonedElement, scope)` where: + * + * * `clonedElement` - is a clone of the original `element` passed into the compiler. + * * `scope` - is the current scope with which the linking function is working with. + * + * Calling the linking function returns the element of the template. It is either the original element + * passed in, or the clone of the element if the `cloneAttachFn` is provided. + * + * After linking the view is not updated until after a call to $digest which typically is done by + * Angular automatically. + * + * If you need access to the bound view, there are two ways to do it: + * + * - If you are not asking the linking function to clone the template, create the DOM element(s) + * before you send them to the compiler and keep this reference around. + *
        + *     var element = $compile('

        {{total}}

        ')(scope); + *
        + * + * - if on the other hand, you need the element to be cloned, the view reference from the original + * example would not point to the clone, but rather to the original template that was cloned. In + * this case, you can access the clone via the cloneAttachFn: + *
        + *     var templateHTML = angular.element('

        {{total}}

        '), + * scope = ....; + * + * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) { + * //attach the clone to DOM document at the right place + * }); + * + * //now we have reference to the cloned DOM via `clone` + *
        + * + * + * For information on how the compiler works, see the + * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. + */ + + +/** + * @ngdoc service + * @name ng.$compileProvider + * @function + * + * @description + */ +$CompileProvider.$inject = ['$provide']; +function $CompileProvider($provide) { + var hasDirectives = {}, + Suffix = 'Directive', + COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, + CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, + MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ', + urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/; + + + /** + * @ngdoc function + * @name ng.$compileProvider#directive + * @methodOf ng.$compileProvider + * @function + * + * @description + * Register a new directive with the compiler. + * + * @param {string} name Name of the directive in camel-case. (ie ngBind which will match as + * ng-bind). + * @param {function|Array} directiveFactory An injectable directive factory function. See {@link guide/directive} for more + * info. + * @returns {ng.$compileProvider} Self for chaining. + */ + this.directive = function registerDirective(name, directiveFactory) { + if (isString(name)) { + assertArg(directiveFactory, 'directive'); + if (!hasDirectives.hasOwnProperty(name)) { + hasDirectives[name] = []; + $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', + function($injector, $exceptionHandler) { + var directives = []; + forEach(hasDirectives[name], function(directiveFactory) { + try { + var directive = $injector.invoke(directiveFactory); + if (isFunction(directive)) { + directive = { compile: valueFn(directive) }; + } else if (!directive.compile && directive.link) { + directive.compile = valueFn(directive.link); + } + directive.priority = directive.priority || 0; + directive.name = directive.name || name; + directive.require = directive.require || (directive.controller && directive.name); + directive.restrict = directive.restrict || 'A'; + directives.push(directive); + } catch (e) { + $exceptionHandler(e); + } + }); + return directives; + }]); + } + hasDirectives[name].push(directiveFactory); + } else { + forEach(name, reverseParams(registerDirective)); + } + return this; + }; + + + /** + * @ngdoc function + * @name ng.$compileProvider#urlSanitizationWhitelist + * @methodOf ng.$compileProvider + * @function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during a[href] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to a[href] via data-binding is first normalized and turned into an + * absolute url. Afterwards the url is matched against the `urlSanitizationWhitelist` regular + * expression. If a match is found the original url is written into the dom. Otherwise the + * absolute url is prefixed with `'unsafe:'` string and only then it is written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.urlSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + urlSanitizationWhitelist = regexp; + return this; + } + return urlSanitizationWhitelist; + }; + + + this.$get = [ + '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', + '$controller', '$rootScope', '$document', + function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, + $controller, $rootScope, $document) { + + var Attributes = function(element, attr) { + this.$$element = element; + this.$attr = attr || {}; + }; + + Attributes.prototype = { + $normalize: directiveNormalize, + + + /** + * Set a normalized attribute on the element in a way such that all directives + * can share the attribute. This function properly handles boolean attributes. + * @param {string} key Normalized key. (ie ngAttribute) + * @param {string|boolean} value The value to set. If `null` attribute will be deleted. + * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. + * Defaults to true. + * @param {string=} attrName Optional none normalized name. Defaults to key. + */ + $set: function(key, value, writeAttr, attrName) { + var booleanKey = getBooleanAttrName(this.$$element[0], key), + $$observers = this.$$observers, + normalizedVal; + + if (booleanKey) { + this.$$element.prop(key, value); + attrName = booleanKey; + } + + this[key] = value; + + // translate normalized key to actual key + if (attrName) { + this.$attr[key] = attrName; + } else { + attrName = this.$attr[key]; + if (!attrName) { + this.$attr[key] = attrName = snake_case(key, '-'); + } + } + + + // sanitize a[href] values + if (nodeName_(this.$$element[0]) === 'A' && key === 'href') { + urlSanitizationNode.setAttribute('href', value); + + // href property always returns normalized absolute url, so we can match against that + normalizedVal = urlSanitizationNode.href; + if (normalizedVal !== '' && !normalizedVal.match(urlSanitizationWhitelist)) { + this[key] = value = 'unsafe:' + normalizedVal; + } + } + + + if (writeAttr !== false) { + if (value === null || value === undefined) { + this.$$element.removeAttr(attrName); + } else { + this.$$element.attr(attrName, value); + } + } + + // fire observers + $$observers && forEach($$observers[key], function(fn) { + try { + fn(value); + } catch (e) { + $exceptionHandler(e); + } + }); + }, + + + /** + * Observe an interpolated attribute. + * The observer will never be called, if given attribute is not interpolated. + * + * @param {string} key Normalized key. (ie ngAttribute) . + * @param {function(*)} fn Function that will be called whenever the attribute value changes. + * @returns {function(*)} the `fn` Function passed in. + */ + $observe: function(key, fn) { + var attrs = this, + $$observers = (attrs.$$observers || (attrs.$$observers = {})), + listeners = ($$observers[key] || ($$observers[key] = [])); + + listeners.push(fn); + $rootScope.$evalAsync(function() { + if (!listeners.$$inter) { + // no one registered attribute interpolation function, so lets call it manually + fn(attrs[key]); + } + }); + return fn; + } + }; + + var urlSanitizationNode = $document[0].createElement('a'), + startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(), + denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') + ? identity + : function denormalizeTemplate(template) { + return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); + }; + + + return compile; + + //================================ + + function compile($compileNodes, transcludeFn, maxPriority) { + if (!($compileNodes instanceof jqLite)) { + // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it. + $compileNodes = jqLite($compileNodes); + } + // We can not compile top level text elements since text nodes can be merged and we will + // not be able to attach scope data to them, so we will wrap them in + forEach($compileNodes, function(node, index){ + if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { + $compileNodes[index] = jqLite(node).wrap('').parent()[0]; + } + }); + var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority); + return function publicLinkFn(scope, cloneConnectFn){ + assertArg(scope, 'scope'); + // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart + // and sometimes changes the structure of the DOM. + var $linkNode = cloneConnectFn + ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! + : $compileNodes; + + // Attach scope only to non-text nodes. + for(var i = 0, ii = $linkNode.length; i + addDirective(directives, + directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority); + + // iterate over the attributes + for (var attr, name, nName, value, nAttrs = node.attributes, + j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { + attr = nAttrs[j]; + if (!msie || msie >= 8 || attr.specified) { + name = attr.name; + nName = directiveNormalize(name.toLowerCase()); + attrsMap[nName] = name; + attrs[nName] = value = trim((msie && name == 'href') + ? decodeURIComponent(node.getAttribute(name, 2)) + : attr.value); + if (getBooleanAttrName(node, nName)) { + attrs[nName] = true; // presence means true + } + addAttrInterpolateDirective(node, directives, value, nName); + addDirective(directives, nName, 'A', maxPriority); + } + } + + // use class as directive + className = node.className; + if (isString(className) && className !== '') { + while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { + nName = directiveNormalize(match[2]); + if (addDirective(directives, nName, 'C', maxPriority)) { + attrs[nName] = trim(match[3]); + } + className = className.substr(match.index + match[0].length); + } + } + break; + case 3: /* Text Node */ + addTextInterpolateDirective(directives, node.nodeValue); + break; + case 8: /* Comment */ + try { + match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); + if (match) { + nName = directiveNormalize(match[1]); + if (addDirective(directives, nName, 'M', maxPriority)) { + attrs[nName] = trim(match[2]); + } + } + } catch (e) { + // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value. + // Just ignore it and continue. (Can't seem to reproduce in test case.) + } + break; + } + + directives.sort(byPriority); + return directives; + } + + + /** + * Once the directives have been collected, their compile functions are executed. This method + * is responsible for inlining directive templates as well as terminating the application + * of the directives if the terminal directive has been reached. + * + * @param {Array} directives Array of collected directives to execute their compile function. + * this needs to be pre-sorted by priority order. + * @param {Node} compileNode The raw DOM node to apply the compile functions to + * @param {Object} templateAttrs The shared attribute function + * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the + * scope argument is auto-generated to the new child of the transcluded parent scope. + * @param {JQLite} jqCollection If we are working on the root of the compile tree then this + * argument has the root jqLite array so that we can replace nodes on it. + * @returns linkFn + */ + function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection) { + var terminalPriority = -Number.MAX_VALUE, + preLinkFns = [], + postLinkFns = [], + newScopeDirective = null, + newIsolateScopeDirective = null, + templateDirective = null, + $compileNode = templateAttrs.$$element = jqLite(compileNode), + directive, + directiveName, + $template, + transcludeDirective, + childTranscludeFn = transcludeFn, + controllerDirectives, + linkFn, + directiveValue; + + // executes all directives on the current element + for(var i = 0, ii = directives.length; i < ii; i++) { + directive = directives[i]; + $template = undefined; + + if (terminalPriority > directive.priority) { + break; // prevent further processing of directives + } + + if (directiveValue = directive.scope) { + assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode); + if (isObject(directiveValue)) { + safeAddClass($compileNode, 'ng-isolate-scope'); + newIsolateScopeDirective = directive; + } + safeAddClass($compileNode, 'ng-scope'); + newScopeDirective = newScopeDirective || directive; + } + + directiveName = directive.name; + + if (directiveValue = directive.controller) { + controllerDirectives = controllerDirectives || {}; + assertNoDuplicate("'" + directiveName + "' controller", + controllerDirectives[directiveName], directive, $compileNode); + controllerDirectives[directiveName] = directive; + } + + if (directiveValue = directive.transclude) { + assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode); + transcludeDirective = directive; + terminalPriority = directive.priority; + if (directiveValue == 'element') { + $template = jqLite(compileNode); + $compileNode = templateAttrs.$$element = + jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); + compileNode = $compileNode[0]; + replaceWith(jqCollection, jqLite($template[0]), compileNode); + childTranscludeFn = compile($template, transcludeFn, terminalPriority); + } else { + $template = jqLite(JQLiteClone(compileNode)).contents(); + $compileNode.html(''); // clear contents + childTranscludeFn = compile($template, transcludeFn); + } + } + + if ((directiveValue = directive.template)) { + assertNoDuplicate('template', templateDirective, directive, $compileNode); + templateDirective = directive; + directiveValue = denormalizeTemplate(directiveValue); + + if (directive.replace) { + $template = jqLite('
        ' + + trim(directiveValue) + + '
        ').contents(); + compileNode = $template[0]; + + if ($template.length != 1 || compileNode.nodeType !== 1) { + throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue); + } + + replaceWith(jqCollection, $compileNode, compileNode); + + var newTemplateAttrs = {$attr: {}}; + + // combine directives from the original node and from the template: + // - take the array of directives for this element + // - split it into two parts, those that were already applied and those that weren't + // - collect directives from the template, add them to the second group and sort them + // - append the second group with new directives to the first group + directives = directives.concat( + collectDirectives( + compileNode, + directives.splice(i + 1, directives.length - (i + 1)), + newTemplateAttrs + ) + ); + mergeTemplateAttributes(templateAttrs, newTemplateAttrs); + + ii = directives.length; + } else { + $compileNode.html(directiveValue); + } + } + + if (directive.templateUrl) { + assertNoDuplicate('template', templateDirective, directive, $compileNode); + templateDirective = directive; + nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), + nodeLinkFn, $compileNode, templateAttrs, jqCollection, directive.replace, + childTranscludeFn); + ii = directives.length; + } else if (directive.compile) { + try { + linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); + if (isFunction(linkFn)) { + addLinkFns(null, linkFn); + } else if (linkFn) { + addLinkFns(linkFn.pre, linkFn.post); + } + } catch (e) { + $exceptionHandler(e, startingTag($compileNode)); + } + } + + if (directive.terminal) { + nodeLinkFn.terminal = true; + terminalPriority = Math.max(terminalPriority, directive.priority); + } + + } + + nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope; + nodeLinkFn.transclude = transcludeDirective && childTranscludeFn; + + // might be normal or delayed nodeLinkFn depending on if templateUrl is present + return nodeLinkFn; + + //////////////////// + + function addLinkFns(pre, post) { + if (pre) { + pre.require = directive.require; + preLinkFns.push(pre); + } + if (post) { + post.require = directive.require; + postLinkFns.push(post); + } + } + + + function getControllers(require, $element) { + var value, retrievalMethod = 'data', optional = false; + if (isString(require)) { + while((value = require.charAt(0)) == '^' || value == '?') { + require = require.substr(1); + if (value == '^') { + retrievalMethod = 'inheritedData'; + } + optional = optional || value == '?'; + } + value = $element[retrievalMethod]('$' + require + 'Controller'); + if (!value && !optional) { + throw Error("No controller: " + require); + } + return value; + } else if (isArray(require)) { + value = []; + forEach(require, function(require) { + value.push(getControllers(require, $element)); + }); + } + return value; + } + + + function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { + var attrs, $element, i, ii, linkFn, controller; + + if (compileNode === linkNode) { + attrs = templateAttrs; + } else { + attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); + } + $element = attrs.$$element; + + if (newIsolateScopeDirective) { + var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/; + + var parentScope = scope.$parent || scope; + + forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) { + var match = definiton.match(LOCAL_REGEXP) || [], + attrName = match[2]|| scopeName, + mode = match[1], // @, =, or & + lastValue, + parentGet, parentSet; + + scope.$$isolateBindings[scopeName] = mode + attrName; + + switch (mode) { + + case '@': { + attrs.$observe(attrName, function(value) { + scope[scopeName] = value; + }); + attrs.$$observers[attrName].$$scope = parentScope; + break; + } + + case '=': { + parentGet = $parse(attrs[attrName]); + parentSet = parentGet.assign || function() { + // reset the change, or we will throw this exception on every $digest + lastValue = scope[scopeName] = parentGet(parentScope); + throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] + + ' (directive: ' + newIsolateScopeDirective.name + ')'); + }; + lastValue = scope[scopeName] = parentGet(parentScope); + scope.$watch(function parentValueWatch() { + var parentValue = parentGet(parentScope); + + if (parentValue !== scope[scopeName]) { + // we are out of sync and need to copy + if (parentValue !== lastValue) { + // parent changed and it has precedence + lastValue = scope[scopeName] = parentValue; + } else { + // if the parent can be assigned then do so + parentSet(parentScope, parentValue = lastValue = scope[scopeName]); + } + } + return parentValue; + }); + break; + } + + case '&': { + parentGet = $parse(attrs[attrName]); + scope[scopeName] = function(locals) { + return parentGet(parentScope, locals); + }; + break; + } + + default: { + throw Error('Invalid isolate scope definition for directive ' + + newIsolateScopeDirective.name + ': ' + definiton); + } + } + }); + } + + if (controllerDirectives) { + forEach(controllerDirectives, function(directive) { + var locals = { + $scope: scope, + $element: $element, + $attrs: attrs, + $transclude: boundTranscludeFn + }; + + controller = directive.controller; + if (controller == '@') { + controller = attrs[directive.name]; + } + + $element.data( + '$' + directive.name + 'Controller', + $controller(controller, locals)); + }); + } + + // PRELINKING + for(i = 0, ii = preLinkFns.length; i < ii; i++) { + try { + linkFn = preLinkFns[i]; + linkFn(scope, $element, attrs, + linkFn.require && getControllers(linkFn.require, $element)); + } catch (e) { + $exceptionHandler(e, startingTag($element)); + } + } + + // RECURSION + childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn); + + // POSTLINKING + for(i = 0, ii = postLinkFns.length; i < ii; i++) { + try { + linkFn = postLinkFns[i]; + linkFn(scope, $element, attrs, + linkFn.require && getControllers(linkFn.require, $element)); + } catch (e) { + $exceptionHandler(e, startingTag($element)); + } + } + } + } + + + /** + * looks up the directive and decorates it with exception handling and proper parameters. We + * call this the boundDirective. + * + * @param {string} name name of the directive to look up. + * @param {string} location The directive must be found in specific format. + * String containing any of theses characters: + * + * * `E`: element name + * * `A': attribute + * * `C`: class + * * `M`: comment + * @returns true if directive was added. + */ + function addDirective(tDirectives, name, location, maxPriority) { + var match = false; + if (hasDirectives.hasOwnProperty(name)) { + for(var directive, directives = $injector.get(name + Suffix), + i = 0, ii = directives.length; i directive.priority) && + directive.restrict.indexOf(location) != -1) { + tDirectives.push(directive); + match = true; + } + } catch(e) { $exceptionHandler(e); } + } + } + return match; + } + + + /** + * When the element is replaced with HTML template then the new attributes + * on the template need to be merged with the existing attributes in the DOM. + * The desired effect is to have both of the attributes present. + * + * @param {object} dst destination attributes (original DOM) + * @param {object} src source attributes (from the directive template) + */ + function mergeTemplateAttributes(dst, src) { + var srcAttr = src.$attr, + dstAttr = dst.$attr, + $element = dst.$$element; + + // reapply the old attributes to the new element + forEach(dst, function(value, key) { + if (key.charAt(0) != '$') { + if (src[key]) { + value += (key === 'style' ? ';' : ' ') + src[key]; + } + dst.$set(key, value, true, srcAttr[key]); + } + }); + + // copy the new attributes on the old attrs object + forEach(src, function(value, key) { + if (key == 'class') { + safeAddClass($element, value); + dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; + } else if (key == 'style') { + $element.attr('style', $element.attr('style') + ';' + value); + } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { + dst[key] = value; + dstAttr[key] = srcAttr[key]; + } + }); + } + + + function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs, + $rootElement, replace, childTranscludeFn) { + var linkQueue = [], + afterTemplateNodeLinkFn, + afterTemplateChildLinkFn, + beforeTemplateCompileNode = $compileNode[0], + origAsyncDirective = directives.shift(), + // The fact that we have to copy and patch the directive seems wrong! + derivedSyncDirective = extend({}, origAsyncDirective, { + controller: null, templateUrl: null, transclude: null, scope: null + }); + + $compileNode.html(''); + + $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}). + success(function(content) { + var compileNode, tempTemplateAttrs, $template; + + content = denormalizeTemplate(content); + + if (replace) { + $template = jqLite('
        ' + trim(content) + '
        ').contents(); + compileNode = $template[0]; + + if ($template.length != 1 || compileNode.nodeType !== 1) { + throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content); + } + + tempTemplateAttrs = {$attr: {}}; + replaceWith($rootElement, $compileNode, compileNode); + collectDirectives(compileNode, directives, tempTemplateAttrs); + mergeTemplateAttributes(tAttrs, tempTemplateAttrs); + } else { + compileNode = beforeTemplateCompileNode; + $compileNode.html(content); + } + + directives.unshift(derivedSyncDirective); + afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn); + afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); + + + while(linkQueue.length) { + var controller = linkQueue.pop(), + linkRootElement = linkQueue.pop(), + beforeTemplateLinkNode = linkQueue.pop(), + scope = linkQueue.pop(), + linkNode = compileNode; + + if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { + // it was cloned therefore we have to clone as well. + linkNode = JQLiteClone(compileNode); + replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); + } + + afterTemplateNodeLinkFn(function() { + beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller); + }, scope, linkNode, $rootElement, controller); + } + linkQueue = null; + }). + error(function(response, code, headers, config) { + throw Error('Failed to load template: ' + config.url); + }); + + return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) { + if (linkQueue) { + linkQueue.push(scope); + linkQueue.push(node); + linkQueue.push(rootElement); + linkQueue.push(controller); + } else { + afterTemplateNodeLinkFn(function() { + beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller); + }, scope, node, rootElement, controller); + } + }; + } + + + /** + * Sorting function for bound directives. + */ + function byPriority(a, b) { + return b.priority - a.priority; + } + + + function assertNoDuplicate(what, previousDirective, directive, element) { + if (previousDirective) { + throw Error('Multiple directives [' + previousDirective.name + ', ' + + directive.name + '] asking for ' + what + ' on: ' + startingTag(element)); + } + } + + + function addTextInterpolateDirective(directives, text) { + var interpolateFn = $interpolate(text, true); + if (interpolateFn) { + directives.push({ + priority: 0, + compile: valueFn(function textInterpolateLinkFn(scope, node) { + var parent = node.parent(), + bindings = parent.data('$binding') || []; + bindings.push(interpolateFn); + safeAddClass(parent.data('$binding', bindings), 'ng-binding'); + scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { + node[0].nodeValue = value; + }); + }) + }); + } + } + + + function addAttrInterpolateDirective(node, directives, value, name) { + var interpolateFn = $interpolate(value, true); + + // no interpolation found -> ignore + if (!interpolateFn) return; + + + directives.push({ + priority: 100, + compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) { + var $$observers = (attr.$$observers || (attr.$$observers = {})); + + if (name === 'class') { + // we need to interpolate classes again, in the case the element was replaced + // and therefore the two class attrs got merged - we want to interpolate the result + interpolateFn = $interpolate(attr[name], true); + } + + attr[name] = undefined; + ($$observers[name] || ($$observers[name] = [])).$$inter = true; + (attr.$$observers && attr.$$observers[name].$$scope || scope). + $watch(interpolateFn, function interpolateFnWatchAction(value) { + attr.$set(name, value); + }); + }) + }); + } + + + /** + * This is a special jqLite.replaceWith, which can replace items which + * have no parents, provided that the containing jqLite collection is provided. + * + * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes + * in the root of the tree. + * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell, + * but replace its DOM node reference. + * @param {Node} newNode The new DOM node. + */ + function replaceWith($rootElement, $element, newNode) { + var oldNode = $element[0], + parent = oldNode.parentNode, + i, ii; + + if ($rootElement) { + for(i = 0, ii = $rootElement.length; i < ii; i++) { + if ($rootElement[i] == oldNode) { + $rootElement[i] = newNode; + break; + } + } + } + + if (parent) { + parent.replaceChild(newNode, oldNode); + } + + newNode[jqLite.expando] = oldNode[jqLite.expando]; + $element[0] = newNode; + } + }]; +} + +var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; +/** + * Converts all accepted directives format into proper directive name. + * All of these will become 'myDirective': + * my:DiRective + * my-directive + * x-my-directive + * data-my:directive + * + * Also there is special case for Moz prefix starting with upper case letter. + * @param name Name to normalize + */ +function directiveNormalize(name) { + return camelCase(name.replace(PREFIX_REGEXP, '')); +} + +/** + * @ngdoc object + * @name ng.$compile.directive.Attributes + * @description + * + * A shared object between directive compile / linking functions which contains normalized DOM element + * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed + * since all of these are treated as equivalent in Angular: + * + * + */ + +/** + * @ngdoc property + * @name ng.$compile.directive.Attributes#$attr + * @propertyOf ng.$compile.directive.Attributes + * @returns {object} A map of DOM element attribute names to the normalized name. This is + * needed to do reverse lookup from normalized name back to actual name. + */ + + +/** + * @ngdoc function + * @name ng.$compile.directive.Attributes#$set + * @methodOf ng.$compile.directive.Attributes + * @function + * + * @description + * Set DOM element attribute value. + * + * + * @param {string} name Normalized element attribute name of the property to modify. The name is + * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr} + * property to the original name. + * @param {string} value Value to set the attribute to. + */ + + + +/** + * Closure compiler type information + */ + +function nodesetLinkingFn( + /* angular.Scope */ scope, + /* NodeList */ nodeList, + /* Element */ rootElement, + /* function(Function) */ boundTranscludeFn +){} + +function directiveLinkingFn( + /* nodesetLinkingFn */ nodesetLinkingFn, + /* angular.Scope */ scope, + /* Node */ node, + /* Element */ rootElement, + /* function(Function) */ boundTranscludeFn +){} + +/** + * @ngdoc object + * @name ng.$controllerProvider + * @description + * The {@link ng.$controller $controller service} is used by Angular to create new + * controllers. + * + * This provider allows controller registration via the + * {@link ng.$controllerProvider#register register} method. + */ +function $ControllerProvider() { + var controllers = {}; + + + /** + * @ngdoc function + * @name ng.$controllerProvider#register + * @methodOf ng.$controllerProvider + * @param {string} name Controller name + * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI + * annotations in the array notation). + */ + this.register = function(name, constructor) { + if (isObject(name)) { + extend(controllers, name) + } else { + controllers[name] = constructor; + } + }; + + + this.$get = ['$injector', '$window', function($injector, $window) { + + /** + * @ngdoc function + * @name ng.$controller + * @requires $injector + * + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * * check `window[constructor]` on the global `window` object + * + * @param {Object} locals Injection locals for Controller. + * @return {Object} Instance of given controller. + * + * @description + * `$controller` service is responsible for instantiating controllers. + * + * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into + * a service, so that one can override this service with {@link https://gist.github.com/1649788 + * BC version}. + */ + return function(constructor, locals) { + if(isString(constructor)) { + var name = constructor; + constructor = controllers.hasOwnProperty(name) + ? controllers[name] + : getter(locals.$scope, name, true) || getter($window, name, true); + + assertArgFn(constructor, name, true); + } + + return $injector.instantiate(constructor, locals); + }; + }]; +} + +/** + * @ngdoc object + * @name ng.$document + * @requires $window + * + * @description + * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document` + * element. + */ +function $DocumentProvider(){ + this.$get = ['$window', function(window){ + return jqLite(window.document); + }]; +} + +/** + * @ngdoc function + * @name ng.$exceptionHandler + * @requires $log + * + * @description + * Any uncaught exception in angular expressions is delegated to this service. + * The default implementation simply delegates to `$log.error` which logs it into + * the browser console. + * + * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by + * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. + * + * @param {Error} exception Exception associated with the error. + * @param {string=} cause optional information about the context in which + * the error was thrown. + * + */ +function $ExceptionHandlerProvider() { + this.$get = ['$log', function($log) { + return function(exception, cause) { + $log.error.apply($log, arguments); + }; + }]; +} + +/** + * @ngdoc object + * @name ng.$interpolateProvider + * @function + * + * @description + * + * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. + */ +function $InterpolateProvider() { + var startSymbol = '{{'; + var endSymbol = '}}'; + + /** + * @ngdoc method + * @name ng.$interpolateProvider#startSymbol + * @methodOf ng.$interpolateProvider + * @description + * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. + * + * @param {string=} value new value to set the starting symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. + */ + this.startSymbol = function(value){ + if (value) { + startSymbol = value; + return this; + } else { + return startSymbol; + } + }; + + /** + * @ngdoc method + * @name ng.$interpolateProvider#endSymbol + * @methodOf ng.$interpolateProvider + * @description + * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. + * + * @param {string=} value new value to set the ending symbol to. + * @returns {string|self} Returns the symbol when used as getter and self if used as setter. + */ + this.endSymbol = function(value){ + if (value) { + endSymbol = value; + return this; + } else { + return endSymbol; + } + }; + + + this.$get = ['$parse', function($parse) { + var startSymbolLength = startSymbol.length, + endSymbolLength = endSymbol.length; + + /** + * @ngdoc function + * @name ng.$interpolate + * @function + * + * @requires $parse + * + * @description + * + * Compiles a string with markup into an interpolation function. This service is used by the + * HTML {@link ng.$compile $compile} service for data binding. See + * {@link ng.$interpolateProvider $interpolateProvider} for configuring the + * interpolation markup. + * + * +
        +         var $interpolate = ...; // injected
        +         var exp = $interpolate('Hello {{name}}!');
        +         expect(exp({name:'Angular'}).toEqual('Hello Angular!');
        +       
        + * + * + * @param {string} text The text with markup to interpolate. + * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have + * embedded expression in order to return an interpolation function. Strings with no + * embedded expression will return null for the interpolation function. + * @returns {function(context)} an interpolation function which is used to compute the interpolated + * string. The function has these parameters: + * + * * `context`: an object against which any expressions embedded in the strings are evaluated + * against. + * + */ + function $interpolate(text, mustHaveExpression) { + var startIndex, + endIndex, + index = 0, + parts = [], + length = text.length, + hasInterpolation = false, + fn, + exp, + concat = []; + + while(index < length) { + if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && + ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { + (index != startIndex) && parts.push(text.substring(index, startIndex)); + parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); + fn.exp = exp; + index = endIndex + endSymbolLength; + hasInterpolation = true; + } else { + // we did not find anything, so we have to add the remainder to the parts array + (index != length) && parts.push(text.substring(index)); + index = length; + } + } + + if (!(length = parts.length)) { + // we added, nothing, must have been an empty string. + parts.push(''); + length = 1; + } + + if (!mustHaveExpression || hasInterpolation) { + concat.length = length; + fn = function(context) { + for(var i = 0, ii = length, part; i html5 url + } else { + return composeProtocolHostPort(match.protocol, match.host, match.port) + + pathPrefixFromBase(basePath) + match.hash.substr(hashPrefix.length); + } +} + + +function convertToHashbangUrl(url, basePath, hashPrefix) { + var match = matchUrl(url); + + // already hashbang url + if (decodeURIComponent(match.path) == basePath && !isUndefined(match.hash) && + match.hash.indexOf(hashPrefix) === 0) { + return url; + // convert html5 url -> hashbang url + } else { + var search = match.search && '?' + match.search || '', + hash = match.hash && '#' + match.hash || '', + pathPrefix = pathPrefixFromBase(basePath), + path = match.path.substr(pathPrefix.length); + + if (match.path.indexOf(pathPrefix) !== 0) { + throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !'); + } + + return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath + + '#' + hashPrefix + path + search + hash; + } +} + + +/** + * LocationUrl represents an url + * This object is exposed as $location service when HTML5 mode is enabled and supported + * + * @constructor + * @param {string} url HTML5 url + * @param {string} pathPrefix + */ +function LocationUrl(url, pathPrefix, appBaseUrl) { + pathPrefix = pathPrefix || ''; + + /** + * Parse given html5 (regular) url string into properties + * @param {string} newAbsoluteUrl HTML5 url + * @private + */ + this.$$parse = function(newAbsoluteUrl) { + var match = matchUrl(newAbsoluteUrl, this); + + if (match.path.indexOf(pathPrefix) !== 0) { + throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !'); + } + + this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length)); + this.$$search = parseKeyValue(match.search); + this.$$hash = match.hash && decodeURIComponent(match.hash) || ''; + + this.$$compose(); + }; + + /** + * Compose url and update `absUrl` property + * @private + */ + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) + + pathPrefix + this.$$url; + }; + + + this.$$rewriteAppUrl = function(absoluteLinkUrl) { + if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) { + return absoluteLinkUrl; + } + } + + + this.$$parse(url); +} + + +/** + * LocationHashbangUrl represents url + * This object is exposed as $location service when html5 history api is disabled or not supported + * + * @constructor + * @param {string} url Legacy url + * @param {string} hashPrefix Prefix for hash part (containing path and search) + */ +function LocationHashbangUrl(url, hashPrefix, appBaseUrl) { + var basePath; + + /** + * Parse given hashbang url into properties + * @param {string} url Hashbang url + * @private + */ + this.$$parse = function(url) { + var match = matchUrl(url, this); + + + if (match.hash && match.hash.indexOf(hashPrefix) !== 0) { + throw Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !'); + } + + basePath = match.path + (match.search ? '?' + match.search : ''); + match = HASH_MATCH.exec((match.hash || '').substr(hashPrefix.length)); + if (match[1]) { + this.$$path = (match[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match[1]); + } else { + this.$$path = ''; + } + + this.$$search = parseKeyValue(match[3]); + this.$$hash = match[5] && decodeURIComponent(match[5]) || ''; + + this.$$compose(); + }; + + /** + * Compose hashbang url and update `absUrl` property + * @private + */ + this.$$compose = function() { + var search = toKeyValue(this.$$search), + hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; + + this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; + this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) + + basePath + (this.$$url ? '#' + hashPrefix + this.$$url : ''); + }; + + this.$$rewriteAppUrl = function(absoluteLinkUrl) { + if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) { + return absoluteLinkUrl; + } + } + + + this.$$parse(url); +} + + +LocationUrl.prototype = { + + /** + * Has any change been replacing ? + * @private + */ + $$replace: false, + + /** + * @ngdoc method + * @name ng.$location#absUrl + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return full url representation with all segments encoded according to rules specified in + * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. + * + * @return {string} full url + */ + absUrl: locationGetter('$$absUrl'), + + /** + * @ngdoc method + * @name ng.$location#url + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return url (e.g. `/path?a=b#hash`) when called without any parameter. + * + * Change path, search and hash, when called with parameter and return `$location`. + * + * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) + * @return {string} url + */ + url: function(url, replace) { + if (isUndefined(url)) + return this.$$url; + + var match = PATH_MATCH.exec(url); + if (match[1]) this.path(decodeURIComponent(match[1])); + if (match[2] || match[1]) this.search(match[3] || ''); + this.hash(match[5] || '', replace); + + return this; + }, + + /** + * @ngdoc method + * @name ng.$location#protocol + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return protocol of current url. + * + * @return {string} protocol of current url + */ + protocol: locationGetter('$$protocol'), + + /** + * @ngdoc method + * @name ng.$location#host + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return host of current url. + * + * @return {string} host of current url. + */ + host: locationGetter('$$host'), + + /** + * @ngdoc method + * @name ng.$location#port + * @methodOf ng.$location + * + * @description + * This method is getter only. + * + * Return port of current url. + * + * @return {Number} port + */ + port: locationGetter('$$port'), + + /** + * @ngdoc method + * @name ng.$location#path + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return path of current url when called without any parameter. + * + * Change path when called with parameter and return `$location`. + * + * Note: Path should always begin with forward slash (/), this method will add the forward slash + * if it is missing. + * + * @param {string=} path New path + * @return {string} path + */ + path: locationGetterSetter('$$path', function(path) { + return path.charAt(0) == '/' ? path : '/' + path; + }), + + /** + * @ngdoc method + * @name ng.$location#search + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return search part (as object) of current url when called without any parameter. + * + * Change search part when called with parameter and return `$location`. + * + * @param {string|object=} search New search params - string or hash object + * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a + * single search parameter. If the value is `null`, the parameter will be deleted. + * + * @return {string} search + */ + search: function(search, paramValue) { + if (isUndefined(search)) + return this.$$search; + + if (isDefined(paramValue)) { + if (paramValue === null) { + delete this.$$search[search]; + } else { + this.$$search[search] = paramValue; + } + } else { + this.$$search = isString(search) ? parseKeyValue(search) : search; + } + + this.$$compose(); + return this; + }, + + /** + * @ngdoc method + * @name ng.$location#hash + * @methodOf ng.$location + * + * @description + * This method is getter / setter. + * + * Return hash fragment when called without any parameter. + * + * Change hash fragment when called with parameter and return `$location`. + * + * @param {string=} hash New hash fragment + * @return {string} hash + */ + hash: locationGetterSetter('$$hash', identity), + + /** + * @ngdoc method + * @name ng.$location#replace + * @methodOf ng.$location + * + * @description + * If called, all changes to $location during current `$digest` will be replacing current history + * record, instead of adding new one. + */ + replace: function() { + this.$$replace = true; + return this; + } +}; + +LocationHashbangUrl.prototype = inherit(LocationUrl.prototype); + +function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) { + LocationHashbangUrl.apply(this, arguments); + + + this.$$rewriteAppUrl = function(absoluteLinkUrl) { + if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) { + return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length); + } + } +} + +LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype); + +function locationGetter(property) { + return function() { + return this[property]; + }; +} + + +function locationGetterSetter(property, preprocess) { + return function(value) { + if (isUndefined(value)) + return this[property]; + + this[property] = preprocess(value); + this.$$compose(); + + return this; + }; +} + + +/** + * @ngdoc object + * @name ng.$location + * + * @requires $browser + * @requires $sniffer + * @requires $rootElement + * + * @description + * The $location service parses the URL in the browser address bar (based on the + * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL + * available to your application. Changes to the URL in the address bar are reflected into + * $location service and changes to $location are reflected into the browser address bar. + * + * **The $location service:** + * + * - Exposes the current URL in the browser address bar, so you can + * - Watch and observe the URL. + * - Change the URL. + * - Synchronizes the URL with the browser when the user + * - Changes the address bar. + * - Clicks the back or forward button (or clicks a History link). + * - Clicks on a link. + * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). + * + * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular + * Services: Using $location} + */ + +/** + * @ngdoc object + * @name ng.$locationProvider + * @description + * Use the `$locationProvider` to configure how the application deep linking paths are stored. + */ +function $LocationProvider(){ + var hashPrefix = '', + html5Mode = false; + + /** + * @ngdoc property + * @name ng.$locationProvider#hashPrefix + * @methodOf ng.$locationProvider + * @description + * @param {string=} prefix Prefix for hash part (containing path and search) + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.hashPrefix = function(prefix) { + if (isDefined(prefix)) { + hashPrefix = prefix; + return this; + } else { + return hashPrefix; + } + }; + + /** + * @ngdoc property + * @name ng.$locationProvider#html5Mode + * @methodOf ng.$locationProvider + * @description + * @param {string=} mode Use HTML5 strategy if available. + * @returns {*} current value if used as getter or itself (chaining) if used as setter + */ + this.html5Mode = function(mode) { + if (isDefined(mode)) { + html5Mode = mode; + return this; + } else { + return html5Mode; + } + }; + + this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', + function( $rootScope, $browser, $sniffer, $rootElement) { + var $location, + basePath, + pathPrefix, + initUrl = $browser.url(), + initUrlParts = matchUrl(initUrl), + appBaseUrl; + + if (html5Mode) { + basePath = $browser.baseHref() || '/'; + pathPrefix = pathPrefixFromBase(basePath); + appBaseUrl = + composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) + + pathPrefix + '/'; + + if ($sniffer.history) { + $location = new LocationUrl( + convertToHtml5Url(initUrl, basePath, hashPrefix), + pathPrefix, appBaseUrl); + } else { + $location = new LocationHashbangInHtml5Url( + convertToHashbangUrl(initUrl, basePath, hashPrefix), + hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1)); + } + } else { + appBaseUrl = + composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) + + (initUrlParts.path || '') + + (initUrlParts.search ? ('?' + initUrlParts.search) : '') + + '#' + hashPrefix + '/'; + + $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl); + } + + $rootElement.bind('click', function(event) { + // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) + // currently we open nice url link and redirect then + + if (event.ctrlKey || event.metaKey || event.which == 2) return; + + var elm = jqLite(event.target); + + // traverse the DOM up to find first A tag + while (lowercase(elm[0].nodeName) !== 'a') { + // ignore rewriting if no A tag (reached root element, or no parent - removed from document) + if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; + } + + var absHref = elm.prop('href'), + rewrittenUrl = $location.$$rewriteAppUrl(absHref); + + if (absHref && !elm.attr('target') && rewrittenUrl) { + // update location manually + $location.$$parse(rewrittenUrl); + $rootScope.$apply(); + event.preventDefault(); + // hack to work around FF6 bug 684208 when scenario runner clicks on links + window.angular['ff-684208-preventDefault'] = true; + } + }); + + + // rewrite hashbang url <> html5 url + if ($location.absUrl() != initUrl) { + $browser.url($location.absUrl(), true); + } + + // update $location when $browser url changes + $browser.onUrlChange(function(newUrl) { + if ($location.absUrl() != newUrl) { + if ($rootScope.$broadcast('$locationChangeStart', newUrl, $location.absUrl()).defaultPrevented) { + $browser.url($location.absUrl()); + return; + } + $rootScope.$evalAsync(function() { + var oldUrl = $location.absUrl(); + + $location.$$parse(newUrl); + afterLocationChange(oldUrl); + }); + if (!$rootScope.$$phase) $rootScope.$digest(); + } + }); + + // update browser + var changeCounter = 0; + $rootScope.$watch(function $locationWatch() { + var oldUrl = $browser.url(); + var currentReplace = $location.$$replace; + + if (!changeCounter || oldUrl != $location.absUrl()) { + changeCounter++; + $rootScope.$evalAsync(function() { + if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). + defaultPrevented) { + $location.$$parse(oldUrl); + } else { + $browser.url($location.absUrl(), currentReplace); + afterLocationChange(oldUrl); + } + }); + } + $location.$$replace = false; + + return changeCounter; + }); + + return $location; + + function afterLocationChange(oldUrl) { + $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); + } +}]; +} + +/** + * @ngdoc object + * @name ng.$log + * @requires $window + * + * @description + * Simple service for logging. Default implementation writes the message + * into the browser's console (if present). + * + * The main purpose of this service is to simplify debugging and troubleshooting. + * + * @example + + + function LogCtrl($scope, $log) { + $scope.$log = $log; + $scope.message = 'Hello World!'; + } + + +
        +

        Reload this page with open console, enter text and hit the log button...

        + Message: + + + + + +
        +
        +
        + */ + +function $LogProvider(){ + this.$get = ['$window', function($window){ + return { + /** + * @ngdoc method + * @name ng.$log#log + * @methodOf ng.$log + * + * @description + * Write a log message + */ + log: consoleLog('log'), + + /** + * @ngdoc method + * @name ng.$log#warn + * @methodOf ng.$log + * + * @description + * Write a warning message + */ + warn: consoleLog('warn'), + + /** + * @ngdoc method + * @name ng.$log#info + * @methodOf ng.$log + * + * @description + * Write an information message + */ + info: consoleLog('info'), + + /** + * @ngdoc method + * @name ng.$log#error + * @methodOf ng.$log + * + * @description + * Write an error message + */ + error: consoleLog('error') + }; + + function formatError(arg) { + if (arg instanceof Error) { + if (arg.stack) { + arg = (arg.message && arg.stack.indexOf(arg.message) === -1) + ? 'Error: ' + arg.message + '\n' + arg.stack + : arg.stack; + } else if (arg.sourceURL) { + arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; + } + } + return arg; + } + + function consoleLog(type) { + var console = $window.console || {}, + logFn = console[type] || console.log || noop; + + if (logFn.apply) { + return function() { + var args = []; + forEach(arguments, function(arg) { + args.push(formatError(arg)); + }); + return logFn.apply(console, args); + }; + } + + // we are IE which either doesn't have window.console => this is noop and we do nothing, + // or we are IE where console.log doesn't have apply so we log at least first 2 args + return function(arg1, arg2) { + logFn(arg1, arg2); + } + } + }]; +} + +var OPERATORS = { + 'null':function(){return null;}, + 'true':function(){return true;}, + 'false':function(){return false;}, + undefined:noop, + '+':function(self, locals, a,b){ + a=a(self, locals); b=b(self, locals); + if (isDefined(a)) { + if (isDefined(b)) { + return a + b; + } + return a; + } + return isDefined(b)?b:undefined;}, + '-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);}, + '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, + '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, + '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, + '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, + '=':noop, + '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, + '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, + '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, + '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, + '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, + '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, + '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, + '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, +// '|':function(self, locals, a,b){return a|b;}, + '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, + '!':function(self, locals, a){return !a(self, locals);} +}; +var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; + +function lex(text, csp){ + var tokens = [], + token, + index = 0, + json = [], + ch, + lastCh = ':'; // can start regexp + + while (index < text.length) { + ch = text.charAt(index); + if (is('"\'')) { + readString(ch); + } else if (isNumber(ch) || is('.') && isNumber(peek())) { + readNumber(); + } else if (isIdent(ch)) { + readIdent(); + // identifiers can only be if the preceding char was a { or , + if (was('{,') && json[0]=='{' && + (token=tokens[tokens.length-1])) { + token.json = token.text.indexOf('.') == -1; + } + } else if (is('(){}[].,;:')) { + tokens.push({ + index:index, + text:ch, + json:(was(':[,') && is('{[')) || is('}]:,') + }); + if (is('{[')) json.unshift(ch); + if (is('}]')) json.shift(); + index++; + } else if (isWhitespace(ch)) { + index++; + continue; + } else { + var ch2 = ch + peek(), + fn = OPERATORS[ch], + fn2 = OPERATORS[ch2]; + if (fn2) { + tokens.push({index:index, text:ch2, fn:fn2}); + index += 2; + } else if (fn) { + tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')}); + index += 1; + } else { + throwError("Unexpected next character ", index, index+1); + } + } + lastCh = ch; + } + return tokens; + + function is(chars) { + return chars.indexOf(ch) != -1; + } + + function was(chars) { + return chars.indexOf(lastCh) != -1; + } + + function peek() { + return index + 1 < text.length ? text.charAt(index + 1) : false; + } + function isNumber(ch) { + return '0' <= ch && ch <= '9'; + } + function isWhitespace(ch) { + return ch == ' ' || ch == '\r' || ch == '\t' || + ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0 + } + function isIdent(ch) { + return 'a' <= ch && ch <= 'z' || + 'A' <= ch && ch <= 'Z' || + '_' == ch || ch == '$'; + } + function isExpOperator(ch) { + return ch == '-' || ch == '+' || isNumber(ch); + } + + function throwError(error, start, end) { + end = end || index; + throw Error("Lexer Error: " + error + " at column" + + (isDefined(start) + ? "s " + start + "-" + index + " [" + text.substring(start, end) + "]" + : " " + end) + + " in expression [" + text + "]."); + } + + function readNumber() { + var number = ""; + var start = index; + while (index < text.length) { + var ch = lowercase(text.charAt(index)); + if (ch == '.' || isNumber(ch)) { + number += ch; + } else { + var peekCh = peek(); + if (ch == 'e' && isExpOperator(peekCh)) { + number += ch; + } else if (isExpOperator(ch) && + peekCh && isNumber(peekCh) && + number.charAt(number.length - 1) == 'e') { + number += ch; + } else if (isExpOperator(ch) && + (!peekCh || !isNumber(peekCh)) && + number.charAt(number.length - 1) == 'e') { + throwError('Invalid exponent'); + } else { + break; + } + } + index++; + } + number = 1 * number; + tokens.push({index:start, text:number, json:true, + fn:function() {return number;}}); + } + function readIdent() { + var ident = "", + start = index, + lastDot, peekIndex, methodName, ch; + + while (index < text.length) { + ch = text.charAt(index); + if (ch == '.' || isIdent(ch) || isNumber(ch)) { + if (ch == '.') lastDot = index; + ident += ch; + } else { + break; + } + index++; + } + + //check if this is not a method invocation and if it is back out to last dot + if (lastDot) { + peekIndex = index; + while(peekIndex < text.length) { + ch = text.charAt(peekIndex); + if (ch == '(') { + methodName = ident.substr(lastDot - start + 1); + ident = ident.substr(0, lastDot - start); + index = peekIndex; + break; + } + if(isWhitespace(ch)) { + peekIndex++; + } else { + break; + } + } + } + + + var token = { + index:start, + text:ident + }; + + if (OPERATORS.hasOwnProperty(ident)) { + token.fn = token.json = OPERATORS[ident]; + } else { + var getter = getterFn(ident, csp); + token.fn = extend(function(self, locals) { + return (getter(self, locals)); + }, { + assign: function(self, value) { + return setter(self, ident, value); + } + }); + } + + tokens.push(token); + + if (methodName) { + tokens.push({ + index:lastDot, + text: '.', + json: false + }); + tokens.push({ + index: lastDot + 1, + text: methodName, + json: false + }); + } + } + + function readString(quote) { + var start = index; + index++; + var string = ""; + var rawString = quote; + var escape = false; + while (index < text.length) { + var ch = text.charAt(index); + rawString += ch; + if (escape) { + if (ch == 'u') { + var hex = text.substring(index + 1, index + 5); + if (!hex.match(/[\da-f]{4}/i)) + throwError( "Invalid unicode escape [\\u" + hex + "]"); + index += 4; + string += String.fromCharCode(parseInt(hex, 16)); + } else { + var rep = ESCAPE[ch]; + if (rep) { + string += rep; + } else { + string += ch; + } + } + escape = false; + } else if (ch == '\\') { + escape = true; + } else if (ch == quote) { + index++; + tokens.push({ + index:start, + text:rawString, + string:string, + json:true, + fn:function() { return string; } + }); + return; + } else { + string += ch; + } + index++; + } + throwError("Unterminated quote", start); + } +} + +///////////////////////////////////////// + +function parser(text, json, $filter, csp){ + var ZERO = valueFn(0), + value, + tokens = lex(text, csp), + assignment = _assignment, + functionCall = _functionCall, + fieldAccess = _fieldAccess, + objectIndex = _objectIndex, + filterChain = _filterChain; + + if(json){ + // The extra level of aliasing is here, just in case the lexer misses something, so that + // we prevent any accidental execution in JSON. + assignment = logicalOR; + functionCall = + fieldAccess = + objectIndex = + filterChain = + function() { throwError("is not valid json", {text:text, index:0}); }; + value = primary(); + } else { + value = statements(); + } + if (tokens.length !== 0) { + throwError("is an unexpected token", tokens[0]); + } + return value; + + /////////////////////////////////// + function throwError(msg, token) { + throw Error("Syntax Error: Token '" + token.text + + "' " + msg + " at column " + + (token.index + 1) + " of the expression [" + + text + "] starting at [" + text.substring(token.index) + "]."); + } + + function peekToken() { + if (tokens.length === 0) + throw Error("Unexpected end of expression: " + text); + return tokens[0]; + } + + function peek(e1, e2, e3, e4) { + if (tokens.length > 0) { + var token = tokens[0]; + var t = token.text; + if (t==e1 || t==e2 || t==e3 || t==e4 || + (!e1 && !e2 && !e3 && !e4)) { + return token; + } + } + return false; + } + + function expect(e1, e2, e3, e4){ + var token = peek(e1, e2, e3, e4); + if (token) { + if (json && !token.json) { + throwError("is not valid json", token); + } + tokens.shift(); + return token; + } + return false; + } + + function consume(e1){ + if (!expect(e1)) { + throwError("is unexpected, expecting [" + e1 + "]", peek()); + } + } + + function unaryFn(fn, right) { + return function(self, locals) { + return fn(self, locals, right); + }; + } + + function binaryFn(left, fn, right) { + return function(self, locals) { + return fn(self, locals, left, right); + }; + } + + function statements() { + var statements = []; + while(true) { + if (tokens.length > 0 && !peek('}', ')', ';', ']')) + statements.push(filterChain()); + if (!expect(';')) { + // optimize for the common case where there is only one statement. + // TODO(size): maybe we should not support multiple statements? + return statements.length == 1 + ? statements[0] + : function(self, locals){ + var value; + for ( var i = 0; i < statements.length; i++) { + var statement = statements[i]; + if (statement) + value = statement(self, locals); + } + return value; + }; + } + } + } + + function _filterChain() { + var left = expression(); + var token; + while(true) { + if ((token = expect('|'))) { + left = binaryFn(left, token.fn, filter()); + } else { + return left; + } + } + } + + function filter() { + var token = expect(); + var fn = $filter(token.text); + var argsFn = []; + while(true) { + if ((token = expect(':'))) { + argsFn.push(expression()); + } else { + var fnInvoke = function(self, locals, input){ + var args = [input]; + for ( var i = 0; i < argsFn.length; i++) { + args.push(argsFn[i](self, locals)); + } + return fn.apply(self, args); + }; + return function() { + return fnInvoke; + }; + } + } + } + + function expression() { + return assignment(); + } + + function _assignment() { + var left = logicalOR(); + var right; + var token; + if ((token = expect('='))) { + if (!left.assign) { + throwError("implies assignment but [" + + text.substring(0, token.index) + "] can not be assigned to", token); + } + right = logicalOR(); + return function(scope, locals){ + return left.assign(scope, right(scope, locals), locals); + }; + } else { + return left; + } + } + + function logicalOR() { + var left = logicalAND(); + var token; + while(true) { + if ((token = expect('||'))) { + left = binaryFn(left, token.fn, logicalAND()); + } else { + return left; + } + } + } + + function logicalAND() { + var left = equality(); + var token; + if ((token = expect('&&'))) { + left = binaryFn(left, token.fn, logicalAND()); + } + return left; + } + + function equality() { + var left = relational(); + var token; + if ((token = expect('==','!='))) { + left = binaryFn(left, token.fn, equality()); + } + return left; + } + + function relational() { + var left = additive(); + var token; + if ((token = expect('<', '>', '<=', '>='))) { + left = binaryFn(left, token.fn, relational()); + } + return left; + } + + function additive() { + var left = multiplicative(); + var token; + while ((token = expect('+','-'))) { + left = binaryFn(left, token.fn, multiplicative()); + } + return left; + } + + function multiplicative() { + var left = unary(); + var token; + while ((token = expect('*','/','%'))) { + left = binaryFn(left, token.fn, unary()); + } + return left; + } + + function unary() { + var token; + if (expect('+')) { + return primary(); + } else if ((token = expect('-'))) { + return binaryFn(ZERO, token.fn, unary()); + } else if ((token = expect('!'))) { + return unaryFn(token.fn, unary()); + } else { + return primary(); + } + } + + + function primary() { + var primary; + if (expect('(')) { + primary = filterChain(); + consume(')'); + } else if (expect('[')) { + primary = arrayDeclaration(); + } else if (expect('{')) { + primary = object(); + } else { + var token = expect(); + primary = token.fn; + if (!primary) { + throwError("not a primary expression", token); + } + } + + var next, context; + while ((next = expect('(', '[', '.'))) { + if (next.text === '(') { + primary = functionCall(primary, context); + context = null; + } else if (next.text === '[') { + context = primary; + primary = objectIndex(primary); + } else if (next.text === '.') { + context = primary; + primary = fieldAccess(primary); + } else { + throwError("IMPOSSIBLE"); + } + } + return primary; + } + + function _fieldAccess(object) { + var field = expect().text; + var getter = getterFn(field, csp); + return extend( + function(scope, locals, self) { + return getter(self || object(scope, locals), locals); + }, + { + assign:function(scope, value, locals) { + return setter(object(scope, locals), field, value); + } + } + ); + } + + function _objectIndex(obj) { + var indexFn = expression(); + consume(']'); + return extend( + function(self, locals){ + var o = obj(self, locals), + i = indexFn(self, locals), + v, p; + + if (!o) return undefined; + v = o[i]; + if (v && v.then) { + p = v; + if (!('$$v' in v)) { + p.$$v = undefined; + p.then(function(val) { p.$$v = val; }); + } + v = v.$$v; + } + return v; + }, { + assign:function(self, value, locals){ + return obj(self, locals)[indexFn(self, locals)] = value; + } + }); + } + + function _functionCall(fn, contextGetter) { + var argsFn = []; + if (peekToken().text != ')') { + do { + argsFn.push(expression()); + } while (expect(',')); + } + consume(')'); + return function(scope, locals){ + var args = [], + context = contextGetter ? contextGetter(scope, locals) : scope; + + for ( var i = 0; i < argsFn.length; i++) { + args.push(argsFn[i](scope, locals)); + } + var fnPtr = fn(scope, locals, context) || noop; + // IE stupidity! + return fnPtr.apply + ? fnPtr.apply(context, args) + : fnPtr(args[0], args[1], args[2], args[3], args[4]); + }; + } + + // This is used with json array declaration + function arrayDeclaration () { + var elementFns = []; + if (peekToken().text != ']') { + do { + elementFns.push(expression()); + } while (expect(',')); + } + consume(']'); + return function(self, locals){ + var array = []; + for ( var i = 0; i < elementFns.length; i++) { + array.push(elementFns[i](self, locals)); + } + return array; + }; + } + + function object () { + var keyValues = []; + if (peekToken().text != '}') { + do { + var token = expect(), + key = token.string || token.text; + consume(":"); + var value = expression(); + keyValues.push({key:key, value:value}); + } while (expect(',')); + } + consume('}'); + return function(self, locals){ + var object = {}; + for ( var i = 0; i < keyValues.length; i++) { + var keyValue = keyValues[i]; + object[keyValue.key] = keyValue.value(self, locals); + } + return object; + }; + } +} + +////////////////////////////////////////////////// +// Parser helper functions +////////////////////////////////////////////////// + +function setter(obj, path, setValue) { + var element = path.split('.'); + for (var i = 0; element.length > 1; i++) { + var key = element.shift(); + var propertyObj = obj[key]; + if (!propertyObj) { + propertyObj = {}; + obj[key] = propertyObj; + } + obj = propertyObj; + } + obj[element.shift()] = setValue; + return setValue; +} + +var getterFnCache = {}; + +/** + * Implementation of the "Black Hole" variant from: + * - http://jsperf.com/angularjs-parse-getter/4 + * - http://jsperf.com/path-evaluation-simplified/7 + */ +function cspSafeGetterFn(key0, key1, key2, key3, key4) { + return function(scope, locals) { + var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, + promise; + + if (pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key0]; + if (pathVal && pathVal.then) { + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key1 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key1]; + if (pathVal && pathVal.then) { + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key2 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key2]; + if (pathVal && pathVal.then) { + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key3 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key3]; + if (pathVal && pathVal.then) { + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + if (!key4 || pathVal === null || pathVal === undefined) return pathVal; + + pathVal = pathVal[key4]; + if (pathVal && pathVal.then) { + if (!("$$v" in pathVal)) { + promise = pathVal; + promise.$$v = undefined; + promise.then(function(val) { promise.$$v = val; }); + } + pathVal = pathVal.$$v; + } + return pathVal; + }; +} + +function getterFn(path, csp) { + if (getterFnCache.hasOwnProperty(path)) { + return getterFnCache[path]; + } + + var pathKeys = path.split('.'), + pathKeysLength = pathKeys.length, + fn; + + if (csp) { + fn = (pathKeysLength < 6) + ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4]) + : function(scope, locals) { + var i = 0, val; + do { + val = cspSafeGetterFn( + pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++] + )(scope, locals); + + locals = undefined; // clear after first iteration + scope = val; + } while (i < pathKeysLength); + return val; + } + } else { + var code = 'var l, fn, p;\n'; + forEach(pathKeys, function(key, index) { + code += 'if(s === null || s === undefined) return s;\n' + + 'l=s;\n' + + 's='+ (index + // we simply dereference 's' on any .dot notation + ? 's' + // but if we are first then we check locals first, and if so read it first + : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + + 'if (s && s.then) {\n' + + ' if (!("$$v" in s)) {\n' + + ' p=s;\n' + + ' p.$$v = undefined;\n' + + ' p.then(function(v) {p.$$v=v;});\n' + + '}\n' + + ' s=s.$$v\n' + + '}\n'; + }); + code += 'return s;'; + fn = Function('s', 'k', code); // s=scope, k=locals + fn.toString = function() { return code; }; + } + + return getterFnCache[path] = fn; +} + +/////////////////////////////////// + +/** + * @ngdoc function + * @name ng.$parse + * @function + * + * @description + * + * Converts Angular {@link guide/expression expression} into a function. + * + *
        + *   var getter = $parse('user.name');
        + *   var setter = getter.assign;
        + *   var context = {user:{name:'angular'}};
        + *   var locals = {user:{name:'local'}};
        + *
        + *   expect(getter(context)).toEqual('angular');
        + *   setter(context, 'newValue');
        + *   expect(context.user.name).toEqual('newValue');
        + *   expect(getter(context, locals)).toEqual('local');
        + * 
        + * + * + * @param {string} expression String expression to compile. + * @returns {function(context, locals)} a function which represents the compiled expression: + * + * * `context` – `{object}` – an object against which any expressions embedded in the strings + * are evaluated against (tipically a scope object). + * * `locals` – `{object=}` – local variables context object, useful for overriding values in + * `context`. + * + * The return function also has an `assign` property, if the expression is assignable, which + * allows one to set values to expressions. + * + */ +function $ParseProvider() { + var cache = {}; + this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { + return function(exp) { + switch(typeof exp) { + case 'string': + return cache.hasOwnProperty(exp) + ? cache[exp] + : cache[exp] = parser(exp, false, $filter, $sniffer.csp); + case 'function': + return exp; + default: + return noop; + } + }; + }]; +} + +/** + * @ngdoc service + * @name ng.$q + * @requires $rootScope + * + * @description + * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). + * + * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an + * interface for interacting with an object that represents the result of an action that is + * performed asynchronously, and may or may not be finished at any given point in time. + * + * From the perspective of dealing with error handling, deferred and promise APIs are to + * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. + * + *
        + *   // for the purpose of this example let's assume that variables `$q` and `scope` are
        + *   // available in the current lexical scope (they could have been injected or passed in).
        + *
        + *   function asyncGreet(name) {
        + *     var deferred = $q.defer();
        + *
        + *     setTimeout(function() {
        + *       // since this fn executes async in a future turn of the event loop, we need to wrap
        + *       // our code into an $apply call so that the model changes are properly observed.
        + *       scope.$apply(function() {
        + *         if (okToGreet(name)) {
        + *           deferred.resolve('Hello, ' + name + '!');
        + *         } else {
        + *           deferred.reject('Greeting ' + name + ' is not allowed.');
        + *         }
        + *       });
        + *     }, 1000);
        + *
        + *     return deferred.promise;
        + *   }
        + *
        + *   var promise = asyncGreet('Robin Hood');
        + *   promise.then(function(greeting) {
        + *     alert('Success: ' + greeting);
        + *   }, function(reason) {
        + *     alert('Failed: ' + reason);
        + *   });
        + * 
        + * + * At first it might not be obvious why this extra complexity is worth the trouble. The payoff + * comes in the way of + * [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md). + * + * Additionally the promise api allows for composition that is very hard to do with the + * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. + * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the + * section on serial or parallel joining of promises. + * + * + * # The Deferred API + * + * A new instance of deferred is constructed by calling `$q.defer()`. + * + * The purpose of the deferred object is to expose the associated Promise instance as well as APIs + * that can be used for signaling the successful or unsuccessful completion of the task. + * + * **Methods** + * + * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection + * constructed via `$q.reject`, the promise will be rejected instead. + * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to + * resolving it with a rejection constructed via `$q.reject`. + * + * **Properties** + * + * - promise – `{Promise}` – promise object associated with this deferred. + * + * + * # The Promise API + * + * A new promise instance is created when a deferred instance is created and can be retrieved by + * calling `deferred.promise`. + * + * The purpose of the promise object is to allow for interested parties to get access to the result + * of the deferred task when it completes. + * + * **Methods** + * + * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved + * or rejected, `then` calls one of the success or error callbacks asynchronously as soon as the result + * is available. The callbacks are called with a single argument: the result or rejection reason. + * + * This method *returns a new promise* which is resolved or rejected via the return value of the + * `successCallback` or `errorCallback`. + * + * + * # Chaining promises + * + * Because calling the `then` method of a promise returns a new derived promise, it is easily possible + * to create a chain of promises: + * + *
        + *   promiseB = promiseA.then(function(result) {
        + *     return result + 1;
        + *   });
        + *
        + *   // promiseB will be resolved immediately after promiseA is resolved and its value
        + *   // will be the result of promiseA incremented by 1
        + * 
        + * + * It is possible to create chains of any length and since a promise can be resolved with another + * promise (which will defer its resolution further), it is possible to pause/defer resolution of + * the promises at any point in the chain. This makes it possible to implement powerful APIs like + * $http's response interceptors. + * + * + * # Differences between Kris Kowal's Q and $q + * + * There are three main differences: + * + * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation + * mechanism in angular, which means faster propagation of resolution or rejection into your + * models and avoiding unnecessary browser repaints, which would result in flickering UI. + * - $q promises are recognized by the templating engine in angular, which means that in templates + * you can treat promises attached to a scope as if they were the resulting values. + * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains + * all the important functionality needed for common async tasks. + * + * # Testing + * + *
        + *    it('should simulate promise', inject(function($q, $rootScope) {
        + *      var deferred = $q.defer();
        + *      var promise = deferred.promise;
        + *      var resolvedValue;
        + * 
        + *      promise.then(function(value) { resolvedValue = value; });
        + *      expect(resolvedValue).toBeUndefined();
        + * 
        + *      // Simulate resolving of promise
        + *      deferred.resolve(123);
        + *      // Note that the 'then' function does not get called synchronously.
        + *      // This is because we want the promise API to always be async, whether or not
        + *      // it got called synchronously or asynchronously.
        + *      expect(resolvedValue).toBeUndefined();
        + * 
        + *      // Propagate promise resolution to 'then' functions using $apply().
        + *      $rootScope.$apply();
        + *      expect(resolvedValue).toEqual(123);
        + *    });
        + *  
        + */ +function $QProvider() { + + this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { + return qFactory(function(callback) { + $rootScope.$evalAsync(callback); + }, $exceptionHandler); + }]; +} + + +/** + * Constructs a promise manager. + * + * @param {function(function)} nextTick Function for executing functions in the next turn. + * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for + * debugging purposes. + * @returns {object} Promise manager. + */ +function qFactory(nextTick, exceptionHandler) { + + /** + * @ngdoc + * @name ng.$q#defer + * @methodOf ng.$q + * @description + * Creates a `Deferred` object which represents a task which will finish in the future. + * + * @returns {Deferred} Returns a new instance of deferred. + */ + var defer = function() { + var pending = [], + value, deferred; + + deferred = { + + resolve: function(val) { + if (pending) { + var callbacks = pending; + pending = undefined; + value = ref(val); + + if (callbacks.length) { + nextTick(function() { + var callback; + for (var i = 0, ii = callbacks.length; i < ii; i++) { + callback = callbacks[i]; + value.then(callback[0], callback[1]); + } + }); + } + } + }, + + + reject: function(reason) { + deferred.resolve(reject(reason)); + }, + + + promise: { + then: function(callback, errback) { + var result = defer(); + + var wrappedCallback = function(value) { + try { + result.resolve((callback || defaultCallback)(value)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }; + + var wrappedErrback = function(reason) { + try { + result.resolve((errback || defaultErrback)(reason)); + } catch(e) { + result.reject(e); + exceptionHandler(e); + } + }; + + if (pending) { + pending.push([wrappedCallback, wrappedErrback]); + } else { + value.then(wrappedCallback, wrappedErrback); + } + + return result.promise; + } + } + }; + + return deferred; + }; + + + var ref = function(value) { + if (value && value.then) return value; + return { + then: function(callback) { + var result = defer(); + nextTick(function() { + result.resolve(callback(value)); + }); + return result.promise; + } + }; + }; + + + /** + * @ngdoc + * @name ng.$q#reject + * @methodOf ng.$q + * @description + * Creates a promise that is resolved as rejected with the specified `reason`. This api should be + * used to forward rejection in a chain of promises. If you are dealing with the last promise in + * a promise chain, you don't need to worry about it. + * + * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of + * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via + * a promise error callback and you want to forward the error to the promise derived from the + * current promise, you have to "rethrow" the error by returning a rejection constructed via + * `reject`. + * + *
        +   *   promiseB = promiseA.then(function(result) {
        +   *     // success: do something and resolve promiseB
        +   *     //          with the old or a new result
        +   *     return result;
        +   *   }, function(reason) {
        +   *     // error: handle the error if possible and
        +   *     //        resolve promiseB with newPromiseOrValue,
        +   *     //        otherwise forward the rejection to promiseB
        +   *     if (canHandle(reason)) {
        +   *      // handle the error and recover
        +   *      return newPromiseOrValue;
        +   *     }
        +   *     return $q.reject(reason);
        +   *   });
        +   * 
        + * + * @param {*} reason Constant, message, exception or an object representing the rejection reason. + * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. + */ + var reject = function(reason) { + return { + then: function(callback, errback) { + var result = defer(); + nextTick(function() { + result.resolve((errback || defaultErrback)(reason)); + }); + return result.promise; + } + }; + }; + + + /** + * @ngdoc + * @name ng.$q#when + * @methodOf ng.$q + * @description + * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. + * This is useful when you are dealing with an object that might or might not be a promise, or if + * the promise comes from a source that can't be trusted. + * + * @param {*} value Value or a promise + * @returns {Promise} Returns a promise of the passed value or promise + */ + var when = function(value, callback, errback) { + var result = defer(), + done; + + var wrappedCallback = function(value) { + try { + return (callback || defaultCallback)(value); + } catch (e) { + exceptionHandler(e); + return reject(e); + } + }; + + var wrappedErrback = function(reason) { + try { + return (errback || defaultErrback)(reason); + } catch (e) { + exceptionHandler(e); + return reject(e); + } + }; + + nextTick(function() { + ref(value).then(function(value) { + if (done) return; + done = true; + result.resolve(ref(value).then(wrappedCallback, wrappedErrback)); + }, function(reason) { + if (done) return; + done = true; + result.resolve(wrappedErrback(reason)); + }); + }); + + return result.promise; + }; + + + function defaultCallback(value) { + return value; + } + + + function defaultErrback(reason) { + return reject(reason); + } + + + /** + * @ngdoc + * @name ng.$q#all + * @methodOf ng.$q + * @description + * Combines multiple promises into a single promise that is resolved when all of the input + * promises are resolved. + * + * @param {Array.} promises An array of promises. + * @returns {Promise} Returns a single promise that will be resolved with an array of values, + * each value corresponding to the promise at the same index in the `promises` array. If any of + * the promises is resolved with a rejection, this resulting promise will be resolved with the + * same rejection. + */ + function all(promises) { + var deferred = defer(), + counter = promises.length, + results = []; + + if (counter) { + forEach(promises, function(promise, index) { + ref(promise).then(function(value) { + if (index in results) return; + results[index] = value; + if (!(--counter)) deferred.resolve(results); + }, function(reason) { + if (index in results) return; + deferred.reject(reason); + }); + }); + } else { + deferred.resolve(results); + } + + return deferred.promise; + } + + return { + defer: defer, + reject: reject, + when: when, + all: all + }; +} + +/** + * @ngdoc object + * @name ng.$routeProvider + * @function + * + * @description + * + * Used for configuring routes. See {@link ng.$route $route} for an example. + */ +function $RouteProvider(){ + var routes = {}; + + /** + * @ngdoc method + * @name ng.$routeProvider#when + * @methodOf ng.$routeProvider + * + * @param {string} path Route path (matched against `$location.path`). If `$location.path` + * contains redundant trailing slash or is missing one, the route will still match and the + * `$location.path` will be updated to add or drop the trailing slash to exactly match the + * route definition. + * + * `path` can contain named groups starting with a colon (`:name`). All characters up to the + * next slash are matched and stored in `$routeParams` under the given `name` after the route + * is resolved. + * + * @param {Object} route Mapping information to be assigned to `$route.current` on route + * match. + * + * Object properties: + * + * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly + * created scope or the name of a {@link angular.Module#controller registered controller} + * if passed as a string. + * - `template` – `{string=}` – html template as a string that should be used by + * {@link ng.directive:ngView ngView} or + * {@link ng.directive:ngInclude ngInclude} directives. + * this property takes precedence over `templateUrl`. + * - `templateUrl` – `{string=}` – path to an html template that should be used by + * {@link ng.directive:ngView ngView}. + * - `resolve` - `{Object.=}` - An optional map of dependencies which should + * be injected into the controller. If any of these dependencies are promises, they will be + * resolved and converted to a value before the controller is instantiated and the + * `$routeChangeSuccess` event is fired. The map object is: + * + * - `key` – `{string}`: a name of a dependency to be injected into the controller. + * - `factory` - `{string|function}`: If `string` then it is an alias for a service. + * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} + * and the return value is treated as the dependency. If the result is a promise, it is resolved + * before its value is injected into the controller. Be aware that `ngRoute.$routeParams` will + * still refer to the previous route within these resolve functions. Use `$route.current.params` + * to access the new route parameters, instead. + * + * - `redirectTo` – {(string|function())=} – value to update + * {@link ng.$location $location} path with and trigger route redirection. + * + * If `redirectTo` is a function, it will be called with the following parameters: + * + * - `{Object.}` - route parameters extracted from the current + * `$location.path()` by applying the current route templateUrl. + * - `{string}` - current `$location.path()` + * - `{Object}` - current `$location.search()` + * + * The custom `redirectTo` function is expected to return a string which will be used + * to update `$location.path()` and `$location.search()`. + * + * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search() + * changes. + * + * If the option is set to `false` and url in the browser changes, then + * `$routeUpdate` event is broadcasted on the root scope. + * + * @returns {Object} self + * + * @description + * Adds a new route definition to the `$route` service. + */ + this.when = function(path, route) { + routes[path] = extend({reloadOnSearch: true}, route); + + // create redirection for trailing slashes + if (path) { + var redirectPath = (path[path.length-1] == '/') + ? path.substr(0, path.length-1) + : path +'/'; + + routes[redirectPath] = {redirectTo: path}; + } + + return this; + }; + + /** + * @ngdoc method + * @name ng.$routeProvider#otherwise + * @methodOf ng.$routeProvider + * + * @description + * Sets route definition that will be used on route change when no other route definition + * is matched. + * + * @param {Object} params Mapping information to be assigned to `$route.current`. + * @returns {Object} self + */ + this.otherwise = function(params) { + this.when(null, params); + return this; + }; + + + this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache', + function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) { + + /** + * @ngdoc object + * @name ng.$route + * @requires $location + * @requires $routeParams + * + * @property {Object} current Reference to the current route definition. + * The route definition contains: + * + * - `controller`: The controller constructor as define in route definition. + * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for + * controller instantiation. The `locals` contain + * the resolved values of the `resolve` map. Additionally the `locals` also contain: + * + * - `$scope` - The current route scope. + * - `$template` - The current route template HTML. + * + * @property {Array.} routes Array of all configured routes. + * + * @description + * Is used for deep-linking URLs to controllers and views (HTML partials). + * It watches `$location.url()` and tries to map the path to an existing route definition. + * + * You can define routes through {@link ng.$routeProvider $routeProvider}'s API. + * + * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView} + * directive and the {@link ng.$routeParams $routeParams} service. + * + * @example + This example shows how changing the URL hash causes the `$route` to match a route against the + URL, and the `ngView` pulls in the partial. + + Note that this example is using {@link ng.directive:script inlined templates} + to get it working on jsfiddle as well. + + + +
        + Choose: + Moby | + Moby: Ch1 | + Gatsby | + Gatsby: Ch4 | + Scarlet Letter
        + +
        +
        + +
        $location.path() = {{$location.path()}}
        +
        $route.current.templateUrl = {{$route.current.templateUrl}}
        +
        $route.current.params = {{$route.current.params}}
        +
        $route.current.scope.name = {{$route.current.scope.name}}
        +
        $routeParams = {{$routeParams}}
        +
        +
        + + + controller: {{name}}
        + Book Id: {{params.bookId}}
        +
        + + + controller: {{name}}
        + Book Id: {{params.bookId}}
        + Chapter Id: {{params.chapterId}} +
        + + + angular.module('ngView', [], function($routeProvider, $locationProvider) { + $routeProvider.when('/Book/:bookId', { + templateUrl: 'book.html', + controller: BookCntl, + resolve: { + // I will cause a 1 second delay + delay: function($q, $timeout) { + var delay = $q.defer(); + $timeout(delay.resolve, 1000); + return delay.promise; + } + } + }); + $routeProvider.when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: ChapterCntl + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }); + + function MainCntl($scope, $route, $routeParams, $location) { + $scope.$route = $route; + $scope.$location = $location; + $scope.$routeParams = $routeParams; + } + + function BookCntl($scope, $routeParams) { + $scope.name = "BookCntl"; + $scope.params = $routeParams; + } + + function ChapterCntl($scope, $routeParams) { + $scope.name = "ChapterCntl"; + $scope.params = $routeParams; + } + + + + it('should load and compile correct template', function() { + element('a:contains("Moby: Ch1")').click(); + var content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: ChapterCntl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element('a:contains("Scarlet")').click(); + sleep(2); // promises are not part of scenario waiting + content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: BookCntl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + +
        + */ + + /** + * @ngdoc event + * @name ng.$route#$routeChangeStart + * @eventOf ng.$route + * @eventType broadcast on root scope + * @description + * Broadcasted before a route change. At this point the route services starts + * resolving all of the dependencies needed for the route change to occurs. + * Typically this involves fetching the view template as well as any dependencies + * defined in `resolve` route property. Once all of the dependencies are resolved + * `$routeChangeSuccess` is fired. + * + * @param {Route} next Future route information. + * @param {Route} current Current route information. + */ + + /** + * @ngdoc event + * @name ng.$route#$routeChangeSuccess + * @eventOf ng.$route + * @eventType broadcast on root scope + * @description + * Broadcasted after a route dependencies are resolved. + * {@link ng.directive:ngView ngView} listens for the directive + * to instantiate the controller and render the view. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} current Current route information. + * @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered. + */ + + /** + * @ngdoc event + * @name ng.$route#$routeChangeError + * @eventOf ng.$route + * @eventType broadcast on root scope + * @description + * Broadcasted if any of the resolve promises are rejected. + * + * @param {Route} current Current route information. + * @param {Route} previous Previous route information. + * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. + */ + + /** + * @ngdoc event + * @name ng.$route#$routeUpdate + * @eventOf ng.$route + * @eventType broadcast on root scope + * @description + * + * The `reloadOnSearch` property has been set to false, and we are reusing the same + * instance of the Controller. + */ + + var forceReload = false, + $route = { + routes: routes, + + /** + * @ngdoc method + * @name ng.$route#reload + * @methodOf ng.$route + * + * @description + * Causes `$route` service to reload the current route even if + * {@link ng.$location $location} hasn't changed. + * + * As a result of that, {@link ng.directive:ngView ngView} + * creates new scope, reinstantiates the controller. + */ + reload: function() { + forceReload = true; + $rootScope.$evalAsync(updateRoute); + } + }; + + $rootScope.$on('$locationChangeSuccess', updateRoute); + + return $route; + + ///////////////////////////////////////////////////// + + /** + * @param on {string} current url + * @param when {string} route when template to match the url against + * @return {?Object} + */ + function switchRouteMatcher(on, when) { + // TODO(i): this code is convoluted and inefficient, we should construct the route matching + // regex only once and then reuse it + + // Escape regexp special characters. + when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$'; + var regex = '', + params = [], + dst = {}; + + var re = /:(\w+)/g, + paramMatch, + lastMatchedIndex = 0; + + while ((paramMatch = re.exec(when)) !== null) { + // Find each :param in `when` and replace it with a capturing group. + // Append all other sections of when unchanged. + regex += when.slice(lastMatchedIndex, paramMatch.index); + regex += '([^\\/]*)'; + params.push(paramMatch[1]); + lastMatchedIndex = re.lastIndex; + } + // Append trailing path part. + regex += when.substr(lastMatchedIndex); + + var match = on.match(new RegExp(regex)); + if (match) { + forEach(params, function(name, index) { + dst[name] = match[index + 1]; + }); + } + return match ? dst : null; + } + + function updateRoute() { + var next = parseRoute(), + last = $route.current; + + if (next && last && next.$$route === last.$$route + && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) { + last.params = next.params; + copy(last.params, $routeParams); + $rootScope.$broadcast('$routeUpdate', last); + } else if (next || last) { + forceReload = false; + $rootScope.$broadcast('$routeChangeStart', next, last); + $route.current = next; + if (next) { + if (next.redirectTo) { + if (isString(next.redirectTo)) { + $location.path(interpolate(next.redirectTo, next.params)).search(next.params) + .replace(); + } else { + $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) + .replace(); + } + } + } + + $q.when(next). + then(function() { + if (next) { + var keys = [], + values = [], + template; + + forEach(next.resolve || {}, function(value, key) { + keys.push(key); + values.push(isString(value) ? $injector.get(value) : $injector.invoke(value)); + }); + if (isDefined(template = next.template)) { + } else if (isDefined(template = next.templateUrl)) { + template = $http.get(template, {cache: $templateCache}). + then(function(response) { return response.data; }); + } + if (isDefined(template)) { + keys.push('$template'); + values.push(template); + } + return $q.all(values).then(function(values) { + var locals = {}; + forEach(values, function(value, index) { + locals[keys[index]] = value; + }); + return locals; + }); + } + }). + // after route change + then(function(locals) { + if (next == $route.current) { + if (next) { + next.locals = locals; + copy(next.params, $routeParams); + } + $rootScope.$broadcast('$routeChangeSuccess', next, last); + } + }, function(error) { + if (next == $route.current) { + $rootScope.$broadcast('$routeChangeError', next, last, error); + } + }); + } + } + + + /** + * @returns the current active route, by matching it against the URL + */ + function parseRoute() { + // Match a route + var params, match; + forEach(routes, function(route, path) { + if (!match && (params = switchRouteMatcher($location.path(), path))) { + match = inherit(route, { + params: extend({}, $location.search(), params), + pathParams: params}); + match.$$route = route; + } + }); + // No route matched; fallback to "otherwise" route + return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); + } + + /** + * @returns interpolation of the redirect path with the parametrs + */ + function interpolate(string, params) { + var result = []; + forEach((string||'').split(':'), function(segment, i) { + if (i == 0) { + result.push(segment); + } else { + var segmentMatch = segment.match(/(\w+)(.*)/); + var key = segmentMatch[1]; + result.push(params[key]); + result.push(segmentMatch[2] || ''); + delete params[key]; + } + }); + return result.join(''); + } + }]; +} + +/** + * @ngdoc object + * @name ng.$routeParams + * @requires $route + * + * @description + * Current set of route parameters. The route parameters are a combination of the + * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters + * are extracted when the {@link ng.$route $route} path is matched. + * + * In case of parameter name collision, `path` params take precedence over `search` params. + * + * The service guarantees that the identity of the `$routeParams` object will remain unchanged + * (but its properties will likely change) even when a route change occurs. + * + * Note that the `$routeParams` are only updated *after* a route change completes successfully. + * This means that you cannot rely on `$routeParams` being correct in route resolve functions. + * Instead you can use `$route.current.params` to access the new route's parameters. + * + * @example + *
        + *  // Given:
        + *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
        + *  // Route: /Chapter/:chapterId/Section/:sectionId
        + *  //
        + *  // Then
        + *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
        + * 
        + */ +function $RouteParamsProvider() { + this.$get = valueFn({}); +} + +/** + * DESIGN NOTES + * + * The design decisions behind the scope are heavily favored for speed and memory consumption. + * + * The typical use of scope is to watch the expressions, which most of the time return the same + * value as last time so we optimize the operation. + * + * Closures construction is expensive in terms of speed as well as memory: + * - No closures, instead use prototypical inheritance for API + * - Internal state needs to be stored on scope directly, which means that private state is + * exposed as $$____ properties + * + * Loop operations are optimized by using while(count--) { ... } + * - this means that in order to keep the same order of execution as addition we have to add + * items to the array at the beginning (shift) instead of at the end (push) + * + * Child scopes are created and removed often + * - Using an array would be slow since inserts in middle are expensive so we use linked list + * + * There are few watches then a lot of observers. This is why you don't want the observer to be + * implemented in the same way as watch. Watch requires return of initialization function which + * are expensive to construct. + */ + + +/** + * @ngdoc object + * @name ng.$rootScopeProvider + * @description + * + * Provider for the $rootScope service. + */ + +/** + * @ngdoc function + * @name ng.$rootScopeProvider#digestTtl + * @methodOf ng.$rootScopeProvider + * @description + * + * Sets the number of digest iterations the scope should attempt to execute before giving up and + * assuming that the model is unstable. + * + * The current default is 10 iterations. + * + * @param {number} limit The number of digest iterations. + */ + + +/** + * @ngdoc object + * @name ng.$rootScope + * @description + * + * Every application has a single root {@link ng.$rootScope.Scope scope}. + * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide + * event processing life-cycle. See {@link guide/scope developer guide on scopes}. + */ +function $RootScopeProvider(){ + var TTL = 10; + + this.digestTtl = function(value) { + if (arguments.length) { + TTL = value; + } + return TTL; + }; + + this.$get = ['$injector', '$exceptionHandler', '$parse', + function( $injector, $exceptionHandler, $parse) { + + /** + * @ngdoc function + * @name ng.$rootScope.Scope + * + * @description + * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the + * {@link AUTO.$injector $injector}. Child scopes are created using the + * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when + * compiled HTML template is executed.) + * + * Here is a simple scope snippet to show how you can interact with the scope. + *
        +        angular.injector(['ng']).invoke(function($rootScope) {
        +           var scope = $rootScope.$new();
        +           scope.salutation = 'Hello';
        +           scope.name = 'World';
        +
        +           expect(scope.greeting).toEqual(undefined);
        +
        +           scope.$watch('name', function() {
        +             scope.greeting = scope.salutation + ' ' + scope.name + '!';
        +           }); // initialize the watch
        +
        +           expect(scope.greeting).toEqual(undefined);
        +           scope.name = 'Misko';
        +           // still old value, since watches have not been called yet
        +           expect(scope.greeting).toEqual(undefined);
        +
        +           scope.$digest(); // fire all  the watches
        +           expect(scope.greeting).toEqual('Hello Misko!');
        +        });
        +     * 
        + * + * # Inheritance + * A scope can inherit from a parent scope, as in this example: + *
        +         var parent = $rootScope;
        +         var child = parent.$new();
        +
        +         parent.salutation = "Hello";
        +         child.name = "World";
        +         expect(child.salutation).toEqual('Hello');
        +
        +         child.salutation = "Welcome";
        +         expect(child.salutation).toEqual('Welcome');
        +         expect(parent.salutation).toEqual('Hello');
        +     * 
        + * + * + * @param {Object.=} providers Map of service factory which need to be provided + * for the current scope. Defaults to {@link ng}. + * @param {Object.=} instanceCache Provides pre-instantiated services which should + * append/override services provided by `providers`. This is handy when unit-testing and having + * the need to override a default service. + * @returns {Object} Newly created scope. + * + */ + function Scope() { + this.$id = nextUid(); + this.$$phase = this.$parent = this.$$watchers = + this.$$nextSibling = this.$$prevSibling = + this.$$childHead = this.$$childTail = null; + this['this'] = this.$root = this; + this.$$destroyed = false; + this.$$asyncQueue = []; + this.$$listeners = {}; + this.$$isolateBindings = {}; + } + + /** + * @ngdoc property + * @name ng.$rootScope.Scope#$id + * @propertyOf ng.$rootScope.Scope + * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for + * debugging. + */ + + + Scope.prototype = { + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$new + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Creates a new child {@link ng.$rootScope.Scope scope}. + * + * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and + * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope + * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. + * + * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for + * the scope and its child scopes to be permanently detached from the parent and thus stop + * participating in model change detection and listener notification by invoking. + * + * @param {boolean} isolate if true then the scope does not prototypically inherit from the + * parent scope. The scope is isolated, as it can not see parent scope properties. + * When creating widgets it is useful for the widget to not accidentally read parent + * state. + * + * @returns {Object} The newly created child scope. + * + */ + $new: function(isolate) { + var Child, + child; + + if (isFunction(isolate)) { + // TODO: remove at some point + throw Error('API-CHANGE: Use $controller to instantiate controllers.'); + } + if (isolate) { + child = new Scope(); + child.$root = this.$root; + } else { + Child = function() {}; // should be anonymous; This is so that when the minifier munges + // the name it does not become random set of chars. These will then show up as class + // name in the debugger. + Child.prototype = this; + child = new Child(); + child.$id = nextUid(); + } + child['this'] = child; + child.$$listeners = {}; + child.$parent = this; + child.$$asyncQueue = []; + child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; + child.$$prevSibling = this.$$childTail; + if (this.$$childHead) { + this.$$childTail.$$nextSibling = child; + this.$$childTail = child; + } else { + this.$$childHead = this.$$childTail = child; + } + return child; + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$watch + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Registers a `listener` callback to be executed whenever the `watchExpression` changes. + * + * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and + * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()} + * reruns when it detects changes the `watchExpression` can execute multiple times per + * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) + * - The `listener` is called only when the value from the current `watchExpression` and the + * previous call to `watchExpression` are not equal (with the exception of the initial run, + * see below). The inequality is determined according to + * {@link angular.equals} function. To save the value of the object for later comparison, the + * {@link angular.copy} function is used. It also means that watching complex options will + * have adverse memory and performance implications. + * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This + * is achieved by rerunning the watchers until no changes are detected. The rerun iteration + * limit is 10 to prevent an infinite loop deadlock. + * + * + * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, + * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` + * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is + * detected, be prepared for multiple calls to your listener.) + * + * After a watcher is registered with the scope, the `listener` fn is called asynchronously + * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the + * watcher. In rare cases, this is undesirable because the listener is called when the result + * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you + * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the + * listener was called due to initialization. + * + * + * # Example + *
        +           // let's assume that scope was dependency injected as the $rootScope
        +           var scope = $rootScope;
        +           scope.name = 'misko';
        +           scope.counter = 0;
        +
        +           expect(scope.counter).toEqual(0);
        +           scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; });
        +           expect(scope.counter).toEqual(0);
        +
        +           scope.$digest();
        +           // no variable change
        +           expect(scope.counter).toEqual(0);
        +
        +           scope.name = 'adam';
        +           scope.$digest();
        +           expect(scope.counter).toEqual(1);
        +       * 
        + * + * + * + * @param {(function()|string)} watchExpression Expression that is evaluated on each + * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a + * call to the `listener`. + * + * - `string`: Evaluated as {@link guide/expression expression} + * - `function(scope)`: called with current `scope` as a parameter. + * @param {(function()|string)=} listener Callback called whenever the return value of + * the `watchExpression` changes. + * + * - `string`: Evaluated as {@link guide/expression expression} + * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters. + * + * @param {boolean=} objectEquality Compare object for equality rather than for reference. + * @returns {function()} Returns a deregistration function for this listener. + */ + $watch: function(watchExp, listener, objectEquality) { + var scope = this, + get = compileToFn(watchExp, 'watch'), + array = scope.$$watchers, + watcher = { + fn: listener, + last: initWatchVal, + get: get, + exp: watchExp, + eq: !!objectEquality + }; + + // in the case user pass string, we need to compile it, do we really need this ? + if (!isFunction(listener)) { + var listenFn = compileToFn(listener || noop, 'listener'); + watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; + } + + if (!array) { + array = scope.$$watchers = []; + } + // we use unshift since we use a while loop in $digest for speed. + // the while loop reads in reverse order. + array.unshift(watcher); + + return function() { + arrayRemove(array, watcher); + }; + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$digest + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children. + * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the + * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are + * firing. This means that it is possible to get into an infinite loop. This function will throw + * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10. + * + * Usually you don't call `$digest()` directly in + * {@link ng.directive:ngController controllers} or in + * {@link ng.$compileProvider#directive directives}. + * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a + * {@link ng.$compileProvider#directive directives}) will force a `$digest()`. + * + * If you want to be notified whenever `$digest()` is called, + * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()} + * with no `listener`. + * + * You may have a need to call `$digest()` from within unit-tests, to simulate the scope + * life-cycle. + * + * # Example + *
        +           var scope = ...;
        +           scope.name = 'misko';
        +           scope.counter = 0;
        +
        +           expect(scope.counter).toEqual(0);
        +           scope.$watch('name', function(newValue, oldValue) {
        +             scope.counter = scope.counter + 1;
        +           });
        +           expect(scope.counter).toEqual(0);
        +
        +           scope.$digest();
        +           // no variable change
        +           expect(scope.counter).toEqual(0);
        +
        +           scope.name = 'adam';
        +           scope.$digest();
        +           expect(scope.counter).toEqual(1);
        +       * 
        + * + */ + $digest: function() { + var watch, value, last, + watchers, + asyncQueue, + length, + dirty, ttl = TTL, + next, current, target = this, + watchLog = [], + logIdx, logMsg; + + beginPhase('$digest'); + + do { + dirty = false; + current = target; + do { + asyncQueue = current.$$asyncQueue; + while(asyncQueue.length) { + try { + current.$eval(asyncQueue.shift()); + } catch (e) { + $exceptionHandler(e); + } + } + if ((watchers = current.$$watchers)) { + // process our watches + length = watchers.length; + while (length--) { + try { + watch = watchers[length]; + // Most common watches are on primitives, in which case we can short + // circuit it with === operator, only when === fails do we use .equals + if (watch && (value = watch.get(current)) !== (last = watch.last) && + !(watch.eq + ? equals(value, last) + : (typeof value == 'number' && typeof last == 'number' + && isNaN(value) && isNaN(last)))) { + dirty = true; + watch.last = watch.eq ? copy(value) : value; + watch.fn(value, ((last === initWatchVal) ? value : last), current); + if (ttl < 5) { + logIdx = 4 - ttl; + if (!watchLog[logIdx]) watchLog[logIdx] = []; + logMsg = (isFunction(watch.exp)) + ? 'fn: ' + (watch.exp.name || watch.exp.toString()) + : watch.exp; + logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); + watchLog[logIdx].push(logMsg); + } + } + } catch (e) { + $exceptionHandler(e); + } + } + } + + // Insanity Warning: scope depth-first traversal + // yes, this code is a bit crazy, but it works and we have tests to prove it! + // this piece should be kept in sync with the traversal in $broadcast + if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { + while(current !== target && !(next = current.$$nextSibling)) { + current = current.$parent; + } + } + } while ((current = next)); + + if(dirty && !(ttl--)) { + clearPhase(); + throw Error(TTL + ' $digest() iterations reached. Aborting!\n' + + 'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); + } + } while (dirty || asyncQueue.length); + + clearPhase(); + }, + + + /** + * @ngdoc event + * @name ng.$rootScope.Scope#$destroy + * @eventOf ng.$rootScope.Scope + * @eventType broadcast on scope being destroyed + * + * @description + * Broadcasted when a scope and its children are being destroyed. + * + * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to + * clean up DOM bindings before an element is removed from the DOM. + */ + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$destroy + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Removes the current scope (and all of its children) from the parent scope. Removal implies + * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer + * propagate to the current scope and its children. Removal also implies that the current + * scope is eligible for garbage collection. + * + * The `$destroy()` is usually used by directives such as + * {@link ng.directive:ngRepeat ngRepeat} for managing the + * unrolling of the loop. + * + * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope. + * Application code can register a `$destroy` event handler that will give it chance to + * perform any necessary cleanup. + * + * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to + * clean up DOM bindings before an element is removed from the DOM. + */ + $destroy: function() { + // we can't destroy the root scope or a scope that has been already destroyed + if ($rootScope == this || this.$$destroyed) return; + var parent = this.$parent; + + this.$broadcast('$destroy'); + this.$$destroyed = true; + + if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; + if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; + if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; + if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; + + // This is bogus code that works around Chrome's GC leak + // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 + this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = + this.$$childTail = null; + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$eval + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Executes the `expression` on the current scope returning the result. Any exceptions in the + * expression are propagated (uncaught). This is useful when evaluating Angular expressions. + * + * # Example + *
        +           var scope = ng.$rootScope.Scope();
        +           scope.a = 1;
        +           scope.b = 2;
        +
        +           expect(scope.$eval('a+b')).toEqual(3);
        +           expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
        +       * 
        + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + * @returns {*} The result of evaluating the expression. + */ + $eval: function(expr, locals) { + return $parse(expr)(this, locals); + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$evalAsync + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Executes the expression on the current scope at a later point in time. + * + * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that: + * + * - it will execute in the current script execution context (before any DOM rendering). + * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after + * `expression` execution. + * + * Any exceptions from the execution of the expression are forwarded to the + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * @param {(string|function())=} expression An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with the current `scope` parameter. + * + */ + $evalAsync: function(expr) { + this.$$asyncQueue.push(expr); + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$apply + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * `$apply()` is used to execute an expression in angular from outside of the angular framework. + * (For example from browser DOM events, setTimeout, XHR or third party libraries). + * Because we are calling into the angular framework we need to perform proper scope life-cycle + * of {@link ng.$exceptionHandler exception handling}, + * {@link ng.$rootScope.Scope#$digest executing watches}. + * + * ## Life cycle + * + * # Pseudo-Code of `$apply()` + *
        +           function $apply(expr) {
        +             try {
        +               return $eval(expr);
        +             } catch (e) {
        +               $exceptionHandler(e);
        +             } finally {
        +               $root.$digest();
        +             }
        +           }
        +       * 
        + * + * + * Scope's `$apply()` method transitions through the following stages: + * + * 1. The {@link guide/expression expression} is executed using the + * {@link ng.$rootScope.Scope#$eval $eval()} method. + * 2. Any exceptions from the execution of the expression are forwarded to the + * {@link ng.$exceptionHandler $exceptionHandler} service. + * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression + * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. + * + * + * @param {(string|function())=} exp An angular expression to be executed. + * + * - `string`: execute using the rules as defined in {@link guide/expression expression}. + * - `function(scope)`: execute the function with current `scope` parameter. + * + * @returns {*} The result of evaluating the expression. + */ + $apply: function(expr) { + try { + beginPhase('$apply'); + return this.$eval(expr); + } catch (e) { + $exceptionHandler(e); + } finally { + clearPhase(); + try { + $rootScope.$digest(); + } catch (e) { + $exceptionHandler(e); + throw e; + } + } + }, + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$on + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of + * event life cycle. + * + * The event listener function format is: `function(event, args...)`. The `event` object + * passed into the listener has the following attributes: + * + * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or `$broadcast`-ed. + * - `currentScope` - `{Scope}`: the current scope which is handling the event. + * - `name` - `{string}`: Name of the event. + * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel further event + * propagation (available only for events that were `$emit`-ed). + * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true. + * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. + * + * @param {string} name Event name to listen on. + * @param {function(event, args...)} listener Function to call when the event is emitted. + * @returns {function()} Returns a deregistration function for this listener. + */ + $on: function(name, listener) { + var namedListeners = this.$$listeners[name]; + if (!namedListeners) { + this.$$listeners[name] = namedListeners = []; + } + namedListeners.push(listener); + + return function() { + namedListeners[indexOf(namedListeners, listener)] = null; + }; + }, + + + /** + * @ngdoc function + * @name ng.$rootScope.Scope#$emit + * @methodOf ng.$rootScope.Scope + * @function + * + * @description + * Dispatches an event `name` upwards through the scope hierarchy notifying the + * registered {@link ng.$rootScope.Scope#$on} listeners. + * + * The event life cycle starts at the scope on which `$emit` was called. All + * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified. + * Afterwards, the event traverses upwards toward the root scope and calls all registered + * listeners along the way. The event will stop propagating if one of the listeners cancels it. + * + * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed + * onto the {@link ng.$exceptionHandler $exceptionHandler} service. + * + * @param {string} name Event name to emit. + * @param {...*} args Optional set of arguments which will be passed onto the event listeners. + * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} + */ + $emit: function(name, args) { + var empty = [], + namedListeners, + scope = this, + stopPropagation = false, + event = { + name: name, + targetScope: scope, + stopPropagation: function() {stopPropagation = true;}, + preventDefault: function() { + event.defaultPrevented = true; + }, + defaultPrevented: false + }, + listenerArgs = concat([event], arguments, 1), + i, length; + + do { + namedListeners = scope.$$listeners[name] || empty; + event.currentScope = scope; + for (i=0, length=namedListeners.length; i 7), + hasEvent: function(event) { + // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have + // it. In particular the event is not fired when backspace or delete key are pressed or + // when cut operation is performed. + if (event == 'input' && msie == 9) return false; + + if (isUndefined(eventSupport[event])) { + var divElm = $window.document.createElement('div'); + eventSupport[event] = 'on' + event in divElm; + } + + return eventSupport[event]; + }, + // TODO(i): currently there is no way to feature detect CSP without triggering alerts + csp: false + }; + }]; +} + +/** + * @ngdoc object + * @name ng.$window + * + * @description + * A reference to the browser's `window` object. While `window` + * is globally available in JavaScript, it causes testability problems, because + * it is a global variable. In angular we always refer to it through the + * `$window` service, so it may be overriden, removed or mocked for testing. + * + * Expressions, like the one defined for the `ngClick` directive in the example + * below, are evaluated with respect to the current scope. Therefore, there is + * no risk of inadvertently coding in a dependency on a global value in such an + * expression. + * + * @example + + + +
        + + +
        +
        + + it('should display the greeting in the input box', function() { + input('greeting').enter('Hello, E2E Tests'); + // If we click the button it will block the test runner + // element(':button').click(); + }); + +
        + */ +function $WindowProvider(){ + this.$get = valueFn(window); +} + +/** + * Parse headers into key value object + * + * @param {string} headers Raw headers as a string + * @returns {Object} Parsed headers as key value object + */ +function parseHeaders(headers) { + var parsed = {}, key, val, i; + + if (!headers) return parsed; + + forEach(headers.split('\n'), function(line) { + i = line.indexOf(':'); + key = lowercase(trim(line.substr(0, i))); + val = trim(line.substr(i + 1)); + + if (key) { + if (parsed[key]) { + parsed[key] += ', ' + val; + } else { + parsed[key] = val; + } + } + }); + + return parsed; +} + + +/** + * Returns a function that provides access to parsed headers. + * + * Headers are lazy parsed when first requested. + * @see parseHeaders + * + * @param {(string|Object)} headers Headers to provide access to. + * @returns {function(string=)} Returns a getter function which if called with: + * + * - if called with single an argument returns a single header value or null + * - if called with no arguments returns an object containing all headers. + */ +function headersGetter(headers) { + var headersObj = isObject(headers) ? headers : undefined; + + return function(name) { + if (!headersObj) headersObj = parseHeaders(headers); + + if (name) { + return headersObj[lowercase(name)] || null; + } + + return headersObj; + }; +} + + +/** + * Chain all given functions + * + * This function is used for both request and response transforming + * + * @param {*} data Data to transform. + * @param {function(string=)} headers Http headers getter fn. + * @param {(function|Array.)} fns Function or an array of functions. + * @returns {*} Transformed data. + */ +function transformData(data, headers, fns) { + if (isFunction(fns)) + return fns(data, headers); + + forEach(fns, function(fn) { + data = fn(data, headers); + }); + + return data; +} + + +function isSuccess(status) { + return 200 <= status && status < 300; +} + + +function $HttpProvider() { + var JSON_START = /^\s*(\[|\{[^\{])/, + JSON_END = /[\}\]]\s*$/, + PROTECTION_PREFIX = /^\)\]\}',?\n/; + + var $config = this.defaults = { + // transform incoming response data + transformResponse: [function(data) { + if (isString(data)) { + // strip json vulnerability protection prefix + data = data.replace(PROTECTION_PREFIX, ''); + if (JSON_START.test(data) && JSON_END.test(data)) + data = fromJson(data, true); + } + return data; + }], + + // transform outgoing request data + transformRequest: [function(d) { + return isObject(d) && !isFile(d) ? toJson(d) : d; + }], + + // default headers + headers: { + common: { + 'Accept': 'application/json, text/plain, */*', + 'X-Requested-With': 'XMLHttpRequest' + }, + post: {'Content-Type': 'application/json;charset=utf-8'}, + put: {'Content-Type': 'application/json;charset=utf-8'} + } + }; + + var providerResponseInterceptors = this.responseInterceptors = []; + + this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', + function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { + + var defaultCache = $cacheFactory('$http'), + responseInterceptors = []; + + forEach(providerResponseInterceptors, function(interceptor) { + responseInterceptors.push( + isString(interceptor) + ? $injector.get(interceptor) + : $injector.invoke(interceptor) + ); + }); + + + /** + * @ngdoc function + * @name ng.$http + * @requires $httpBackend + * @requires $browser + * @requires $cacheFactory + * @requires $rootScope + * @requires $q + * @requires $injector + * + * @description + * The `$http` service is a core Angular service that facilitates communication with the remote + * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest + * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}. + * + * For unit testing applications that use `$http` service, see + * {@link ngMock.$httpBackend $httpBackend mock}. + * + * For a higher level of abstraction, please check out the {@link ngResource.$resource + * $resource} service. + * + * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by + * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage + * it is important to familiarize yourself with these APIs and the guarantees they provide. + * + * + * # General usage + * The `$http` service is a function which takes a single argument — a configuration object — + * that is used to generate an HTTP request and returns a {@link ng.$q promise} + * with two $http specific methods: `success` and `error`. + * + *
        +     *   $http({method: 'GET', url: '/someUrl'}).
        +     *     success(function(data, status, headers, config) {
        +     *       // this callback will be called asynchronously
        +     *       // when the response is available
        +     *     }).
        +     *     error(function(data, status, headers, config) {
        +     *       // called asynchronously if an error occurs
        +     *       // or server returns response with an error status.
        +     *     });
        +     * 
        + * + * Since the returned value of calling the $http function is a `promise`, you can also use + * the `then` method to register callbacks, and these callbacks will receive a single argument – + * an object representing the response. See the API signature and type info below for more + * details. + * + * A response status code between 200 and 299 is considered a success status and + * will result in the success callback being called. Note that if the response is a redirect, + * XMLHttpRequest will transparently follow it, meaning that the error callback will not be + * called for such responses. + * + * # Shortcut methods + * + * Since all invocations of the $http service require passing in an HTTP method and URL, and + * POST/PUT requests require request data to be provided as well, shortcut methods + * were created: + * + *
        +     *   $http.get('/someUrl').success(successCallback);
        +     *   $http.post('/someUrl', data).success(successCallback);
        +     * 
        + * + * Complete list of shortcut methods: + * + * - {@link ng.$http#get $http.get} + * - {@link ng.$http#head $http.head} + * - {@link ng.$http#post $http.post} + * - {@link ng.$http#put $http.put} + * - {@link ng.$http#delete $http.delete} + * - {@link ng.$http#jsonp $http.jsonp} + * + * + * # Setting HTTP Headers + * + * The $http service will automatically add certain HTTP headers to all requests. These defaults + * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration + * object, which currently contains this default configuration: + * + * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): + * - `Accept: application/json, text/plain, * / *` + * - `X-Requested-With: XMLHttpRequest` + * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) + * - `Content-Type: application/json` + * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) + * - `Content-Type: application/json` + * + * To add or overwrite these defaults, simply add or remove a property from these configuration + * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object + * with the lowercased HTTP method name as the key, e.g. + * `$httpProvider.defaults.headers.get['My-Header']='value'`. + * + * Additionally, the defaults can be set at runtime via the `$http.defaults` object in the same + * fashion. + * + * + * # Transforming Requests and Responses + * + * Both requests and responses can be transformed using transform functions. By default, Angular + * applies these transformations: + * + * Request transformations: + * + * - If the `data` property of the request configuration object contains an object, serialize it into + * JSON format. + * + * Response transformations: + * + * - If XSRF prefix is detected, strip it (see Security Considerations section below). + * - If JSON response is detected, deserialize it using a JSON parser. + * + * To globally augment or override the default transforms, modify the `$httpProvider.defaults.transformRequest` and + * `$httpProvider.defaults.transformResponse` properties. These properties are by default an + * array of transform functions, which allows you to `push` or `unshift` a new transformation function into the + * transformation chain. You can also decide to completely override any default transformations by assigning your + * transformation functions to these properties directly without the array wrapper. + * + * Similarly, to locally override the request/response transforms, augment the `transformRequest` and/or + * `transformResponse` properties of the configuration object passed into `$http`. + * + * + * # Caching + * + * To enable caching, set the configuration property `cache` to `true`. When the cache is + * enabled, `$http` stores the response from the server in local cache. Next time the + * response is served from the cache without sending a request to the server. + * + * Note that even if the response is served from cache, delivery of the data is asynchronous in + * the same way that real requests are. + * + * If there are multiple GET requests for the same URL that should be cached using the same + * cache, but the cache is not populated yet, only one request to the server will be made and + * the remaining requests will be fulfilled using the response from the first request. + * + * + * # Response interceptors + * + * Before you start creating interceptors, be sure to understand the + * {@link ng.$q $q and deferred/promise APIs}. + * + * For purposes of global error handling, authentication or any kind of synchronous or + * asynchronous preprocessing of received responses, it is desirable to be able to intercept + * responses for http requests before they are handed over to the application code that + * initiated these requests. The response interceptors leverage the {@link ng.$q + * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. + * + * The interceptors are service factories that are registered with the $httpProvider by + * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and + * injected with dependencies (if specified) and returns the interceptor — a function that + * takes a {@link ng.$q promise} and returns the original or a new promise. + * + *
        +     *   // register the interceptor as a service
        +     *   $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
        +     *     return function(promise) {
        +     *       return promise.then(function(response) {
        +     *         // do something on success
        +     *         return response;
        +     *       }, function(response) {
        +     *         // do something on error
        +     *         if (canRecover(response)) {
        +     *           return responseOrNewPromise
        +     *         }
        +     *         return $q.reject(response);
        +     *       });
        +     *     }
        +     *   });
        +     *
        +     *   $httpProvider.responseInterceptors.push('myHttpInterceptor');
        +     *
        +     *
        +     *   // register the interceptor via an anonymous factory
        +     *   $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
        +     *     return function(promise) {
        +     *       // same as above
        +     *     }
        +     *   });
        +     * 
        + * + * + * # Security Considerations + * + * When designing web applications, consider security threats from: + * + * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx + * JSON vulnerability} + * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} + * + * Both server and the client must cooperate in order to eliminate these threats. Angular comes + * pre-configured with strategies that address these issues, but for this to work backend server + * cooperation is required. + * + * ## JSON Vulnerability Protection + * + * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx + * JSON vulnerability} allows third party website to turn your JSON resource URL into + * {@link http://en.wikipedia.org/wiki/JSONP JSONP} request under some conditions. To + * counter this your server can prefix all JSON requests with following string `")]}',\n"`. + * Angular will automatically strip the prefix before processing it as JSON. + * + * For example if your server needs to return: + *
        +     * ['one','two']
        +     * 
        + * + * which is vulnerable to attack, your server can return: + *
        +     * )]}',
        +     * ['one','two']
        +     * 
        + * + * Angular will strip the prefix, before processing the JSON. + * + * + * ## Cross Site Request Forgery (XSRF) Protection + * + * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which + * an unauthorized site can gain your user's private data. Angular provides a mechanism + * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie + * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that + * runs on your domain could read the cookie, your server can be assured that the XHR came from + * JavaScript running on your domain. + * + * To take advantage of this, your server needs to set a token in a JavaScript readable session + * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the + * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure + * that only JavaScript running on your domain could have sent the request. The token must be + * unique for each user and must be verifiable by the server (to prevent the JavaScript from making + * up its own tokens). We recommend that the token is a digest of your site's authentication + * cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt} for added security. + * + * + * @param {object} config Object describing the request to be made and how it should be + * processed. The object has following properties: + * + * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) + * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. + * - **params** – `{Object.}` – Map of strings or objects which will be turned to + * `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified. + * - **data** – `{string|Object}` – Data to be sent as the request message data. + * - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server. + * - **transformRequest** – `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * - **transformResponse** – `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * response body and headers and returns its transformed (typically deserialized) version. + * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the + * GET request, otherwise if a cache instance built with + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **timeout** – `{number}` – timeout in milliseconds. + * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the + * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 + * requests with credentials} for more information. + * + * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the + * standard `then` method and two http specific methods: `success` and `error`. The `then` + * method takes two arguments a success and an error callback which will be called with a + * response object. The `success` and `error` methods take a single argument - a function that + * will be called when the request succeeds or fails respectively. The arguments passed into + * these functions are destructured representation of the response object passed into the + * `then` method. The response object has these properties: + * + * - **data** – `{string|Object}` – The response body transformed with the transform functions. + * - **status** – `{number}` – HTTP status code of the response. + * - **headers** – `{function([headerName])}` – Header getter function. + * - **config** – `{Object}` – The configuration object that was used to generate the request. + * + * @property {Array.} pendingRequests Array of config objects for currently pending + * requests. This is primarily meant to be used for debugging purposes. + * + * + * @example + + +
        + + +
        + + + +
        http status code: {{status}}
        +
        http response data: {{data}}
        +
        +
        + + function FetchCtrl($scope, $http, $templateCache) { + $scope.method = 'GET'; + $scope.url = 'http-hello.html'; + + $scope.fetch = function() { + $scope.code = null; + $scope.response = null; + + $http({method: $scope.method, url: $scope.url, cache: $templateCache}). + success(function(data, status) { + $scope.status = status; + $scope.data = data; + }). + error(function(data, status) { + $scope.data = data || "Request failed"; + $scope.status = status; + }); + }; + + $scope.updateModel = function(method, url) { + $scope.method = method; + $scope.url = url; + }; + } + + + Hello, $http! + + + it('should make an xhr GET request', function() { + element(':button:contains("Sample GET")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('200'); + expect(binding('data')).toMatch(/Hello, \$http!/); + }); + + it('should make a JSONP request to angularjs.org', function() { + element(':button:contains("Sample JSONP")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('200'); + expect(binding('data')).toMatch(/Super Hero!/); + }); + + it('should make JSONP request to invalid URL and invoke the error handler', + function() { + element(':button:contains("Invalid JSONP")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('0'); + expect(binding('data')).toBe('Request failed'); + }); + +
        + */ + function $http(config) { + config.method = uppercase(config.method); + + var reqTransformFn = config.transformRequest || $config.transformRequest, + respTransformFn = config.transformResponse || $config.transformResponse, + reqHeaders = extend({}, config.headers), + defHeaders = extend( + {'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']}, + $config.headers.common, + $config.headers[lowercase(config.method)] + ), + reqData, + defHeaderName, lowercaseDefHeaderName, headerName, + promise; + + // using for-in instead of forEach to avoid unecessary iteration after header has been found + defaultHeadersIteration: + for(defHeaderName in defHeaders) { + lowercaseDefHeaderName = lowercase(defHeaderName); + for(headerName in config.headers) { + if (lowercase(headerName) === lowercaseDefHeaderName) { + continue defaultHeadersIteration; + } + } + reqHeaders[defHeaderName] = defHeaders[defHeaderName]; + } + + // strip content-type if data is undefined + if (isUndefined(config.data)) { + for(var header in reqHeaders) { + if (lowercase(header) === 'content-type') { + delete reqHeaders[header]; + break; + } + } + } + + reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn); + + // send request + promise = sendReq(config, reqData, reqHeaders); + + + // transform future response + promise = promise.then(transformResponse, transformResponse); + + // apply interceptors + forEach(responseInterceptors, function(interceptor) { + promise = interceptor(promise); + }); + + promise.success = function(fn) { + promise.then(function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.error = function(fn) { + promise.then(null, function(response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + return promise; + + function transformResponse(response) { + // make a copy since the response must be cacheable + var resp = extend({}, response, { + data: transformData(response.data, response.headers, respTransformFn) + }); + return (isSuccess(response.status)) + ? resp + : $q.reject(resp); + } + } + + $http.pendingRequests = []; + + /** + * @ngdoc method + * @name ng.$http#get + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `GET` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#delete + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `DELETE` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#head + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `HEAD` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#jsonp + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `JSONP` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request. + * Should contain `JSON_CALLBACK` string. + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + createShortMethods('get', 'delete', 'head', 'jsonp'); + + /** + * @ngdoc method + * @name ng.$http#post + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `POST` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + + /** + * @ngdoc method + * @name ng.$http#put + * @methodOf ng.$http + * + * @description + * Shortcut method to perform `PUT` request. + * + * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {*} data Request content + * @param {Object=} config Optional configuration object + * @returns {HttpPromise} Future object + */ + createShortMethodsWithData('post', 'put'); + + /** + * @ngdoc property + * @name ng.$http#defaults + * @propertyOf ng.$http + * + * @description + * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of + * default headers as well as request and response transformations. + * + * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. + */ + $http.defaults = $config; + + + return $http; + + + function createShortMethods(names) { + forEach(arguments, function(name) { + $http[name] = function(url, config) { + return $http(extend(config || {}, { + method: name, + url: url + })); + }; + }); + } + + + function createShortMethodsWithData(name) { + forEach(arguments, function(name) { + $http[name] = function(url, data, config) { + return $http(extend(config || {}, { + method: name, + url: url, + data: data + })); + }; + }); + } + + + /** + * Makes the request. + * + * !!! ACCESSES CLOSURE VARS: + * $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests + */ + function sendReq(config, reqData, reqHeaders) { + var deferred = $q.defer(), + promise = deferred.promise, + cache, + cachedResp, + url = buildUrl(config.url, config.params); + + $http.pendingRequests.push(config); + promise.then(removePendingReq, removePendingReq); + + + if (config.cache && config.method == 'GET') { + cache = isObject(config.cache) ? config.cache : defaultCache; + } + + if (cache) { + cachedResp = cache.get(url); + if (cachedResp) { + if (cachedResp.then) { + // cached request has already been sent, but there is no response yet + cachedResp.then(removePendingReq, removePendingReq); + return cachedResp; + } else { + // serving from cache + if (isArray(cachedResp)) { + resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2])); + } else { + resolvePromise(cachedResp, 200, {}); + } + } + } else { + // put the promise for the non-transformed response into cache as a placeholder + cache.put(url, promise); + } + } + + // if we won't have the response in cache, send the request to the backend + if (!cachedResp) { + $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, + config.withCredentials); + } + + return promise; + + + /** + * Callback registered to $httpBackend(): + * - caches the response if desired + * - resolves the raw $http promise + * - calls $apply + */ + function done(status, response, headersString) { + if (cache) { + if (isSuccess(status)) { + cache.put(url, [status, response, parseHeaders(headersString)]); + } else { + // remove promise from the cache + cache.remove(url); + } + } + + resolvePromise(response, status, headersString); + $rootScope.$apply(); + } + + + /** + * Resolves the raw $http promise. + */ + function resolvePromise(response, status, headers) { + // normalize internal statuses to 0 + status = Math.max(status, 0); + + (isSuccess(status) ? deferred.resolve : deferred.reject)({ + data: response, + status: status, + headers: headersGetter(headers), + config: config + }); + } + + + function removePendingReq() { + var idx = indexOf($http.pendingRequests, config); + if (idx !== -1) $http.pendingRequests.splice(idx, 1); + } + } + + + function buildUrl(url, params) { + if (!params) return url; + var parts = []; + forEachSorted(params, function(value, key) { + if (value == null || value == undefined) return; + if (isObject(value)) { + value = toJson(value); + } + parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); + }); + return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); + } + + + }]; +} + +var XHR = window.XMLHttpRequest || function() { + try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {} + try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {} + try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {} + throw new Error("This browser does not support XMLHttpRequest."); +}; + + +/** + * @ngdoc object + * @name ng.$httpBackend + * @requires $browser + * @requires $window + * @requires $document + * + * @description + * HTTP backend used by the {@link ng.$http service} that delegates to + * XMLHttpRequest object or JSONP and deals with browser incompatibilities. + * + * You should never need to use this service directly, instead use the higher-level abstractions: + * {@link ng.$http $http} or {@link ngResource.$resource $resource}. + * + * During testing this implementation is swapped with {@link ngMock.$httpBackend mock + * $httpBackend} which can be trained with responses. + */ +function $HttpBackendProvider() { + this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { + return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks, + $document[0], $window.location.protocol.replace(':', '')); + }]; +} + +function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) { + // TODO(vojta): fix the signature + return function(method, url, post, callback, headers, timeout, withCredentials) { + $browser.$$incOutstandingRequestCount(); + url = url || $browser.url(); + + if (lowercase(method) == 'jsonp') { + var callbackId = '_' + (callbacks.counter++).toString(36); + callbacks[callbackId] = function(data) { + callbacks[callbackId].data = data; + }; + + jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), + function() { + if (callbacks[callbackId].data) { + completeRequest(callback, 200, callbacks[callbackId].data); + } else { + completeRequest(callback, -2); + } + delete callbacks[callbackId]; + }); + } else { + var xhr = new XHR(); + xhr.open(method, url, true); + forEach(headers, function(value, key) { + if (value) xhr.setRequestHeader(key, value); + }); + + var status; + + // In IE6 and 7, this might be called synchronously when xhr.send below is called and the + // response is in the cache. the promise api will ensure that to the app code the api is + // always async + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + var responseHeaders = xhr.getAllResponseHeaders(); + + // TODO(vojta): remove once Firefox 21 gets released. + // begin: workaround to overcome Firefox CORS http response headers bug + // https://bugzilla.mozilla.org/show_bug.cgi?id=608735 + // Firefox already patched in nightly. Should land in Firefox 21. + + // CORS "simple response headers" http://www.w3.org/TR/cors/ + var value, + simpleHeaders = ["Cache-Control", "Content-Language", "Content-Type", + "Expires", "Last-Modified", "Pragma"]; + if (!responseHeaders) { + responseHeaders = ""; + forEach(simpleHeaders, function (header) { + var value = xhr.getResponseHeader(header); + if (value) { + responseHeaders += header + ": " + value + "\n"; + } + }); + } + // end of the workaround. + + completeRequest(callback, status || xhr.status, xhr.responseText, + responseHeaders); + } + }; + + if (withCredentials) { + xhr.withCredentials = true; + } + + xhr.send(post || ''); + + if (timeout > 0) { + $browserDefer(function() { + status = -1; + xhr.abort(); + }, timeout); + } + } + + + function completeRequest(callback, status, response, headersString) { + // URL_MATCH is defined in src/service/location.js + var protocol = (url.match(URL_MATCH) || ['', locationProtocol])[1]; + + // fix status code for file protocol (it's always 0) + status = (protocol == 'file') ? (response ? 200 : 404) : status; + + // normalize IE bug (http://bugs.jquery.com/ticket/1450) + status = status == 1223 ? 204 : status; + + callback(status, response, headersString); + $browser.$$completeOutstandingRequest(noop); + } + }; + + function jsonpReq(url, done) { + // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: + // - fetches local scripts via XHR and evals them + // - adds and immediately removes script elements from the document + var script = rawDocument.createElement('script'), + doneWrapper = function() { + rawDocument.body.removeChild(script); + if (done) done(); + }; + + script.type = 'text/javascript'; + script.src = url; + + if (msie) { + script.onreadystatechange = function() { + if (/loaded|complete/.test(script.readyState)) doneWrapper(); + }; + } else { + script.onload = script.onerror = doneWrapper; + } + + rawDocument.body.appendChild(script); + } +} + +/** + * @ngdoc object + * @name ng.$locale + * + * @description + * $locale service provides localization rules for various Angular components. As of right now the + * only public api is: + * + * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) + */ +function $LocaleProvider(){ + this.$get = function() { + return { + id: 'en-us', + + NUMBER_FORMATS: { + DECIMAL_SEP: '.', + GROUP_SEP: ',', + PATTERNS: [ + { // Decimal Pattern + minInt: 1, + minFrac: 0, + maxFrac: 3, + posPre: '', + posSuf: '', + negPre: '-', + negSuf: '', + gSize: 3, + lgSize: 3 + },{ //Currency Pattern + minInt: 1, + minFrac: 2, + maxFrac: 2, + posPre: '\u00A4', + posSuf: '', + negPre: '(\u00A4', + negSuf: ')', + gSize: 3, + lgSize: 3 + } + ], + CURRENCY_SYM: '$' + }, + + DATETIME_FORMATS: { + MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December' + .split(','), + SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), + DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), + SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), + AMPMS: ['AM','PM'], + medium: 'MMM d, y h:mm:ss a', + short: 'M/d/yy h:mm a', + fullDate: 'EEEE, MMMM d, y', + longDate: 'MMMM d, y', + mediumDate: 'MMM d, y', + shortDate: 'M/d/yy', + mediumTime: 'h:mm:ss a', + shortTime: 'h:mm a' + }, + + pluralCat: function(num) { + if (num === 1) { + return 'one'; + } + return 'other'; + } + }; + }; +} + +function $TimeoutProvider() { + this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', + function($rootScope, $browser, $q, $exceptionHandler) { + var deferreds = {}; + + + /** + * @ngdoc function + * @name ng.$timeout + * @requires $browser + * + * @description + * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch + * block and delegates any exceptions to + * {@link ng.$exceptionHandler $exceptionHandler} service. + * + * The return value of registering a timeout function is a promise, which will be resolved when + * the timeout is reached and the timeout function is executed. + * + * To cancel a timeout request, call `$timeout.cancel(promise)`. + * + * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to + * synchronously flush the queue of deferred functions. + * + * @param {function()} fn A function, whose execution should be delayed. + * @param {number=} [delay=0] Delay in milliseconds. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this + * promise will be resolved with is the return value of the `fn` function. + */ + function timeout(fn, delay, invokeApply) { + var deferred = $q.defer(), + promise = deferred.promise, + skipApply = (isDefined(invokeApply) && !invokeApply), + timeoutId, cleanup; + + timeoutId = $browser.defer(function() { + try { + deferred.resolve(fn()); + } catch(e) { + deferred.reject(e); + $exceptionHandler(e); + } + finally { + delete deferreds[promise.$$timeoutId]; + } + + if (!skipApply) $rootScope.$apply(); + }, delay); + + promise.$$timeoutId = timeoutId; + deferreds[timeoutId] = deferred; + + return promise; + } + + + /** + * @ngdoc function + * @name ng.$timeout#cancel + * @methodOf ng.$timeout + * + * @description + * Cancels a task associated with the `promise`. As a result of this, the promise will be + * resolved with a rejection. + * + * @param {Promise=} promise Promise returned by the `$timeout` function. + * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully + * canceled. + */ + timeout.cancel = function(promise) { + if (promise && promise.$$timeoutId in deferreds) { + deferreds[promise.$$timeoutId].reject('canceled'); + delete deferreds[promise.$$timeoutId]; + return $browser.defer.cancel(promise.$$timeoutId); + } + return false; + }; + + return timeout; + }]; +} + +/** + * @ngdoc object + * @name ng.$filterProvider + * @description + * + * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To + * achieve this a filter definition consists of a factory function which is annotated with dependencies and is + * responsible for creating a filter function. + * + *
        + *   // Filter registration
        + *   function MyModule($provide, $filterProvider) {
        + *     // create a service to demonstrate injection (not always needed)
        + *     $provide.value('greet', function(name){
        + *       return 'Hello ' + name + '!';
        + *     });
        + *
        + *     // register a filter factory which uses the
        + *     // greet service to demonstrate DI.
        + *     $filterProvider.register('greet', function(greet){
        + *       // return the filter function which uses the greet service
        + *       // to generate salutation
        + *       return function(text) {
        + *         // filters need to be forgiving so check input validity
        + *         return text && greet(text) || text;
        + *       };
        + *     });
        + *   }
        + * 
        + * + * The filter function is registered with the `$injector` under the filter name suffixe with `Filter`. + *
        + *   it('should be the same instance', inject(
        + *     function($filterProvider) {
        + *       $filterProvider.register('reverse', function(){
        + *         return ...;
        + *       });
        + *     },
        + *     function($filter, reverseFilter) {
        + *       expect($filter('reverse')).toBe(reverseFilter);
        + *     });
        + * 
        + * + * + * For more information about how angular filters work, and how to create your own filters, see + * {@link guide/dev_guide.templates.filters Understanding Angular Filters} in the angular Developer + * Guide. + */ +/** + * @ngdoc method + * @name ng.$filterProvider#register + * @methodOf ng.$filterProvider + * @description + * Register filter factory function. + * + * @param {String} name Name of the filter. + * @param {function} fn The filter factory function which is injectable. + */ + + +/** + * @ngdoc function + * @name ng.$filter + * @function + * @description + * Filters are used for formatting data displayed to the user. + * + * The general syntax in templates is as follows: + * + * {{ expression [| filter_name[:parameter_value] ... ] }} + * + * @param {String} name Name of the filter function to retrieve + * @return {Function} the filter function + */ +$FilterProvider.$inject = ['$provide']; +function $FilterProvider($provide) { + var suffix = 'Filter'; + + function register(name, factory) { + return $provide.factory(name + suffix, factory); + } + this.register = register; + + this.$get = ['$injector', function($injector) { + return function(name) { + return $injector.get(name + suffix); + } + }]; + + //////////////////////////////////////// + + register('currency', currencyFilter); + register('date', dateFilter); + register('filter', filterFilter); + register('json', jsonFilter); + register('limitTo', limitToFilter); + register('lowercase', lowercaseFilter); + register('number', numberFilter); + register('orderBy', orderByFilter); + register('uppercase', uppercaseFilter); +} + +/** + * @ngdoc filter + * @name ng.filter:filter + * @function + * + * @description + * Selects a subset of items from `array` and returns it as a new array. + * + * Note: This function is used to augment the `Array` type in Angular expressions. See + * {@link ng.$filter} for more information about Angular arrays. + * + * @param {Array} array The source array. + * @param {string|Object|function()} expression The predicate to be used for selecting items from + * `array`. + * + * Can be one of: + * + * - `string`: Predicate that results in a substring match using the value of `expression` + * string. All strings or objects with string properties in `array` that contain this string + * will be returned. The predicate can be negated by prefixing the string with `!`. + * + * - `Object`: A pattern object can be used to filter specific properties on objects contained + * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items + * which have property `name` containing "M" and property `phone` containing "1". A special + * property name `$` can be used (as in `{$:"text"}`) to accept a match against any + * property of the object. That's equivalent to the simple substring match with a `string` + * as described above. + * + * - `function`: A predicate function can be used to write arbitrary filters. The function is + * called for each element of `array`. The final result is an array of those elements that + * the predicate returned true for. + * + * @example + + +
        + + Search: + + + + + + +
        NamePhone
        {{friend.name}}{{friend.phone}}
        +
        + Any:
        + Name only
        + Phone only
        + + + + + + +
        NamePhone
        {{friend.name}}{{friend.phone}}
        +
        + + it('should search across all fields when filtering with a string', function() { + input('searchText').enter('m'); + expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). + toEqual(['Mary', 'Mike', 'Adam']); + + input('searchText').enter('76'); + expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). + toEqual(['John', 'Julie']); + }); + + it('should search in specific fields when filtering with a predicate object', function() { + input('search.$').enter('i'); + expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')). + toEqual(['Mary', 'Mike', 'Julie']); + }); + +
        + */ +function filterFilter() { + return function(array, expression) { + if (!isArray(array)) return array; + var predicates = []; + predicates.check = function(value) { + for (var j = 0; j < predicates.length; j++) { + if(!predicates[j](value)) { + return false; + } + } + return true; + }; + var search = function(obj, text){ + if (text.charAt(0) === '!') { + return !search(obj, text.substr(1)); + } + switch (typeof obj) { + case "boolean": + case "number": + case "string": + return ('' + obj).toLowerCase().indexOf(text) > -1; + case "object": + for ( var objKey in obj) { + if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { + return true; + } + } + return false; + case "array": + for ( var i = 0; i < obj.length; i++) { + if (search(obj[i], text)) { + return true; + } + } + return false; + default: + return false; + } + }; + switch (typeof expression) { + case "boolean": + case "number": + case "string": + expression = {$:expression}; + case "object": + for (var key in expression) { + if (key == '$') { + (function() { + var text = (''+expression[key]).toLowerCase(); + if (!text) return; + predicates.push(function(value) { + return search(value, text); + }); + })(); + } else { + (function() { + var path = key; + var text = (''+expression[key]).toLowerCase(); + if (!text) return; + predicates.push(function(value) { + return search(getter(value, path), text); + }); + })(); + } + } + break; + case 'function': + predicates.push(expression); + break; + default: + return array; + } + var filtered = []; + for ( var j = 0; j < array.length; j++) { + var value = array[j]; + if (predicates.check(value)) { + filtered.push(value); + } + } + return filtered; + } +} + +/** + * @ngdoc filter + * @name ng.filter:currency + * @function + * + * @description + * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default + * symbol for current locale is used. + * + * @param {number} amount Input to filter. + * @param {string=} symbol Currency symbol or identifier to be displayed. + * @returns {string} Formatted number. + * + * + * @example + + + +
        +
        + default currency symbol ($): {{amount | currency}}
        + custom currency identifier (USD$): {{amount | currency:"USD$"}} +
        +
        + + it('should init with 1234.56', function() { + expect(binding('amount | currency')).toBe('$1,234.56'); + expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56'); + }); + it('should update', function() { + input('amount').enter('-1234'); + expect(binding('amount | currency')).toBe('($1,234.00)'); + expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)'); + }); + +
        + */ +currencyFilter.$inject = ['$locale']; +function currencyFilter($locale) { + var formats = $locale.NUMBER_FORMATS; + return function(amount, currencySymbol){ + if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; + return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). + replace(/\u00A4/g, currencySymbol); + }; +} + +/** + * @ngdoc filter + * @name ng.filter:number + * @function + * + * @description + * Formats a number as text. + * + * If the input is not a number an empty string is returned. + * + * @param {number|string} number Number to format. + * @param {(number|string)=} fractionSize Number of decimal places to round the number to. + * If this is not provided then the fraction size is computed from the current locale's number + * formatting pattern. In the case of the default locale, it will be 3. + * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. + * + * @example + + + +
        + Enter number:
        + Default formatting: {{val | number}}
        + No fractions: {{val | number:0}}
        + Negative number: {{-val | number:4}} +
        +
        + + it('should format numbers', function() { + expect(binding('val | number')).toBe('1,234.568'); + expect(binding('val | number:0')).toBe('1,235'); + expect(binding('-val | number:4')).toBe('-1,234.5679'); + }); + + it('should update', function() { + input('val').enter('3374.333'); + expect(binding('val | number')).toBe('3,374.333'); + expect(binding('val | number:0')).toBe('3,374'); + expect(binding('-val | number:4')).toBe('-3,374.3330'); + }); + +
        + */ + + +numberFilter.$inject = ['$locale']; +function numberFilter($locale) { + var formats = $locale.NUMBER_FORMATS; + return function(number, fractionSize) { + return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, + fractionSize); + }; +} + +var DECIMAL_SEP = '.'; +function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { + if (isNaN(number) || !isFinite(number)) return ''; + + var isNegative = number < 0; + number = Math.abs(number); + var numStr = number + '', + formatedText = '', + parts = []; + + var hasExponent = false; + if (numStr.indexOf('e') !== -1) { + var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); + if (match && match[2] == '-' && match[3] > fractionSize + 1) { + numStr = '0'; + } else { + formatedText = numStr; + hasExponent = true; + } + } + + if (!hasExponent) { + var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; + + // determine fractionSize if it is not specified + if (isUndefined(fractionSize)) { + fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); + } + + var pow = Math.pow(10, fractionSize); + number = Math.round(number * pow) / pow; + var fraction = ('' + number).split(DECIMAL_SEP); + var whole = fraction[0]; + fraction = fraction[1] || ''; + + var pos = 0, + lgroup = pattern.lgSize, + group = pattern.gSize; + + if (whole.length >= (lgroup + group)) { + pos = whole.length - lgroup; + for (var i = 0; i < pos; i++) { + if ((pos - i)%group === 0 && i !== 0) { + formatedText += groupSep; + } + formatedText += whole.charAt(i); + } + } + + for (i = pos; i < whole.length; i++) { + if ((whole.length - i)%lgroup === 0 && i !== 0) { + formatedText += groupSep; + } + formatedText += whole.charAt(i); + } + + // format fraction part. + while(fraction.length < fractionSize) { + fraction += '0'; + } + + if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); + } else { + + if (fractionSize > 0 && number > -1 && number < 1) { + formatedText = number.toFixed(fractionSize); + } + } + + parts.push(isNegative ? pattern.negPre : pattern.posPre); + parts.push(formatedText); + parts.push(isNegative ? pattern.negSuf : pattern.posSuf); + return parts.join(''); +} + +function padNumber(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while(num.length < digits) num = '0' + num; + if (trim) + num = num.substr(num.length - digits); + return neg + num; +} + + +function dateGetter(name, size, offset, trim) { + offset = offset || 0; + return function(date) { + var value = date['get' + name](); + if (offset > 0 || value > -offset) + value += offset; + if (value === 0 && offset == -12 ) value = 12; + return padNumber(value, size, trim); + }; +} + +function dateStrGetter(name, shortForm) { + return function(date, formats) { + var value = date['get' + name](); + var get = uppercase(shortForm ? ('SHORT' + name) : name); + + return formats[get][value]; + }; +} + +function timeZoneGetter(date) { + var zone = -1 * date.getTimezoneOffset(); + var paddedZone = (zone >= 0) ? "+" : ""; + + paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + + padNumber(Math.abs(zone % 60), 2); + + return paddedZone; +} + +function ampmGetter(date, formats) { + return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; +} + +var DATE_FORMATS = { + yyyy: dateGetter('FullYear', 4), + yy: dateGetter('FullYear', 2, 0, true), + y: dateGetter('FullYear', 1), + MMMM: dateStrGetter('Month'), + MMM: dateStrGetter('Month', true), + MM: dateGetter('Month', 2, 1), + M: dateGetter('Month', 1, 1), + dd: dateGetter('Date', 2), + d: dateGetter('Date', 1), + HH: dateGetter('Hours', 2), + H: dateGetter('Hours', 1), + hh: dateGetter('Hours', 2, -12), + h: dateGetter('Hours', 1, -12), + mm: dateGetter('Minutes', 2), + m: dateGetter('Minutes', 1), + ss: dateGetter('Seconds', 2), + s: dateGetter('Seconds', 1), + EEEE: dateStrGetter('Day'), + EEE: dateStrGetter('Day', true), + a: ampmGetter, + Z: timeZoneGetter +}; + +var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, + NUMBER_STRING = /^\d+$/; + +/** + * @ngdoc filter + * @name ng.filter:date + * @function + * + * @description + * Formats `date` to a string based on the requested `format`. + * + * `format` string can be composed of the following elements: + * + * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) + * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) + * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) + * * `'MMMM'`: Month in year (January-December) + * * `'MMM'`: Month in year (Jan-Dec) + * * `'MM'`: Month in year, padded (01-12) + * * `'M'`: Month in year (1-12) + * * `'dd'`: Day in month, padded (01-31) + * * `'d'`: Day in month (1-31) + * * `'EEEE'`: Day in Week,(Sunday-Saturday) + * * `'EEE'`: Day in Week, (Sun-Sat) + * * `'HH'`: Hour in day, padded (00-23) + * * `'H'`: Hour in day (0-23) + * * `'hh'`: Hour in am/pm, padded (01-12) + * * `'h'`: Hour in am/pm, (1-12) + * * `'mm'`: Minute in hour, padded (00-59) + * * `'m'`: Minute in hour (0-59) + * * `'ss'`: Second in minute, padded (00-59) + * * `'s'`: Second in minute (0-59) + * * `'a'`: am/pm marker + * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) + * + * `format` string can also be one of the following predefined + * {@link guide/i18n localizable formats}: + * + * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale + * (e.g. Sep 3, 2010 12:05:08 pm) + * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) + * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale + * (e.g. Friday, September 3, 2010) + * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) + * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) + * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) + * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) + * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) + * + * `format` string can contain literal values. These need to be quoted with single quotes (e.g. + * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence + * (e.g. `"h 'o''clock'"`). + * + * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or + * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its + * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is + * specified in the string input, the time is considered to be in the local timezone. + * @param {string=} format Formatting rules (see Description). If not specified, + * `mediumDate` is used. + * @returns {string} Formatted string or the input if input is not recognized as date/millis. + * + * @example + + + {{1288323623006 | date:'medium'}}: + {{1288323623006 | date:'medium'}}
        + {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}: + {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
        + {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}: + {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
        +
        + + it('should format date', function() { + expect(binding("1288323623006 | date:'medium'")). + toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); + expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")). + toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); + expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")). + toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); + }); + +
        + */ +dateFilter.$inject = ['$locale']; +function dateFilter($locale) { + + + var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; + function jsonStringToDate(string){ + var match; + if (match = string.match(R_ISO8601_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0; + if (match[9]) { + tzHour = int(match[9] + match[10]); + tzMin = int(match[9] + match[11]); + } + date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); + date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); + return date; + } + return string; + } + + + return function(date, format) { + var text = '', + parts = [], + fn, match; + + format = format || 'mediumDate'; + format = $locale.DATETIME_FORMATS[format] || format; + if (isString(date)) { + if (NUMBER_STRING.test(date)) { + date = int(date); + } else { + date = jsonStringToDate(date); + } + } + + if (isNumber(date)) { + date = new Date(date); + } + + if (!isDate(date)) { + return date; + } + + while(format) { + match = DATE_FORMATS_SPLIT.exec(format); + if (match) { + parts = concat(parts, match, 1); + format = parts.pop(); + } else { + parts.push(format); + format = null; + } + } + + forEach(parts, function(value){ + fn = DATE_FORMATS[value]; + text += fn ? fn(date, $locale.DATETIME_FORMATS) + : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); + }); + + return text; + }; +} + + +/** + * @ngdoc filter + * @name ng.filter:json + * @function + * + * @description + * Allows you to convert a JavaScript object into JSON string. + * + * This filter is mostly useful for debugging. When using the double curly {{value}} notation + * the binding is automatically converted to JSON. + * + * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. + * @returns {string} JSON string. + * + * + * @example: + + +
        {{ {'name':'value'} | json }}
        +
        + + it('should jsonify filtered objects', function() { + expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/); + }); + +
        + * + */ +function jsonFilter() { + return function(object) { + return toJson(object, true); + }; +} + + +/** + * @ngdoc filter + * @name ng.filter:lowercase + * @function + * @description + * Converts string to lowercase. + * @see angular.lowercase + */ +var lowercaseFilter = valueFn(lowercase); + + +/** + * @ngdoc filter + * @name ng.filter:uppercase + * @function + * @description + * Converts string to uppercase. + * @see angular.uppercase + */ +var uppercaseFilter = valueFn(uppercase); + +/** + * @ngdoc function + * @name ng.filter:limitTo + * @function + * + * @description + * Creates a new array containing only a specified number of elements in an array. The elements + * are taken from either the beginning or the end of the source array, as specified by the + * value and sign (positive or negative) of `limit`. + * + * Note: This function is used to augment the `Array` type in Angular expressions. See + * {@link ng.$filter} for more information about Angular arrays. + * + * @param {Array} array Source array to be limited. + * @param {string|Number} limit The length of the returned array. If the `limit` number is + * positive, `limit` number of items from the beginning of the source array are copied. + * If the number is negative, `limit` number of items from the end of the source array are + * copied. The `limit` will be trimmed if it exceeds `array.length` + * @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit` + * elements. + * + * @example + + + +
        + Limit {{numbers}} to: +

        Output: {{ numbers | limitTo:limit }}

        +
        +
        + + it('should limit the numer array to first three items', function() { + expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3'); + expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]'); + }); + + it('should update the output when -3 is entered', function() { + input('limit').enter(-3); + expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]'); + }); + + it('should not exceed the maximum size of input array', function() { + input('limit').enter(100); + expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]'); + }); + +
        + */ +function limitToFilter(){ + return function(array, limit) { + if (!(array instanceof Array)) return array; + limit = int(limit); + var out = [], + i, n; + + // check that array is iterable + if (!array || !(array instanceof Array)) + return out; + + // if abs(limit) exceeds maximum length, trim it + if (limit > array.length) + limit = array.length; + else if (limit < -array.length) + limit = -array.length; + + if (limit > 0) { + i = 0; + n = limit; + } else { + i = array.length + limit; + n = array.length; + } + + for (; i} expression A predicate to be + * used by the comparator to determine the order of elements. + * + * Can be one of: + * + * - `function`: Getter function. The result of this function will be sorted using the + * `<`, `=`, `>` operator. + * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' + * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control + * ascending or descending sort order (for example, +name or -name). + * - `Array`: An array of function or string predicates. The first predicate in the array + * is used for sorting, but when two items are equivalent, the next predicate is used. + * + * @param {boolean=} reverse Reverse the order the array. + * @returns {Array} Sorted copy of the source array. + * + * @example + + + +
        +
        Sorting predicate = {{predicate}}; reverse = {{reverse}}
        +
        + [ unsorted ] + + + + + + + + + + + +
        Name + (^)Phone NumberAge
        {{friend.name}}{{friend.phone}}{{friend.age}}
        +
        +
        + + it('should be reverse ordered by aged', function() { + expect(binding('predicate')).toBe('-age'); + expect(repeater('table.friend', 'friend in friends').column('friend.age')). + toEqual(['35', '29', '21', '19', '10']); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']); + }); + + it('should reorder the table when user selects different predicate', function() { + element('.doc-example-live a:contains("Name")').click(); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']); + expect(repeater('table.friend', 'friend in friends').column('friend.age')). + toEqual(['35', '10', '29', '19', '21']); + + element('.doc-example-live a:contains("Phone")').click(); + expect(repeater('table.friend', 'friend in friends').column('friend.phone')). + toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']); + expect(repeater('table.friend', 'friend in friends').column('friend.name')). + toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']); + }); + +
        + */ +orderByFilter.$inject = ['$parse']; +function orderByFilter($parse){ + return function(array, sortPredicate, reverseOrder) { + if (!isArray(array)) return array; + if (!sortPredicate) return array; + sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; + sortPredicate = map(sortPredicate, function(predicate){ + var descending = false, get = predicate || identity; + if (isString(predicate)) { + if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { + descending = predicate.charAt(0) == '-'; + predicate = predicate.substring(1); + } + get = $parse(predicate); + } + return reverseComparator(function(a,b){ + return compare(get(a),get(b)); + }, descending); + }); + var arrayCopy = []; + for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } + return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); + + function comparator(o1, o2){ + for ( var i = 0; i < sortPredicate.length; i++) { + var comp = sortPredicate[i](o1, o2); + if (comp !== 0) return comp; + } + return 0; + } + function reverseComparator(comp, descending) { + return toBoolean(descending) + ? function(a,b){return comp(b,a);} + : comp; + } + function compare(v1, v2){ + var t1 = typeof v1; + var t2 = typeof v2; + if (t1 == t2) { + if (t1 == "string") { + v1 = v1.toLowerCase(); + v2 = v2.toLowerCase(); + } + if (v1 === v2) return 0; + return v1 < v2 ? -1 : 1; + } else { + return t1 < t2 ? -1 : 1; + } + } + } +} + +function ngDirective(directive) { + if (isFunction(directive)) { + directive = { + link: directive + } + } + directive.restrict = directive.restrict || 'AC'; + return valueFn(directive); +} + +/** + * @ngdoc directive + * @name ng.directive:a + * @restrict E + * + * @description + * Modifies the default behavior of html A tag, so that the default action is prevented when href + * attribute is empty. + * + * The reasoning for this change is to allow easy creation of action links with `ngClick` directive + * without changing the location or causing page reloads, e.g.: + * `Save` + */ +var htmlAnchorDirective = valueFn({ + restrict: 'E', + compile: function(element, attr) { + + if (msie <= 8) { + + // turn link into a stylable link in IE + // but only if it doesn't have name attribute, in which case it's an anchor + if (!attr.href && !attr.name) { + attr.$set('href', ''); + } + + // add a comment node to anchors to workaround IE bug that causes element content to be reset + // to new attribute content if attribute is updated with value containing @ and element also + // contains value with @ + // see issue #1949 + element.append(document.createComment('IE fix')); + } + + return function(scope, element) { + element.bind('click', function(event){ + // if we have no href url, then don't navigate anywhere. + if (!element.attr('href')) { + event.preventDefault(); + } + }); + } + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngHref + * @restrict A + * + * @description + * Using Angular markup like {{hash}} in an href attribute makes + * the page open to a wrong URL, if the user clicks that link before + * angular has a chance to replace the {{hash}} with actual URL, the + * link will be broken and will most likely return a 404 error. + * The `ngHref` directive solves this problem. + * + * The buggy way to write it: + *
        + * 
        + * 
        + * + * The correct way to write it: + *
        + * 
        + * 
        + * + * @element A + * @param {template} ngHref any string which can contain `{{}}` markup. + * + * @example + * This example uses `link` variable inside `href` attribute: + + +
        +
        link 1 (link, don't reload)
        + link 2 (link, don't reload)
        + link 3 (link, reload!)
        + anchor (link, don't reload)
        + anchor (no link)
        + link (link, change location) + + + it('should execute ng-click but not reload when href without value', function() { + element('#link-1').click(); + expect(input('value').val()).toEqual('1'); + expect(element('#link-1').attr('href')).toBe(""); + }); + + it('should execute ng-click but not reload when href empty string', function() { + element('#link-2').click(); + expect(input('value').val()).toEqual('2'); + expect(element('#link-2').attr('href')).toBe(""); + }); + + it('should execute ng-click and change url when ng-href specified', function() { + expect(element('#link-3').attr('href')).toBe("/123"); + + element('#link-3').click(); + expect(browser().window().path()).toEqual('/123'); + }); + + it('should execute ng-click but not reload when href empty string and name specified', function() { + element('#link-4').click(); + expect(input('value').val()).toEqual('4'); + expect(element('#link-4').attr('href')).toBe(''); + }); + + it('should execute ng-click but not reload when no href but name specified', function() { + element('#link-5').click(); + expect(input('value').val()).toEqual('5'); + expect(element('#link-5').attr('href')).toBe(undefined); + }); + + it('should only change url when only ng-href', function() { + input('value').enter('6'); + expect(element('#link-6').attr('href')).toBe('6'); + + element('#link-6').click(); + expect(browser().location().url()).toEqual('/6'); + }); + + + */ + +/** + * @ngdoc directive + * @name ng.directive:ngSrc + * @restrict A + * + * @description + * Using Angular markup like `{{hash}}` in a `src` attribute doesn't + * work right: The browser will fetch from the URL with the literal + * text `{{hash}}` until Angular replaces the expression inside + * `{{hash}}`. The `ngSrc` directive solves this problem. + * + * The buggy way to write it: + *
        + * 
        + * 
        + * + * The correct way to write it: + *
        + * 
        + * 
        + * + * @element IMG + * @param {template} ngSrc any string which can contain `{{}}` markup. + */ + +/** + * @ngdoc directive + * @name ng.directive:ngDisabled + * @restrict A + * + * @description + * + * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: + *
        + * 
        + * + *
        + *
        + * + * The HTML specs do not require browsers to preserve the special attributes such as disabled. + * (The presence of them means true and absence means false) + * This prevents the angular compiler from correctly retrieving the binding expression. + * To solve this problem, we introduce the `ngDisabled` directive. + * + * @example + + + Click me to toggle:
        + +
        + + it('should toggle button', function() { + expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy(); + input('checked').check(); + expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy(); + }); + +
        + * + * @element INPUT + * @param {expression} ngDisabled Angular expression that will be evaluated. + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngChecked + * @restrict A + * + * @description + * The HTML specs do not require browsers to preserve the special attributes such as checked. + * (The presence of them means true and absence means false) + * This prevents the angular compiler from correctly retrieving the binding expression. + * To solve this problem, we introduce the `ngChecked` directive. + * @example + + + Check me to check both:
        + +
        + + it('should check both checkBoxes', function() { + expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy(); + input('master').check(); + expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy(); + }); + +
        + * + * @element INPUT + * @param {expression} ngChecked Angular expression that will be evaluated. + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMultiple + * @restrict A + * + * @description + * The HTML specs do not require browsers to preserve the special attributes such as multiple. + * (The presence of them means true and absence means false) + * This prevents the angular compiler from correctly retrieving the binding expression. + * To solve this problem, we introduce the `ngMultiple` directive. + * + * @example + + + Check me check multiple:
        + +
        + + it('should toggle multiple', function() { + expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy(); + input('checked').check(); + expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy(); + }); + +
        + * + * @element SELECT + * @param {expression} ngMultiple Angular expression that will be evaluated. + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngReadonly + * @restrict A + * + * @description + * The HTML specs do not require browsers to preserve the special attributes such as readonly. + * (The presence of them means true and absence means false) + * This prevents the angular compiler from correctly retrieving the binding expression. + * To solve this problem, we introduce the `ngReadonly` directive. + * @example + + + Check me to make text readonly:
        + +
        + + it('should toggle readonly attr', function() { + expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy(); + input('checked').check(); + expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy(); + }); + +
        + * + * @element INPUT + * @param {string} expression Angular expression that will be evaluated. + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngSelected + * @restrict A + * + * @description + * The HTML specs do not require browsers to preserve the special attributes such as selected. + * (The presence of them means true and absence means false) + * This prevents the angular compiler from correctly retrieving the binding expression. + * To solve this problem, we introduced the `ngSelected` directive. + * @example + + + Check me to select:
        + +
        + + it('should select Greetings!', function() { + expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy(); + input('selected').check(); + expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy(); + }); + +
        + * + * @element OPTION + * @param {string} expression Angular expression that will be evaluated. + */ + + +var ngAttributeAliasDirectives = {}; + + +// boolean attrs are evaluated +forEach(BOOLEAN_ATTR, function(propName, attrName) { + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + priority: 100, + compile: function() { + return function(scope, element, attr) { + scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { + attr.$set(attrName, !!value); + }); + }; + } + }; + }; +}); + + +// ng-src, ng-href are interpolated +forEach(['src', 'href'], function(attrName) { + var normalized = directiveNormalize('ng-' + attrName); + ngAttributeAliasDirectives[normalized] = function() { + return { + priority: 99, // it needs to run after the attributes are interpolated + link: function(scope, element, attr) { + attr.$observe(normalized, function(value) { + if (!value) + return; + + attr.$set(attrName, value); + + // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist + // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need + // to set the property as well to achieve the desired effect. + // we use attr[attrName] value since $set can sanitize the url. + if (msie) element.prop(attrName, attr[attrName]); + }); + } + }; + }; +}); + +var nullFormCtrl = { + $addControl: noop, + $removeControl: noop, + $setValidity: noop, + $setDirty: noop +}; + +/** + * @ngdoc object + * @name ng.directive:form.FormController + * + * @property {boolean} $pristine True if user has not interacted with the form yet. + * @property {boolean} $dirty True if user has already interacted with the form. + * @property {boolean} $valid True if all of the containing forms and controls are valid. + * @property {boolean} $invalid True if at least one containing control or form is invalid. + * + * @property {Object} $error Is an object hash, containing references to all invalid controls or + * forms, where: + * + * - keys are validation tokens (error names) — such as `required`, `url` or `email`), + * - values are arrays of controls or forms that are invalid with given error. + * + * @description + * `FormController` keeps track of all its controls and nested forms as well as state of them, + * such as being valid/invalid or dirty/pristine. + * + * Each {@link ng.directive:form form} directive creates an instance + * of `FormController`. + * + */ +//asks for $scope to fool the BC controller module +FormController.$inject = ['$element', '$attrs', '$scope']; +function FormController(element, attrs) { + var form = this, + parentForm = element.parent().controller('form') || nullFormCtrl, + invalidCount = 0, // used to easily determine if we are valid + errors = form.$error = {}; + + // init state + form.$name = attrs.name || attrs.ngForm; + form.$dirty = false; + form.$pristine = true; + form.$valid = true; + form.$invalid = false; + + parentForm.$addControl(form); + + // Setup initial state of the control + element.addClass(PRISTINE_CLASS); + toggleValidCss(true); + + // convenience method for easy toggling of classes + function toggleValidCss(isValid, validationErrorKey) { + validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; + element. + removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). + addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); + } + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$addControl + * @methodOf ng.directive:form.FormController + * + * @description + * Register a control with the form. + * + * Input elements using ngModelController do this automatically when they are linked. + */ + form.$addControl = function(control) { + if (control.$name && !form.hasOwnProperty(control.$name)) { + form[control.$name] = control; + } + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$removeControl + * @methodOf ng.directive:form.FormController + * + * @description + * Deregister a control from the form. + * + * Input elements using ngModelController do this automatically when they are destroyed. + */ + form.$removeControl = function(control) { + if (control.$name && form[control.$name] === control) { + delete form[control.$name]; + } + forEach(errors, function(queue, validationToken) { + form.$setValidity(validationToken, true, control); + }); + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$setValidity + * @methodOf ng.directive:form.FormController + * + * @description + * Sets the validity of a form control. + * + * This method will also propagate to parent forms. + */ + form.$setValidity = function(validationToken, isValid, control) { + var queue = errors[validationToken]; + + if (isValid) { + if (queue) { + arrayRemove(queue, control); + if (!queue.length) { + invalidCount--; + if (!invalidCount) { + toggleValidCss(isValid); + form.$valid = true; + form.$invalid = false; + } + errors[validationToken] = false; + toggleValidCss(true, validationToken); + parentForm.$setValidity(validationToken, true, form); + } + } + + } else { + if (!invalidCount) { + toggleValidCss(isValid); + } + if (queue) { + if (includes(queue, control)) return; + } else { + errors[validationToken] = queue = []; + invalidCount++; + toggleValidCss(false, validationToken); + parentForm.$setValidity(validationToken, false, form); + } + queue.push(control); + + form.$valid = false; + form.$invalid = true; + } + }; + + /** + * @ngdoc function + * @name ng.directive:form.FormController#$setDirty + * @methodOf ng.directive:form.FormController + * + * @description + * Sets the form to a dirty state. + * + * This method can be called to add the 'ng-dirty' class and set the form to a dirty + * state (ng-dirty class). This method will also propagate to parent forms. + */ + form.$setDirty = function() { + element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); + form.$dirty = true; + form.$pristine = false; + parentForm.$setDirty(); + }; + +} + + +/** + * @ngdoc directive + * @name ng.directive:ngForm + * @restrict EAC + * + * @description + * Nestable alias of {@link ng.directive:form `form`} directive. HTML + * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a + * sub-group of controls needs to be determined. + * + * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into + * related scope, under this name. + * + */ + + /** + * @ngdoc directive + * @name ng.directive:form + * @restrict E + * + * @description + * Directive that instantiates + * {@link ng.directive:form.FormController FormController}. + * + * If `name` attribute is specified, the form controller is published onto the current scope under + * this name. + * + * # Alias: {@link ng.directive:ngForm `ngForm`} + * + * In angular forms can be nested. This means that the outer form is valid when all of the child + * forms are valid as well. However browsers do not allow nesting of `
        ` elements, for this + * reason angular provides {@link ng.directive:ngForm `ngForm`} alias + * which behaves identical to `` but allows form nesting. + * + * + * # CSS classes + * - `ng-valid` Is set if the form is valid. + * - `ng-invalid` Is set if the form is invalid. + * - `ng-pristine` Is set if the form is pristine. + * - `ng-dirty` Is set if the form is dirty. + * + * + * # Submitting a form and preventing default action + * + * Since the role of forms in client-side Angular applications is different than in classical + * roundtrip apps, it is desirable for the browser not to translate the form submission into a full + * page reload that sends the data to the server. Instead some javascript logic should be triggered + * to handle the form submission in application specific way. + * + * For this reason, Angular prevents the default action (form submission to the server) unless the + * `` element has an `action` attribute specified. + * + * You can use one of the following two ways to specify what javascript method should be called when + * a form is submitted: + * + * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element + * - {@link ng.directive:ngClick ngClick} directive on the first + * button or input field of type submit (input[type=submit]) + * + * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This + * is because of the following form submission rules coming from the html spec: + * + * - If a form has only one input field then hitting enter in this field triggers form submit + * (`ngSubmit`) + * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter + * doesn't trigger submit + * - if a form has one or more input fields and one or more buttons or input[type=submit] then + * hitting enter in any of the input fields will trigger the click handler on the *first* button or + * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) + * + * @param {string=} name Name of the form. If specified, the form controller will be published into + * related scope, under this name. + * + * @example + + + + + userType: + Required!
        + userType = {{userType}}
        + myForm.input.$valid = {{myForm.input.$valid}}
        + myForm.input.$error = {{myForm.input.$error}}
        + myForm.$valid = {{myForm.$valid}}
        + myForm.$error.required = {{!!myForm.$error.required}}
        + +
        + + it('should initialize to model', function() { + expect(binding('userType')).toEqual('guest'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('userType').enter(''); + expect(binding('userType')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
        + */ +var formDirectiveFactory = function(isNgForm) { + return ['$timeout', function($timeout) { + var formDirective = { + name: 'form', + restrict: 'E', + controller: FormController, + compile: function() { + return { + pre: function(scope, formElement, attr, controller) { + if (!attr.action) { + // we can't use jq events because if a form is destroyed during submission the default + // action is not prevented. see #1238 + // + // IE 9 is not affected because it doesn't fire a submit event and try to do a full + // page reload if the form was destroyed by submission of the form via a click handler + // on a button in the form. Looks like an IE9 specific bug. + var preventDefaultListener = function(event) { + event.preventDefault + ? event.preventDefault() + : event.returnValue = false; // IE + }; + + addEventListenerFn(formElement[0], 'submit', preventDefaultListener); + + // unregister the preventDefault listener so that we don't not leak memory but in a + // way that will achieve the prevention of the default action. + formElement.bind('$destroy', function() { + $timeout(function() { + removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); + }, 0, false); + }); + } + + var parentFormCtrl = formElement.parent().controller('form'), + alias = attr.name || attr.ngForm; + + if (alias) { + scope[alias] = controller; + } + if (parentFormCtrl) { + formElement.bind('$destroy', function() { + parentFormCtrl.$removeControl(controller); + if (alias) { + scope[alias] = undefined; + } + extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards + }); + } + } + }; + } + }; + + return isNgForm ? extend(copy(formDirective), {restrict: 'EAC'}) : formDirective; + }]; +}; + +var formDirective = formDirectiveFactory(); +var ngFormDirective = formDirectiveFactory(true); + +var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; +var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$/; +var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; + +var inputType = { + + /** + * @ngdoc inputType + * @name ng.directive:input.text + * + * @description + * Standard HTML text input with angular data binding. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Adds `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
        + Single word: + + Required! + + Single word only! + + text = {{text}}
        + myForm.input.$valid = {{myForm.input.$valid}}
        + myForm.input.$error = {{myForm.input.$error}}
        + myForm.$valid = {{myForm.$valid}}
        + myForm.$error.required = {{!!myForm.$error.required}}
        +
        +
        + + it('should initialize to model', function() { + expect(binding('text')).toEqual('guest'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('text').enter(''); + expect(binding('text')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if multi word', function() { + input('text').enter('hello world'); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
        + */ + 'text': textInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.number + * + * @description + * Text input with number validation and transformation. Sets the `number` validation + * error if not a valid number. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. + * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
        + Number: + + Required! + + Not valid number! + value = {{value}}
        + myForm.input.$valid = {{myForm.input.$valid}}
        + myForm.input.$error = {{myForm.input.$error}}
        + myForm.$valid = {{myForm.$valid}}
        + myForm.$error.required = {{!!myForm.$error.required}}
        +
        +
        + + it('should initialize to model', function() { + expect(binding('value')).toEqual('12'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('value').enter(''); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if over max', function() { + input('value').enter('123'); + expect(binding('value')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
        + */ + 'number': numberInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.url + * + * @description + * Text input with URL validation. Sets the `url` validation error key if the content is not a + * valid URL. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
        + URL: + + Required! + + Not valid url! + text = {{text}}
        + myForm.input.$valid = {{myForm.input.$valid}}
        + myForm.input.$error = {{myForm.input.$error}}
        + myForm.$valid = {{myForm.$valid}}
        + myForm.$error.required = {{!!myForm.$error.required}}
        + myForm.$error.url = {{!!myForm.$error.url}}
        +
        +
        + + it('should initialize to model', function() { + expect(binding('text')).toEqual('http://google.com'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('text').enter(''); + expect(binding('text')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if not url', function() { + input('text').enter('xxx'); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
        + */ + 'url': urlInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.email + * + * @description + * Text input with email validation. Sets the `email` validation error key if not a valid email + * address. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
        + Email: + + Required! + + Not valid email! + text = {{text}}
        + myForm.input.$valid = {{myForm.input.$valid}}
        + myForm.input.$error = {{myForm.input.$error}}
        + myForm.$valid = {{myForm.$valid}}
        + myForm.$error.required = {{!!myForm.$error.required}}
        + myForm.$error.email = {{!!myForm.$error.email}}
        +
        +
        + + it('should initialize to model', function() { + expect(binding('text')).toEqual('me@example.com'); + expect(binding('myForm.input.$valid')).toEqual('true'); + }); + + it('should be invalid if empty', function() { + input('text').enter(''); + expect(binding('text')).toEqual(''); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + + it('should be invalid if not email', function() { + input('text').enter('xxx'); + expect(binding('myForm.input.$valid')).toEqual('false'); + }); + +
        + */ + 'email': emailInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.radio + * + * @description + * HTML radio button. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string} value The value to which the expression should be set when selected. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
        + Red
        + Green
        + Blue
        + color = {{color}}
        +
        +
        + + it('should change state', function() { + expect(binding('color')).toEqual('blue'); + + input('color').select('red'); + expect(binding('color')).toEqual('red'); + }); + +
        + */ + 'radio': radioInputType, + + + /** + * @ngdoc inputType + * @name ng.directive:input.checkbox + * + * @description + * HTML checkbox. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} ngTrueValue The value to which the expression should be set when selected. + * @param {string=} ngFalseValue The value to which the expression should be set when not selected. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
        + Value1:
        + Value2:
        + value1 = {{value1}}
        + value2 = {{value2}}
        +
        +
        + + it('should change state', function() { + expect(binding('value1')).toEqual('true'); + expect(binding('value2')).toEqual('YES'); + + input('value1').check(); + input('value2').check(); + expect(binding('value1')).toEqual('false'); + expect(binding('value2')).toEqual('NO'); + }); + +
        + */ + 'checkbox': checkboxInputType, + + 'hidden': noop, + 'button': noop, + 'submit': noop, + 'reset': noop +}; + + +function isEmpty(value) { + return isUndefined(value) || value === '' || value === null || value !== value; +} + + +function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { + + var listener = function() { + var value = trim(element.val()); + + if (ctrl.$viewValue !== value) { + scope.$apply(function() { + ctrl.$setViewValue(value); + }); + } + }; + + // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the + // input event on backspace, delete or cut + if ($sniffer.hasEvent('input')) { + element.bind('input', listener); + } else { + var timeout; + + var deferListener = function() { + if (!timeout) { + timeout = $browser.defer(function() { + listener(); + timeout = null; + }); + } + }; + + element.bind('keydown', function(event) { + var key = event.keyCode; + + // ignore + // command modifiers arrows + if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; + + deferListener(); + }); + + // if user paste into input using mouse, we need "change" event to catch it + element.bind('change', listener); + + // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it + if ($sniffer.hasEvent('paste')) { + element.bind('paste cut', deferListener); + } + } + + + ctrl.$render = function() { + element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); + }; + + // pattern validator + var pattern = attr.ngPattern, + patternValidator; + + var validate = function(regexp, value) { + if (isEmpty(value) || regexp.test(value)) { + ctrl.$setValidity('pattern', true); + return value; + } else { + ctrl.$setValidity('pattern', false); + return undefined; + } + }; + + if (pattern) { + if (pattern.match(/^\/(.*)\/$/)) { + pattern = new RegExp(pattern.substr(1, pattern.length - 2)); + patternValidator = function(value) { + return validate(pattern, value) + }; + } else { + patternValidator = function(value) { + var patternObj = scope.$eval(pattern); + + if (!patternObj || !patternObj.test) { + throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj); + } + return validate(patternObj, value); + }; + } + + ctrl.$formatters.push(patternValidator); + ctrl.$parsers.push(patternValidator); + } + + // min length validator + if (attr.ngMinlength) { + var minlength = int(attr.ngMinlength); + var minLengthValidator = function(value) { + if (!isEmpty(value) && value.length < minlength) { + ctrl.$setValidity('minlength', false); + return undefined; + } else { + ctrl.$setValidity('minlength', true); + return value; + } + }; + + ctrl.$parsers.push(minLengthValidator); + ctrl.$formatters.push(minLengthValidator); + } + + // max length validator + if (attr.ngMaxlength) { + var maxlength = int(attr.ngMaxlength); + var maxLengthValidator = function(value) { + if (!isEmpty(value) && value.length > maxlength) { + ctrl.$setValidity('maxlength', false); + return undefined; + } else { + ctrl.$setValidity('maxlength', true); + return value; + } + }; + + ctrl.$parsers.push(maxLengthValidator); + ctrl.$formatters.push(maxLengthValidator); + } +} + +function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + ctrl.$parsers.push(function(value) { + var empty = isEmpty(value); + if (empty || NUMBER_REGEXP.test(value)) { + ctrl.$setValidity('number', true); + return value === '' ? null : (empty ? value : parseFloat(value)); + } else { + ctrl.$setValidity('number', false); + return undefined; + } + }); + + ctrl.$formatters.push(function(value) { + return isEmpty(value) ? '' : '' + value; + }); + + if (attr.min) { + var min = parseFloat(attr.min); + var minValidator = function(value) { + if (!isEmpty(value) && value < min) { + ctrl.$setValidity('min', false); + return undefined; + } else { + ctrl.$setValidity('min', true); + return value; + } + }; + + ctrl.$parsers.push(minValidator); + ctrl.$formatters.push(minValidator); + } + + if (attr.max) { + var max = parseFloat(attr.max); + var maxValidator = function(value) { + if (!isEmpty(value) && value > max) { + ctrl.$setValidity('max', false); + return undefined; + } else { + ctrl.$setValidity('max', true); + return value; + } + }; + + ctrl.$parsers.push(maxValidator); + ctrl.$formatters.push(maxValidator); + } + + ctrl.$formatters.push(function(value) { + + if (isEmpty(value) || isNumber(value)) { + ctrl.$setValidity('number', true); + return value; + } else { + ctrl.$setValidity('number', false); + return undefined; + } + }); +} + +function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + var urlValidator = function(value) { + if (isEmpty(value) || URL_REGEXP.test(value)) { + ctrl.$setValidity('url', true); + return value; + } else { + ctrl.$setValidity('url', false); + return undefined; + } + }; + + ctrl.$formatters.push(urlValidator); + ctrl.$parsers.push(urlValidator); +} + +function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { + textInputType(scope, element, attr, ctrl, $sniffer, $browser); + + var emailValidator = function(value) { + if (isEmpty(value) || EMAIL_REGEXP.test(value)) { + ctrl.$setValidity('email', true); + return value; + } else { + ctrl.$setValidity('email', false); + return undefined; + } + }; + + ctrl.$formatters.push(emailValidator); + ctrl.$parsers.push(emailValidator); +} + +function radioInputType(scope, element, attr, ctrl) { + // make the name unique, if not defined + if (isUndefined(attr.name)) { + element.attr('name', nextUid()); + } + + element.bind('click', function() { + if (element[0].checked) { + scope.$apply(function() { + ctrl.$setViewValue(attr.value); + }); + } + }); + + ctrl.$render = function() { + var value = attr.value; + element[0].checked = (value == ctrl.$viewValue); + }; + + attr.$observe('value', ctrl.$render); +} + +function checkboxInputType(scope, element, attr, ctrl) { + var trueValue = attr.ngTrueValue, + falseValue = attr.ngFalseValue; + + if (!isString(trueValue)) trueValue = true; + if (!isString(falseValue)) falseValue = false; + + element.bind('click', function() { + scope.$apply(function() { + ctrl.$setViewValue(element[0].checked); + }); + }); + + ctrl.$render = function() { + element[0].checked = ctrl.$viewValue; + }; + + ctrl.$formatters.push(function(value) { + return value === trueValue; + }); + + ctrl.$parsers.push(function(value) { + return value ? trueValue : falseValue; + }); +} + + +/** + * @ngdoc directive + * @name ng.directive:textarea + * @restrict E + * + * @description + * HTML textarea element control with angular data-binding. The data-binding and validation + * properties of this element are exactly the same as those of the + * {@link ng.directive:input input element}. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to + * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of + * `required` when you want to data-bind to the `required` attribute. + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + */ + + +/** + * @ngdoc directive + * @name ng.directive:input + * @restrict E + * + * @description + * HTML input element control with angular data-binding. Input control follows HTML5 input types + * and polyfills the HTML5 validation behavior for older browsers. + * + * @param {string} ngModel Assignable angular expression to data-bind to. + * @param {string=} name Property name of the form under which the control is published. + * @param {string=} required Sets `required` validation error key if the value is not entered. + * @param {boolean=} ngRequired Sets `required` attribute if set to true + * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than + * minlength. + * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than + * maxlength. + * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the + * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for + * patterns defined as scope expressions. + * @param {string=} ngChange Angular expression to be executed when input changes due to user + * interaction with the input element. + * + * @example + + + +
        +
        + User name: + + Required!
        + Last name: + + Too short! + + Too long!
        +
        +
        + user = {{user}}
        + myForm.userName.$valid = {{myForm.userName.$valid}}
        + myForm.userName.$error = {{myForm.userName.$error}}
        + myForm.lastName.$valid = {{myForm.lastName.$valid}}
        + myForm.lastName.$error = {{myForm.lastName.$error}}
        + myForm.$valid = {{myForm.$valid}}
        + myForm.$error.required = {{!!myForm.$error.required}}
        + myForm.$error.minlength = {{!!myForm.$error.minlength}}
        + myForm.$error.maxlength = {{!!myForm.$error.maxlength}}
        +
        +
        + + it('should initialize to model', function() { + expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}'); + expect(binding('myForm.userName.$valid')).toEqual('true'); + expect(binding('myForm.$valid')).toEqual('true'); + }); + + it('should be invalid if empty when required', function() { + input('user.name').enter(''); + expect(binding('user')).toEqual('{"last":"visitor"}'); + expect(binding('myForm.userName.$valid')).toEqual('false'); + expect(binding('myForm.$valid')).toEqual('false'); + }); + + it('should be valid if empty when min length is set', function() { + input('user.last').enter(''); + expect(binding('user')).toEqual('{"name":"guest","last":""}'); + expect(binding('myForm.lastName.$valid')).toEqual('true'); + expect(binding('myForm.$valid')).toEqual('true'); + }); + + it('should be invalid if less than required min length', function() { + input('user.last').enter('xx'); + expect(binding('user')).toEqual('{"name":"guest"}'); + expect(binding('myForm.lastName.$valid')).toEqual('false'); + expect(binding('myForm.lastName.$error')).toMatch(/minlength/); + expect(binding('myForm.$valid')).toEqual('false'); + }); + + it('should be invalid if longer than max length', function() { + input('user.last').enter('some ridiculously long name'); + expect(binding('user')) + .toEqual('{"name":"guest"}'); + expect(binding('myForm.lastName.$valid')).toEqual('false'); + expect(binding('myForm.lastName.$error')).toMatch(/maxlength/); + expect(binding('myForm.$valid')).toEqual('false'); + }); + +
        + */ +var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { + return { + restrict: 'E', + require: '?ngModel', + link: function(scope, element, attr, ctrl) { + if (ctrl) { + (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, + $browser); + } + } + }; +}]; + +var VALID_CLASS = 'ng-valid', + INVALID_CLASS = 'ng-invalid', + PRISTINE_CLASS = 'ng-pristine', + DIRTY_CLASS = 'ng-dirty'; + +/** + * @ngdoc object + * @name ng.directive:ngModel.NgModelController + * + * @property {string} $viewValue Actual string value in the view. + * @property {*} $modelValue The value in the model, that the control is bound to. + * @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever + the control reads value from the DOM. Each function is called, in turn, passing the value + through to the next. Used to sanitize / convert the value as well as validation. + + For validation, the parsers should update the validity state using + {@link ng.directive:ngModel.NgModelController#$setValidity $setValidity()}, + and return `undefined` for invalid values. + * + * @property {Array.} $formatters Array of functions to execute, as a pipeline, whenever + * the model value changes. Each function is called, in turn, passing the value through to the + * next. Used to format / convert values for display in the control and validation. + *
        + *      function formatter(value) {
        + *        if (value) {
        + *          return value.toUpperCase();
        + *        }
        + *      }
        + *      ngModel.$formatters.push(formatter);
        + *      
        + * @property {Object} $error An bject hash with all errors as keys. + * + * @property {boolean} $pristine True if user has not interacted with the control yet. + * @property {boolean} $dirty True if user has already interacted with the control. + * @property {boolean} $valid True if there is no error. + * @property {boolean} $invalid True if at least one error on the control. + * + * @description + * + * `NgModelController` provides API for the `ng-model` directive. The controller contains + * services for data-binding, validation, CSS update, value formatting and parsing. It + * specifically does not contain any logic which deals with DOM rendering or listening to + * DOM events. The `NgModelController` is meant to be extended by other directives where, the + * directive provides DOM manipulation and the `NgModelController` provides the data-binding. + * Note that you cannot use `NgModelController` in a directive with an isolated scope, + * as, in that case, the `ng-model` value gets put into the isolated scope and does not get + * propogated to the parent scope. + * + * + * This example shows how to use `NgModelController` with a custom control to achieve + * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) + * collaborate together to achieve the desired result. + * + * + + [contenteditable] { + border: 1px solid black; + background-color: white; + min-height: 20px; + } + + .ng-invalid { + border: 1px solid red; + } + + + + angular.module('customControl', []). + directive('contenteditable', function() { + return { + restrict: 'A', // only activate on element attribute + require: '?ngModel', // get a hold of NgModelController + link: function(scope, element, attrs, ngModel) { + if(!ngModel) return; // do nothing if no ng-model + + // Specify how UI should be updated + ngModel.$render = function() { + element.html(ngModel.$viewValue || ''); + }; + + // Listen for change events to enable binding + element.bind('blur keyup change', function() { + scope.$apply(read); + }); + read(); // initialize + + // Write data to the model + function read() { + var html = element.html(); + // When we clear the content editable the browser leaves a
        behind + // If strip-br attribute is provided then we strip this out + if( attrs.stripBr && html == '
        ' ) { + html = ''; + } + ngModel.$setViewValue(html); + } + } + }; + }); +
        + +
        +
        Change me!
        + Required! +
        + +
        +
        + + it('should data-bind and become invalid', function() { + var contentEditable = element('[contenteditable]'); + + expect(contentEditable.text()).toEqual('Change me!'); + input('userContent').enter(''); + expect(contentEditable.text()).toEqual(''); + expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/); + }); + + *
        + * + */ +var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', + function($scope, $exceptionHandler, $attr, $element, $parse) { + this.$viewValue = Number.NaN; + this.$modelValue = Number.NaN; + this.$parsers = []; + this.$formatters = []; + this.$viewChangeListeners = []; + this.$pristine = true; + this.$dirty = false; + this.$valid = true; + this.$invalid = false; + this.$name = $attr.name; + + var ngModelGet = $parse($attr.ngModel), + ngModelSet = ngModelGet.assign; + + if (!ngModelSet) { + throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel + + ' (' + startingTag($element) + ')'); + } + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$render + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Called when the view needs to be updated. It is expected that the user of the ng-model + * directive will implement this method. + */ + this.$render = noop; + + var parentForm = $element.inheritedData('$formController') || nullFormCtrl, + invalidCount = 0, // used to easily determine if we are valid + $error = this.$error = {}; // keep invalid keys here + + + // Setup initial state of the control + $element.addClass(PRISTINE_CLASS); + toggleValidCss(true); + + // convenience method for easy toggling of classes + function toggleValidCss(isValid, validationErrorKey) { + validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; + $element. + removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). + addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); + } + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$setValidity + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Change the validity state, and notifies the form when the control changes validity. (i.e. it + * does not notify form if given validator is already marked as invalid). + * + * This method should be called by validators - i.e. the parser or formatter functions. + * + * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign + * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. + * The `validationErrorKey` should be in camelCase and will get converted into dash-case + * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` + * class and can be bound to as `{{someForm.someControl.$error.myError}}` . + * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). + */ + this.$setValidity = function(validationErrorKey, isValid) { + if ($error[validationErrorKey] === !isValid) return; + + if (isValid) { + if ($error[validationErrorKey]) invalidCount--; + if (!invalidCount) { + toggleValidCss(true); + this.$valid = true; + this.$invalid = false; + } + } else { + toggleValidCss(false); + this.$invalid = true; + this.$valid = false; + invalidCount++; + } + + $error[validationErrorKey] = !isValid; + toggleValidCss(isValid, validationErrorKey); + + parentForm.$setValidity(validationErrorKey, isValid, this); + }; + + + /** + * @ngdoc function + * @name ng.directive:ngModel.NgModelController#$setViewValue + * @methodOf ng.directive:ngModel.NgModelController + * + * @description + * Read a value from view. + * + * This method should be called from within a DOM event handler. + * For example {@link ng.directive:input input} or + * {@link ng.directive:select select} directives call it. + * + * It internally calls all `$parsers` (including validators) and updates the `$modelValue` and the actual model path. + * Lastly it calls all registered change listeners. + * + * @param {string} value Value from the view. + */ + this.$setViewValue = function(value) { + this.$viewValue = value; + + // change to dirty + if (this.$pristine) { + this.$dirty = true; + this.$pristine = false; + $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); + parentForm.$setDirty(); + } + + forEach(this.$parsers, function(fn) { + value = fn(value); + }); + + if (this.$modelValue !== value) { + this.$modelValue = value; + ngModelSet($scope, value); + forEach(this.$viewChangeListeners, function(listener) { + try { + listener(); + } catch(e) { + $exceptionHandler(e); + } + }) + } + }; + + // model -> value + var ctrl = this; + + $scope.$watch(function ngModelWatch() { + var value = ngModelGet($scope); + + // if scope model value and ngModel value are out of sync + if (ctrl.$modelValue !== value) { + + var formatters = ctrl.$formatters, + idx = formatters.length; + + ctrl.$modelValue = value; + while(idx--) { + value = formatters[idx](value); + } + + if (ctrl.$viewValue !== value) { + ctrl.$viewValue = value; + ctrl.$render(); + } + } + }); +}]; + + +/** + * @ngdoc directive + * @name ng.directive:ngModel + * + * @element input + * + * @description + * Is a directive that tells Angular to do two-way data binding. It works together with `input`, + * `select`, `textarea`. You can easily write your own directives to use `ngModel` as well. + * + * `ngModel` is responsible for: + * + * - binding the view into the model, which other directives such as `input`, `textarea` or `select` + * require, + * - providing validation behavior (i.e. required, number, email, url), + * - keeping state of the control (valid/invalid, dirty/pristine, validation errors), + * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`), + * - register the control with parent {@link ng.directive:form form}. + * + * Note: `ngModel` will try to bind to the property given by evaluating the expression on the + * current scope. If the property doesn't already exist on this scope, it will be created + * implicitly and added to the scope. + * + * For basic examples, how to use `ngModel`, see: + * + * - {@link ng.directive:input input} + * - {@link ng.directive:input.text text} + * - {@link ng.directive:input.checkbox checkbox} + * - {@link ng.directive:input.radio radio} + * - {@link ng.directive:input.number number} + * - {@link ng.directive:input.email email} + * - {@link ng.directive:input.url url} + * - {@link ng.directive:select select} + * - {@link ng.directive:textarea textarea} + * + */ +var ngModelDirective = function() { + return { + require: ['ngModel', '^?form'], + controller: NgModelController, + link: function(scope, element, attr, ctrls) { + // notify others, especially parent forms + + var modelCtrl = ctrls[0], + formCtrl = ctrls[1] || nullFormCtrl; + + formCtrl.$addControl(modelCtrl); + + element.bind('$destroy', function() { + formCtrl.$removeControl(modelCtrl); + }); + } + }; +}; + + +/** + * @ngdoc directive + * @name ng.directive:ngChange + * @restrict E + * + * @description + * Evaluate given expression when user changes the input. + * The expression is not evaluated when the value change is coming from the model. + * + * Note, this directive requires `ngModel` to be present. + * + * @element input + * + * @example + * + * + * + *
        + * + * + *
        + * debug = {{confirmed}}
        + * counter = {{counter}} + *
        + *
        + * + * it('should evaluate the expression if changing from view', function() { + * expect(binding('counter')).toEqual('0'); + * element('#ng-change-example1').click(); + * expect(binding('counter')).toEqual('1'); + * expect(binding('confirmed')).toEqual('true'); + * }); + * + * it('should not evaluate the expression if changing from model', function() { + * element('#ng-change-example2').click(); + * expect(binding('counter')).toEqual('0'); + * expect(binding('confirmed')).toEqual('true'); + * }); + * + *
        + */ +var ngChangeDirective = valueFn({ + require: 'ngModel', + link: function(scope, element, attr, ctrl) { + ctrl.$viewChangeListeners.push(function() { + scope.$eval(attr.ngChange); + }); + } +}); + + +var requiredDirective = function() { + return { + require: '?ngModel', + link: function(scope, elm, attr, ctrl) { + if (!ctrl) return; + attr.required = true; // force truthy in case we are on non input element + + var validator = function(value) { + if (attr.required && (isEmpty(value) || value === false)) { + ctrl.$setValidity('required', false); + return; + } else { + ctrl.$setValidity('required', true); + return value; + } + }; + + ctrl.$formatters.push(validator); + ctrl.$parsers.unshift(validator); + + attr.$observe('required', function() { + validator(ctrl.$viewValue); + }); + } + }; +}; + + +/** + * @ngdoc directive + * @name ng.directive:ngList + * + * @description + * Text input that converts between comma-separated string into an array of strings. + * + * @element input + * @param {string=} ngList optional delimiter that should be used to split the value. If + * specified in form `/something/` then the value will be converted into a regular expression. + * + * @example + + + +
        + List: + + Required! +
        + names = {{names}}
        + myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
        + myForm.namesInput.$error = {{myForm.namesInput.$error}}
        + myForm.$valid = {{myForm.$valid}}
        + myForm.$error.required = {{!!myForm.$error.required}}
        +
        +
        + + it('should initialize to model', function() { + expect(binding('names')).toEqual('["igor","misko","vojta"]'); + expect(binding('myForm.namesInput.$valid')).toEqual('true'); + expect(element('span.error').css('display')).toBe('none'); + }); + + it('should be invalid if empty', function() { + input('names').enter(''); + expect(binding('names')).toEqual('[]'); + expect(binding('myForm.namesInput.$valid')).toEqual('false'); + expect(element('span.error').css('display')).not().toBe('none'); + }); + +
        + */ +var ngListDirective = function() { + return { + require: 'ngModel', + link: function(scope, element, attr, ctrl) { + var match = /\/(.*)\//.exec(attr.ngList), + separator = match && new RegExp(match[1]) || attr.ngList || ','; + + var parse = function(viewValue) { + var list = []; + + if (viewValue) { + forEach(viewValue.split(separator), function(value) { + if (value) list.push(trim(value)); + }); + } + + return list; + }; + + ctrl.$parsers.push(parse); + ctrl.$formatters.push(function(value) { + if (isArray(value)) { + return value.join(', '); + } + + return undefined; + }); + } + }; +}; + + +var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; + +var ngValueDirective = function() { + return { + priority: 100, + compile: function(tpl, tplAttr) { + if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { + return function(scope, elm, attr) { + attr.$set('value', scope.$eval(attr.ngValue)); + }; + } else { + return function(scope, elm, attr) { + scope.$watch(attr.ngValue, function valueWatchAction(value) { + attr.$set('value', value); + }); + }; + } + } + }; +}; + +/** + * @ngdoc directive + * @name ng.directive:ngBind + * + * @description + * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element + * with the value of a given expression, and to update the text content when the value of that + * expression changes. + * + * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like + * `{{ expression }}` which is similar but less verbose. + * + * It is preferrable to use `ngBind` instead of `{{ expression }}` when a template is momentarily + * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an + * element attribute, it makes the bindings invisible to the user while the page is loading. + * + * An alternative solution to this problem would be using the + * {@link ng.directive:ngCloak ngCloak} directive. + * + * + * @element ANY + * @param {expression} ngBind {@link guide/expression Expression} to evaluate. + * + * @example + * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. + + + +
        + Enter name:
        + Hello ! +
        +
        + + it('should check ng-bind', function() { + expect(using('.doc-example-live').binding('name')).toBe('Whirled'); + using('.doc-example-live').input('name').enter('world'); + expect(using('.doc-example-live').binding('name')).toBe('world'); + }); + +
        + */ +var ngBindDirective = ngDirective(function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.ngBind); + scope.$watch(attr.ngBind, function ngBindWatchAction(value) { + element.text(value == undefined ? '' : value); + }); +}); + + +/** + * @ngdoc directive + * @name ng.directive:ngBindTemplate + * + * @description + * The `ngBindTemplate` directive specifies that the element + * text content should be replaced with the interpolation of the template + * in the `ngBindTemplate` attribute. + * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` + * expressions. This directive is needed since some HTML elements + * (such as TITLE and OPTION) cannot contain SPAN elements. + * + * @element ANY + * @param {string} ngBindTemplate template of form + * {{ expression }} to eval. + * + * @example + * Try it here: enter text in text box and watch the greeting change. + + + +
        + Salutation:
        + Name:
        +
        
        +       
        +
        + + it('should check ng-bind', function() { + expect(using('.doc-example-live').binding('salutation')). + toBe('Hello'); + expect(using('.doc-example-live').binding('name')). + toBe('World'); + using('.doc-example-live').input('salutation').enter('Greetings'); + using('.doc-example-live').input('name').enter('user'); + expect(using('.doc-example-live').binding('salutation')). + toBe('Greetings'); + expect(using('.doc-example-live').binding('name')). + toBe('user'); + }); + +
        + */ +var ngBindTemplateDirective = ['$interpolate', function($interpolate) { + return function(scope, element, attr) { + // TODO: move this to scenario runner + var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); + element.addClass('ng-binding').data('$binding', interpolateFn); + attr.$observe('ngBindTemplate', function(value) { + element.text(value); + }); + } +}]; + + +/** + * @ngdoc directive + * @name ng.directive:ngBindHtmlUnsafe + * + * @description + * Creates a binding that will innerHTML the result of evaluating the `expression` into the current + * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if + * {@link ngSanitize.directive:ngBindHtml ngBindHtml} directive is too + * restrictive and when you absolutely trust the source of the content you are binding to. + * + * See {@link ngSanitize.$sanitize $sanitize} docs for examples. + * + * @element ANY + * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate. + */ +var ngBindHtmlUnsafeDirective = [function() { + return function(scope, element, attr) { + element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe); + scope.$watch(attr.ngBindHtmlUnsafe, function ngBindHtmlUnsafeWatchAction(value) { + element.html(value || ''); + }); + }; +}]; + +function classDirective(name, selector) { + name = 'ngClass' + name; + return ngDirective(function(scope, element, attr) { + var oldVal = undefined; + + scope.$watch(attr[name], ngClassWatchAction, true); + + attr.$observe('class', function(value) { + var ngClass = scope.$eval(attr[name]); + ngClassWatchAction(ngClass, ngClass); + }); + + + if (name !== 'ngClass') { + scope.$watch('$index', function($index, old$index) { + var mod = $index & 1; + if (mod !== old$index & 1) { + if (mod === selector) { + addClass(scope.$eval(attr[name])); + } else { + removeClass(scope.$eval(attr[name])); + } + } + }); + } + + + function ngClassWatchAction(newVal) { + if (selector === true || scope.$index % 2 === selector) { + if (oldVal && !equals(newVal,oldVal)) { + removeClass(oldVal); + } + addClass(newVal); + } + oldVal = copy(newVal); + } + + + function removeClass(classVal) { + if (isObject(classVal) && !isArray(classVal)) { + classVal = map(classVal, function(v, k) { if (v) return k }); + } + element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal); + } + + + function addClass(classVal) { + if (isObject(classVal) && !isArray(classVal)) { + classVal = map(classVal, function(v, k) { if (v) return k }); + } + if (classVal) { + element.addClass(isArray(classVal) ? classVal.join(' ') : classVal); + } + } + }); +} + +/** + * @ngdoc directive + * @name ng.directive:ngClass + * + * @description + * The `ngClass` allows you to set CSS classes on HTML an element, dynamically, by databinding + * an expression that represents all classes to be added. + * + * The directive won't add duplicate classes if a particular class was already set. + * + * When the expression changes, the previously added classes are removed and only then the + * new classes are added. + * + * @element ANY + * @param {expression} ngClass {@link guide/expression Expression} to eval. The result + * of the evaluation can be a string representing space delimited class + * names, an array, or a map of class names to boolean values. In the case of a map, the + * names of the properties whose values are truthy will be added as css classes to the + * element. + * + * @example + + + + +
        + Sample Text +
        + + .my-class { + color: red; + } + + + it('should check ng-class', function() { + expect(element('.doc-example-live span').prop('className')).not(). + toMatch(/my-class/); + + using('.doc-example-live').element(':button:first').click(); + + expect(element('.doc-example-live span').prop('className')). + toMatch(/my-class/); + + using('.doc-example-live').element(':button:last').click(); + + expect(element('.doc-example-live span').prop('className')).not(). + toMatch(/my-class/); + }); + +
        + */ +var ngClassDirective = classDirective('', true); + +/** + * @ngdoc directive + * @name ng.directive:ngClassOdd + * + * @description + * The `ngClassOdd` and `ngClassEven` directives work exactly as + * {@link ng.directive:ngClass ngClass}, except it works in + * conjunction with `ngRepeat` and takes affect only on odd (even) rows. + * + * This directive can be applied only within a scope of an + * {@link ng.directive:ngRepeat ngRepeat}. + * + * @element ANY + * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result + * of the evaluation can be a string representing space delimited class names or an array. + * + * @example + + +
          +
        1. + + {{name}} + +
        2. +
        +
        + + .odd { + color: red; + } + .even { + color: blue; + } + + + it('should check ng-class-odd and ng-class-even', function() { + expect(element('.doc-example-live li:first span').prop('className')). + toMatch(/odd/); + expect(element('.doc-example-live li:last span').prop('className')). + toMatch(/even/); + }); + +
        + */ +var ngClassOddDirective = classDirective('Odd', 0); + +/** + * @ngdoc directive + * @name ng.directive:ngClassEven + * + * @description + * The `ngClassOdd` and `ngClassEven` directives work exactly as + * {@link ng.directive:ngClass ngClass}, except it works in + * conjunction with `ngRepeat` and takes affect only on odd (even) rows. + * + * This directive can be applied only within a scope of an + * {@link ng.directive:ngRepeat ngRepeat}. + * + * @element ANY + * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The + * result of the evaluation can be a string representing space delimited class names or an array. + * + * @example + + +
          +
        1. + + {{name}}       + +
        2. +
        +
        + + .odd { + color: red; + } + .even { + color: blue; + } + + + it('should check ng-class-odd and ng-class-even', function() { + expect(element('.doc-example-live li:first span').prop('className')). + toMatch(/odd/); + expect(element('.doc-example-live li:last span').prop('className')). + toMatch(/even/); + }); + +
        + */ +var ngClassEvenDirective = classDirective('Even', 1); + +/** + * @ngdoc directive + * @name ng.directive:ngCloak + * + * @description + * The `ngCloak` directive is used to prevent the Angular html template from being briefly + * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this + * directive to avoid the undesirable flicker effect caused by the html template display. + * + * The directive can be applied to the `` element, but typically a fine-grained application is + * prefered in order to benefit from progressive rendering of the browser view. + * + * `ngCloak` works in cooperation with a css rule that is embedded within `angular.js` and + * `angular.min.js` files. Following is the css rule: + * + *
        + * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
        + *   display: none !important;
        + * }
        + * 
        + * + * When this css rule is loaded by the browser, all html elements (including their children) that + * are tagged with the `ng-cloak` directive are hidden. When Angular comes across this directive + * during the compilation of the template it deletes the `ngCloak` element attribute, which + * makes the compiled element visible. + * + * For the best result, `angular.js` script must be loaded in the head section of the html file; + * alternatively, the css rule (above) must be included in the external stylesheet of the + * application. + * + * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they + * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css + * class `ngCloak` in addition to `ngCloak` directive as shown in the example below. + * + * @element ANY + * + * @example + + +
        {{ 'hello' }}
        +
        {{ 'hello IE7' }}
        +
        + + it('should remove the template directive and css class', function() { + expect(element('.doc-example-live #template1').attr('ng-cloak')). + not().toBeDefined(); + expect(element('.doc-example-live #template2').attr('ng-cloak')). + not().toBeDefined(); + }); + +
        + * + */ +var ngCloakDirective = ngDirective({ + compile: function(element, attr) { + attr.$set('ngCloak', undefined); + element.removeClass('ng-cloak'); + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngController + * + * @description + * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular + * supports the principles behind the Model-View-Controller design pattern. + * + * MVC components in angular: + * + * * Model — The Model is data in scope properties; scopes are attached to the DOM. + * * View — The template (HTML with data bindings) is rendered into the View. + * * Controller — The `ngController` directive specifies a Controller class; the class has + * methods that typically express the business logic behind the application. + * + * Note that an alternative way to define controllers is via the {@link ng.$route $route} service. + * + * @element ANY + * @scope + * @param {expression} ngController Name of a globally accessible constructor function or an + * {@link guide/expression expression} that on the current scope evaluates to a + * constructor function. + * + * @example + * Here is a simple form for editing user contact information. Adding, removing, clearing, and + * greeting are methods declared on the $scope by the controller (see source tab). These methods can + * easily be called from the angular markup. Notice that any changes to the data are automatically + * reflected in the View without the need for a manual update. + + + +
        + Name: + [ greet ]
        + Contact: +
          +
        • + + + [ clear + | X ] +
        • +
        • [ add ]
        • +
        +
        +
        + + it('should check controller', function() { + expect(element('.doc-example-live div>:input').val()).toBe('John Smith'); + expect(element('.doc-example-live li:nth-child(1) input').val()) + .toBe('408 555 1212'); + expect(element('.doc-example-live li:nth-child(2) input').val()) + .toBe('john.smith@example.org'); + + element('.doc-example-live li:first a:contains("clear")').click(); + expect(element('.doc-example-live li:first input').val()).toBe(''); + + element('.doc-example-live li:last a:contains("add")').click(); + expect(element('.doc-example-live li:nth-child(3) input').val()) + .toBe('yourname@example.org'); + }); + +
        + */ +var ngControllerDirective = [function() { + return { + scope: true, + controller: '@' + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngCsp + * @priority 1000 + * + * @element html + * @description + * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. + * + * This is necessary when developing things like Google Chrome Extensions. + * + * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). + * For us to be compatible, we just need to implement the "getterFn" in $parse without violating + * any of these restrictions. + * + * AngularJS uses `Function(string)` generated functions as a speed optimization. By applying `ngCsp` + * it is be possible to opt into the CSP compatible mode. When this mode is on AngularJS will + * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will + * be raised. + * + * In order to use this feature put `ngCsp` directive on the root element of the application. + * + * @example + * This example shows how to apply the `ngCsp` directive to the `html` tag. +
        +     
        +     
        +     ...
        +     ...
        +     
        +   
        + */ + +var ngCspDirective = ['$sniffer', function($sniffer) { + return { + priority: 1000, + compile: function() { + $sniffer.csp = true; + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngClick + * + * @description + * The ngClick allows you to specify custom behavior when + * element is clicked. + * + * @element ANY + * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon + * click. (Event object is available as `$event`) + * + * @example + + + + count: {{count}} + + + it('should check ng-click', function() { + expect(binding('count')).toBe('0'); + element('.doc-example-live :button').click(); + expect(binding('count')).toBe('1'); + }); + + + */ +/* + * A directive that allows creation of custom onclick handlers that are defined as angular + * expressions and are compiled and executed within the current scope. + * + * Events that are handled via these handler are always configured not to propagate further. + */ +var ngEventDirectives = {}; +forEach( + 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave submit'.split(' '), + function(name) { + var directiveName = directiveNormalize('ng-' + name); + ngEventDirectives[directiveName] = ['$parse', function($parse) { + return function(scope, element, attr) { + var fn = $parse(attr[directiveName]); + element.bind(lowercase(name), function(event) { + scope.$apply(function() { + fn(scope, {$event:event}); + }); + }); + }; + }]; + } +); + +/** + * @ngdoc directive + * @name ng.directive:ngDblclick + * + * @description + * The `ngDblclick` directive allows you to specify custom behavior on dblclick event. + * + * @element ANY + * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon + * dblclick. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMousedown + * + * @description + * The ngMousedown directive allows you to specify custom behavior on mousedown event. + * + * @element ANY + * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon + * mousedown. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMouseup + * + * @description + * Specify custom behavior on mouseup event. + * + * @element ANY + * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon + * mouseup. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + +/** + * @ngdoc directive + * @name ng.directive:ngMouseover + * + * @description + * Specify custom behavior on mouseover event. + * + * @element ANY + * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon + * mouseover. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMouseenter + * + * @description + * Specify custom behavior on mouseenter event. + * + * @element ANY + * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon + * mouseenter. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMouseleave + * + * @description + * Specify custom behavior on mouseleave event. + * + * @element ANY + * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon + * mouseleave. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngMousemove + * + * @description + * Specify custom behavior on mousemove event. + * + * @element ANY + * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon + * mousemove. (Event object is available as `$event`) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngKeydown + * + * @description + * Specify custom behavior on keydown event. + * + * @element ANY + * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon + * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngKeyup + * + * @description + * Specify custom behavior on keyup event. + * + * @element ANY + * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon + * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngKeypress + * + * @description + * Specify custom behavior on keypress event. + * + * @element ANY + * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon + * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) + * + * @example + * See {@link ng.directive:ngClick ngClick} + */ + + +/** + * @ngdoc directive + * @name ng.directive:ngSubmit + * + * @description + * Enables binding angular expressions to onsubmit events. + * + * Additionally it prevents the default action (which for form means sending the request to the + * server and reloading the current page) **but only if the form does not contain an `action` + * attribute**. + * + * @element form + * @param {expression} ngSubmit {@link guide/expression Expression} to eval. (Event object is available as `$event`) + * + * @example + + + +
        + Enter text and hit enter: + + +
        list={{list}}
        +
        +
        + + it('should check ng-submit', function() { + expect(binding('list')).toBe('[]'); + element('.doc-example-live #submit').click(); + expect(binding('list')).toBe('["hello"]'); + expect(input('text').val()).toBe(''); + }); + it('should ignore empty strings', function() { + expect(binding('list')).toBe('[]'); + element('.doc-example-live #submit').click(); + element('.doc-example-live #submit').click(); + expect(binding('list')).toBe('["hello"]'); + }); + +
        + */ + +/** + * @ngdoc directive + * @name ng.directive:ngInclude + * @restrict ECA + * + * @description + * Fetches, compiles and includes an external HTML fragment. + * + * Keep in mind that Same Origin Policy applies to included resources + * (e.g. ngInclude won't work for cross-domain requests on all browsers and for + * file:// access on some browsers). + * + * @scope + * + * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, + * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`. + * @param {string=} onload Expression to evaluate when a new partial is loaded. + * + * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll + * $anchorScroll} to scroll the viewport after the content is loaded. + * + * - If the attribute is not set, disable scrolling. + * - If the attribute is set without value, enable scrolling. + * - Otherwise enable scrolling only if the expression evaluates to truthy value. + * + * @example + + +
        + + url of the template: {{template.url}} +
        +
        +
        +
        + + function Ctrl($scope) { + $scope.templates = + [ { name: 'template1.html', url: 'template1.html'} + , { name: 'template2.html', url: 'template2.html'} ]; + $scope.template = $scope.templates[0]; + } + + + Content of template1.html + + + Content of template2.html + + + it('should load template1.html', function() { + expect(element('.doc-example-live [ng-include]').text()). + toMatch(/Content of template1.html/); + }); + it('should load template2.html', function() { + select('template').option('1'); + expect(element('.doc-example-live [ng-include]').text()). + toMatch(/Content of template2.html/); + }); + it('should change to blank', function() { + select('template').option(''); + expect(element('.doc-example-live [ng-include]').text()).toEqual(''); + }); + +
        + */ + + +/** + * @ngdoc event + * @name ng.directive:ngInclude#$includeContentLoaded + * @eventOf ng.directive:ngInclude + * @eventType emit on the current ngInclude scope + * @description + * Emitted every time the ngInclude content is reloaded. + */ +var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', + function($http, $templateCache, $anchorScroll, $compile) { + return { + restrict: 'ECA', + terminal: true, + compile: function(element, attr) { + var srcExp = attr.ngInclude || attr.src, + onloadExp = attr.onload || '', + autoScrollExp = attr.autoscroll; + + return function(scope, element) { + var changeCounter = 0, + childScope; + + var clearContent = function() { + if (childScope) { + childScope.$destroy(); + childScope = null; + } + + element.html(''); + }; + + scope.$watch(srcExp, function ngIncludeWatchAction(src) { + var thisChangeId = ++changeCounter; + + if (src) { + $http.get(src, {cache: $templateCache}).success(function(response) { + if (thisChangeId !== changeCounter) return; + + if (childScope) childScope.$destroy(); + childScope = scope.$new(); + + element.html(response); + $compile(element.contents())(childScope); + + if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } + + childScope.$emit('$includeContentLoaded'); + scope.$eval(onloadExp); + }).error(function() { + if (thisChangeId === changeCounter) clearContent(); + }); + } else clearContent(); + }); + }; + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngInit + * + * @description + * The `ngInit` directive specifies initialization tasks to be executed + * before the template enters execution mode during bootstrap. + * + * @element ANY + * @param {expression} ngInit {@link guide/expression Expression} to eval. + * + * @example + + +
        + {{greeting}} {{person}}! +
        +
        + + it('should check greeting', function() { + expect(binding('greeting')).toBe('Hello'); + expect(binding('person')).toBe('World'); + }); + +
        + */ +var ngInitDirective = ngDirective({ + compile: function() { + return { + pre: function(scope, element, attrs) { + scope.$eval(attrs.ngInit); + } + } + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngNonBindable + * @priority 1000 + * + * @description + * Sometimes it is necessary to write code which looks like bindings but which should be left alone + * by angular. Use `ngNonBindable` to make angular ignore a chunk of HTML. + * + * @element ANY + * + * @example + * In this example there are two location where a simple binding (`{{}}`) is present, but the one + * wrapped in `ngNonBindable` is left alone. + * + * @example + + +
        Normal: {{1 + 2}}
        +
        Ignored: {{1 + 2}}
        +
        + + it('should check ng-non-bindable', function() { + expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); + expect(using('.doc-example-live').element('div:last').text()). + toMatch(/1 \+ 2/); + }); + +
        + */ +var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); + +/** + * @ngdoc directive + * @name ng.directive:ngPluralize + * @restrict EA + * + * @description + * # Overview + * `ngPluralize` is a directive that displays messages according to en-US localization rules. + * These rules are bundled with angular.js, but can be overridden + * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive + * by specifying the mappings between + * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * plural categories} and the strings to be displayed. + * + * # Plural categories and explicit number rules + * There are two + * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html + * plural categories} in Angular's default en-US locale: "one" and "other". + * + * While a pural category may match many numbers (for example, in en-US locale, "other" can match + * any number that is not 1), an explicit number rule can only match one number. For example, the + * explicit number rule for "3" matches the number 3. There are examples of plural categories + * and explicit number rules throughout the rest of this documentation. + * + * # Configuring ngPluralize + * You configure ngPluralize by providing 2 attributes: `count` and `when`. + * You can also provide an optional attribute, `offset`. + * + * The value of the `count` attribute can be either a string or an {@link guide/expression + * Angular expression}; these are evaluated on the current scope for its bound value. + * + * The `when` attribute specifies the mappings between plural categories and the actual + * string to be displayed. The value of the attribute should be a JSON object. + * + * The following example shows how to configure ngPluralize: + * + *
        + * 
        + * 
        + *
        + * + * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not + * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" + * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for + * other numbers, for example 12, so that instead of showing "12 people are viewing", you can + * show "a dozen people are viewing". + * + * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted + * into pluralized strings. In the previous example, Angular will replace `{}` with + * `{{personCount}}`. The closed braces `{}` is a placeholder + * for {{numberExpression}}. + * + * # Configuring ngPluralize with offset + * The `offset` attribute allows further customization of pluralized text, which can result in + * a better user experience. For example, instead of the message "4 people are viewing this document", + * you might display "John, Kate and 2 others are viewing this document". + * The offset attribute allows you to offset a number by any desired value. + * Let's take a look at an example: + * + *
        + * 
        + * 
        + * 
        + * + * Notice that we are still using two plural categories(one, other), but we added + * three explicit number rules 0, 1 and 2. + * When one person, perhaps John, views the document, "John is viewing" will be shown. + * When three people view the document, no explicit number rule is found, so + * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. + * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" + * is shown. + * + * Note that when you specify offsets, you must provide explicit number rules for + * numbers from 0 up to and including the offset. If you use an offset of 3, for example, + * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for + * plural categories "one" and "other". + * + * @param {string|expression} count The variable to be bounded to. + * @param {string} when The mapping between plural category to its correspoding strings. + * @param {number=} offset Offset to deduct from the total number. + * + * @example + + + +
        + Person 1:
        + Person 2:
        + Number of People:
        + + + Without Offset: + +
        + + + With Offset(2): + + +
        +
        + + it('should show correct pluralized string', function() { + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('1 person is viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor is viewing.'); + + using('.doc-example-live').input('personCount').enter('0'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('Nobody is viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Nobody is viewing.'); + + using('.doc-example-live').input('personCount').enter('2'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('2 people are viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor and Misko are viewing.'); + + using('.doc-example-live').input('personCount').enter('3'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('3 people are viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor, Misko and one other person are viewing.'); + + using('.doc-example-live').input('personCount').enter('4'); + expect(element('.doc-example-live ng-pluralize:first').text()). + toBe('4 people are viewing.'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor, Misko and 2 other people are viewing.'); + }); + + it('should show data-binded names', function() { + using('.doc-example-live').input('personCount').enter('4'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Igor, Misko and 2 other people are viewing.'); + + using('.doc-example-live').input('person1').enter('Di'); + using('.doc-example-live').input('person2').enter('Vojta'); + expect(element('.doc-example-live ng-pluralize:last').text()). + toBe('Di, Vojta and 2 other people are viewing.'); + }); + +
        + */ +var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { + var BRACE = /{}/g; + return { + restrict: 'EA', + link: function(scope, element, attr) { + var numberExp = attr.count, + whenExp = element.attr(attr.$attr.when), // this is because we have {{}} in attrs + offset = attr.offset || 0, + whens = scope.$eval(whenExp), + whensExpFns = {}, + startSymbol = $interpolate.startSymbol(), + endSymbol = $interpolate.endSymbol(); + + forEach(whens, function(expression, key) { + whensExpFns[key] = + $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + + offset + endSymbol)); + }); + + scope.$watch(function ngPluralizeWatch() { + var value = parseFloat(scope.$eval(numberExp)); + + if (!isNaN(value)) { + //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, + //check it against pluralization rules in $locale service + if (!(value in whens)) value = $locale.pluralCat(value - offset); + return whensExpFns[value](scope, element, true); + } else { + return ''; + } + }, function ngPluralizeWatchAction(newVal) { + element.text(newVal); + }); + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:ngRepeat + * + * @description + * The `ngRepeat` directive instantiates a template once per item from a collection. Each template + * instance gets its own scope, where the given loop variable is set to the current collection item, + * and `$index` is set to the item index or key. + * + * Special properties are exposed on the local scope of each template instance, including: + * + * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1) + * * `$first` – `{boolean}` – true if the repeated element is first in the iterator. + * * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator. + * * `$last` – `{boolean}` – true if the repeated element is last in the iterator. + * + * + * @element ANY + * @scope + * @priority 1000 + * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. Two + * formats are currently supported: + * + * * `variable in expression` – where variable is the user defined loop variable and `expression` + * is a scope expression giving the collection to enumerate. + * + * For example: `track in cd.tracks`. + * + * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, + * and `expression` is the scope expression giving the collection to enumerate. + * + * For example: `(name, age) in {'adam':10, 'amalie':12}`. + * + * @example + * This example initializes the scope to a list of names and + * then uses `ngRepeat` to display every person: + + +
        + I have {{friends.length}} friends. They are: +
          +
        • + [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. +
        • +
        +
        +
        + + it('should check ng-repeat', function() { + var r = using('.doc-example-live').repeater('ul li'); + expect(r.count()).toBe(2); + expect(r.row(0)).toEqual(["1","John","25"]); + expect(r.row(1)).toEqual(["2","Mary","28"]); + }); + +
        + */ +var ngRepeatDirective = ngDirective({ + transclude: 'element', + priority: 1000, + terminal: true, + compile: function(element, attr, linker) { + return function(scope, iterStartElement, attr){ + var expression = attr.ngRepeat; + var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), + lhs, rhs, valueIdent, keyIdent; + if (! match) { + throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" + + expression + "'."); + } + lhs = match[1]; + rhs = match[2]; + match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); + if (!match) { + throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" + + lhs + "'."); + } + valueIdent = match[3] || match[1]; + keyIdent = match[2]; + + // Store a list of elements from previous run. This is a hash where key is the item from the + // iterator, and the value is an array of objects with following properties. + // - scope: bound scope + // - element: previous element. + // - index: position + // We need an array of these objects since the same object can be returned from the iterator. + // We expect this to be a rare case. + var lastOrder = new HashQueueMap(); + + scope.$watch(function ngRepeatWatch(scope){ + var index, length, + collection = scope.$eval(rhs), + cursor = iterStartElement, // current position of the node + // Same as lastOrder but it has the current state. It will become the + // lastOrder on the next iteration. + nextOrder = new HashQueueMap(), + arrayBound, + childScope, + key, value, // key/value of iteration + array, + last; // last object information {scope, element, index} + + + + if (!isArray(collection)) { + // if object, extract keys, sort them and use to determine order of iteration over obj props + array = []; + for(key in collection) { + if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { + array.push(key); + } + } + array.sort(); + } else { + array = collection || []; + } + + arrayBound = array.length-1; + + // we are not using forEach for perf reasons (trying to avoid #call) + for (index = 0, length = array.length; index < length; index++) { + key = (collection === array) ? index : array[index]; + value = collection[key]; + + last = lastOrder.shift(value); + + if (last) { + // if we have already seen this object, then we need to reuse the + // associated scope/element + childScope = last.scope; + nextOrder.push(value, last); + + if (index === last.index) { + // do nothing + cursor = last.element; + } else { + // existing item which got moved + last.index = index; + // This may be a noop, if the element is next, but I don't know of a good way to + // figure this out, since it would require extra DOM access, so let's just hope that + // the browsers realizes that it is noop, and treats it as such. + cursor.after(last.element); + cursor = last.element; + } + } else { + // new item which we don't know about + childScope = scope.$new(); + } + + childScope[valueIdent] = value; + if (keyIdent) childScope[keyIdent] = key; + childScope.$index = index; + + childScope.$first = (index === 0); + childScope.$last = (index === arrayBound); + childScope.$middle = !(childScope.$first || childScope.$last); + + if (!last) { + linker(childScope, function(clone){ + cursor.after(clone); + last = { + scope: childScope, + element: (cursor = clone), + index: index + }; + nextOrder.push(value, last); + }); + } + } + + //shrink children + for (key in lastOrder) { + if (lastOrder.hasOwnProperty(key)) { + array = lastOrder[key]; + while(array.length) { + value = array.pop(); + value.element.remove(); + value.scope.$destroy(); + } + } + } + + lastOrder = nextOrder; + }); + }; + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngShow + * + * @description + * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML) + * conditionally. + * + * @element ANY + * @param {expression} ngShow If the {@link guide/expression expression} is truthy + * then the element is shown or hidden respectively. + * + * @example + + + Click me:
        + Show: I show up when your checkbox is checked.
        + Hide: I hide when your checkbox is checked. +
        + + it('should check ng-show / ng-hide', function() { + expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); + expect(element('.doc-example-live span:last:visible').count()).toEqual(1); + + input('checked').check(); + + expect(element('.doc-example-live span:first:visible').count()).toEqual(1); + expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); + }); + +
        + */ +//TODO(misko): refactor to remove element from the DOM +var ngShowDirective = ngDirective(function(scope, element, attr){ + scope.$watch(attr.ngShow, function ngShowWatchAction(value){ + element.css('display', toBoolean(value) ? '' : 'none'); + }); +}); + + +/** + * @ngdoc directive + * @name ng.directive:ngHide + * + * @description + * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML) + * conditionally. + * + * @element ANY + * @param {expression} ngHide If the {@link guide/expression expression} is truthy then + * the element is shown or hidden respectively. + * + * @example + + + Click me:
        + Show: I show up when you checkbox is checked?
        + Hide: I hide when you checkbox is checked? +
        + + it('should check ng-show / ng-hide', function() { + expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); + expect(element('.doc-example-live span:last:visible').count()).toEqual(1); + + input('checked').check(); + + expect(element('.doc-example-live span:first:visible').count()).toEqual(1); + expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); + }); + +
        + */ +//TODO(misko): refactor to remove element from the DOM +var ngHideDirective = ngDirective(function(scope, element, attr){ + scope.$watch(attr.ngHide, function ngHideWatchAction(value){ + element.css('display', toBoolean(value) ? 'none' : ''); + }); +}); + +/** + * @ngdoc directive + * @name ng.directive:ngStyle + * + * @description + * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. + * + * @element ANY + * @param {expression} ngStyle {@link guide/expression Expression} which evals to an + * object whose keys are CSS style names and values are corresponding values for those CSS + * keys. + * + * @example + + + + +
        + Sample Text +
        myStyle={{myStyle}}
        +
        + + span { + color: black; + } + + + it('should check ng-style', function() { + expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); + element('.doc-example-live :button[value=set]').click(); + expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)'); + element('.doc-example-live :button[value=clear]').click(); + expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); + }); + +
        + */ +var ngStyleDirective = ngDirective(function(scope, element, attr) { + scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { + if (oldStyles && (newStyles !== oldStyles)) { + forEach(oldStyles, function(val, style) { element.css(style, '');}); + } + if (newStyles) element.css(newStyles); + }, true); +}); + +/** + * @ngdoc directive + * @name ng.directive:ngSwitch + * @restrict EA + * + * @description + * Conditionally change the DOM structure. + * + * @usage + * + * ... + * ... + * ... + * ... + * + * + * @scope + * @param {*} ngSwitch|on expression to match against ng-switch-when. + * @paramDescription + * On child elments add: + * + * * `ngSwitchWhen`: the case statement to match against. If match then this + * case will be displayed. + * * `ngSwitchDefault`: the default case when no other casses match. + * + * @example + + + +
        + + selection={{selection}} +
        +
        +
        Settings Div
        + Home Span + default +
        +
        +
        + + it('should start in settings', function() { + expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/); + }); + it('should change to home', function() { + select('selection').option('home'); + expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/); + }); + it('should select deafault', function() { + select('selection').option('other'); + expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/); + }); + +
        + */ +var NG_SWITCH = 'ng-switch'; +var ngSwitchDirective = valueFn({ + restrict: 'EA', + require: 'ngSwitch', + // asks for $scope to fool the BC controller module + controller: ['$scope', function ngSwitchController() { + this.cases = {}; + }], + link: function(scope, element, attr, ctrl) { + var watchExpr = attr.ngSwitch || attr.on, + selectedTransclude, + selectedElement, + selectedScope; + + scope.$watch(watchExpr, function ngSwitchWatchAction(value) { + if (selectedElement) { + selectedScope.$destroy(); + selectedElement.remove(); + selectedElement = selectedScope = null; + } + if ((selectedTransclude = ctrl.cases['!' + value] || ctrl.cases['?'])) { + scope.$eval(attr.change); + selectedScope = scope.$new(); + selectedTransclude(selectedScope, function(caseElement) { + selectedElement = caseElement; + element.append(caseElement); + }); + } + }); + } +}); + +var ngSwitchWhenDirective = ngDirective({ + transclude: 'element', + priority: 500, + require: '^ngSwitch', + compile: function(element, attrs, transclude) { + return function(scope, element, attr, ctrl) { + ctrl.cases['!' + attrs.ngSwitchWhen] = transclude; + }; + } +}); + +var ngSwitchDefaultDirective = ngDirective({ + transclude: 'element', + priority: 500, + require: '^ngSwitch', + compile: function(element, attrs, transclude) { + return function(scope, element, attr, ctrl) { + ctrl.cases['?'] = transclude; + }; + } +}); + +/** + * @ngdoc directive + * @name ng.directive:ngTransclude + * + * @description + * Insert the transcluded DOM here. + * + * @element ANY + * + * @example + + + +
        +
        +
        + {{text}} +
        +
        + + it('should have transcluded', function() { + input('title').enter('TITLE'); + input('text').enter('TEXT'); + expect(binding('title')).toEqual('TITLE'); + expect(binding('text')).toEqual('TEXT'); + }); + +
        + * + */ +var ngTranscludeDirective = ngDirective({ + controller: ['$transclude', '$element', function($transclude, $element) { + $transclude(function(clone) { + $element.append(clone); + }); + }] +}); + +/** + * @ngdoc directive + * @name ng.directive:ngView + * @restrict ECA + * + * @description + * # Overview + * `ngView` is a directive that complements the {@link ng.$route $route} service by + * including the rendered template of the current route into the main layout (`index.html`) file. + * Every time the current route changes, the included view changes with it according to the + * configuration of the `$route` service. + * + * @scope + * @example + + +
        + Choose: + Moby | + Moby: Ch1 | + Gatsby | + Gatsby: Ch4 | + Scarlet Letter
        + +
        +
        + +
        $location.path() = {{$location.path()}}
        +
        $route.current.templateUrl = {{$route.current.templateUrl}}
        +
        $route.current.params = {{$route.current.params}}
        +
        $route.current.scope.name = {{$route.current.scope.name}}
        +
        $routeParams = {{$routeParams}}
        +
        +
        + + + controller: {{name}}
        + Book Id: {{params.bookId}}
        +
        + + + controller: {{name}}
        + Book Id: {{params.bookId}}
        + Chapter Id: {{params.chapterId}} +
        + + + angular.module('ngView', [], function($routeProvider, $locationProvider) { + $routeProvider.when('/Book/:bookId', { + templateUrl: 'book.html', + controller: BookCntl + }); + $routeProvider.when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: ChapterCntl + }); + + // configure html5 to get links working on jsfiddle + $locationProvider.html5Mode(true); + }); + + function MainCntl($scope, $route, $routeParams, $location) { + $scope.$route = $route; + $scope.$location = $location; + $scope.$routeParams = $routeParams; + } + + function BookCntl($scope, $routeParams) { + $scope.name = "BookCntl"; + $scope.params = $routeParams; + } + + function ChapterCntl($scope, $routeParams) { + $scope.name = "ChapterCntl"; + $scope.params = $routeParams; + } + + + + it('should load and compile correct template', function() { + element('a:contains("Moby: Ch1")').click(); + var content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: ChapterCntl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element('a:contains("Scarlet")').click(); + content = element('.doc-example-live [ng-view]').text(); + expect(content).toMatch(/controller\: BookCntl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + +
        + */ + + +/** + * @ngdoc event + * @name ng.directive:ngView#$viewContentLoaded + * @eventOf ng.directive:ngView + * @eventType emit on the current ngView scope + * @description + * Emitted every time the ngView content is reloaded. + */ +var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile', + '$controller', + function($http, $templateCache, $route, $anchorScroll, $compile, + $controller) { + return { + restrict: 'ECA', + terminal: true, + link: function(scope, element, attr) { + var lastScope, + onloadExp = attr.onload || ''; + + scope.$on('$routeChangeSuccess', update); + update(); + + + function destroyLastScope() { + if (lastScope) { + lastScope.$destroy(); + lastScope = null; + } + } + + function clearContent() { + element.html(''); + destroyLastScope(); + } + + function update() { + var locals = $route.current && $route.current.locals, + template = locals && locals.$template; + + if (template) { + element.html(template); + destroyLastScope(); + + var link = $compile(element.contents()), + current = $route.current, + controller; + + lastScope = current.scope = scope.$new(); + if (current.controller) { + locals.$scope = lastScope; + controller = $controller(current.controller, locals); + element.children().data('$ngControllerController', controller); + } + + link(lastScope); + lastScope.$emit('$viewContentLoaded'); + lastScope.$eval(onloadExp); + + // $anchorScroll might listen on event... + $anchorScroll(); + } else { + clearContent(); + } + } + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:script + * + * @description + * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the + * template can be used by `ngInclude`, `ngView` or directive templates. + * + * @restrict E + * @param {'text/ng-template'} type must be set to `'text/ng-template'` + * + * @example + + + + + Load inlined template +
        +
        + + it('should load template defined inside script tag', function() { + element('#tpl-link').click(); + expect(element('#tpl-content').text()).toMatch(/Content of the template/); + }); + +
        + */ +var scriptDirective = ['$templateCache', function($templateCache) { + return { + restrict: 'E', + terminal: true, + compile: function(element, attr) { + if (attr.type == 'text/ng-template') { + var templateUrl = attr.id, + // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent + text = element[0].text; + + $templateCache.put(templateUrl, text); + } + } + }; +}]; + +/** + * @ngdoc directive + * @name ng.directive:select + * @restrict E + * + * @description + * HTML `SELECT` element with angular data-binding. + * + * # `ngOptions` + * + * Optionally `ngOptions` attribute can be used to dynamically generate a list of `` + * DOM element. + * + * @example + + + +
        +
          +
        • + Name: + [X] +
        • +
        • + [add] +
        • +
        +
        + Color (null not allowed): +
        + + Color (null allowed): + + +
        + + Color grouped by shade: +
        + + + Select bogus.
        +
        + Currently selected: {{ {selected_color:color} }} +
        +
        +
        +
        + + it('should check ng-options', function() { + expect(binding('{selected_color:color}')).toMatch('red'); + select('color').option('0'); + expect(binding('{selected_color:color}')).toMatch('black'); + using('.nullable').select('color').option(''); + expect(binding('{selected_color:color}')).toMatch('null'); + }); + +
        + */ + +var ngOptionsDirective = valueFn({ terminal: true }); +var selectDirective = ['$compile', '$parse', function($compile, $parse) { + //0000111110000000000022220000000000000000000000333300000000000000444444444444444440000000005555555555555555500000006666666666666666600000000000000077770 + var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/, + nullModelCtrl = {$setViewValue: noop}; + + return { + restrict: 'E', + require: ['select', '?ngModel'], + controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { + var self = this, + optionsMap = {}, + ngModelCtrl = nullModelCtrl, + nullOption, + unknownOption; + + + self.databound = $attrs.ngModel; + + + self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { + ngModelCtrl = ngModelCtrl_; + nullOption = nullOption_; + unknownOption = unknownOption_; + } + + + self.addOption = function(value) { + optionsMap[value] = true; + + if (ngModelCtrl.$viewValue == value) { + $element.val(value); + if (unknownOption.parent()) unknownOption.remove(); + } + }; + + + self.removeOption = function(value) { + if (this.hasOption(value)) { + delete optionsMap[value]; + if (ngModelCtrl.$viewValue == value) { + this.renderUnknownOption(value); + } + } + }; + + + self.renderUnknownOption = function(val) { + var unknownVal = '? ' + hashKey(val) + ' ?'; + unknownOption.val(unknownVal); + $element.prepend(unknownOption); + $element.val(unknownVal); + unknownOption.prop('selected', true); // needed for IE + } + + + self.hasOption = function(value) { + return optionsMap.hasOwnProperty(value); + } + + $scope.$on('$destroy', function() { + // disable unknown option so that we don't do work when the whole select is being destroyed + self.renderUnknownOption = noop; + }); + }], + + link: function(scope, element, attr, ctrls) { + // if ngModel is not defined, we don't need to do anything + if (!ctrls[1]) return; + + var selectCtrl = ctrls[0], + ngModelCtrl = ctrls[1], + multiple = attr.multiple, + optionsExp = attr.ngOptions, + nullOption = false, // if false, user will not be able to select it (used by ngOptions) + emptyOption, + // we can't just jqLite('