jquery-timeentry-rails 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +4 -0
  3. data/Rakefile +2 -0
  4. data/jquery-timeentry-rails.gemspec +19 -0
  5. data/lib/jquery-timeentry-rails.rb +10 -0
  6. data/lib/jquery-timeentry-rails/version.rb +7 -0
  7. data/vendor/assets/images/spinnerBlue.png +0 -0
  8. data/vendor/assets/images/spinnerBlueBig.png +0 -0
  9. data/vendor/assets/images/spinnerDefault.png +0 -0
  10. data/vendor/assets/images/spinnerDefaultBig.png +0 -0
  11. data/vendor/assets/images/spinnerGem.png +0 -0
  12. data/vendor/assets/images/spinnerGemBig.png +0 -0
  13. data/vendor/assets/images/spinnerGreen.png +0 -0
  14. data/vendor/assets/images/spinnerGreenBig.png +0 -0
  15. data/vendor/assets/images/spinnerOrange.png +0 -0
  16. data/vendor/assets/images/spinnerOrangeBig.png +0 -0
  17. data/vendor/assets/images/spinnerSquare.png +0 -0
  18. data/vendor/assets/images/spinnerSquareBig.png +0 -0
  19. data/vendor/assets/images/spinnerText.png +0 -0
  20. data/vendor/assets/images/spinnerTextBig.png +0 -0
  21. data/vendor/assets/images/spinnerUpDown.png +0 -0
  22. data/vendor/assets/images/spinnerUpDownBig.png +0 -0
  23. data/vendor/assets/javascripts/jquery.timeentry-ar.js +9 -0
  24. data/vendor/assets/javascripts/jquery.timeentry-ca.js +9 -0
  25. data/vendor/assets/javascripts/jquery.timeentry-cs.js +9 -0
  26. data/vendor/assets/javascripts/jquery.timeentry-de.js +9 -0
  27. data/vendor/assets/javascripts/jquery.timeentry-es.js +9 -0
  28. data/vendor/assets/javascripts/jquery.timeentry-fa.js +9 -0
  29. data/vendor/assets/javascripts/jquery.timeentry-fr.js +9 -0
  30. data/vendor/assets/javascripts/jquery.timeentry-hu.js +9 -0
  31. data/vendor/assets/javascripts/jquery.timeentry-is.js +9 -0
  32. data/vendor/assets/javascripts/jquery.timeentry-it.js +9 -0
  33. data/vendor/assets/javascripts/jquery.timeentry-ja.js +9 -0
  34. data/vendor/assets/javascripts/jquery.timeentry-lt.js +9 -0
  35. data/vendor/assets/javascripts/jquery.timeentry-lv.js +9 -0
  36. data/vendor/assets/javascripts/jquery.timeentry-nl.js +9 -0
  37. data/vendor/assets/javascripts/jquery.timeentry-pl.js +9 -0
  38. data/vendor/assets/javascripts/jquery.timeentry-pt.js +9 -0
  39. data/vendor/assets/javascripts/jquery.timeentry-ro.js +9 -0
  40. data/vendor/assets/javascripts/jquery.timeentry-ru.js +9 -0
  41. data/vendor/assets/javascripts/jquery.timeentry-sk.js +9 -0
  42. data/vendor/assets/javascripts/jquery.timeentry-sv.js +9 -0
  43. data/vendor/assets/javascripts/jquery.timeentry-tr.js +9 -0
  44. data/vendor/assets/javascripts/jquery.timeentry-vi.js +10 -0
  45. data/vendor/assets/javascripts/jquery.timeentry-zh-CN.js +9 -0
  46. data/vendor/assets/javascripts/jquery.timeentry-zh-TW.js +9 -0
  47. data/vendor/assets/javascripts/jquery.timeentry.js.erb +931 -0
  48. data/vendor/assets/stylesheets/jquery.timeentry.css +5 -0
  49. metadata +106 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 267d86f1272e280a7dc37d0c643292dcac18d2e8
4
+ data.tar.gz: 22fcbc127fa33fbc342575df377e5386898e3edb
5
+ SHA512:
6
+ metadata.gz: 28e605acb6dc8d46eb027604b87a48fd469932886520f6b7ab896b23ea59733fbd5105892eb7def2d8a754191b25976f53a9a13ad00b9fc77592ab7aab208967
7
+ data.tar.gz: eebb9acf574a4febcafd7d6092abd9f9bed91c54918788231e90766ac3b701a972ebb384ab3500c390bed7793c3f9d26f428f4204e336a3d0cb546b8a2144006
@@ -0,0 +1,4 @@
1
+ jquery-timeentry-rails
2
+ ======================
3
+
4
+ A jQuery plugin that sets an input field up to pick a time value using a spinner.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/jquery-timeentry-rails/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Frédéric Rodrigo"]
6
+ gem.email = ["fred.rodrigo@gmail.com"]
7
+ gem.description = %q{A jQuery plugin that sets an input field up to pick a time value using a spinner.}
8
+ gem.summary = %q{A jQuery plugin that sets an input field up to pick a time value using a spinner.}
9
+ gem.homepage = "http://keith-wood.name/timeEntry.html"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "jquery-timeentry-rails"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Jquery::Timeentry::Rails::VERSION
17
+
18
+ gem.add_dependency 'railties', '>= 3.1.0'
19
+ end
@@ -0,0 +1,10 @@
1
+ require "jquery-timeentry-rails/version"
2
+
3
+ module Jquery
4
+ module Timeentry
5
+ module Rails
6
+ class Engine < ::Rails::Engine
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Jquery
2
+ module Timeentry
3
+ module Rails
4
+ VERSION = "1.5.2"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Arabic initialize for the jQuery time entry extension
3
+ Written by Mohammad Baydoun (mab@modbay.me). */
4
+ (function($) {
5
+ $.timeEntry.regional['ar'] = {show24Hours: false, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['ص', 'م'],
7
+ spinnerTexts: ['الأن', 'الحقل السابق', 'الحقل التالي', 'زيادة', 'تنقيص']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['ar']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Catalan initialisation for the jQuery time entry extension
3
+ Written by Gabriel Guzman (gabriel@josoft.com.ar). */
4
+ (function($) {
5
+ $.timeEntry.regional['ca'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Ara', 'Camp anterior', 'Següent camp', 'Augmentar', 'Disminuir']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['ca']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Czech initialisation for the jQuery time entry extension
3
+ Written by Stanislav Kurinec (stenly.kurinec@gmail.com) */
4
+ (function($) {
5
+ $.timeEntry.regional['cs'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['Dop', 'Odp'],
7
+ spinnerTexts: ['Nyní', 'Předchozí pole', 'Následující pole', 'Zvýšit', 'Snížit']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['cs']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ German initialisation for the jQuery time entry extension
3
+ Written by Eyk Schulz (eyk.schulz@gmx.net) */
4
+ (function($) {
5
+ $.timeEntry.regional['de'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Jetzt', 'vorheriges Feld', 'nächstes Feld', 'hoch', 'runter']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['de']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Spanish initialisation for the jQuery time entry extension
3
+ Written by diegok (diego@freekeylabs.com). */
4
+ (function($) {
5
+ $.timeEntry.regional['es'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Ahora', 'Campo anterior', 'Siguiente campo', 'Aumentar', 'Disminuir']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['es']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Persian initialisation for the jQuery time entry extension
3
+ translation by benyblack */
4
+ (function($) {
5
+ $.timeEntry.regional['fa'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['ق.ظ', 'ب.ظ'],
7
+ spinnerTexts: ['اکنون', 'قبلی', 'بعدی', 'افزایش', 'کاهش']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['fa']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ French initialisation for the jQuery time entry extension
3
+ Written by Keith Wood (kbwood@iprimus.com.au) June 2007. */
4
+ (function($) {
5
+ $.timeEntry.regional['fr'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Maintenant', 'Précédent', 'Suivant', 'Augmenter', 'Diminuer']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['fr']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Hungarian initialisation for the jQuery time entry extension
3
+ Written by Karaszi Istvan (raszi@spam.raszi.hu) */
4
+ (function($) {
5
+ $.timeEntry.regional['hu'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['DE', 'DU'],
7
+ spinnerTexts: ['Most', 'Előző mező', 'Következő mező', 'Növel', 'Csökkent']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['hu']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Icelandic initialisation for the jQuery time entry extension
3
+ Written by Már Örlygsson (http://mar.anomy.net/) */
4
+ (function($) {
5
+ $.timeEntry.regional['is'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['fh', 'eh'],
7
+ spinnerTexts: ['Núna', 'Fyrra svæði', 'Næsta svæði', 'Hækka', 'Lækka']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['is']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Italian initialisation for the jQuery time entry extension
3
+ Written by Apaella (apaella@gmail.com) June 2007. */
4
+ (function($) {
5
+ $.timeEntry.regional['it'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Adesso', 'Precedente', 'Successivo', 'Aumenta', 'Diminuisci']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['it']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Japanese initialisation for the jQuery time entry extension
3
+ Written by Yuuki Takahashi (yuuki&#64fb69.jp) */
4
+ (function($) {
5
+ $.timeEntry.regional['ja'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['現在時刻', '前へ', '次へ', '増やす', '減らす']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['ja']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Lithuanian initialisation for the jQuery time entry extension
3
+ Written by Andrej Andrejev */
4
+ (function($) {
5
+ $.timeEntry.regional['lt'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Dabar', 'Ankstesnis laukas', 'Kitas laukas', 'Daugiau', 'Mažiau']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['lt']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Latvian (UTF-8) initialisation for the jQuery $.timeEntry extension.
3
+ Written by Rihards Prieditis (rprieditis@gmail.com). */
4
+ (function($) {
5
+ $.timeEntry.regional['lv'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Pašlaik', 'Iepriekšējais lauks', 'Nākamais lauks', 'Palielināt', 'Samazināt']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['lv']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Dutch initialisation written for the jQuery time entry extension.
3
+ Glenn plas (glenn.plas@telenet.be) March 2008. */
4
+ (function($) {
5
+ $.timeEntry.regional['nl'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Nu', 'Vorig veld', 'Volgend veld','Verhoog', 'Verlaag']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['nl']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Polish initialisation for the jQuery time entry extension.
3
+ Polish translation by Jacek Wysocki (jacek.wysocki@gmail.com). */
4
+ (function($) {
5
+ $.timeEntry.regional['pl'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Teraz', 'Poprzednie pole', 'Następne pole', 'Zwiększ wartość', 'Zmniejsz wartość']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['pl']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Portuguese initialisation for the jQuery time entry extension
3
+ Written by Dino Sane (dino@asttra.com.br). */
4
+ (function($) {
5
+ $.timeEntry.regional['pt'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Agora', 'Campo anterior', 'Campo Seguinte', 'Aumentar', 'Diminuir']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['pt']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Romanian initialisation for the jQuery time entry extension
3
+ Written by Edmond L. (ll_edmond@walla.com) */
4
+ (function($) {
5
+ $.timeEntry.regional['ro'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Acum', 'Campul Anterior', 'Campul Urmator', 'Mareste', 'Micsoreaza']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['ro']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Russian (UTF-8) initialisation for the jQuery $.timeEntry extension.
3
+ Written by Andrew Stromnov (stromnov@gmail.com). */
4
+ (function($) {
5
+ $.timeEntry.regional['ru'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Сейчас', 'Предыдущее поле', 'Следующее поле', 'Больше', 'Меньше']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['ru']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Slovak initialisation for the jQuery time entry extension
3
+ Written by Vojtech Rinik (vojto@hmm.sk) */
4
+ (function($) {
5
+ $.timeEntry.regional['sk'] = {show24Hours: false, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Teraz', 'Predchádzajúce pole', 'Nasledujúce pole', 'Zvýšiť', 'Znížiť']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['sk']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Swedish initialisation for the jQuery time entry extension.
3
+ Written by Anders Ekdahl ( anders@nomadiz.se). */
4
+ (function($) {
5
+ $.timeEntry.regional['sv'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Nu', 'Förra fältet', 'Nästa fält', 'öka', 'minska']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['sv']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Turkish initialisation for the jQuery time entry extension
3
+ Written by Vural Dinçer */
4
+ (function($) {
5
+ $.timeEntry.regional['tr'] = {show24Hours: true, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['şu an', 'önceki alan', 'sonraki alan', 'arttır', 'azalt']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['tr']);
9
+ })(jQuery);
@@ -0,0 +1,10 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Vietnamese template for the jQuery time entry extension
3
+ Written by Le Thanh Huy (lthanhhuy@cit.ctu.edu.vn). */
4
+ (function($) {
5
+ $.timeEntry.regional['vi'] = {show24Hours: false, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['Hiện tại', 'Mục trước', 'Mục sau', 'Tăng', 'Giảm']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['vi']);
9
+ })(jQuery);
10
+
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Simplified Chinese initialisation for the jQuery time entry extension.
3
+ By Cloudream(cloudream@gmail.com) */
4
+ (function($) {
5
+ $.timeEntry.regional['zh-CN'] = {show24Hours: false, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['当前', '左移', '右移', '加一', '减一']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['zh-CN']);
9
+ })(jQuery);
@@ -0,0 +1,9 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Traditional Chinese initialisation for the jQuery time entry extension.
3
+ By Taian Su(taiansu@gmail.com) */
4
+ (function($) {
5
+ $.timeEntry.regional['zh-TW'] = {show24Hours: false, separator: ':',
6
+ ampmPrefix: '', ampmNames: ['AM', 'PM'],
7
+ spinnerTexts: ['現在時刻', '上一個欄位', '下一個欄位', '增加', '减少']};
8
+ $.timeEntry.setDefaults($.timeEntry.regional['zh-TW']);
9
+ })(jQuery);
@@ -0,0 +1,931 @@
1
+ /* http://keith-wood.name/timeEntry.html
2
+ Time entry for jQuery v1.5.2.
3
+ Written by Keith Wood (kbwood{at}iinet.com.au) June 2007.
4
+ Available under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license.
5
+ Please attribute the author if you use it. */
6
+
7
+ /* Turn an input field into an entry point for a time value.
8
+ The time can be entered via directly typing the value,
9
+ via the arrow keys, or via spinner buttons.
10
+ It is configurable to show 12 or 24-hour time, to show or hide seconds,
11
+ to enforce a minimum and/or maximum time, to change the spinner image,
12
+ and to constrain the time to steps, e.g. only on the quarter hours.
13
+ Attach it with $('input selector').timeEntry(); for default settings,
14
+ or configure it with options like:
15
+ $('input selector').timeEntry(
16
+ {spinnerImage: 'spinnerSquare.png', spinnerSize: [20, 20, 0]}); */
17
+
18
+ (function($) { // Hide scope, no $ conflict
19
+
20
+ /* TimeEntry manager.
21
+ Use the singleton instance of this class, $.timeEntry, to interact with the time entry
22
+ functionality. Settings for (groups of) fields are maintained in an instance object,
23
+ allowing multiple different settings on the same page. */
24
+ function TimeEntry() {
25
+ this._disabledInputs = []; // List of time entry inputs that have been disabled
26
+ this.regional = []; // Available regional settings, indexed by language code
27
+ this.regional[''] = { // Default regional settings
28
+ show24Hours: false, // True to use 24 hour time, false for 12 hour (AM/PM)
29
+ separator: ':', // The separator between time fields
30
+ ampmPrefix: '', // The separator before the AM/PM text
31
+ ampmNames: ['AM', 'PM'], // Names of morning/evening markers
32
+ spinnerTexts: ['Now', 'Previous field', 'Next field', 'Increment', 'Decrement']
33
+ // The popup texts for the spinner image areas
34
+ };
35
+ this._defaults = {
36
+ appendText: '', // Display text following the input box, e.g. showing the format
37
+ showSeconds: false, // True to show seconds as well, false for hours/minutes only
38
+ timeSteps: [1, 1, 1], // Steps for each of hours/minutes/seconds when incrementing/decrementing
39
+ initialField: 0, // The field to highlight initially, 0 = hours, 1 = minutes, ...
40
+ noSeparatorEntry: false, // True to move to next sub-field after two digits entry
41
+ useMouseWheel: true, // True to use mouse wheel for increment/decrement if possible,
42
+ // false to never use it
43
+ defaultTime: null, // The time to use if none has been set, leave at null for now
44
+ minTime: null, // The earliest selectable time, or null for no limit
45
+ maxTime: null, // The latest selectable time, or null for no limit
46
+ spinnerImage: '<%= asset_path 'spinnerDefault.png' %>', // The URL of the images to use for the time spinner
47
+ // Seven images packed horizontally for normal, each button pressed, and disabled
48
+ spinnerSize: [20, 20, 8], // The width and height of the spinner image,
49
+ // and size of centre button for current time
50
+ spinnerBigImage: '', // The URL of the images to use for the expanded time spinner
51
+ // Seven images packed horizontally for normal, each button pressed, and disabled
52
+ spinnerBigSize: [40, 40, 16], // The width and height of the expanded spinner image,
53
+ // and size of centre button for current time
54
+ spinnerIncDecOnly: false, // True for increment/decrement buttons only, false for all
55
+ spinnerRepeat: [500, 250], // Initial and subsequent waits in milliseconds
56
+ // for repeats on the spinner buttons
57
+ beforeShow: null, // Function that takes an input field and
58
+ // returns a set of custom settings for the time entry
59
+ beforeSetTime: null // Function that runs before updating the time,
60
+ // takes the old and new times, and minimum and maximum times as parameters,
61
+ // and returns an adjusted time if necessary
62
+ };
63
+ $.extend(this._defaults, this.regional['']);
64
+ }
65
+
66
+ $.extend(TimeEntry.prototype, {
67
+ /* Class name added to elements to indicate already configured with time entry. */
68
+ markerClassName: 'hasTimeEntry',
69
+ /* Name of the data property for instance settings. */
70
+ propertyName: 'timeEntry',
71
+
72
+ /* Class name for the appended content. */
73
+ _appendClass: 'timeEntry_append',
74
+ /* Class name for the time entry control. */
75
+ _controlClass: 'timeEntry_control',
76
+ /* Class name for the expanded spinner. */
77
+ _expandClass: 'timeEntry_expand',
78
+
79
+ /* Override the default settings for all instances of the time entry.
80
+ @param options (object) the new settings to use as defaults (anonymous object)
81
+ @return (DateEntry) this object */
82
+ setDefaults: function(options) {
83
+ $.extend(this._defaults, options || {});
84
+ return this;
85
+ },
86
+
87
+ /* Attach the time entry handler to an input field.
88
+ @param target (element) the field to attach to
89
+ @param options (object) custom settings for this instance */
90
+ _attachPlugin: function(target, options) {
91
+ var input = $(target);
92
+ if (input.hasClass(this.markerClassName)) {
93
+ return;
94
+ }
95
+ var inst = {options: $.extend({}, this._defaults, options), input: input, _field: 0,
96
+ _selectedHour: 0, _selectedMinute: 0, _selectedSecond: 0};
97
+ input.data(this.propertyName, inst).addClass(this.markerClassName).
98
+ bind('focus.' + this.propertyName, this._doFocus).
99
+ bind('blur.' + this.propertyName, this._doBlur).
100
+ bind('click.' + this.propertyName, this._doClick).
101
+ bind('keydown.' + this.propertyName, this._doKeyDown).
102
+ bind('keypress.' + this.propertyName, this._doKeyPress).
103
+ bind('paste.' + this.propertyName, function(event) { // Check pastes
104
+ setTimeout(function() { plugin._parseTime(inst); }, 1);
105
+ });
106
+ this._optionPlugin(target, options);
107
+ },
108
+
109
+ /* Retrieve or reconfigure the settings for a time entry control.
110
+ @param target (element) the control to affect
111
+ @param options (object) the new options for this instance or
112
+ (string) an individual property name
113
+ @param value (any) the individual property value (omit if options
114
+ is an object or to retrieve the value of a setting)
115
+ @return (any) if retrieving a value */
116
+ _optionPlugin: function(target, options, value) {
117
+ target = $(target);
118
+ var inst = target.data(this.propertyName);
119
+ if (!options || (typeof options == 'string' && value == null)) { // Get option
120
+ var name = options;
121
+ options = (inst || {}).options;
122
+ return (options && name ? options[name] : options);
123
+ }
124
+
125
+ if (!target.hasClass(this.markerClassName)) {
126
+ return;
127
+ }
128
+ options = options || {};
129
+ if (typeof options == 'string') {
130
+ var name = options;
131
+ options = {};
132
+ options[name] = value;
133
+ }
134
+ var currentTime = this._extractTime(inst);
135
+ $.extend(inst.options, options);
136
+ inst._field = 0;
137
+ if (currentTime) {
138
+ this._setTime(inst, new Date(0, 0, 0, currentTime[0], currentTime[1], currentTime[2]));
139
+ }
140
+ // Remove stuff dependent on old settings
141
+ target.next('span.' + this._appendClass).remove();
142
+ target.parent().find('span.' + this._controlClass).remove();
143
+ if ($.fn.mousewheel) {
144
+ target.unmousewheel();
145
+ }
146
+ // And re-add if requested
147
+ var spinner = (!inst.options.spinnerImage ? null :
148
+ $('<span class="' + this._controlClass + '" style="display: inline-block; ' +
149
+ 'background: url(\'' + inst.options.spinnerImage + '\') 0 0 no-repeat; width: ' +
150
+ inst.options.spinnerSize[0] + 'px; height: ' + inst.options.spinnerSize[1] + 'px;"></span>'));
151
+ target.after(inst.options.appendText ? '<span class="' + this._appendClass + '">' +
152
+ inst.options.appendText + '</span>' : '').after(spinner || '');
153
+ // Allow mouse wheel usage
154
+ if (inst.options.useMouseWheel && $.fn.mousewheel) {
155
+ target.mousewheel(this._doMouseWheel);
156
+ }
157
+ if (spinner) {
158
+ spinner.mousedown(this._handleSpinner).mouseup(this._endSpinner).
159
+ mouseover(this._expandSpinner).mouseout(this._endSpinner).
160
+ mousemove(this._describeSpinner);
161
+ }
162
+ },
163
+
164
+ /* Enable a time entry input and any associated spinner.
165
+ @param target (element) single input field */
166
+ _enablePlugin: function(target) {
167
+ this._enableDisable(target, false);
168
+ },
169
+
170
+ /* Disable a time entry input and any associated spinner.
171
+ @param target (element) single input field */
172
+ _disablePlugin: function(target) {
173
+ this._enableDisable(target, true);
174
+ },
175
+
176
+ /* Enable or disable a time entry input and any associated spinner.
177
+ @param target (element) single input field
178
+ @param disable (boolean) true to disable, false to enable */
179
+ _enableDisable: function(target, disable) {
180
+ var inst = $.data(target, this.propertyName);
181
+ if (!inst) {
182
+ return;
183
+ }
184
+ target.disabled = disable;
185
+ if (target.nextSibling && target.nextSibling.nodeName.toLowerCase() == 'span') {
186
+ plugin._changeSpinner(inst, target.nextSibling, (disable ? 5 : -1));
187
+ }
188
+ plugin._disabledInputs = $.map(plugin._disabledInputs,
189
+ function(value) { return (value == target ? null : value); }); // Delete entry
190
+ if (disable) {
191
+ plugin._disabledInputs.push(target);
192
+ }
193
+ },
194
+
195
+ /* Check whether an input field has been disabled.
196
+ @param target (element) input field to check
197
+ @return (boolean) true if this field has been disabled, false if it is enabled */
198
+ _isDisabledPlugin: function(target) {
199
+ return $.inArray(target, this._disabledInputs) > -1;
200
+ },
201
+
202
+ /* Remove the time entry functionality from an input.
203
+ @param target (element) the control to affect */
204
+ _destroyPlugin: function(target) {
205
+ target = $(target);
206
+ if (!target.hasClass(this.markerClassName)) {
207
+ return;
208
+ }
209
+ target.removeClass(this.markerClassName).removeData(this.propertyName).
210
+ unbind('.' + this.propertyName);
211
+ if ($.fn.mousewheel) {
212
+ target.unmousewheel();
213
+ }
214
+ this._disabledInputs = $.map(this._disabledInputs,
215
+ function(value) { return (value == target[0] ? null : value); }); // Delete entry
216
+ target.siblings('.' + this._appendClass + ',.' + this._controlClass).remove();
217
+ },
218
+
219
+ /* Initialise the current time for a time entry input field.
220
+ @param target (element) input field to update
221
+ @param time (Date) the new time (year/month/day ignored) or null for now */
222
+ _setTimePlugin: function(target, time) {
223
+ var inst = $.data(target, this.propertyName);
224
+ if (inst) {
225
+ if (time === null || time === '') {
226
+ inst.input.val('');
227
+ }
228
+ else {
229
+ this._setTime(inst, time ? (typeof time == 'object' ?
230
+ new Date(time.getTime()) : time) : null);
231
+ }
232
+ }
233
+ },
234
+
235
+ /* Retrieve the current time for a time entry input field.
236
+ @param target (element) input field to examine
237
+ @return (Date) current time (year/month/day zero) or null if none */
238
+ _getTimePlugin: function(target) {
239
+ var inst = $.data(target, this.propertyName);
240
+ var currentTime = (inst ? this._extractTime(inst) : null);
241
+ return (!currentTime ? null :
242
+ new Date(0, 0, 0, currentTime[0], currentTime[1], currentTime[2]));
243
+ },
244
+
245
+ /* Retrieve the millisecond offset for the current time.
246
+ @param target (element) input field to examine
247
+ @return (number) the time as milliseconds offset or zero if none */
248
+ _getOffsetPlugin: function(target) {
249
+ var inst = $.data(target, this.propertyName);
250
+ var currentTime = (inst ? this._extractTime(inst) : null);
251
+ return (!currentTime ? 0 :
252
+ (currentTime[0] * 3600 + currentTime[1] * 60 + currentTime[2]) * 1000);
253
+ },
254
+
255
+ /* Initialise time entry.
256
+ @param target (element) the input field or
257
+ (event) the focus event */
258
+ _doFocus: function(target) {
259
+ var input = (target.nodeName && target.nodeName.toLowerCase() == 'input' ? target : this);
260
+ if (plugin._lastInput == input || plugin._isDisabledPlugin(input)) {
261
+ plugin._focussed = false;
262
+ return;
263
+ }
264
+ var inst = $.data(input, plugin.propertyName);
265
+ plugin._focussed = true;
266
+ plugin._lastInput = input;
267
+ plugin._blurredInput = null;
268
+ $.extend(inst.options, ($.isFunction(inst.options.beforeShow) ?
269
+ inst.options.beforeShow.apply(input, [input]) : {}));
270
+ plugin._parseTime(inst);
271
+ setTimeout(function() { plugin._showField(inst); }, 10);
272
+ },
273
+
274
+ /* Note that the field has been exited.
275
+ @param event (event) the blur event */
276
+ _doBlur: function(event) {
277
+ plugin._blurredInput = plugin._lastInput;
278
+ plugin._lastInput = null;
279
+ },
280
+
281
+ /* Select appropriate field portion on click, if already in the field.
282
+ @param event (event) the click event */
283
+ _doClick: function(event) {
284
+ var input = event.target;
285
+ var inst = $.data(input, plugin.propertyName);
286
+ var prevField = inst._field;
287
+ if (!plugin._focussed) {
288
+ var fieldSize = inst.options.separator.length + 2;
289
+ inst._field = 0;
290
+ if (input.selectionStart != null) { // Use input select range
291
+ for (var field = 0; field <= Math.max(1, inst._secondField, inst._ampmField); field++) {
292
+ var end = (field != inst._ampmField ? (field * fieldSize) + 2 :
293
+ (inst._ampmField * fieldSize) + inst.options.ampmPrefix.length +
294
+ inst.options.ampmNames[0].length);
295
+ inst._field = field;
296
+ if (input.selectionStart < end) {
297
+ break;
298
+ }
299
+ }
300
+ }
301
+ else if (input.createTextRange) { // Check against bounding boxes
302
+ var src = $(event.srcElement);
303
+ var range = input.createTextRange();
304
+ var convert = function(value) {
305
+ return {thin: 2, medium: 4, thick: 6}[value] || value;
306
+ };
307
+ var offsetX = event.clientX + document.documentElement.scrollLeft -
308
+ (src.offset().left + parseInt(convert(src.css('border-left-width')), 10)) -
309
+ range.offsetLeft; // Position - left edge - alignment
310
+ for (var field = 0; field <= Math.max(1, inst._secondField, inst._ampmField); field++) {
311
+ var end = (field != inst._ampmField ? (field * fieldSize) + 2 :
312
+ (inst._ampmField * fieldSize) + inst.options.ampmPrefix.length +
313
+ inst.options.ampmNames[0].length);
314
+ range.collapse();
315
+ range.moveEnd('character', end);
316
+ inst._field = field;
317
+ if (offsetX < range.boundingWidth) { // And compare
318
+ break;
319
+ }
320
+ }
321
+ }
322
+ }
323
+ if (prevField != inst._field) {
324
+ inst._lastChr = '';
325
+ }
326
+ plugin._showField(inst);
327
+ plugin._focussed = false;
328
+ },
329
+
330
+ /* Handle keystrokes in the field.
331
+ @param event (event) the keydown event
332
+ @return (boolean) true to continue, false to stop processing */
333
+ _doKeyDown: function(event) {
334
+ if (event.keyCode >= 48) { // >= '0'
335
+ return true;
336
+ }
337
+ var inst = $.data(event.target, plugin.propertyName);
338
+ switch (event.keyCode) {
339
+ case 9: return (event.shiftKey ?
340
+ // Move to previous time field, or out if at the beginning
341
+ plugin._changeField(inst, -1, true) :
342
+ // Move to next time field, or out if at the end
343
+ plugin._changeField(inst, +1, true));
344
+ case 35: if (event.ctrlKey) { // Clear time on ctrl+end
345
+ plugin._setValue(inst, '');
346
+ }
347
+ else { // Last field on end
348
+ inst._field = Math.max(1, inst._secondField, inst._ampmField);
349
+ plugin._adjustField(inst, 0);
350
+ }
351
+ break;
352
+ case 36: if (event.ctrlKey) { // Current time on ctrl+home
353
+ plugin._setTime(inst);
354
+ }
355
+ else { // First field on home
356
+ inst._field = 0;
357
+ plugin._adjustField(inst, 0);
358
+ }
359
+ break;
360
+ case 37: plugin._changeField(inst, -1, false); break; // Previous field on left
361
+ case 38: plugin._adjustField(inst, +1); break; // Increment time field on up
362
+ case 39: plugin._changeField(inst, +1, false); break; // Next field on right
363
+ case 40: plugin._adjustField(inst, -1); break; // Decrement time field on down
364
+ case 46: plugin._setValue(inst, ''); break; // Clear time on delete
365
+ default: return true;
366
+ }
367
+ return false;
368
+ },
369
+
370
+ /* Disallow unwanted characters.
371
+ @param event (event) the keypress event
372
+ @return (boolean) true to continue, false to stop processing */
373
+ _doKeyPress: function(event) {
374
+ var chr = String.fromCharCode(event.charCode == undefined ? event.keyCode : event.charCode);
375
+ if (chr < ' ') {
376
+ return true;
377
+ }
378
+ var inst = $.data(event.target, plugin.propertyName);
379
+ plugin._handleKeyPress(inst, chr);
380
+ return false;
381
+ },
382
+
383
+ /* Increment/decrement on mouse wheel activity.
384
+ @param event (event) the mouse wheel event
385
+ @param delta (number) the amount of change */
386
+ _doMouseWheel: function(event, delta) {
387
+ if (plugin._isDisabledPlugin(event.target)) {
388
+ return;
389
+ }
390
+ var inst = $.data(event.target, plugin.propertyName);
391
+ inst.input.focus();
392
+ if (!inst.input.val()) {
393
+ plugin._parseTime(inst);
394
+ }
395
+ plugin._adjustField(inst, delta);
396
+ event.preventDefault();
397
+ },
398
+
399
+ /* Expand the spinner, if possible, to make it easier to use.
400
+ @param event (event) the mouse over event */
401
+ _expandSpinner: function(event) {
402
+ var spinner = plugin._getSpinnerTarget(event);
403
+ var inst = $.data(plugin._getInput(spinner), plugin.propertyName);
404
+ if (plugin._isDisabledPlugin(inst.input[0])) {
405
+ return;
406
+ }
407
+ if (inst.options.spinnerBigImage) {
408
+ inst._expanded = true;
409
+ var offset = $(spinner).offset();
410
+ var relative = null;
411
+ $(spinner).parents().each(function() {
412
+ var parent = $(this);
413
+ if (parent.css('position') == 'relative' ||
414
+ parent.css('position') == 'absolute') {
415
+ relative = parent.offset();
416
+ }
417
+ return !relative;
418
+ });
419
+ $('<div class="' + plugin._expandClass + '" style="position: absolute; left: ' +
420
+ (offset.left - (inst.options.spinnerBigSize[0] - inst.options.spinnerSize[0]) / 2 -
421
+ (relative ? relative.left : 0)) + 'px; top: ' +
422
+ (offset.top - (inst.options.spinnerBigSize[1] - inst.options.spinnerSize[1]) / 2 -
423
+ (relative ? relative.top : 0)) + 'px; width: ' +
424
+ inst.options.spinnerBigSize[0] + 'px; height: ' +
425
+ inst.options.spinnerBigSize[1] + 'px; background: transparent url(' +
426
+ inst.options.spinnerBigImage + ') no-repeat 0px 0px; z-index: 10;"></div>').
427
+ mousedown(plugin._handleSpinner).mouseup(plugin._endSpinner).
428
+ mouseout(plugin._endExpand).mousemove(plugin._describeSpinner).
429
+ insertAfter(spinner);
430
+ }
431
+ },
432
+
433
+ /* Locate the actual input field from the spinner.
434
+ @param spinner (element) the current spinner
435
+ @return (element) the corresponding input */
436
+ _getInput: function(spinner) {
437
+ return $(spinner).siblings('.' + plugin.markerClassName)[0];
438
+ },
439
+
440
+ /* Change the title based on position within the spinner.
441
+ @param event (event) the mouse move event */
442
+ _describeSpinner: function(event) {
443
+ var spinner = plugin._getSpinnerTarget(event);
444
+ var inst = $.data(plugin._getInput(spinner), plugin.propertyName);
445
+ spinner.title = inst.options.spinnerTexts[plugin._getSpinnerRegion(inst, event)];
446
+ },
447
+
448
+ /* Handle a click on the spinner.
449
+ @param event (event) the mouse click event */
450
+ _handleSpinner: function(event) {
451
+ var spinner = plugin._getSpinnerTarget(event);
452
+ var input = plugin._getInput(spinner);
453
+ if (plugin._isDisabledPlugin(input)) {
454
+ return;
455
+ }
456
+ if (input == plugin._blurredInput) {
457
+ plugin._lastInput = input;
458
+ plugin._blurredInput = null;
459
+ }
460
+ var inst = $.data(input, plugin.propertyName);
461
+ plugin._doFocus(input);
462
+ var region = plugin._getSpinnerRegion(inst, event);
463
+ plugin._changeSpinner(inst, spinner, region);
464
+ plugin._actionSpinner(inst, region);
465
+ plugin._timer = null;
466
+ plugin._handlingSpinner = true;
467
+ if (region >= 3 && inst.options.spinnerRepeat[0]) { // Repeat increment/decrement
468
+ plugin._timer = setTimeout(
469
+ function() { plugin._repeatSpinner(inst, region); },
470
+ inst.options.spinnerRepeat[0]);
471
+ $(spinner).one('mouseout', plugin._releaseSpinner).
472
+ one('mouseup', plugin._releaseSpinner);
473
+ }
474
+ },
475
+
476
+ /* Action a click on the spinner.
477
+ @param inst (object) the instance settings
478
+ @param region (number) the spinner "button" */
479
+ _actionSpinner: function(inst, region) {
480
+ if (!inst.input.val()) {
481
+ plugin._parseTime(inst);
482
+ }
483
+ switch (region) {
484
+ case 0: this._setTime(inst); break;
485
+ case 1: this._changeField(inst, -1, false); break;
486
+ case 2: this._changeField(inst, +1, false); break;
487
+ case 3: this._adjustField(inst, +1); break;
488
+ case 4: this._adjustField(inst, -1); break;
489
+ }
490
+ },
491
+
492
+ /* Repeat a click on the spinner.
493
+ @param inst (object) the instance settings
494
+ @param region (number) the spinner "button" */
495
+ _repeatSpinner: function(inst, region) {
496
+ if (!plugin._timer) {
497
+ return;
498
+ }
499
+ plugin._lastInput = plugin._blurredInput;
500
+ this._actionSpinner(inst, region);
501
+ this._timer = setTimeout(
502
+ function() { plugin._repeatSpinner(inst, region); },
503
+ inst.options.spinnerRepeat[1]);
504
+ },
505
+
506
+ /* Stop a spinner repeat.
507
+ @param event (event) the mouse event */
508
+ _releaseSpinner: function(event) {
509
+ clearTimeout(plugin._timer);
510
+ plugin._timer = null;
511
+ },
512
+
513
+ /* Tidy up after an expanded spinner.
514
+ @param event (event) the mouse event */
515
+ _endExpand: function(event) {
516
+ plugin._timer = null;
517
+ var spinner = plugin._getSpinnerTarget(event);
518
+ var input = plugin._getInput(spinner);
519
+ var inst = $.data(input, plugin.propertyName);
520
+ $(spinner).remove();
521
+ inst._expanded = false;
522
+ },
523
+
524
+ /* Tidy up after a spinner click.
525
+ @param event (event) the mouse event */
526
+ _endSpinner: function(event) {
527
+ plugin._timer = null;
528
+ var spinner = plugin._getSpinnerTarget(event);
529
+ var input = plugin._getInput(spinner);
530
+ var inst = $.data(input, plugin.propertyName);
531
+ if (!plugin._isDisabledPlugin(input)) {
532
+ plugin._changeSpinner(inst, spinner, -1);
533
+ }
534
+ if (plugin._handlingSpinner) {
535
+ plugin._lastInput = plugin._blurredInput;
536
+ }
537
+ if (plugin._lastInput && plugin._handlingSpinner) {
538
+ plugin._showField(inst);
539
+ }
540
+ plugin._handlingSpinner = false;
541
+ },
542
+
543
+ /* Retrieve the spinner from the event.
544
+ @param event (event) the mouse click event
545
+ @return (element) the target field */
546
+ _getSpinnerTarget: function(event) {
547
+ return event.target || event.srcElement;
548
+ },
549
+
550
+ /* Determine which "button" within the spinner was clicked.
551
+ @param inst (object) the instance settings
552
+ @param event (event) the mouse event
553
+ @return (number) the spinner "button" number */
554
+ _getSpinnerRegion: function(inst, event) {
555
+ var spinner = this._getSpinnerTarget(event);
556
+ var pos = $(spinner).offset();
557
+ var scrolled = [document.documentElement.scrollLeft || document.body.scrollLeft,
558
+ document.documentElement.scrollTop || document.body.scrollTop];
559
+ var left = (inst.options.spinnerIncDecOnly ? 99 : event.clientX + scrolled[0] - pos.left);
560
+ var top = event.clientY + scrolled[1] - pos.top;
561
+ var spinnerSize = inst.options[inst._expanded ? 'spinnerBigSize' : 'spinnerSize'];
562
+ var right = (inst.options.spinnerIncDecOnly ? 99 : spinnerSize[0] - 1 - left);
563
+ var bottom = spinnerSize[1] - 1 - top;
564
+ if (spinnerSize[2] > 0 && Math.abs(left - right) <= spinnerSize[2] &&
565
+ Math.abs(top - bottom) <= spinnerSize[2]) {
566
+ return 0; // Centre button
567
+ }
568
+ var min = Math.min(left, top, right, bottom);
569
+ return (min == left ? 1 : (min == right ? 2 : (min == top ? 3 : 4))); // Nearest edge
570
+ },
571
+
572
+ /* Change the spinner image depending on button clicked.
573
+ @param inst (object) the instance settings
574
+ @param spinner (element) the spinner control
575
+ @param region (number) the spinner "button" */
576
+ _changeSpinner: function(inst, spinner, region) {
577
+ $(spinner).css('background-position', '-' + ((region + 1) *
578
+ inst.options[inst._expanded ? 'spinnerBigSize' : 'spinnerSize'][0]) + 'px 0px');
579
+ },
580
+
581
+ /* Extract the time value from the input field, or default to now.
582
+ @param inst (object) the instance settings */
583
+ _parseTime: function(inst) {
584
+ var currentTime = this._extractTime(inst);
585
+ if (currentTime) {
586
+ inst._selectedHour = currentTime[0];
587
+ inst._selectedMinute = currentTime[1];
588
+ inst._selectedSecond = currentTime[2];
589
+ }
590
+ else {
591
+ var now = this._constrainTime(inst);
592
+ inst._selectedHour = now[0];
593
+ inst._selectedMinute = now[1];
594
+ inst._selectedSecond = (inst.options.showSeconds ? now[2] : 0);
595
+ }
596
+ inst._secondField = (inst.options.showSeconds ? 2 : -1);
597
+ inst._ampmField = (inst.options.show24Hours ? -1 : (inst.options.showSeconds ? 3 : 2));
598
+ inst._lastChr = '';
599
+ inst._field = Math.max(0, Math.min(
600
+ Math.max(1, inst._secondField, inst._ampmField), inst.options.initialField));
601
+ if (inst.input.val() != '') {
602
+ this._showTime(inst);
603
+ }
604
+ },
605
+
606
+ /* Extract the time value from a string as an array of values, or default to null.
607
+ @param inst (object) the instance settings
608
+ @param value (string) the time value to parse
609
+ @return (number[3]) the time components (hours, minutes, seconds)
610
+ or null if no value */
611
+ _extractTime: function(inst, value) {
612
+ value = value || inst.input.val();
613
+ var currentTime = value.split(inst.options.separator);
614
+ if (inst.options.separator == '' && value != '') {
615
+ currentTime[0] = value.substring(0, 2);
616
+ currentTime[1] = value.substring(2, 4);
617
+ currentTime[2] = value.substring(4, 6);
618
+ }
619
+ if (currentTime.length >= 2) {
620
+ var isAM = !inst.options.show24Hours && (value.indexOf(inst.options.ampmNames[0]) > -1);
621
+ var isPM = !inst.options.show24Hours && (value.indexOf(inst.options.ampmNames[1]) > -1);
622
+ var hour = parseInt(currentTime[0], 10);
623
+ hour = (isNaN(hour) ? 0 : hour);
624
+ hour = ((isAM || isPM) && hour == 12 ? 0 : hour) + (isPM ? 12 : 0);
625
+ var minute = parseInt(currentTime[1], 10);
626
+ minute = (isNaN(minute) ? 0 : minute);
627
+ var second = (currentTime.length >= 3 ?
628
+ parseInt(currentTime[2], 10) : 0);
629
+ second = (isNaN(second) || !inst.options.showSeconds ? 0 : second);
630
+ return this._constrainTime(inst, [hour, minute, second]);
631
+ }
632
+ return null;
633
+ },
634
+
635
+ /* Constrain the given/current time to the time steps.
636
+ @param inst (object) the instance settings
637
+ @param fields (number[3]) the current time components (hours, minutes, seconds)
638
+ @return (number[3]) the constrained time components (hours, minutes, seconds) */
639
+ _constrainTime: function(inst, fields) {
640
+ var specified = (fields != null);
641
+ if (!specified) {
642
+ var now = this._determineTime(inst.options.defaultTime, inst) || new Date();
643
+ fields = [now.getHours(), now.getMinutes(), now.getSeconds()];
644
+ }
645
+ var reset = false;
646
+ for (var i = 0; i < inst.options.timeSteps.length; i++) {
647
+ if (reset) {
648
+ fields[i] = 0;
649
+ }
650
+ else if (inst.options.timeSteps[i] > 1) {
651
+ fields[i] = Math.round(fields[i] / inst.options.timeSteps[i]) *
652
+ inst.options.timeSteps[i];
653
+ reset = true;
654
+ }
655
+ }
656
+ return fields;
657
+ },
658
+
659
+ /* Set the selected time into the input field.
660
+ @param inst (object) the instance settings */
661
+ _showTime: function(inst) {
662
+ var currentTime = (this._formatNumber(inst.options.show24Hours ? inst._selectedHour :
663
+ ((inst._selectedHour + 11) % 12) + 1) + inst.options.separator +
664
+ this._formatNumber(inst._selectedMinute) +
665
+ (inst.options.showSeconds ? inst.options.separator +
666
+ this._formatNumber(inst._selectedSecond) : '') +
667
+ (inst.options.show24Hours ? '' : inst.options.ampmPrefix +
668
+ inst.options.ampmNames[(inst._selectedHour < 12 ? 0 : 1)]));
669
+ this._setValue(inst, currentTime);
670
+ this._showField(inst);
671
+ },
672
+
673
+ /* Highlight the current time field.
674
+ @param inst (object) the instance settings */
675
+ _showField: function(inst) {
676
+ var input = inst.input[0];
677
+ if (inst.input.is(':hidden') || plugin._lastInput != input) {
678
+ return;
679
+ }
680
+ var fieldSize = inst.options.separator.length + 2;
681
+ var start = (inst._field != inst._ampmField ? (inst._field * fieldSize) :
682
+ (inst._ampmField * fieldSize) - inst.options.separator.length +
683
+ inst.options.ampmPrefix.length);
684
+ var end = start + (inst._field != inst._ampmField ? 2 : inst.options.ampmNames[0].length);
685
+ if (input.setSelectionRange) { // Mozilla
686
+ input.setSelectionRange(start, end);
687
+ }
688
+ else if (input.createTextRange) { // IE
689
+ var range = input.createTextRange();
690
+ range.moveStart('character', start);
691
+ range.moveEnd('character', end - inst.input.val().length);
692
+ range.select();
693
+ }
694
+ if (!input.disabled) {
695
+ input.focus();
696
+ }
697
+ },
698
+
699
+ /* Ensure displayed single number has a leading zero.
700
+ @param value (number) current value
701
+ @return (string) number with at least two digits */
702
+ _formatNumber: function(value) {
703
+ return (value < 10 ? '0' : '') + value;
704
+ },
705
+
706
+ /* Update the input field and notify listeners.
707
+ @param inst (object) the instance settings
708
+ @param value (string) the new value */
709
+ _setValue: function(inst, value) {
710
+ if (value != inst.input.val()) {
711
+ inst.input.val(value).trigger('change');
712
+ }
713
+ },
714
+
715
+ /* Move to previous/next field, or out of field altogether if appropriate.
716
+ @param inst (object) the instance settings
717
+ @param offset (number) the direction of change (-1, +1)
718
+ @param moveOut (boolean) true if can move out of the field
719
+ @return (boolean) true if exitting the field, false if not */
720
+ _changeField: function(inst, offset, moveOut) {
721
+ var atFirstLast = (inst.input.val() == '' || inst._field ==
722
+ (offset == -1 ? 0 : Math.max(1, inst._secondField, inst._ampmField)));
723
+ if (!atFirstLast) {
724
+ inst._field += offset;
725
+ }
726
+ this._showField(inst);
727
+ inst._lastChr = '';
728
+ return (atFirstLast && moveOut);
729
+ },
730
+
731
+ /* Update the current field in the direction indicated.
732
+ @param inst (object) the instance settings
733
+ @param offset (number) the amount to change by */
734
+ _adjustField: function(inst, offset) {
735
+ if (inst.input.val() == '') {
736
+ offset = 0;
737
+ }
738
+ this._setTime(inst, new Date(0, 0, 0,
739
+ inst._selectedHour + (inst._field == 0 ? offset * inst.options.timeSteps[0] : 0) +
740
+ (inst._field == inst._ampmField ? offset * 12 : 0),
741
+ inst._selectedMinute + (inst._field == 1 ? offset * inst.options.timeSteps[1] : 0),
742
+ inst._selectedSecond +
743
+ (inst._field == inst._secondField ? offset * inst.options.timeSteps[2] : 0)));
744
+ },
745
+
746
+ /* Check against minimum/maximum and display time.
747
+ @param inst (object) the instance settings
748
+ @param time (Date) an actual time or
749
+ (number) offset in seconds from now or
750
+ (string) units and periods of offsets from now */
751
+ _setTime: function(inst, time) {
752
+ time = this._determineTime(time, inst);
753
+ var fields = this._constrainTime(inst, time ?
754
+ [time.getHours(), time.getMinutes(), time.getSeconds()] : null);
755
+ time = new Date(0, 0, 0, fields[0], fields[1], fields[2]);
756
+ // Normalise to base date
757
+ var time = this._normaliseTime(time);
758
+ var minTime = this._normaliseTime(this._determineTime(inst.options.minTime, inst));
759
+ var maxTime = this._normaliseTime(this._determineTime(inst.options.maxTime, inst));
760
+ // Ensure it is within the bounds set
761
+ if (minTime && maxTime && minTime > maxTime) {
762
+ if (time < minTime && time > maxTime) {
763
+ time = (Math.abs(time - minTime) < Math.abs(time - maxTime) ? minTime : maxTime);
764
+ }
765
+ }
766
+ else {
767
+ time = (minTime && time < minTime ? minTime :
768
+ (maxTime && time > maxTime ? maxTime : time));
769
+ }
770
+ // Perform further restrictions if required
771
+ if ($.isFunction(inst.options.beforeSetTime)) {
772
+ time = inst.options.beforeSetTime.apply(inst.input[0],
773
+ [this._getTimePlugin(inst.input[0]), time, minTime, maxTime]);
774
+ }
775
+ inst._selectedHour = time.getHours();
776
+ inst._selectedMinute = time.getMinutes();
777
+ inst._selectedSecond = time.getSeconds();
778
+ this._showTime(inst);
779
+ },
780
+
781
+ /* A time may be specified as an exact value or a relative one.
782
+ @param setting (Date) an actual time or
783
+ (number) offset in seconds from now or
784
+ (string) units and periods of offsets from now
785
+ @param inst (object) the instance settings
786
+ @return (Date) the calculated time */
787
+ _determineTime: function(setting, inst) {
788
+ var offsetNumeric = function(offset) { // E.g. +300, -2
789
+ var time = new Date();
790
+ time.setTime(time.getTime() + offset * 1000);
791
+ return time;
792
+ };
793
+ var offsetString = function(offset) { // E.g. '+2m', '-4h', '+3h +30m' or '12:34:56PM'
794
+ var fields = plugin._extractTime(inst, offset); // Actual time?
795
+ var time = new Date();
796
+ var hour = (fields ? fields[0] : time.getHours());
797
+ var minute = (fields ? fields[1] : time.getMinutes());
798
+ var second = (fields ? fields[2] : time.getSeconds());
799
+ if (!fields) {
800
+ var pattern = /([+-]?[0-9]+)\s*(s|S|m|M|h|H)?/g;
801
+ var matches = pattern.exec(offset);
802
+ while (matches) {
803
+ switch (matches[2] || 's') {
804
+ case 's' : case 'S' :
805
+ second += parseInt(matches[1], 10); break;
806
+ case 'm' : case 'M' :
807
+ minute += parseInt(matches[1], 10); break;
808
+ case 'h' : case 'H' :
809
+ hour += parseInt(matches[1], 10); break;
810
+ }
811
+ matches = pattern.exec(offset);
812
+ }
813
+ }
814
+ time = new Date(0, 0, 10, hour, minute, second, 0);
815
+ if (/^!/.test(offset)) { // No wrapping
816
+ if (time.getDate() > 10) {
817
+ time = new Date(0, 0, 10, 23, 59, 59);
818
+ }
819
+ else if (time.getDate() < 10) {
820
+ time = new Date(0, 0, 10, 0, 0, 0);
821
+ }
822
+ }
823
+ return time;
824
+ };
825
+ return (setting ? (typeof setting == 'string' ? offsetString(setting) :
826
+ (typeof setting == 'number' ? offsetNumeric(setting) : setting)) : null);
827
+ },
828
+
829
+ /* Normalise time object to a common date.
830
+ @param time (Date) the original time
831
+ @return (Date) the normalised time */
832
+ _normaliseTime: function(time) {
833
+ if (!time) {
834
+ return null;
835
+ }
836
+ time.setFullYear(1900);
837
+ time.setMonth(0);
838
+ time.setDate(0);
839
+ return time;
840
+ },
841
+
842
+ /* Update time based on keystroke entered.
843
+ @param inst (object) the instance settings
844
+ @param chr (ch) the new character */
845
+ _handleKeyPress: function(inst, chr) {
846
+ if (chr == inst.options.separator) {
847
+ this._changeField(inst, +1, false);
848
+ }
849
+ else if (chr >= '0' && chr <= '9') { // Allow direct entry of time
850
+ var key = parseInt(chr, 10);
851
+ var value = parseInt(inst._lastChr + chr, 10);
852
+ var hour = (inst._field != 0 ? inst._selectedHour :
853
+ (inst.options.show24Hours ? (value < 24 ? value : key) :
854
+ (value >= 1 && value <= 12 ? value :
855
+ (key > 0 ? key : inst._selectedHour)) % 12 +
856
+ (inst._selectedHour >= 12 ? 12 : 0)));
857
+ var minute = (inst._field != 1 ? inst._selectedMinute :
858
+ (value < 60 ? value : key));
859
+ var second = (inst._field != inst._secondField ? inst._selectedSecond :
860
+ (value < 60 ? value : key));
861
+ var fields = this._constrainTime(inst, [hour, minute, second]);
862
+ this._setTime(inst, new Date(0, 0, 0, fields[0], fields[1], fields[2]));
863
+ if (inst.options.noSeparatorEntry && inst._lastChr) {
864
+ this._changeField(inst, +1, false);
865
+ }
866
+ else {
867
+ inst._lastChr = chr;
868
+ }
869
+ }
870
+ else if (!inst.options.show24Hours) { // Set am/pm based on first char of names
871
+ chr = chr.toLowerCase();
872
+ if ((chr == inst.options.ampmNames[0].substring(0, 1).toLowerCase() &&
873
+ inst._selectedHour >= 12) ||
874
+ (chr == inst.options.ampmNames[1].substring(0, 1).toLowerCase() &&
875
+ inst._selectedHour < 12)) {
876
+ var saveField = inst._field;
877
+ inst._field = inst._ampmField;
878
+ this._adjustField(inst, +1);
879
+ inst._field = saveField;
880
+ this._showField(inst);
881
+ }
882
+ }
883
+ }
884
+ });
885
+
886
+ // The list of commands that return values and don't permit chaining
887
+ var getters = ['getOffset', 'getTime', 'isDisabled'];
888
+
889
+ /* Determine whether a command is a getter and doesn't permit chaining.
890
+ @param command (string, optional) the command to run
891
+ @param otherArgs ([], optional) any other arguments for the command
892
+ @return true if the command is a getter, false if not */
893
+ function isNotChained(command, otherArgs) {
894
+ if (command == 'option' && (otherArgs.length == 0 ||
895
+ (otherArgs.length == 1 && typeof otherArgs[0] == 'string'))) {
896
+ return true;
897
+ }
898
+ return $.inArray(command, getters) > -1;
899
+ }
900
+
901
+ /* Attach the time entry functionality to a jQuery selection.
902
+ @param options (object) the new settings to use for these instances (optional) or
903
+ (string) the command to run (optional)
904
+ @return (jQuery) for chaining further calls or
905
+ (any) getter value */
906
+ $.fn.timeEntry = function(options) {
907
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
908
+ if (isNotChained(options, otherArgs)) {
909
+ return plugin['_' + options + 'Plugin'].
910
+ apply(plugin, [this[0]].concat(otherArgs));
911
+ }
912
+ return this.each(function() {
913
+ if (typeof options == 'string') {
914
+ if (!plugin['_' + options + 'Plugin']) {
915
+ throw 'Unknown command: ' + options;
916
+ }
917
+ plugin['_' + options + 'Plugin'].
918
+ apply(plugin, [this].concat(otherArgs));
919
+ }
920
+ else {
921
+ // Check for settings on the control itself
922
+ var inlineSettings = ($.fn.metadata ? $(this).metadata() : {});
923
+ plugin._attachPlugin(this, $.extend({}, inlineSettings, options || {}));
924
+ }
925
+ });
926
+ };
927
+
928
+ /* Initialise the time entry functionality. */
929
+ var plugin = $.timeEntry = new TimeEntry(); // Singleton instance
930
+
931
+ })(jQuery);