pwpush 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Capfile +8 -0
- data/Gemfile +51 -0
- data/Gemfile.lock +224 -0
- data/LICENSE.txt +674 -0
- data/Procfile +3 -0
- data/README.md +91 -0
- data/Rakefile +8 -0
- data/TODO +21 -0
- data/app.json +21 -0
- data/app/assets/flash/clippy.swf +0 -0
- data/app/assets/flash/github-clippy.swf +0 -0
- data/app/assets/images/apple-touch-icon-ipad.png +0 -0
- data/app/assets/images/apple-touch-icon-ipad3.png +0 -0
- data/app/assets/images/apple-touch-icon-iphone.png +0 -0
- data/app/assets/images/apple-touch-icon-iphone4.png +0 -0
- data/app/assets/images/black_wood.jpg +0 -0
- data/app/assets/images/broken_noise.png +0 -0
- data/app/assets/images/button_down.png +0 -0
- data/app/assets/images/button_over.png +0 -0
- data/app/assets/images/button_up.png +0 -0
- data/app/assets/images/concrete_wall_3.png +0 -0
- data/app/assets/images/favicon.ico +0 -0
- data/app/assets/images/forkme.png +0 -0
- data/app/assets/images/outlets.png +0 -0
- data/app/assets/images/pwpush_favicon.jpg +0 -0
- data/app/assets/images/pwpush_logo.png +0 -0
- data/app/assets/images/rails.png +0 -0
- data/app/assets/javascripts/api.js.coffee +4 -0
- data/app/assets/javascripts/application.js +52 -0
- data/app/assets/javascripts/errors.js.coffee +3 -0
- data/app/assets/javascripts/fd-slider.js +1299 -0
- data/app/assets/javascripts/jquery-cookie.js +117 -0
- data/app/assets/javascripts/jquery.noty.js +520 -0
- data/app/assets/javascripts/layouts/top.js +34 -0
- data/app/assets/javascripts/passwords.js +62 -0
- data/app/assets/javascripts/spoiler.js +101 -0
- data/app/assets/javascripts/themes/default.js +156 -0
- data/app/assets/stylesheets/api.css.scss +3 -0
- data/app/assets/stylesheets/application.css +7 -0
- data/app/assets/stylesheets/errors.css.scss +3 -0
- data/app/assets/stylesheets/fd-slider.css +650 -0
- data/app/assets/stylesheets/global.css.scss +52 -0
- data/app/assets/stylesheets/passwords.css.scss +114 -0
- data/app/assets/stylesheets/users.css.scss +11 -0
- data/app/controllers/api_controller.rb +30 -0
- data/app/controllers/application_controller.rb +23 -0
- data/app/controllers/errors_controller.rb +7 -0
- data/app/controllers/passwords_controller.rb +153 -0
- data/app/controllers/users/omniauth_callbacks_controller.rb +71 -0
- data/app/controllers/views_controller.rb +11 -0
- data/app/helpers/api_helper.rb +2 -0
- data/app/helpers/application_helper.rb +31 -0
- data/app/helpers/errors_helper.rb +2 -0
- data/app/helpers/passwords_helper.rb +2 -0
- data/app/helpers/views_helper.rb +2 -0
- data/app/mailers/.gitkeep +0 -0
- data/app/models/.gitkeep +0 -0
- data/app/models/password.rb +51 -0
- data/app/models/user.rb +20 -0
- data/app/models/view.rb +4 -0
- data/app/views/api/config.html.haml +2 -0
- data/app/views/api/create.html.haml +2 -0
- data/app/views/api/generate.html.haml +2 -0
- data/app/views/api/list.html.haml +2 -0
- data/app/views/devise/confirmations/new.html.erb +12 -0
- data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
- data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/devise/passwords/edit.html.erb +16 -0
- data/app/views/devise/passwords/new.html.erb +12 -0
- data/app/views/devise/registrations/edit.html.erb +25 -0
- data/app/views/devise/registrations/new.html.haml +50 -0
- data/app/views/devise/sessions/new.html.haml +51 -0
- data/app/views/devise/shared/_links.erb +25 -0
- data/app/views/devise/unlocks/new.html.erb +12 -0
- data/app/views/errors/error_404.html.haml +21 -0
- data/app/views/errors/error_500.html.haml +21 -0
- data/app/views/layouts/_ga.html.erb +14 -0
- data/app/views/layouts/application.html.haml +41 -0
- data/app/views/pages/about.html.haml +159 -0
- data/app/views/passwords/edit.html.haml +7 -0
- data/app/views/passwords/index.html.haml +17 -0
- data/app/views/passwords/new.html.haml +68 -0
- data/app/views/passwords/show.html.haml +58 -0
- data/app/views/shared/_auth_providers.html.haml +9 -0
- data/app/views/shared/_messages.html.haml +4 -0
- data/app/views/views/_form.html.erb +16 -0
- data/app/views/views/edit.html.erb +8 -0
- data/app/views/views/index.html.erb +21 -0
- data/app/views/views/new.html.erb +5 -0
- data/app/views/views/show.html.erb +20 -0
- data/bin/bundle +13 -0
- data/config.ru +4 -0
- data/config/application.rb +51 -0
- data/config/boot.rb +6 -0
- data/config/capistrano_database_yml.rb +158 -0
- data/config/database.yml +19 -0
- data/config/deploy.rb +140 -0
- data/config/deploy/database.yml.erb +52 -0
- data/config/deploy/local_cap_config.rb.example +54 -0
- data/config/environment.rb +42 -0
- data/config/environments/development.rb +30 -0
- data/config/environments/engineyard.rb +60 -0
- data/config/environments/private.rb +60 -0
- data/config/environments/production.rb +60 -0
- data/config/environments/test.rb +39 -0
- data/config/initializers/backtrace_silencers.rb +7 -0
- data/config/initializers/devise.rb +211 -0
- data/config/initializers/inflections.rb +10 -0
- data/config/initializers/mime_types.rb +5 -0
- data/config/initializers/secret_token.rb +7 -0
- data/config/initializers/session_store.rb +8 -0
- data/config/initializers/wrap_parameters.rb +14 -0
- data/config/locales/devise.en.yml +58 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +16 -0
- data/config/unicorn.rb +22 -0
- data/db/migrate/20111128183630_create_passwords.rb +12 -0
- data/db/migrate/20111228183300_create_views.rb +16 -0
- data/db/migrate/20120102210558_devise_create_users.rb +54 -0
- data/db/migrate/20120102210559_create_rails_admin_histories_table.rb +18 -0
- data/db/migrate/20120102220933_add_admin_to_user.rb +9 -0
- data/db/migrate/20120129211750_add_lockable_to_users.rb +10 -0
- data/db/migrate/20120220172426_add_user_to_password.rb +11 -0
- data/db/migrate/20121105144421_add_deleted_to_password.rb +5 -0
- data/db/migrate/20150323145847_add_first_view_flag.rb +9 -0
- data/db/migrate/20160214205926_add_deletable_to_password.rb +5 -0
- data/db/schema.rb +78 -0
- data/db/seeds.rb +7 -0
- data/log/.gitkeep +0 -0
- data/public/404.html +26 -0
- data/public/422.html +26 -0
- data/public/500.html +26 -0
- data/public/favicon.ico +0 -0
- data/public/robots.txt +3 -0
- data/script/rails +6 -0
- metadata +226 -0
data/Procfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
PasswordPusher is an opensource Ruby on Rails application to communicate passwords over the web. Links to passwords expire after a certain number of views and/or time has passed. Hosted at [pwpush.com](https://pwpush.com) but you can also easily run your own instance internally or on Heroku with just a few steps.
|
|
4
|
+
|
|
5
|
+
I previously posted this project on [Reddit](http://www.reddit.com/r/sysadmin/comments/pfda0/do_you_email_out_passwords_i_wrote_this_utility/) which provided some great feedback - most of which has been implemented.
|
|
6
|
+
|
|
7
|
+
## Deploy to Heroku
|
|
8
|
+
|
|
9
|
+
[](https://heroku.com/deploy)
|
|
10
|
+
|
|
11
|
+
## Deploy Internally
|
|
12
|
+
|
|
13
|
+
Make sure you have git and Ruby installed and then:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
git clone git@github.com:pglombardo/PasswordPusher.git
|
|
17
|
+
cd PasswordPusher
|
|
18
|
+
gem install bundler
|
|
19
|
+
bundle install --without development production test --deployment
|
|
20
|
+
bundle exec rake assets:precompile
|
|
21
|
+
RAILS_ENV=private bundle exec rake db:setup
|
|
22
|
+
foreman start internalweb
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then view the site @ [http://localhost:5000/](http://localhost:5000/).
|
|
26
|
+
|
|
27
|
+
_Note: You can change the listening port by modifying the
|
|
28
|
+
[Procfile](https://github.com/pglombardo/PasswordPusher/blob/master/Procfile#L2)_
|
|
29
|
+
|
|
30
|
+
### Troubleshooting
|
|
31
|
+
|
|
32
|
+
#### Command not found: bundle
|
|
33
|
+
|
|
34
|
+
If you get something like `Command not found: bundle`, then you need to run
|
|
35
|
+
|
|
36
|
+
gem install bundler
|
|
37
|
+
|
|
38
|
+
_If you get something like 'Command not found: gem', then you need to install Ruby. :)_
|
|
39
|
+
|
|
40
|
+
#### SQLite3
|
|
41
|
+
|
|
42
|
+
If the 'bundle install' fails with 'checking for sqlite3.h... no', you have to install the sqlite3 packages for your operating system. For Ubuntu, the command is:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
sudo apt-get install sqlite3 ruby-sqlite3 libsqlite3-ruby libsqlite3-dev
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Other Information
|
|
49
|
+
|
|
50
|
+
* How to use the [Password API](https://github.com/pglombardo/PasswordPusher/wiki/Password-API)
|
|
51
|
+
* How to [Change the Front Page Default Values](https://github.com/pglombardo/PasswordPusher/wiki/Changing-the-Front-Page-Default-Values)
|
|
52
|
+
* How to [Switch to Production Environment](https://github.com/pglombardo/PasswordPusher/wiki/Switch-to-Production-Environment)
|
|
53
|
+
* How to [Switch to Another Backend Database](https://github.com/pglombardo/PasswordPusher/wiki/Switch-to-Another-Backend-Database)
|
|
54
|
+
|
|
55
|
+
### Tip
|
|
56
|
+
|
|
57
|
+
With the internal deploy process described above, SQLite3 is provided by default for a quick and easy setup of the application.
|
|
58
|
+
|
|
59
|
+
If you plan to use PasswordPusher internally at your organization and expect to have multiple users concurrently creating passwords, it's advised to move away from SQLite3 as it doesn't support write concurrency and errors will occur.
|
|
60
|
+
|
|
61
|
+
For example, on [https://pwpush.com](https://pwpush.com), I run the application with a Postgres database.
|
|
62
|
+
|
|
63
|
+
*Initiated from [this discussion](http://www.reddit.com/r/sysadmin/comments/yxps8/passwordpusher_best_way_to_deliver_passwords_to/c5zwts9) on reddit.*
|
|
64
|
+
|
|
65
|
+
See How to [Switch to Another Backend Database](https://github.com/pglombardo/PasswordPusher/wiki/Switch-to-Another-Backend-Database) for details.
|
|
66
|
+
|
|
67
|
+
## Note for Existing Users
|
|
68
|
+
|
|
69
|
+
If you're already hosting your own private instance of PasswordPusher, make sure to do a periodic `git pull` from time to time to always get the latest updates.
|
|
70
|
+
|
|
71
|
+
You can always checkout out the [latest commits](https://github.com/pglombardo/PasswordPusher/commits/master) to see what's been updated recently.
|
|
72
|
+
|
|
73
|
+
## Credits
|
|
74
|
+
|
|
75
|
+
Thanks to:
|
|
76
|
+
|
|
77
|
+
* [@iandunn](https://github.com/iandunn) for better password form security.
|
|
78
|
+
|
|
79
|
+
* [Kasper 'kapöw' Grubbe](https://github.com/kaspergrubbe) for the [JSON POST fix](https://github.com/pglombardo/PasswordPusher/pull/3).
|
|
80
|
+
|
|
81
|
+
* [JarvisAndPi](http://www.reddit.com/user/JarvisAndPi) for the favicon design
|
|
82
|
+
|
|
83
|
+
## See Also
|
|
84
|
+
|
|
85
|
+
[quasarj](https://github.com/quasarj) created a [django application](https://github.com/quasarj/projectgiraffe) based off of PasswordPusher
|
|
86
|
+
|
|
87
|
+
[phanaster](https://github.com/phanaster) created a [Coupon Pushing application](https://github.com/phanaster/cpsh.me) ([cpsh.me](http://cpsh.me/)) based off of PasswordPusher
|
|
88
|
+
|
|
89
|
+
[bemosior](https://github.com/bemosior) put together a PHP port of PasswordPusher: [PHPasswordPusher](https://github.com/bemosior/PHPasswordPusher)
|
|
90
|
+
|
|
91
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env rake
|
|
2
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
|
3
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
|
4
|
+
|
|
5
|
+
require File.expand_path('../config/application', __FILE__)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
PasswordPusher::Application.load_tasks
|
data/TODO
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
This is the official TODO list for PasswordPusher.
|
|
2
|
+
|
|
3
|
+
This list is a collection of suggestions and ideas by myself
|
|
4
|
+
and those contributed by the community. The list is in no particular
|
|
5
|
+
order.
|
|
6
|
+
|
|
7
|
+
Items are identifed with one of the following:
|
|
8
|
+
|
|
9
|
+
[F] = Feature
|
|
10
|
+
[E] = Enhancement/Improvement
|
|
11
|
+
[I] = Idea, possibility, thought
|
|
12
|
+
|
|
13
|
+
Issues should be filed in the Github repo:
|
|
14
|
+
https://github.com/pglombardo/PasswordPusher/issues
|
|
15
|
+
|
|
16
|
+
* [F] Add a password generator to the front page - John Connelly
|
|
17
|
+
* [E] Change from using Rails to something much lighter such as Sinatra
|
|
18
|
+
* [E] Internationalize strings
|
|
19
|
+
* [F] Add logins and password tracking (view # of views, user agent, expire passwords)
|
|
20
|
+
* [F] Add command line utility to push passwords from the command line
|
|
21
|
+
|
data/app.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "PasswordPusher",
|
|
3
|
+
"description": "An application to communicate passwords over the web. Passwords expire after a certain number of views and/or time has passed.",
|
|
4
|
+
"repository": "https://github.com/pglombardo/PasswordPusher",
|
|
5
|
+
"logo": "https://s3-eu-west-1.amazonaws.com/pwpush/pwpush_logo.png",
|
|
6
|
+
"keywords": ["password", "security", "expire"],
|
|
7
|
+
"addons": [
|
|
8
|
+
"heroku-postgresql"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"postdeploy": "bundle exec rake db:setup"
|
|
12
|
+
},
|
|
13
|
+
"env": {
|
|
14
|
+
"BUNDLE_WITHOUT": "development:test:private",
|
|
15
|
+
"WEB_CONCURRENCY": {
|
|
16
|
+
"description": "The number of processes to run.",
|
|
17
|
+
"value": "3"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// This is a manifest file that'll be compiled into including all the files listed below.
|
|
2
|
+
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
|
|
3
|
+
// be included in the compiled file accessible from http://example.com/assets/application.js
|
|
4
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
|
5
|
+
// the compiled file.
|
|
6
|
+
//
|
|
7
|
+
//= require jquery
|
|
8
|
+
//= require jquery_ujs
|
|
9
|
+
//= require_tree .
|
|
10
|
+
//= require modernizr
|
|
11
|
+
|
|
12
|
+
function showDaysValue(newValue)
|
|
13
|
+
{
|
|
14
|
+
if (newValue > 1) {
|
|
15
|
+
document.getElementById("daysrange").innerHTML=newValue + ' Days';
|
|
16
|
+
} else {
|
|
17
|
+
document.getElementById("daysrange").innerHTML=newValue + ' Day';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function showViewsValue(newValue)
|
|
22
|
+
{
|
|
23
|
+
if (newValue > 1) {
|
|
24
|
+
document.getElementById("viewsrange").innerHTML=newValue + ' Views';
|
|
25
|
+
} else {
|
|
26
|
+
document.getElementById("viewsrange").innerHTML=newValue + ' View';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
msg = "Enter the Password to be Shared"
|
|
32
|
+
function prepareTextField(e) {
|
|
33
|
+
if (e) {
|
|
34
|
+
if (e.value == msg) {
|
|
35
|
+
e.value = '';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function revertTextField(e) {
|
|
41
|
+
if (e)
|
|
42
|
+
if (e.value == '') {
|
|
43
|
+
e.value = msg;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function setCopied() {
|
|
48
|
+
$('#clip_tip').text('copied!');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
$('spoiler, .spoiler').spoilerAlert({max: 10, partial: 7})
|
|
52
|
+
|
|
@@ -0,0 +1,1299 @@
|
|
|
1
|
+
/*! Unobtrusive Slider Control / HTML5 Input Range polyfill - MIT/GPL2 @freqdec */
|
|
2
|
+
var fdSlider = (function() {
|
|
3
|
+
var sliders = {},
|
|
4
|
+
uniqueid = 0,
|
|
5
|
+
mouseWheelEnabled = true,
|
|
6
|
+
fullARIA = true,
|
|
7
|
+
describedBy = "fd-slider-describedby",
|
|
8
|
+
varSetRules = {
|
|
9
|
+
onfocus:true,
|
|
10
|
+
onvalue:true
|
|
11
|
+
},
|
|
12
|
+
noRangeBar = false,
|
|
13
|
+
html5Animation = "jump",
|
|
14
|
+
isOpera = Object.prototype.toString.call(window.opera) === "[object Opera]",
|
|
15
|
+
fpRegExp = /^([-]{0,1}[0-9]+(\.[0-9]+){0,1})$/,
|
|
16
|
+
stepRegExp = /^([0-9]+(\.[0-9]+){0,1})$/;
|
|
17
|
+
|
|
18
|
+
var parseJSON = function(str) {
|
|
19
|
+
// Check we have a String
|
|
20
|
+
if(typeof str !== 'string' || str == "") {
|
|
21
|
+
return {};
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
// Does a JSON (native or not) Object exist
|
|
25
|
+
if(typeof JSON === "object" && JSON.parse) {
|
|
26
|
+
return window.JSON.parse(str);
|
|
27
|
+
// Genious code taken from: http://kentbrewster.com/badges/
|
|
28
|
+
} else if(/mousewheelenabled|fullaria|describedby|norangebar|html5animation|varsetrules/.test(str.toLowerCase())) {
|
|
29
|
+
var f = Function(['var document,top,self,window,parent,Number,Date,Object,Function,',
|
|
30
|
+
'Array,String,Math,RegExp,Image,ActiveXObject;',
|
|
31
|
+
'return (' , str.replace(/<\!--.+-->/gim,'').replace(/\bfunction\b/g,'function-') , ');'].join(''));
|
|
32
|
+
return f();
|
|
33
|
+
};
|
|
34
|
+
} catch (e) { };
|
|
35
|
+
|
|
36
|
+
return {"err":"Could not parse the JSON object"};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
var affectJSON = function(json) {
|
|
40
|
+
if(typeof json !== "object") { return; };
|
|
41
|
+
for(key in json) {
|
|
42
|
+
value = json[key];
|
|
43
|
+
switch(key.toLowerCase()) {
|
|
44
|
+
case "mousewheelenabled":
|
|
45
|
+
mouseWheelEnabled = !!value;
|
|
46
|
+
break;
|
|
47
|
+
case "fullaria":
|
|
48
|
+
fullARIA = !!value;
|
|
49
|
+
break;
|
|
50
|
+
case "describedby":
|
|
51
|
+
describedBy = String(value);
|
|
52
|
+
break;
|
|
53
|
+
case "norangebar":
|
|
54
|
+
noRangeBar = !!value;
|
|
55
|
+
break;
|
|
56
|
+
case "html5animation":
|
|
57
|
+
html5Animation = String(value).search(/^(jump|tween|timed)$/i) != -1 ? String(value).toLowerCase() : "jump";
|
|
58
|
+
break;
|
|
59
|
+
case "varsetrules":
|
|
60
|
+
if("onfocus" in value) {
|
|
61
|
+
varSetRules.onfocus = !!value.onfocus;
|
|
62
|
+
};
|
|
63
|
+
if("onvalue" in value) {
|
|
64
|
+
varSetRules.onvalue = !!value.onvalue;
|
|
65
|
+
};
|
|
66
|
+
break;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Classic event functions
|
|
72
|
+
var addEvent = function(obj, type, fn) {
|
|
73
|
+
if( obj.attachEvent ) {
|
|
74
|
+
obj.attachEvent( "on"+type, fn );
|
|
75
|
+
} else { obj.addEventListener( type, fn, true ); }
|
|
76
|
+
};
|
|
77
|
+
var removeEvent = function(obj, type, fn) {
|
|
78
|
+
try {
|
|
79
|
+
if( obj.detachEvent ) {
|
|
80
|
+
obj.detachEvent( "on"+type, fn );
|
|
81
|
+
} else { obj.removeEventListener( type, fn, true ); }
|
|
82
|
+
} catch(err) {};
|
|
83
|
+
};
|
|
84
|
+
var stopEvent = function(e) {
|
|
85
|
+
e = e || window.event;
|
|
86
|
+
if(e.stopPropagation) {
|
|
87
|
+
e.stopPropagation();
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/*@cc_on@*/
|
|
92
|
+
/*@if(@_win32)
|
|
93
|
+
e.cancelBubble = true;
|
|
94
|
+
e.returnValue = false;
|
|
95
|
+
/*@end@*/
|
|
96
|
+
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
var preventDefault = function(e) {
|
|
100
|
+
e = e || window.event;
|
|
101
|
+
if(e.preventDefault) {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
return;
|
|
104
|
+
};
|
|
105
|
+
e.returnValue = false;
|
|
106
|
+
};
|
|
107
|
+
// Add/Remove classname utility functions
|
|
108
|
+
var addClass = function(e,c) {
|
|
109
|
+
if(new RegExp("(^|\\s)" + c + "(\\s|$)").test(e.className)) { return; };
|
|
110
|
+
e.className += ( e.className ? " " : "" ) + c;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
var removeClass = function(e,c) {
|
|
114
|
+
e.className = !c ? "" : e.className.replace(new RegExp("(^|\\s)" + c + "(\\s|$)"), " ").replace(/^\s\s*/, '').replace(/\s\s*$/, '');
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Returns an Object of key value pairs indicating which sliders have values
|
|
118
|
+
// that have been "set" by the user
|
|
119
|
+
var getValueSet = function() {
|
|
120
|
+
var obj = {};
|
|
121
|
+
for(id in sliders) {
|
|
122
|
+
obj[id] = sliders[id].getValueSet();
|
|
123
|
+
};
|
|
124
|
+
return obj;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Sets the valueSet variable for a specific slider
|
|
128
|
+
var setValueSet = function(sliderId, tf) {
|
|
129
|
+
sliders[sliderId].setValueSet(!!tf);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Does the slider exist in memory
|
|
133
|
+
var sliderExists = function(slider) {
|
|
134
|
+
return !!(slider in sliders && sliders.hasOwnProperty(slider));
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// Javascript instantiation of a slider (input type="text" or select list)
|
|
138
|
+
var createSlider = function(options) {
|
|
139
|
+
if(!options || !options.inp || !options.inp.tagName || options.inp.tagName.search(/^input|select/i) == -1) { return false; };
|
|
140
|
+
|
|
141
|
+
options.html5Shim = false;
|
|
142
|
+
|
|
143
|
+
if(options.inp.tagName.toLowerCase() == "select") {
|
|
144
|
+
if(options.inp.options.length < 2) {
|
|
145
|
+
return false;
|
|
146
|
+
};
|
|
147
|
+
options.min = 0;
|
|
148
|
+
options.max = options.inp.options.length - 1;
|
|
149
|
+
options.step = 1;
|
|
150
|
+
options.precision = 0;
|
|
151
|
+
options.scale = false;
|
|
152
|
+
options.forceValue = true;
|
|
153
|
+
} else {
|
|
154
|
+
if(String(options.inp.type).search(/^text$/i) == -1) {
|
|
155
|
+
return false;
|
|
156
|
+
};
|
|
157
|
+
options.min = options.min && String(options.min).search(fpRegExp) != -1 ? +options.min : 0;
|
|
158
|
+
options.max = options.max && String(options.max).search(fpRegExp) != -1 ? +options.max : 100;
|
|
159
|
+
options.step = options.step && String(options.step).search(stepRegExp) != -1 ? options.step : 1;
|
|
160
|
+
options.precision = options.precision && String(options.precision).search(/^[0-9]+$/) != -1 ? options.precision : (String(options.step).search(/\.([0-9]+)$/) != -1 ? String(options.step).match(/\.([0-9]+)$/)[1].length : 0);
|
|
161
|
+
options.scale = options.scale || false;
|
|
162
|
+
options.forceValue = ("forceValue" in options) ? !!options.forceValue : false;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
options.maxStep = options.maxStep && String(options.maxStep).search(stepRegExp) != -1 ? +options.maxStep : +options.step * 2;
|
|
166
|
+
options.classNames = options.classNames || "";
|
|
167
|
+
options.callbacks = options.callbacks || false;
|
|
168
|
+
|
|
169
|
+
destroySingleSlider(options.inp.id);
|
|
170
|
+
sliders[options.inp.id] = new fdRange(options);
|
|
171
|
+
return true;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
var getAttribute = function(elem, att) {
|
|
175
|
+
return elem.getAttribute(att) || "";
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// HTML5 input type="range" shim - called onload or onDomReady
|
|
179
|
+
var init = function() {
|
|
180
|
+
var inputs = document.getElementsByTagName("input"),
|
|
181
|
+
options;
|
|
182
|
+
|
|
183
|
+
for(var i = 0, inp; inp = inputs[i]; i++) {
|
|
184
|
+
|
|
185
|
+
if(inp.tagName.toLowerCase() == "input"
|
|
186
|
+
&&
|
|
187
|
+
inp.type.toLowerCase() == "text"
|
|
188
|
+
&&
|
|
189
|
+
(getAttribute(inp, "min") && getAttribute(inp, "min").search(fpRegExp) != -1
|
|
190
|
+
||
|
|
191
|
+
getAttribute(inp, "max") && getAttribute(inp, "max").search(fpRegExp) != -1
|
|
192
|
+
||
|
|
193
|
+
getAttribute(inp, "step") && getAttribute(inp, "step").search(/^(any|([0-9]+(\.[0-9]+){0,1}))$/i) != -1
|
|
194
|
+
)) {
|
|
195
|
+
|
|
196
|
+
// Skip elements that have already been created are are resident in the DOM
|
|
197
|
+
if(inp.id && document.getElementById("fd-slider-"+inp.id)) {
|
|
198
|
+
continue;
|
|
199
|
+
// Destroy elements that have already been created but not resident in the DOM
|
|
200
|
+
} else if(inp.id && !document.getElementById("fd-slider-"+inp.id)) {
|
|
201
|
+
destroySingleSlider(inp.id);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Create an id for the form element if necessary
|
|
205
|
+
if(!inp.id) {
|
|
206
|
+
inp.id = "fd-slider-form-elem-" + uniqueid++;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Basic option Object
|
|
210
|
+
options = {
|
|
211
|
+
inp: inp,
|
|
212
|
+
callbacks: [],
|
|
213
|
+
animation: html5Animation,
|
|
214
|
+
vertical: getAttribute(inp, "data-fd-slider-vertical") ? true : !!(inp.offsetHeight > inp.offsetWidth),
|
|
215
|
+
classNames: getAttribute(inp, "data-fd-slider-vertical"),
|
|
216
|
+
html5Shim: true
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if(options.vertical && !getAttribute(inp, "data-fd-slider-vertical")) {
|
|
220
|
+
options.inpHeight = inp.offsetHeight;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
options.min = getAttribute(inp, "min") || 0;
|
|
224
|
+
options.max = getAttribute(inp, "max") || 100;
|
|
225
|
+
options.step = getAttribute(inp, "step").search(/^any$/i) != -1 ? options.max - options.min : getAttribute(inp, "step").search(stepRegExp) != -1 ? inp.getAttribute("step") : 1;
|
|
226
|
+
options.precision = String(options.step).search(/\.([0-9]+)$/) != -1 ? String(options.step).match(/\.([0-9]+)$/)[1].length : 0;
|
|
227
|
+
options.maxStep = options.step * 2;
|
|
228
|
+
|
|
229
|
+
destroySingleSlider(options.inp.id);
|
|
230
|
+
sliders[options.inp.id] = new fdRange(options);
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
return true;
|
|
235
|
+
};
|
|
236
|
+
var destroySingleSlider = function(id) {
|
|
237
|
+
if(id in sliders && sliders.hasOwnProperty(id)) {
|
|
238
|
+
sliders[id].destroy();
|
|
239
|
+
delete sliders[id];
|
|
240
|
+
return true;
|
|
241
|
+
};
|
|
242
|
+
return false;
|
|
243
|
+
};
|
|
244
|
+
var destroyAllsliders = function(e) {
|
|
245
|
+
for(slider in sliders) {
|
|
246
|
+
if(sliders.hasOwnProperty(slider)) {
|
|
247
|
+
sliders[slider].destroy();
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
sliders = [];
|
|
251
|
+
};
|
|
252
|
+
var unload = function(e) {
|
|
253
|
+
destroyAllsliders();
|
|
254
|
+
sliders = null;
|
|
255
|
+
};
|
|
256
|
+
var resize = function(e) {
|
|
257
|
+
for(slider in sliders) {
|
|
258
|
+
if(sliders.hasOwnProperty(slider)) {
|
|
259
|
+
sliders[slider].onResize();
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
var onDomReady = function() {
|
|
264
|
+
removeEvent(window, "load", init);
|
|
265
|
+
init();
|
|
266
|
+
};
|
|
267
|
+
var removeOnLoadEvent = function() {
|
|
268
|
+
removeEvent(window, "load", init);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
function fdRange(options) {
|
|
272
|
+
var inp = options.inp,
|
|
273
|
+
disabled = false,
|
|
274
|
+
tagName = inp.tagName.toLowerCase(),
|
|
275
|
+
min = +options.min,
|
|
276
|
+
max = +options.max,
|
|
277
|
+
rMin = +options.min,
|
|
278
|
+
rMax = +options.max,
|
|
279
|
+
range = Math.abs(max - min),
|
|
280
|
+
step = tagName == "select" ? 1 : +options.step,
|
|
281
|
+
maxStep = options.maxStep ? +options.maxStep : step * 2,
|
|
282
|
+
precision = options.precision || 0,
|
|
283
|
+
steps = Math.ceil(range / step),
|
|
284
|
+
scale = options.scale || false,
|
|
285
|
+
hideInput = !!options.hideInput,
|
|
286
|
+
animation = options.animation || "",
|
|
287
|
+
vertical = !!options.vertical,
|
|
288
|
+
callbacks = options.callbacks || {},
|
|
289
|
+
classNames = options.classNames || "",
|
|
290
|
+
html5Shim = !!options.html5Shim,
|
|
291
|
+
defaultVal = max < min ? min : min + ((max - min) / 2),
|
|
292
|
+
resetDef = tagName == "select" ? inp.selectedIndex : inp.defaultValue || defaultVal,
|
|
293
|
+
forceValue = html5Shim || !!options.forceValue,
|
|
294
|
+
inpHeight = html5Shim && vertical && ("inpHeight" in options) ? options.inpHeight : false,
|
|
295
|
+
timer = null,
|
|
296
|
+
kbEnabled = true,
|
|
297
|
+
initialVal = tagName == "select" ? inp.selectedIndex : inp.value,
|
|
298
|
+
sliderH = 0,
|
|
299
|
+
sliderW = 0,
|
|
300
|
+
tweenX = 0,
|
|
301
|
+
tweenB = 0,
|
|
302
|
+
tweenC = 0,
|
|
303
|
+
tweenD = 0,
|
|
304
|
+
frame = 0,
|
|
305
|
+
x = 0,
|
|
306
|
+
y = 0,
|
|
307
|
+
rMaxPx = 0,
|
|
308
|
+
rMinPx = 0,
|
|
309
|
+
handlePos = 0,
|
|
310
|
+
destPos = 0,
|
|
311
|
+
mousePos = 0,
|
|
312
|
+
stepPx = 0,
|
|
313
|
+
userSet = false,
|
|
314
|
+
touchEvents = false,
|
|
315
|
+
outerWrapper,
|
|
316
|
+
wrapper,
|
|
317
|
+
handle,
|
|
318
|
+
rangeBar,
|
|
319
|
+
bar;
|
|
320
|
+
|
|
321
|
+
// For the reset event to work we have set a defaultValue
|
|
322
|
+
if(tagName == "input" && forceValue && !inp.defaultValue) {
|
|
323
|
+
inp.defaultValue = getWorkingValueFromInput();
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Make sure we have a negative step if the max < min
|
|
327
|
+
if(max < min) {
|
|
328
|
+
step = -Math.abs(step);
|
|
329
|
+
maxStep = -Math.abs(maxStep);
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
// Add the 100% scale mark if needs be
|
|
333
|
+
if(scale) {
|
|
334
|
+
scale[100] = max;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// Set the "userSet" variable programmatically for this slider
|
|
338
|
+
function valueSet(tf) {
|
|
339
|
+
tf = !!tf;
|
|
340
|
+
if(tf != userSet) {
|
|
341
|
+
userSet = tf;
|
|
342
|
+
valueToPixels(getWorkingValueFromInput());
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
function disableSlider(noCallback) {
|
|
347
|
+
if(disabled && !noCallback) {
|
|
348
|
+
return;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
setTabIndex(handle, -1);
|
|
353
|
+
removeEvent(handle, "focus", onFocus);
|
|
354
|
+
removeEvent(handle, "blur", onBlur);
|
|
355
|
+
|
|
356
|
+
if(!isOpera) {
|
|
357
|
+
removeEvent(handle, "keydown", onKeyDown);
|
|
358
|
+
removeEvent(handle, "keypress", onKeyPress);
|
|
359
|
+
} else {
|
|
360
|
+
removeEvent(handle, "keypress", onKeyDown);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
removeEvent(outerWrapper, "mouseover", onMouseOver);
|
|
364
|
+
removeEvent(outerWrapper, "mouseout", onMouseOut);
|
|
365
|
+
removeEvent(outerWrapper, "mousedown", onMouseDown);
|
|
366
|
+
removeEvent(outerWrapper, "touchstart", onMouseDown);
|
|
367
|
+
|
|
368
|
+
if(mouseWheelEnabled) {
|
|
369
|
+
if (window.addEventListener && !window.devicePixelRatio) window.removeEventListener('DOMMouseScroll', trackMouseWheel, false);
|
|
370
|
+
else {
|
|
371
|
+
removeEvent(document, "mousewheel", trackMouseWheel);
|
|
372
|
+
removeEvent(window, "mousewheel", trackMouseWheel);
|
|
373
|
+
};
|
|
374
|
+
};
|
|
375
|
+
} catch(err) {};
|
|
376
|
+
|
|
377
|
+
removeClass(outerWrapper, "fd-slider-focused");
|
|
378
|
+
removeClass(outerWrapper, "fd-slider-active");
|
|
379
|
+
|
|
380
|
+
addClass(outerWrapper, "fd-slider-disabled");
|
|
381
|
+
outerWrapper.setAttribute("aria-disabled", true);
|
|
382
|
+
inp.disabled = disabled = true;
|
|
383
|
+
clearTimeout(timer);
|
|
384
|
+
|
|
385
|
+
if(!noCallback) {
|
|
386
|
+
callback("disable");
|
|
387
|
+
};
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
function enableSlider(noCallback) {
|
|
391
|
+
if(!disabled && !noCallback) {
|
|
392
|
+
return;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
setTabIndex(handle, 0);
|
|
396
|
+
addEvent(handle, "focus", onFocus);
|
|
397
|
+
addEvent(handle, "blur", onBlur);
|
|
398
|
+
|
|
399
|
+
if(!isOpera) {
|
|
400
|
+
addEvent(handle, "keydown", onKeyDown);
|
|
401
|
+
addEvent(handle, "keypress", onKeyPress);
|
|
402
|
+
} else {
|
|
403
|
+
addEvent(handle, "keypress", onKeyDown);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
addEvent(outerWrapper, "touchstart", onMouseDown);
|
|
407
|
+
addEvent(outerWrapper, "mousedown", onMouseDown);
|
|
408
|
+
addEvent(outerWrapper, "mouseover", onMouseOver);
|
|
409
|
+
addEvent(outerWrapper, "mouseout", onMouseOut);
|
|
410
|
+
|
|
411
|
+
removeClass(outerWrapper, "fd-slider-disabled");
|
|
412
|
+
outerWrapper.setAttribute("aria-disabled", false);
|
|
413
|
+
inp.disabled = disabled = touchEvents = false;
|
|
414
|
+
|
|
415
|
+
if(!noCallback) {
|
|
416
|
+
callback("enable");
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Destroys a slider
|
|
421
|
+
function destroySlider() {
|
|
422
|
+
// Clear any timeouts
|
|
423
|
+
clearTimeout(timer);
|
|
424
|
+
|
|
425
|
+
// Remove pointers to DOM nodes
|
|
426
|
+
wrapper = bar = handle = outerWrapper = timer = null;
|
|
427
|
+
|
|
428
|
+
// Call the "destroy" callback
|
|
429
|
+
callback("destroy");
|
|
430
|
+
|
|
431
|
+
// Delete the callback functions
|
|
432
|
+
callbacks = null;
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// Calculates the pixel increment etc
|
|
436
|
+
function redraw() {
|
|
437
|
+
locate();
|
|
438
|
+
// Internet Explorer requires the try catch as hidden
|
|
439
|
+
// elements throw errors
|
|
440
|
+
try {
|
|
441
|
+
var sW = outerWrapper.offsetWidth,
|
|
442
|
+
sH = outerWrapper.offsetHeight,
|
|
443
|
+
hW = handle.offsetWidth,
|
|
444
|
+
hH = handle.offsetHeight,
|
|
445
|
+
bH = bar.offsetHeight,
|
|
446
|
+
bW = bar.offsetWidth,
|
|
447
|
+
mPx = vertical ? sH - hH : sW - hW;
|
|
448
|
+
|
|
449
|
+
stepPx = mPx / steps;
|
|
450
|
+
rMinPx = Math.max(scale ? percentToPixels(valueToPercent(rMin)) : Math.abs((rMin - min) / step) * stepPx, 0);
|
|
451
|
+
rMaxPx = Math.min(scale ? percentToPixels(valueToPercent(rMax)) : Math.abs((rMax - min) / step) * stepPx, Math.floor(vertical ? sH - hH : sW - hW));
|
|
452
|
+
|
|
453
|
+
sliderW = sW;
|
|
454
|
+
sliderH = sH;
|
|
455
|
+
|
|
456
|
+
// Use the input value
|
|
457
|
+
valueToPixels(forceValue ? getWorkingValueFromInput() : (tagName == "select" ? inp.selectedIndex : parseFloat(inp.value)));
|
|
458
|
+
|
|
459
|
+
} catch(err) {};
|
|
460
|
+
callback("redraw");
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// Calls a callback function
|
|
464
|
+
function callback(type) {
|
|
465
|
+
if(!html5Shim) {
|
|
466
|
+
if(callbacks.hasOwnProperty(type)) {
|
|
467
|
+
var cbObj = {"disabled":disabled, "elem":inp, "value":tagName == "select" ? inp.options[inp.selectedIndex].value : inp.value};
|
|
468
|
+
|
|
469
|
+
// Call all functions in sequence
|
|
470
|
+
for(var i = 0, func; func = callbacks[type][i]; i++) {
|
|
471
|
+
func.call(inp, cbObj);
|
|
472
|
+
};
|
|
473
|
+
};
|
|
474
|
+
} else if(type.match(/^(blur|focus|change)$/i)) {
|
|
475
|
+
if(typeof(document.createEventObject) != 'undefined') {
|
|
476
|
+
try {
|
|
477
|
+
var e = document.createEventObject();
|
|
478
|
+
inp.fireEvent('on' + type.toLowerCase(), e);
|
|
479
|
+
} catch(err){ };
|
|
480
|
+
} else if(typeof(document.createEvent) != 'undefined') {
|
|
481
|
+
var e = document.createEvent('HTMLEvents');
|
|
482
|
+
e.initEvent(type, true, true);
|
|
483
|
+
inp.dispatchEvent(e);
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// FOCUS & BLUR events
|
|
489
|
+
function onFocus(e) {
|
|
490
|
+
addClass(outerWrapper, 'fd-slider-focused');
|
|
491
|
+
|
|
492
|
+
// Is the value said to have been set by the user onfocus
|
|
493
|
+
if(varSetRules.onfocus) {
|
|
494
|
+
userSet = true;
|
|
495
|
+
valueToPixels(getWorkingValueFromInput());
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// If mousewheel events required then add them
|
|
499
|
+
if(mouseWheelEnabled) {
|
|
500
|
+
addEvent(window, 'DOMMouseScroll', trackMouseWheel);
|
|
501
|
+
addEvent(document, 'mousewheel', trackMouseWheel);
|
|
502
|
+
if(!isOpera) {
|
|
503
|
+
addEvent(window, 'mousewheel', trackMouseWheel);
|
|
504
|
+
};
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// Callback...
|
|
508
|
+
callback("focus");
|
|
509
|
+
return true;
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
function onBlur(e) {
|
|
513
|
+
removeClass(outerWrapper, 'fd-slider-focused');
|
|
514
|
+
|
|
515
|
+
// Remove mousewheel events if necessary
|
|
516
|
+
if(mouseWheelEnabled) {
|
|
517
|
+
removeEvent(document, 'mousewheel', trackMouseWheel);
|
|
518
|
+
removeEvent(window, 'DOMMouseScroll', trackMouseWheel);
|
|
519
|
+
if(!isOpera) {
|
|
520
|
+
removeEvent(window, 'mousewheel', trackMouseWheel);
|
|
521
|
+
};
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
kbEnabled = true;
|
|
525
|
+
|
|
526
|
+
// Callback...
|
|
527
|
+
callback("blur");
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// MOUSEWHEEL events
|
|
531
|
+
function trackMouseWheel(e) {
|
|
532
|
+
if(!kbEnabled) {
|
|
533
|
+
return;
|
|
534
|
+
};
|
|
535
|
+
e = e || window.event;
|
|
536
|
+
var delta = 0,
|
|
537
|
+
value;
|
|
538
|
+
|
|
539
|
+
if (e.wheelDelta) {
|
|
540
|
+
delta = e.wheelDelta/120;
|
|
541
|
+
// Older versions of Opera require a small hack to inverse the delta
|
|
542
|
+
if (isOpera && window.opera.version() < 9.2) {
|
|
543
|
+
delta = -delta;
|
|
544
|
+
};
|
|
545
|
+
} else if(e.detail) {
|
|
546
|
+
delta = -e.detail/3;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
if(vertical) {
|
|
550
|
+
delta = -delta;
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
if(delta) {
|
|
554
|
+
value = getWorkingValueFromInput();
|
|
555
|
+
value += (delta < 0) ? -step : step;
|
|
556
|
+
userSet = true;
|
|
557
|
+
valueToPixels(getValidValue(value));
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
return stopEvent(e);
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
// KEYBOARD events
|
|
564
|
+
function onKeyPress(e) {
|
|
565
|
+
e = e || window.event;
|
|
566
|
+
// Let all non-hijacked keyboard events pass
|
|
567
|
+
if((e.keyCode >= 33 && e.keyCode <= 40) || !kbEnabled || e.keyCode == 45 || e.keyCode == 46) {
|
|
568
|
+
return stopEvent(e);
|
|
569
|
+
};
|
|
570
|
+
return true;
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
function onKeyDown(e) {
|
|
574
|
+
if(!kbEnabled) {
|
|
575
|
+
return true;
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
e = e || window.event;
|
|
579
|
+
var kc = e.keyCode != null ? e.keyCode : e.charCode,
|
|
580
|
+
value;
|
|
581
|
+
|
|
582
|
+
if(kc < 33 || (kc > 40 && (kc != 45 && kc != 46))) {
|
|
583
|
+
return true;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
value = getWorkingValueFromInput();
|
|
587
|
+
|
|
588
|
+
if( kc == 37 || kc == 40 || kc == 46 || kc == 34) {
|
|
589
|
+
// left, down, ins, page down
|
|
590
|
+
value -= (e.ctrlKey || kc == 34 ? +maxStep : +step);
|
|
591
|
+
} else if( kc == 39 || kc == 38 || kc == 45 || kc == 33) {
|
|
592
|
+
// right, up, del, page up
|
|
593
|
+
value += (e.ctrlKey || kc == 33 ? +maxStep : +step);
|
|
594
|
+
} else if( kc == 35 ) {
|
|
595
|
+
// max
|
|
596
|
+
value = rMax;
|
|
597
|
+
} else if( kc == 36 ) {
|
|
598
|
+
// min
|
|
599
|
+
value = rMin;
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
userSet = true;
|
|
603
|
+
valueToPixels(getValidValue(value));
|
|
604
|
+
|
|
605
|
+
callback("update");
|
|
606
|
+
|
|
607
|
+
// Opera doesn't let us cancel key events so the up/down arrows and home/end buttons will scroll the screen - which sucks
|
|
608
|
+
preventDefault(e);
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
// MOUSE & TOUCH events
|
|
612
|
+
|
|
613
|
+
// Mouseover the slider
|
|
614
|
+
function onMouseOver(e) {
|
|
615
|
+
addClass(outerWrapper, 'fd-slider-hover');
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
// Mouseout of the slider
|
|
619
|
+
function onMouseOut(e) {
|
|
620
|
+
// Should really check we are not still in the slider
|
|
621
|
+
removeClass(outerWrapper, 'fd-slider-hover');
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
// Mousedown on the slider
|
|
625
|
+
function onMouseDown(e) {
|
|
626
|
+
e = e || window.event;
|
|
627
|
+
|
|
628
|
+
// Stop page scrolling
|
|
629
|
+
preventDefault(e);
|
|
630
|
+
|
|
631
|
+
// Grab the event target
|
|
632
|
+
var targ;
|
|
633
|
+
if (e.target) {
|
|
634
|
+
targ = e.target;
|
|
635
|
+
} else if (e.srcElement) {
|
|
636
|
+
targ = e.srcElement;
|
|
637
|
+
};
|
|
638
|
+
if(targ && targ.nodeType == 3) {
|
|
639
|
+
targ = targ.parentNode;
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// Are we using touchEvents
|
|
643
|
+
if(e.touches) {
|
|
644
|
+
// Skip gestures
|
|
645
|
+
if(e.targetTouches && e.targetTouches.length != 1) {
|
|
646
|
+
return false;
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
e = e.touches[0];
|
|
650
|
+
touchEvents = true;
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
// Stop any animation timers
|
|
654
|
+
clearTimeout(timer);
|
|
655
|
+
timer = null;
|
|
656
|
+
|
|
657
|
+
// Not keyboard enabled
|
|
658
|
+
kbEnabled = false;
|
|
659
|
+
|
|
660
|
+
// User has set a value
|
|
661
|
+
userSet = true;
|
|
662
|
+
|
|
663
|
+
// Handle mousedown - initiate drag
|
|
664
|
+
if(targ.className.search("fd-slider-handle") != -1) {
|
|
665
|
+
mousePos = vertical ? e.clientY : e.clientX;
|
|
666
|
+
handlePos = parseInt(vertical ? handle.offsetTop : handle.offsetLeft)||0;
|
|
667
|
+
|
|
668
|
+
// Set a value on first click even if no movement
|
|
669
|
+
trackMouse(e);
|
|
670
|
+
|
|
671
|
+
if(!touchEvents) {
|
|
672
|
+
addEvent(document, 'mousemove', trackMouse);
|
|
673
|
+
addEvent(document, 'mouseup', stopDrag);
|
|
674
|
+
} else {
|
|
675
|
+
addEvent(document, 'touchmove', trackMouse);
|
|
676
|
+
addEvent(document, 'touchend', stopDrag);
|
|
677
|
+
// Remove mouseEvents to stop them firing after the touch event
|
|
678
|
+
removeEvent(outerWrapper, "mousedown", onMouseDown);
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
addClass(outerWrapper, 'fd-slider-active');
|
|
682
|
+
addClass(document.body, "fd-slider-drag-" + (vertical ? "vertical" : "horizontal"));
|
|
683
|
+
|
|
684
|
+
callback("dragstart");
|
|
685
|
+
|
|
686
|
+
// Wrapper mousedown - initiate animation to click point
|
|
687
|
+
} else {
|
|
688
|
+
locate();
|
|
689
|
+
|
|
690
|
+
var posx = 0,
|
|
691
|
+
sLft = 0,
|
|
692
|
+
sTop = 0;
|
|
693
|
+
|
|
694
|
+
// Internet Explorer doctype woes
|
|
695
|
+
if(document.documentElement && document.documentElement.scrollTop) {
|
|
696
|
+
sTop = document.documentElement.scrollTop;
|
|
697
|
+
sLft = document.documentElement.scrollLeft;
|
|
698
|
+
} else if (document.body) {
|
|
699
|
+
sTop = document.body.scrollTop;
|
|
700
|
+
sLft = document.body.scrollLeft;
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
if(e.pageX) {
|
|
704
|
+
posx = vertical ? e.pageY : e.pageX;
|
|
705
|
+
} else if (e.clientX) {
|
|
706
|
+
posx = vertical ? e.clientY + sTop : e.clientX + sLft;
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
posx -= vertical ? y + Math.round(handle.offsetHeight / 2) : x + Math.round(handle.offsetWidth / 2);
|
|
710
|
+
posx = snapToPxValue(posx);
|
|
711
|
+
|
|
712
|
+
// Tween animation to click point
|
|
713
|
+
if(animation == "tween") {
|
|
714
|
+
addClass(outerWrapper, 'fd-slider-active');
|
|
715
|
+
tweenTo(posx);
|
|
716
|
+
// Progressive increment to click point
|
|
717
|
+
} else if(animation == "timed") {
|
|
718
|
+
addClass(outerWrapper, 'fd-slider-active');
|
|
719
|
+
addEvent(document, touchEvents ? 'touchend' : 'mouseup', onDocMouseUp);
|
|
720
|
+
destPos = posx;
|
|
721
|
+
onTimer();
|
|
722
|
+
// Immediate jump to click point
|
|
723
|
+
} else {
|
|
724
|
+
pixelsToValue(posx);
|
|
725
|
+
//addEvent(document, touchEvents ? 'touchend' : 'mouseup', onMouseUp);
|
|
726
|
+
};
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
return stopEvent(e);
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// Progressive increment to click point - clear the animation timer and remove the mouseup/touchend event
|
|
733
|
+
function onDocMouseUp( e ) {
|
|
734
|
+
e = e || window.event;
|
|
735
|
+
|
|
736
|
+
preventDefault(e);
|
|
737
|
+
removeEvent(document, touchEvents ? 'touchend' : 'mouseup', onDocMouseUp);
|
|
738
|
+
removeClass(outerWrapper, "fd-slider-active");
|
|
739
|
+
|
|
740
|
+
clearTimeout(timer);
|
|
741
|
+
timer = null;
|
|
742
|
+
kbEnabled = true;
|
|
743
|
+
|
|
744
|
+
return stopEvent(e);
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
// Mouseup or touchend event on the document to stop drag
|
|
748
|
+
function stopDrag(e) {
|
|
749
|
+
e = e || window.event;
|
|
750
|
+
|
|
751
|
+
preventDefault(e);
|
|
752
|
+
|
|
753
|
+
if(touchEvents) {
|
|
754
|
+
removeEvent(document, 'touchmove', trackMouse);
|
|
755
|
+
removeEvent(document, 'touchend', stopDrag);
|
|
756
|
+
} else {
|
|
757
|
+
removeEvent(document, 'mousemove', trackMouse);
|
|
758
|
+
removeEvent(document, 'mouseup', stopDrag);
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
kbEnabled = true;
|
|
762
|
+
removeClass(document.body, "fd-slider-drag-" + (vertical ? "vertical" : "horizontal"));
|
|
763
|
+
removeClass(outerWrapper, "fd-slider-active");
|
|
764
|
+
|
|
765
|
+
callback("dragend");
|
|
766
|
+
|
|
767
|
+
return stopEvent(e);
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// Mousemove or touchmove event on the drag handle
|
|
771
|
+
function trackMouse(e) {
|
|
772
|
+
e = e || window.event;
|
|
773
|
+
|
|
774
|
+
preventDefault(e);
|
|
775
|
+
|
|
776
|
+
if(e.touches) {
|
|
777
|
+
// Skip gestures
|
|
778
|
+
if(e.targetTouches && e.targetTouches.length != 1) {
|
|
779
|
+
return false;
|
|
780
|
+
};
|
|
781
|
+
e = e.touches[0];
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
pixelsToValue(snapToPxValue(handlePos + (vertical ? e.clientY - mousePos : e.clientX - mousePos)));
|
|
785
|
+
|
|
786
|
+
return false;
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
// Increments the slider by "inc" steps
|
|
790
|
+
function increment(inc) {
|
|
791
|
+
var value = getWorkingValueFromInput();
|
|
792
|
+
userSet = true;
|
|
793
|
+
value += inc * step;
|
|
794
|
+
valueToPixels(getValidValue(value));
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// Attempts to locate the on-screen position of the slider
|
|
798
|
+
function locate(){
|
|
799
|
+
var curleft = 0,
|
|
800
|
+
curtop = 0,
|
|
801
|
+
obj = outerWrapper;
|
|
802
|
+
|
|
803
|
+
// Try catch for IE's benefit
|
|
804
|
+
try {
|
|
805
|
+
while (obj.offsetParent) {
|
|
806
|
+
curleft += obj.offsetLeft;
|
|
807
|
+
curtop += obj.offsetTop;
|
|
808
|
+
obj = obj.offsetParent;
|
|
809
|
+
};
|
|
810
|
+
} catch(err) {};
|
|
811
|
+
x = curleft;
|
|
812
|
+
y = curtop;
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
// Used during the progressive animation to click point
|
|
816
|
+
function onTimer() {
|
|
817
|
+
var xtmp = parseInt(vertical ? handle.offsetTop : handle.offsetLeft, 10);
|
|
818
|
+
xtmp = Math.round((destPos < xtmp) ? Math.max(destPos, Math.floor(xtmp - stepPx)) : Math.min(destPos, Math.ceil(xtmp + stepPx)));
|
|
819
|
+
|
|
820
|
+
pixelsToValue(snapToPxValue(xtmp));
|
|
821
|
+
if(xtmp != destPos) {
|
|
822
|
+
timer = setTimeout(onTimer, steps > 20 ? 50 : 100);
|
|
823
|
+
} else {
|
|
824
|
+
kbEnabled = true;
|
|
825
|
+
removeClass(outerWrapper, "fd-slider-active");
|
|
826
|
+
callback("finalise");
|
|
827
|
+
};
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
var tween = function(){
|
|
831
|
+
frame++;
|
|
832
|
+
var c = tweenC,
|
|
833
|
+
d = 20,
|
|
834
|
+
t = frame,
|
|
835
|
+
b = tweenB,
|
|
836
|
+
x = Math.ceil((t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b);
|
|
837
|
+
|
|
838
|
+
pixelsToValue(t == d ? tweenX : x);
|
|
839
|
+
|
|
840
|
+
if(t!=d) {
|
|
841
|
+
// Call the "move" callback on each animation increment
|
|
842
|
+
callback("move");
|
|
843
|
+
timer = setTimeout(tween, 20);
|
|
844
|
+
} else {
|
|
845
|
+
clearTimeout(timer);
|
|
846
|
+
timer = null;
|
|
847
|
+
kbEnabled = true;
|
|
848
|
+
|
|
849
|
+
removeClass(outerWrapper, "fd-slider-focused");
|
|
850
|
+
removeClass(outerWrapper, "fd-slider-active");
|
|
851
|
+
|
|
852
|
+
// Call the "finalise" callback whenever the animation is complete
|
|
853
|
+
callback("finalise");
|
|
854
|
+
};
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
function tweenTo(tx){
|
|
858
|
+
kbEnabled = false;
|
|
859
|
+
tweenX = parseInt(tx, 10);
|
|
860
|
+
tweenB = parseInt(vertical ? handle.offsetTop : handle.offsetLeft, 10);
|
|
861
|
+
tweenC = tweenX - tweenB;
|
|
862
|
+
tweenD = 20;
|
|
863
|
+
frame = 0;
|
|
864
|
+
|
|
865
|
+
if(!timer) {
|
|
866
|
+
timer = setTimeout(tween, 20);
|
|
867
|
+
};
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
// Returns a value within the range & sets the userSet var
|
|
871
|
+
// i.e. has the user entered a valid value
|
|
872
|
+
function checkValue(value) {
|
|
873
|
+
if(isNaN(value) || value === "" || typeof value == "undefined") {
|
|
874
|
+
userSet = false;
|
|
875
|
+
return defaultVal;
|
|
876
|
+
} else if(value < Math.min(rMin,rMax)) {
|
|
877
|
+
userSet = false;
|
|
878
|
+
return Math.min(rMin,rMax);
|
|
879
|
+
} else if(value > Math.max(rMin,rMax)) {
|
|
880
|
+
userSet = false;
|
|
881
|
+
return Math.max(rMin,rMax);
|
|
882
|
+
};
|
|
883
|
+
userSet = true;
|
|
884
|
+
return value;
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
// Returns a value within a range - uses the form element value as base
|
|
888
|
+
function getWorkingValueFromInput() {
|
|
889
|
+
return getValidValue(tagName == "input" ? parseFloat(inp.value) : inp.selectedIndex);
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
// Returns a value within the range
|
|
893
|
+
function getValidValue(value) {
|
|
894
|
+
return (isNaN(value) || value === "" || typeof value == "undefined") ? defaultVal : Math.min(Math.max(value, Math.min(rMin,rMax)), Math.max(rMin,rMax));
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// Calculates value according to pixel position of slider handle
|
|
898
|
+
function pixelsToValue(px) {
|
|
899
|
+
var val = getValidValue(scale ? percentToValue(pixelsToPercent(px)) : vertical ? max - (Math.round(px / stepPx) * step) : min + (Math.round(px / stepPx) * step));
|
|
900
|
+
|
|
901
|
+
handle.style[vertical ? "top" : "left"] = (px || 0) + "px";
|
|
902
|
+
redrawRange();
|
|
903
|
+
setInputValue((tagName == "select" || step == 1) ? Math.round(val) : val);
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
// Calculates pixel position according to form element value
|
|
907
|
+
function valueToPixels(val) {
|
|
908
|
+
var clearVal = false,
|
|
909
|
+
value;
|
|
910
|
+
|
|
911
|
+
// Allow empty values for non-polyfill sliders
|
|
912
|
+
if((typeof val == "undefined" || isNaN(val) || val === "") && tagName == "input" && !forceValue) {
|
|
913
|
+
value = defaultVal;
|
|
914
|
+
clearVal = true;
|
|
915
|
+
userSet = false;
|
|
916
|
+
} else {
|
|
917
|
+
value = checkValue(val);
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
handle.style[vertical ? "top" : "left"] = (scale ? percentToPixels(valueToPercent(value)) : vertical ? Math.round(((max - value) / step) * stepPx) : Math.round(((value - min) / step) * stepPx)) + "px";
|
|
921
|
+
redrawRange();
|
|
922
|
+
setInputValue(clearVal ? "" : value);
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// Rounds a pixel value to the nearest "snap" point on the slider scale
|
|
926
|
+
function snapToPxValue(px) {
|
|
927
|
+
if(scale) {
|
|
928
|
+
return Math.max(Math.min(rMaxPx, px), rMinPx);
|
|
929
|
+
} else {
|
|
930
|
+
var rem = px % stepPx;
|
|
931
|
+
if(rem && rem >= (stepPx / 2)) {
|
|
932
|
+
px += (stepPx - rem);
|
|
933
|
+
} else {
|
|
934
|
+
px -= rem;
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
if(px < Math.min(Math.abs(rMinPx), Math.abs(rMaxPx))) {
|
|
938
|
+
px = Math.min(Math.abs(rMinPx), Math.abs(rMaxPx));
|
|
939
|
+
} else if(px > Math.max(Math.abs(rMinPx), Math.abs(rMaxPx))) {
|
|
940
|
+
px = Math.max(Math.abs(rMinPx), Math.abs(rMaxPx));
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
return Math.min(Math.max(px, 0), rMaxPx);
|
|
944
|
+
};
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
// Calculates a value according to percentage of distance handle has travelled
|
|
948
|
+
function percentToValue(pct) {
|
|
949
|
+
var st = 0,
|
|
950
|
+
fr = min,
|
|
951
|
+
value;
|
|
952
|
+
|
|
953
|
+
for(var s in scale) {
|
|
954
|
+
if(!scale.hasOwnProperty(s)) {
|
|
955
|
+
continue;
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
if(pct >= st && pct <= +s ) {
|
|
959
|
+
value = fr + ((pct - st) * (+scale[s] - fr) ) / (+s - st);
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
st = +s;
|
|
963
|
+
fr = +scale[s];
|
|
964
|
+
};
|
|
965
|
+
|
|
966
|
+
return value;
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
// Calculates the percentage handle position according to form element value
|
|
970
|
+
function valueToPercent(value) {
|
|
971
|
+
var st = 0,
|
|
972
|
+
fr = min,
|
|
973
|
+
pct = 0;
|
|
974
|
+
|
|
975
|
+
for(var s in scale) {
|
|
976
|
+
if(!scale.hasOwnProperty(s)) {
|
|
977
|
+
continue;
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
if(value >= fr && value <= +scale[s]){
|
|
981
|
+
pct = st + (value - fr) * (+s - st) / (+scale[s] - fr);
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
st = +s;
|
|
985
|
+
fr = +scale[s];
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
return pct;
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
function percentToPixels(percent) {
|
|
992
|
+
return ((outerWrapper[vertical ? "offsetHeight" : "offsetWidth"] - handle[vertical ? "offsetHeight" : "offsetWidth"]) / 100) * percent;
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
function pixelsToPercent(pixels) {
|
|
996
|
+
return pixels / ((outerWrapper[vertical ? "offsetHeight" : "offsetWidth"] - outerWrapper[handle ? "offsetHeight" : "offsetWidth"]) / 100);
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
// Sets the form element with a valid value
|
|
1000
|
+
function setInputValue(val) {
|
|
1001
|
+
// The update callback doesn't mean the input value has changed
|
|
1002
|
+
callback("update");
|
|
1003
|
+
|
|
1004
|
+
// If the user has not set this value or has entered an incorrect value then set a class
|
|
1005
|
+
// to enable styling of the slider
|
|
1006
|
+
if(!userSet) {
|
|
1007
|
+
addClass(outerWrapper, "fd-slider-no-value");
|
|
1008
|
+
} else {
|
|
1009
|
+
removeClass(outerWrapper, "fd-slider-no-value");
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
if(tagName == "select") {
|
|
1013
|
+
try {
|
|
1014
|
+
val = parseInt(val, 10);
|
|
1015
|
+
if(inp.selectedIndex === val) {
|
|
1016
|
+
updateAriaValues();
|
|
1017
|
+
return;
|
|
1018
|
+
};
|
|
1019
|
+
inp.options[val].selected = true;
|
|
1020
|
+
} catch (err) {};
|
|
1021
|
+
} else {
|
|
1022
|
+
if(val != "") {
|
|
1023
|
+
val = (min + (Math.round((val - min) / step) * step)).toFixed(precision);
|
|
1024
|
+
};
|
|
1025
|
+
if(inp.value === val) {
|
|
1026
|
+
updateAriaValues();
|
|
1027
|
+
return;
|
|
1028
|
+
};
|
|
1029
|
+
inp.value = val;
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
updateAriaValues();
|
|
1033
|
+
callback("change");
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
function checkInputValue(value) {
|
|
1037
|
+
return !(isNaN(value) || value === "" || value < Math.min(rMin,rMax) || value > Math.max(rMin,rMax));
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
function setSliderRange(newMin, newMax) {
|
|
1041
|
+
if(rMin > rMax) {
|
|
1042
|
+
newMin = Math.min(min, Math.max(newMin, newMax));
|
|
1043
|
+
newMax = Math.max(max, Math.min(newMin, newMax));
|
|
1044
|
+
rMin = Math.max(newMin, newMax);
|
|
1045
|
+
rMax = Math.min(newMin, newMax);
|
|
1046
|
+
} else {
|
|
1047
|
+
newMin = Math.max(min, Math.min(newMin, newMax));
|
|
1048
|
+
newMax = Math.min(max, Math.max(newMin, newMax));
|
|
1049
|
+
rMin = Math.min(newMin, newMax);
|
|
1050
|
+
rMax = Math.max(newMin, newMax);
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
if(defaultVal < Math.min(rMin, rMax)) {
|
|
1054
|
+
defaultVal = Math.min(rMin, rMax);
|
|
1055
|
+
} else if(defaultVal > Math.max(rMin, rMax)) {
|
|
1056
|
+
defaultVal = Math.max(rMin, rMax);
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
handle.setAttribute("aria-valuemin", rMin);
|
|
1060
|
+
handle.setAttribute("aria-valuemax", rMax);
|
|
1061
|
+
|
|
1062
|
+
checkValue(tagName == "input" ? parseFloat(inp.value) : inp.selectedIndex);
|
|
1063
|
+
redraw();
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
function redrawRange() {
|
|
1067
|
+
if(noRangeBar) {
|
|
1068
|
+
return;
|
|
1069
|
+
};
|
|
1070
|
+
if(vertical) {
|
|
1071
|
+
rangeBar.style["height"] = (bar.offsetHeight - handle.offsetTop) + "px";
|
|
1072
|
+
} else {
|
|
1073
|
+
rangeBar.style["width"] = handle.offsetLeft + "px";
|
|
1074
|
+
};
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
function findLabel() {
|
|
1078
|
+
var label = false,
|
|
1079
|
+
labelList = document.getElementsByTagName('label');
|
|
1080
|
+
// loop through label array attempting to match each 'for' attribute to the id of the current element
|
|
1081
|
+
for(var i = 0, lbl; lbl = labelList[i]; i++) {
|
|
1082
|
+
// Internet Explorer requires the htmlFor test
|
|
1083
|
+
if((lbl['htmlFor'] && lbl['htmlFor'] == inp.id) || (lbl.getAttribute('for') == inp.id)) {
|
|
1084
|
+
label = lbl;
|
|
1085
|
+
break;
|
|
1086
|
+
};
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
if(label && !label.id) {
|
|
1090
|
+
label.id = inp.id + "_label";
|
|
1091
|
+
};
|
|
1092
|
+
|
|
1093
|
+
return label;
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
function updateAriaValues() {
|
|
1097
|
+
handle.setAttribute("aria-valuenow", tagName == "select" ? inp.options[inp.selectedIndex].value : inp.value);
|
|
1098
|
+
handle.setAttribute("aria-valuetext", tagName == "select" ? (inp.options[inp.selectedIndex].text ? inp.options[inp.selectedIndex].text : inp.options[inp.selectedIndex].value) : inp.value);
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
function onInputChange(e) {
|
|
1102
|
+
userSet = true;
|
|
1103
|
+
valueToPixels(tagName == "input" ? parseFloat(inp.value) : inp.selectedIndex);
|
|
1104
|
+
updateAriaValues();
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
function onReset(e) {
|
|
1108
|
+
if(tagName == "input") {
|
|
1109
|
+
inp.value = inp.defaultValue;
|
|
1110
|
+
} else {
|
|
1111
|
+
inp.selectedIndex = resetDef;
|
|
1112
|
+
};
|
|
1113
|
+
checkValue(tagName == "select" ? inp.options[inp.selectedIndex].value : inp.value);
|
|
1114
|
+
redraw();
|
|
1115
|
+
updateAriaValues();
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
function valueSet(tf) {
|
|
1119
|
+
userSet = !!tf;
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
// Sets a tabindex attribute on an element, bends over for IE.
|
|
1123
|
+
function setTabIndex(e, i) {
|
|
1124
|
+
e.setAttribute(!/*@cc_on!@*/false ? "tabIndex" : "tabindex", i);
|
|
1125
|
+
e.tabIndex = i;
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
(function() {
|
|
1129
|
+
if(html5Shim || hideInput) {
|
|
1130
|
+
addClass(inp, "fd-form-element-hidden");
|
|
1131
|
+
} else {
|
|
1132
|
+
addEvent(inp, 'change', onInputChange);
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
// Add stepUp & stepDown methods to input element if using the html5Shim
|
|
1136
|
+
if(html5Shim) {
|
|
1137
|
+
inp.stepUp = function(n) { increment(n||1); };
|
|
1138
|
+
inp.stepDown = function(n) { increment(n||-1); };
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
outerWrapper = document.createElement('span');
|
|
1142
|
+
outerWrapper.className = "fd-slider" + (vertical ? "-vertical " : " ") + (!html5Shim ? " fd-slider-no-value " : "") + classNames;
|
|
1143
|
+
outerWrapper.id = "fd-slider-" + inp.id;
|
|
1144
|
+
|
|
1145
|
+
if(vertical && inpHeight) {
|
|
1146
|
+
outerWrapper.style.height = inpHeight + "px";
|
|
1147
|
+
};
|
|
1148
|
+
|
|
1149
|
+
wrapper = document.createElement('span');
|
|
1150
|
+
wrapper.className = "fd-slider-inner";
|
|
1151
|
+
|
|
1152
|
+
bar = document.createElement('span');
|
|
1153
|
+
bar.className = "fd-slider-bar";
|
|
1154
|
+
|
|
1155
|
+
if(!noRangeBar) {
|
|
1156
|
+
rangeBar = document.createElement('span');
|
|
1157
|
+
rangeBar.className = "fd-slider-range";
|
|
1158
|
+
};
|
|
1159
|
+
|
|
1160
|
+
if(fullARIA) {
|
|
1161
|
+
handle = document.createElement('span');
|
|
1162
|
+
} else {
|
|
1163
|
+
handle = document.createElement('a');
|
|
1164
|
+
handle.setAttribute("href", "#");
|
|
1165
|
+
addEvent(handle, "click", stopEvent);
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
setTabIndex(handle, 0);
|
|
1169
|
+
|
|
1170
|
+
handle.className = "fd-slider-handle";
|
|
1171
|
+
handle.appendChild(document.createTextNode(String.fromCharCode(160)));
|
|
1172
|
+
|
|
1173
|
+
outerWrapper.appendChild(wrapper);
|
|
1174
|
+
if(!noRangeBar) {
|
|
1175
|
+
outerWrapper.appendChild(rangeBar);
|
|
1176
|
+
};
|
|
1177
|
+
outerWrapper.appendChild(bar);
|
|
1178
|
+
outerWrapper.appendChild(handle);
|
|
1179
|
+
|
|
1180
|
+
inp.parentNode.insertBefore(outerWrapper, inp);
|
|
1181
|
+
|
|
1182
|
+
/*@cc_on@*/
|
|
1183
|
+
/*@if(@_win32)
|
|
1184
|
+
handle.unselectable = "on";
|
|
1185
|
+
if(!noRangeBar) rangeBar.unselectable = "on";
|
|
1186
|
+
bar.unselectable = "on";
|
|
1187
|
+
wrapper.unselectable = "on";
|
|
1188
|
+
outerWrapper.unselectable = "on";
|
|
1189
|
+
/*@end@*/
|
|
1190
|
+
|
|
1191
|
+
// Add ARIA accessibility info programmatically
|
|
1192
|
+
outerWrapper.setAttribute("role", "application");
|
|
1193
|
+
|
|
1194
|
+
handle.setAttribute("role", "slider");
|
|
1195
|
+
handle.setAttribute("aria-valuemin", tagName == "select" ? inp.options[0].value : min);
|
|
1196
|
+
handle.setAttribute("aria-valuemax", tagName == "select" ? inp.options[inp.options.length - 1].value : max);
|
|
1197
|
+
|
|
1198
|
+
var lbl = findLabel();
|
|
1199
|
+
if(lbl) {
|
|
1200
|
+
handle.setAttribute("aria-labelledby", lbl.id);
|
|
1201
|
+
handle.id = "fd-slider-handle-" + inp.id;
|
|
1202
|
+
/*@cc_on
|
|
1203
|
+
/*@if(@_win32)
|
|
1204
|
+
lbl.setAttribute("htmlFor", handle.id);
|
|
1205
|
+
@else @*/
|
|
1206
|
+
lbl.setAttribute("for", handle.id);
|
|
1207
|
+
/*@end
|
|
1208
|
+
@*/
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// Are there page instructions
|
|
1212
|
+
if(document.getElementById(describedBy)) {
|
|
1213
|
+
handle.setAttribute("aria-describedby", describedBy);
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
// Is the form element initially disabled
|
|
1217
|
+
if(inp.getAttribute("disabled") == true) {
|
|
1218
|
+
disableSlider(true);
|
|
1219
|
+
} else {
|
|
1220
|
+
enableSlider(true);
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
// Does an initial form element value mean the user has set a valid value?
|
|
1224
|
+
// Also called onload in case browsers have automatically set the input value
|
|
1225
|
+
if(varSetRules.onvalue) {
|
|
1226
|
+
userSet = true;
|
|
1227
|
+
checkValue(tagName == "input" ? parseFloat(inp.value) : inp.selectedIndex);
|
|
1228
|
+
};
|
|
1229
|
+
|
|
1230
|
+
if(inp.form) {
|
|
1231
|
+
addEvent(inp.form, "reset", onReset);
|
|
1232
|
+
};
|
|
1233
|
+
|
|
1234
|
+
updateAriaValues();
|
|
1235
|
+
callback("create");
|
|
1236
|
+
redraw();
|
|
1237
|
+
})();
|
|
1238
|
+
|
|
1239
|
+
return {
|
|
1240
|
+
onResize: function(e) { if(outerWrapper.offsetHeight != sliderH || outerWrapper.offsetWidth != sliderW) { redraw(); }; },
|
|
1241
|
+
destroy: function() { destroySlider(); },
|
|
1242
|
+
reset: function() { valueToPixels(tagName == "input" ? parseFloat(inp.value) : inp.selectedIndex); },
|
|
1243
|
+
stepUp: function(n) { increment(Math.abs(n)||1); },
|
|
1244
|
+
stepDown: function(n) { increment(-Math.abs(n)||-1); },
|
|
1245
|
+
increment: function(n) { increment(n); },
|
|
1246
|
+
disable: function() { disableSlider(); },
|
|
1247
|
+
enable: function() { enableSlider(); },
|
|
1248
|
+
setRange: function(mi, mx) { setSliderRange(mi, mx); },
|
|
1249
|
+
getValueSet: function() { return !!userSet; },
|
|
1250
|
+
setValueSet: function(tf) { valueSet(tf); },
|
|
1251
|
+
checkValue: function() { if(varSetRules.onvalue) { userSet = true; checkValue(tagName == "input" ? parseFloat(inp.value) : inp.selectedIndex); }; updateAriaValues(); redraw(); }
|
|
1252
|
+
};
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
addEvent(window, "load", init);
|
|
1256
|
+
addEvent(window, "load", function() { setTimeout(function() { var slider; for(slider in sliders) { sliders[slider].checkValue(); } }, 0); });
|
|
1257
|
+
addEvent(window, "resize", resize);
|
|
1258
|
+
addEvent(window, "unload", unload);
|
|
1259
|
+
|
|
1260
|
+
// Have we been passed JSON within the including script tag
|
|
1261
|
+
(function() {
|
|
1262
|
+
var scriptFiles = document.getElementsByTagName('script'),
|
|
1263
|
+
scriptInner = String(scriptFiles[scriptFiles.length - 1].innerHTML).replace(/[\n\r\s\t]+/g, " ").replace(/^\s+/, "").replace(/\s+$/, ""),
|
|
1264
|
+
json = parseJSON(scriptInner);
|
|
1265
|
+
|
|
1266
|
+
if(typeof json === "object" && !("err" in json)) {
|
|
1267
|
+
affectJSON(json);
|
|
1268
|
+
};
|
|
1269
|
+
})();
|
|
1270
|
+
|
|
1271
|
+
// Add oldie class if needed for IE < 9
|
|
1272
|
+
/*@cc_on
|
|
1273
|
+
@if (@_jscript_version < 9)
|
|
1274
|
+
addClass(document.documentElement, "oldie");
|
|
1275
|
+
@end
|
|
1276
|
+
@*/
|
|
1277
|
+
|
|
1278
|
+
return {
|
|
1279
|
+
createSlider: function(opts) { return createSlider(opts); },
|
|
1280
|
+
onDomReady: function() { onDomReady(); },
|
|
1281
|
+
destroyAll: function() { destroyAllsliders(); },
|
|
1282
|
+
destroySlider: function(id) { return destroySingleSlider(id); },
|
|
1283
|
+
redrawAll: function() { resize(); },
|
|
1284
|
+
addEvent: addEvent,
|
|
1285
|
+
removeEvent: removeEvent,
|
|
1286
|
+
stopEvent: stopEvent,
|
|
1287
|
+
increment: function(id, numSteps) { if(!sliderExists(id)) { return false; }; sliders[id].increment(numSteps); },
|
|
1288
|
+
stepUp: function(id, n) { if(!sliderExists(id)) { return false; }; sliders[id].stepUp(Math.abs(n)||1); },
|
|
1289
|
+
stepDown: function(id, n) { if(!sliderExists(id)) { return false; }; sliders[id].stepDown(-Math.abs(n)||-1); },
|
|
1290
|
+
setRange: function(id, newMin, newMax) { if(!sliderExists(id)) { return false; }; sliders[id].setRange(newMin, newMax); },
|
|
1291
|
+
updateSlider: function(id) { if(!sliderExists(id)) { return false; }; sliders[id].reset(); },
|
|
1292
|
+
disable: function(id) { if(!sliderExists(id)) { return false; }; sliders[id].disable(); },
|
|
1293
|
+
enable: function(id) { if(!sliderExists(id)) { return false; }; sliders[id].enable(); },
|
|
1294
|
+
getValueSet: function() { return getValueSet(); },
|
|
1295
|
+
setValueSet: function(a, tf) { if(!sliderExists(id)) { return false; }; setValueSet(a, tf); },
|
|
1296
|
+
setGlobalVariables: function(json) { affectJSON(json); },
|
|
1297
|
+
removeOnload: function() { removeOnLoadEvent(); }
|
|
1298
|
+
};
|
|
1299
|
+
})();
|