From 8d81147d1f757e70393e08947c0be1b73ebfb3ae Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Tue, 11 Mar 2014 15:10:30 +0000 Subject: cobrand file with geocoder etc config also, make readonly, disable photos, questionnaires, never hide old pins Fixes #701 --- perllib/FixMyStreet/Cobrand/ZeroTB.pm | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 perllib/FixMyStreet/Cobrand/ZeroTB.pm diff --git a/perllib/FixMyStreet/Cobrand/ZeroTB.pm b/perllib/FixMyStreet/Cobrand/ZeroTB.pm new file mode 100644 index 000000000..bd25b6076 --- /dev/null +++ b/perllib/FixMyStreet/Cobrand/ZeroTB.pm @@ -0,0 +1,31 @@ +package FixMyStreet::Cobrand::ZeroTB; +use base 'FixMyStreet::Cobrand::Default'; + +use strict; +use warnings; + +sub site_title { return 'ZeroTB'; } + +sub country { + return 'IN'; +} + +sub languages { [ 'en-gb,English,en_GB' ] } +sub language_override { 'en-gb' } + +sub disambiguate_location { + return { + country => 'in', + town => 'Delhi', + bounds => [ 28.404625000000024, 76.838845800000072, 28.884380600000028, 77.347877500000067 ], + }; +} + +sub only_authed_can_create { return 1; } +sub allow_photo_display { return 0; } +sub allow_photo_upload{ return 0; } +sub send_questionnaires { return 0; } +sub on_map_default_max_pin_age { return 0; } + +1; + -- cgit v1.2.3 From 47b110116b3e98ecc76b7d3a8ba046e85d5d329a Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Tue, 11 Mar 2014 16:17:13 +0000 Subject: remove unwanted items from navigation basically everything bar report and help --- templates/web/zerotb/footer.html | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 templates/web/zerotb/footer.html diff --git a/templates/web/zerotb/footer.html b/templates/web/zerotb/footer.html new file mode 100644 index 000000000..8b93e470e --- /dev/null +++ b/templates/web/zerotb/footer.html @@ -0,0 +1,27 @@ + + + + + + + + + + + -- cgit v1.2.3 From cbc79c13e9b4d1418f0a717cfff4d978a09f46e8 Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Tue, 11 Mar 2014 16:40:32 +0000 Subject: add template for updates with only required elements remove login, name made public, photo upload, alert sign up --- templates/web/zerotb/report/update-form.html | 66 ++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 templates/web/zerotb/report/update-form.html diff --git a/templates/web/zerotb/report/update-form.html b/templates/web/zerotb/report/update-form.html new file mode 100644 index 000000000..db58cb9d1 --- /dev/null +++ b/templates/web/zerotb/report/update-form.html @@ -0,0 +1,66 @@ +
+

[% loc( 'Provide an update') %]

+ + [% IF c.cobrand.moniker != 'emptyhomes' AND c.cobrand.moniker != 'stevenage' %] +
+ [% INCLUDE 'report/updates-sidebar-notes.html' %] +
+ [% END %] + + [% INCLUDE 'errors.html' %] + +
+
+ + + + + [% IF field_errors.update %] +
[% field_errors.update %]
+ [% END %] + + + [% IF c.user_exists %] + + [% INCLUDE name %] + + + + + [% ELSE %] + + + [% IF field_errors.email %] +

[% field_errors.email %]

+ [% END %] + + +
+
+ [% INCLUDE name %] + +
+

[% loc('We never show your email') %]

+

[% loc('Providing a password is optional, but doing so will allow you to more easily report problems, leave updates and manage your reports.') %]

+
+ +
+ +
+
+
+ + [% END %] +
+
+
+ +[% BLOCK name %] + [% INCLUDE 'report/new/extra_name.html' %] + + [% IF field_errors.name %] +

[% field_errors.name %]

+ [% END %] + + +[% END %] -- cgit v1.2.3 From 1956601629e2f7c9da4cbce9eb0cbba9242e2f3e Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Tue, 11 Mar 2014 17:13:17 +0000 Subject: add in report display template removing so much that we might as well copy the template, especially as we need to change the header include. Removed from default: most of shadow wrap RSS list of updates which are not public plus Report -> Clinic --- templates/web/zerotb/report/display.html | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 templates/web/zerotb/report/display.html diff --git a/templates/web/zerotb/report/display.html b/templates/web/zerotb/report/display.html new file mode 100644 index 000000000..5febcaaab --- /dev/null +++ b/templates/web/zerotb/report/display.html @@ -0,0 +1,28 @@ +[% + PROCESS "report/photo-js.html"; + PROCESS "maps/${map.type}.html"; + + problem_title = problem.title_safe _ ' - ' _ loc('Viewing a Clinic'); + INCLUDE 'header.html' + title = problem_title + robots = 'index, nofollow' + bodyclass = 'mappage'; +%] + +[% map_html %] + + + +[% INCLUDE 'report/banner.html' %] + +[% INCLUDE 'report/_main.html' %] + + + +[% INCLUDE 'report/update-form.html' %] +[% INCLUDE 'footer.html' %] -- cgit v1.2.3 From f1446b4f149bafff477fc3c0cc2b3636c683d6ae Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Wed, 12 Mar 2014 11:06:33 +0000 Subject: Remove subscribe to updates link from around page --- templates/web/zerotb/around/_updates.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 templates/web/zerotb/around/_updates.html diff --git a/templates/web/zerotb/around/_updates.html b/templates/web/zerotb/around/_updates.html new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3 From ff7ab3c5472035007ea919cbad6a55d4f2e5c3ec Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Wed, 12 Mar 2014 15:15:13 +0000 Subject: split out RSS link tag to its own template makes removing RSS from cobrands much easier --- templates/web/default/common_header_tags.html | 4 +--- templates/web/default/header_rss.html | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 templates/web/default/header_rss.html diff --git a/templates/web/default/common_header_tags.html b/templates/web/default/common_header_tags.html index 1080e8a88..cd6b4ab3a 100644 --- a/templates/web/default/common_header_tags.html +++ b/templates/web/default/common_header_tags.html @@ -33,9 +33,7 @@ [% END %] -[% IF rss %] - -[% END %] +[% INCLUDE 'header_rss.html' %] [% "$title :: " | html IF title %] diff --git a/templates/web/default/header_rss.html b/templates/web/default/header_rss.html new file mode 100644 index 000000000..b93a2e7fc --- /dev/null +++ b/templates/web/default/header_rss.html @@ -0,0 +1,3 @@ +[% IF rss %] + <link rel="alternate" type="application/rss+xml" title="[% rss.0 %]" href="[% rss.1 %]"> +[% END %] -- cgit v1.2.3 From bbd92ab46bc2bc6440395eddeed0ce6b8aaa14ba Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 12 Mar 2014 15:15:57 +0000 Subject: blank header_rss template for ZeroTB we don't want any RSS feeds for the site so disable --- templates/web/zerotb/header_rss.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 templates/web/zerotb/header_rss.html diff --git a/templates/web/zerotb/header_rss.html b/templates/web/zerotb/header_rss.html new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3 From 25e168724a6043f82428c2a232c128a844ec53db Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 12 Mar 2014 17:48:14 +0000 Subject: extract opengraph meta tags out to a template for ease of alteration/removal in cobrands --- templates/web/default/header_opengraph.html | 0 templates/web/fixmystreet/header.html | 10 +--------- templates/web/fixmystreet/header_opengraph.html | 9 +++++++++ 3 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 templates/web/default/header_opengraph.html create mode 100644 templates/web/fixmystreet/header_opengraph.html diff --git a/templates/web/default/header_opengraph.html b/templates/web/default/header_opengraph.html new file mode 100644 index 000000000..e69de29bb diff --git a/templates/web/fixmystreet/header.html b/templates/web/fixmystreet/header.html index e0d0e7af2..6736f19f5 100644 --- a/templates/web/fixmystreet/header.html +++ b/templates/web/fixmystreet/header.html @@ -11,15 +11,7 @@ <meta name="HandHeldFriendly" content="true"> <meta name="mobileoptimized" content="0"> - <meta property="og:url" content="[% c.cobrand.base_url %][% c.req.uri.path %]"> - <meta property="og:title" content="[% title || 'FixMyStreet' %]"> - <meta property="og:site_name" content="[% c.cobrand.site_title %]"> - [% IF c.req.uri.path == '/' %]<meta property="og:description" content="Report, view, and discuss local street-related problems.">[% END %] - <meta property="og:type" content="website"> - <meta property="og:image" content="[% c.cobrand.base_url %]/cobrands/fixmystreet/fms-og_image.png"> - <meta property="og:image:type" content="image/png"> - <meta property="og:image:width" content="500"> - <meta property="og:image:height" content="500"> + [% INCLUDE 'header_opengraph.html' %] [% SET start = c.config.ADMIN_BASE_URL IF admin %] diff --git a/templates/web/fixmystreet/header_opengraph.html b/templates/web/fixmystreet/header_opengraph.html new file mode 100644 index 000000000..45e7c246c --- /dev/null +++ b/templates/web/fixmystreet/header_opengraph.html @@ -0,0 +1,9 @@ + <meta property="og:url" content="[% c.cobrand.base_url %][% c.req.uri.path %]"> + <meta property="og:title" content="[% title || 'FixMyStreet' %]"> + <meta property="og:site_name" content="[% c.cobrand.site_title %]"> + [% IF c.req.uri.path == '/' %]<meta property="og:description" content="Report, view, and discuss local street-related problems.">[% END %] + <meta property="og:type" content="website"> + <meta property="og:image" content="[% c.cobrand.base_url %]/cobrands/fixmystreet/fms-og_image.png"> + <meta property="og:image:type" content="image/png"> + <meta property="og:image:width" content="500"> + <meta property="og:image:height" content="500"> -- cgit v1.2.3 From 35188ef646ebce2233292d9b7511930019235e93 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 12 Mar 2014 17:49:26 +0000 Subject: blank opengraph header template for ZeroTB --- templates/web/zerotb/header_opengraph.html | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 templates/web/zerotb/header_opengraph.html diff --git a/templates/web/zerotb/header_opengraph.html b/templates/web/zerotb/header_opengraph.html new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3 From 09aa80425c5618e5a1a2974e46568265260dcabf Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 12 Mar 2014 18:46:50 +0000 Subject: Add some overrides to zurich tests to stop failure Make sure mapit_id_whitelist set for tests that submit --- t/cobrand/zurich.t | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/cobrand/zurich.t b/t/cobrand/zurich.t index 73007ff59..e97891b91 100644 --- a/t/cobrand/zurich.t +++ b/t/cobrand/zurich.t @@ -565,6 +565,7 @@ subtest "phone number is mandatory" => sub { MAPIT_TYPES => [ 'O08' ], MAPIT_URL => 'http://global.mapit.mysociety.org/', ALLOWED_COBRANDS => [ 'zurich' ], + MAPIT_ID_WHITELIST => [ 423017 ], }, sub { $user = $mech->log_in_ok( 'dm1@example.org' ); $mech->get_ok( '/report/new?lat=47.381817&lon=8.529156' ); @@ -578,6 +579,8 @@ subtest "phone number is not mandatory for reports from mobile apps" => sub { FixMyStreet::override_config { MAPIT_TYPES => [ 'O08' ], MAPIT_URL => 'http://global.mapit.mysociety.org/', + ALLOWED_COBRANDS => [ 'zurich' ], + MAPIT_ID_WHITELIST => [ 423017 ], }, sub { $mech->post_ok( '/report/new/mobile?lat=47.381817&lon=8.529156' , { service => 'iPhone', @@ -587,6 +590,7 @@ subtest "phone number is not mandatory for reports from mobile apps" => sub { email => 'user@example.org', pc => '', name => '', + category => 'bad category', }); my $res = $mech->response; ok $res->header('Content-Type') =~ m{^application/json\b}, 'response should be json'; -- cgit v1.2.3 From 21bf819455562ab75874b230af82affd0138400c Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 14 Mar 2014 14:53:07 +0000 Subject: change text of location search instructions --- perllib/FixMyStreet/Cobrand/ZeroTB.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/perllib/FixMyStreet/Cobrand/ZeroTB.pm b/perllib/FixMyStreet/Cobrand/ZeroTB.pm index bd25b6076..589a9e922 100644 --- a/perllib/FixMyStreet/Cobrand/ZeroTB.pm +++ b/perllib/FixMyStreet/Cobrand/ZeroTB.pm @@ -6,6 +6,8 @@ use warnings; sub site_title { return 'ZeroTB'; } +sub enter_postcode_text { return _ ('Enter a nearby street name and area, postal code or district in Delhi'); } + sub country { return 'IN'; } -- cgit v1.2.3 From a3f5ea204ae4383e651f181dee1ef90b77193aac Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 14 Mar 2014 14:54:49 +0000 Subject: split out header logo to its own template to make it easier to change how cobrands do headers --- templates/web/default/header_logo.html | 0 templates/web/fixmystreet/header.html | 2 +- templates/web/fixmystreet/header_logo.html | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 templates/web/default/header_logo.html create mode 100644 templates/web/fixmystreet/header_logo.html diff --git a/templates/web/default/header_logo.html b/templates/web/default/header_logo.html new file mode 100644 index 000000000..e69de29bb diff --git a/templates/web/fixmystreet/header.html b/templates/web/fixmystreet/header.html index 6736f19f5..6ed84a85d 100644 --- a/templates/web/fixmystreet/header.html +++ b/templates/web/fixmystreet/header.html @@ -42,7 +42,7 @@ <div class="table-cell"> <header id="site-header" role="banner"> <div class="container"> - <a href="/" id="site-logo">[% c.cobrand.site_title | html %]</a> + [% INCLUDE 'header_logo.html' %] <a href="#main-nav" id="nav-link">Main Navigation</a> </div> </header> diff --git a/templates/web/fixmystreet/header_logo.html b/templates/web/fixmystreet/header_logo.html new file mode 100644 index 000000000..e5e4aae60 --- /dev/null +++ b/templates/web/fixmystreet/header_logo.html @@ -0,0 +1 @@ + <a href="/" id="site-logo">[% c.cobrand.site_title | html %]</a> -- cgit v1.2.3 From 7187775c54baf4f1aa30c3cc5230b7656df1b495 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 14 Mar 2014 14:57:24 +0000 Subject: import base styles with colour changes Change colour scheme to black and white --- web/cobrands/zerotb/_colours.scss | 21 +++++++++++++++++++++ web/cobrands/zerotb/base.scss | 6 ++++++ web/cobrands/zerotb/config.rb | 25 +++++++++++++++++++++++++ web/cobrands/zerotb/layout.scss | 2 ++ 4 files changed, 54 insertions(+) create mode 100644 web/cobrands/zerotb/_colours.scss create mode 100644 web/cobrands/zerotb/base.scss create mode 100644 web/cobrands/zerotb/config.rb create mode 100644 web/cobrands/zerotb/layout.scss diff --git a/web/cobrands/zerotb/_colours.scss b/web/cobrands/zerotb/_colours.scss new file mode 100644 index 000000000..8118feaf6 --- /dev/null +++ b/web/cobrands/zerotb/_colours.scss @@ -0,0 +1,21 @@ +/* COLOURS */ + +$white: #ffffff; +$black: #000000; + +$primary: $black; +$primary_b: $black; +$primary_text: $white; + +$base_bg: $white; +$base_fg: #000; + +$map_nav_bg: $black; +$nav_fg: #ffffff; +$nav_fg_hover: #000; + +$col_click_map: $black; +$col_click_map_dark: darken($black, 10%); + +$col_fixed_label: #00BD08; +$col_fixed_label_dark: #4B8304; diff --git a/web/cobrands/zerotb/base.scss b/web/cobrands/zerotb/base.scss new file mode 100644 index 000000000..7c303e874 --- /dev/null +++ b/web/cobrands/zerotb/base.scss @@ -0,0 +1,6 @@ +@import "../sass/h5bp"; +@import "./_colours"; +@import "../sass/mixins"; +@import "compass"; + +@import "../sass/base"; diff --git a/web/cobrands/zerotb/config.rb b/web/cobrands/zerotb/config.rb new file mode 100644 index 000000000..cab97b18f --- /dev/null +++ b/web/cobrands/zerotb/config.rb @@ -0,0 +1,25 @@ +# Require any additional compass plugins here. + +# Set this to the root of your project when deployed: +http_path = "/" +css_dir = "" +sass_dir = "" +images_dir = "" +javascripts_dir = "" + +# You can select your preferred output style here (can be overridden via the command line): +# output_style = :expanded or :nested or :compact or :compressed + +# To enable relative paths to assets via compass helper functions. Uncomment: +# relative_assets = true + +# To disable debugging comments that display the original location of your selectors. Uncomment: +# line_comments = false + +# If you prefer the indented syntax, you might want to regenerate this +# project again passing --syntax sass, or you can uncomment this: +# preferred_syntax = :sass +# and then run: +# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass + +line_comments = false # by Compass.app diff --git a/web/cobrands/zerotb/layout.scss b/web/cobrands/zerotb/layout.scss new file mode 100644 index 000000000..69e455aa5 --- /dev/null +++ b/web/cobrands/zerotb/layout.scss @@ -0,0 +1,2 @@ +@import "_colours"; +@import "../sass/layout"; -- cgit v1.2.3 From 8c65dc95a9551419a10a63944fea803db6b7d73c Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 14 Mar 2014 15:03:48 +0000 Subject: set default fonts and weight --- web/cobrands/zerotb/base.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/cobrands/zerotb/base.scss b/web/cobrands/zerotb/base.scss index 7c303e874..f166934b4 100644 --- a/web/cobrands/zerotb/base.scss +++ b/web/cobrands/zerotb/base.scss @@ -3,4 +3,14 @@ @import "../sass/mixins"; @import "compass"; +@import url(http://fonts.googleapis.com/css?family=Lato:300,700); + +$body-font: 'Lato', sans-serif !default; +$meta-font: 'Lato', sans-serif !default; +$heading-font: 'Lato', sans-serif !default; + @import "../sass/base"; + +body { + font-weight: 300; +} -- cgit v1.2.3 From e90cc2785ed1d2ee4b5a107337f9100f3815b884 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 14 Mar 2014 15:06:20 +0000 Subject: change site logo and add css for this --- templates/web/zerotb/header_logo.html | 6 ++++++ web/cobrands/zerotb/base.scss | 28 ++++++++++++++++++++++++++++ web/cobrands/zerotb/layout.scss | 26 ++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 templates/web/zerotb/header_logo.html diff --git a/templates/web/zerotb/header_logo.html b/templates/web/zerotb/header_logo.html new file mode 100644 index 000000000..bcc690d97 --- /dev/null +++ b/templates/web/zerotb/header_logo.html @@ -0,0 +1,6 @@ + <div id="site-logo"> + <p><a href="/">[% loc('<span class="orange">Report</span> Delhi TB drug <b>stockouts</b>') %]</a></p> + [% IF c.req.uri.path == '/' %] + <p id="brought-to-you">Brought to you by <a href="http://www.fixmystreet.org" id="fms-logo-small">FixMyStreet</a></p> + [% END %] + </div> diff --git a/web/cobrands/zerotb/base.scss b/web/cobrands/zerotb/base.scss index f166934b4..179578fc5 100644 --- a/web/cobrands/zerotb/base.scss +++ b/web/cobrands/zerotb/base.scss @@ -14,3 +14,31 @@ $heading-font: 'Lato', sans-serif !default; body { font-weight: 300; } + +#site-logo { + background: none; + text-indent: 0px; + text-transform: uppercase; + width: auto; + margin-right: 80px; + @media( min-width: 350px ) { + margin-top: 0.5em; + } + a { + color: white; + &:hover { + text-decoration: none; + } + } + p { + font-weight: 300; + margin-bottom: 0em; + } + .orange { + color: $button-color; + font-weight: bold; + } + #brought-to-you { + display: none; + } +} diff --git a/web/cobrands/zerotb/layout.scss b/web/cobrands/zerotb/layout.scss index 69e455aa5..58b4d9279 100644 --- a/web/cobrands/zerotb/layout.scss +++ b/web/cobrands/zerotb/layout.scss @@ -1,2 +1,28 @@ @import "_colours"; @import "../sass/layout"; + +body { + &.frontpage { + #site-logo { + background: none; + color: black; + width: auto; + a { + color: black; + } + p { + font-size: 2em; + font-weight: 300; + } + #brought-to-you { + display: block; + font-size: 0.75em; + } + } + } +} + +.container { + padding: 0em 1em; +} + -- cgit v1.2.3 From 64f54e965254f2209488f3a7f98f0f792b852162 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 14 Mar 2014 15:07:25 +0000 Subject: top nav bar should always be black --- web/cobrands/zerotb/layout.scss | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/web/cobrands/zerotb/layout.scss b/web/cobrands/zerotb/layout.scss index 58b4d9279..c11594141 100644 --- a/web/cobrands/zerotb/layout.scss +++ b/web/cobrands/zerotb/layout.scss @@ -3,6 +3,16 @@ body { &.frontpage { + .nav-wrapper-2 { + background-color: $primary; + color: $primary_text; + height: auto; + } + + #site-header { + background-color: $base_bg; + } + #site-logo { background: none; color: black; @@ -22,6 +32,35 @@ body { } } +.nav-wrapper { + .nav-wrapper-2 { + border: none; + min-height: 0; + } +} + +#main-nav { + ul#mysoc-menu { + li { + a { + color: $primary_text; + } + } + } + ul#main-menu { + li { + span { + color: $primary_text; + } + } + } +} + +#site-header { + background: $primary; + height: auto; +} + .container { padding: 0em 1em; } -- cgit v1.2.3 From 7d228fe7009aba30bc6ee457b41b3ca9f12bd63a Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 14 Mar 2014 15:07:51 +0000 Subject: update nav bar text Problem -> Stockout change mysoc nav to be language changer --- templates/web/zerotb/footer.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/web/zerotb/footer.html b/templates/web/zerotb/footer.html index 8b93e470e..a5e0ba7bc 100644 --- a/templates/web/zerotb/footer.html +++ b/templates/web/zerotb/footer.html @@ -7,13 +7,13 @@ <div class="nav-wrapper-2"> <div id="main-nav" role="navigation"> <ul id="mysoc-menu"> - <li><a href="http://mysociety.org/donate/">Donate</a></li>[% - %]<li><a href="http://www.mysociety.org/projects/">Our Sites</a></li> + <li><a href="http://en.[% c.cobrand.base_host %][% c.req.uri.path_query %]">English</a></li>[% + %]<li><a href="http://hi.[% c.cobrand.base_host %][% c.req.uri.path_query %]">हिन्दी</a></li> </ul> <ul id="main-menu"> <li><[% IF c.req.uri.path == '/' %]span[% ELSE %]a href="/"[% END %] class="report-a-problem-btn" - >[% loc("Report a problem") %]</[% c.req.uri.path == '/' ? 'span' : 'a' %]></li>[% + >[% loc("Report a Stockout") %]</[% c.req.uri.path == '/' ? 'span' : 'a' %]></li>[% %]<li><[% IF c.req.uri.path == '/faq' %]span[% ELSE %]a href="/faq"[% END %]>[% loc("Help") %]</[% c.req.uri.path == '/faq' ? 'span' : 'a' %]></li> </ul> -- cgit v1.2.3 From e74eff85b9e9fc809e96451c97e44a2095d86021 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 14 Mar 2014 15:09:43 +0000 Subject: Style front page location lookup button -> orange set background colour for input boxes make everything a bit wider and left aligned update the what is this for text --- templates/web/zerotb/around/intro.html | 1 + web/cobrands/zerotb/_colours.scss | 3 +++ web/cobrands/zerotb/base.scss | 31 +++++++++++++++++++++++++++++++ web/cobrands/zerotb/layout.scss | 17 +++++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 templates/web/zerotb/around/intro.html diff --git a/templates/web/zerotb/around/intro.html b/templates/web/zerotb/around/intro.html new file mode 100644 index 000000000..07d018f57 --- /dev/null +++ b/templates/web/zerotb/around/intro.html @@ -0,0 +1 @@ +<h2>[% loc('If a hospital, clinic or other health facility near you is experiencing <b>stockouts of tuberculosis drugs</b> you can use this form to report it') %]</h2> diff --git a/web/cobrands/zerotb/_colours.scss b/web/cobrands/zerotb/_colours.scss index 8118feaf6..a5eb83b71 100644 --- a/web/cobrands/zerotb/_colours.scss +++ b/web/cobrands/zerotb/_colours.scss @@ -19,3 +19,6 @@ $col_click_map_dark: darken($black, 10%); $col_fixed_label: #00BD08; $col_fixed_label_dark: #4B8304; + +$button-color: #FF5308; +$alt-background: #f5f5f5; diff --git a/web/cobrands/zerotb/base.scss b/web/cobrands/zerotb/base.scss index 179578fc5..4434dcae4 100644 --- a/web/cobrands/zerotb/base.scss +++ b/web/cobrands/zerotb/base.scss @@ -15,6 +15,37 @@ body { font-weight: 300; } +input { + background-color: $alt-background; +} + +#front-main { + h2 { + font-weight: 300; + } + + #postcodeForm { + div { + border: 0px; + background: $primary_b; + input#pc { + background-color: $alt-background; + border: 0px; + border-radius: 0px; + box-shadow: none; + width: 80%; + } + input#sub { + background-color: $button-color; + border: 1px solid $alt-background; + border-radius: 5px; + color: white; + margin-left: 5px; + } + } + } +} + #site-logo { background: none; text-indent: 0px; diff --git a/web/cobrands/zerotb/layout.scss b/web/cobrands/zerotb/layout.scss index c11594141..8bc314914 100644 --- a/web/cobrands/zerotb/layout.scss +++ b/web/cobrands/zerotb/layout.scss @@ -65,3 +65,20 @@ body { padding: 0em 1em; } +#front-main { + #front-main-container { + max-width: 40em; + h2 { + text-align: left; + } + } + #postcodeForm { + div { + width: 30em; + input#pc { + max-width: none; + width: 27em; + } + } + } +} -- cgit v1.2.3 From ea01f4ff8681284e8f5f1e45e4c6c61a14f11311 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 14 Mar 2014 15:13:08 +0000 Subject: update content and style for front page instructions remove photos etc update step text change background colour and remove crop shadow --- templates/web/zerotb/index-steps.html | 8 +++++++ templates/web/zerotb/index.html | 45 +++++++++++++++++++++++++++++++++++ web/cobrands/zerotb/layout.scss | 5 ++++ 3 files changed, 58 insertions(+) create mode 100644 templates/web/zerotb/index-steps.html create mode 100644 templates/web/zerotb/index.html diff --git a/templates/web/zerotb/index-steps.html b/templates/web/zerotb/index-steps.html new file mode 100644 index 000000000..6c11ca6d4 --- /dev/null +++ b/templates/web/zerotb/index-steps.html @@ -0,0 +1,8 @@ +<h2>[% loc('How to report a stockout') %]</h2> + +<ol class="big-numbers"> + <li>[% loc('Enter a Delhi TB clinic or street name and area') %]</li> + <li>[% loc('Locate the clinic on a map of the area') %]</li> + <li>[% loc('Enter details of the problem') %]</li> + <li>[% loc('We will send your stockout report to the district TB officer and to local activists to ensure action is taken') %]</li> +</ol> diff --git a/templates/web/zerotb/index.html b/templates/web/zerotb/index.html new file mode 100644 index 000000000..95d6fa013 --- /dev/null +++ b/templates/web/zerotb/index.html @@ -0,0 +1,45 @@ +[%# Assumes fixmystreet cobrand is using FMS map template - for bonus points preload all the right map elements. %] +[% map_js = BLOCK %] +<script> +yepnope.addPrefix( 'preload', function ( resource ) { + resource.noexec = true; + return resource; +}); +Modernizr.load({ + load: [ + "preload![% version('/js/OpenLayers.fixmystreet.js') %]", + "preload![% version('/js/map-OpenLayers.js') %]", + "preload![% version('/js/map-bing-ol.js') %]", + "preload![% version('/js/jquery.ba-hashchange.min.js') %]" + ] +}); +</script> +[% END %] + +[% pre_container_extra = PROCESS 'around/postcode_form.html' %] +[% INCLUDE 'header.html', title = '', bodyclass = 'frontpage fullwidthpage' %] + +[% IF error %] + <p class="form-error">[% error %]</p> +[% END %] + +<div class="tablewrapper"> + <div id="front-howto"> + [% INCLUDE 'index-steps.html' %] + </div> + + <div id="front-recently"> + <h2> + [% loc('About stockouts') %] + </h2> + + <section class="full-width"> + <p> + Explain stockouts here. + </p> + </section> + + </div> +</div> + +[% INCLUDE 'footer.html' pagefooter = 'yes' %] diff --git a/web/cobrands/zerotb/layout.scss b/web/cobrands/zerotb/layout.scss index 8bc314914..91a3d7166 100644 --- a/web/cobrands/zerotb/layout.scss +++ b/web/cobrands/zerotb/layout.scss @@ -82,3 +82,8 @@ body { } } } + +.content { + background-color: $alt-background; + box-shadow: none; +} -- cgit v1.2.3 From 96a2bb583e614cdd721909ebff21f3c1522e1024 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 19 Mar 2014 10:06:06 +0000 Subject: enable generation of cobrand specific po files This is to stop the main FixMyStreet .po file being filled up with strings that only apply to a single Cobrand that are not relevant to people running their own fairly vanilla version. Restrict the list of templates that we pull strings out of by generating a list of all the ones in currently translated cobrands for minimal impact. Use that to create the FixMyStreet.po file in gettext-extract. I've left the existing cobrands in but at some point we should split these out. Add a specific script for generating Cobrand .po files which extracts strings from the Cobrand's templates. This is then translated and the translated file place in the locale/$lang/LC_MESSAGES. The the merge_cobrand_po script is run which will create an autoCobrand.po file which has the Cobrand strings and the FixMyStreet strings. This is then the domain used by the Cobrand. Any cobrand specific strings from the Perl code are still in the main FixMyStreet .po file but there should be relatively few of them so it's less of an issue. --- bin/generate_cobrand_po | 40 ++++++++++++++++++++++++++++++++++++++++ bin/gettext-extract | 27 +++++++++------------------ bin/merge_cobrand_po | 29 +++++++++++++++++++++++++++++ bin/update_po_header.bash | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 18 deletions(-) create mode 100755 bin/generate_cobrand_po create mode 100755 bin/merge_cobrand_po create mode 100755 bin/update_po_header.bash diff --git a/bin/generate_cobrand_po b/bin/generate_cobrand_po new file mode 100755 index 000000000..1770de7c2 --- /dev/null +++ b/bin/generate_cobrand_po @@ -0,0 +1,40 @@ +#!/usr/bin/env perl +use strict; + +=for instructions + +This script generates a po file that contains only the strings related to a cobrand. +Its purpose is to avoid having lots of cobrand specific strings in the +main FixMyStreet po file confusing translators. Once this is run the +generated po file can be used to translate the strings. The po files +with the translation should then be placed in the relevant language +directories and merge_cobrand_po run which will merge these with +the main FixMyStreet po files + +=cut + +my $cobrand = shift; + +die "Please provide a cobrand name\n" unless $cobrand; + +my $cobrand_lc = lc( $cobrand ); + +my $PO = "locale/$cobrand.po"; + +my $cobrand_module = "perllib/FixMyStreet/Cobrand/$cobrand.pm"; +my $web_templates = "templates/web/$cobrand_lc/"; + +my $opts = ''; +if ( -e $cobrand_module ) { + $opts .= " $cobrand_module"; +} + +if ( -e -d $web_templates ) { + $opts .= " --directory $web_templates"; +} + +# first we get the strings from the cobrand +system("xgettext.pl --gnu-gettext --verbose --output $PO --plugin perl=* --plugin tt2 $opts"); + +# now replace the template header elements +system("bin/update_po_header.bash $PO"); diff --git a/bin/gettext-extract b/bin/gettext-extract index 934bf1711..a38c17127 100755 --- a/bin/gettext-extract +++ b/bin/gettext-extract @@ -13,28 +13,19 @@ cd "$(dirname $(readlink -f $BASH_SOURCE))/.." PO=locale/FixMyStreet.po rm -f $PO +# we don't want to extract strings from all the cobrand templates so list +# the ones we care about +find templates/web/default templates/web/fixmystreet templates/web/zurich templates/web/fiksgatami templates/web/emptyhomes templates/web/fixmybarangay -name '*.html' > template_list + # Extract from Perl -xgettext.pl --gnu-gettext --verbose --output $PO --plugin perl=* --plugin tt2 --directory perllib --directory templates/web --directory db --directory bin +xgettext.pl --gnu-gettext --verbose --output $PO --plugin perl=* --plugin tt2 --directory perllib -f template_list --directory db --directory bin + +# remove temporary list of templates +rm template_list # Fix headers # no such thing as tempfile on OS X -TEMP=`tempfile 2>/dev/null || mktemp /tmp/gettext-extract.XXXXXX` -NOW=`date +"%Y-%m-%d %H:%M%z"` -# strictly POSIX sed on e.g. OS X doesn't let you used \n in replacements so we do this -nl=$'\n'; -cat $PO | sed " - s/SOME DESCRIPTIVE TITLE/FixMyStreet original .po file, autogenerated by gettext-extract/; - s/YEAR THE PACKAGE'S COPYRIGHT HOLDER/2011 UK Citizens Online Democracy/; - s/PACKAGE package/main FixMyStreet code/; - s/FIRST AUTHOR <EMAIL@ADDRESS>, YEAR./Matthew Somerville <matthew@mysociety.org>, 2011-06-03./; - - s/PACKAGE VERSION/1.0\\\n\"\\$nl\"Report-Msgid-Bugs-To: matthew@mysociety.org/; - s/POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE/POT-Creation-Date: $NOW/; - s/LL@li.org/team@fixmystreet.com/; - s/charset=CHARSET/charset=UTF-8/; - s/8bit/8bit\\\n\"\\$nl\"Plural-Forms: nplurals=2; plural=n != 1;/; -" >> $TEMP -mv $TEMP $PO +bin/update_po_header.bash $PO echo "$( bin/gettext-nget-patch )" >> $PO diff --git a/bin/merge_cobrand_po b/bin/merge_cobrand_po new file mode 100755 index 000000000..2785162cf --- /dev/null +++ b/bin/merge_cobrand_po @@ -0,0 +1,29 @@ +#!/usr/bin/env perl +use strict; + +=for instructions + +This script is used to merge cobrand po files with the main +FixMyStreet po file. It should be run after generate_cobrand_po +and once the cobrand po files with translations are placed in the +language directories. + +It will then create an autoCobrand.po file for each language that +has a Cobrand.po + +=cut + +my $cobrand = shift; + +die "Please provide a cobrand name\n" unless $cobrand; + +# for each language create a .po file with an existing translations +for (glob( 'locale/*/LC_MESSAGES' ) ) { + my $fms = "$_/FixMyStreet.po"; + my $cobrand_po = "$_/$cobrand.po"; + my $out = "$_/auto$cobrand.po"; + if ( -e $cobrand_po and -e $fms ) { + print "$_\n"; + system("msgcat --no-wrap -o $out $fms $cobrand_po"); + } +} diff --git a/bin/update_po_header.bash b/bin/update_po_header.bash new file mode 100755 index 000000000..2f37ad5d3 --- /dev/null +++ b/bin/update_po_header.bash @@ -0,0 +1,33 @@ +#!/bin/bash +# +# fixmystreet/bin/gettext-extract +# Generate English language .po files from the source code and email templates, +# for FixMyStreet. Writes the output to appropriate .po files in locale/. +# +# Copyright (c) 2011 UK Citizens Online Democracy. All rights reserved. +# Email: matthew@mysociety.org; WWW: http://www.mysociety.org/ + +cd "$(dirname $(readlink -f $BASH_SOURCE))/.." + +# File to write to, clear it to start with +PO=$1 + +# Fix headers +# no such thing as tempfile on OS X +TEMP=`tempfile 2>/dev/null || mktemp /tmp/gettext-extract.XXXXXX` +NOW=`date +"%Y-%m-%d %H:%M%z"` +# strictly POSIX sed on e.g. OS X doesn't let you used \n in replacements so we do this +nl=$'\n'; +cat $PO | sed " + s/SOME DESCRIPTIVE TITLE/FixMyStreet original .po file, autogenerated by gettext-extract/; + s/YEAR THE PACKAGE'S COPYRIGHT HOLDER/2011 UK Citizens Online Democracy/; + s/PACKAGE package/main FixMyStreet code/; + s/FIRST AUTHOR <EMAIL@ADDRESS>, YEAR./Matthew Somerville <matthew@mysociety.org>, 2011-06-03./; + + s/PACKAGE VERSION/1.0\\\n\"\\$nl\"Report-Msgid-Bugs-To: matthew@mysociety.org/; + s/POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE/POT-Creation-Date: $NOW/; + s/LL@li.org/team@fixmystreet.com/; + s/charset=CHARSET/charset=UTF-8/; + s/8bit/8bit\\\n\"\\$nl\"Plural-Forms: nplurals=2; plural=n != 1;/; +" >> $TEMP +mv $TEMP $PO -- cgit v1.2.3 From f0c1e25cd4774996d6e523bdad73ae3d8f6cabae Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 19 Mar 2014 12:26:18 +0000 Subject: Allow comments to be confirmed with no confirmation Add never_confirm_updates setting to cobrand that, when set to 0 means that comments are confirmed with no confirmation step. Essentially behaves as it would if the user was logged in. Also add a flag to the flash that indicates the comment was accepted to you can display a message on the report page if required --- .../FixMyStreet/App/Controller/Report/Update.pm | 10 ++- perllib/FixMyStreet/Cobrand/Default.pm | 8 +++ t/app/controller/report_updates.t | 79 ++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/perllib/FixMyStreet/App/Controller/Report/Update.pm b/perllib/FixMyStreet/App/Controller/Report/Update.pm index dbfd57e78..bc79cafd3 100644 --- a/perllib/FixMyStreet/App/Controller/Report/Update.pm +++ b/perllib/FixMyStreet/App/Controller/Report/Update.pm @@ -312,7 +312,14 @@ sub save_update : Private { my $update = $c->stash->{update}; - if ( !$update->user->in_storage ) { + if ( $c->cobrand->never_confirm_updates ) { + if ( $update->user->in_storage() ) { + $update->user->update(); + } else { + $update->user->insert(); + } + $update->confirm(); + } elsif ( !$update->user->in_storage ) { # User does not exist. # Store changes in token for when token is validated. $c->stash->{token_data} = { @@ -370,6 +377,7 @@ sub redirect_or_confirm_creation : Private { $c->forward( 'signup_for_alerts' ); my $report_uri = $c->cobrand->base_url_for_report( $update->problem ) . $update->problem->url; + $c->flash->{comment_created} = 1; $c->res->redirect($report_uri); $c->detach; } diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index 2de3eae5f..60ed216d7 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -829,6 +829,14 @@ sub show_unconfirmed_reports { 0; } +=head2 never_confirm_updates + +If true then we never send an email to confirm an update + +=cut + +sub never_confirm_updates { 0; } + =head2 prettify_dt my $date = $c->prettify_dt( $datetime ); diff --git a/t/app/controller/report_updates.t b/t/app/controller/report_updates.t index 3356867bb..e84755650 100644 --- a/t/app/controller/report_updates.t +++ b/t/app/controller/report_updates.t @@ -407,6 +407,85 @@ for my $test ( $report->state('confirmed'); $report->update; +for my $test ( + { + desc => 'overriding email confirmation allows report confirmation with no email sent', + initial_values => { + name => '', + rznvy => '', + may_show_name => 1, + add_alert => 1, + photo => '', + update => '', + fixed => undef, + remember_me => undef, + password_register => '', + password_sign_in => '', + }, + form_values => { + submit_update => 1, + rznvy => 'unregistered@example.com', + update => "update no email confirm", + add_alert => 1, + name => 'Unreg User', + may_show_name => undef, + }, + changes => { + update => "Update no email confirm", + }, + } +) { + subtest $test->{desc} => sub { + my $send_confirmation_mail_override = Sub::Override->new( + "FixMyStreet::Cobrand::Default::never_confirm_updates", + sub { return 1; } + ); + $mech->log_out_ok(); + $mech->clear_emails_ok(); + + $mech->get_ok("/report/$report_id"); + + my $values = $mech->visible_form_values('updateForm'); + + is_deeply $values, $test->{initial_values}, 'initial form values'; + + $mech->submit_form_ok( + { + with_fields => $test->{form_values} + }, + 'submit update' + ); + + $mech->content_contains('Test 2'); + $mech->content_contains('Update no email confirm'); + + my $email = $mech->email_count_is(0); + + my $update = + FixMyStreet::App->model('DB::Comment')->find( { problem_id => $report_id, text => 'Update no email confirm' } ); + my $update_id = $update->id; + + $mech->content_contains('name="update_' . $update_id . '"'); + + my $details = { + %{ $test->{form_values} }, + %{ $test->{changes} } + }; + + ok $update, 'found update in database'; + is $update->state, 'confirmed', 'update confirmed'; + is $update->user->email, $details->{rznvy}, 'update email'; + is $update->text, $details->{update}, 'update text'; + + my $unreg_user = FixMyStreet::App->model( 'DB::User' )->find( { email => $details->{rznvy} } ); + + ok $unreg_user, 'found user'; + + $mech->delete_user( $unreg_user ); + $send_confirmation_mail_override->restore(); + }; +} + subtest 'check non authority user cannot change set state' => sub { $mech->log_in_ok( $user->email ); $user->from_body( undef ); -- cgit v1.2.3 From 90bf8d40800452046b4b89e80e2af64ee65872bc Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 19 Mar 2014 12:29:34 +0000 Subject: turn on never_confirm_updates for ZeroTB and display a message on the report page to indicate the update has been sent as it's never displayed --- perllib/FixMyStreet/Cobrand/ZeroTB.pm | 1 + templates/web/zerotb/report/display.html | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/perllib/FixMyStreet/Cobrand/ZeroTB.pm b/perllib/FixMyStreet/Cobrand/ZeroTB.pm index 589a9e922..a63a451cc 100644 --- a/perllib/FixMyStreet/Cobrand/ZeroTB.pm +++ b/perllib/FixMyStreet/Cobrand/ZeroTB.pm @@ -28,6 +28,7 @@ sub allow_photo_display { return 0; } sub allow_photo_upload{ return 0; } sub send_questionnaires { return 0; } sub on_map_default_max_pin_age { return 0; } +sub never_confirm_updates { 1; } 1; diff --git a/templates/web/zerotb/report/display.html b/templates/web/zerotb/report/display.html index 5febcaaab..9161fb586 100644 --- a/templates/web/zerotb/report/display.html +++ b/templates/web/zerotb/report/display.html @@ -15,6 +15,12 @@ [% INCLUDE 'report/banner.html' %] +[% IF c.flash.comment_created %] +<p> +[% loc('Your report has been sent') %] +</p> +[% END %] + [% INCLUDE 'report/_main.html' %] <div class="shadow-wrap"> -- cgit v1.2.3 From 6daa28af47cb7a854b961601d9f23a8ddfc20db3 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 19 Mar 2014 16:29:08 +0000 Subject: remove most of the detail from zerotb report pages only really want the title and the details as all the rest is not relevant --- templates/web/zerotb/report/_main.html | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 templates/web/zerotb/report/_main.html diff --git a/templates/web/zerotb/report/_main.html b/templates/web/zerotb/report/_main.html new file mode 100644 index 000000000..189b4eeb5 --- /dev/null +++ b/templates/web/zerotb/report/_main.html @@ -0,0 +1,6 @@ +<div class="problem-header cf"> + <h1>[% problem.title | html %]</h1> + + [% add_links( problem.detail ) | html_para %] + +</div> -- cgit v1.2.3 From d20a625ce63fc4182b54d12d5392b78fabdd1ca9 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 19 Mar 2014 17:40:16 +0000 Subject: update report page text for ZeroTB Change to use sites terminology update notes on what happens to information remove some unused template tags --- templates/web/zerotb/report/update-form.html | 16 +++++++--------- templates/web/zerotb/report/updates-sidebar-notes.html | 3 +++ 2 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 templates/web/zerotb/report/updates-sidebar-notes.html diff --git a/templates/web/zerotb/report/update-form.html b/templates/web/zerotb/report/update-form.html index db58cb9d1..b278b5f8d 100644 --- a/templates/web/zerotb/report/update-form.html +++ b/templates/web/zerotb/report/update-form.html @@ -1,11 +1,9 @@ <div id="update_form"> - <h2>[% loc( 'Provide an update') %]</h2> + <h2>[% loc( 'Report a stockout') %]</h2> - [% IF c.cobrand.moniker != 'emptyhomes' AND c.cobrand.moniker != 'stevenage' %] <div class="general-sidebar-notes"> [% INCLUDE 'report/updates-sidebar-notes.html' %] </div> - [% END %] [% INCLUDE 'errors.html' %] @@ -14,7 +12,7 @@ <input type="hidden" name="submit_update" value="1"> <input type="hidden" name="id" value="[% problem.id | html %]"> - <label for="form_update">[% loc( 'Update' ) %]</label> + <label for="form_update">[% loc( 'Details' ) %]</label> [% IF field_errors.update %] <div class='form-error'>[% field_errors.update %]</div> [% END %] @@ -30,6 +28,11 @@ [% ELSE %] <label for="form_rznvy">[% loc('Email' ) %]</label> + + <div class="general-sidebar-notes"> + <p class="dark">[% loc('We never show your email or name') %]</p> + </div> + [% IF field_errors.email %] <p class='form-error'>[% field_errors.email %]</p> [% END %] @@ -39,11 +42,6 @@ <div id="form_sign_in_no"> [% INCLUDE name %] - <div class="general-sidebar-notes"> - <p class="dark">[% loc('We never show your email') %]</p> - <p>[% loc('Providing a password is optional, but doing so will allow you to more easily report problems, leave updates and manage your reports.') %]</p> - </div> - <div class="form-txt-submit-box"> <input class="green-btn" type="submit" name="submit_register" id="submit_register" value="[% loc('Post') %]"> </div> diff --git a/templates/web/zerotb/report/updates-sidebar-notes.html b/templates/web/zerotb/report/updates-sidebar-notes.html new file mode 100644 index 000000000..28de7d4fa --- /dev/null +++ b/templates/web/zerotb/report/updates-sidebar-notes.html @@ -0,0 +1,3 @@ + <p> + [% loc( 'Please note that reports are sent to the clinic.' ) %] + [% loc( 'Your information will only be used in accordance with our <a href="/privacy">privacy policy</a>' ) %]</p> -- cgit v1.2.3 From 36c4b4453badef9f9d162de326d36e6e17e172a5 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 19 Mar 2014 17:41:51 +0000 Subject: Problem -> Clinic on around page for ZeroTB --- templates/web/zerotb/around/tabbed_lists.html | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 templates/web/zerotb/around/tabbed_lists.html diff --git a/templates/web/zerotb/around/tabbed_lists.html b/templates/web/zerotb/around/tabbed_lists.html new file mode 100644 index 000000000..8b8e8753e --- /dev/null +++ b/templates/web/zerotb/around/tabbed_lists.html @@ -0,0 +1,14 @@ +<menu id="problems-nav" class="tab-nav"> + <ul> + <li><a href="#current">[% loc('Clinics on the map') %]</a></li> + <li><a href="#current_near">[% loc( 'Clinics nearby' ) %]</a></li> + </ul> +</menu> + +<ul id="current" class="issue-list-a tab"> + [% INCLUDE "around/on_map_list_items.html" %] +</ul> + +<ul id="current_near" class="issue-list-a tab"> + [% INCLUDE "around/around_map_list_items.html" %] +</ul> -- cgit v1.2.3 From 39f714f27fd78ab3223d14510199bb09053d16f6 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 19 Mar 2014 17:51:44 +0000 Subject: change ZeroTB button colour to orange --- web/cobrands/zerotb/base.scss | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/web/cobrands/zerotb/base.scss b/web/cobrands/zerotb/base.scss index 4434dcae4..4871575fb 100644 --- a/web/cobrands/zerotb/base.scss +++ b/web/cobrands/zerotb/base.scss @@ -73,3 +73,21 @@ input { display: none; } } + +button.green-btn, +input.green-btn, +.green-btn { + background: none; + background-color: $button-color; + border: 1px solid $alt-background; + border-radius: 5px; + color: white; +} + +button.green-btn:hover, +input.green-btn:hover, +.green-btn:hover { + background: none; + background-color: darken( $button-color, 10% ); + border: 1px solid $alt-background; +} -- cgit v1.2.3 From b20cb6576485017970f025e6f294ac8c09f39e69 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Thu, 20 Mar 2014 17:27:00 +0000 Subject: Add a searchable dropdown of clinic names to postcode form uses select2 for the dropdown and a cobrand call to fetch the list of report titles as the clinic names. Relies on a redirect in httpd.conf to make the non JS version work. Still leaves the postcode search in place as select2 won't accept custom terms if you base it on a select box, which we want to do so it works if JavaScript is disabled Fixes #723 --- perllib/FixMyStreet/Cobrand/ZeroTB.pm | 6 + templates/web/zerotb/around/postcode_form.html | 36 + templates/web/zerotb/header_extra.html | 18 + web/cobrands/zerotb/base.scss | 27 + web/cobrands/zerotb/layout.scss | 15 + web/js/select2/LICENSE | 18 + web/js/select2/bower.json | 8 + web/js/select2/release.sh | 73 + web/js/select2/select2-bootstrap.css | 87 + web/js/select2/select2-spinner.gif | Bin 0 -> 1849 bytes web/js/select2/select2.css | 615 +++++ web/js/select2/select2.jquery.json | 36 + web/js/select2/select2.js | 3255 ++++++++++++++++++++++++ web/js/select2/select2.min.js | 22 + web/js/select2/select2.png | Bin 0 -> 613 bytes web/js/select2/select2_locale_ar.js | 17 + web/js/select2/select2_locale_bg.js | 17 + web/js/select2/select2_locale_ca.js | 17 + web/js/select2/select2_locale_cs.js | 49 + web/js/select2/select2_locale_da.js | 17 + web/js/select2/select2_locale_de.js | 15 + web/js/select2/select2_locale_el.js | 17 + web/js/select2/select2_locale_en.js.template | 17 + web/js/select2/select2_locale_es.js | 15 + web/js/select2/select2_locale_et.js | 17 + web/js/select2/select2_locale_eu.js | 43 + web/js/select2/select2_locale_fa.js | 17 + web/js/select2/select2_locale_fi.js | 28 + web/js/select2/select2_locale_fr.js | 15 + web/js/select2/select2_locale_gl.js | 43 + web/js/select2/select2_locale_he.js | 17 + web/js/select2/select2_locale_hr.js | 42 + web/js/select2/select2_locale_hu.js | 15 + web/js/select2/select2_locale_id.js | 17 + web/js/select2/select2_locale_is.js | 16 + web/js/select2/select2_locale_it.js | 15 + web/js/select2/select2_locale_ja.js | 15 + web/js/select2/select2_locale_ko.js | 17 + web/js/select2/select2_locale_lt.js | 29 + web/js/select2/select2_locale_lv.js | 16 + web/js/select2/select2_locale_mk.js | 17 + web/js/select2/select2_locale_ms.js | 17 + web/js/select2/select2_locale_nl.js | 15 + web/js/select2/select2_locale_no.js | 18 + web/js/select2/select2_locale_pl.js | 37 + web/js/select2/select2_locale_pt-BR.js | 15 + web/js/select2/select2_locale_pt-PT.js | 15 + web/js/select2/select2_locale_ro.js | 15 + web/js/select2/select2_locale_ru.js | 15 + web/js/select2/select2_locale_sk.js | 48 + web/js/select2/select2_locale_sv.js | 17 + web/js/select2/select2_locale_th.js | 17 + web/js/select2/select2_locale_tr.js | 17 + web/js/select2/select2_locale_ua.js | 17 + web/js/select2/select2_locale_vi.js | 18 + web/js/select2/select2_locale_zh-CN.js | 14 + web/js/select2/select2_locale_zh-TW.js | 14 + web/js/select2/select2x2.png | Bin 0 -> 845 bytes 58 files changed, 5085 insertions(+) create mode 100644 templates/web/zerotb/around/postcode_form.html create mode 100644 templates/web/zerotb/header_extra.html create mode 100644 web/js/select2/LICENSE create mode 100644 web/js/select2/bower.json create mode 100755 web/js/select2/release.sh create mode 100644 web/js/select2/select2-bootstrap.css create mode 100644 web/js/select2/select2-spinner.gif create mode 100644 web/js/select2/select2.css create mode 100644 web/js/select2/select2.jquery.json create mode 100644 web/js/select2/select2.js create mode 100644 web/js/select2/select2.min.js create mode 100644 web/js/select2/select2.png create mode 100644 web/js/select2/select2_locale_ar.js create mode 100644 web/js/select2/select2_locale_bg.js create mode 100644 web/js/select2/select2_locale_ca.js create mode 100644 web/js/select2/select2_locale_cs.js create mode 100644 web/js/select2/select2_locale_da.js create mode 100644 web/js/select2/select2_locale_de.js create mode 100644 web/js/select2/select2_locale_el.js create mode 100644 web/js/select2/select2_locale_en.js.template create mode 100644 web/js/select2/select2_locale_es.js create mode 100644 web/js/select2/select2_locale_et.js create mode 100644 web/js/select2/select2_locale_eu.js create mode 100644 web/js/select2/select2_locale_fa.js create mode 100644 web/js/select2/select2_locale_fi.js create mode 100644 web/js/select2/select2_locale_fr.js create mode 100644 web/js/select2/select2_locale_gl.js create mode 100644 web/js/select2/select2_locale_he.js create mode 100644 web/js/select2/select2_locale_hr.js create mode 100644 web/js/select2/select2_locale_hu.js create mode 100644 web/js/select2/select2_locale_id.js create mode 100644 web/js/select2/select2_locale_is.js create mode 100644 web/js/select2/select2_locale_it.js create mode 100644 web/js/select2/select2_locale_ja.js create mode 100644 web/js/select2/select2_locale_ko.js create mode 100644 web/js/select2/select2_locale_lt.js create mode 100644 web/js/select2/select2_locale_lv.js create mode 100644 web/js/select2/select2_locale_mk.js create mode 100644 web/js/select2/select2_locale_ms.js create mode 100644 web/js/select2/select2_locale_nl.js create mode 100644 web/js/select2/select2_locale_no.js create mode 100644 web/js/select2/select2_locale_pl.js create mode 100644 web/js/select2/select2_locale_pt-BR.js create mode 100644 web/js/select2/select2_locale_pt-PT.js create mode 100644 web/js/select2/select2_locale_ro.js create mode 100644 web/js/select2/select2_locale_ru.js create mode 100644 web/js/select2/select2_locale_sk.js create mode 100644 web/js/select2/select2_locale_sv.js create mode 100644 web/js/select2/select2_locale_th.js create mode 100644 web/js/select2/select2_locale_tr.js create mode 100644 web/js/select2/select2_locale_ua.js create mode 100644 web/js/select2/select2_locale_vi.js create mode 100644 web/js/select2/select2_locale_zh-CN.js create mode 100755 web/js/select2/select2_locale_zh-TW.js create mode 100644 web/js/select2/select2x2.png diff --git a/perllib/FixMyStreet/Cobrand/ZeroTB.pm b/perllib/FixMyStreet/Cobrand/ZeroTB.pm index a63a451cc..c774610c4 100644 --- a/perllib/FixMyStreet/Cobrand/ZeroTB.pm +++ b/perllib/FixMyStreet/Cobrand/ZeroTB.pm @@ -30,5 +30,11 @@ sub send_questionnaires { return 0; } sub on_map_default_max_pin_age { return 0; } sub never_confirm_updates { 1; } +sub get_clinic_list { + my $self = shift; + + return $self->problems->search({ state => 'confirmed' }, { order_by => 'title' }); +} + 1; diff --git a/templates/web/zerotb/around/postcode_form.html b/templates/web/zerotb/around/postcode_form.html new file mode 100644 index 000000000..484f41c5c --- /dev/null +++ b/templates/web/zerotb/around/postcode_form.html @@ -0,0 +1,36 @@ +<div id="front-main"> + <div id="front-main-container"> + [% INCLUDE 'around/intro.html' %] + + [% + question = c.cobrand.enter_postcode_text || loc('Enter a nearby street name and area'); + %] + + <div id="postcodeForm"> + <form action="[% c.uri_for('/around') %]" method="get" name="postcodeForm"> + <label for="pc">[% question %]:</label> + <div> + <input type="text" name="pc" value="[% pc | html %]" id="pc" size="10" maxlength="200" placeholder="[% tprintf(loc('e.g. ‘%s’ or ‘%s’'), c.cobrand.example_places) %]"> + <input type="submit" value="[% loc('Go') %]" id="sub"> + </div> + + [% IF partial_token %] + <input type="hidden" name="partial" value="[% partial_token.token %]"> + [% END %] + + </form> + <form action="[% c.uri_for( '/report' ) %]/" method="get" name="clinicForm"> + <label for="clinic">[% loc('... or select a clinic by name' ) %]</label> + <div> + <select id="clinic" name="clinic"> + [% FOR clinic IN c.cobrand.get_clinic_list %] + <option value="[% clinic.id %]">[% clinic.title %]</option> + [% END %] + </select> + <input class="green-btn" type="submit" value="[% loc('Go') %]" id="show_report"> + </div> + </form> + </div> + + </div> +</div> diff --git a/templates/web/zerotb/header_extra.html b/templates/web/zerotb/header_extra.html new file mode 100644 index 000000000..23f061799 --- /dev/null +++ b/templates/web/zerotb/header_extra.html @@ -0,0 +1,18 @@ +<link rel="stylesheet" href="[% start %][% version('/js/select2/select2.css') %]" /> +<script type="text/javascript" src="[% start %][% version('/js/select2/select2.min.js') %]"></script> + +<script> +$(document).ready(function() { + $("#clinic").select2({ + placeholder: '[% loc('Enter a clinic name') %]', + allowClear: true, + }); + $('#show_report').click( + function() { + var report = $('#clinic').select2('val'); + document.location = '[% c.uri_for( '/report' ) %]/' + report; + return false; + } + ); +}); +</script> diff --git a/web/cobrands/zerotb/base.scss b/web/cobrands/zerotb/base.scss index 4871575fb..db612094d 100644 --- a/web/cobrands/zerotb/base.scss +++ b/web/cobrands/zerotb/base.scss @@ -28,8 +28,20 @@ input { div { border: 0px; background: $primary_b; + select#clinic, input#pc { background-color: $alt-background; + } + select#clinic { + border: 0; + border-radius: 0px; + display: table-cell; + line-height: 1.5em; + padding: 0.25em 2%; + } + .select2-container, + select#clinic, + input#pc { border: 0px; border-radius: 0px; box-shadow: none; @@ -42,6 +54,21 @@ input { color: white; margin-left: 5px; } + input#show_report { + width: 14%; + margin-left: 5px; + } + .select2-container { + display: inline-block; + text-align: left; + a { + line-height: 1.5em; + padding: 0.25em 2%; + } + } + .select2-display-none { + display: none; + } } } } diff --git a/web/cobrands/zerotb/layout.scss b/web/cobrands/zerotb/layout.scss index 91a3d7166..7c12ab6ba 100644 --- a/web/cobrands/zerotb/layout.scss +++ b/web/cobrands/zerotb/layout.scss @@ -75,10 +75,25 @@ body { #postcodeForm { div { width: 30em; + .select2-container, + select#clinic, input#pc { max-width: none; width: 27em; } + input#show_report { + display: block; + float: right; + width: 3em; + } + } + } + + .select2-container { + a { + color: #444; + text-align: left; + text-decoration: none; } } } diff --git a/web/js/select2/LICENSE b/web/js/select2/LICENSE new file mode 100644 index 000000000..3c98f3df4 --- /dev/null +++ b/web/js/select2/LICENSE @@ -0,0 +1,18 @@ +Copyright 2012 Igor Vaynberg + +Version: @@ver@@ Timestamp: @@timestamp@@ + +This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU +General Public License version 2 (the "GPL License"). You may choose either license to govern your +use of this software only upon the condition that you accept all of the terms of either the Apache +License or the GPL License. + +You may obtain a copy of the Apache License and the GPL License at: + +http://www.apache.org/licenses/LICENSE-2.0 +http://www.gnu.org/licenses/gpl-2.0.html + +Unless required by applicable law or agreed to in writing, software distributed under the Apache License +or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +either express or implied. See the Apache License and the GPL License for the specific language governing +permissions and limitations under the Apache License and the GPL License. \ No newline at end of file diff --git a/web/js/select2/bower.json b/web/js/select2/bower.json new file mode 100644 index 000000000..887cbf84d --- /dev/null +++ b/web/js/select2/bower.json @@ -0,0 +1,8 @@ +{ + "name": "select2", + "version": "3.4.5", + "main": ["select2.js", "select2.css", "select2.png", "select2x2.png", "select2-spinner.gif"], + "dependencies": { + "jquery": ">= 1.7.1" + } +} diff --git a/web/js/select2/release.sh b/web/js/select2/release.sh new file mode 100755 index 000000000..92fe0e959 --- /dev/null +++ b/web/js/select2/release.sh @@ -0,0 +1,73 @@ +#!/bin/bash +set -e + +echo -n "Enter the version for this release: " + +read ver + +if [ ! $ver ]; then + echo "Invalid version." + exit +fi + +name="select2" +js="$name.js" +mini="$name.min.js" +css="$name.css" +release="$name-$ver" +tag="$ver" +branch="build-$ver" +curbranch=`git branch | grep "*" | sed "s/* //"` +timestamp=$(date) +tokens="s/@@ver@@/$ver/g;s/\@@timestamp@@/$timestamp/g" +remote="github" + +echo "Pulling from origin" + +git pull + +echo "Updating Version Identifiers" + +sed -E -e "s/\"version\": \"([0-9\.]+)\",/\"version\": \"$ver\",/g" -i "" bower.json select2.jquery.json +git add bower.json +git add select2.jquery.json +git commit -m "modified version identifiers in descriptors for release $ver" +git push + +git branch "$branch" +git checkout "$branch" + +echo "Tokenizing..." + +find . -name "$js" | xargs -I{} sed -e "$tokens" -i "" {} +find . -name "$css" | xargs -I{} sed -e "$tokens" -i "" {} +sed -e "s/latest/$ver/g" -i "" bower.json + +git add "$js" +git add "$css" + +echo "Minifying..." + +echo "/*" > "$mini" +cat LICENSE | sed "$tokens" >> "$mini" +echo "*/" >> "$mini" + +curl -s \ + --data-urlencode "js_code@$js" \ + http://marijnhaverbeke.nl/uglifyjs \ + >> "$mini" + +git add "$mini" + +git commit -m "release $ver" + +echo "Tagging..." +git tag -a "$tag" -m "tagged version $ver" +git push "$remote" --tags + +echo "Cleaning Up..." + +git checkout "$curbranch" +git branch -D "$branch" + +echo "Done" diff --git a/web/js/select2/select2-bootstrap.css b/web/js/select2/select2-bootstrap.css new file mode 100644 index 000000000..909971074 --- /dev/null +++ b/web/js/select2/select2-bootstrap.css @@ -0,0 +1,87 @@ +.form-control .select2-choice { + border: 0; + border-radius: 2px; +} + +.form-control .select2-choice .select2-arrow { + border-radius: 0 2px 2px 0; +} + +.form-control.select2-container { + height: auto !important; + padding: 0px; +} + +.form-control.select2-container.select2-dropdown-open { + border-color: #5897FB; + border-radius: 3px 3px 0 0; +} + +.form-control .select2-container.select2-dropdown-open .select2-choices { + border-radius: 3px 3px 0 0; +} + +.form-control.select2-container .select2-choices { + border: 0 !important; + border-radius: 3px; +} + +.control-group.warning .select2-container .select2-choice, +.control-group.warning .select2-container .select2-choices, +.control-group.warning .select2-container-active .select2-choice, +.control-group.warning .select2-container-active .select2-choices, +.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.warning .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #C09853 !important; +} + +.control-group.warning .select2-container .select2-choice div { + border-left: 1px solid #C09853 !important; + background: #FCF8E3 !important; +} + +.control-group.error .select2-container .select2-choice, +.control-group.error .select2-container .select2-choices, +.control-group.error .select2-container-active .select2-choice, +.control-group.error .select2-container-active .select2-choices, +.control-group.error .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.error .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.error .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #B94A48 !important; +} + +.control-group.error .select2-container .select2-choice div { + border-left: 1px solid #B94A48 !important; + background: #F2DEDE !important; +} + +.control-group.info .select2-container .select2-choice, +.control-group.info .select2-container .select2-choices, +.control-group.info .select2-container-active .select2-choice, +.control-group.info .select2-container-active .select2-choices, +.control-group.info .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.info .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.info .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #3A87AD !important; +} + +.control-group.info .select2-container .select2-choice div { + border-left: 1px solid #3A87AD !important; + background: #D9EDF7 !important; +} + +.control-group.success .select2-container .select2-choice, +.control-group.success .select2-container .select2-choices, +.control-group.success .select2-container-active .select2-choice, +.control-group.success .select2-container-active .select2-choices, +.control-group.success .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.success .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.success .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #468847 !important; +} + +.control-group.success .select2-container .select2-choice div { + border-left: 1px solid #468847 !important; + background: #DFF0D8 !important; +} diff --git a/web/js/select2/select2-spinner.gif b/web/js/select2/select2-spinner.gif new file mode 100644 index 000000000..5b33f7e54 Binary files /dev/null and b/web/js/select2/select2-spinner.gif differ diff --git a/web/js/select2/select2.css b/web/js/select2/select2.css new file mode 100644 index 000000000..2b32ed6f2 --- /dev/null +++ b/web/js/select2/select2.css @@ -0,0 +1,615 @@ +/* +Version: 3.4.5 Timestamp: Mon Nov 4 08:22:42 PST 2013 +*/ +.select2-container { + margin: 0; + position: relative; + display: inline-block; + /* inline-block for ie7 */ + zoom: 1; + *display: inline; + vertical-align: middle; +} + +.select2-container, +.select2-drop, +.select2-search, +.select2-search input { + /* + Force border-box so that % widths fit the parent + container without overlap because of margin/padding. + + More Info : http://www.quirksmode.org/css/box.html + */ + -webkit-box-sizing: border-box; /* webkit */ + -moz-box-sizing: border-box; /* firefox */ + box-sizing: border-box; /* css3 */ +} + +.select2-container .select2-choice { + display: block; + height: 26px; + padding: 0 0 0 8px; + overflow: hidden; + position: relative; + + border: 1px solid #aaa; + white-space: nowrap; + line-height: 26px; + color: #444; + text-decoration: none; + + border-radius: 4px; + + background-clip: padding-box; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + background-color: #fff; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff)); + background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%); + background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0); + background-image: linear-gradient(top, #fff 0%, #eee 50%); +} + +.select2-container.select2-drop-above .select2-choice { + border-bottom-color: #aaa; + + border-radius: 0 0 4px 4px; + + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff)); + background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%); + background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0); + background-image: linear-gradient(top, #eee 0%, #fff 90%); +} + +.select2-container.select2-allowclear .select2-choice .select2-chosen { + margin-right: 42px; +} + +.select2-container .select2-choice > .select2-chosen { + margin-right: 26px; + display: block; + overflow: hidden; + + white-space: nowrap; + + text-overflow: ellipsis; +} + +.select2-container .select2-choice abbr { + display: none; + width: 12px; + height: 12px; + position: absolute; + right: 24px; + top: 8px; + + font-size: 1px; + text-decoration: none; + + border: 0; + background: url('select2.png') right top no-repeat; + cursor: pointer; + outline: 0; +} + +.select2-container.select2-allowclear .select2-choice abbr { + display: inline-block; +} + +.select2-container .select2-choice abbr:hover { + background-position: right -11px; + cursor: pointer; +} + +.select2-drop-mask { + border: 0; + margin: 0; + padding: 0; + position: fixed; + left: 0; + top: 0; + min-height: 100%; + min-width: 100%; + height: auto; + width: auto; + opacity: 0; + z-index: 9998; + /* styles required for IE to work */ + background-color: #fff; + filter: alpha(opacity=0); +} + +.select2-drop { + width: 100%; + margin-top: -1px; + position: absolute; + z-index: 9999; + top: 100%; + + background: #fff; + color: #000; + border: 1px solid #aaa; + border-top: 0; + + border-radius: 0 0 4px 4px; + + -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); + box-shadow: 0 4px 5px rgba(0, 0, 0, .15); +} + +.select2-drop-auto-width { + border-top: 1px solid #aaa; + width: auto; +} + +.select2-drop-auto-width .select2-search { + padding-top: 4px; +} + +.select2-drop.select2-drop-above { + margin-top: 1px; + border-top: 1px solid #aaa; + border-bottom: 0; + + border-radius: 4px 4px 0 0; + + -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); + box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); +} + +.select2-drop-active { + border: 1px solid #5897fb; + border-top: none; +} + +.select2-drop.select2-drop-above.select2-drop-active { + border-top: 1px solid #5897fb; +} + +.select2-container .select2-choice .select2-arrow { + display: inline-block; + width: 18px; + height: 100%; + position: absolute; + right: 0; + top: 0; + + border-left: 1px solid #aaa; + border-radius: 0 4px 4px 0; + + background-clip: padding-box; + + background: #ccc; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee)); + background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%); + background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0); + background-image: linear-gradient(top, #ccc 0%, #eee 60%); +} + +.select2-container .select2-choice .select2-arrow b { + display: block; + width: 100%; + height: 100%; + background: url('select2.png') no-repeat 0 1px; +} + +.select2-search { + display: inline-block; + width: 100%; + min-height: 26px; + margin: 0; + padding-left: 4px; + padding-right: 4px; + + position: relative; + z-index: 10000; + + white-space: nowrap; +} + +.select2-search input { + width: 100%; + height: auto !important; + min-height: 26px; + padding: 4px 20px 4px 5px; + margin: 0; + + outline: 0; + font-family: sans-serif; + font-size: 1em; + + border: 1px solid #aaa; + border-radius: 0; + + -webkit-box-shadow: none; + box-shadow: none; + + background: #fff url('select2.png') no-repeat 100% -22px; + background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); + background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2.png') no-repeat 100% -22px, linear-gradient(top, #fff 85%, #eee 99%); +} + +.select2-drop.select2-drop-above .select2-search input { + margin-top: 4px; +} + +.select2-search input.select2-active { + background: #fff url('select2-spinner.gif') no-repeat 100%; + background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); + background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(top, #fff 85%, #eee 99%); +} + +.select2-container-active .select2-choice, +.select2-container-active .select2-choices { + border: 1px solid #5897fb; + outline: none; + + -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); + box-shadow: 0 0 5px rgba(0, 0, 0, .3); +} + +.select2-dropdown-open .select2-choice { + border-bottom-color: transparent; + -webkit-box-shadow: 0 1px 0 #fff inset; + box-shadow: 0 1px 0 #fff inset; + + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + + background-color: #eee; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee)); + background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%); + background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); + background-image: linear-gradient(top, #fff 0%, #eee 50%); +} + +.select2-dropdown-open.select2-drop-above .select2-choice, +.select2-dropdown-open.select2-drop-above .select2-choices { + border: 1px solid #5897fb; + border-top-color: transparent; + + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee)); + background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%); + background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); + background-image: linear-gradient(bottom, #fff 0%, #eee 50%); +} + +.select2-dropdown-open .select2-choice .select2-arrow { + background: transparent; + border-left: none; + filter: none; +} +.select2-dropdown-open .select2-choice .select2-arrow b { + background-position: -18px 1px; +} + +/* results */ +.select2-results { + max-height: 200px; + padding: 0 0 0 4px; + margin: 4px 4px 4px 0; + position: relative; + overflow-x: hidden; + overflow-y: auto; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.select2-results ul.select2-result-sub { + margin: 0; + padding-left: 0; +} + +.select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px } +.select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px } +.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px } +.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px } +.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px } +.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px } +.select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px } + +.select2-results li { + list-style: none; + display: list-item; + background-image: none; +} + +.select2-results li.select2-result-with-children > .select2-result-label { + font-weight: bold; +} + +.select2-results .select2-result-label { + padding: 3px 7px 4px; + margin: 0; + cursor: pointer; + + min-height: 1em; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.select2-results .select2-highlighted { + background: #3875d7; + color: #fff; +} + +.select2-results li em { + background: #feffde; + font-style: normal; +} + +.select2-results .select2-highlighted em { + background: transparent; +} + +.select2-results .select2-highlighted ul { + background: #fff; + color: #000; +} + + +.select2-results .select2-no-results, +.select2-results .select2-searching, +.select2-results .select2-selection-limit { + background: #f4f4f4; + display: list-item; +} + +/* +disabled look for disabled choices in the results dropdown +*/ +.select2-results .select2-disabled.select2-highlighted { + color: #666; + background: #f4f4f4; + display: list-item; + cursor: default; +} +.select2-results .select2-disabled { + background: #f4f4f4; + display: list-item; + cursor: default; +} + +.select2-results .select2-selected { + display: none; +} + +.select2-more-results.select2-active { + background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%; +} + +.select2-more-results { + background: #f4f4f4; + display: list-item; +} + +/* disabled styles */ + +.select2-container.select2-container-disabled .select2-choice { + background-color: #f4f4f4; + background-image: none; + border: 1px solid #ddd; + cursor: default; +} + +.select2-container.select2-container-disabled .select2-choice .select2-arrow { + background-color: #f4f4f4; + background-image: none; + border-left: 0; +} + +.select2-container.select2-container-disabled .select2-choice abbr { + display: none; +} + + +/* multiselect */ + +.select2-container-multi .select2-choices { + height: auto !important; + height: 1%; + margin: 0; + padding: 0; + position: relative; + + border: 1px solid #aaa; + cursor: text; + overflow: hidden; + + background-color: #fff; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff)); + background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%); + background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%); + background-image: linear-gradient(top, #eee 1%, #fff 15%); +} + +.select2-locked { + padding: 3px 5px 3px 5px !important; +} + +.select2-container-multi .select2-choices { + min-height: 26px; +} + +.select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #5897fb; + outline: none; + + -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); + box-shadow: 0 0 5px rgba(0, 0, 0, .3); +} +.select2-container-multi .select2-choices li { + float: left; + list-style: none; +} +.select2-container-multi .select2-choices .select2-search-field { + margin: 0; + padding: 0; + white-space: nowrap; +} + +.select2-container-multi .select2-choices .select2-search-field input { + padding: 5px; + margin: 1px 0; + + font-family: sans-serif; + font-size: 100%; + color: #666; + outline: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + background: transparent !important; +} + +.select2-container-multi .select2-choices .select2-search-field input.select2-active { + background: #fff url('select2-spinner.gif') no-repeat 100% !important; +} + +.select2-default { + color: #999 !important; +} + +.select2-container-multi .select2-choices .select2-search-choice { + padding: 3px 5px 3px 18px; + margin: 3px 0 3px 5px; + position: relative; + + line-height: 13px; + color: #333; + cursor: default; + border: 1px solid #aaaaaa; + + border-radius: 3px; + + -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); + box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); + + background-clip: padding-box; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + background-color: #e4e4e4; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee)); + background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); + background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); + background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); +} +.select2-container-multi .select2-choices .select2-search-choice .select2-chosen { + cursor: default; +} +.select2-container-multi .select2-choices .select2-search-choice-focus { + background: #d4d4d4; +} + +.select2-search-choice-close { + display: block; + width: 12px; + height: 13px; + position: absolute; + right: 3px; + top: 4px; + + font-size: 1px; + outline: none; + background: url('select2.png') right top no-repeat; +} + +.select2-container-multi .select2-search-choice-close { + left: 3px; +} + +.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { + background-position: right -11px; +} +.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { + background-position: right -11px; +} + +/* disabled styles */ +.select2-container-multi.select2-container-disabled .select2-choices { + background-color: #f4f4f4; + background-image: none; + border: 1px solid #ddd; + cursor: default; +} + +.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice { + padding: 3px 5px 3px 5px; + border: 1px solid #ddd; + background-image: none; + background-color: #f4f4f4; +} + +.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none; + background: none; +} +/* end multiselect */ + + +.select2-result-selectable .select2-match, +.select2-result-unselectable .select2-match { + text-decoration: underline; +} + +.select2-offscreen, .select2-offscreen:focus { + clip: rect(0 0 0 0) !important; + width: 1px !important; + height: 1px !important; + border: 0 !important; + margin: 0 !important; + padding: 0 !important; + overflow: hidden !important; + position: absolute !important; + outline: 0 !important; + left: 0px !important; + top: 0px !important; +} + +.select2-display-none { + display: none; +} + +.select2-measure-scrollbar { + position: absolute; + top: -10000px; + left: -10000px; + width: 100px; + height: 100px; + overflow: scroll; +} +/* Retina-ize icons */ + +@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) { + .select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice .select2-arrow b { + background-image: url('select2x2.png') !important; + background-repeat: no-repeat !important; + background-size: 60px 40px !important; + } + .select2-search input { + background-position: 100% -21px !important; + } +} diff --git a/web/js/select2/select2.jquery.json b/web/js/select2/select2.jquery.json new file mode 100644 index 000000000..b426c7058 --- /dev/null +++ b/web/js/select2/select2.jquery.json @@ -0,0 +1,36 @@ +{ + "name": "select2", + "title": "Select2", + "description": "Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results.", + "keywords": [ + "select", + "autocomplete", + "typeahead", + "dropdown", + "multiselect", + "tag", + "tagging" + ], + "version": "3.4.5", + "author": { + "name": "Igor Vaynberg", + "url": "https://github.com/ivaynberg" + }, + "licenses": [ + { + "type": "Apache", + "url": "http://www.apache.org/licenses/LICENSE-2.0" + }, + { + "type": "GPL v2", + "url": "http://www.gnu.org/licenses/gpl-2.0.html" + } + ], + "bugs": "https://github.com/ivaynberg/select2/issues", + "homepage": "http://ivaynberg.github.com/select2", + "docs": "http://ivaynberg.github.com/select2/", + "download": "https://github.com/ivaynberg/select2/tags", + "dependencies": { + "jquery": ">=1.7.1" + } +} diff --git a/web/js/select2/select2.js b/web/js/select2/select2.js new file mode 100644 index 000000000..3b5e8e280 --- /dev/null +++ b/web/js/select2/select2.js @@ -0,0 +1,3255 @@ +/* +Copyright 2012 Igor Vaynberg + +Version: 3.4.5 Timestamp: Mon Nov 4 08:22:42 PST 2013 + +This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU +General Public License version 2 (the "GPL License"). You may choose either license to govern your +use of this software only upon the condition that you accept all of the terms of either the Apache +License or the GPL License. + +You may obtain a copy of the Apache License and the GPL License at: + + http://www.apache.org/licenses/LICENSE-2.0 + http://www.gnu.org/licenses/gpl-2.0.html + +Unless required by applicable law or agreed to in writing, software distributed under the +Apache License or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the Apache License and the GPL License for +the specific language governing permissions and limitations under the Apache License and the GPL License. +*/ +(function ($) { + if(typeof $.fn.each2 == "undefined") { + $.extend($.fn, { + /* + * 4-10 times faster .each replacement + * use it carefully, as it overrides jQuery context of element on each iteration + */ + each2 : function (c) { + var j = $([0]), i = -1, l = this.length; + while ( + ++i < l + && (j.context = j[0] = this[i]) + && c.call(j[0], i, j) !== false //"this"=DOM, i=index, j=jQuery object + ); + return this; + } + }); + } +})(jQuery); + +(function ($, undefined) { + "use strict"; + /*global document, window, jQuery, console */ + + if (window.Select2 !== undefined) { + return; + } + + var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, nextUid, sizer, + lastMousePosition={x:0,y:0}, $document, scrollBarDimensions, + + KEY = { + TAB: 9, + ENTER: 13, + ESC: 27, + SPACE: 32, + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + SHIFT: 16, + CTRL: 17, + ALT: 18, + PAGE_UP: 33, + PAGE_DOWN: 34, + HOME: 36, + END: 35, + BACKSPACE: 8, + DELETE: 46, + isArrow: function (k) { + k = k.which ? k.which : k; + switch (k) { + case KEY.LEFT: + case KEY.RIGHT: + case KEY.UP: + case KEY.DOWN: + return true; + } + return false; + }, + isControl: function (e) { + var k = e.which; + switch (k) { + case KEY.SHIFT: + case KEY.CTRL: + case KEY.ALT: + return true; + } + + if (e.metaKey) return true; + + return false; + }, + isFunctionKey: function (k) { + k = k.which ? k.which : k; + return k >= 112 && k <= 123; + } + }, + MEASURE_SCROLLBAR_TEMPLATE = "<div class='select2-measure-scrollbar'></div>", + + DIACRITICS = {"\u24B6":"A","\uFF21":"A","\u00C0":"A","\u00C1":"A","\u00C2":"A","\u1EA6":"A","\u1EA4":"A","\u1EAA":"A","\u1EA8":"A","\u00C3":"A","\u0100":"A","\u0102":"A","\u1EB0":"A","\u1EAE":"A","\u1EB4":"A","\u1EB2":"A","\u0226":"A","\u01E0":"A","\u00C4":"A","\u01DE":"A","\u1EA2":"A","\u00C5":"A","\u01FA":"A","\u01CD":"A","\u0200":"A","\u0202":"A","\u1EA0":"A","\u1EAC":"A","\u1EB6":"A","\u1E00":"A","\u0104":"A","\u023A":"A","\u2C6F":"A","\uA732":"AA","\u00C6":"AE","\u01FC":"AE","\u01E2":"AE","\uA734":"AO","\uA736":"AU","\uA738":"AV","\uA73A":"AV","\uA73C":"AY","\u24B7":"B","\uFF22":"B","\u1E02":"B","\u1E04":"B","\u1E06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24B8":"C","\uFF23":"C","\u0106":"C","\u0108":"C","\u010A":"C","\u010C":"C","\u00C7":"C","\u1E08":"C","\u0187":"C","\u023B":"C","\uA73E":"C","\u24B9":"D","\uFF24":"D","\u1E0A":"D","\u010E":"D","\u1E0C":"D","\u1E10":"D","\u1E12":"D","\u1E0E":"D","\u0110":"D","\u018B":"D","\u018A":"D","\u0189":"D","\uA779":"D","\u01F1":"DZ","\u01C4":"DZ","\u01F2":"Dz","\u01C5":"Dz","\u24BA":"E","\uFF25":"E","\u00C8":"E","\u00C9":"E","\u00CA":"E","\u1EC0":"E","\u1EBE":"E","\u1EC4":"E","\u1EC2":"E","\u1EBC":"E","\u0112":"E","\u1E14":"E","\u1E16":"E","\u0114":"E","\u0116":"E","\u00CB":"E","\u1EBA":"E","\u011A":"E","\u0204":"E","\u0206":"E","\u1EB8":"E","\u1EC6":"E","\u0228":"E","\u1E1C":"E","\u0118":"E","\u1E18":"E","\u1E1A":"E","\u0190":"E","\u018E":"E","\u24BB":"F","\uFF26":"F","\u1E1E":"F","\u0191":"F","\uA77B":"F","\u24BC":"G","\uFF27":"G","\u01F4":"G","\u011C":"G","\u1E20":"G","\u011E":"G","\u0120":"G","\u01E6":"G","\u0122":"G","\u01E4":"G","\u0193":"G","\uA7A0":"G","\uA77D":"G","\uA77E":"G","\u24BD":"H","\uFF28":"H","\u0124":"H","\u1E22":"H","\u1E26":"H","\u021E":"H","\u1E24":"H","\u1E28":"H","\u1E2A":"H","\u0126":"H","\u2C67":"H","\u2C75":"H","\uA78D":"H","\u24BE":"I","\uFF29":"I","\u00CC":"I","\u00CD":"I","\u00CE":"I","\u0128":"I","\u012A":"I","\u012C":"I","\u0130":"I","\u00CF":"I","\u1E2E":"I","\u1EC8":"I","\u01CF":"I","\u0208":"I","\u020A":"I","\u1ECA":"I","\u012E":"I","\u1E2C":"I","\u0197":"I","\u24BF":"J","\uFF2A":"J","\u0134":"J","\u0248":"J","\u24C0":"K","\uFF2B":"K","\u1E30":"K","\u01E8":"K","\u1E32":"K","\u0136":"K","\u1E34":"K","\u0198":"K","\u2C69":"K","\uA740":"K","\uA742":"K","\uA744":"K","\uA7A2":"K","\u24C1":"L","\uFF2C":"L","\u013F":"L","\u0139":"L","\u013D":"L","\u1E36":"L","\u1E38":"L","\u013B":"L","\u1E3C":"L","\u1E3A":"L","\u0141":"L","\u023D":"L","\u2C62":"L","\u2C60":"L","\uA748":"L","\uA746":"L","\uA780":"L","\u01C7":"LJ","\u01C8":"Lj","\u24C2":"M","\uFF2D":"M","\u1E3E":"M","\u1E40":"M","\u1E42":"M","\u2C6E":"M","\u019C":"M","\u24C3":"N","\uFF2E":"N","\u01F8":"N","\u0143":"N","\u00D1":"N","\u1E44":"N","\u0147":"N","\u1E46":"N","\u0145":"N","\u1E4A":"N","\u1E48":"N","\u0220":"N","\u019D":"N","\uA790":"N","\uA7A4":"N","\u01CA":"NJ","\u01CB":"Nj","\u24C4":"O","\uFF2F":"O","\u00D2":"O","\u00D3":"O","\u00D4":"O","\u1ED2":"O","\u1ED0":"O","\u1ED6":"O","\u1ED4":"O","\u00D5":"O","\u1E4C":"O","\u022C":"O","\u1E4E":"O","\u014C":"O","\u1E50":"O","\u1E52":"O","\u014E":"O","\u022E":"O","\u0230":"O","\u00D6":"O","\u022A":"O","\u1ECE":"O","\u0150":"O","\u01D1":"O","\u020C":"O","\u020E":"O","\u01A0":"O","\u1EDC":"O","\u1EDA":"O","\u1EE0":"O","\u1EDE":"O","\u1EE2":"O","\u1ECC":"O","\u1ED8":"O","\u01EA":"O","\u01EC":"O","\u00D8":"O","\u01FE":"O","\u0186":"O","\u019F":"O","\uA74A":"O","\uA74C":"O","\u01A2":"OI","\uA74E":"OO","\u0222":"OU","\u24C5":"P","\uFF30":"P","\u1E54":"P","\u1E56":"P","\u01A4":"P","\u2C63":"P","\uA750":"P","\uA752":"P","\uA754":"P","\u24C6":"Q","\uFF31":"Q","\uA756":"Q","\uA758":"Q","\u024A":"Q","\u24C7":"R","\uFF32":"R","\u0154":"R","\u1E58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1E5A":"R","\u1E5C":"R","\u0156":"R","\u1E5E":"R","\u024C":"R","\u2C64":"R","\uA75A":"R","\uA7A6":"R","\uA782":"R","\u24C8":"S","\uFF33":"S","\u1E9E":"S","\u015A":"S","\u1E64":"S","\u015C":"S","\u1E60":"S","\u0160":"S","\u1E66":"S","\u1E62":"S","\u1E68":"S","\u0218":"S","\u015E":"S","\u2C7E":"S","\uA7A8":"S","\uA784":"S","\u24C9":"T","\uFF34":"T","\u1E6A":"T","\u0164":"T","\u1E6C":"T","\u021A":"T","\u0162":"T","\u1E70":"T","\u1E6E":"T","\u0166":"T","\u01AC":"T","\u01AE":"T","\u023E":"T","\uA786":"T","\uA728":"TZ","\u24CA":"U","\uFF35":"U","\u00D9":"U","\u00DA":"U","\u00DB":"U","\u0168":"U","\u1E78":"U","\u016A":"U","\u1E7A":"U","\u016C":"U","\u00DC":"U","\u01DB":"U","\u01D7":"U","\u01D5":"U","\u01D9":"U","\u1EE6":"U","\u016E":"U","\u0170":"U","\u01D3":"U","\u0214":"U","\u0216":"U","\u01AF":"U","\u1EEA":"U","\u1EE8":"U","\u1EEE":"U","\u1EEC":"U","\u1EF0":"U","\u1EE4":"U","\u1E72":"U","\u0172":"U","\u1E76":"U","\u1E74":"U","\u0244":"U","\u24CB":"V","\uFF36":"V","\u1E7C":"V","\u1E7E":"V","\u01B2":"V","\uA75E":"V","\u0245":"V","\uA760":"VY","\u24CC":"W","\uFF37":"W","\u1E80":"W","\u1E82":"W","\u0174":"W","\u1E86":"W","\u1E84":"W","\u1E88":"W","\u2C72":"W","\u24CD":"X","\uFF38":"X","\u1E8A":"X","\u1E8C":"X","\u24CE":"Y","\uFF39":"Y","\u1EF2":"Y","\u00DD":"Y","\u0176":"Y","\u1EF8":"Y","\u0232":"Y","\u1E8E":"Y","\u0178":"Y","\u1EF6":"Y","\u1EF4":"Y","\u01B3":"Y","\u024E":"Y","\u1EFE":"Y","\u24CF":"Z","\uFF3A":"Z","\u0179":"Z","\u1E90":"Z","\u017B":"Z","\u017D":"Z","\u1E92":"Z","\u1E94":"Z","\u01B5":"Z","\u0224":"Z","\u2C7F":"Z","\u2C6B":"Z","\uA762":"Z","\u24D0":"a","\uFF41":"a","\u1E9A":"a","\u00E0":"a","\u00E1":"a","\u00E2":"a","\u1EA7":"a","\u1EA5":"a","\u1EAB":"a","\u1EA9":"a","\u00E3":"a","\u0101":"a","\u0103":"a","\u1EB1":"a","\u1EAF":"a","\u1EB5":"a","\u1EB3":"a","\u0227":"a","\u01E1":"a","\u00E4":"a","\u01DF":"a","\u1EA3":"a","\u00E5":"a","\u01FB":"a","\u01CE":"a","\u0201":"a","\u0203":"a","\u1EA1":"a","\u1EAD":"a","\u1EB7":"a","\u1E01":"a","\u0105":"a","\u2C65":"a","\u0250":"a","\uA733":"aa","\u00E6":"ae","\u01FD":"ae","\u01E3":"ae","\uA735":"ao","\uA737":"au","\uA739":"av","\uA73B":"av","\uA73D":"ay","\u24D1":"b","\uFF42":"b","\u1E03":"b","\u1E05":"b","\u1E07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24D2":"c","\uFF43":"c","\u0107":"c","\u0109":"c","\u010B":"c","\u010D":"c","\u00E7":"c","\u1E09":"c","\u0188":"c","\u023C":"c","\uA73F":"c","\u2184":"c","\u24D3":"d","\uFF44":"d","\u1E0B":"d","\u010F":"d","\u1E0D":"d","\u1E11":"d","\u1E13":"d","\u1E0F":"d","\u0111":"d","\u018C":"d","\u0256":"d","\u0257":"d","\uA77A":"d","\u01F3":"dz","\u01C6":"dz","\u24D4":"e","\uFF45":"e","\u00E8":"e","\u00E9":"e","\u00EA":"e","\u1EC1":"e","\u1EBF":"e","\u1EC5":"e","\u1EC3":"e","\u1EBD":"e","\u0113":"e","\u1E15":"e","\u1E17":"e","\u0115":"e","\u0117":"e","\u00EB":"e","\u1EBB":"e","\u011B":"e","\u0205":"e","\u0207":"e","\u1EB9":"e","\u1EC7":"e","\u0229":"e","\u1E1D":"e","\u0119":"e","\u1E19":"e","\u1E1B":"e","\u0247":"e","\u025B":"e","\u01DD":"e","\u24D5":"f","\uFF46":"f","\u1E1F":"f","\u0192":"f","\uA77C":"f","\u24D6":"g","\uFF47":"g","\u01F5":"g","\u011D":"g","\u1E21":"g","\u011F":"g","\u0121":"g","\u01E7":"g","\u0123":"g","\u01E5":"g","\u0260":"g","\uA7A1":"g","\u1D79":"g","\uA77F":"g","\u24D7":"h","\uFF48":"h","\u0125":"h","\u1E23":"h","\u1E27":"h","\u021F":"h","\u1E25":"h","\u1E29":"h","\u1E2B":"h","\u1E96":"h","\u0127":"h","\u2C68":"h","\u2C76":"h","\u0265":"h","\u0195":"hv","\u24D8":"i","\uFF49":"i","\u00EC":"i","\u00ED":"i","\u00EE":"i","\u0129":"i","\u012B":"i","\u012D":"i","\u00EF":"i","\u1E2F":"i","\u1EC9":"i","\u01D0":"i","\u0209":"i","\u020B":"i","\u1ECB":"i","\u012F":"i","\u1E2D":"i","\u0268":"i","\u0131":"i","\u24D9":"j","\uFF4A":"j","\u0135":"j","\u01F0":"j","\u0249":"j","\u24DA":"k","\uFF4B":"k","\u1E31":"k","\u01E9":"k","\u1E33":"k","\u0137":"k","\u1E35":"k","\u0199":"k","\u2C6A":"k","\uA741":"k","\uA743":"k","\uA745":"k","\uA7A3":"k","\u24DB":"l","\uFF4C":"l","\u0140":"l","\u013A":"l","\u013E":"l","\u1E37":"l","\u1E39":"l","\u013C":"l","\u1E3D":"l","\u1E3B":"l","\u017F":"l","\u0142":"l","\u019A":"l","\u026B":"l","\u2C61":"l","\uA749":"l","\uA781":"l","\uA747":"l","\u01C9":"lj","\u24DC":"m","\uFF4D":"m","\u1E3F":"m","\u1E41":"m","\u1E43":"m","\u0271":"m","\u026F":"m","\u24DD":"n","\uFF4E":"n","\u01F9":"n","\u0144":"n","\u00F1":"n","\u1E45":"n","\u0148":"n","\u1E47":"n","\u0146":"n","\u1E4B":"n","\u1E49":"n","\u019E":"n","\u0272":"n","\u0149":"n","\uA791":"n","\uA7A5":"n","\u01CC":"nj","\u24DE":"o","\uFF4F":"o","\u00F2":"o","\u00F3":"o","\u00F4":"o","\u1ED3":"o","\u1ED1":"o","\u1ED7":"o","\u1ED5":"o","\u00F5":"o","\u1E4D":"o","\u022D":"o","\u1E4F":"o","\u014D":"o","\u1E51":"o","\u1E53":"o","\u014F":"o","\u022F":"o","\u0231":"o","\u00F6":"o","\u022B":"o","\u1ECF":"o","\u0151":"o","\u01D2":"o","\u020D":"o","\u020F":"o","\u01A1":"o","\u1EDD":"o","\u1EDB":"o","\u1EE1":"o","\u1EDF":"o","\u1EE3":"o","\u1ECD":"o","\u1ED9":"o","\u01EB":"o","\u01ED":"o","\u00F8":"o","\u01FF":"o","\u0254":"o","\uA74B":"o","\uA74D":"o","\u0275":"o","\u01A3":"oi","\u0223":"ou","\uA74F":"oo","\u24DF":"p","\uFF50":"p","\u1E55":"p","\u1E57":"p","\u01A5":"p","\u1D7D":"p","\uA751":"p","\uA753":"p","\uA755":"p","\u24E0":"q","\uFF51":"q","\u024B":"q","\uA757":"q","\uA759":"q","\u24E1":"r","\uFF52":"r","\u0155":"r","\u1E59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1E5B":"r","\u1E5D":"r","\u0157":"r","\u1E5F":"r","\u024D":"r","\u027D":"r","\uA75B":"r","\uA7A7":"r","\uA783":"r","\u24E2":"s","\uFF53":"s","\u00DF":"s","\u015B":"s","\u1E65":"s","\u015D":"s","\u1E61":"s","\u0161":"s","\u1E67":"s","\u1E63":"s","\u1E69":"s","\u0219":"s","\u015F":"s","\u023F":"s","\uA7A9":"s","\uA785":"s","\u1E9B":"s","\u24E3":"t","\uFF54":"t","\u1E6B":"t","\u1E97":"t","\u0165":"t","\u1E6D":"t","\u021B":"t","\u0163":"t","\u1E71":"t","\u1E6F":"t","\u0167":"t","\u01AD":"t","\u0288":"t","\u2C66":"t","\uA787":"t","\uA729":"tz","\u24E4":"u","\uFF55":"u","\u00F9":"u","\u00FA":"u","\u00FB":"u","\u0169":"u","\u1E79":"u","\u016B":"u","\u1E7B":"u","\u016D":"u","\u00FC":"u","\u01DC":"u","\u01D8":"u","\u01D6":"u","\u01DA":"u","\u1EE7":"u","\u016F":"u","\u0171":"u","\u01D4":"u","\u0215":"u","\u0217":"u","\u01B0":"u","\u1EEB":"u","\u1EE9":"u","\u1EEF":"u","\u1EED":"u","\u1EF1":"u","\u1EE5":"u","\u1E73":"u","\u0173":"u","\u1E77":"u","\u1E75":"u","\u0289":"u","\u24E5":"v","\uFF56":"v","\u1E7D":"v","\u1E7F":"v","\u028B":"v","\uA75F":"v","\u028C":"v","\uA761":"vy","\u24E6":"w","\uFF57":"w","\u1E81":"w","\u1E83":"w","\u0175":"w","\u1E87":"w","\u1E85":"w","\u1E98":"w","\u1E89":"w","\u2C73":"w","\u24E7":"x","\uFF58":"x","\u1E8B":"x","\u1E8D":"x","\u24E8":"y","\uFF59":"y","\u1EF3":"y","\u00FD":"y","\u0177":"y","\u1EF9":"y","\u0233":"y","\u1E8F":"y","\u00FF":"y","\u1EF7":"y","\u1E99":"y","\u1EF5":"y","\u01B4":"y","\u024F":"y","\u1EFF":"y","\u24E9":"z","\uFF5A":"z","\u017A":"z","\u1E91":"z","\u017C":"z","\u017E":"z","\u1E93":"z","\u1E95":"z","\u01B6":"z","\u0225":"z","\u0240":"z","\u2C6C":"z","\uA763":"z"}; + + $document = $(document); + + nextUid=(function() { var counter=1; return function() { return counter++; }; }()); + + + function stripDiacritics(str) { + var ret, i, l, c; + + if (!str || str.length < 1) return str; + + ret = ""; + for (i = 0, l = str.length; i < l; i++) { + c = str.charAt(i); + ret += DIACRITICS[c] || c; + } + return ret; + } + + function indexOf(value, array) { + var i = 0, l = array.length; + for (; i < l; i = i + 1) { + if (equal(value, array[i])) return i; + } + return -1; + } + + function measureScrollbar () { + var $template = $( MEASURE_SCROLLBAR_TEMPLATE ); + $template.appendTo('body'); + + var dim = { + width: $template.width() - $template[0].clientWidth, + height: $template.height() - $template[0].clientHeight + }; + $template.remove(); + + return dim; + } + + /** + * Compares equality of a and b + * @param a + * @param b + */ + function equal(a, b) { + if (a === b) return true; + if (a === undefined || b === undefined) return false; + if (a === null || b === null) return false; + // Check whether 'a' or 'b' is a string (primitive or object). + // The concatenation of an empty string (+'') converts its argument to a string's primitive. + if (a.constructor === String) return a+'' === b+''; // a+'' - in case 'a' is a String object + if (b.constructor === String) return b+'' === a+''; // b+'' - in case 'b' is a String object + return false; + } + + /** + * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty + * strings + * @param string + * @param separator + */ + function splitVal(string, separator) { + var val, i, l; + if (string === null || string.length < 1) return []; + val = string.split(separator); + for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]); + return val; + } + + function getSideBorderPadding(element) { + return element.outerWidth(false) - element.width(); + } + + function installKeyUpChangeEvent(element) { + var key="keyup-change-value"; + element.on("keydown", function () { + if ($.data(element, key) === undefined) { + $.data(element, key, element.val()); + } + }); + element.on("keyup", function () { + var val= $.data(element, key); + if (val !== undefined && element.val() !== val) { + $.removeData(element, key); + element.trigger("keyup-change"); + } + }); + } + + $document.on("mousemove", function (e) { + lastMousePosition.x = e.pageX; + lastMousePosition.y = e.pageY; + }); + + /** + * filters mouse events so an event is fired only if the mouse moved. + * + * filters out mouse events that occur when mouse is stationary but + * the elements under the pointer are scrolled. + */ + function installFilteredMouseMove(element) { + element.on("mousemove", function (e) { + var lastpos = lastMousePosition; + if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) { + $(e.target).trigger("mousemove-filtered", e); + } + }); + } + + /** + * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made + * within the last quietMillis milliseconds. + * + * @param quietMillis number of milliseconds to wait before invoking fn + * @param fn function to be debounced + * @param ctx object to be used as this reference within fn + * @return debounced version of fn + */ + function debounce(quietMillis, fn, ctx) { + ctx = ctx || undefined; + var timeout; + return function () { + var args = arguments; + window.clearTimeout(timeout); + timeout = window.setTimeout(function() { + fn.apply(ctx, args); + }, quietMillis); + }; + } + + /** + * A simple implementation of a thunk + * @param formula function used to lazily initialize the thunk + * @return {Function} + */ + function thunk(formula) { + var evaluated = false, + value; + return function() { + if (evaluated === false) { value = formula(); evaluated = true; } + return value; + }; + }; + + function installDebouncedScroll(threshold, element) { + var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);}); + element.on("scroll", function (e) { + if (indexOf(e.target, element.get()) >= 0) notify(e); + }); + } + + function focus($el) { + if ($el[0] === document.activeElement) return; + + /* set the focus in a 0 timeout - that way the focus is set after the processing + of the current event has finished - which seems like the only reliable way + to set focus */ + window.setTimeout(function() { + var el=$el[0], pos=$el.val().length, range; + + $el.focus(); + + /* make sure el received focus so we do not error out when trying to manipulate the caret. + sometimes modals or others listeners may steal it after its set */ + if ($el.is(":visible") && el === document.activeElement) { + + /* after the focus is set move the caret to the end, necessary when we val() + just before setting focus */ + if(el.setSelectionRange) + { + el.setSelectionRange(pos, pos); + } + else if (el.createTextRange) { + range = el.createTextRange(); + range.collapse(false); + range.select(); + } + } + }, 0); + } + + function getCursorInfo(el) { + el = $(el)[0]; + var offset = 0; + var length = 0; + if ('selectionStart' in el) { + offset = el.selectionStart; + length = el.selectionEnd - offset; + } else if ('selection' in document) { + el.focus(); + var sel = document.selection.createRange(); + length = document.selection.createRange().text.length; + sel.moveStart('character', -el.value.length); + offset = sel.text.length - length; + } + return { offset: offset, length: length }; + } + + function killEvent(event) { + event.preventDefault(); + event.stopPropagation(); + } + function killEventImmediately(event) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + + function measureTextWidth(e) { + if (!sizer){ + var style = e[0].currentStyle || window.getComputedStyle(e[0], null); + sizer = $(document.createElement("div")).css({ + position: "absolute", + left: "-10000px", + top: "-10000px", + display: "none", + fontSize: style.fontSize, + fontFamily: style.fontFamily, + fontStyle: style.fontStyle, + fontWeight: style.fontWeight, + letterSpacing: style.letterSpacing, + textTransform: style.textTransform, + whiteSpace: "nowrap" + }); + sizer.attr("class","select2-sizer"); + $("body").append(sizer); + } + sizer.text(e.val()); + return sizer.width(); + } + + function syncCssClasses(dest, src, adapter) { + var classes, replacements = [], adapted; + + classes = dest.attr("class"); + if (classes) { + classes = '' + classes; // for IE which returns object + $(classes.split(" ")).each2(function() { + if (this.indexOf("select2-") === 0) { + replacements.push(this); + } + }); + } + classes = src.attr("class"); + if (classes) { + classes = '' + classes; // for IE which returns object + $(classes.split(" ")).each2(function() { + if (this.indexOf("select2-") !== 0) { + adapted = adapter(this); + if (adapted) { + replacements.push(adapted); + } + } + }); + } + dest.attr("class", replacements.join(" ")); + } + + + function markMatch(text, term, markup, escapeMarkup) { + var match=stripDiacritics(text.toUpperCase()).indexOf(stripDiacritics(term.toUpperCase())), + tl=term.length; + + if (match<0) { + markup.push(escapeMarkup(text)); + return; + } + + markup.push(escapeMarkup(text.substring(0, match))); + markup.push("<span class='select2-match'>"); + markup.push(escapeMarkup(text.substring(match, match + tl))); + markup.push("</span>"); + markup.push(escapeMarkup(text.substring(match + tl, text.length))); + } + + function defaultEscapeMarkup(markup) { + var replace_map = { + '\\': '\', + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + "/": '/' + }; + + return String(markup).replace(/[&<>"'\/\\]/g, function (match) { + return replace_map[match]; + }); + } + + /** + * Produces an ajax-based query function + * + * @param options object containing configuration paramters + * @param options.params parameter map for the transport ajax call, can contain such options as cache, jsonpCallback, etc. see $.ajax + * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax + * @param options.url url for the data + * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url. + * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified + * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often + * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2. + * The expected format is an object containing the following keys: + * results array of objects that will be used as choices + * more (optional) boolean indicating whether there are more results available + * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true} + */ + function ajax(options) { + var timeout, // current scheduled but not yet executed request + handler = null, + quietMillis = options.quietMillis || 100, + ajaxUrl = options.url, + self = this; + + return function (query) { + window.clearTimeout(timeout); + timeout = window.setTimeout(function () { + var data = options.data, // ajax data function + url = ajaxUrl, // ajax url string or function + transport = options.transport || $.fn.select2.ajaxDefaults.transport, + // deprecated - to be removed in 4.0 - use params instead + deprecated = { + type: options.type || 'GET', // set type of request (GET or POST) + cache: options.cache || false, + jsonpCallback: options.jsonpCallback||undefined, + dataType: options.dataType||"json" + }, + params = $.extend({}, $.fn.select2.ajaxDefaults.params, deprecated); + + data = data ? data.call(self, query.term, query.page, query.context) : null; + url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url; + + if (handler) { handler.abort(); } + + if (options.params) { + if ($.isFunction(options.params)) { + $.extend(params, options.params.call(self)); + } else { + $.extend(params, options.params); + } + } + + $.extend(params, { + url: url, + dataType: options.dataType, + data: data, + success: function (data) { + // TODO - replace query.page with query so users have access to term, page, etc. + var results = options.results(data, query.page); + query.callback(results); + } + }); + handler = transport.call(self, params); + }, quietMillis); + }; + } + + /** + * Produces a query function that works with a local array + * + * @param options object containing configuration parameters. The options parameter can either be an array or an + * object. + * + * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys. + * + * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain + * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text' + * key can either be a String in which case it is expected that each element in the 'data' array has a key with the + * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract + * the text. + */ + function local(options) { + var data = options, // data elements + dataText, + tmp, + text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search + + if ($.isArray(data)) { + tmp = data; + data = { results: tmp }; + } + + if ($.isFunction(data) === false) { + tmp = data; + data = function() { return tmp; }; + } + + var dataItem = data(); + if (dataItem.text) { + text = dataItem.text; + // if text is not a function we assume it to be a key name + if (!$.isFunction(text)) { + dataText = dataItem.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available + text = function (item) { return item[dataText]; }; + } + } + + return function (query) { + var t = query.term, filtered = { results: [] }, process; + if (t === "") { + query.callback(data()); + return; + } + + process = function(datum, collection) { + var group, attr; + datum = datum[0]; + if (datum.children) { + group = {}; + for (attr in datum) { + if (datum.hasOwnProperty(attr)) group[attr]=datum[attr]; + } + group.children=[]; + $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); }); + if (group.children.length || query.matcher(t, text(group), datum)) { + collection.push(group); + } + } else { + if (query.matcher(t, text(datum), datum)) { + collection.push(datum); + } + } + }; + + $(data().results).each2(function(i, datum) { process(datum, filtered.results); }); + query.callback(filtered); + }; + } + + // TODO javadoc + function tags(data) { + var isFunc = $.isFunction(data); + return function (query) { + var t = query.term, filtered = {results: []}; + $(isFunc ? data() : data).each(function () { + var isObject = this.text !== undefined, + text = isObject ? this.text : this; + if (t === "" || query.matcher(t, text)) { + filtered.results.push(isObject ? this : {id: this, text: this}); + } + }); + query.callback(filtered); + }; + } + + /** + * Checks if the formatter function should be used. + * + * Throws an error if it is not a function. Returns true if it should be used, + * false if no formatting should be performed. + * + * @param formatter + */ + function checkFormatter(formatter, formatterName) { + if ($.isFunction(formatter)) return true; + if (!formatter) return false; + throw new Error(formatterName +" must be a function or a falsy value"); + } + + function evaluate(val) { + return $.isFunction(val) ? val() : val; + } + + function countResults(results) { + var count = 0; + $.each(results, function(i, item) { + if (item.children) { + count += countResults(item.children); + } else { + count++; + } + }); + return count; + } + + /** + * Default tokenizer. This function uses breaks the input on substring match of any string from the + * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those + * two options have to be defined in order for the tokenizer to work. + * + * @param input text user has typed so far or pasted into the search field + * @param selection currently selected choices + * @param selectCallback function(choice) callback tho add the choice to selection + * @param opts select2's opts + * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value + */ + function defaultTokenizer(input, selection, selectCallback, opts) { + var original = input, // store the original so we can compare and know if we need to tell the search to update its text + dupe = false, // check for whether a token we extracted represents a duplicate selected choice + token, // token + index, // position at which the separator was found + i, l, // looping variables + separator; // the matched separator + + if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined; + + while (true) { + index = -1; + + for (i = 0, l = opts.tokenSeparators.length; i < l; i++) { + separator = opts.tokenSeparators[i]; + index = input.indexOf(separator); + if (index >= 0) break; + } + + if (index < 0) break; // did not find any token separator in the input string, bail + + token = input.substring(0, index); + input = input.substring(index + separator.length); + + if (token.length > 0) { + token = opts.createSearchChoice.call(this, token, selection); + if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) { + dupe = false; + for (i = 0, l = selection.length; i < l; i++) { + if (equal(opts.id(token), opts.id(selection[i]))) { + dupe = true; break; + } + } + + if (!dupe) selectCallback(token); + } + } + } + + if (original!==input) return input; + } + + /** + * Creates a new class + * + * @param superClass + * @param methods + */ + function clazz(SuperClass, methods) { + var constructor = function () {}; + constructor.prototype = new SuperClass; + constructor.prototype.constructor = constructor; + constructor.prototype.parent = SuperClass.prototype; + constructor.prototype = $.extend(constructor.prototype, methods); + return constructor; + } + + AbstractSelect2 = clazz(Object, { + + // abstract + bind: function (func) { + var self = this; + return function () { + func.apply(self, arguments); + }; + }, + + // abstract + init: function (opts) { + var results, search, resultsSelector = ".select2-results"; + + // prepare options + this.opts = opts = this.prepareOpts(opts); + + this.id=opts.id; + + // destroy if called on an existing component + if (opts.element.data("select2") !== undefined && + opts.element.data("select2") !== null) { + opts.element.data("select2").destroy(); + } + + this.container = this.createContainer(); + + this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid()); + this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1'); + this.container.attr("id", this.containerId); + + // cache the body so future lookups are cheap + this.body = thunk(function() { return opts.element.closest("body"); }); + + syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass); + + this.container.attr("style", opts.element.attr("style")); + this.container.css(evaluate(opts.containerCss)); + this.container.addClass(evaluate(opts.containerCssClass)); + + this.elementTabIndex = this.opts.element.attr("tabindex"); + + // swap container for the element + this.opts.element + .data("select2", this) + .attr("tabindex", "-1") + .before(this.container) + .on("click.select2", killEvent); // do not leak click events + + this.container.data("select2", this); + + this.dropdown = this.container.find(".select2-drop"); + + syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass); + + this.dropdown.addClass(evaluate(opts.dropdownCssClass)); + this.dropdown.data("select2", this); + this.dropdown.on("click", killEvent); + + this.results = results = this.container.find(resultsSelector); + this.search = search = this.container.find("input.select2-input"); + + this.queryCount = 0; + this.resultsPage = 0; + this.context = null; + + // initialize the container + this.initContainer(); + + this.container.on("click", killEvent); + + installFilteredMouseMove(this.results); + this.dropdown.on("mousemove-filtered touchstart touchmove touchend", resultsSelector, this.bind(this.highlightUnderEvent)); + + installDebouncedScroll(80, this.results); + this.dropdown.on("scroll-debounced", resultsSelector, this.bind(this.loadMoreIfNeeded)); + + // do not propagate change event from the search field out of the component + $(this.container).on("change", ".select2-input", function(e) {e.stopPropagation();}); + $(this.dropdown).on("change", ".select2-input", function(e) {e.stopPropagation();}); + + // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel + if ($.fn.mousewheel) { + results.mousewheel(function (e, delta, deltaX, deltaY) { + var top = results.scrollTop(); + if (deltaY > 0 && top - deltaY <= 0) { + results.scrollTop(0); + killEvent(e); + } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) { + results.scrollTop(results.get(0).scrollHeight - results.height()); + killEvent(e); + } + }); + } + + installKeyUpChangeEvent(search); + search.on("keyup-change input paste", this.bind(this.updateResults)); + search.on("focus", function () { search.addClass("select2-focused"); }); + search.on("blur", function () { search.removeClass("select2-focused");}); + + this.dropdown.on("mouseup", resultsSelector, this.bind(function (e) { + if ($(e.target).closest(".select2-result-selectable").length > 0) { + this.highlightUnderEvent(e); + this.selectHighlighted(e); + } + })); + + // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening + // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's + // dom it will trigger the popup close, which is not what we want + this.dropdown.on("click mouseup mousedown", function (e) { e.stopPropagation(); }); + + if ($.isFunction(this.opts.initSelection)) { + // initialize selection based on the current value of the source element + this.initSelection(); + + // if the user has provided a function that can set selection based on the value of the source element + // we monitor the change event on the element and trigger it, allowing for two way synchronization + this.monitorSource(); + } + + if (opts.maximumInputLength !== null) { + this.search.attr("maxlength", opts.maximumInputLength); + } + + var disabled = opts.element.prop("disabled"); + if (disabled === undefined) disabled = false; + this.enable(!disabled); + + var readonly = opts.element.prop("readonly"); + if (readonly === undefined) readonly = false; + this.readonly(readonly); + + // Calculate size of scrollbar + scrollBarDimensions = scrollBarDimensions || measureScrollbar(); + + this.autofocus = opts.element.prop("autofocus"); + opts.element.prop("autofocus", false); + if (this.autofocus) this.focus(); + + this.nextSearchTerm = undefined; + }, + + // abstract + destroy: function () { + var element=this.opts.element, select2 = element.data("select2"); + + this.close(); + + if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; } + + if (select2 !== undefined) { + select2.container.remove(); + select2.dropdown.remove(); + element + .removeClass("select2-offscreen") + .removeData("select2") + .off(".select2") + .prop("autofocus", this.autofocus || false); + if (this.elementTabIndex) { + element.attr({tabindex: this.elementTabIndex}); + } else { + element.removeAttr("tabindex"); + } + element.show(); + } + }, + + // abstract + optionToData: function(element) { + if (element.is("option")) { + return { + id:element.prop("value"), + text:element.text(), + element: element.get(), + css: element.attr("class"), + disabled: element.prop("disabled"), + locked: equal(element.attr("locked"), "locked") || equal(element.data("locked"), true) + }; + } else if (element.is("optgroup")) { + return { + text:element.attr("label"), + children:[], + element: element.get(), + css: element.attr("class") + }; + } + }, + + // abstract + prepareOpts: function (opts) { + var element, select, idKey, ajaxUrl, self = this; + + element = opts.element; + + if (element.get(0).tagName.toLowerCase() === "select") { + this.select = select = opts.element; + } + + if (select) { + // these options are not allowed when attached to a select because they are picked up off the element itself + $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () { + if (this in opts) { + throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element."); + } + }); + } + + opts = $.extend({}, { + populateResults: function(container, results, query) { + var populate, id=this.opts.id; + + populate=function(results, container, depth) { + + var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted; + + results = opts.sortResults(results, container, query); + + for (i = 0, l = results.length; i < l; i = i + 1) { + + result=results[i]; + + disabled = (result.disabled === true); + selectable = (!disabled) && (id(result) !== undefined); + + compound=result.children && result.children.length > 0; + + node=$("<li></li>"); + node.addClass("select2-results-dept-"+depth); + node.addClass("select2-result"); + node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable"); + if (disabled) { node.addClass("select2-disabled"); } + if (compound) { node.addClass("select2-result-with-children"); } + node.addClass(self.opts.formatResultCssClass(result)); + + label=$(document.createElement("div")); + label.addClass("select2-result-label"); + + formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup); + if (formatted!==undefined) { + label.html(formatted); + } + + node.append(label); + + if (compound) { + + innerContainer=$("<ul></ul>"); + innerContainer.addClass("select2-result-sub"); + populate(result.children, innerContainer, depth+1); + node.append(innerContainer); + } + + node.data("select2-data", result); + container.append(node); + } + }; + + populate(results, container, 0); + } + }, $.fn.select2.defaults, opts); + + if (typeof(opts.id) !== "function") { + idKey = opts.id; + opts.id = function (e) { return e[idKey]; }; + } + + if ($.isArray(opts.element.data("select2Tags"))) { + if ("tags" in opts) { + throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id"); + } + opts.tags=opts.element.data("select2Tags"); + } + + if (select) { + opts.query = this.bind(function (query) { + var data = { results: [], more: false }, + term = query.term, + children, placeholderOption, process; + + process=function(element, collection) { + var group; + if (element.is("option")) { + if (query.matcher(term, element.text(), element)) { + collection.push(self.optionToData(element)); + } + } else if (element.is("optgroup")) { + group=self.optionToData(element); + element.children().each2(function(i, elm) { process(elm, group.children); }); + if (group.children.length>0) { + collection.push(group); + } + } + }; + + children=element.children(); + + // ignore the placeholder option if there is one + if (this.getPlaceholder() !== undefined && children.length > 0) { + placeholderOption = this.getPlaceholderOption(); + if (placeholderOption) { + children=children.not(placeholderOption); + } + } + + children.each2(function(i, elm) { process(elm, data.results); }); + + query.callback(data); + }); + // this is needed because inside val() we construct choices from options and there id is hardcoded + opts.id=function(e) { return e.id; }; + opts.formatResultCssClass = function(data) { return data.css; }; + } else { + if (!("query" in opts)) { + + if ("ajax" in opts) { + ajaxUrl = opts.element.data("ajax-url"); + if (ajaxUrl && ajaxUrl.length > 0) { + opts.ajax.url = ajaxUrl; + } + opts.query = ajax.call(opts.element, opts.ajax); + } else if ("data" in opts) { + opts.query = local(opts.data); + } else if ("tags" in opts) { + opts.query = tags(opts.tags); + if (opts.createSearchChoice === undefined) { + opts.createSearchChoice = function (term) { return {id: $.trim(term), text: $.trim(term)}; }; + } + if (opts.initSelection === undefined) { + opts.initSelection = function (element, callback) { + var data = []; + $(splitVal(element.val(), opts.separator)).each(function () { + var obj = { id: this, text: this }, + tags = opts.tags; + if ($.isFunction(tags)) tags=tags(); + $(tags).each(function() { if (equal(this.id, obj.id)) { obj = this; return false; } }); + data.push(obj); + }); + + callback(data); + }; + } + } + } + } + if (typeof(opts.query) !== "function") { + throw "query function not defined for Select2 " + opts.element.attr("id"); + } + + return opts; + }, + + /** + * Monitor the original element for changes and update select2 accordingly + */ + // abstract + monitorSource: function () { + var el = this.opts.element, sync, observer; + + el.on("change.select2", this.bind(function (e) { + if (this.opts.element.data("select2-change-triggered") !== true) { + this.initSelection(); + } + })); + + sync = this.bind(function () { + + // sync enabled state + var disabled = el.prop("disabled"); + if (disabled === undefined) disabled = false; + this.enable(!disabled); + + var readonly = el.prop("readonly"); + if (readonly === undefined) readonly = false; + this.readonly(readonly); + + syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass); + this.container.addClass(evaluate(this.opts.containerCssClass)); + + syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass); + this.dropdown.addClass(evaluate(this.opts.dropdownCssClass)); + + }); + + // IE8-10 + el.on("propertychange.select2", sync); + + // hold onto a reference of the callback to work around a chromium bug + if (this.mutationCallback === undefined) { + this.mutationCallback = function (mutations) { + mutations.forEach(sync); + } + } + + // safari, chrome, firefox, IE11 + observer = window.MutationObserver || window.WebKitMutationObserver|| window.MozMutationObserver; + if (observer !== undefined) { + if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; } + this.propertyObserver = new observer(this.mutationCallback); + this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false }); + } + }, + + // abstract + triggerSelect: function(data) { + var evt = $.Event("select2-selecting", { val: this.id(data), object: data }); + this.opts.element.trigger(evt); + return !evt.isDefaultPrevented(); + }, + + /** + * Triggers the change event on the source element + */ + // abstract + triggerChange: function (details) { + + details = details || {}; + details= $.extend({}, details, { type: "change", val: this.val() }); + // prevents recursive triggering + this.opts.element.data("select2-change-triggered", true); + this.opts.element.trigger(details); + this.opts.element.data("select2-change-triggered", false); + + // some validation frameworks ignore the change event and listen instead to keyup, click for selects + // so here we trigger the click event manually + this.opts.element.click(); + + // ValidationEngine ignorea the change event and listens instead to blur + // so here we trigger the blur event manually if so desired + if (this.opts.blurOnChange) + this.opts.element.blur(); + }, + + //abstract + isInterfaceEnabled: function() + { + return this.enabledInterface === true; + }, + + // abstract + enableInterface: function() { + var enabled = this._enabled && !this._readonly, + disabled = !enabled; + + if (enabled === this.enabledInterface) return false; + + this.container.toggleClass("select2-container-disabled", disabled); + this.close(); + this.enabledInterface = enabled; + + return true; + }, + + // abstract + enable: function(enabled) { + if (enabled === undefined) enabled = true; + if (this._enabled === enabled) return; + this._enabled = enabled; + + this.opts.element.prop("disabled", !enabled); + this.enableInterface(); + }, + + // abstract + disable: function() { + this.enable(false); + }, + + // abstract + readonly: function(enabled) { + if (enabled === undefined) enabled = false; + if (this._readonly === enabled) return false; + this._readonly = enabled; + + this.opts.element.prop("readonly", enabled); + this.enableInterface(); + return true; + }, + + // abstract + opened: function () { + return this.container.hasClass("select2-dropdown-open"); + }, + + // abstract + positionDropdown: function() { + var $dropdown = this.dropdown, + offset = this.container.offset(), + height = this.container.outerHeight(false), + width = this.container.outerWidth(false), + dropHeight = $dropdown.outerHeight(false), + $window = $(window), + windowWidth = $window.width(), + windowHeight = $window.height(), + viewPortRight = $window.scrollLeft() + windowWidth, + viewportBottom = $window.scrollTop() + windowHeight, + dropTop = offset.top + height, + dropLeft = offset.left, + enoughRoomBelow = dropTop + dropHeight <= viewportBottom, + enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(), + dropWidth = $dropdown.outerWidth(false), + enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight, + aboveNow = $dropdown.hasClass("select2-drop-above"), + bodyOffset, + above, + changeDirection, + css, + resultsListNode; + + // always prefer the current above/below alignment, unless there is not enough room + if (aboveNow) { + above = true; + if (!enoughRoomAbove && enoughRoomBelow) { + changeDirection = true; + above = false; + } + } else { + above = false; + if (!enoughRoomBelow && enoughRoomAbove) { + changeDirection = true; + above = true; + } + } + + //if we are changing direction we need to get positions when dropdown is hidden; + if (changeDirection) { + $dropdown.hide(); + offset = this.container.offset(); + height = this.container.outerHeight(false); + width = this.container.outerWidth(false); + dropHeight = $dropdown.outerHeight(false); + viewPortRight = $window.scrollLeft() + windowWidth; + viewportBottom = $window.scrollTop() + windowHeight; + dropTop = offset.top + height; + dropLeft = offset.left; + dropWidth = $dropdown.outerWidth(false); + enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight; + $dropdown.show(); + } + + if (this.opts.dropdownAutoWidth) { + resultsListNode = $('.select2-results', $dropdown)[0]; + $dropdown.addClass('select2-drop-auto-width'); + $dropdown.css('width', ''); + // Add scrollbar width to dropdown if vertical scrollbar is present + dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width); + dropWidth > width ? width = dropWidth : dropWidth = width; + enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight; + } + else { + this.container.removeClass('select2-drop-auto-width'); + } + + //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow); + //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove); + + // fix positioning when body has an offset and is not position: static + if (this.body().css('position') !== 'static') { + bodyOffset = this.body().offset(); + dropTop -= bodyOffset.top; + dropLeft -= bodyOffset.left; + } + + if (!enoughRoomOnRight) { + dropLeft = offset.left + width - dropWidth; + } + + css = { + left: dropLeft, + width: width + }; + + if (above) { + css.bottom = windowHeight - offset.top; + css.top = 'auto'; + this.container.addClass("select2-drop-above"); + $dropdown.addClass("select2-drop-above"); + } + else { + css.top = dropTop; + css.bottom = 'auto'; + this.container.removeClass("select2-drop-above"); + $dropdown.removeClass("select2-drop-above"); + } + css = $.extend(css, evaluate(this.opts.dropdownCss)); + + $dropdown.css(css); + }, + + // abstract + shouldOpen: function() { + var event; + + if (this.opened()) return false; + + if (this._enabled === false || this._readonly === true) return false; + + event = $.Event("select2-opening"); + this.opts.element.trigger(event); + return !event.isDefaultPrevented(); + }, + + // abstract + clearDropdownAlignmentPreference: function() { + // clear the classes used to figure out the preference of where the dropdown should be opened + this.container.removeClass("select2-drop-above"); + this.dropdown.removeClass("select2-drop-above"); + }, + + /** + * Opens the dropdown + * + * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example, + * the dropdown is already open, or if the 'open' event listener on the element called preventDefault(). + */ + // abstract + open: function () { + + if (!this.shouldOpen()) return false; + + this.opening(); + + return true; + }, + + /** + * Performs the opening of the dropdown + */ + // abstract + opening: function() { + var cid = this.containerId, + scroll = "scroll." + cid, + resize = "resize."+cid, + orient = "orientationchange."+cid, + mask; + + this.container.addClass("select2-dropdown-open").addClass("select2-container-active"); + + this.clearDropdownAlignmentPreference(); + + if(this.dropdown[0] !== this.body().children().last()[0]) { + this.dropdown.detach().appendTo(this.body()); + } + + // create the dropdown mask if doesnt already exist + mask = $("#select2-drop-mask"); + if (mask.length == 0) { + mask = $(document.createElement("div")); + mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask"); + mask.hide(); + mask.appendTo(this.body()); + mask.on("mousedown touchstart click", function (e) { + var dropdown = $("#select2-drop"), self; + if (dropdown.length > 0) { + self=dropdown.data("select2"); + if (self.opts.selectOnBlur) { + self.selectHighlighted({noFocus: true}); + } + self.close({focus:true}); + e.preventDefault(); + e.stopPropagation(); + } + }); + } + + // ensure the mask is always right before the dropdown + if (this.dropdown.prev()[0] !== mask[0]) { + this.dropdown.before(mask); + } + + // move the global id to the correct dropdown + $("#select2-drop").removeAttr("id"); + this.dropdown.attr("id", "select2-drop"); + + // show the elements + mask.show(); + + this.positionDropdown(); + this.dropdown.show(); + this.positionDropdown(); + + this.dropdown.addClass("select2-drop-active"); + + // attach listeners to events that can change the position of the container and thus require + // the position of the dropdown to be updated as well so it does not come unglued from the container + var that = this; + this.container.parents().add(window).each(function () { + $(this).on(resize+" "+scroll+" "+orient, function (e) { + that.positionDropdown(); + }); + }); + + + }, + + // abstract + close: function () { + if (!this.opened()) return; + + var cid = this.containerId, + scroll = "scroll." + cid, + resize = "resize."+cid, + orient = "orientationchange."+cid; + + // unbind event listeners + this.container.parents().add(window).each(function () { $(this).off(scroll).off(resize).off(orient); }); + + this.clearDropdownAlignmentPreference(); + + $("#select2-drop-mask").hide(); + this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id + this.dropdown.hide(); + this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active"); + this.results.empty(); + + + this.clearSearch(); + this.search.removeClass("select2-active"); + this.opts.element.trigger($.Event("select2-close")); + }, + + /** + * Opens control, sets input value, and updates results. + */ + // abstract + externalSearch: function (term) { + this.open(); + this.search.val(term); + this.updateResults(false); + }, + + // abstract + clearSearch: function () { + + }, + + //abstract + getMaximumSelectionSize: function() { + return evaluate(this.opts.maximumSelectionSize); + }, + + // abstract + ensureHighlightVisible: function () { + var results = this.results, children, index, child, hb, rb, y, more; + + index = this.highlight(); + + if (index < 0) return; + + if (index == 0) { + + // if the first element is highlighted scroll all the way to the top, + // that way any unselectable headers above it will also be scrolled + // into view + + results.scrollTop(0); + return; + } + + children = this.findHighlightableChoices().find('.select2-result-label'); + + child = $(children[index]); + + hb = child.offset().top + child.outerHeight(true); + + // if this is the last child lets also make sure select2-more-results is visible + if (index === children.length - 1) { + more = results.find("li.select2-more-results"); + if (more.length > 0) { + hb = more.offset().top + more.outerHeight(true); + } + } + + rb = results.offset().top + results.outerHeight(true); + if (hb > rb) { + results.scrollTop(results.scrollTop() + (hb - rb)); + } + y = child.offset().top - results.offset().top; + + // make sure the top of the element is visible + if (y < 0 && child.css('display') != 'none' ) { + results.scrollTop(results.scrollTop() + y); // y is negative + } + }, + + // abstract + findHighlightableChoices: function() { + return this.results.find(".select2-result-selectable:not(.select2-disabled, .select2-selected)"); + }, + + // abstract + moveHighlight: function (delta) { + var choices = this.findHighlightableChoices(), + index = this.highlight(); + + while (index > -1 && index < choices.length) { + index += delta; + var choice = $(choices[index]); + if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) { + this.highlight(index); + break; + } + } + }, + + // abstract + highlight: function (index) { + var choices = this.findHighlightableChoices(), + choice, + data; + + if (arguments.length === 0) { + return indexOf(choices.filter(".select2-highlighted")[0], choices.get()); + } + + if (index >= choices.length) index = choices.length - 1; + if (index < 0) index = 0; + + this.removeHighlight(); + + choice = $(choices[index]); + choice.addClass("select2-highlighted"); + + this.ensureHighlightVisible(); + + data = choice.data("select2-data"); + if (data) { + this.opts.element.trigger({ type: "select2-highlight", val: this.id(data), choice: data }); + } + }, + + removeHighlight: function() { + this.results.find(".select2-highlighted").removeClass("select2-highlighted"); + }, + + // abstract + countSelectableResults: function() { + return this.findHighlightableChoices().length; + }, + + // abstract + highlightUnderEvent: function (event) { + var el = $(event.target).closest(".select2-result-selectable"); + if (el.length > 0 && !el.is(".select2-highlighted")) { + var choices = this.findHighlightableChoices(); + this.highlight(choices.index(el)); + } else if (el.length == 0) { + // if we are over an unselectable item remove all highlights + this.removeHighlight(); + } + }, + + // abstract + loadMoreIfNeeded: function () { + var results = this.results, + more = results.find("li.select2-more-results"), + below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible + page = this.resultsPage + 1, + self=this, + term=this.search.val(), + context=this.context; + + if (more.length === 0) return; + below = more.offset().top - results.offset().top - results.height(); + + if (below <= this.opts.loadMorePadding) { + more.addClass("select2-active"); + this.opts.query({ + element: this.opts.element, + term: term, + page: page, + context: context, + matcher: this.opts.matcher, + callback: this.bind(function (data) { + + // ignore a response if the select2 has been closed before it was received + if (!self.opened()) return; + + + self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context}); + self.postprocessResults(data, false, false); + + if (data.more===true) { + more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1)); + window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10); + } else { + more.remove(); + } + self.positionDropdown(); + self.resultsPage = page; + self.context = data.context; + this.opts.element.trigger({ type: "select2-loaded", items: data }); + })}); + } + }, + + /** + * Default tokenizer function which does nothing + */ + tokenize: function() { + + }, + + /** + * @param initial whether or not this is the call to this method right after the dropdown has been opened + */ + // abstract + updateResults: function (initial) { + var search = this.search, + results = this.results, + opts = this.opts, + data, + self = this, + input, + term = search.val(), + lastTerm = $.data(this.container, "select2-last-term"), + // sequence number used to drop out-of-order responses + queryNumber; + + // prevent duplicate queries against the same term + if (initial !== true && lastTerm && equal(term, lastTerm)) return; + + $.data(this.container, "select2-last-term", term); + + // if the search is currently hidden we do not alter the results + if (initial !== true && (this.showSearchInput === false || !this.opened())) { + return; + } + + function postRender() { + search.removeClass("select2-active"); + self.positionDropdown(); + } + + function render(html) { + results.html(html); + postRender(); + } + + queryNumber = ++this.queryCount; + + var maxSelSize = this.getMaximumSelectionSize(); + if (maxSelSize >=1) { + data = this.data(); + if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) { + render("<li class='select2-selection-limit'>" + opts.formatSelectionTooBig(maxSelSize) + "</li>"); + return; + } + } + + if (search.val().length < opts.minimumInputLength) { + if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) { + render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>"); + } else { + render(""); + } + if (initial && this.showSearch) this.showSearch(true); + return; + } + + if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) { + if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) { + render("<li class='select2-no-results'>" + opts.formatInputTooLong(search.val(), opts.maximumInputLength) + "</li>"); + } else { + render(""); + } + return; + } + + if (opts.formatSearching && this.findHighlightableChoices().length === 0) { + render("<li class='select2-searching'>" + opts.formatSearching() + "</li>"); + } + + search.addClass("select2-active"); + + this.removeHighlight(); + + // give the tokenizer a chance to pre-process the input + input = this.tokenize(); + if (input != undefined && input != null) { + search.val(input); + } + + this.resultsPage = 1; + + opts.query({ + element: opts.element, + term: search.val(), + page: this.resultsPage, + context: null, + matcher: opts.matcher, + callback: this.bind(function (data) { + var def; // default choice + + // ignore old responses + if (queryNumber != this.queryCount) { + return; + } + + // ignore a response if the select2 has been closed before it was received + if (!this.opened()) { + this.search.removeClass("select2-active"); + return; + } + + // save context, if any + this.context = (data.context===undefined) ? null : data.context; + // create a default choice and prepend it to the list + if (this.opts.createSearchChoice && search.val() !== "") { + def = this.opts.createSearchChoice.call(self, search.val(), data.results); + if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) { + if ($(data.results).filter( + function () { + return equal(self.id(this), self.id(def)); + }).length === 0) { + data.results.unshift(def); + } + } + } + + if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) { + render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>"); + return; + } + + results.empty(); + self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null}); + + if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) { + results.append("<li class='select2-more-results'>" + self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + "</li>"); + window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10); + } + + this.postprocessResults(data, initial); + + postRender(); + + this.opts.element.trigger({ type: "select2-loaded", items: data }); + })}); + }, + + // abstract + cancel: function () { + this.close(); + }, + + // abstract + blur: function () { + // if selectOnBlur == true, select the currently highlighted option + if (this.opts.selectOnBlur) + this.selectHighlighted({noFocus: true}); + + this.close(); + this.container.removeClass("select2-container-active"); + // synonymous to .is(':focus'), which is available in jquery >= 1.6 + if (this.search[0] === document.activeElement) { this.search.blur(); } + this.clearSearch(); + this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); + }, + + // abstract + focusSearch: function () { + focus(this.search); + }, + + // abstract + selectHighlighted: function (options) { + var index=this.highlight(), + highlighted=this.results.find(".select2-highlighted"), + data = highlighted.closest('.select2-result').data("select2-data"); + + if (data) { + this.highlight(index); + this.onSelect(data, options); + } else if (options && options.noFocus) { + this.close(); + } + }, + + // abstract + getPlaceholder: function () { + var placeholderOption; + return this.opts.element.attr("placeholder") || + this.opts.element.attr("data-placeholder") || // jquery 1.4 compat + this.opts.element.data("placeholder") || + this.opts.placeholder || + ((placeholderOption = this.getPlaceholderOption()) !== undefined ? placeholderOption.text() : undefined); + }, + + // abstract + getPlaceholderOption: function() { + if (this.select) { + var firstOption = this.select.children('option').first(); + if (this.opts.placeholderOption !== undefined ) { + //Determine the placeholder option based on the specified placeholderOption setting + return (this.opts.placeholderOption === "first" && firstOption) || + (typeof this.opts.placeholderOption === "function" && this.opts.placeholderOption(this.select)); + } else if (firstOption.text() === "" && firstOption.val() === "") { + //No explicit placeholder option specified, use the first if it's blank + return firstOption; + } + } + }, + + /** + * Get the desired width for the container element. This is + * derived first from option `width` passed to select2, then + * the inline 'style' on the original element, and finally + * falls back to the jQuery calculated element width. + */ + // abstract + initContainerWidth: function () { + function resolveContainerWidth() { + var style, attrs, matches, i, l, attr; + + if (this.opts.width === "off") { + return null; + } else if (this.opts.width === "element"){ + return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px'; + } else if (this.opts.width === "copy" || this.opts.width === "resolve") { + // check if there is inline style on the element that contains width + style = this.opts.element.attr('style'); + if (style !== undefined) { + attrs = style.split(';'); + for (i = 0, l = attrs.length; i < l; i = i + 1) { + attr = attrs[i].replace(/\s/g, ''); + matches = attr.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i); + if (matches !== null && matches.length >= 1) + return matches[1]; + } + } + + if (this.opts.width === "resolve") { + // next check if css('width') can resolve a width that is percent based, this is sometimes possible + // when attached to input type=hidden or elements hidden via css + style = this.opts.element.css('width'); + if (style.indexOf("%") > 0) return style; + + // finally, fallback on the calculated width of the element + return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px'); + } + + return null; + } else if ($.isFunction(this.opts.width)) { + return this.opts.width(); + } else { + return this.opts.width; + } + }; + + var width = resolveContainerWidth.call(this); + if (width !== null) { + this.container.css("width", width); + } + } + }); + + SingleSelect2 = clazz(AbstractSelect2, { + + // single + + createContainer: function () { + var container = $(document.createElement("div")).attr({ + "class": "select2-container" + }).html([ + "<a href='javascript:void(0)' onclick='return false;' class='select2-choice' tabindex='-1'>", + " <span class='select2-chosen'> </span><abbr class='select2-search-choice-close'></abbr>", + " <span class='select2-arrow'><b></b></span>", + "</a>", + "<input class='select2-focusser select2-offscreen' type='text'/>", + "<div class='select2-drop select2-display-none'>", + " <div class='select2-search'>", + " <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'/>", + " </div>", + " <ul class='select2-results'>", + " </ul>", + "</div>"].join("")); + return container; + }, + + // single + enableInterface: function() { + if (this.parent.enableInterface.apply(this, arguments)) { + this.focusser.prop("disabled", !this.isInterfaceEnabled()); + } + }, + + // single + opening: function () { + var el, range, len; + + if (this.opts.minimumResultsForSearch >= 0) { + this.showSearch(true); + } + + this.parent.opening.apply(this, arguments); + + if (this.showSearchInput !== false) { + // IE appends focusser.val() at the end of field :/ so we manually insert it at the beginning using a range + // all other browsers handle this just fine + + this.search.val(this.focusser.val()); + } + this.search.focus(); + // move the cursor to the end after focussing, otherwise it will be at the beginning and + // new text will appear *before* focusser.val() + el = this.search.get(0); + if (el.createTextRange) { + range = el.createTextRange(); + range.collapse(false); + range.select(); + } else if (el.setSelectionRange) { + len = this.search.val().length; + el.setSelectionRange(len, len); + } + + // initializes search's value with nextSearchTerm (if defined by user) + // ignore nextSearchTerm if the dropdown is opened by the user pressing a letter + if(this.search.val() === "") { + if(this.nextSearchTerm != undefined){ + this.search.val(this.nextSearchTerm); + this.search.select(); + } + } + + this.focusser.prop("disabled", true).val(""); + this.updateResults(true); + this.opts.element.trigger($.Event("select2-open")); + }, + + // single + close: function (params) { + if (!this.opened()) return; + this.parent.close.apply(this, arguments); + + params = params || {focus: true}; + this.focusser.removeAttr("disabled"); + + if (params.focus) { + this.focusser.focus(); + } + }, + + // single + focus: function () { + if (this.opened()) { + this.close(); + } else { + this.focusser.removeAttr("disabled"); + this.focusser.focus(); + } + }, + + // single + isFocused: function () { + return this.container.hasClass("select2-container-active"); + }, + + // single + cancel: function () { + this.parent.cancel.apply(this, arguments); + this.focusser.removeAttr("disabled"); + this.focusser.focus(); + }, + + // single + destroy: function() { + $("label[for='" + this.focusser.attr('id') + "']") + .attr('for', this.opts.element.attr("id")); + this.parent.destroy.apply(this, arguments); + }, + + // single + initContainer: function () { + + var selection, + container = this.container, + dropdown = this.dropdown; + + if (this.opts.minimumResultsForSearch < 0) { + this.showSearch(false); + } else { + this.showSearch(true); + } + + this.selection = selection = container.find(".select2-choice"); + + this.focusser = container.find(".select2-focusser"); + + // rewrite labels from original element to focusser + this.focusser.attr("id", "s2id_autogen"+nextUid()); + + $("label[for='" + this.opts.element.attr("id") + "']") + .attr('for', this.focusser.attr('id')); + + this.focusser.attr("tabindex", this.elementTabIndex); + + this.search.on("keydown", this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + + if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { + // prevent the page from scrolling + killEvent(e); + return; + } + + switch (e.which) { + case KEY.UP: + case KEY.DOWN: + this.moveHighlight((e.which === KEY.UP) ? -1 : 1); + killEvent(e); + return; + case KEY.ENTER: + this.selectHighlighted(); + killEvent(e); + return; + case KEY.TAB: + this.selectHighlighted({noFocus: true}); + return; + case KEY.ESC: + this.cancel(e); + killEvent(e); + return; + } + })); + + this.search.on("blur", this.bind(function(e) { + // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown. + // without this the search field loses focus which is annoying + if (document.activeElement === this.body().get(0)) { + window.setTimeout(this.bind(function() { + this.search.focus(); + }), 0); + } + })); + + this.focusser.on("keydown", this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { + return; + } + + if (this.opts.openOnEnter === false && e.which === KEY.ENTER) { + killEvent(e); + return; + } + + if (e.which == KEY.DOWN || e.which == KEY.UP + || (e.which == KEY.ENTER && this.opts.openOnEnter)) { + + if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return; + + this.open(); + killEvent(e); + return; + } + + if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) { + if (this.opts.allowClear) { + this.clear(); + } + killEvent(e); + return; + } + })); + + + installKeyUpChangeEvent(this.focusser); + this.focusser.on("keyup-change input", this.bind(function(e) { + if (this.opts.minimumResultsForSearch >= 0) { + e.stopPropagation(); + if (this.opened()) return; + this.open(); + } + })); + + selection.on("mousedown", "abbr", this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + this.clear(); + killEventImmediately(e); + this.close(); + this.selection.focus(); + })); + + selection.on("mousedown", this.bind(function (e) { + + if (!this.container.hasClass("select2-container-active")) { + this.opts.element.trigger($.Event("select2-focus")); + } + + if (this.opened()) { + this.close(); + } else if (this.isInterfaceEnabled()) { + this.open(); + } + + killEvent(e); + })); + + dropdown.on("mousedown", this.bind(function() { this.search.focus(); })); + + selection.on("focus", this.bind(function(e) { + killEvent(e); + })); + + this.focusser.on("focus", this.bind(function(){ + if (!this.container.hasClass("select2-container-active")) { + this.opts.element.trigger($.Event("select2-focus")); + } + this.container.addClass("select2-container-active"); + })).on("blur", this.bind(function() { + if (!this.opened()) { + this.container.removeClass("select2-container-active"); + this.opts.element.trigger($.Event("select2-blur")); + } + })); + this.search.on("focus", this.bind(function(){ + if (!this.container.hasClass("select2-container-active")) { + this.opts.element.trigger($.Event("select2-focus")); + } + this.container.addClass("select2-container-active"); + })); + + this.initContainerWidth(); + this.opts.element.addClass("select2-offscreen"); + this.setPlaceholder(); + + }, + + // single + clear: function(triggerChange) { + var data=this.selection.data("select2-data"); + if (data) { // guard against queued quick consecutive clicks + var evt = $.Event("select2-clearing"); + this.opts.element.trigger(evt); + if (evt.isDefaultPrevented()) { + return; + } + var placeholderOption = this.getPlaceholderOption(); + this.opts.element.val(placeholderOption ? placeholderOption.val() : ""); + this.selection.find(".select2-chosen").empty(); + this.selection.removeData("select2-data"); + this.setPlaceholder(); + + if (triggerChange !== false){ + this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data }); + this.triggerChange({removed:data}); + } + } + }, + + /** + * Sets selection based on source element's value + */ + // single + initSelection: function () { + var selected; + if (this.isPlaceholderOptionSelected()) { + this.updateSelection(null); + this.close(); + this.setPlaceholder(); + } else { + var self = this; + this.opts.initSelection.call(null, this.opts.element, function(selected){ + if (selected !== undefined && selected !== null) { + self.updateSelection(selected); + self.close(); + self.setPlaceholder(); + } + }); + } + }, + + isPlaceholderOptionSelected: function() { + var placeholderOption; + if (!this.getPlaceholder()) return false; // no placeholder specified so no option should be considered + return ((placeholderOption = this.getPlaceholderOption()) !== undefined && placeholderOption.prop("selected")) + || (this.opts.element.val() === "") + || (this.opts.element.val() === undefined) + || (this.opts.element.val() === null); + }, + + // single + prepareOpts: function () { + var opts = this.parent.prepareOpts.apply(this, arguments), + self=this; + + if (opts.element.get(0).tagName.toLowerCase() === "select") { + // install the selection initializer + opts.initSelection = function (element, callback) { + var selected = element.find("option").filter(function() { return this.selected }); + // a single select box always has a value, no need to null check 'selected' + callback(self.optionToData(selected)); + }; + } else if ("data" in opts) { + // install default initSelection when applied to hidden input and data is local + opts.initSelection = opts.initSelection || function (element, callback) { + var id = element.val(); + //search in data by id, storing the actual matching item + var match = null; + opts.query({ + matcher: function(term, text, el){ + var is_match = equal(id, opts.id(el)); + if (is_match) { + match = el; + } + return is_match; + }, + callback: !$.isFunction(callback) ? $.noop : function() { + callback(match); + } + }); + }; + } + + return opts; + }, + + // single + getPlaceholder: function() { + // if a placeholder is specified on a single select without a valid placeholder option ignore it + if (this.select) { + if (this.getPlaceholderOption() === undefined) { + return undefined; + } + } + + return this.parent.getPlaceholder.apply(this, arguments); + }, + + // single + setPlaceholder: function () { + var placeholder = this.getPlaceholder(); + + if (this.isPlaceholderOptionSelected() && placeholder !== undefined) { + + // check for a placeholder option if attached to a select + if (this.select && this.getPlaceholderOption() === undefined) return; + + this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(placeholder)); + + this.selection.addClass("select2-default"); + + this.container.removeClass("select2-allowclear"); + } + }, + + // single + postprocessResults: function (data, initial, noHighlightUpdate) { + var selected = 0, self = this, showSearchInput = true; + + // find the selected element in the result list + + this.findHighlightableChoices().each2(function (i, elm) { + if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) { + selected = i; + return false; + } + }); + + // and highlight it + if (noHighlightUpdate !== false) { + if (initial === true && selected >= 0) { + this.highlight(selected); + } else { + this.highlight(0); + } + } + + // hide the search box if this is the first we got the results and there are enough of them for search + + if (initial === true) { + var min = this.opts.minimumResultsForSearch; + if (min >= 0) { + this.showSearch(countResults(data.results) >= min); + } + } + }, + + // single + showSearch: function(showSearchInput) { + if (this.showSearchInput === showSearchInput) return; + + this.showSearchInput = showSearchInput; + + this.dropdown.find(".select2-search").toggleClass("select2-search-hidden", !showSearchInput); + this.dropdown.find(".select2-search").toggleClass("select2-offscreen", !showSearchInput); + //add "select2-with-searchbox" to the container if search box is shown + $(this.dropdown, this.container).toggleClass("select2-with-searchbox", showSearchInput); + }, + + // single + onSelect: function (data, options) { + + if (!this.triggerSelect(data)) { return; } + + var old = this.opts.element.val(), + oldData = this.data(); + + this.opts.element.val(this.id(data)); + this.updateSelection(data); + + this.opts.element.trigger({ type: "select2-selected", val: this.id(data), choice: data }); + + this.nextSearchTerm = this.opts.nextSearchTerm(data, this.search.val()); + this.close(); + + if (!options || !options.noFocus) + this.focusser.focus(); + + if (!equal(old, this.id(data))) { this.triggerChange({added:data,removed:oldData}); } + }, + + // single + updateSelection: function (data) { + + var container=this.selection.find(".select2-chosen"), formatted, cssClass; + + this.selection.data("select2-data", data); + + container.empty(); + if (data !== null) { + formatted=this.opts.formatSelection(data, container, this.opts.escapeMarkup); + } + if (formatted !== undefined) { + container.append(formatted); + } + cssClass=this.opts.formatSelectionCssClass(data, container); + if (cssClass !== undefined) { + container.addClass(cssClass); + } + + this.selection.removeClass("select2-default"); + + if (this.opts.allowClear && this.getPlaceholder() !== undefined) { + this.container.addClass("select2-allowclear"); + } + }, + + // single + val: function () { + var val, + triggerChange = false, + data = null, + self = this, + oldData = this.data(); + + if (arguments.length === 0) { + return this.opts.element.val(); + } + + val = arguments[0]; + + if (arguments.length > 1) { + triggerChange = arguments[1]; + } + + if (this.select) { + this.select + .val(val) + .find("option").filter(function() { return this.selected }).each2(function (i, elm) { + data = self.optionToData(elm); + return false; + }); + this.updateSelection(data); + this.setPlaceholder(); + if (triggerChange) { + this.triggerChange({added: data, removed:oldData}); + } + } else { + // val is an id. !val is true for [undefined,null,'',0] - 0 is legal + if (!val && val !== 0) { + this.clear(triggerChange); + return; + } + if (this.opts.initSelection === undefined) { + throw new Error("cannot call val() if initSelection() is not defined"); + } + this.opts.element.val(val); + this.opts.initSelection(this.opts.element, function(data){ + self.opts.element.val(!data ? "" : self.id(data)); + self.updateSelection(data); + self.setPlaceholder(); + if (triggerChange) { + self.triggerChange({added: data, removed:oldData}); + } + }); + } + }, + + // single + clearSearch: function () { + this.search.val(""); + this.focusser.val(""); + }, + + // single + data: function(value) { + var data, + triggerChange = false; + + if (arguments.length === 0) { + data = this.selection.data("select2-data"); + if (data == undefined) data = null; + return data; + } else { + if (arguments.length > 1) { + triggerChange = arguments[1]; + } + if (!value) { + this.clear(triggerChange); + } else { + data = this.data(); + this.opts.element.val(!value ? "" : this.id(value)); + this.updateSelection(value); + if (triggerChange) { + this.triggerChange({added: value, removed:data}); + } + } + } + } + }); + + MultiSelect2 = clazz(AbstractSelect2, { + + // multi + createContainer: function () { + var container = $(document.createElement("div")).attr({ + "class": "select2-container select2-container-multi" + }).html([ + "<ul class='select2-choices'>", + " <li class='select2-search-field'>", + " <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>", + " </li>", + "</ul>", + "<div class='select2-drop select2-drop-multi select2-display-none'>", + " <ul class='select2-results'>", + " </ul>", + "</div>"].join("")); + return container; + }, + + // multi + prepareOpts: function () { + var opts = this.parent.prepareOpts.apply(this, arguments), + self=this; + + // TODO validate placeholder is a string if specified + + if (opts.element.get(0).tagName.toLowerCase() === "select") { + // install sthe selection initializer + opts.initSelection = function (element, callback) { + + var data = []; + + element.find("option").filter(function() { return this.selected }).each2(function (i, elm) { + data.push(self.optionToData(elm)); + }); + callback(data); + }; + } else if ("data" in opts) { + // install default initSelection when applied to hidden input and data is local + opts.initSelection = opts.initSelection || function (element, callback) { + var ids = splitVal(element.val(), opts.separator); + //search in data by array of ids, storing matching items in a list + var matches = []; + opts.query({ + matcher: function(term, text, el){ + var is_match = $.grep(ids, function(id) { + return equal(id, opts.id(el)); + }).length; + if (is_match) { + matches.push(el); + } + return is_match; + }, + callback: !$.isFunction(callback) ? $.noop : function() { + // reorder matches based on the order they appear in the ids array because right now + // they are in the order in which they appear in data array + var ordered = []; + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + if (equal(id, opts.id(match))) { + ordered.push(match); + matches.splice(j, 1); + break; + } + } + } + callback(ordered); + } + }); + }; + } + + return opts; + }, + + // multi + selectChoice: function (choice) { + + var selected = this.container.find(".select2-search-choice-focus"); + if (selected.length && choice && choice[0] == selected[0]) { + + } else { + if (selected.length) { + this.opts.element.trigger("choice-deselected", selected); + } + selected.removeClass("select2-search-choice-focus"); + if (choice && choice.length) { + this.close(); + choice.addClass("select2-search-choice-focus"); + this.opts.element.trigger("choice-selected", choice); + } + } + }, + + // multi + destroy: function() { + $("label[for='" + this.search.attr('id') + "']") + .attr('for', this.opts.element.attr("id")); + this.parent.destroy.apply(this, arguments); + }, + + // multi + initContainer: function () { + + var selector = ".select2-choices", selection; + + this.searchContainer = this.container.find(".select2-search-field"); + this.selection = selection = this.container.find(selector); + + var _this = this; + this.selection.on("click", ".select2-search-choice:not(.select2-locked)", function (e) { + //killEvent(e); + _this.search[0].focus(); + _this.selectChoice($(this)); + }); + + // rewrite labels from original element to focusser + this.search.attr("id", "s2id_autogen"+nextUid()); + $("label[for='" + this.opts.element.attr("id") + "']") + .attr('for', this.search.attr('id')); + + this.search.on("input paste", this.bind(function() { + if (!this.isInterfaceEnabled()) return; + if (!this.opened()) { + this.open(); + } + })); + + this.search.attr("tabindex", this.elementTabIndex); + + this.keydowns = 0; + this.search.on("keydown", this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + + ++this.keydowns; + var selected = selection.find(".select2-search-choice-focus"); + var prev = selected.prev(".select2-search-choice:not(.select2-locked)"); + var next = selected.next(".select2-search-choice:not(.select2-locked)"); + var pos = getCursorInfo(this.search); + + if (selected.length && + (e.which == KEY.LEFT || e.which == KEY.RIGHT || e.which == KEY.BACKSPACE || e.which == KEY.DELETE || e.which == KEY.ENTER)) { + var selectedChoice = selected; + if (e.which == KEY.LEFT && prev.length) { + selectedChoice = prev; + } + else if (e.which == KEY.RIGHT) { + selectedChoice = next.length ? next : null; + } + else if (e.which === KEY.BACKSPACE) { + this.unselect(selected.first()); + this.search.width(10); + selectedChoice = prev.length ? prev : next; + } else if (e.which == KEY.DELETE) { + this.unselect(selected.first()); + this.search.width(10); + selectedChoice = next.length ? next : null; + } else if (e.which == KEY.ENTER) { + selectedChoice = null; + } + + this.selectChoice(selectedChoice); + killEvent(e); + if (!selectedChoice || !selectedChoice.length) { + this.open(); + } + return; + } else if (((e.which === KEY.BACKSPACE && this.keydowns == 1) + || e.which == KEY.LEFT) && (pos.offset == 0 && !pos.length)) { + + this.selectChoice(selection.find(".select2-search-choice:not(.select2-locked)").last()); + killEvent(e); + return; + } else { + this.selectChoice(null); + } + + if (this.opened()) { + switch (e.which) { + case KEY.UP: + case KEY.DOWN: + this.moveHighlight((e.which === KEY.UP) ? -1 : 1); + killEvent(e); + return; + case KEY.ENTER: + this.selectHighlighted(); + killEvent(e); + return; + case KEY.TAB: + this.selectHighlighted({noFocus:true}); + this.close(); + return; + case KEY.ESC: + this.cancel(e); + killEvent(e); + return; + } + } + + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) + || e.which === KEY.BACKSPACE || e.which === KEY.ESC) { + return; + } + + if (e.which === KEY.ENTER) { + if (this.opts.openOnEnter === false) { + return; + } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { + return; + } + } + + this.open(); + + if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { + // prevent the page from scrolling + killEvent(e); + } + + if (e.which === KEY.ENTER) { + // prevent form from being submitted + killEvent(e); + } + + })); + + this.search.on("keyup", this.bind(function (e) { + this.keydowns = 0; + this.resizeSearch(); + }) + ); + + this.search.on("blur", this.bind(function(e) { + this.container.removeClass("select2-container-active"); + this.search.removeClass("select2-focused"); + this.selectChoice(null); + if (!this.opened()) this.clearSearch(); + e.stopImmediatePropagation(); + this.opts.element.trigger($.Event("select2-blur")); + })); + + this.container.on("click", selector, this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + if ($(e.target).closest(".select2-search-choice").length > 0) { + // clicked inside a select2 search choice, do not open + return; + } + this.selectChoice(null); + this.clearPlaceholder(); + if (!this.container.hasClass("select2-container-active")) { + this.opts.element.trigger($.Event("select2-focus")); + } + this.open(); + this.focusSearch(); + e.preventDefault(); + })); + + this.container.on("focus", selector, this.bind(function () { + if (!this.isInterfaceEnabled()) return; + if (!this.container.hasClass("select2-container-active")) { + this.opts.element.trigger($.Event("select2-focus")); + } + this.container.addClass("select2-container-active"); + this.dropdown.addClass("select2-drop-active"); + this.clearPlaceholder(); + })); + + this.initContainerWidth(); + this.opts.element.addClass("select2-offscreen"); + + // set the placeholder if necessary + this.clearSearch(); + }, + + // multi + enableInterface: function() { + if (this.parent.enableInterface.apply(this, arguments)) { + this.search.prop("disabled", !this.isInterfaceEnabled()); + } + }, + + // multi + initSelection: function () { + var data; + if (this.opts.element.val() === "" && this.opts.element.text() === "") { + this.updateSelection([]); + this.close(); + // set the placeholder if necessary + this.clearSearch(); + } + if (this.select || this.opts.element.val() !== "") { + var self = this; + this.opts.initSelection.call(null, this.opts.element, function(data){ + if (data !== undefined && data !== null) { + self.updateSelection(data); + self.close(); + // set the placeholder if necessary + self.clearSearch(); + } + }); + } + }, + + // multi + clearSearch: function () { + var placeholder = this.getPlaceholder(), + maxWidth = this.getMaxSearchWidth(); + + if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) { + this.search.val(placeholder).addClass("select2-default"); + // stretch the search box to full width of the container so as much of the placeholder is visible as possible + // we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944 + this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width")); + } else { + this.search.val("").width(10); + } + }, + + // multi + clearPlaceholder: function () { + if (this.search.hasClass("select2-default")) { + this.search.val("").removeClass("select2-default"); + } + }, + + // multi + opening: function () { + this.clearPlaceholder(); // should be done before super so placeholder is not used to search + this.resizeSearch(); + + this.parent.opening.apply(this, arguments); + + this.focusSearch(); + + this.updateResults(true); + this.search.focus(); + this.opts.element.trigger($.Event("select2-open")); + }, + + // multi + close: function () { + if (!this.opened()) return; + this.parent.close.apply(this, arguments); + }, + + // multi + focus: function () { + this.close(); + this.search.focus(); + }, + + // multi + isFocused: function () { + return this.search.hasClass("select2-focused"); + }, + + // multi + updateSelection: function (data) { + var ids = [], filtered = [], self = this; + + // filter out duplicates + $(data).each(function () { + if (indexOf(self.id(this), ids) < 0) { + ids.push(self.id(this)); + filtered.push(this); + } + }); + data = filtered; + + this.selection.find(".select2-search-choice").remove(); + $(data).each(function () { + self.addSelectedChoice(this); + }); + self.postprocessResults(); + }, + + // multi + tokenize: function() { + var input = this.search.val(); + input = this.opts.tokenizer.call(this, input, this.data(), this.bind(this.onSelect), this.opts); + if (input != null && input != undefined) { + this.search.val(input); + if (input.length > 0) { + this.open(); + } + } + + }, + + // multi + onSelect: function (data, options) { + + if (!this.triggerSelect(data)) { return; } + + this.addSelectedChoice(data); + + this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data }); + + if (this.select || !this.opts.closeOnSelect) this.postprocessResults(data, false, this.opts.closeOnSelect===true); + + if (this.opts.closeOnSelect) { + this.close(); + this.search.width(10); + } else { + if (this.countSelectableResults()>0) { + this.search.width(10); + this.resizeSearch(); + if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) { + // if we reached max selection size repaint the results so choices + // are replaced with the max selection reached message + this.updateResults(true); + } + this.positionDropdown(); + } else { + // if nothing left to select close + this.close(); + this.search.width(10); + } + } + + // since its not possible to select an element that has already been + // added we do not need to check if this is a new element before firing change + this.triggerChange({ added: data }); + + if (!options || !options.noFocus) + this.focusSearch(); + }, + + // multi + cancel: function () { + this.close(); + this.focusSearch(); + }, + + addSelectedChoice: function (data) { + var enableChoice = !data.locked, + enabledItem = $( + "<li class='select2-search-choice'>" + + " <div></div>" + + " <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a>" + + "</li>"), + disabledItem = $( + "<li class='select2-search-choice select2-locked'>" + + "<div></div>" + + "</li>"); + var choice = enableChoice ? enabledItem : disabledItem, + id = this.id(data), + val = this.getVal(), + formatted, + cssClass; + + formatted=this.opts.formatSelection(data, choice.find("div"), this.opts.escapeMarkup); + if (formatted != undefined) { + choice.find("div").replaceWith("<div>"+formatted+"</div>"); + } + cssClass=this.opts.formatSelectionCssClass(data, choice.find("div")); + if (cssClass != undefined) { + choice.addClass(cssClass); + } + + if(enableChoice){ + choice.find(".select2-search-choice-close") + .on("mousedown", killEvent) + .on("click dblclick", this.bind(function (e) { + if (!this.isInterfaceEnabled()) return; + + $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){ + this.unselect($(e.target)); + this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); + this.close(); + this.focusSearch(); + })).dequeue(); + killEvent(e); + })).on("focus", this.bind(function () { + if (!this.isInterfaceEnabled()) return; + this.container.addClass("select2-container-active"); + this.dropdown.addClass("select2-drop-active"); + })); + } + + choice.data("select2-data", data); + choice.insertBefore(this.searchContainer); + + val.push(id); + this.setVal(val); + }, + + // multi + unselect: function (selected) { + var val = this.getVal(), + data, + index; + selected = selected.closest(".select2-search-choice"); + + if (selected.length === 0) { + throw "Invalid argument: " + selected + ". Must be .select2-search-choice"; + } + + data = selected.data("select2-data"); + + if (!data) { + // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued + // and invoked on an element already removed + return; + } + + while((index = indexOf(this.id(data), val)) >= 0) { + val.splice(index, 1); + this.setVal(val); + if (this.select) this.postprocessResults(); + } + + var evt = $.Event("select2-removing"); + evt.val = this.id(data); + evt.choice = data; + this.opts.element.trigger(evt); + + if (evt.isDefaultPrevented()) { + return; + } + + selected.remove(); + + this.opts.element.trigger({ type: "select2-removed", val: this.id(data), choice: data }); + this.triggerChange({ removed: data }); + }, + + // multi + postprocessResults: function (data, initial, noHighlightUpdate) { + var val = this.getVal(), + choices = this.results.find(".select2-result"), + compound = this.results.find(".select2-result-with-children"), + self = this; + + choices.each2(function (i, choice) { + var id = self.id(choice.data("select2-data")); + if (indexOf(id, val) >= 0) { + choice.addClass("select2-selected"); + // mark all children of the selected parent as selected + choice.find(".select2-result-selectable").addClass("select2-selected"); + } + }); + + compound.each2(function(i, choice) { + // hide an optgroup if it doesnt have any selectable children + if (!choice.is('.select2-result-selectable') + && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) { + choice.addClass("select2-selected"); + } + }); + + if (this.highlight() == -1 && noHighlightUpdate !== false){ + self.highlight(0); + } + + //If all results are chosen render formatNoMAtches + if(!this.opts.createSearchChoice && !choices.filter('.select2-result:not(.select2-selected)').length > 0){ + if(!data || data && !data.more && this.results.find(".select2-no-results").length === 0) { + if (checkFormatter(self.opts.formatNoMatches, "formatNoMatches")) { + this.results.append("<li class='select2-no-results'>" + self.opts.formatNoMatches(self.search.val()) + "</li>"); + } + } + } + + }, + + // multi + getMaxSearchWidth: function() { + return this.selection.width() - getSideBorderPadding(this.search); + }, + + // multi + resizeSearch: function () { + var minimumWidth, left, maxWidth, containerLeft, searchWidth, + sideBorderPadding = getSideBorderPadding(this.search); + + minimumWidth = measureTextWidth(this.search) + 10; + + left = this.search.offset().left; + + maxWidth = this.selection.width(); + containerLeft = this.selection.offset().left; + + searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding; + + if (searchWidth < minimumWidth) { + searchWidth = maxWidth - sideBorderPadding; + } + + if (searchWidth < 40) { + searchWidth = maxWidth - sideBorderPadding; + } + + if (searchWidth <= 0) { + searchWidth = minimumWidth; + } + + this.search.width(Math.floor(searchWidth)); + }, + + // multi + getVal: function () { + var val; + if (this.select) { + val = this.select.val(); + return val === null ? [] : val; + } else { + val = this.opts.element.val(); + return splitVal(val, this.opts.separator); + } + }, + + // multi + setVal: function (val) { + var unique; + if (this.select) { + this.select.val(val); + } else { + unique = []; + // filter out duplicates + $(val).each(function () { + if (indexOf(this, unique) < 0) unique.push(this); + }); + this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator)); + } + }, + + // multi + buildChangeDetails: function (old, current) { + var current = current.slice(0), + old = old.slice(0); + + // remove intersection from each array + for (var i = 0; i < current.length; i++) { + for (var j = 0; j < old.length; j++) { + if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) { + current.splice(i, 1); + if(i>0){ + i--; + } + old.splice(j, 1); + j--; + } + } + } + + return {added: current, removed: old}; + }, + + + // multi + val: function (val, triggerChange) { + var oldData, self=this; + + if (arguments.length === 0) { + return this.getVal(); + } + + oldData=this.data(); + if (!oldData.length) oldData=[]; + + // val is an id. !val is true for [undefined,null,'',0] - 0 is legal + if (!val && val !== 0) { + this.opts.element.val(""); + this.updateSelection([]); + this.clearSearch(); + if (triggerChange) { + this.triggerChange({added: this.data(), removed: oldData}); + } + return; + } + + // val is a list of ids + this.setVal(val); + + if (this.select) { + this.opts.initSelection(this.select, this.bind(this.updateSelection)); + if (triggerChange) { + this.triggerChange(this.buildChangeDetails(oldData, this.data())); + } + } else { + if (this.opts.initSelection === undefined) { + throw new Error("val() cannot be called if initSelection() is not defined"); + } + + this.opts.initSelection(this.opts.element, function(data){ + var ids=$.map(data, self.id); + self.setVal(ids); + self.updateSelection(data); + self.clearSearch(); + if (triggerChange) { + self.triggerChange(self.buildChangeDetails(oldData, self.data())); + } + }); + } + this.clearSearch(); + }, + + // multi + onSortStart: function() { + if (this.select) { + throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead."); + } + + // collapse search field into 0 width so its container can be collapsed as well + this.search.width(0); + // hide the container + this.searchContainer.hide(); + }, + + // multi + onSortEnd:function() { + + var val=[], self=this; + + // show search and move it to the end of the list + this.searchContainer.show(); + // make sure the search container is the last item in the list + this.searchContainer.appendTo(this.searchContainer.parent()); + // since we collapsed the width in dragStarted, we resize it here + this.resizeSearch(); + + // update selection + this.selection.find(".select2-search-choice").each(function() { + val.push(self.opts.id($(this).data("select2-data"))); + }); + this.setVal(val); + this.triggerChange(); + }, + + // multi + data: function(values, triggerChange) { + var self=this, ids, old; + if (arguments.length === 0) { + return this.selection + .find(".select2-search-choice") + .map(function() { return $(this).data("select2-data"); }) + .get(); + } else { + old = this.data(); + if (!values) { values = []; } + ids = $.map(values, function(e) { return self.opts.id(e); }); + this.setVal(ids); + this.updateSelection(values); + this.clearSearch(); + if (triggerChange) { + this.triggerChange(this.buildChangeDetails(old, this.data())); + } + } + } + }); + + $.fn.select2 = function () { + + var args = Array.prototype.slice.call(arguments, 0), + opts, + select2, + method, value, multiple, + allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "dropdown", "onSortStart", "onSortEnd", "enable", "disable", "readonly", "positionDropdown", "data", "search"], + valueMethods = ["opened", "isFocused", "container", "dropdown"], + propertyMethods = ["val", "data"], + methodsMap = { search: "externalSearch" }; + + this.each(function () { + if (args.length === 0 || typeof(args[0]) === "object") { + opts = args.length === 0 ? {} : $.extend({}, args[0]); + opts.element = $(this); + + if (opts.element.get(0).tagName.toLowerCase() === "select") { + multiple = opts.element.prop("multiple"); + } else { + multiple = opts.multiple || false; + if ("tags" in opts) {opts.multiple = multiple = true;} + } + + select2 = multiple ? new MultiSelect2() : new SingleSelect2(); + select2.init(opts); + } else if (typeof(args[0]) === "string") { + + if (indexOf(args[0], allowedMethods) < 0) { + throw "Unknown method: " + args[0]; + } + + value = undefined; + select2 = $(this).data("select2"); + if (select2 === undefined) return; + + method=args[0]; + + if (method === "container") { + value = select2.container; + } else if (method === "dropdown") { + value = select2.dropdown; + } else { + if (methodsMap[method]) method = methodsMap[method]; + + value = select2[method].apply(select2, args.slice(1)); + } + if (indexOf(args[0], valueMethods) >= 0 + || (indexOf(args[0], propertyMethods) && args.length == 1)) { + return false; // abort the iteration, ready to return first matched value + } + } else { + throw "Invalid arguments to select2 plugin: " + args; + } + }); + return (value === undefined) ? this : value; + }; + + // plugin defaults, accessible to users + $.fn.select2.defaults = { + width: "copy", + loadMorePadding: 0, + closeOnSelect: true, + openOnEnter: true, + containerCss: {}, + dropdownCss: {}, + containerCssClass: "", + dropdownCssClass: "", + formatResult: function(result, container, query, escapeMarkup) { + var markup=[]; + markMatch(result.text, query.term, markup, escapeMarkup); + return markup.join(""); + }, + formatSelection: function (data, container, escapeMarkup) { + return data ? escapeMarkup(data.text) : undefined; + }, + sortResults: function (results, container, query) { + return results; + }, + formatResultCssClass: function(data) {return undefined;}, + formatSelectionCssClass: function(data, container) {return undefined;}, + formatNoMatches: function () { return "No matches found"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1? "" : "s"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); }, + formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "Loading more results..."; }, + formatSearching: function () { return "Searching..."; }, + minimumResultsForSearch: 0, + minimumInputLength: 0, + maximumInputLength: null, + maximumSelectionSize: 0, + id: function (e) { return e.id; }, + matcher: function(term, text) { + return stripDiacritics(''+text).toUpperCase().indexOf(stripDiacritics(''+term).toUpperCase()) >= 0; + }, + separator: ",", + tokenSeparators: [], + tokenizer: defaultTokenizer, + escapeMarkup: defaultEscapeMarkup, + blurOnChange: false, + selectOnBlur: false, + adaptContainerCssClass: function(c) { return c; }, + adaptDropdownCssClass: function(c) { return null; }, + nextSearchTerm: function(selectedObject, currentSearchTerm) { return undefined; } + }; + + $.fn.select2.ajaxDefaults = { + transport: $.ajax, + params: { + type: "GET", + cache: false, + dataType: "json" + } + }; + + // exports + window.Select2 = { + query: { + ajax: ajax, + local: local, + tags: tags + }, util: { + debounce: debounce, + markMatch: markMatch, + escapeMarkup: defaultEscapeMarkup, + stripDiacritics: stripDiacritics + }, "class": { + "abstract": AbstractSelect2, + "single": SingleSelect2, + "multi": MultiSelect2 + } + }; + +}(jQuery)); diff --git a/web/js/select2/select2.min.js b/web/js/select2/select2.min.js new file mode 100644 index 000000000..bffe4c241 --- /dev/null +++ b/web/js/select2/select2.min.js @@ -0,0 +1,22 @@ +/* +Copyright 2012 Igor Vaynberg + +Version: 3.4.5 Timestamp: Mon Nov 4 08:22:42 PST 2013 + +This software is licensed under the Apache License, Version 2.0 (the "Apache License") or the GNU +General Public License version 2 (the "GPL License"). You may choose either license to govern your +use of this software only upon the condition that you accept all of the terms of either the Apache +License or the GPL License. + +You may obtain a copy of the Apache License and the GPL License at: + +http://www.apache.org/licenses/LICENSE-2.0 +http://www.gnu.org/licenses/gpl-2.0.html + +Unless required by applicable law or agreed to in writing, software distributed under the Apache License +or the GPL Licesnse is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +either express or implied. See the Apache License and the GPL License for the specific language governing +permissions and limitations under the Apache License and the GPL License. +*/ +!function(a){"undefined"==typeof a.fn.each2&&a.extend(a.fn,{each2:function(b){for(var c=a([0]),d=-1,e=this.length;++d<e&&(c.context=c[0]=this[d])&&b.call(c[0],d,c)!==!1;);return this}})}(jQuery),function(a,b){"use strict";function n(a){var b,c,d,e;if(!a||a.length<1)return a;for(b="",c=0,d=a.length;d>c;c++)e=a.charAt(c),b+=m[e]||e;return b}function o(a,b){for(var c=0,d=b.length;d>c;c+=1)if(q(a,b[c]))return c;return-1}function p(){var b=a(l);b.appendTo("body");var c={width:b.width()-b[0].clientWidth,height:b.height()-b[0].clientHeight};return b.remove(),c}function q(a,c){return a===c?!0:a===b||c===b?!1:null===a||null===c?!1:a.constructor===String?a+""==c+"":c.constructor===String?c+""==a+"":!1}function r(b,c){var d,e,f;if(null===b||b.length<1)return[];for(d=b.split(c),e=0,f=d.length;f>e;e+=1)d[e]=a.trim(d[e]);return d}function s(a){return a.outerWidth(!1)-a.width()}function t(c){var d="keyup-change-value";c.on("keydown",function(){a.data(c,d)===b&&a.data(c,d,c.val())}),c.on("keyup",function(){var e=a.data(c,d);e!==b&&c.val()!==e&&(a.removeData(c,d),c.trigger("keyup-change"))})}function u(c){c.on("mousemove",function(c){var d=i;(d===b||d.x!==c.pageX||d.y!==c.pageY)&&a(c.target).trigger("mousemove-filtered",c)})}function v(a,c,d){d=d||b;var e;return function(){var b=arguments;window.clearTimeout(e),e=window.setTimeout(function(){c.apply(d,b)},a)}}function w(a){var c,b=!1;return function(){return b===!1&&(c=a(),b=!0),c}}function x(a,b){var c=v(a,function(a){b.trigger("scroll-debounced",a)});b.on("scroll",function(a){o(a.target,b.get())>=0&&c(a)})}function y(a){a[0]!==document.activeElement&&window.setTimeout(function(){var d,b=a[0],c=a.val().length;a.focus(),a.is(":visible")&&b===document.activeElement&&(b.setSelectionRange?b.setSelectionRange(c,c):b.createTextRange&&(d=b.createTextRange(),d.collapse(!1),d.select()))},0)}function z(b){b=a(b)[0];var c=0,d=0;if("selectionStart"in b)c=b.selectionStart,d=b.selectionEnd-c;else if("selection"in document){b.focus();var e=document.selection.createRange();d=document.selection.createRange().text.length,e.moveStart("character",-b.value.length),c=e.text.length-d}return{offset:c,length:d}}function A(a){a.preventDefault(),a.stopPropagation()}function B(a){a.preventDefault(),a.stopImmediatePropagation()}function C(b){if(!h){var c=b[0].currentStyle||window.getComputedStyle(b[0],null);h=a(document.createElement("div")).css({position:"absolute",left:"-10000px",top:"-10000px",display:"none",fontSize:c.fontSize,fontFamily:c.fontFamily,fontStyle:c.fontStyle,fontWeight:c.fontWeight,letterSpacing:c.letterSpacing,textTransform:c.textTransform,whiteSpace:"nowrap"}),h.attr("class","select2-sizer"),a("body").append(h)}return h.text(b.val()),h.width()}function D(b,c,d){var e,g,f=[];e=b.attr("class"),e&&(e=""+e,a(e.split(" ")).each2(function(){0===this.indexOf("select2-")&&f.push(this)})),e=c.attr("class"),e&&(e=""+e,a(e.split(" ")).each2(function(){0!==this.indexOf("select2-")&&(g=d(this),g&&f.push(g))})),b.attr("class",f.join(" "))}function E(a,b,c,d){var e=n(a.toUpperCase()).indexOf(n(b.toUpperCase())),f=b.length;return 0>e?(c.push(d(a)),void 0):(c.push(d(a.substring(0,e))),c.push("<span class='select2-match'>"),c.push(d(a.substring(e,e+f))),c.push("</span>"),c.push(d(a.substring(e+f,a.length))),void 0)}function F(a){var b={"\\":"\","&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return String(a).replace(/[&<>"'\/\\]/g,function(a){return b[a]})}function G(c){var d,e=null,f=c.quietMillis||100,g=c.url,h=this;return function(i){window.clearTimeout(d),d=window.setTimeout(function(){var d=c.data,f=g,j=c.transport||a.fn.select2.ajaxDefaults.transport,k={type:c.type||"GET",cache:c.cache||!1,jsonpCallback:c.jsonpCallback||b,dataType:c.dataType||"json"},l=a.extend({},a.fn.select2.ajaxDefaults.params,k);d=d?d.call(h,i.term,i.page,i.context):null,f="function"==typeof f?f.call(h,i.term,i.page,i.context):f,e&&e.abort(),c.params&&(a.isFunction(c.params)?a.extend(l,c.params.call(h)):a.extend(l,c.params)),a.extend(l,{url:f,dataType:c.dataType,data:d,success:function(a){var b=c.results(a,i.page);i.callback(b)}}),e=j.call(h,l)},f)}}function H(b){var d,e,c=b,f=function(a){return""+a.text};a.isArray(c)&&(e=c,c={results:e}),a.isFunction(c)===!1&&(e=c,c=function(){return e});var g=c();return g.text&&(f=g.text,a.isFunction(f)||(d=g.text,f=function(a){return a[d]})),function(b){var g,d=b.term,e={results:[]};return""===d?(b.callback(c()),void 0):(g=function(c,e){var h,i;if(c=c[0],c.children){h={};for(i in c)c.hasOwnProperty(i)&&(h[i]=c[i]);h.children=[],a(c.children).each2(function(a,b){g(b,h.children)}),(h.children.length||b.matcher(d,f(h),c))&&e.push(h)}else b.matcher(d,f(c),c)&&e.push(c)},a(c().results).each2(function(a,b){g(b,e.results)}),b.callback(e),void 0)}}function I(c){var d=a.isFunction(c);return function(e){var f=e.term,g={results:[]};a(d?c():c).each(function(){var a=this.text!==b,c=a?this.text:this;(""===f||e.matcher(f,c))&&g.results.push(a?this:{id:this,text:this})}),e.callback(g)}}function J(b,c){if(a.isFunction(b))return!0;if(!b)return!1;throw new Error(c+" must be a function or a falsy value")}function K(b){return a.isFunction(b)?b():b}function L(b){var c=0;return a.each(b,function(a,b){b.children?c+=L(b.children):c++}),c}function M(a,c,d,e){var h,i,j,k,l,f=a,g=!1;if(!e.createSearchChoice||!e.tokenSeparators||e.tokenSeparators.length<1)return b;for(;;){for(i=-1,j=0,k=e.tokenSeparators.length;k>j&&(l=e.tokenSeparators[j],i=a.indexOf(l),!(i>=0));j++);if(0>i)break;if(h=a.substring(0,i),a=a.substring(i+l.length),h.length>0&&(h=e.createSearchChoice.call(this,h,c),h!==b&&null!==h&&e.id(h)!==b&&null!==e.id(h))){for(g=!1,j=0,k=c.length;k>j;j++)if(q(e.id(h),e.id(c[j]))){g=!0;break}g||d(h)}}return f!==a?a:void 0}function N(b,c){var d=function(){};return d.prototype=new b,d.prototype.constructor=d,d.prototype.parent=b.prototype,d.prototype=a.extend(d.prototype,c),d}if(window.Select2===b){var c,d,e,f,g,h,j,k,i={x:0,y:0},c={TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,SHIFT:16,CTRL:17,ALT:18,PAGE_UP:33,PAGE_DOWN:34,HOME:36,END:35,BACKSPACE:8,DELETE:46,isArrow:function(a){switch(a=a.which?a.which:a){case c.LEFT:case c.RIGHT:case c.UP:case c.DOWN:return!0}return!1},isControl:function(a){var b=a.which;switch(b){case c.SHIFT:case c.CTRL:case c.ALT:return!0}return a.metaKey?!0:!1},isFunctionKey:function(a){return a=a.which?a.which:a,a>=112&&123>=a}},l="<div class='select2-measure-scrollbar'></div>",m={"\u24b6":"A","\uff21":"A","\xc0":"A","\xc1":"A","\xc2":"A","\u1ea6":"A","\u1ea4":"A","\u1eaa":"A","\u1ea8":"A","\xc3":"A","\u0100":"A","\u0102":"A","\u1eb0":"A","\u1eae":"A","\u1eb4":"A","\u1eb2":"A","\u0226":"A","\u01e0":"A","\xc4":"A","\u01de":"A","\u1ea2":"A","\xc5":"A","\u01fa":"A","\u01cd":"A","\u0200":"A","\u0202":"A","\u1ea0":"A","\u1eac":"A","\u1eb6":"A","\u1e00":"A","\u0104":"A","\u023a":"A","\u2c6f":"A","\ua732":"AA","\xc6":"AE","\u01fc":"AE","\u01e2":"AE","\ua734":"AO","\ua736":"AU","\ua738":"AV","\ua73a":"AV","\ua73c":"AY","\u24b7":"B","\uff22":"B","\u1e02":"B","\u1e04":"B","\u1e06":"B","\u0243":"B","\u0182":"B","\u0181":"B","\u24b8":"C","\uff23":"C","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\xc7":"C","\u1e08":"C","\u0187":"C","\u023b":"C","\ua73e":"C","\u24b9":"D","\uff24":"D","\u1e0a":"D","\u010e":"D","\u1e0c":"D","\u1e10":"D","\u1e12":"D","\u1e0e":"D","\u0110":"D","\u018b":"D","\u018a":"D","\u0189":"D","\ua779":"D","\u01f1":"DZ","\u01c4":"DZ","\u01f2":"Dz","\u01c5":"Dz","\u24ba":"E","\uff25":"E","\xc8":"E","\xc9":"E","\xca":"E","\u1ec0":"E","\u1ebe":"E","\u1ec4":"E","\u1ec2":"E","\u1ebc":"E","\u0112":"E","\u1e14":"E","\u1e16":"E","\u0114":"E","\u0116":"E","\xcb":"E","\u1eba":"E","\u011a":"E","\u0204":"E","\u0206":"E","\u1eb8":"E","\u1ec6":"E","\u0228":"E","\u1e1c":"E","\u0118":"E","\u1e18":"E","\u1e1a":"E","\u0190":"E","\u018e":"E","\u24bb":"F","\uff26":"F","\u1e1e":"F","\u0191":"F","\ua77b":"F","\u24bc":"G","\uff27":"G","\u01f4":"G","\u011c":"G","\u1e20":"G","\u011e":"G","\u0120":"G","\u01e6":"G","\u0122":"G","\u01e4":"G","\u0193":"G","\ua7a0":"G","\ua77d":"G","\ua77e":"G","\u24bd":"H","\uff28":"H","\u0124":"H","\u1e22":"H","\u1e26":"H","\u021e":"H","\u1e24":"H","\u1e28":"H","\u1e2a":"H","\u0126":"H","\u2c67":"H","\u2c75":"H","\ua78d":"H","\u24be":"I","\uff29":"I","\xcc":"I","\xcd":"I","\xce":"I","\u0128":"I","\u012a":"I","\u012c":"I","\u0130":"I","\xcf":"I","\u1e2e":"I","\u1ec8":"I","\u01cf":"I","\u0208":"I","\u020a":"I","\u1eca":"I","\u012e":"I","\u1e2c":"I","\u0197":"I","\u24bf":"J","\uff2a":"J","\u0134":"J","\u0248":"J","\u24c0":"K","\uff2b":"K","\u1e30":"K","\u01e8":"K","\u1e32":"K","\u0136":"K","\u1e34":"K","\u0198":"K","\u2c69":"K","\ua740":"K","\ua742":"K","\ua744":"K","\ua7a2":"K","\u24c1":"L","\uff2c":"L","\u013f":"L","\u0139":"L","\u013d":"L","\u1e36":"L","\u1e38":"L","\u013b":"L","\u1e3c":"L","\u1e3a":"L","\u0141":"L","\u023d":"L","\u2c62":"L","\u2c60":"L","\ua748":"L","\ua746":"L","\ua780":"L","\u01c7":"LJ","\u01c8":"Lj","\u24c2":"M","\uff2d":"M","\u1e3e":"M","\u1e40":"M","\u1e42":"M","\u2c6e":"M","\u019c":"M","\u24c3":"N","\uff2e":"N","\u01f8":"N","\u0143":"N","\xd1":"N","\u1e44":"N","\u0147":"N","\u1e46":"N","\u0145":"N","\u1e4a":"N","\u1e48":"N","\u0220":"N","\u019d":"N","\ua790":"N","\ua7a4":"N","\u01ca":"NJ","\u01cb":"Nj","\u24c4":"O","\uff2f":"O","\xd2":"O","\xd3":"O","\xd4":"O","\u1ed2":"O","\u1ed0":"O","\u1ed6":"O","\u1ed4":"O","\xd5":"O","\u1e4c":"O","\u022c":"O","\u1e4e":"O","\u014c":"O","\u1e50":"O","\u1e52":"O","\u014e":"O","\u022e":"O","\u0230":"O","\xd6":"O","\u022a":"O","\u1ece":"O","\u0150":"O","\u01d1":"O","\u020c":"O","\u020e":"O","\u01a0":"O","\u1edc":"O","\u1eda":"O","\u1ee0":"O","\u1ede":"O","\u1ee2":"O","\u1ecc":"O","\u1ed8":"O","\u01ea":"O","\u01ec":"O","\xd8":"O","\u01fe":"O","\u0186":"O","\u019f":"O","\ua74a":"O","\ua74c":"O","\u01a2":"OI","\ua74e":"OO","\u0222":"OU","\u24c5":"P","\uff30":"P","\u1e54":"P","\u1e56":"P","\u01a4":"P","\u2c63":"P","\ua750":"P","\ua752":"P","\ua754":"P","\u24c6":"Q","\uff31":"Q","\ua756":"Q","\ua758":"Q","\u024a":"Q","\u24c7":"R","\uff32":"R","\u0154":"R","\u1e58":"R","\u0158":"R","\u0210":"R","\u0212":"R","\u1e5a":"R","\u1e5c":"R","\u0156":"R","\u1e5e":"R","\u024c":"R","\u2c64":"R","\ua75a":"R","\ua7a6":"R","\ua782":"R","\u24c8":"S","\uff33":"S","\u1e9e":"S","\u015a":"S","\u1e64":"S","\u015c":"S","\u1e60":"S","\u0160":"S","\u1e66":"S","\u1e62":"S","\u1e68":"S","\u0218":"S","\u015e":"S","\u2c7e":"S","\ua7a8":"S","\ua784":"S","\u24c9":"T","\uff34":"T","\u1e6a":"T","\u0164":"T","\u1e6c":"T","\u021a":"T","\u0162":"T","\u1e70":"T","\u1e6e":"T","\u0166":"T","\u01ac":"T","\u01ae":"T","\u023e":"T","\ua786":"T","\ua728":"TZ","\u24ca":"U","\uff35":"U","\xd9":"U","\xda":"U","\xdb":"U","\u0168":"U","\u1e78":"U","\u016a":"U","\u1e7a":"U","\u016c":"U","\xdc":"U","\u01db":"U","\u01d7":"U","\u01d5":"U","\u01d9":"U","\u1ee6":"U","\u016e":"U","\u0170":"U","\u01d3":"U","\u0214":"U","\u0216":"U","\u01af":"U","\u1eea":"U","\u1ee8":"U","\u1eee":"U","\u1eec":"U","\u1ef0":"U","\u1ee4":"U","\u1e72":"U","\u0172":"U","\u1e76":"U","\u1e74":"U","\u0244":"U","\u24cb":"V","\uff36":"V","\u1e7c":"V","\u1e7e":"V","\u01b2":"V","\ua75e":"V","\u0245":"V","\ua760":"VY","\u24cc":"W","\uff37":"W","\u1e80":"W","\u1e82":"W","\u0174":"W","\u1e86":"W","\u1e84":"W","\u1e88":"W","\u2c72":"W","\u24cd":"X","\uff38":"X","\u1e8a":"X","\u1e8c":"X","\u24ce":"Y","\uff39":"Y","\u1ef2":"Y","\xdd":"Y","\u0176":"Y","\u1ef8":"Y","\u0232":"Y","\u1e8e":"Y","\u0178":"Y","\u1ef6":"Y","\u1ef4":"Y","\u01b3":"Y","\u024e":"Y","\u1efe":"Y","\u24cf":"Z","\uff3a":"Z","\u0179":"Z","\u1e90":"Z","\u017b":"Z","\u017d":"Z","\u1e92":"Z","\u1e94":"Z","\u01b5":"Z","\u0224":"Z","\u2c7f":"Z","\u2c6b":"Z","\ua762":"Z","\u24d0":"a","\uff41":"a","\u1e9a":"a","\xe0":"a","\xe1":"a","\xe2":"a","\u1ea7":"a","\u1ea5":"a","\u1eab":"a","\u1ea9":"a","\xe3":"a","\u0101":"a","\u0103":"a","\u1eb1":"a","\u1eaf":"a","\u1eb5":"a","\u1eb3":"a","\u0227":"a","\u01e1":"a","\xe4":"a","\u01df":"a","\u1ea3":"a","\xe5":"a","\u01fb":"a","\u01ce":"a","\u0201":"a","\u0203":"a","\u1ea1":"a","\u1ead":"a","\u1eb7":"a","\u1e01":"a","\u0105":"a","\u2c65":"a","\u0250":"a","\ua733":"aa","\xe6":"ae","\u01fd":"ae","\u01e3":"ae","\ua735":"ao","\ua737":"au","\ua739":"av","\ua73b":"av","\ua73d":"ay","\u24d1":"b","\uff42":"b","\u1e03":"b","\u1e05":"b","\u1e07":"b","\u0180":"b","\u0183":"b","\u0253":"b","\u24d2":"c","\uff43":"c","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\xe7":"c","\u1e09":"c","\u0188":"c","\u023c":"c","\ua73f":"c","\u2184":"c","\u24d3":"d","\uff44":"d","\u1e0b":"d","\u010f":"d","\u1e0d":"d","\u1e11":"d","\u1e13":"d","\u1e0f":"d","\u0111":"d","\u018c":"d","\u0256":"d","\u0257":"d","\ua77a":"d","\u01f3":"dz","\u01c6":"dz","\u24d4":"e","\uff45":"e","\xe8":"e","\xe9":"e","\xea":"e","\u1ec1":"e","\u1ebf":"e","\u1ec5":"e","\u1ec3":"e","\u1ebd":"e","\u0113":"e","\u1e15":"e","\u1e17":"e","\u0115":"e","\u0117":"e","\xeb":"e","\u1ebb":"e","\u011b":"e","\u0205":"e","\u0207":"e","\u1eb9":"e","\u1ec7":"e","\u0229":"e","\u1e1d":"e","\u0119":"e","\u1e19":"e","\u1e1b":"e","\u0247":"e","\u025b":"e","\u01dd":"e","\u24d5":"f","\uff46":"f","\u1e1f":"f","\u0192":"f","\ua77c":"f","\u24d6":"g","\uff47":"g","\u01f5":"g","\u011d":"g","\u1e21":"g","\u011f":"g","\u0121":"g","\u01e7":"g","\u0123":"g","\u01e5":"g","\u0260":"g","\ua7a1":"g","\u1d79":"g","\ua77f":"g","\u24d7":"h","\uff48":"h","\u0125":"h","\u1e23":"h","\u1e27":"h","\u021f":"h","\u1e25":"h","\u1e29":"h","\u1e2b":"h","\u1e96":"h","\u0127":"h","\u2c68":"h","\u2c76":"h","\u0265":"h","\u0195":"hv","\u24d8":"i","\uff49":"i","\xec":"i","\xed":"i","\xee":"i","\u0129":"i","\u012b":"i","\u012d":"i","\xef":"i","\u1e2f":"i","\u1ec9":"i","\u01d0":"i","\u0209":"i","\u020b":"i","\u1ecb":"i","\u012f":"i","\u1e2d":"i","\u0268":"i","\u0131":"i","\u24d9":"j","\uff4a":"j","\u0135":"j","\u01f0":"j","\u0249":"j","\u24da":"k","\uff4b":"k","\u1e31":"k","\u01e9":"k","\u1e33":"k","\u0137":"k","\u1e35":"k","\u0199":"k","\u2c6a":"k","\ua741":"k","\ua743":"k","\ua745":"k","\ua7a3":"k","\u24db":"l","\uff4c":"l","\u0140":"l","\u013a":"l","\u013e":"l","\u1e37":"l","\u1e39":"l","\u013c":"l","\u1e3d":"l","\u1e3b":"l","\u017f":"l","\u0142":"l","\u019a":"l","\u026b":"l","\u2c61":"l","\ua749":"l","\ua781":"l","\ua747":"l","\u01c9":"lj","\u24dc":"m","\uff4d":"m","\u1e3f":"m","\u1e41":"m","\u1e43":"m","\u0271":"m","\u026f":"m","\u24dd":"n","\uff4e":"n","\u01f9":"n","\u0144":"n","\xf1":"n","\u1e45":"n","\u0148":"n","\u1e47":"n","\u0146":"n","\u1e4b":"n","\u1e49":"n","\u019e":"n","\u0272":"n","\u0149":"n","\ua791":"n","\ua7a5":"n","\u01cc":"nj","\u24de":"o","\uff4f":"o","\xf2":"o","\xf3":"o","\xf4":"o","\u1ed3":"o","\u1ed1":"o","\u1ed7":"o","\u1ed5":"o","\xf5":"o","\u1e4d":"o","\u022d":"o","\u1e4f":"o","\u014d":"o","\u1e51":"o","\u1e53":"o","\u014f":"o","\u022f":"o","\u0231":"o","\xf6":"o","\u022b":"o","\u1ecf":"o","\u0151":"o","\u01d2":"o","\u020d":"o","\u020f":"o","\u01a1":"o","\u1edd":"o","\u1edb":"o","\u1ee1":"o","\u1edf":"o","\u1ee3":"o","\u1ecd":"o","\u1ed9":"o","\u01eb":"o","\u01ed":"o","\xf8":"o","\u01ff":"o","\u0254":"o","\ua74b":"o","\ua74d":"o","\u0275":"o","\u01a3":"oi","\u0223":"ou","\ua74f":"oo","\u24df":"p","\uff50":"p","\u1e55":"p","\u1e57":"p","\u01a5":"p","\u1d7d":"p","\ua751":"p","\ua753":"p","\ua755":"p","\u24e0":"q","\uff51":"q","\u024b":"q","\ua757":"q","\ua759":"q","\u24e1":"r","\uff52":"r","\u0155":"r","\u1e59":"r","\u0159":"r","\u0211":"r","\u0213":"r","\u1e5b":"r","\u1e5d":"r","\u0157":"r","\u1e5f":"r","\u024d":"r","\u027d":"r","\ua75b":"r","\ua7a7":"r","\ua783":"r","\u24e2":"s","\uff53":"s","\xdf":"s","\u015b":"s","\u1e65":"s","\u015d":"s","\u1e61":"s","\u0161":"s","\u1e67":"s","\u1e63":"s","\u1e69":"s","\u0219":"s","\u015f":"s","\u023f":"s","\ua7a9":"s","\ua785":"s","\u1e9b":"s","\u24e3":"t","\uff54":"t","\u1e6b":"t","\u1e97":"t","\u0165":"t","\u1e6d":"t","\u021b":"t","\u0163":"t","\u1e71":"t","\u1e6f":"t","\u0167":"t","\u01ad":"t","\u0288":"t","\u2c66":"t","\ua787":"t","\ua729":"tz","\u24e4":"u","\uff55":"u","\xf9":"u","\xfa":"u","\xfb":"u","\u0169":"u","\u1e79":"u","\u016b":"u","\u1e7b":"u","\u016d":"u","\xfc":"u","\u01dc":"u","\u01d8":"u","\u01d6":"u","\u01da":"u","\u1ee7":"u","\u016f":"u","\u0171":"u","\u01d4":"u","\u0215":"u","\u0217":"u","\u01b0":"u","\u1eeb":"u","\u1ee9":"u","\u1eef":"u","\u1eed":"u","\u1ef1":"u","\u1ee5":"u","\u1e73":"u","\u0173":"u","\u1e77":"u","\u1e75":"u","\u0289":"u","\u24e5":"v","\uff56":"v","\u1e7d":"v","\u1e7f":"v","\u028b":"v","\ua75f":"v","\u028c":"v","\ua761":"vy","\u24e6":"w","\uff57":"w","\u1e81":"w","\u1e83":"w","\u0175":"w","\u1e87":"w","\u1e85":"w","\u1e98":"w","\u1e89":"w","\u2c73":"w","\u24e7":"x","\uff58":"x","\u1e8b":"x","\u1e8d":"x","\u24e8":"y","\uff59":"y","\u1ef3":"y","\xfd":"y","\u0177":"y","\u1ef9":"y","\u0233":"y","\u1e8f":"y","\xff":"y","\u1ef7":"y","\u1e99":"y","\u1ef5":"y","\u01b4":"y","\u024f":"y","\u1eff":"y","\u24e9":"z","\uff5a":"z","\u017a":"z","\u1e91":"z","\u017c":"z","\u017e":"z","\u1e93":"z","\u1e95":"z","\u01b6":"z","\u0225":"z","\u0240":"z","\u2c6c":"z","\ua763":"z"};j=a(document),g=function(){var a=1;return function(){return a++}}(),j.on("mousemove",function(a){i.x=a.pageX,i.y=a.pageY}),d=N(Object,{bind:function(a){var b=this;return function(){a.apply(b,arguments)}},init:function(c){var d,e,f=".select2-results";this.opts=c=this.prepareOpts(c),this.id=c.id,c.element.data("select2")!==b&&null!==c.element.data("select2")&&c.element.data("select2").destroy(),this.container=this.createContainer(),this.containerId="s2id_"+(c.element.attr("id")||"autogen"+g()),this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g,"\\$1"),this.container.attr("id",this.containerId),this.body=w(function(){return c.element.closest("body")}),D(this.container,this.opts.element,this.opts.adaptContainerCssClass),this.container.attr("style",c.element.attr("style")),this.container.css(K(c.containerCss)),this.container.addClass(K(c.containerCssClass)),this.elementTabIndex=this.opts.element.attr("tabindex"),this.opts.element.data("select2",this).attr("tabindex","-1").before(this.container).on("click.select2",A),this.container.data("select2",this),this.dropdown=this.container.find(".select2-drop"),D(this.dropdown,this.opts.element,this.opts.adaptDropdownCssClass),this.dropdown.addClass(K(c.dropdownCssClass)),this.dropdown.data("select2",this),this.dropdown.on("click",A),this.results=d=this.container.find(f),this.search=e=this.container.find("input.select2-input"),this.queryCount=0,this.resultsPage=0,this.context=null,this.initContainer(),this.container.on("click",A),u(this.results),this.dropdown.on("mousemove-filtered touchstart touchmove touchend",f,this.bind(this.highlightUnderEvent)),x(80,this.results),this.dropdown.on("scroll-debounced",f,this.bind(this.loadMoreIfNeeded)),a(this.container).on("change",".select2-input",function(a){a.stopPropagation()}),a(this.dropdown).on("change",".select2-input",function(a){a.stopPropagation()}),a.fn.mousewheel&&d.mousewheel(function(a,b,c,e){var f=d.scrollTop();e>0&&0>=f-e?(d.scrollTop(0),A(a)):0>e&&d.get(0).scrollHeight-d.scrollTop()+e<=d.height()&&(d.scrollTop(d.get(0).scrollHeight-d.height()),A(a))}),t(e),e.on("keyup-change input paste",this.bind(this.updateResults)),e.on("focus",function(){e.addClass("select2-focused")}),e.on("blur",function(){e.removeClass("select2-focused")}),this.dropdown.on("mouseup",f,this.bind(function(b){a(b.target).closest(".select2-result-selectable").length>0&&(this.highlightUnderEvent(b),this.selectHighlighted(b))})),this.dropdown.on("click mouseup mousedown",function(a){a.stopPropagation()}),a.isFunction(this.opts.initSelection)&&(this.initSelection(),this.monitorSource()),null!==c.maximumInputLength&&this.search.attr("maxlength",c.maximumInputLength);var h=c.element.prop("disabled");h===b&&(h=!1),this.enable(!h);var i=c.element.prop("readonly");i===b&&(i=!1),this.readonly(i),k=k||p(),this.autofocus=c.element.prop("autofocus"),c.element.prop("autofocus",!1),this.autofocus&&this.focus(),this.nextSearchTerm=b},destroy:function(){var a=this.opts.element,c=a.data("select2");this.close(),this.propertyObserver&&(delete this.propertyObserver,this.propertyObserver=null),c!==b&&(c.container.remove(),c.dropdown.remove(),a.removeClass("select2-offscreen").removeData("select2").off(".select2").prop("autofocus",this.autofocus||!1),this.elementTabIndex?a.attr({tabindex:this.elementTabIndex}):a.removeAttr("tabindex"),a.show())},optionToData:function(a){return a.is("option")?{id:a.prop("value"),text:a.text(),element:a.get(),css:a.attr("class"),disabled:a.prop("disabled"),locked:q(a.attr("locked"),"locked")||q(a.data("locked"),!0)}:a.is("optgroup")?{text:a.attr("label"),children:[],element:a.get(),css:a.attr("class")}:void 0},prepareOpts:function(c){var d,e,f,g,h=this;if(d=c.element,"select"===d.get(0).tagName.toLowerCase()&&(this.select=e=c.element),e&&a.each(["id","multiple","ajax","query","createSearchChoice","initSelection","data","tags"],function(){if(this in c)throw new Error("Option '"+this+"' is not allowed for Select2 when attached to a <select> element.")}),c=a.extend({},{populateResults:function(d,e,f){var g,i=this.opts.id;g=function(d,e,j){var k,l,m,n,o,p,q,r,s,t;for(d=c.sortResults(d,e,f),k=0,l=d.length;l>k;k+=1)m=d[k],o=m.disabled===!0,n=!o&&i(m)!==b,p=m.children&&m.children.length>0,q=a("<li></li>"),q.addClass("select2-results-dept-"+j),q.addClass("select2-result"),q.addClass(n?"select2-result-selectable":"select2-result-unselectable"),o&&q.addClass("select2-disabled"),p&&q.addClass("select2-result-with-children"),q.addClass(h.opts.formatResultCssClass(m)),r=a(document.createElement("div")),r.addClass("select2-result-label"),t=c.formatResult(m,r,f,h.opts.escapeMarkup),t!==b&&r.html(t),q.append(r),p&&(s=a("<ul></ul>"),s.addClass("select2-result-sub"),g(m.children,s,j+1),q.append(s)),q.data("select2-data",m),e.append(q)},g(e,d,0)}},a.fn.select2.defaults,c),"function"!=typeof c.id&&(f=c.id,c.id=function(a){return a[f]}),a.isArray(c.element.data("select2Tags"))){if("tags"in c)throw"tags specified as both an attribute 'data-select2-tags' and in options of Select2 "+c.element.attr("id");c.tags=c.element.data("select2Tags")}if(e?(c.query=this.bind(function(a){var f,g,i,c={results:[],more:!1},e=a.term;i=function(b,c){var d;b.is("option")?a.matcher(e,b.text(),b)&&c.push(h.optionToData(b)):b.is("optgroup")&&(d=h.optionToData(b),b.children().each2(function(a,b){i(b,d.children)}),d.children.length>0&&c.push(d))},f=d.children(),this.getPlaceholder()!==b&&f.length>0&&(g=this.getPlaceholderOption(),g&&(f=f.not(g))),f.each2(function(a,b){i(b,c.results)}),a.callback(c)}),c.id=function(a){return a.id},c.formatResultCssClass=function(a){return a.css}):"query"in c||("ajax"in c?(g=c.element.data("ajax-url"),g&&g.length>0&&(c.ajax.url=g),c.query=G.call(c.element,c.ajax)):"data"in c?c.query=H(c.data):"tags"in c&&(c.query=I(c.tags),c.createSearchChoice===b&&(c.createSearchChoice=function(b){return{id:a.trim(b),text:a.trim(b)}}),c.initSelection===b&&(c.initSelection=function(b,d){var e=[];a(r(b.val(),c.separator)).each(function(){var b={id:this,text:this},d=c.tags;a.isFunction(d)&&(d=d()),a(d).each(function(){return q(this.id,b.id)?(b=this,!1):void 0}),e.push(b)}),d(e)}))),"function"!=typeof c.query)throw"query function not defined for Select2 "+c.element.attr("id");return c},monitorSource:function(){var c,d,a=this.opts.element;a.on("change.select2",this.bind(function(){this.opts.element.data("select2-change-triggered")!==!0&&this.initSelection()})),c=this.bind(function(){var c=a.prop("disabled");c===b&&(c=!1),this.enable(!c);var d=a.prop("readonly");d===b&&(d=!1),this.readonly(d),D(this.container,this.opts.element,this.opts.adaptContainerCssClass),this.container.addClass(K(this.opts.containerCssClass)),D(this.dropdown,this.opts.element,this.opts.adaptDropdownCssClass),this.dropdown.addClass(K(this.opts.dropdownCssClass))}),a.on("propertychange.select2",c),this.mutationCallback===b&&(this.mutationCallback=function(a){a.forEach(c)}),d=window.MutationObserver||window.WebKitMutationObserver||window.MozMutationObserver,d!==b&&(this.propertyObserver&&(delete this.propertyObserver,this.propertyObserver=null),this.propertyObserver=new d(this.mutationCallback),this.propertyObserver.observe(a.get(0),{attributes:!0,subtree:!1}))},triggerSelect:function(b){var c=a.Event("select2-selecting",{val:this.id(b),object:b});return this.opts.element.trigger(c),!c.isDefaultPrevented()},triggerChange:function(b){b=b||{},b=a.extend({},b,{type:"change",val:this.val()}),this.opts.element.data("select2-change-triggered",!0),this.opts.element.trigger(b),this.opts.element.data("select2-change-triggered",!1),this.opts.element.click(),this.opts.blurOnChange&&this.opts.element.blur()},isInterfaceEnabled:function(){return this.enabledInterface===!0},enableInterface:function(){var a=this._enabled&&!this._readonly,b=!a;return a===this.enabledInterface?!1:(this.container.toggleClass("select2-container-disabled",b),this.close(),this.enabledInterface=a,!0)},enable:function(a){a===b&&(a=!0),this._enabled!==a&&(this._enabled=a,this.opts.element.prop("disabled",!a),this.enableInterface())},disable:function(){this.enable(!1)},readonly:function(a){return a===b&&(a=!1),this._readonly===a?!1:(this._readonly=a,this.opts.element.prop("readonly",a),this.enableInterface(),!0)},opened:function(){return this.container.hasClass("select2-dropdown-open")},positionDropdown:function(){var t,u,v,w,x,b=this.dropdown,c=this.container.offset(),d=this.container.outerHeight(!1),e=this.container.outerWidth(!1),f=b.outerHeight(!1),g=a(window),h=g.width(),i=g.height(),j=g.scrollLeft()+h,l=g.scrollTop()+i,m=c.top+d,n=c.left,o=l>=m+f,p=c.top-f>=this.body().scrollTop(),q=b.outerWidth(!1),r=j>=n+q,s=b.hasClass("select2-drop-above");s?(u=!0,!p&&o&&(v=!0,u=!1)):(u=!1,!o&&p&&(v=!0,u=!0)),v&&(b.hide(),c=this.container.offset(),d=this.container.outerHeight(!1),e=this.container.outerWidth(!1),f=b.outerHeight(!1),j=g.scrollLeft()+h,l=g.scrollTop()+i,m=c.top+d,n=c.left,q=b.outerWidth(!1),r=j>=n+q,b.show()),this.opts.dropdownAutoWidth?(x=a(".select2-results",b)[0],b.addClass("select2-drop-auto-width"),b.css("width",""),q=b.outerWidth(!1)+(x.scrollHeight===x.clientHeight?0:k.width),q>e?e=q:q=e,r=j>=n+q):this.container.removeClass("select2-drop-auto-width"),"static"!==this.body().css("position")&&(t=this.body().offset(),m-=t.top,n-=t.left),r||(n=c.left+e-q),w={left:n,width:e},u?(w.bottom=i-c.top,w.top="auto",this.container.addClass("select2-drop-above"),b.addClass("select2-drop-above")):(w.top=m,w.bottom="auto",this.container.removeClass("select2-drop-above"),b.removeClass("select2-drop-above")),w=a.extend(w,K(this.opts.dropdownCss)),b.css(w)},shouldOpen:function(){var b;return this.opened()?!1:this._enabled===!1||this._readonly===!0?!1:(b=a.Event("select2-opening"),this.opts.element.trigger(b),!b.isDefaultPrevented())},clearDropdownAlignmentPreference:function(){this.container.removeClass("select2-drop-above"),this.dropdown.removeClass("select2-drop-above")},open:function(){return this.shouldOpen()?(this.opening(),!0):!1},opening:function(){var f,b=this.containerId,c="scroll."+b,d="resize."+b,e="orientationchange."+b;this.container.addClass("select2-dropdown-open").addClass("select2-container-active"),this.clearDropdownAlignmentPreference(),this.dropdown[0]!==this.body().children().last()[0]&&this.dropdown.detach().appendTo(this.body()),f=a("#select2-drop-mask"),0==f.length&&(f=a(document.createElement("div")),f.attr("id","select2-drop-mask").attr("class","select2-drop-mask"),f.hide(),f.appendTo(this.body()),f.on("mousedown touchstart click",function(b){var d,c=a("#select2-drop");c.length>0&&(d=c.data("select2"),d.opts.selectOnBlur&&d.selectHighlighted({noFocus:!0}),d.close({focus:!0}),b.preventDefault(),b.stopPropagation())})),this.dropdown.prev()[0]!==f[0]&&this.dropdown.before(f),a("#select2-drop").removeAttr("id"),this.dropdown.attr("id","select2-drop"),f.show(),this.positionDropdown(),this.dropdown.show(),this.positionDropdown(),this.dropdown.addClass("select2-drop-active");var g=this;this.container.parents().add(window).each(function(){a(this).on(d+" "+c+" "+e,function(){g.positionDropdown()})})},close:function(){if(this.opened()){var b=this.containerId,c="scroll."+b,d="resize."+b,e="orientationchange."+b;this.container.parents().add(window).each(function(){a(this).off(c).off(d).off(e)}),this.clearDropdownAlignmentPreference(),a("#select2-drop-mask").hide(),this.dropdown.removeAttr("id"),this.dropdown.hide(),this.container.removeClass("select2-dropdown-open").removeClass("select2-container-active"),this.results.empty(),this.clearSearch(),this.search.removeClass("select2-active"),this.opts.element.trigger(a.Event("select2-close"))}},externalSearch:function(a){this.open(),this.search.val(a),this.updateResults(!1)},clearSearch:function(){},getMaximumSelectionSize:function(){return K(this.opts.maximumSelectionSize)},ensureHighlightVisible:function(){var c,d,e,f,g,h,i,b=this.results;if(d=this.highlight(),!(0>d)){if(0==d)return b.scrollTop(0),void 0;c=this.findHighlightableChoices().find(".select2-result-label"),e=a(c[d]),f=e.offset().top+e.outerHeight(!0),d===c.length-1&&(i=b.find("li.select2-more-results"),i.length>0&&(f=i.offset().top+i.outerHeight(!0))),g=b.offset().top+b.outerHeight(!0),f>g&&b.scrollTop(b.scrollTop()+(f-g)),h=e.offset().top-b.offset().top,0>h&&"none"!=e.css("display")&&b.scrollTop(b.scrollTop()+h)}},findHighlightableChoices:function(){return this.results.find(".select2-result-selectable:not(.select2-disabled, .select2-selected)")},moveHighlight:function(b){for(var c=this.findHighlightableChoices(),d=this.highlight();d>-1&&d<c.length;){d+=b;var e=a(c[d]);if(e.hasClass("select2-result-selectable")&&!e.hasClass("select2-disabled")&&!e.hasClass("select2-selected")){this.highlight(d);break}}},highlight:function(b){var d,e,c=this.findHighlightableChoices();return 0===arguments.length?o(c.filter(".select2-highlighted")[0],c.get()):(b>=c.length&&(b=c.length-1),0>b&&(b=0),this.removeHighlight(),d=a(c[b]),d.addClass("select2-highlighted"),this.ensureHighlightVisible(),e=d.data("select2-data"),e&&this.opts.element.trigger({type:"select2-highlight",val:this.id(e),choice:e}),void 0)},removeHighlight:function(){this.results.find(".select2-highlighted").removeClass("select2-highlighted")},countSelectableResults:function(){return this.findHighlightableChoices().length},highlightUnderEvent:function(b){var c=a(b.target).closest(".select2-result-selectable");if(c.length>0&&!c.is(".select2-highlighted")){var d=this.findHighlightableChoices();this.highlight(d.index(c))}else 0==c.length&&this.removeHighlight()},loadMoreIfNeeded:function(){var c,a=this.results,b=a.find("li.select2-more-results"),d=this.resultsPage+1,e=this,f=this.search.val(),g=this.context;0!==b.length&&(c=b.offset().top-a.offset().top-a.height(),c<=this.opts.loadMorePadding&&(b.addClass("select2-active"),this.opts.query({element:this.opts.element,term:f,page:d,context:g,matcher:this.opts.matcher,callback:this.bind(function(c){e.opened()&&(e.opts.populateResults.call(this,a,c.results,{term:f,page:d,context:g}),e.postprocessResults(c,!1,!1),c.more===!0?(b.detach().appendTo(a).text(e.opts.formatLoadMore(d+1)),window.setTimeout(function(){e.loadMoreIfNeeded()},10)):b.remove(),e.positionDropdown(),e.resultsPage=d,e.context=c.context,this.opts.element.trigger({type:"select2-loaded",items:c}))})})))},tokenize:function(){},updateResults:function(c){function m(){d.removeClass("select2-active"),h.positionDropdown()}function n(a){e.html(a),m()}var g,i,l,d=this.search,e=this.results,f=this.opts,h=this,j=d.val(),k=a.data(this.container,"select2-last-term");if((c===!0||!k||!q(j,k))&&(a.data(this.container,"select2-last-term",j),c===!0||this.showSearchInput!==!1&&this.opened())){l=++this.queryCount;var o=this.getMaximumSelectionSize();if(o>=1&&(g=this.data(),a.isArray(g)&&g.length>=o&&J(f.formatSelectionTooBig,"formatSelectionTooBig")))return n("<li class='select2-selection-limit'>"+f.formatSelectionTooBig(o)+"</li>"),void 0;if(d.val().length<f.minimumInputLength)return J(f.formatInputTooShort,"formatInputTooShort")?n("<li class='select2-no-results'>"+f.formatInputTooShort(d.val(),f.minimumInputLength)+"</li>"):n(""),c&&this.showSearch&&this.showSearch(!0),void 0; +if(f.maximumInputLength&&d.val().length>f.maximumInputLength)return J(f.formatInputTooLong,"formatInputTooLong")?n("<li class='select2-no-results'>"+f.formatInputTooLong(d.val(),f.maximumInputLength)+"</li>"):n(""),void 0;f.formatSearching&&0===this.findHighlightableChoices().length&&n("<li class='select2-searching'>"+f.formatSearching()+"</li>"),d.addClass("select2-active"),this.removeHighlight(),i=this.tokenize(),i!=b&&null!=i&&d.val(i),this.resultsPage=1,f.query({element:f.element,term:d.val(),page:this.resultsPage,context:null,matcher:f.matcher,callback:this.bind(function(g){var i;if(l==this.queryCount){if(!this.opened())return this.search.removeClass("select2-active"),void 0;if(this.context=g.context===b?null:g.context,this.opts.createSearchChoice&&""!==d.val()&&(i=this.opts.createSearchChoice.call(h,d.val(),g.results),i!==b&&null!==i&&h.id(i)!==b&&null!==h.id(i)&&0===a(g.results).filter(function(){return q(h.id(this),h.id(i))}).length&&g.results.unshift(i)),0===g.results.length&&J(f.formatNoMatches,"formatNoMatches"))return n("<li class='select2-no-results'>"+f.formatNoMatches(d.val())+"</li>"),void 0;e.empty(),h.opts.populateResults.call(this,e,g.results,{term:d.val(),page:this.resultsPage,context:null}),g.more===!0&&J(f.formatLoadMore,"formatLoadMore")&&(e.append("<li class='select2-more-results'>"+h.opts.escapeMarkup(f.formatLoadMore(this.resultsPage))+"</li>"),window.setTimeout(function(){h.loadMoreIfNeeded()},10)),this.postprocessResults(g,c),m(),this.opts.element.trigger({type:"select2-loaded",items:g})}})})}},cancel:function(){this.close()},blur:function(){this.opts.selectOnBlur&&this.selectHighlighted({noFocus:!0}),this.close(),this.container.removeClass("select2-container-active"),this.search[0]===document.activeElement&&this.search.blur(),this.clearSearch(),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus")},focusSearch:function(){y(this.search)},selectHighlighted:function(a){var b=this.highlight(),c=this.results.find(".select2-highlighted"),d=c.closest(".select2-result").data("select2-data");d?(this.highlight(b),this.onSelect(d,a)):a&&a.noFocus&&this.close()},getPlaceholder:function(){var a;return this.opts.element.attr("placeholder")||this.opts.element.attr("data-placeholder")||this.opts.element.data("placeholder")||this.opts.placeholder||((a=this.getPlaceholderOption())!==b?a.text():b)},getPlaceholderOption:function(){if(this.select){var a=this.select.children("option").first();if(this.opts.placeholderOption!==b)return"first"===this.opts.placeholderOption&&a||"function"==typeof this.opts.placeholderOption&&this.opts.placeholderOption(this.select);if(""===a.text()&&""===a.val())return a}},initContainerWidth:function(){function c(){var c,d,e,f,g,h;if("off"===this.opts.width)return null;if("element"===this.opts.width)return 0===this.opts.element.outerWidth(!1)?"auto":this.opts.element.outerWidth(!1)+"px";if("copy"===this.opts.width||"resolve"===this.opts.width){if(c=this.opts.element.attr("style"),c!==b)for(d=c.split(";"),f=0,g=d.length;g>f;f+=1)if(h=d[f].replace(/\s/g,""),e=h.match(/^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i),null!==e&&e.length>=1)return e[1];return"resolve"===this.opts.width?(c=this.opts.element.css("width"),c.indexOf("%")>0?c:0===this.opts.element.outerWidth(!1)?"auto":this.opts.element.outerWidth(!1)+"px"):null}return a.isFunction(this.opts.width)?this.opts.width():this.opts.width}var d=c.call(this);null!==d&&this.container.css("width",d)}}),e=N(d,{createContainer:function(){var b=a(document.createElement("div")).attr({"class":"select2-container"}).html(["<a href='javascript:void(0)' onclick='return false;' class='select2-choice' tabindex='-1'>"," <span class='select2-chosen'> </span><abbr class='select2-search-choice-close'></abbr>"," <span class='select2-arrow'><b></b></span>","</a>","<input class='select2-focusser select2-offscreen' type='text'/>","<div class='select2-drop select2-display-none'>"," <div class='select2-search'>"," <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'/>"," </div>"," <ul class='select2-results'>"," </ul>","</div>"].join(""));return b},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.focusser.prop("disabled",!this.isInterfaceEnabled())},opening:function(){var c,d,e;this.opts.minimumResultsForSearch>=0&&this.showSearch(!0),this.parent.opening.apply(this,arguments),this.showSearchInput!==!1&&this.search.val(this.focusser.val()),this.search.focus(),c=this.search.get(0),c.createTextRange?(d=c.createTextRange(),d.collapse(!1),d.select()):c.setSelectionRange&&(e=this.search.val().length,c.setSelectionRange(e,e)),""===this.search.val()&&this.nextSearchTerm!=b&&(this.search.val(this.nextSearchTerm),this.search.select()),this.focusser.prop("disabled",!0).val(""),this.updateResults(!0),this.opts.element.trigger(a.Event("select2-open"))},close:function(a){this.opened()&&(this.parent.close.apply(this,arguments),a=a||{focus:!0},this.focusser.removeAttr("disabled"),a.focus&&this.focusser.focus())},focus:function(){this.opened()?this.close():(this.focusser.removeAttr("disabled"),this.focusser.focus())},isFocused:function(){return this.container.hasClass("select2-container-active")},cancel:function(){this.parent.cancel.apply(this,arguments),this.focusser.removeAttr("disabled"),this.focusser.focus()},destroy:function(){a("label[for='"+this.focusser.attr("id")+"']").attr("for",this.opts.element.attr("id")),this.parent.destroy.apply(this,arguments)},initContainer:function(){var b,d=this.container,e=this.dropdown;this.opts.minimumResultsForSearch<0?this.showSearch(!1):this.showSearch(!0),this.selection=b=d.find(".select2-choice"),this.focusser=d.find(".select2-focusser"),this.focusser.attr("id","s2id_autogen"+g()),a("label[for='"+this.opts.element.attr("id")+"']").attr("for",this.focusser.attr("id")),this.focusser.attr("tabindex",this.elementTabIndex),this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()){if(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)return A(a),void 0;switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),A(a),void 0;case c.ENTER:return this.selectHighlighted(),A(a),void 0;case c.TAB:return this.selectHighlighted({noFocus:!0}),void 0;case c.ESC:return this.cancel(a),A(a),void 0}}})),this.search.on("blur",this.bind(function(){document.activeElement===this.body().get(0)&&window.setTimeout(this.bind(function(){this.search.focus()}),0)})),this.focusser.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()&&a.which!==c.TAB&&!c.isControl(a)&&!c.isFunctionKey(a)&&a.which!==c.ESC){if(this.opts.openOnEnter===!1&&a.which===c.ENTER)return A(a),void 0;if(a.which==c.DOWN||a.which==c.UP||a.which==c.ENTER&&this.opts.openOnEnter){if(a.altKey||a.ctrlKey||a.shiftKey||a.metaKey)return;return this.open(),A(a),void 0}return a.which==c.DELETE||a.which==c.BACKSPACE?(this.opts.allowClear&&this.clear(),A(a),void 0):void 0}})),t(this.focusser),this.focusser.on("keyup-change input",this.bind(function(a){if(this.opts.minimumResultsForSearch>=0){if(a.stopPropagation(),this.opened())return;this.open()}})),b.on("mousedown","abbr",this.bind(function(a){this.isInterfaceEnabled()&&(this.clear(),B(a),this.close(),this.selection.focus())})),b.on("mousedown",this.bind(function(b){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.opened()?this.close():this.isInterfaceEnabled()&&this.open(),A(b)})),e.on("mousedown",this.bind(function(){this.search.focus()})),b.on("focus",this.bind(function(a){A(a)})),this.focusser.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active")})).on("blur",this.bind(function(){this.opened()||(this.container.removeClass("select2-container-active"),this.opts.element.trigger(a.Event("select2-blur")))})),this.search.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active")})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.setPlaceholder()},clear:function(b){var c=this.selection.data("select2-data");if(c){var d=a.Event("select2-clearing");if(this.opts.element.trigger(d),d.isDefaultPrevented())return;var e=this.getPlaceholderOption();this.opts.element.val(e?e.val():""),this.selection.find(".select2-chosen").empty(),this.selection.removeData("select2-data"),this.setPlaceholder(),b!==!1&&(this.opts.element.trigger({type:"select2-removed",val:this.id(c),choice:c}),this.triggerChange({removed:c}))}},initSelection:function(){if(this.isPlaceholderOptionSelected())this.updateSelection(null),this.close(),this.setPlaceholder();else{var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.setPlaceholder())})}},isPlaceholderOptionSelected:function(){var a;return this.getPlaceholder()?(a=this.getPlaceholderOption())!==b&&a.prop("selected")||""===this.opts.element.val()||this.opts.element.val()===b||null===this.opts.element.val():!1},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=a.find("option").filter(function(){return this.selected});b(c.optionToData(d))}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=c.val(),f=null;b.query({matcher:function(a,c,d){var g=q(e,b.id(d));return g&&(f=d),g},callback:a.isFunction(d)?function(){d(f)}:a.noop})}),b},getPlaceholder:function(){return this.select&&this.getPlaceholderOption()===b?b:this.parent.getPlaceholder.apply(this,arguments)},setPlaceholder:function(){var a=this.getPlaceholder();if(this.isPlaceholderOptionSelected()&&a!==b){if(this.select&&this.getPlaceholderOption()===b)return;this.selection.find(".select2-chosen").html(this.opts.escapeMarkup(a)),this.selection.addClass("select2-default"),this.container.removeClass("select2-allowclear")}},postprocessResults:function(a,b,c){var d=0,e=this;if(this.findHighlightableChoices().each2(function(a,b){return q(e.id(b.data("select2-data")),e.opts.element.val())?(d=a,!1):void 0}),c!==!1&&(b===!0&&d>=0?this.highlight(d):this.highlight(0)),b===!0){var g=this.opts.minimumResultsForSearch;g>=0&&this.showSearch(L(a.results)>=g)}},showSearch:function(b){this.showSearchInput!==b&&(this.showSearchInput=b,this.dropdown.find(".select2-search").toggleClass("select2-search-hidden",!b),this.dropdown.find(".select2-search").toggleClass("select2-offscreen",!b),a(this.dropdown,this.container).toggleClass("select2-with-searchbox",b))},onSelect:function(a,b){if(this.triggerSelect(a)){var c=this.opts.element.val(),d=this.data();this.opts.element.val(this.id(a)),this.updateSelection(a),this.opts.element.trigger({type:"select2-selected",val:this.id(a),choice:a}),this.nextSearchTerm=this.opts.nextSearchTerm(a,this.search.val()),this.close(),b&&b.noFocus||this.focusser.focus(),q(c,this.id(a))||this.triggerChange({added:a,removed:d})}},updateSelection:function(a){var d,e,c=this.selection.find(".select2-chosen");this.selection.data("select2-data",a),c.empty(),null!==a&&(d=this.opts.formatSelection(a,c,this.opts.escapeMarkup)),d!==b&&c.append(d),e=this.opts.formatSelectionCssClass(a,c),e!==b&&c.addClass(e),this.selection.removeClass("select2-default"),this.opts.allowClear&&this.getPlaceholder()!==b&&this.container.addClass("select2-allowclear")},val:function(){var a,c=!1,d=null,e=this,f=this.data();if(0===arguments.length)return this.opts.element.val();if(a=arguments[0],arguments.length>1&&(c=arguments[1]),this.select)this.select.val(a).find("option").filter(function(){return this.selected}).each2(function(a,b){return d=e.optionToData(b),!1}),this.updateSelection(d),this.setPlaceholder(),c&&this.triggerChange({added:d,removed:f});else{if(!a&&0!==a)return this.clear(c),void 0;if(this.opts.initSelection===b)throw new Error("cannot call val() if initSelection() is not defined");this.opts.element.val(a),this.opts.initSelection(this.opts.element,function(a){e.opts.element.val(a?e.id(a):""),e.updateSelection(a),e.setPlaceholder(),c&&e.triggerChange({added:a,removed:f})})}},clearSearch:function(){this.search.val(""),this.focusser.val("")},data:function(a){var c,d=!1;return 0===arguments.length?(c=this.selection.data("select2-data"),c==b&&(c=null),c):(arguments.length>1&&(d=arguments[1]),a?(c=this.data(),this.opts.element.val(a?this.id(a):""),this.updateSelection(a),d&&this.triggerChange({added:a,removed:c})):this.clear(d),void 0)}}),f=N(d,{createContainer:function(){var b=a(document.createElement("div")).attr({"class":"select2-container select2-container-multi"}).html(["<ul class='select2-choices'>"," <li class='select2-search-field'>"," <input type='text' autocomplete='off' autocorrect='off' autocapitalize='off' spellcheck='false' class='select2-input'>"," </li>","</ul>","<div class='select2-drop select2-drop-multi select2-display-none'>"," <ul class='select2-results'>"," </ul>","</div>"].join(""));return b},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=[];a.find("option").filter(function(){return this.selected}).each2(function(a,b){d.push(c.optionToData(b))}),b(d)}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=r(c.val(),b.separator),f=[];b.query({matcher:function(c,d,g){var h=a.grep(e,function(a){return q(a,b.id(g))}).length;return h&&f.push(g),h},callback:a.isFunction(d)?function(){for(var a=[],c=0;c<e.length;c++)for(var g=e[c],h=0;h<f.length;h++){var i=f[h];if(q(g,b.id(i))){a.push(i),f.splice(h,1);break}}d(a)}:a.noop})}),b},selectChoice:function(a){var b=this.container.find(".select2-search-choice-focus");b.length&&a&&a[0]==b[0]||(b.length&&this.opts.element.trigger("choice-deselected",b),b.removeClass("select2-search-choice-focus"),a&&a.length&&(this.close(),a.addClass("select2-search-choice-focus"),this.opts.element.trigger("choice-selected",a)))},destroy:function(){a("label[for='"+this.search.attr("id")+"']").attr("for",this.opts.element.attr("id")),this.parent.destroy.apply(this,arguments)},initContainer:function(){var d,b=".select2-choices";this.searchContainer=this.container.find(".select2-search-field"),this.selection=d=this.container.find(b);var e=this;this.selection.on("click",".select2-search-choice:not(.select2-locked)",function(){e.search[0].focus(),e.selectChoice(a(this))}),this.search.attr("id","s2id_autogen"+g()),a("label[for='"+this.opts.element.attr("id")+"']").attr("for",this.search.attr("id")),this.search.on("input paste",this.bind(function(){this.isInterfaceEnabled()&&(this.opened()||this.open())})),this.search.attr("tabindex",this.elementTabIndex),this.keydowns=0,this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()){++this.keydowns;var b=d.find(".select2-search-choice-focus"),e=b.prev(".select2-search-choice:not(.select2-locked)"),f=b.next(".select2-search-choice:not(.select2-locked)"),g=z(this.search);if(b.length&&(a.which==c.LEFT||a.which==c.RIGHT||a.which==c.BACKSPACE||a.which==c.DELETE||a.which==c.ENTER)){var h=b;return a.which==c.LEFT&&e.length?h=e:a.which==c.RIGHT?h=f.length?f:null:a.which===c.BACKSPACE?(this.unselect(b.first()),this.search.width(10),h=e.length?e:f):a.which==c.DELETE?(this.unselect(b.first()),this.search.width(10),h=f.length?f:null):a.which==c.ENTER&&(h=null),this.selectChoice(h),A(a),h&&h.length||this.open(),void 0}if((a.which===c.BACKSPACE&&1==this.keydowns||a.which==c.LEFT)&&0==g.offset&&!g.length)return this.selectChoice(d.find(".select2-search-choice:not(.select2-locked)").last()),A(a),void 0;if(this.selectChoice(null),this.opened())switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),A(a),void 0;case c.ENTER:return this.selectHighlighted(),A(a),void 0;case c.TAB:return this.selectHighlighted({noFocus:!0}),this.close(),void 0;case c.ESC:return this.cancel(a),A(a),void 0}if(a.which!==c.TAB&&!c.isControl(a)&&!c.isFunctionKey(a)&&a.which!==c.BACKSPACE&&a.which!==c.ESC){if(a.which===c.ENTER){if(this.opts.openOnEnter===!1)return;if(a.altKey||a.ctrlKey||a.shiftKey||a.metaKey)return}this.open(),(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)&&A(a),a.which===c.ENTER&&A(a)}}})),this.search.on("keyup",this.bind(function(){this.keydowns=0,this.resizeSearch()})),this.search.on("blur",this.bind(function(b){this.container.removeClass("select2-container-active"),this.search.removeClass("select2-focused"),this.selectChoice(null),this.opened()||this.clearSearch(),b.stopImmediatePropagation(),this.opts.element.trigger(a.Event("select2-blur"))})),this.container.on("click",b,this.bind(function(b){this.isInterfaceEnabled()&&(a(b.target).closest(".select2-search-choice").length>0||(this.selectChoice(null),this.clearPlaceholder(),this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.open(),this.focusSearch(),b.preventDefault()))})),this.container.on("focus",b,this.bind(function(){this.isInterfaceEnabled()&&(this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"),this.clearPlaceholder())})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.clearSearch()},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.search.prop("disabled",!this.isInterfaceEnabled())},initSelection:function(){if(""===this.opts.element.val()&&""===this.opts.element.text()&&(this.updateSelection([]),this.close(),this.clearSearch()),this.select||""!==this.opts.element.val()){var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.clearSearch())})}},clearSearch:function(){var a=this.getPlaceholder(),c=this.getMaxSearchWidth();a!==b&&0===this.getVal().length&&this.search.hasClass("select2-focused")===!1?(this.search.val(a).addClass("select2-default"),this.search.width(c>0?c:this.container.css("width"))):this.search.val("").width(10)},clearPlaceholder:function(){this.search.hasClass("select2-default")&&this.search.val("").removeClass("select2-default")},opening:function(){this.clearPlaceholder(),this.resizeSearch(),this.parent.opening.apply(this,arguments),this.focusSearch(),this.updateResults(!0),this.search.focus(),this.opts.element.trigger(a.Event("select2-open"))},close:function(){this.opened()&&this.parent.close.apply(this,arguments)},focus:function(){this.close(),this.search.focus()},isFocused:function(){return this.search.hasClass("select2-focused")},updateSelection:function(b){var c=[],d=[],e=this;a(b).each(function(){o(e.id(this),c)<0&&(c.push(e.id(this)),d.push(this))}),b=d,this.selection.find(".select2-search-choice").remove(),a(b).each(function(){e.addSelectedChoice(this)}),e.postprocessResults()},tokenize:function(){var a=this.search.val();a=this.opts.tokenizer.call(this,a,this.data(),this.bind(this.onSelect),this.opts),null!=a&&a!=b&&(this.search.val(a),a.length>0&&this.open())},onSelect:function(a,b){this.triggerSelect(a)&&(this.addSelectedChoice(a),this.opts.element.trigger({type:"selected",val:this.id(a),choice:a}),(this.select||!this.opts.closeOnSelect)&&this.postprocessResults(a,!1,this.opts.closeOnSelect===!0),this.opts.closeOnSelect?(this.close(),this.search.width(10)):this.countSelectableResults()>0?(this.search.width(10),this.resizeSearch(),this.getMaximumSelectionSize()>0&&this.val().length>=this.getMaximumSelectionSize()&&this.updateResults(!0),this.positionDropdown()):(this.close(),this.search.width(10)),this.triggerChange({added:a}),b&&b.noFocus||this.focusSearch())},cancel:function(){this.close(),this.focusSearch()},addSelectedChoice:function(c){var j,k,d=!c.locked,e=a("<li class='select2-search-choice'> <div></div> <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a></li>"),f=a("<li class='select2-search-choice select2-locked'><div></div></li>"),g=d?e:f,h=this.id(c),i=this.getVal();j=this.opts.formatSelection(c,g.find("div"),this.opts.escapeMarkup),j!=b&&g.find("div").replaceWith("<div>"+j+"</div>"),k=this.opts.formatSelectionCssClass(c,g.find("div")),k!=b&&g.addClass(k),d&&g.find(".select2-search-choice-close").on("mousedown",A).on("click dblclick",this.bind(function(b){this.isInterfaceEnabled()&&(a(b.target).closest(".select2-search-choice").fadeOut("fast",this.bind(function(){this.unselect(a(b.target)),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"),this.close(),this.focusSearch()})).dequeue(),A(b))})).on("focus",this.bind(function(){this.isInterfaceEnabled()&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"))})),g.data("select2-data",c),g.insertBefore(this.searchContainer),i.push(h),this.setVal(i)},unselect:function(b){var d,e,c=this.getVal();if(b=b.closest(".select2-search-choice"),0===b.length)throw"Invalid argument: "+b+". Must be .select2-search-choice";if(d=b.data("select2-data")){for(;(e=o(this.id(d),c))>=0;)c.splice(e,1),this.setVal(c),this.select&&this.postprocessResults();var f=a.Event("select2-removing");f.val=this.id(d),f.choice=d,this.opts.element.trigger(f),f.isDefaultPrevented()||(b.remove(),this.opts.element.trigger({type:"select2-removed",val:this.id(d),choice:d}),this.triggerChange({removed:d}))}},postprocessResults:function(a,b,c){var d=this.getVal(),e=this.results.find(".select2-result"),f=this.results.find(".select2-result-with-children"),g=this;e.each2(function(a,b){var c=g.id(b.data("select2-data"));o(c,d)>=0&&(b.addClass("select2-selected"),b.find(".select2-result-selectable").addClass("select2-selected"))}),f.each2(function(a,b){b.is(".select2-result-selectable")||0!==b.find(".select2-result-selectable:not(.select2-selected)").length||b.addClass("select2-selected")}),-1==this.highlight()&&c!==!1&&g.highlight(0),!this.opts.createSearchChoice&&!e.filter(".select2-result:not(.select2-selected)").length>0&&(!a||a&&!a.more&&0===this.results.find(".select2-no-results").length)&&J(g.opts.formatNoMatches,"formatNoMatches")&&this.results.append("<li class='select2-no-results'>"+g.opts.formatNoMatches(g.search.val())+"</li>")},getMaxSearchWidth:function(){return this.selection.width()-s(this.search)},resizeSearch:function(){var a,b,c,d,e,f=s(this.search);a=C(this.search)+10,b=this.search.offset().left,c=this.selection.width(),d=this.selection.offset().left,e=c-(b-d)-f,a>e&&(e=c-f),40>e&&(e=c-f),0>=e&&(e=a),this.search.width(Math.floor(e))},getVal:function(){var a;return this.select?(a=this.select.val(),null===a?[]:a):(a=this.opts.element.val(),r(a,this.opts.separator))},setVal:function(b){var c;this.select?this.select.val(b):(c=[],a(b).each(function(){o(this,c)<0&&c.push(this)}),this.opts.element.val(0===c.length?"":c.join(this.opts.separator)))},buildChangeDetails:function(a,b){for(var b=b.slice(0),a=a.slice(0),c=0;c<b.length;c++)for(var d=0;d<a.length;d++)q(this.opts.id(b[c]),this.opts.id(a[d]))&&(b.splice(c,1),c>0&&c--,a.splice(d,1),d--);return{added:b,removed:a}},val:function(c,d){var e,f=this;if(0===arguments.length)return this.getVal();if(e=this.data(),e.length||(e=[]),!c&&0!==c)return this.opts.element.val(""),this.updateSelection([]),this.clearSearch(),d&&this.triggerChange({added:this.data(),removed:e}),void 0;if(this.setVal(c),this.select)this.opts.initSelection(this.select,this.bind(this.updateSelection)),d&&this.triggerChange(this.buildChangeDetails(e,this.data()));else{if(this.opts.initSelection===b)throw new Error("val() cannot be called if initSelection() is not defined");this.opts.initSelection(this.opts.element,function(b){var c=a.map(b,f.id);f.setVal(c),f.updateSelection(b),f.clearSearch(),d&&f.triggerChange(f.buildChangeDetails(e,f.data()))})}this.clearSearch()},onSortStart:function(){if(this.select)throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");this.search.width(0),this.searchContainer.hide()},onSortEnd:function(){var b=[],c=this;this.searchContainer.show(),this.searchContainer.appendTo(this.searchContainer.parent()),this.resizeSearch(),this.selection.find(".select2-search-choice").each(function(){b.push(c.opts.id(a(this).data("select2-data")))}),this.setVal(b),this.triggerChange()},data:function(b,c){var e,f,d=this;return 0===arguments.length?this.selection.find(".select2-search-choice").map(function(){return a(this).data("select2-data")}).get():(f=this.data(),b||(b=[]),e=a.map(b,function(a){return d.opts.id(a)}),this.setVal(e),this.updateSelection(b),this.clearSearch(),c&&this.triggerChange(this.buildChangeDetails(f,this.data())),void 0)}}),a.fn.select2=function(){var d,g,h,i,j,c=Array.prototype.slice.call(arguments,0),k=["val","destroy","opened","open","close","focus","isFocused","container","dropdown","onSortStart","onSortEnd","enable","disable","readonly","positionDropdown","data","search"],l=["opened","isFocused","container","dropdown"],m=["val","data"],n={search:"externalSearch"};return this.each(function(){if(0===c.length||"object"==typeof c[0])d=0===c.length?{}:a.extend({},c[0]),d.element=a(this),"select"===d.element.get(0).tagName.toLowerCase()?j=d.element.prop("multiple"):(j=d.multiple||!1,"tags"in d&&(d.multiple=j=!0)),g=j?new f:new e,g.init(d);else{if("string"!=typeof c[0])throw"Invalid arguments to select2 plugin: "+c;if(o(c[0],k)<0)throw"Unknown method: "+c[0];if(i=b,g=a(this).data("select2"),g===b)return;if(h=c[0],"container"===h?i=g.container:"dropdown"===h?i=g.dropdown:(n[h]&&(h=n[h]),i=g[h].apply(g,c.slice(1))),o(c[0],l)>=0||o(c[0],m)&&1==c.length)return!1}}),i===b?this:i},a.fn.select2.defaults={width:"copy",loadMorePadding:0,closeOnSelect:!0,openOnEnter:!0,containerCss:{},dropdownCss:{},containerCssClass:"",dropdownCssClass:"",formatResult:function(a,b,c,d){var e=[];return E(a.text,c.term,e,d),e.join("")},formatSelection:function(a,c,d){return a?d(a.text):b},sortResults:function(a){return a},formatResultCssClass:function(){return b},formatSelectionCssClass:function(){return b},formatNoMatches:function(){return"No matches found"},formatInputTooShort:function(a,b){var c=b-a.length;return"Please enter "+c+" more character"+(1==c?"":"s")},formatInputTooLong:function(a,b){var c=a.length-b;return"Please delete "+c+" character"+(1==c?"":"s")},formatSelectionTooBig:function(a){return"You can only select "+a+" item"+(1==a?"":"s")},formatLoadMore:function(){return"Loading more results..."},formatSearching:function(){return"Searching..."},minimumResultsForSearch:0,minimumInputLength:0,maximumInputLength:null,maximumSelectionSize:0,id:function(a){return a.id},matcher:function(a,b){return n(""+b).toUpperCase().indexOf(n(""+a).toUpperCase())>=0},separator:",",tokenSeparators:[],tokenizer:M,escapeMarkup:F,blurOnChange:!1,selectOnBlur:!1,adaptContainerCssClass:function(a){return a},adaptDropdownCssClass:function(){return null},nextSearchTerm:function(){return b}},a.fn.select2.ajaxDefaults={transport:a.ajax,params:{type:"GET",cache:!1,dataType:"json"}},window.Select2={query:{ajax:G,local:H,tags:I},util:{debounce:v,markMatch:E,escapeMarkup:F,stripDiacritics:n},"class":{"abstract":d,single:e,multi:f}}}}(jQuery); \ No newline at end of file diff --git a/web/js/select2/select2.png b/web/js/select2/select2.png new file mode 100644 index 000000000..1d804ffb9 Binary files /dev/null and b/web/js/select2/select2.png differ diff --git a/web/js/select2/select2_locale_ar.js b/web/js/select2/select2_locale_ar.js new file mode 100644 index 000000000..5ce010631 --- /dev/null +++ b/web/js/select2/select2_locale_ar.js @@ -0,0 +1,17 @@ +/** + * Select2 Arabic translation. + * + * Author: Your Name <amedhat3@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "لا توجد نتائج"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "من فضلك أدخل " + n + " حروف أكثر"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "من فضلك أحذف " + n + " حروف"; }, + formatSelectionTooBig: function (limit) { return "يمكنك ان تختار " + limit + " أختيارات فقط"; }, + formatLoadMore: function (pageNumber) { return "تحمل المذيد من النتائج ..."; }, + formatSearching: function () { return "جاري البحث ..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_bg.js b/web/js/select2/select2_locale_bg.js new file mode 100644 index 000000000..2d8b9ca2b --- /dev/null +++ b/web/js/select2/select2_locale_bg.js @@ -0,0 +1,17 @@ +/** + * Select2 <Language> translation. + * + * Author: Lubomir Vikev <lubomirvikev@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Няма намерени съвпадения"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Моля въведете още " + n + " символ" + (n == 1 ? "" : "а"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Моля въведете с " + n + " по-малко символ" + (n == 1? "" : "а"); }, + formatSelectionTooBig: function (limit) { return "Можете да направите до " + limit + (limit == 1 ? " избор" : " избора"); }, + formatLoadMore: function (pageNumber) { return "Зареждат се още..."; }, + formatSearching: function () { return "Търсене..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_ca.js b/web/js/select2/select2_locale_ca.js new file mode 100644 index 000000000..bdcdaa798 --- /dev/null +++ b/web/js/select2/select2_locale_ca.js @@ -0,0 +1,17 @@ +/** + * Select2 Catalan translation. + * + * Author: David Planella <david.planella@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "No s'ha trobat cap coincidència"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Introduïu " + n + " caràcter" + (n == 1 ? "" : "s") + " més"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Introduïu " + n + " caràcter" + (n == 1? "" : "s") + "menys"; }, + formatSelectionTooBig: function (limit) { return "Només podeu seleccionar " + limit + " element" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "S'estan carregant més resultats..."; }, + formatSearching: function () { return "S'està cercant..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_cs.js b/web/js/select2/select2_locale_cs.js new file mode 100644 index 000000000..b3c748c23 --- /dev/null +++ b/web/js/select2/select2_locale_cs.js @@ -0,0 +1,49 @@ +/** + * Select2 Czech translation. + * + * Author: Michal Marek <ahoj@michal-marek.cz> + * Author - sklonovani: David Vallner <david@vallner.net> + */ +(function ($) { + "use strict"; + // use text for the numbers 2 through 4 + var smallNumbers = { + 2: function(masc) { return (masc ? "dva" : "dvě"); }, + 3: function() { return "tři"; }, + 4: function() { return "čtyři"; } + } + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Nenalezeny žádné položky"; }, + formatInputTooShort: function (input, min) { + var n = min - input.length; + if (n == 1) { + return "Prosím zadejte ještě jeden znak"; + } else if (n <= 4) { + return "Prosím zadejte ještě další "+smallNumbers[n](true)+" znaky"; + } else { + return "Prosím zadejte ještě dalších "+n+" znaků"; + } + }, + formatInputTooLong: function (input, max) { + var n = input.length - max; + if (n == 1) { + return "Prosím zadejte o jeden znak méně"; + } else if (n <= 4) { + return "Prosím zadejte o "+smallNumbers[n](true)+" znaky méně"; + } else { + return "Prosím zadejte o "+n+" znaků méně"; + } + }, + formatSelectionTooBig: function (limit) { + if (limit == 1) { + return "Můžete zvolit jen jednu položku"; + } else if (limit <= 4) { + return "Můžete zvolit maximálně "+smallNumbers[limit](false)+" položky"; + } else { + return "Můžete zvolit maximálně "+limit+" položek"; + } + }, + formatLoadMore: function (pageNumber) { return "Načítají se další výsledky..."; }, + formatSearching: function () { return "Vyhledávání..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_da.js b/web/js/select2/select2_locale_da.js new file mode 100644 index 000000000..dbce3e174 --- /dev/null +++ b/web/js/select2/select2_locale_da.js @@ -0,0 +1,17 @@ +/** + * Select2 Danish translation. + * + * Author: Anders Jenbo <anders@jenbo.dk> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Ingen resultater fundet"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Angiv venligst " + n + " tegn mere"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Angiv venligst " + n + " tegn mindre"; }, + formatSelectionTooBig: function (limit) { return "Du kan kun vælge " + limit + " emne" + (limit === 1 ? "" : "r"); }, + formatLoadMore: function (pageNumber) { return "Indlæser flere resultater…"; }, + formatSearching: function () { return "Søger…"; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_de.js b/web/js/select2/select2_locale_de.js new file mode 100644 index 000000000..01f94ed4f --- /dev/null +++ b/web/js/select2/select2_locale_de.js @@ -0,0 +1,15 @@ +/** + * Select2 German translation + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Keine Übereinstimmungen gefunden"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Bitte " + n + " Zeichen mehr eingeben"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Bitte " + n + " Zeichen weniger eingeben"; }, + formatSelectionTooBig: function (limit) { return "Sie können nur " + limit + " Eintr" + (limit === 1 ? "ag" : "äge") + " auswählen"; }, + formatLoadMore: function (pageNumber) { return "Lade mehr Ergebnisse..."; }, + formatSearching: function () { return "Suche..."; } + }); +})(jQuery); \ No newline at end of file diff --git a/web/js/select2/select2_locale_el.js b/web/js/select2/select2_locale_el.js new file mode 100644 index 000000000..36e0f4d91 --- /dev/null +++ b/web/js/select2/select2_locale_el.js @@ -0,0 +1,17 @@ +/** + * Select2 <Language> translation. + * + * Author: Your Name <your@email> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Δεν βρέθηκαν αποτελέσματα"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Παρακαλούμε εισάγετε " + n + " περισσότερο" + (n == 1 ? "" : "υς") + " χαρακτήρ" + (n == 1 ? "α" : "ες"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Παρακαλούμε διαγράψτε " + n + " χαρακτήρ" + (n == 1 ? "α" : "ες"); }, + formatSelectionTooBig: function (limit) { return "Μπορείτε να επιλέξετε μόνο " + limit + " αντικείμεν" + (limit == 1 ? "ο" : "α"); }, + formatLoadMore: function (pageNumber) { return "Φόρτωση περισσότερων..."; }, + formatSearching: function () { return "Αναζήτηση..."; } + }); +})(jQuery); \ No newline at end of file diff --git a/web/js/select2/select2_locale_en.js.template b/web/js/select2/select2_locale_en.js.template new file mode 100644 index 000000000..f76b37498 --- /dev/null +++ b/web/js/select2/select2_locale_en.js.template @@ -0,0 +1,17 @@ +/** + * Select2 <Language> translation. + * + * Author: Your Name <your@email> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "No matches found"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1 ? "" : "s"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1 ? "" : "s"); }, + formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "Loading more results..."; }, + formatSearching: function () { return "Searching..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_es.js b/web/js/select2/select2_locale_es.js new file mode 100644 index 000000000..241584caa --- /dev/null +++ b/web/js/select2/select2_locale_es.js @@ -0,0 +1,15 @@ +/** + * Select2 Spanish translation + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "No se encontraron resultados"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Por favor, introduzca " + n + " car" + (n == 1? "á" : "a") + "cter" + (n == 1? "" : "es"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Por favor, elimine " + n + " car" + (n == 1? "á" : "a") + "cter" + (n == 1? "" : "es"); }, + formatSelectionTooBig: function (limit) { return "Sólo puede seleccionar " + limit + " elemento" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "Cargando más resultados..."; }, + formatSearching: function () { return "Buscando..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_et.js b/web/js/select2/select2_locale_et.js new file mode 100644 index 000000000..a4045d22d --- /dev/null +++ b/web/js/select2/select2_locale_et.js @@ -0,0 +1,17 @@ +/** + * Select2 Estonian translation. + * + * Author: Kuldar Kalvik <kuldar@kalvik.ee> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Tulemused puuduvad"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Sisesta " + n + " täht" + (n == 1 ? "" : "e") + " rohkem"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Sisesta " + n + " täht" + (n == 1? "" : "e") + " vähem"; }, + formatSelectionTooBig: function (limit) { return "Saad vaid " + limit + " tulemus" + (limit == 1 ? "e" : "t") + " valida"; }, + formatLoadMore: function (pageNumber) { return "Laen tulemusi.."; }, + formatSearching: function () { return "Otsin.."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_eu.js b/web/js/select2/select2_locale_eu.js new file mode 100644 index 000000000..05665f5fc --- /dev/null +++ b/web/js/select2/select2_locale_eu.js @@ -0,0 +1,43 @@ +/** + * Select2 Basque translation. + * + * Author: Julen Ruiz Aizpuru <julenx at gmail dot com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { + return "Ez da bat datorrenik aurkitu"; + }, + formatInputTooShort: function (input, min) { + var n = min - input.length; + if (n === 1) { + return "Idatzi karaktere bat gehiago"; + } else { + return "Idatzi " + n + " karaktere gehiago"; + } + }, + formatInputTooLong: function (input, max) { + var n = input.length - max; + if (n === 1) { + return "Idatzi karaktere bat gutxiago"; + } else { + return "Idatzi " + n + " karaktere gutxiago"; + } + }, + formatSelectionTooBig: function (limit) { + if (limit === 1 ) { + return "Elementu bakarra hauta dezakezu"; + } else { + return limit + " elementu hauta ditzakezu soilik"; + } + }, + formatLoadMore: function (pageNumber) { + return "Emaitza gehiago kargatzen..."; + }, + formatSearching: function () { + return "Bilatzen..."; + } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_fa.js b/web/js/select2/select2_locale_fa.js new file mode 100644 index 000000000..5465fc8e0 --- /dev/null +++ b/web/js/select2/select2_locale_fa.js @@ -0,0 +1,17 @@ +/** + * Select2 <fa> translation. + * + * Author: Ali Choopan <choopan@arsh.co> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "نتیجه‌ای یافت نشد."; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return " لطفا بیش از"+n+"کاراکتر وارد نمایید "; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return " لطفا" + n + " کاراکتر را حذف کنید."; }, + formatSelectionTooBig: function (limit) { return "شما فقط می‌توانید " + limit + " مورد را انتخاب کنید"; }, + formatLoadMore: function (pageNumber) { return "در حال بارگذاری موارد بیشتر ..."; }, + formatSearching: function () { return "در حال جستجو"; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_fi.js b/web/js/select2/select2_locale_fi.js new file mode 100644 index 000000000..b056e88ae --- /dev/null +++ b/web/js/select2/select2_locale_fi.js @@ -0,0 +1,28 @@ +/** + * Select2 Finnish translation + */ +(function ($) { + "use strict"; + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { + return "Ei tuloksia"; + }, + formatInputTooShort: function (input, min) { + var n = min - input.length; + return "Ole hyvä ja anna " + n + " merkkiä lisää"; + }, + formatInputTooLong: function (input, max) { + var n = input.length - max; + return "Ole hyvä ja anna " + n + " merkkiä vähemmän"; + }, + formatSelectionTooBig: function (limit) { + return "Voit valita ainoastaan " + limit + " kpl"; + }, + formatLoadMore: function (pageNumber) { + return "Ladataan lisää tuloksia..."; + }, + formatSearching: function () { + return "Etsitään..."; + } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_fr.js b/web/js/select2/select2_locale_fr.js new file mode 100644 index 000000000..f8d3e4841 --- /dev/null +++ b/web/js/select2/select2_locale_fr.js @@ -0,0 +1,15 @@ +/** + * Select2 French translation + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Aucun résultat trouvé"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Merci de saisir " + n + " caractère" + (n == 1? "" : "s") + " de plus"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Merci de supprimer " + n + " caractère" + (n == 1? "" : "s"); }, + formatSelectionTooBig: function (limit) { return "Vous pouvez seulement sélectionner " + limit + " élément" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "Chargement de résultats supplémentaires..."; }, + formatSearching: function () { return "Recherche en cours..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_gl.js b/web/js/select2/select2_locale_gl.js new file mode 100644 index 000000000..1017c202e --- /dev/null +++ b/web/js/select2/select2_locale_gl.js @@ -0,0 +1,43 @@ +/** + * Select2 Galician translation + * + * Author: Leandro Regueiro <leandro.regueiro@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { + return "Non se atoparon resultados"; + }, + formatInputTooShort: function (input, min) { + var n = min - input.length; + if (n === 1) { + return "Engada un carácter"; + } else { + return "Engada " + n + " caracteres"; + } + }, + formatInputTooLong: function (input, max) { + var n = input.length - max; + if (n === 1) { + return "Elimine un carácter"; + } else { + return "Elimine " + n + " caracteres"; + } + }, + formatSelectionTooBig: function (limit) { + if (limit === 1 ) { + return "Só pode seleccionar un elemento"; + } else { + return "Só pode seleccionar " + limit + " elementos"; + } + }, + formatLoadMore: function (pageNumber) { + return "Cargando máis resultados..."; + }, + formatSearching: function () { + return "Buscando..."; + } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_he.js b/web/js/select2/select2_locale_he.js new file mode 100644 index 000000000..dd72eaa63 --- /dev/null +++ b/web/js/select2/select2_locale_he.js @@ -0,0 +1,17 @@ +/** +* Select2 Hebrew translation. +* +* Author: Yakir Sitbon <http://www.yakirs.net/> +*/ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "לא נמצאו התאמות"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "נא להזין עוד " + n + " תווים נוספים"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "נא להזין פחות " + n + " תווים"; }, + formatSelectionTooBig: function (limit) { return "ניתן לבחור " + limit + " פריטים"; }, + formatLoadMore: function (pageNumber) { return "טוען תוצאות נוספות..."; }, + formatSearching: function () { return "מחפש..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_hr.js b/web/js/select2/select2_locale_hr.js new file mode 100644 index 000000000..b0615405e --- /dev/null +++ b/web/js/select2/select2_locale_hr.js @@ -0,0 +1,42 @@ +/** + * Select2 Croatian translation. + * + * Author: Edi Modrić <edi.modric@gmail.com> + */ +(function ($) { + "use strict"; + + var specialNumbers = { + 1: function(n) { return (n % 100 != 11 ? "znak" : "znakova"); }, + 2: function(n) { return (n % 100 != 12 ? "znaka" : "znakova"); }, + 3: function(n) { return (n % 100 != 13 ? "znaka" : "znakova"); }, + 4: function(n) { return (n % 100 != 14 ? "znaka" : "znakova"); } + }; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Nema rezultata"; }, + formatInputTooShort: function (input, min) { + var n = min - input.length; + var nMod10 = n % 10; + + if (nMod10 > 0 && nMod10 < 5) { + return "Unesite još " + n + " " + specialNumbers[nMod10](n); + } + + return "Unesite još " + n + " znakova"; + }, + formatInputTooLong: function (input, max) { + var n = input.length - max; + var nMod10 = n % 10; + + if (nMod10 > 0 && nMod10 < 5) { + return "Unesite " + n + " " + specialNumbers[nMod10](n) + " manje"; + } + + return "Unesite " + n + " znakova manje"; + }, + formatSelectionTooBig: function (limit) { return "Maksimalan broj odabranih stavki je " + limit; }, + formatLoadMore: function (pageNumber) { return "Učitavanje rezultata..."; }, + formatSearching: function () { return "Pretraga..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_hu.js b/web/js/select2/select2_locale_hu.js new file mode 100644 index 000000000..572dea96d --- /dev/null +++ b/web/js/select2/select2_locale_hu.js @@ -0,0 +1,15 @@ +/** + * Select2 Hungarian translation + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Nincs találat."; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Túl rövid. Még " + n + " karakter hiányzik."; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Túl hosszú. " + n + " kerekterrel több mint kellene."; }, + formatSelectionTooBig: function (limit) { return "Csak " + limit + " elemet lehet kiválasztani."; }, + formatLoadMore: function (pageNumber) { return "Töltés..."; }, + formatSearching: function () { return "Keresés..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_id.js b/web/js/select2/select2_locale_id.js new file mode 100644 index 000000000..59a896ac5 --- /dev/null +++ b/web/js/select2/select2_locale_id.js @@ -0,0 +1,17 @@ +/** + * Select2 Indonesian translation. + * + * Author: Ibrahim Yusuf <ibrahim7usuf@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Tidak ada data yang sesuai"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Masukkan " + n + " huruf lagi" + (n == 1 ? "" : "s"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Hapus " + n + " huruf" + (n == 1 ? "" : "s"); }, + formatSelectionTooBig: function (limit) { return "Anda hanya dapat memilih " + limit + " pilihan" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "Mengambil data..."; }, + formatSearching: function () { return "Mencari..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_is.js b/web/js/select2/select2_locale_is.js new file mode 100644 index 000000000..b10073b5f --- /dev/null +++ b/web/js/select2/select2_locale_is.js @@ -0,0 +1,16 @@ +/** + * Select2 Icelandic translation. + * + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Ekkert fannst"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Vinsamlegast skrifið " + n + " staf" + (n == 1 ? "" : "i") + " í viðbót"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Vinsamlegast styttið texta um " + n + " staf" + (n == 1 ? "" : "i"); }, + formatSelectionTooBig: function (limit) { return "Þú getur aðeins valið " + limit + " atriði"; }, + formatLoadMore: function (pageNumber) { return "Sæki fleiri niðurstöður..."; }, + formatSearching: function () { return "Leita..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_it.js b/web/js/select2/select2_locale_it.js new file mode 100644 index 000000000..98369dda2 --- /dev/null +++ b/web/js/select2/select2_locale_it.js @@ -0,0 +1,15 @@ +/** + * Select2 Italian translation + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Nessuna corrispondenza trovata"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Inserisci ancora " + n + " caratter" + (n == 1? "e" : "i"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Inserisci " + n + " caratter" + (n == 1? "e" : "i") + " in meno"; }, + formatSelectionTooBig: function (limit) { return "Puoi selezionare solo " + limit + " element" + (limit == 1 ? "o" : "i"); }, + formatLoadMore: function (pageNumber) { return "Caricamento in corso..."; }, + formatSearching: function () { return "Ricerca..."; } + }); +})(jQuery); \ No newline at end of file diff --git a/web/js/select2/select2_locale_ja.js b/web/js/select2/select2_locale_ja.js new file mode 100644 index 000000000..81106e78a --- /dev/null +++ b/web/js/select2/select2_locale_ja.js @@ -0,0 +1,15 @@ +/** + * Select2 Japanese translation. + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "該当なし"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "後" + n + "文字入れてください"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "検索文字列が" + n + "文字長すぎます"; }, + formatSelectionTooBig: function (limit) { return "最多で" + limit + "項目までしか選択できません"; }, + formatLoadMore: function (pageNumber) { return "読込中・・・"; }, + formatSearching: function () { return "検索中・・・"; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_ko.js b/web/js/select2/select2_locale_ko.js new file mode 100644 index 000000000..864906b6b --- /dev/null +++ b/web/js/select2/select2_locale_ko.js @@ -0,0 +1,17 @@ +/** + * Select2 <Language> translation. + * + * Author: Swen Mun <longfinfunnel@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "결과 없음"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "너무 짧습니다. "+n+"글자 더 입력해주세요."; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "너무 깁니다. "+n+"글자 지워주세요."; }, + formatSelectionTooBig: function (limit) { return "최대 "+limit+"개까지만 선택하실 수 있습니다."; }, + formatLoadMore: function (pageNumber) { return "불러오는 중…"; }, + formatSearching: function () { return "검색 중…"; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_lt.js b/web/js/select2/select2_locale_lt.js new file mode 100644 index 000000000..dbb1f09d5 --- /dev/null +++ b/web/js/select2/select2_locale_lt.js @@ -0,0 +1,29 @@ +/** + * Select2 lithuanian translation. + * + * Author: CRONUS Karmalakas <cronus dot karmalakas at gmail dot com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Atitikmenų nerasta"; }, + formatInputTooShort: function (input, min) { + var n = min - input.length, + suffix = (n % 10 == 1) && (n % 100 != 11) ? 'į' : (((n % 10 >= 2) && ((n % 100 < 10) || (n % 100 >= 20))) ? 'ius' : 'ių'); + return "Įrašykite dar " + n + " simbol" + suffix; + }, + formatInputTooLong: function (input, max) { + var n = input.length - max, + suffix = (n % 10 == 1) && (n % 100 != 11) ? 'į' : (((n % 10 >= 2) && ((n % 100 < 10) || (n % 100 >= 20))) ? 'ius' : 'ių'); + return "Pašalinkite " + n + " simbol" + suffix; + }, + formatSelectionTooBig: function (limit) { + var n = limit, + suffix = (n % 10 == 1) && (n % 100 != 11) ? 'ą' : (((n % 10 >= 2) && ((n % 100 < 10) || (n % 100 >= 20))) ? 'us' : 'ų'); + return "Jūs galite pasirinkti tik " + limit + " element" + suffix; + }, + formatLoadMore: function (pageNumber) { return "Kraunama daugiau rezultatų..."; }, + formatSearching: function () { return "Ieškoma..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_lv.js b/web/js/select2/select2_locale_lv.js new file mode 100644 index 000000000..2c05cfdab --- /dev/null +++ b/web/js/select2/select2_locale_lv.js @@ -0,0 +1,16 @@ +/** + * Select2 Latvian translation + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Sakritību nav"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Lūdzu ievadiet vēl " + n + " simbol" + (n == 11 ? "us" : (/^\d*[1]$/im.test(n)? "u" : "us")); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Lūdzu ievadiet par " + n + " simbol" + (n == 11 ? "iem" : (/^\d*[1]$/im.test(n)? "u" : "iem")) + " mazāk"; }, + formatSelectionTooBig: function (limit) { return "Jūs varat izvēlēties ne vairāk kā " + limit + " element" + (limit == 11 ? "us" : (/^\d*[1]$/im.test(limit)? "u" : "us")); }, + formatLoadMore: function (pageNumber) { return "Datu ielāde..."; }, + formatSearching: function () { return "Meklēšana..."; } + }); + +})(jQuery); diff --git a/web/js/select2/select2_locale_mk.js b/web/js/select2/select2_locale_mk.js new file mode 100644 index 000000000..69e3981ab --- /dev/null +++ b/web/js/select2/select2_locale_mk.js @@ -0,0 +1,17 @@ +/** + * Select2 Macedonian translation. + * + * Author: Marko Aleksic <psybaron@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Нема пронајдено совпаѓања"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Ве молиме внесете уште " + n + " карактер" + (n == 1 ? "" : "и"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Ве молиме внесете " + n + " помалку карактер" + (n == 1? "" : "и"); }, + formatSelectionTooBig: function (limit) { return "Можете да изберете само " + limit + " ставк" + (limit == 1 ? "а" : "и"); }, + formatLoadMore: function (pageNumber) { return "Вчитување резултати..."; }, + formatSearching: function () { return "Пребарување..."; } + }); +})(jQuery); \ No newline at end of file diff --git a/web/js/select2/select2_locale_ms.js b/web/js/select2/select2_locale_ms.js new file mode 100644 index 000000000..c7202e1dd --- /dev/null +++ b/web/js/select2/select2_locale_ms.js @@ -0,0 +1,17 @@ +/** + * Select2 Malay translation. + * + * Author: Kepoweran <kepoweran@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Tiada padanan yang ditemui"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Sila masukkan " + n + " aksara lagi"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Sila hapuskan " + n + " aksara"; }, + formatSelectionTooBig: function (limit) { return "Anda hanya boleh memilih " + limit + " pilihan"; }, + formatLoadMore: function (pageNumber) { return "Sedang memuatkan keputusan..."; }, + formatSearching: function () { return "Mencari..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_nl.js b/web/js/select2/select2_locale_nl.js new file mode 100644 index 000000000..8061a28e1 --- /dev/null +++ b/web/js/select2/select2_locale_nl.js @@ -0,0 +1,15 @@ +/** + * Select2 Dutch translation + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Geen resultaten gevonden"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Vul " + n + " karakter" + (n == 1? "" : "s") + " meer in"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Vul " + n + " karakter" + (n == 1? "" : "s") + " minder in"; }, + formatSelectionTooBig: function (limit) { return "Maximaal " + limit + " item" + (limit == 1 ? "" : "s") + " toegestaan"; }, + formatLoadMore: function (pageNumber) { return "Meer resultaten laden..."; }, + formatSearching: function () { return "Zoeken..."; } + }); +})(jQuery); \ No newline at end of file diff --git a/web/js/select2/select2_locale_no.js b/web/js/select2/select2_locale_no.js new file mode 100644 index 000000000..0831360f0 --- /dev/null +++ b/web/js/select2/select2_locale_no.js @@ -0,0 +1,18 @@ +/** + * Select2 Norwegian translation. + * + * Author: Torgeir Veimo <torgeir.veimo@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Ingen treff"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Vennligst skriv inn " + n + (n>1 ? " flere tegn" : " tegn til"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Vennligst fjern " + n + " tegn"; }, + formatSelectionTooBig: function (limit) { return "Du kan velge maks " + limit + " elementer"; }, + formatLoadMore: function (pageNumber) { return "Laster flere resultater..."; }, + formatSearching: function () { return "Søker..."; } + }); +})(jQuery); + diff --git a/web/js/select2/select2_locale_pl.js b/web/js/select2/select2_locale_pl.js new file mode 100644 index 000000000..1d5b32730 --- /dev/null +++ b/web/js/select2/select2_locale_pl.js @@ -0,0 +1,37 @@ +/** + * Select2 Polish translation. + * + * Author: Jan Kondratowicz <jan@kondratowicz.pl> + */ +(function ($) { + "use strict"; + + var pl_suffix = function(n) { + if(n == 1) return ""; + if((n%100 > 1 && n%100 < 5) || (n%100 > 20 && n%10 > 1 && n%10 < 5)) return "i"; + return "ów"; + }; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { + return "Brak wyników."; + }, + formatInputTooShort: function (input, min) { + var n = min - input.length; + return "Wpisz jeszcze " + n + " znak" + pl_suffix(n) + "."; + }, + formatInputTooLong: function (input, max) { + var n = input.length - max; + return "Wpisana fraza jest za długa o " + n + " znak" + pl_suffix(n) + "."; + }, + formatSelectionTooBig: function (limit) { + return "Możesz zaznaczyć najwyżej " + limit + " element" + pl_suffix(limit) + "."; + }, + formatLoadMore: function (pageNumber) { + return "Ładowanie wyników..."; + }, + formatSearching: function () { + return "Szukanie..."; + } + }); +})(jQuery); \ No newline at end of file diff --git a/web/js/select2/select2_locale_pt-BR.js b/web/js/select2/select2_locale_pt-BR.js new file mode 100644 index 000000000..5b2ff2f05 --- /dev/null +++ b/web/js/select2/select2_locale_pt-BR.js @@ -0,0 +1,15 @@ +/** + * Select2 Brazilian Portuguese translation + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Nenhum resultado encontrado"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Informe " + n + " caractere" + (n == 1? "" : "s"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Apague " + n + " caractere" + (n == 1? "" : "s"); }, + formatSelectionTooBig: function (limit) { return "Só é possível selecionar " + limit + " elemento" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "Carregando mais resultados..."; }, + formatSearching: function () { return "Buscando..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_pt-PT.js b/web/js/select2/select2_locale_pt-PT.js new file mode 100644 index 000000000..1a40e800b --- /dev/null +++ b/web/js/select2/select2_locale_pt-PT.js @@ -0,0 +1,15 @@ +/** + * Select2 Portuguese (Portugal) translation + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Nenhum resultado encontrado"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Introduza " + n + " car" + (n == 1 ? "ácter" : "acteres"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Apague " + n + " car" + (n == 1 ? "ácter" : "acteres"); }, + formatSelectionTooBig: function (limit) { return "Só é possível selecionar " + limit + " elemento" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "A carregar mais resultados..."; }, + formatSearching: function () { return "A pesquisar..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_ro.js b/web/js/select2/select2_locale_ro.js new file mode 100644 index 000000000..88b3ac413 --- /dev/null +++ b/web/js/select2/select2_locale_ro.js @@ -0,0 +1,15 @@ +/** + * Select2 Romanian translation. + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Nu a fost găsit nimic"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Vă rugăm să introduceți incă " + n + " caracter" + (n == 1 ? "" : "e"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Vă rugăm să introduceți mai puțin de " + n + " caracter" + (n == 1? "" : "e"); }, + formatSelectionTooBig: function (limit) { return "Aveți voie să selectați cel mult " + limit + " element" + (limit == 1 ? "" : "e"); }, + formatLoadMore: function (pageNumber) { return "Se încarcă..."; }, + formatSearching: function () { return "Căutare..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_ru.js b/web/js/select2/select2_locale_ru.js new file mode 100644 index 000000000..3da956a8c --- /dev/null +++ b/web/js/select2/select2_locale_ru.js @@ -0,0 +1,15 @@ +/** + * Select2 Russian translation + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Совпадений не найдено"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Пожалуйста, введите еще " + n + " символ" + (n == 1 ? "" : ((n > 1)&&(n < 5) ? "а" : "ов")); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Пожалуйста, введите на " + n + " символ" + (n == 1 ? "" : ((n > 1)&&(n < 5)? "а" : "ов")) + " меньше"; }, + formatSelectionTooBig: function (limit) { return "Вы можете выбрать не более " + limit + " элемент" + (limit == 1 ? "а" : "ов"); }, + formatLoadMore: function (pageNumber) { return "Загрузка данных..."; }, + formatSearching: function () { return "Поиск..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_sk.js b/web/js/select2/select2_locale_sk.js new file mode 100644 index 000000000..8d4e46afe --- /dev/null +++ b/web/js/select2/select2_locale_sk.js @@ -0,0 +1,48 @@ +/** + * Select2 Slovak translation. + * + * Author: David Vallner <david@vallner.net> + */ +(function ($) { + "use strict"; + // use text for the numbers 2 through 4 + var smallNumbers = { + 2: function(masc) { return (masc ? "dva" : "dve"); }, + 3: function() { return "tri"; }, + 4: function() { return "štyri"; } + } + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Nenašli sa žiadne položky"; }, + formatInputTooShort: function (input, min) { + var n = min - input.length; + if (n == 1) { + return "Prosím zadajte ešte jeden znak"; + } else if (n <= 4) { + return "Prosím zadajte ešte ďalšie "+smallNumbers[n](true)+" znaky"; + } else { + return "Prosím zadajte ešte ďalších "+n+" znakov"; + } + }, + formatInputTooLong: function (input, max) { + var n = input.length - max; + if (n == 1) { + return "Prosím zadajte o jeden znak menej"; + } else if (n <= 4) { + return "Prosím zadajte o "+smallNumbers[n](true)+" znaky menej"; + } else { + return "Prosím zadajte o "+n+" znakov menej"; + } + }, + formatSelectionTooBig: function (limit) { + if (limit == 1) { + return "Môžete zvoliť len jednu položku"; + } else if (limit <= 4) { + return "Môžete zvoliť najviac "+smallNumbers[limit](false)+" položky"; + } else { + return "Môžete zvoliť najviac "+limit+" položiek"; + } + }, + formatLoadMore: function (pageNumber) { return "Načítavajú sa ďalšie výsledky..."; }, + formatSearching: function () { return "Vyhľadávanie..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_sv.js b/web/js/select2/select2_locale_sv.js new file mode 100644 index 000000000..9f09de30f --- /dev/null +++ b/web/js/select2/select2_locale_sv.js @@ -0,0 +1,17 @@ +/** + * Select2 Swedish translation. + * + * Author: Jens Rantil <jens.rantil@telavox.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Inga träffar"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Var god skriv in " + n + (n>1 ? " till tecken" : " tecken till"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Var god sudda ut " + n + " tecken"; }, + formatSelectionTooBig: function (limit) { return "Du kan max välja " + limit + " element"; }, + formatLoadMore: function (pageNumber) { return "Laddar fler resultat..."; }, + formatSearching: function () { return "Söker..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_th.js b/web/js/select2/select2_locale_th.js new file mode 100644 index 000000000..6b38b1581 --- /dev/null +++ b/web/js/select2/select2_locale_th.js @@ -0,0 +1,17 @@ +/** + * Select2 Thai translation. + * + * Author: Atsawin Chaowanakritsanakul <joke@nakhon.net> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "ไม่พบข้อมูล"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "โปรดพิมพ์เพิ่มอีก " + n + " ตัวอักษร"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "โปรดลบออก " + n + " ตัวอักษร"; }, + formatSelectionTooBig: function (limit) { return "คุณสามารถเลือกได้ไม่เกิน " + limit + " รายการ"; }, + formatLoadMore: function (pageNumber) { return "กำลังค้นข้อมูลเพิ่ม..."; }, + formatSearching: function () { return "กำลังค้นข้อมูล..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_tr.js b/web/js/select2/select2_locale_tr.js new file mode 100644 index 000000000..b47a2fad6 --- /dev/null +++ b/web/js/select2/select2_locale_tr.js @@ -0,0 +1,17 @@ +/** + * Select2 Turkish translation. + * + * Author: Salim KAYABAŞI <salim.kayabasi@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Sonuç bulunamadı"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "En az " + n + " karakter daha girmelisiniz"; }, + formatInputTooLong: function (input, max) { var n = input.length - max; return n + " karakter azaltmalısınız"; }, + formatSelectionTooBig: function (limit) { return "Sadece " + limit + " seçim yapabilirsiniz"; }, + formatLoadMore: function (pageNumber) { return "Daha fazla..."; }, + formatSearching: function () { return "Aranıyor..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_ua.js b/web/js/select2/select2_locale_ua.js new file mode 100644 index 000000000..58d31e762 --- /dev/null +++ b/web/js/select2/select2_locale_ua.js @@ -0,0 +1,17 @@ +/** + * Select2 <Language> translation. + * + * Author: bigmihail <bigmihail@bigmir.net> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Нічого не знайдено"; }, + formatInputTooShort: function (input, min) { var n = min - input.length, s = ["", "и", "ів"], p = [2,0,1,1,1,2]; return "Введіть буль ласка ще " + n + " символ" + s[ (n%100>4 && n%100<=20)? 2 : p[Math.min(n%10, 5)] ]; }, + formatInputTooLong: function (input, max) { var n = input.length - max, s = ["", "и", "ів"], p = [2,0,1,1,1,2]; return "Введіть буль ласка на " + n + " символ" + s[ (n%100>4 && n%100<=20)? 2 : p[Math.min(n%10, 5)] ] + " менше"; }, + formatSelectionTooBig: function (limit) {var s = ["", "и", "ів"], p = [2,0,1,1,1,2]; return "Ви можете вибрати лише " + limit + " елемент" + s[ (limit%100>4 && limit%100<=20)? 2 : p[Math.min(limit%10, 5)] ]; }, + formatLoadMore: function (pageNumber) { return "Завантаження даних..."; }, + formatSearching: function () { return "Пошук..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_vi.js b/web/js/select2/select2_locale_vi.js new file mode 100644 index 000000000..0a45dfc9c --- /dev/null +++ b/web/js/select2/select2_locale_vi.js @@ -0,0 +1,18 @@ +/** + * Select2 Vietnamese translation. + * + * Author: Long Nguyen <olragon@gmail.com> + */ +(function ($) { + "use strict"; + + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "Không tìm thấy kết quả"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "Vui lòng nhập nhiều hơn " + n + " ký tự" + (n == 1 ? "" : "s"); }, + formatInputTooLong: function (input, max) { var n = input.length - max; return "Vui lòng nhập ít hơn " + n + " ký tự" + (n == 1? "" : "s"); }, + formatSelectionTooBig: function (limit) { return "Chỉ có thể chọn được " + limit + " tùy chọn" + (limit == 1 ? "" : "s"); }, + formatLoadMore: function (pageNumber) { return "Đang lấy thêm kết quả..."; }, + formatSearching: function () { return "Đang tìm..."; } + }); +})(jQuery); + diff --git a/web/js/select2/select2_locale_zh-CN.js b/web/js/select2/select2_locale_zh-CN.js new file mode 100644 index 000000000..49d8e5994 --- /dev/null +++ b/web/js/select2/select2_locale_zh-CN.js @@ -0,0 +1,14 @@ +/** + * Select2 Chinese translation + */ +(function ($) { + "use strict"; + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "没有找到匹配项"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "请再输入" + n + "个字符";}, + formatInputTooLong: function (input, max) { var n = input.length - max; return "请删掉" + n + "个字符";}, + formatSelectionTooBig: function (limit) { return "你只能选择最多" + limit + "项"; }, + formatLoadMore: function (pageNumber) { return "加载结果中..."; }, + formatSearching: function () { return "搜索中..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2_locale_zh-TW.js b/web/js/select2/select2_locale_zh-TW.js new file mode 100755 index 000000000..3d447d68a --- /dev/null +++ b/web/js/select2/select2_locale_zh-TW.js @@ -0,0 +1,14 @@ +/** + * Select2 Traditional Chinese translation + */ +(function ($) { + "use strict"; + $.extend($.fn.select2.defaults, { + formatNoMatches: function () { return "沒有找到相符的項目"; }, + formatInputTooShort: function (input, min) { var n = min - input.length; return "請再輸入" + n + "個字元";}, + formatInputTooLong: function (input, max) { var n = input.length - max; return "請刪掉" + n + "個字元";}, + formatSelectionTooBig: function (limit) { return "你只能選擇最多" + limit + "項"; }, + formatLoadMore: function (pageNumber) { return "載入中..."; }, + formatSearching: function () { return "搜尋中..."; } + }); +})(jQuery); diff --git a/web/js/select2/select2x2.png b/web/js/select2/select2x2.png new file mode 100644 index 000000000..4bdd5c961 Binary files /dev/null and b/web/js/select2/select2x2.png differ -- cgit v1.2.3 From 5c871b7093ac2fd137b980979bb210cc1cad59a7 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 21 Mar 2014 17:30:07 +0000 Subject: Add Cobrand config to include update date in email alerts This is useful for sites where the updates aren't published on the site so you need a bit more context in the email alerts. Add inlude_time_in_update_alerts to default cobrand, off as default If this is set then the date is added after the name string in the email, passing in 'alert' as the type to dt_prettify in order to allow the cobrand to format it accordingly. This does require pulling out the alert confirmed time from the database in the send_alerts code where it wasn't before. --- perllib/FixMyStreet/Cobrand/Default.pm | 2 + perllib/FixMyStreet/DB/ResultSet/AlertType.pm | 22 +++++++ t/app/controller/alert_new.t | 91 +++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index 60ed216d7..d2af4949e 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -837,6 +837,8 @@ If true then we never send an email to confirm an update sub never_confirm_updates { 0; } +sub include_time_in_update_alerts { 0; } + =head2 prettify_dt my $date = $c->prettify_dt( $datetime ); diff --git a/perllib/FixMyStreet/DB/ResultSet/AlertType.pm b/perllib/FixMyStreet/DB/ResultSet/AlertType.pm index 8e18832a9..545b54c60 100644 --- a/perllib/FixMyStreet/DB/ResultSet/AlertType.pm +++ b/perllib/FixMyStreet/DB/ResultSet/AlertType.pm @@ -29,6 +29,7 @@ sub email_alerts ($) { $query .= " $item_table.id as item_id, $item_table.text as item_text, $item_table.name as item_name, $item_table.anonymous as item_anonymous, + $item_table.confirmed as item_confirmed, $head_table.* from alert inner join $item_table on alert.parameter::integer = $item_table.${head_table}_id @@ -114,6 +115,27 @@ sub email_alerts ($) { $data{problem_url} = $url . "/report/" . $row->{id}; } $data{data} .= $row->{item_name} . ' : ' if $row->{item_name} && !$row->{item_anonymous}; + if ( $cobrand->include_time_in_update_alerts ) { + # this is basically recreating the code from the inflate wrapper + # in the database model. + my $tz; + if ( FixMyStreet->config('TIME_ZONE') ) { + $tz = FixMyStreet->config('TIME_ZONE'); + } + + my $parser = DateTime::Format::Pg->new(); + my $dt = $parser->parse_timestamp( $row->{item_confirmed} ); + my $l_tz = DateTime::TimeZone->new( name => "local" ); + # We need to always set this otherwise we end up with the DateTime + # object being in the floating timezone in which case applying a + # subsequent timezone set will have no effect. + $dt->set_time_zone( $l_tz ); + if ( $tz ) { + my $tz_obj = DateTime::TimeZone->new( name => $tz ); + $dt->set_time_zone( $tz_obj ); + } + $data{data} .= $cobrand->prettify_dt( $dt, 'alert' ) . "\n\n"; + } $data{data} .= $row->{item_text} . "\n\n------\n\n"; # this is ward and council problems } else { diff --git a/t/app/controller/alert_new.t b/t/app/controller/alert_new.t index 532b071eb..594d4c7a6 100644 --- a/t/app/controller/alert_new.t +++ b/t/app/controller/alert_new.t @@ -683,4 +683,95 @@ subtest 'check new updates alerts for non public reports only go to report owner $mech->delete_user( $user3 ); }; +subtest 'check setting inlude dates in new updates cobrand option' => sub { + my $include_date_in_alert_override= Sub::Override->new( + "FixMyStreet::Cobrand::Default::include_time_in_update_alerts", + sub { return 1; } + ); + $mech->delete_user( 'reporter@example.com' ); + $mech->delete_user( 'alerts@example.com' ); + + my $user1 = FixMyStreet::App->model('DB::User') + ->find_or_create( { email => 'reporter@example.com', name => 'Reporter User' } ); + ok $user1, "created test user"; + + my $user2 = FixMyStreet::App->model('DB::User') + ->find_or_create( { email => 'alerts@example.com', name => 'Alert User' } ); + ok $user2, "created test user"; + + my $user3 = FixMyStreet::App->model('DB::User') + ->find_or_create( { email => 'updates@example.com', name => 'Update User' } ); + ok $user3, "created test user"; + + my $dt = DateTime->now->add( minutes => -30 ); + my $r_dt = $dt->clone->add( minutes => 20 ); + + my $dt_parser = FixMyStreet::App->model('DB')->schema->storage->datetime_parser; + + my $report = FixMyStreet::App->model('DB::Problem')->find_or_create( { + postcode => 'EH1 1BB', + bodies_str => '2651', + areas => ',11808,135007,14419,134935,2651,20728,', + category => 'Street lighting', + title => 'Alert test for non public reports', + detail => 'Testing Detail', + used_map => 1, + name => $user2->name, + anonymous => 0, + state => 'confirmed', + confirmed => $dt_parser->format_datetime($r_dt), + lastupdate => $dt_parser->format_datetime($r_dt), + whensent => $dt_parser->format_datetime($r_dt->clone->add( minutes => 5 )), + lang => 'en-gb', + service => '', + cobrand => 'default', + cobrand_data => '', + send_questionnaire => 1, + latitude => '55.951963', + longitude => '-3.189944', + user_id => $user2->id, + } ); + + my $update = FixMyStreet::App->model('DB::Comment')->create( { + problem_id => $report->id, + user_id => $user3->id, + name => 'Anonymous User', + mark_fixed => 'false', + text => 'This is some more update text', + state => 'confirmed', + confirmed => $r_dt->clone->add( minutes => 8 ), + anonymous => 't', + } ); + + my $alert_user1 = FixMyStreet::App->model('DB::Alert')->create( { + user => $user1, + alert_type => 'new_updates', + parameter => $report->id, + confirmed => 1, + whensubscribed => $dt, + } ); + ok $alert_user1, "alert created"; + + + $mech->clear_emails_ok; + FixMyStreet::App->model('DB::AlertType')->email_alerts(); + $mech->email_count_is(1); + + # if we don't do this then we're applying the date inflation code and + # it's timezone munging to the DateTime object above and not the DateTime + # object that's inflated from the database value and these turn out to be + # different as the one above has a UTC timezone and not the floating one + # that those from the DB do. + $update->discard_changes(); + + my $date_in_alert = Utils::prettify_dt( $update->confirmed ); + my $email = $mech->get_email; + like $email->body, qr/$date_in_alert/, 'alert contains date'; + + $mech->delete_user( $user1 ); + $mech->delete_user( $user2 ); + $mech->delete_user( $user3 ); + $include_date_in_alert_override->restore(); +}; + done_testing(); -- cgit v1.2.3 From ee474f3340c02bc1b9e75accf303e16257d86882 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Fri, 21 Mar 2014 17:34:57 +0000 Subject: Changes to alert-update formatting for ZeroTB Include date in alerts Remove everything apart from the update text as we don't need it --- perllib/FixMyStreet/Cobrand/ZeroTB.pm | 13 +++++++++++++ templates/email/zerotb/alert-update.txt | 9 +++++++++ 2 files changed, 22 insertions(+) create mode 100644 templates/email/zerotb/alert-update.txt diff --git a/perllib/FixMyStreet/Cobrand/ZeroTB.pm b/perllib/FixMyStreet/Cobrand/ZeroTB.pm index c774610c4..32d14d0f6 100644 --- a/perllib/FixMyStreet/Cobrand/ZeroTB.pm +++ b/perllib/FixMyStreet/Cobrand/ZeroTB.pm @@ -29,6 +29,7 @@ sub allow_photo_upload{ return 0; } sub send_questionnaires { return 0; } sub on_map_default_max_pin_age { return 0; } sub never_confirm_updates { 1; } +sub include_time_in_update_alerts { 1; } sub get_clinic_list { my $self = shift; @@ -36,5 +37,17 @@ sub get_clinic_list { return $self->problems->search({ state => 'confirmed' }, { order_by => 'title' }); } +sub prettify_dt { + my ( $self, $dt, $type ) = @_; + $type ||= ''; + + if ( $type eq 'alert' ) { + return $dt->strftime('%H:%M %Y-%m-%d'); + } else { + return Utils::prettify_dt( $dt, $type ); + } + +} + 1; diff --git a/templates/email/zerotb/alert-update.txt b/templates/email/zerotb/alert-update.txt new file mode 100644 index 000000000..ee32524f9 --- /dev/null +++ b/templates/email/zerotb/alert-update.txt @@ -0,0 +1,9 @@ +Subject: New stockout report for Clinic - '<?=$values['title']?>' + +The following stockouts have been reported for this clinic: + +<?=$values['data']?> + +You cannot contact anyone by replying to this email. + +<?=$values['signature']?> -- cgit v1.2.3 From c5cbc9f10b9cc381854058011d0d82348d042e6d Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Tue, 25 Mar 2014 15:25:04 +0000 Subject: text updates front page and faq --- templates/web/zerotb/faq/faq-en-gb.html | 124 ++++++++++++++++++++++++++++++++ templates/web/zerotb/index-steps.html | 4 +- templates/web/zerotb/index.html | 2 +- 3 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 templates/web/zerotb/faq/faq-en-gb.html diff --git a/templates/web/zerotb/faq/faq-en-gb.html b/templates/web/zerotb/faq/faq-en-gb.html new file mode 100644 index 000000000..b2607f769 --- /dev/null +++ b/templates/web/zerotb/faq/faq-en-gb.html @@ -0,0 +1,124 @@ +[% INCLUDE 'header.html', title => loc('Frequently Asked Questions') %] + +<h1>Frequently Asked Questions</h1> + <dl> + <dt>What is this website?</dt> + <dd>This site was created to help people report anti-tuberculosis drug stockouts in Delhi. It was created in response to the ongoing stockouts reported in India last year (see here). The information will be used to encourage the government to address stockouts as they arise.</dd> + <dt>What is FixMyStreet?</dt> + <dd>FixMyStreet is a site to help people report, view, or discuss local problems they’ve found by simply locating them on a map. It launched in early February 2007.</dd> + <dt>How do I get in touch with FixMyStreet?</dt> + <dd>Here’s our <a href="[% c.uri_for( '/contact' ) %]">contact page</a>.</dd> + <dt>What sort of problems should I report with this site?</dt> + <dd>This site is primarily for reporting barriers to TB treatment clinics such as drug stockouts.</dd> + <dt>How do I use the site?</dt> + <dd>You can either enter a location in Delhi or choose a specific clinic name. If you enter a location, you will be presented with a map of that area. You can then search for a TB clinic in that area to report a stockout.</dd> + <dt>How are the problems solved?</dt> + <dd>They are anonymously reported to the District TB Officer by email. They are also shared with local activists and media to generate awareness of the issue and encourage the government to take action. We respect your privacy: your name and email will NOT be given to any of these groups without your written permission.</dd> + <dt>Is it free?</dt> + <dd>The site is free to use, yes. FixMyStreet is run +by a registered charity, though, so if you want to make a contribution, <a +href="https://secure.mysociety.org/donate/">please do</a>.</dd> + + <dt>Can I use the site on my mobile?</dt> + <dd> + <p>The website will already work on your mobile phone, adapting to + the size of your screen automatically. + </dd> + </dl> + + <h2>Practical Questions</h2> + <dl> + <dt>I work with a TB clinic, where do you send the reports?</dt> + <dd>You can contact us to find out where reports go at the moment. Also contact us to update the address or addresses we use.</dd> + <dt>Do you remove silly or illegal content?</dt> + <dd>FixMyStreet is not responsible for the content and accuracy +of material submitted by its users. We reserve the right to edit or remove any +problems or updates which we consider to be inappropriate upon being informed +by a user of the site.</dd> + + <dt>Why can’t I zoom out more on the reporting map?</dt> + <dd>We want to keep the site locally focused, so restrict the ability to + move radically between areas.</dd> + + <dt>This site is great – why aren’t you better publicised?</dt> + <dd>As a tiny charity we simply don’t have a publicity budget, and we + rely on word of mouth to advertise the site. We have a whole <a + href="posters/">array of posters, flyers and badges</a> if you’d like + to publicise us on the web or in your local area, and why not write to your + local paper to let them know about us?</dd> </dl> + + <h2><a name="privacy"></a>Privacy Questions</h2> + <dl> + <dt>Who gets to see my name and email address?</dt> + <dd> + Due to the sensitive nature of the stockout situation, if you submit a report, we will NOT share your name or email with anyone unless you give us permission. We may, however, send other information from your report (such as the clinic name and any description you write about the stockouts) to the TB officers with the government or activists in order to raise awareness issue. + The only people who will be able to see your name and email address will be the FixMyStreet administrators and team members, and they will never use it for anything other than to help administer FixMyStreet. We will never give or sell your email address to anyone else, unless we are obliged to by law. Your name, given in the name field, will not be published anywhere unless you specifically give us permission to. + </dd> + <dt>Will you send nasty, brutish spam to my email address?</dt> + <dd>Never. We will email you if someone leaves an update on a +problem you’ve reported, and send you a questionnaire email four weeks +after you submit a problem, asking for a status update; we’ll only ever +send you emails in relation to your problem.</dd> + </dl> + <h2>Organisation Questions</h2> + <dl> + <dt>Who built FixMyStreet?</dt> + <dd>This site was built by <a href="http://www.mysociety.org/">mySociety</a>, + in conjunction with the <a href="http://www.youngfoundation.org.uk/">Young Foundation</a>. +mySociety is the project of a registered charity which has grown out of the community of +volunteers who built sites like <a href="http://www.theyworkforyou.com/">TheyWorkForYou.com</a>. +mySociety’s primary mission is to build Internet projects which give people simple, tangible +benefits in the civic and community aspects of their lives. Our first project +was <a href="http://www.writetothem.com/">WriteToThem</a>, where you can write to any of your +elected representatives, for free. The charity is called UK Citizens Online Democracy and is charity number 1076346. mySociety +can be contacted by email at <a href="mailto:hello@mysociety.org">hello@mysociety.org</a>, +or by post at mySociety, 483 Green Lanes, London, N13 4BS, UK.</dd> + <dt><img src="/i/moj.png" align="right" alt="Ministry of Justice" hspace="10">Who pays for it?</dt> + <dd>FixMyStreet was originally paid for via the Department for + Constitutional Affairs Innovations Fund. It is now funded by a variety of means, from commercial + work to <a href="http://www.mysociety.org/donate/">donations</a>.</dd> + <dt>Do you need any help with the project?</dt> + <dd>Yes, we can use help in all sorts of ways, technical or +non-technical. Please see our <a +href="http://www.mysociety.org/helpus/">Get Involved page</a>.</dd> + <dt>I’d like a site like this for my own location/ where’s the "source code" to this site?</dt> + <dd> +The software behind this site is open source, and available +to you mainly under the GNU Affero GPL software license. You can <a +href="http://github.com/mysociety/fixmystreet">download the +source code</a> and help us develop it. +You’re welcome to use it in your own projects, although you must also +make available the source code to any such projects. +<a href="http://www.fiksgatami.no/">Fiksgatami</a> is an example of our code +being used in a Norwegian version of this site. +</dd> +<dt>I’d prefer code in a different language?</dt> +<dd> +VisibleGovernment.ca wrote their own code for +<a href="http://www.fixmystreet.ca/">http://www.fixmystreet.ca/</a>, which is +written in GeoDjango and available under an MIT licence at <a +href="http://github.com/visiblegovernment/django-fixmystreet/tree/master">github</a>. +Or <a href="http://www.fixmystreet.org.nz/">FixMyStreet.org.nz</a> is written in +Drupal. +</p> +</dd> + <dt>People build things, not organisations. Who <em>actually</em> built it?</dt> + <dd>Matthew Somerville and Francis Irving wrote the site, +Chris Lightfoot wrote the tileserver and map cutter, Richard Pope created +our pins, Deborah Kerr keeps things up-to-date and does user support, +Ayesha Garrett designed our posters, and Tom Steinberg managed it all. + +Thanks also to +<a href="http://www.ordnancesurvey.co.uk">Ordnance Survey</a> (for the maps, +UK postcodes, and UK addresses – data © Crown copyright, all +rights reserved, Ministry of Justice 100037819 2008), +Yahoo! for their BSD-licensed JavaScript libraries, the entire free software +community (this particular project was brought to you by Perl, PostgreSQL, +and the number 161.290) and <a +href="http://www.bytemark.co.uk/">Bytemark</a> (who kindly host all +our servers). + +Let us know if we’ve missed anyone.</dd> + </dl> + +[% INCLUDE 'footer.html' %] diff --git a/templates/web/zerotb/index-steps.html b/templates/web/zerotb/index-steps.html index 6c11ca6d4..2d49b8f28 100644 --- a/templates/web/zerotb/index-steps.html +++ b/templates/web/zerotb/index-steps.html @@ -1,8 +1,8 @@ <h2>[% loc('How to report a stockout') %]</h2> <ol class="big-numbers"> - <li>[% loc('Enter a Delhi TB clinic or street name and area') %]</li> + <li>[% loc('Enter a Delhi chest clinic or street name and area') %]</li> <li>[% loc('Locate the clinic on a map of the area') %]</li> - <li>[% loc('Enter details of the problem') %]</li> + <li>[% loc('Enter details of the problem (optional)') %]</li> <li>[% loc('We will send your stockout report to the district TB officer and to local activists to ensure action is taken') %]</li> </ol> diff --git a/templates/web/zerotb/index.html b/templates/web/zerotb/index.html index 95d6fa013..754dcbabc 100644 --- a/templates/web/zerotb/index.html +++ b/templates/web/zerotb/index.html @@ -35,7 +35,7 @@ Modernizr.load({ <section class="full-width"> <p> - Explain stockouts here. + [% loc('In 2013 India faced stockouts of anti-tuberculosis medicines, threatening to leave many patients across the country without access to life-saving treatment. Some government officials have denied that these drug stockouts exist. There is no simple mechanism for gathering drug shortage data, making accountability and transparency impossible. We aim to change this by enabling anyone in Delhi to report stockouts through this website.') %] </p> </section> -- cgit v1.2.3 From 647412d2fc0213c5871fe300d1f2c7c32037da20 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Tue, 25 Mar 2014 15:49:11 +0000 Subject: fix banner height and menu placement on map pages make links same height as on other pages increase height of banner to be the same as other pages --- web/cobrands/zerotb/layout.scss | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/web/cobrands/zerotb/layout.scss b/web/cobrands/zerotb/layout.scss index 7c12ab6ba..ea8102ded 100644 --- a/web/cobrands/zerotb/layout.scss +++ b/web/cobrands/zerotb/layout.scss @@ -30,6 +30,23 @@ body { } } } + &.mappage { + #main-nav { + ul#main-menu { + li { + a, + a.report-a-problem-btn, + span { + padding: 0.25em; + margin: 0.5em; + } + } + } + } + .nav-wrapper-2 { + min-height: 4em; + } + } } .nav-wrapper { -- cgit v1.2.3 From 10ebcc9a03156463fe834d66efd85c484c7f4e97 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Tue, 25 Mar 2014 15:51:48 +0000 Subject: remove links to english and hindi translations awaiting the hindi translation so don't have a link to it --- templates/web/zerotb/footer.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/templates/web/zerotb/footer.html b/templates/web/zerotb/footer.html index a5e0ba7bc..c46a03878 100644 --- a/templates/web/zerotb/footer.html +++ b/templates/web/zerotb/footer.html @@ -6,11 +6,6 @@ <div class="nav-wrapper"> <div class="nav-wrapper-2"> <div id="main-nav" role="navigation"> - <ul id="mysoc-menu"> - <li><a href="http://en.[% c.cobrand.base_host %][% c.req.uri.path_query %]">English</a></li>[% - %]<li><a href="http://hi.[% c.cobrand.base_host %][% c.req.uri.path_query %]">हिन्दी</a></li> - </ul> - <ul id="main-menu"> <li><[% IF c.req.uri.path == '/' %]span[% ELSE %]a href="/"[% END %] class="report-a-problem-btn" >[% loc("Report a Stockout") %]</[% c.req.uri.path == '/' ? 'span' : 'a' %]></li>[% -- cgit v1.2.3 From 1208af86126d5e030feda2756a0faf93e4366345 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Tue, 25 Mar 2014 17:00:52 +0000 Subject: script to import zerotb clinic data Closes #703 --- bin/zerotb_import_clinic_list.pl | 115 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100755 bin/zerotb_import_clinic_list.pl diff --git a/bin/zerotb_import_clinic_list.pl b/bin/zerotb_import_clinic_list.pl new file mode 100755 index 000000000..359a63925 --- /dev/null +++ b/bin/zerotb_import_clinic_list.pl @@ -0,0 +1,115 @@ +#!/usr/bin/perl + +use strict; +use FixMyStreet::App; +use Text::CSV; +use Getopt::Long::Descriptive; + +use constant TITLE => 0; +use constant DESC => 1; +use constant LATLONG => 2; +use constant EMAIL => 3; + +my ($opt, $usage) = describe_options( + '%c %o', + ['file|f=s', "path to csv file with list of clinics", { required => 1 } ], + ['email|e=s', "default email address for the updates to be sent to", { required => 1 } ], + ['verbose|v', "print out all services as they are found"], + ['help', "print usage message and exit" ], +); +print($usage->text), exit if $opt->help; + +my $csv = Text::CSV->new ( { binary => 1 } ) # should set binary attribute. + or die "Cannot use CSV: ".Text::CSV->error_diag (); +open my $fh, "<:encoding(utf8)", $opt->file or die "Failed to open " . $opt->file . ": $!"; + +my $clinic_user = FixMyStreet::App->model('DB::User')->find_or_create({ + email => $opt->email +}); +if ( not $clinic_user->in_storage ) { + $clinic_user->insert; +} + +# throw away header line +my $title_row = $csv->getline( $fh ); + +while ( my $row = $csv->getline( $fh ) ) { + my $clinics = FixMyStreet::App->model('DB::Problem')->search({ + title => $row->[TITLE] + }); + + my ($lat, $long) = split(',', $row->[LATLONG]); + my $p; + my $count = $clinics->count; + if ( $count == 0 ) { + $p = FixMyStreet::App->model('DB::Problem')->create({ + title => $row->[TITLE], + latitude => $lat, + longitude => $long, + used_map => 1, + anonymous => 1, + state => 'unconfirmed', + name => '', + user => $clinic_user, + detail => '', + areas => '', + postcode => '' + }); + } elsif ( $count == 1 ) { + $p = $clinics->first; + } else { + printf "Too many matches for: %s\n", $row->[TITLE]; + next; + } + $p->detail( $row->[DESC] ); + $p->latitude( $lat ); + $p->longitude( $long ); + $p->confirm; + + if ( $p->in_storage ) { + printf( "Updating entry for %s\n", $row->[TITLE] ) if $opt->verbose; + $p->update; + } else { + printf( "Creating entry for %s\n", $row->[TITLE] ) if $opt->verbose; + $p->insert; + } + $p->discard_changes; + + # disabling existing alerts in case email addresses have changed + my $existing = FixMyStreet::App->model('DB::Alert')->search({ + alert_type => 'new_updates', + parameter => $p->id + }); + $existing->update( { confirmed => 0 } ); + + if ( $row->[EMAIL] ) { + my $u = FixMyStreet::App->model('DB::User')->find_or_new({ + email => $row->[EMAIL] + }); + $u->insert unless $u->in_storage; + create_update_alert( $u, $p, $opt->verbose ); + } + + create_update_alert( $clinic_user, $p, $opt->verbose ); +} + +sub create_update_alert { + my ( $user, $p, $verbose ) = @_; + my $a = FixMyStreet::App->model('DB::Alert')->find_or_new({ + alert_type => 'new_updates', + user => $user, + parameter => $p->id, + }); + + $a->confirmed(1); + + if ( $a->in_storage ) { + printf( "Updating update alert for %s on %s\n", $user->email, $p->title ) + if $verbose; + $a->update; + } else { + printf( "Creating update alert for %s on %s\n", $user->email, $p->title ) + if $verbose; + $a->insert; + } +} -- cgit v1.2.3 From ecd7e9c67f8cce7b3c080c843066fe9aa822e457 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 26 Mar 2014 11:59:05 +0000 Subject: replace default pins with clinic logo add files and change config in cobrand --- perllib/FixMyStreet/Cobrand/ZeroTB.pm | 8 ++++++++ web/cobrands/zerotb/images/pin-clinic-big.png | Bin 0 -> 725 bytes web/cobrands/zerotb/images/pin-clinic.png | Bin 0 -> 674 bytes web/cobrands/zerotb/images/pin-shadow-big.png | Bin 0 -> 1202 bytes web/cobrands/zerotb/images/pin-shadow.png | Bin 0 -> 720 bytes 5 files changed, 8 insertions(+) create mode 100644 web/cobrands/zerotb/images/pin-clinic-big.png create mode 100644 web/cobrands/zerotb/images/pin-clinic.png create mode 100644 web/cobrands/zerotb/images/pin-shadow-big.png create mode 100644 web/cobrands/zerotb/images/pin-shadow.png diff --git a/perllib/FixMyStreet/Cobrand/ZeroTB.pm b/perllib/FixMyStreet/Cobrand/ZeroTB.pm index 32d14d0f6..087bf0912 100644 --- a/perllib/FixMyStreet/Cobrand/ZeroTB.pm +++ b/perllib/FixMyStreet/Cobrand/ZeroTB.pm @@ -31,6 +31,14 @@ sub on_map_default_max_pin_age { return 0; } sub never_confirm_updates { 1; } sub include_time_in_update_alerts { 1; } +sub pin_colour { + return 'clinic'; +} + +sub path_to_pin_icons { + return '/cobrands/zerotb/images/'; +} + sub get_clinic_list { my $self = shift; diff --git a/web/cobrands/zerotb/images/pin-clinic-big.png b/web/cobrands/zerotb/images/pin-clinic-big.png new file mode 100644 index 000000000..c1e359828 Binary files /dev/null and b/web/cobrands/zerotb/images/pin-clinic-big.png differ diff --git a/web/cobrands/zerotb/images/pin-clinic.png b/web/cobrands/zerotb/images/pin-clinic.png new file mode 100644 index 000000000..5cc0828fd Binary files /dev/null and b/web/cobrands/zerotb/images/pin-clinic.png differ diff --git a/web/cobrands/zerotb/images/pin-shadow-big.png b/web/cobrands/zerotb/images/pin-shadow-big.png new file mode 100644 index 000000000..113184b94 Binary files /dev/null and b/web/cobrands/zerotb/images/pin-shadow-big.png differ diff --git a/web/cobrands/zerotb/images/pin-shadow.png b/web/cobrands/zerotb/images/pin-shadow.png new file mode 100644 index 000000000..8b470592a Binary files /dev/null and b/web/cobrands/zerotb/images/pin-shadow.png differ -- cgit v1.2.3 From 796c4715cf4e0cdfab8e7acadfac5b8e55b884b8 Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 26 Mar 2014 16:49:17 +0000 Subject: Remove unwanted detail from around pages Remove problem confirmed times as well as states Also problem -> clinic --- templates/web/zerotb/around/around_map_list_items.html | 9 +++++++++ templates/web/zerotb/around/on_map_list_items.html | 7 +++++++ templates/web/zerotb/report/_item.html | 5 +++++ 3 files changed, 21 insertions(+) create mode 100644 templates/web/zerotb/around/around_map_list_items.html create mode 100644 templates/web/zerotb/around/on_map_list_items.html create mode 100644 templates/web/zerotb/report/_item.html diff --git a/templates/web/zerotb/around/around_map_list_items.html b/templates/web/zerotb/around/around_map_list_items.html new file mode 100644 index 000000000..225ddde6e --- /dev/null +++ b/templates/web/zerotb/around/around_map_list_items.html @@ -0,0 +1,9 @@ +[% IF around_map.size %] + [% FOREACH p IN around_map %] + [% INCLUDE 'report/_item.html' + problem = p.problem, + dist = tprintf("%.1f", (p.distance || 0) ) %] + [% END %] +[% ELSE %] + <li><p>[% loc('No clinics found.') %]</p></li> +[% END %] diff --git a/templates/web/zerotb/around/on_map_list_items.html b/templates/web/zerotb/around/on_map_list_items.html new file mode 100644 index 000000000..838e2e9fa --- /dev/null +++ b/templates/web/zerotb/around/on_map_list_items.html @@ -0,0 +1,7 @@ +[% IF on_map.size %] + [% FOREACH problem IN on_map %] + [% INCLUDE 'report/_item.html' %] + [% END %] +[% ELSE %] + <li><p>[% loc('No clinics found.') %]</p></li> +[% END %] diff --git a/templates/web/zerotb/report/_item.html b/templates/web/zerotb/report/_item.html new file mode 100644 index 000000000..92fb8e8d8 --- /dev/null +++ b/templates/web/zerotb/report/_item.html @@ -0,0 +1,5 @@ +<li> +<a class="text" href="[% c.uri_for('/report', problem.id ) %]"> + <h4>[% problem.title | html %]</h4> +</a> +</li> -- cgit v1.2.3 From ac7443809e921594c2a5682c49ddf388cdb2598f Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 26 Mar 2014 18:01:55 +0000 Subject: Use flags to indicate type of location error This makes it a bit easier to override the message in templates which makes it a bit easier for cobrands. Leaves the default error message in location_error stash key which should prevent any existing logic breaking. --- perllib/FixMyStreet/App/Controller/Council.pm | 3 +++ perllib/FixMyStreet/App/Controller/Location.pm | 1 + templates/web/default/around/around_index.html | 2 +- templates/web/default/around/location_error.html | 1 + templates/web/fixmystreet/around/around_index.html | 2 +- templates/web/fixmystreet/around/location_error.html | 1 + 6 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 templates/web/default/around/location_error.html create mode 100644 templates/web/fixmystreet/around/location_error.html diff --git a/perllib/FixMyStreet/App/Controller/Council.pm b/perllib/FixMyStreet/App/Controller/Council.pm index 5d95c2538..ceec04027 100644 --- a/perllib/FixMyStreet/App/Controller/Council.pm +++ b/perllib/FixMyStreet/App/Controller/Council.pm @@ -69,6 +69,7 @@ sub load_and_check_areas : Private { type => $area_types ); } if ($all_areas->{error}) { + $c->stash->{location_error_mapit_error} = 1; $c->stash->{location_error} = $all_areas->{error}; return; } @@ -78,6 +79,7 @@ sub load_and_check_areas : Private { $c->cobrand->area_check( { all_areas => $all_areas }, $c->stash->{area_check_action} ); if ( !$success ) { + $c->stash->{location_error_cobrand_check} = 1; $c->stash->{location_error} = $error_msg; return; } @@ -87,6 +89,7 @@ sub load_and_check_areas : Private { # If we don't have any areas we can't accept the report if ( !scalar keys %$all_areas ) { + $c->stash->{location_error_no_areas} = 1; $c->stash->{location_error} = _('That location does not appear to be covered by a council; perhaps it is offshore or outside the country. Please try again.'); return; } diff --git a/perllib/FixMyStreet/App/Controller/Location.pm b/perllib/FixMyStreet/App/Controller/Location.pm index f103ff7f9..4312b6911 100644 --- a/perllib/FixMyStreet/App/Controller/Location.pm +++ b/perllib/FixMyStreet/App/Controller/Location.pm @@ -103,6 +103,7 @@ sub determine_location_from_pc : Private { } # pass errors back to the template + $c->stash->{location_error_pc_lookup} = 1; $c->stash->{location_error} = $error; return; } diff --git a/templates/web/default/around/around_index.html b/templates/web/default/around/around_index.html index a143e90a2..087abf65f 100644 --- a/templates/web/default/around/around_index.html +++ b/templates/web/default/around/around_index.html @@ -6,7 +6,7 @@ [% INCLUDE 'around/postcode_form.html' %] [% IF location_error %] - <p class="error">[% location_error %]</p> + [% INCLUDE 'around/location_error.html' %] [% END %] [% IF possible_location_matches %] diff --git a/templates/web/default/around/location_error.html b/templates/web/default/around/location_error.html new file mode 100644 index 000000000..9b907b64b --- /dev/null +++ b/templates/web/default/around/location_error.html @@ -0,0 +1 @@ +<p class="error">[% location_error %]</p> diff --git a/templates/web/fixmystreet/around/around_index.html b/templates/web/fixmystreet/around/around_index.html index c547b7ba9..0d9d35171 100644 --- a/templates/web/fixmystreet/around/around_index.html +++ b/templates/web/fixmystreet/around/around_index.html @@ -9,7 +9,7 @@ %] [% IF location_error %] - <p class="form-error">[% location_error %]</p> + [% INCLUDE 'around/location_error.html' %] [% END %] [% IF possible_location_matches %] diff --git a/templates/web/fixmystreet/around/location_error.html b/templates/web/fixmystreet/around/location_error.html new file mode 100644 index 000000000..fc9b2b8ce --- /dev/null +++ b/templates/web/fixmystreet/around/location_error.html @@ -0,0 +1 @@ +<p class="form-error">[% location_error %]</p> -- cgit v1.2.3 From dbe9c7ecb157196ed201d94d4159abfb034240ff Mon Sep 17 00:00:00 2001 From: Struan Donald <struan@exo.org.uk> Date: Wed, 26 Mar 2014 18:02:35 +0000 Subject: Improve location out of area error for ZeroTB --- templates/web/zerotb/around/location_error.html | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 templates/web/zerotb/around/location_error.html diff --git a/templates/web/zerotb/around/location_error.html b/templates/web/zerotb/around/location_error.html new file mode 100644 index 000000000..45428e723 --- /dev/null +++ b/templates/web/zerotb/around/location_error.html @@ -0,0 +1,8 @@ +<p class="form-error"> +[% IF location_error_no_areas %] + [% loc('This location seems to be outside the Dehli area.') %] +[% ELSE %] + [% location_error %] +[% END %] +</p> + -- cgit v1.2.3