Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Installed surveyor assets

  • Loading branch information...
commit db190d33302ddab7e4e551af70ebf573fc18b8cb 1 parent d4baeab
@diasks2 authored
Showing with 16,024 additions and 3 deletions.
  1. +9 −3 README.md
  2. +17 −0 config/locales/surveyor_en.yml
  3. +17 −0 config/locales/surveyor_es.yml
  4. +17 −0 config/locales/surveyor_he.yml
  5. +31 −0 db/migrate/20120425000541_create_surveys.rb
  6. +29 −0 db/migrate/20120425000542_create_survey_sections.rb
  7. +36 −0 db/migrate/20120425000543_create_questions.rb
  8. +27 −0 db/migrate/20120425000544_create_question_groups.rb
  9. +37 −0 db/migrate/20120425000545_create_answers.rb
  10. +22 −0 db/migrate/20120425000546_create_response_sets.rb
  11. +33 −0 db/migrate/20120425000547_create_responses.rb
  12. +22 −0 db/migrate/20120425000548_create_dependencies.rb
  13. +29 −0 db/migrate/20120425000549_create_dependency_conditions.rb
  14. +20 −0 db/migrate/20120425000550_create_validations.rb
  15. +32 −0 db/migrate/20120425000551_create_validation_conditions.rb
  16. +9 −0 db/migrate/20120425000552_add_display_order_to_surveys.rb
  17. +9 −0 db/migrate/20120425000553_add_correct_answer_id_to_questions.rb
  18. +9 −0 db/migrate/20120425000554_add_index_to_response_sets.rb
  19. +9 −0 db/migrate/20120425000555_add_index_to_surveys.rb
  20. +17 −0 db/migrate/20120425000556_add_unique_indicies.rb
  21. +11 −0 db/migrate/20120425000557_add_section_id_to_responses.rb
  22. +9 −0 db/migrate/20120425000558_add_default_value_to_answers.rb
  23. +13 −0 db/migrate/20120425000559_add_api_ids.rb
  24. +13 −0 db/migrate/20120425000560_add_display_type_to_answers.rb
  25. +9 −0 db/migrate/20120425000561_add_api_id_to_question_groups.rb
  26. +11 −0 db/migrate/20120425000562_add_api_ids_to_response_sets_and_responses.rb
  27. +21 −0 db/migrate/20120425000563_update_blank_api_ids_on_question_group.rb
  28. +195 −0 db/schema.rb
  29. +158 −0 surveys/README.md
  30. +15 −0 surveys/date_survey.rb
  31. +243 −0 surveys/kitchen_sink_survey.rb
  32. +10 −0 surveys/quiz.rb
  33. BIN  vendor/assets/images/surveyor/next.gif
  34. BIN  vendor/assets/images/surveyor/prev.gif
  35. +1,277 −0 vendor/assets/javascripts/surveyor/jquery-ui-timepicker-addon.js
  36. +11,729 −0 vendor/assets/javascripts/surveyor/jquery-ui.js
  37. +499 −0 vendor/assets/javascripts/surveyor/jquery.blockUI.js
  38. +147 −0 vendor/assets/javascripts/surveyor/jquery.surveyor.js
  39. +204 −0 vendor/assets/javascripts/surveyor/jquery.tools.min.js
  40. +5 −0 vendor/assets/stylesheets/custom.sass
  41. +122 −0 vendor/assets/stylesheets/surveyor.sass
  42. +149 −0 vendor/assets/stylesheets/surveyor/dateinput.css
  43. +6 −0 vendor/assets/stylesheets/surveyor/jquery-ui-timepicker-addon.css
  44. +572 −0 vendor/assets/stylesheets/surveyor/jquery-ui.custom.css
  45. +50 −0 vendor/assets/stylesheets/surveyor/reset.css
  46. +125 −0 vendor/assets/stylesheets/surveyor/results.css
  47. BIN  vendor/assets/stylesheets/surveyor/ui-bg_glass_100_f6f6f6_1x400.png
  48. BIN  vendor/assets/stylesheets/surveyor/ui-bg_glass_100_fdf5ce_1x400.png
  49. BIN  vendor/assets/stylesheets/surveyor/ui-bg_glass_65_ffffff_1x400.png
  50. BIN  vendor/assets/stylesheets/surveyor/ui-bg_gloss-wave_35_f6a828_500x100.png
  51. BIN  vendor/assets/stylesheets/surveyor/ui-bg_highlight-soft_75_ffe45c_1x100.png
  52. BIN  vendor/assets/stylesheets/surveyor/ui-icons_ef8c08_256x240.png
  53. BIN  vendor/assets/stylesheets/surveyor/ui-icons_ffffff_256x240.png
View
12 README.md
@@ -2,9 +2,9 @@
- - -
The [NUBIC/surveyor gem](https://github.com/NUBIC/surveyor) is an awesome ruby gem and developer tool that brings surveys into Rails applications. Surveys are written in the Surveyor DSL (Domain Specific Language). If your Rails app needs to asks users questions as part of a survey, quiz, or questionnaire then you should consider using Surveyor.
-This tutorial is to help Rails beginners implement and extend this gem in their Rails project. The "kitchen sink" Survey in the [surveyor README](https://github.com/NUBIC/surveyor#readme) is a great place to get started, as is the "extending surveyor" README. However, for beginners, I thought it might be useful to have an example that walks through how to tie the surveyor gem to your user model and make some basic customizations.
+This tutorial is to help Rails beginners implement and extend this gem in their Rails project. The "kitchen sink" Survey in the [surveyor README](https://github.com/NUBIC/surveyor#readme) is a great place to get started, as is the "extending surveyor" README. However, for beginners, I thought it might be useful to have an example that walks through how to tie the surveyor gem to your user model and make some basic customizations. For this tutorial I am using Rails 3.2.3 and Ruby 1.9.3p125 (2012-02-16 revision 34643) [x86_64-darwin10.8.0].
-Surveyor Tutorial
+#Surveyor Tutorial
- - -
1) Create a new repository on [GitHub](https://github.com) named 'surveyor_example'
@@ -18,7 +18,7 @@ Surveyor Tutorial
$ subl .
-4) Update the Gemfile. Cut & Paste the contents of this [tutorials Gemfile](https://github.com/diasks2/surveyor_example/blob/master/Gemfile) into your Gemfile
+4) Update the Gemfile. Cut & Paste the contents of this [tutorials Gemfile](https://github.com/diasks2/surveyor_example/blob/master/Gemfile) into your Gemfile.
5) Install and include the new gems
@@ -40,4 +40,10 @@ If this happens to you, run the following in your command line:
$ git remote add origin git@github.com:<username>/surveyor_example.git
$ git push -u origin master
+7) (Optional) Deploy the app to Heroku. (Assuming you have already created a Heroku account. If not, check out this [tutorial](http://ruby.railstutorial.org/chapters/beginning?version=3.2#sec:1.4.1))
+
+ $ heroku create --stack cedar
+ $ git push heroku master
+
+
View
17 config/locales/surveyor_en.yml
@@ -0,0 +1,17 @@
+# Sample localization file for English. Add more files in this directory for other locales.
+# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+en:
+ surveyor:
+ take_these_surveys: "You may take these surveys"
+ take_it: "Take it"
+ completed_survey: "Completed survey"
+ unable_to_find_your_responses: "Unable to find your responses to the survey"
+ unable_to_update_survey: "Unable to update survey"
+ unable_to_find_that_survey: "Unable to find that survey"
+ survey_started_success: "Survey started successfully"
+ click_here_to_finish: "Click here to finish"
+ previous_section: "&laquo; Previous section"
+ next_section: "Next section &raquo;"
+ select_one: "Select one ..."
+ sections: "Sections"
View
17 config/locales/surveyor_es.yml
@@ -0,0 +1,17 @@
+# Sample localization file for Spanish. Add more files in this directory for other locales.
+# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+es:
+ surveyor:
+ take_these_surveys: "Bienvenido, usted puede tomar estas encuestas"
+ take_it: "Tomar"
+ completed_survey: "Encuesta completada"
+ unable_to_find_your_responses: "No se puede encontrar sus respuestas a la encuesta"
+ unable_to_update_survey: "No se puede actualizar la encuesta"
+ unable_to_find_that_survey: "No se puede encontrar la encuesta"
+ survey_started_success: "Encuesta iniciada correctamente"
+ click_here_to_finish: "Haga clic aquí para terminar"
+ previous_section: "&laquo; Sección anterior"
+ next_section: "Sección siguiente &raquo;"
+ select_one: "Seleccione una ..."
+ sections: "Secciones"
View
17 config/locales/surveyor_he.yml
@@ -0,0 +1,17 @@
+# Localization file for Hebrew. Add more files in this directory for other locales.
+# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+he:
+ surveyor:
+ take_these_surveys: "תוכלו לבצע סקרים אלו"
+ take_it: "בצע"
+ completed_survey: "סיום הסקר"
+ unable_to_find_your_responses: "לא נמצאו תשובותיך לסקר"
+ unable_to_update_survey: ""
+ unable_to_find_that_survey: "לא ניתן לאתר את הסקר המבוקש"
+ survey_started_success: "הסקר הוחל בהצלחה"
+ click_here_to_finish: "לסיום"
+ previous_section: "חזרה &raquo;"
+ next_section: "&laquo; המשך"
+ Select_one: "בחר/י"
+ sections: "סעיפים"
View
31 db/migrate/20120425000541_create_surveys.rb
@@ -0,0 +1,31 @@
+class CreateSurveys < ActiveRecord::Migration
+ def self.up
+ create_table :surveys do |t|
+ # Content
+ t.string :title
+ t.text :description
+
+ # Reference
+ t.string :access_code
+ t.string :reference_identifier # from paper
+ t.string :data_export_identifier # data export
+ t.string :common_namespace # maping to a common vocab
+ t.string :common_identifier # maping to a common vocab
+
+ # Expiry
+ t.datetime :active_at
+ t.datetime :inactive_at
+
+ # Display
+ t.string :css_url
+
+ t.string :custom_class
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :surveys
+ end
+end
View
29 db/migrate/20120425000542_create_survey_sections.rb
@@ -0,0 +1,29 @@
+class CreateSurveySections < ActiveRecord::Migration
+ def self.up
+ create_table :survey_sections do |t|
+ # Context
+ t.integer :survey_id
+
+ # Content
+ t.string :title
+ t.text :description
+
+ # Reference
+ t.string :reference_identifier # from paper
+ t.string :data_export_identifier # data export
+ t.string :common_namespace # maping to a common vocab
+ t.string :common_identifier # maping to a common vocab
+
+ # Display
+ t.integer :display_order
+
+ t.string :custom_class
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :survey_sections
+ end
+end
View
36 db/migrate/20120425000543_create_questions.rb
@@ -0,0 +1,36 @@
+class CreateQuestions < ActiveRecord::Migration
+ def self.up
+ create_table :questions do |t|
+ # Context
+ t.integer :survey_section_id
+ t.integer :question_group_id
+
+ # Content
+ t.text :text
+ t.text :short_text # For experts (ie non-survey takers). Short version of text
+ t.text :help_text
+ t.string :pick
+
+ # Reference
+ t.string :reference_identifier # from paper
+ t.string :data_export_identifier # data export
+ t.string :common_namespace # maping to a common vocab
+ t.string :common_identifier # maping to a common vocab
+
+ # Display
+ t.integer :display_order
+ t.string :display_type
+ t.boolean :is_mandatory
+ t.integer :display_width # used only for slider component (if needed)
+
+ t.string :custom_class
+ t.string :custom_renderer
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :questions
+ end
+end
View
27 db/migrate/20120425000544_create_question_groups.rb
@@ -0,0 +1,27 @@
+class CreateQuestionGroups < ActiveRecord::Migration
+ def self.up
+ create_table :question_groups do |t|
+ # Content
+ t.text :text
+ t.text :help_text
+
+ # Reference
+ t.string :reference_identifier # from paper
+ t.string :data_export_identifier # data export
+ t.string :common_namespace # maping to a common vocab
+ t.string :common_identifier # maping to a common vocab
+
+ # Display
+ t.string :display_type
+
+ t.string :custom_class
+ t.string :custom_renderer
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :question_groups
+ end
+end
View
37 db/migrate/20120425000545_create_answers.rb
@@ -0,0 +1,37 @@
+class CreateAnswers < ActiveRecord::Migration
+ def self.up
+ create_table :answers do |t|
+ # Context
+ t.integer :question_id
+
+ # Content
+ t.text :text
+ t.text :short_text #Used for presenting responses to experts (ie non-survey takers). Just a shorted version of the string
+ t.text :help_text
+ t.integer :weight # Used to assign a weight to an answer object (used for computing surveys that have numerical results) (I added this to support the Urology questionnaire -BLC)
+ t.string :response_class # What kind of additional data does this answer accept?
+
+ # Reference
+ t.string :reference_identifier # from paper
+ t.string :data_export_identifier # data export
+ t.string :common_namespace # maping to a common vocab
+ t.string :common_identifier # maping to a common vocab
+
+ # Display
+ t.integer :display_order
+ t.boolean :is_exclusive # If set it causes some UI trigger to remove (and disable) all the other answer choices selected for a question (needed for the WHR)
+ t.boolean :hide_label
+ t.integer :display_length # if smaller than answer.length the html input length will be this value
+
+ t.string :custom_class
+ t.string :custom_renderer
+
+ t.timestamps
+
+ end
+ end
+
+ def self.down
+ drop_table :answers
+ end
+end
View
22 db/migrate/20120425000546_create_response_sets.rb
@@ -0,0 +1,22 @@
+class CreateResponseSets < ActiveRecord::Migration
+ def self.up
+ create_table :response_sets do |t|
+ # Context
+ t.integer :user_id
+ t.integer :survey_id
+
+ # Content
+ t.string :access_code #unique id for the object used in urls
+
+ # Expiry
+ t.datetime :started_at
+ t.datetime :completed_at
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :response_sets
+ end
+end
View
33 db/migrate/20120425000547_create_responses.rb
@@ -0,0 +1,33 @@
+class CreateResponses < ActiveRecord::Migration
+ def self.up
+ create_table :responses do |t|
+ # Context
+ t.integer :response_set_id
+ t.integer :question_id
+
+ # Content
+ t.integer :answer_id
+ t.datetime :datetime_value # handles date, time, and datetime (segregate by answer.response_class)
+
+ #t.datetime :time_value
+ t.integer :integer_value
+ t.float :float_value
+ t.string :unit
+ t.text :text_value
+ t.string :string_value
+ t.string :response_other #used to hold the string entered with "Other" type answers in multiple choice questions
+
+ # arbitrary identifier used to group responses
+ # the pertinent example here is Q: What's your car's make/model/year
+ # group 1: Ford/Focus/2007
+ # group 2: Toyota/Prius/2006
+ t.string :response_group
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :responses
+ end
+end
View
22 db/migrate/20120425000548_create_dependencies.rb
@@ -0,0 +1,22 @@
+class CreateDependencies < ActiveRecord::Migration
+ def self.up
+ create_table :dependencies do |t|
+ # Context
+ t.integer :question_id # the dependent question
+ t.integer :question_group_id
+
+ # Conditional
+ t.string :rule
+
+ # Result - TODO: figure out the dependency hook presentation options
+ # t.string :property_to_toggle # visibility, class_name,
+ # t.string :effect #blind, opacity
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :dependencies
+ end
+end
View
29 db/migrate/20120425000549_create_dependency_conditions.rb
@@ -0,0 +1,29 @@
+class CreateDependencyConditions < ActiveRecord::Migration
+ def self.up
+ create_table :dependency_conditions do |t|
+ # Context
+ t.integer :dependency_id
+ t.string :rule_key
+
+ # Conditional
+ t.integer :question_id # the conditional question
+ t.string :operator
+
+ # Value
+ t.integer :answer_id
+ t.datetime :datetime_value
+ t.integer :integer_value
+ t.float :float_value
+ t.string :unit
+ t.text :text_value
+ t.string :string_value
+ t.string :response_other
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :dependency_conditions
+ end
+end
View
20 db/migrate/20120425000550_create_validations.rb
@@ -0,0 +1,20 @@
+class CreateValidations < ActiveRecord::Migration
+ def self.up
+ create_table :validations do |t|
+ # Context
+ t.integer :answer_id # the answer to validate
+
+ # Conditional
+ t.string :rule
+
+ # Message
+ t.string :message
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :validations
+ end
+end
View
32 db/migrate/20120425000551_create_validation_conditions.rb
@@ -0,0 +1,32 @@
+class CreateValidationConditions < ActiveRecord::Migration
+ def self.up
+ create_table :validation_conditions do |t|
+ # Context
+ t.integer :validation_id
+ t.string :rule_key
+
+ # Conditional
+ t.string :operator
+
+ # Optional external reference
+ t.integer :question_id
+ t.integer :answer_id
+
+ # Value
+ t.datetime :datetime_value
+ t.integer :integer_value
+ t.float :float_value
+ t.string :unit
+ t.text :text_value
+ t.string :string_value
+ t.string :response_other
+ t.string :regexp
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :validation_conditions
+ end
+end
View
9 db/migrate/20120425000552_add_display_order_to_surveys.rb
@@ -0,0 +1,9 @@
+class AddDisplayOrderToSurveys < ActiveRecord::Migration
+ def self.up
+ add_column :surveys, :display_order, :integer
+ end
+
+ def self.down
+ remove_column :surveys, :display_order
+ end
+end
View
9 db/migrate/20120425000553_add_correct_answer_id_to_questions.rb
@@ -0,0 +1,9 @@
+class AddCorrectAnswerIdToQuestions < ActiveRecord::Migration
+ def self.up
+ add_column :questions, :correct_answer_id, :integer
+ end
+
+ def self.down
+ remove_column :questions, :correct_answer_id
+ end
+end
View
9 db/migrate/20120425000554_add_index_to_response_sets.rb
@@ -0,0 +1,9 @@
+class AddIndexToResponseSets < ActiveRecord::Migration
+ def self.up
+ add_index(:response_sets, :access_code, :name => 'response_sets_ac_idx')
+ end
+
+ def self.down
+ remove_index(:response_sets, :name => 'response_sets_ac_idx')
+ end
+end
View
9 db/migrate/20120425000555_add_index_to_surveys.rb
@@ -0,0 +1,9 @@
+class AddIndexToSurveys < ActiveRecord::Migration
+ def self.up
+ add_index(:surveys, :access_code, :name => 'surveys_ac_idx')
+ end
+
+ def self.down
+ remove_index(:surveys, :name => 'surveys_ac_idx')
+ end
+end
View
17 db/migrate/20120425000556_add_unique_indicies.rb
@@ -0,0 +1,17 @@
+class AddUniqueIndicies < ActiveRecord::Migration
+ def self.up
+ remove_index(:response_sets, :name => 'response_sets_ac_idx')
+ add_index(:response_sets, :access_code, :name => 'response_sets_ac_idx', :unique => true)
+
+ remove_index(:surveys, :name => 'surveys_ac_idx')
+ add_index(:surveys, :access_code, :name => 'surveys_ac_idx', :unique => true)
+ end
+
+ def self.down
+ remove_index(:response_sets, :name => 'response_sets_ac_idx')
+ add_index(:response_sets, :access_code, :name => 'response_sets_ac_idx')
+
+ remove_index(:surveys, :name => 'surveys_ac_idx')
+ add_index(:surveys, :access_code, :name => 'surveys_ac_idx')
+ end
+end
View
11 db/migrate/20120425000557_add_section_id_to_responses.rb
@@ -0,0 +1,11 @@
+class AddSectionIdToResponses < ActiveRecord::Migration
+ def self.up
+ add_column :responses, :survey_section_id, :integer
+ add_index :responses, :survey_section_id
+ end
+
+ def self.down
+ remove_index :responses, :survey_section_id
+ remove_column :responses, :survey_section_id
+ end
+end
View
9 db/migrate/20120425000558_add_default_value_to_answers.rb
@@ -0,0 +1,9 @@
+class AddDefaultValueToAnswers < ActiveRecord::Migration
+ def self.up
+ add_column :answers, :default_value, :string
+ end
+
+ def self.down
+ remove_column :answers, :default_value
+ end
+end
View
13 db/migrate/20120425000559_add_api_ids.rb
@@ -0,0 +1,13 @@
+class AddApiIds < ActiveRecord::Migration
+ def self.up
+ add_column :surveys, :api_id, :string
+ add_column :questions, :api_id, :string
+ add_column :answers, :api_id, :string
+ end
+
+ def self.down
+ remove_column :surveys, :api_id
+ remove_column :questions, :api_id
+ remove_column :answers, :api_id
+ end
+end
View
13 db/migrate/20120425000560_add_display_type_to_answers.rb
@@ -0,0 +1,13 @@
+class AddDisplayTypeToAnswers < ActiveRecord::Migration
+ def self.up
+ add_column :answers, :display_type, :string
+ Answer.all.each{|a| a.update_attributes(:display_type => "hidden_label") if a.hide_label == true}
+ remove_column :answers, :hide_label
+ end
+
+ def self.down
+ add_column :answers, :hide_label, :boolean
+ Answer.all.each{|a| a.update_attributes(:hide_label => true) if a.display_type == "hidden_label"}
+ remove_column :answers, :display_type
+ end
+end
View
9 db/migrate/20120425000561_add_api_id_to_question_groups.rb
@@ -0,0 +1,9 @@
+class AddApiIdToQuestionGroups < ActiveRecord::Migration
+ def self.up
+ add_column :question_groups, :api_id, :string
+ end
+
+ def self.down
+ remove_column :question_groups, :api_id
+ end
+end
View
11 db/migrate/20120425000562_add_api_ids_to_response_sets_and_responses.rb
@@ -0,0 +1,11 @@
+class AddApiIdsToResponseSetsAndResponses < ActiveRecord::Migration
+ def self.up
+ add_column :response_sets, :api_id, :string
+ add_column :responses, :api_id, :string
+ end
+
+ def self.down
+ remove_column :response_sets, :api_id
+ remove_column :responses, :api_id
+ end
+end
View
21 db/migrate/20120425000563_update_blank_api_ids_on_question_group.rb
@@ -0,0 +1,21 @@
+class Survey < ActiveRecord::Base; end
+class Question < ActiveRecord::Base; end
+class QuestionGroup < ActiveRecord::Base; end
+class Answer < ActiveRecord::Base; end
+class Response < ActiveRecord::Base; end
+class ResponseSet < ActiveRecord::Base; end
+
+class UpdateBlankApiIdsOnQuestionGroup < ActiveRecord::Migration
+ def self.up
+ check = [Survey, Question, QuestionGroup, Answer, Response, ResponseSet]
+ check.each do |clazz|
+ clazz.where('api_id IS ?', nil).each do |c|
+ c.api_id = Surveyor::Common.generate_api_id
+ c.save!
+ end
+ end
+ end
+
+ def self.down
+ end
+end
View
195 db/schema.rb
@@ -0,0 +1,195 @@
+# encoding: UTF-8
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 20120425000563) do
+
+ create_table "answers", :force => true do |t|
+ t.integer "question_id"
+ t.text "text"
+ t.text "short_text"
+ t.text "help_text"
+ t.integer "weight"
+ t.string "response_class"
+ t.string "reference_identifier"
+ t.string "data_export_identifier"
+ t.string "common_namespace"
+ t.string "common_identifier"
+ t.integer "display_order"
+ t.boolean "is_exclusive"
+ t.integer "display_length"
+ t.string "custom_class"
+ t.string "custom_renderer"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "default_value"
+ t.string "api_id"
+ t.string "display_type"
+ end
+
+ create_table "dependencies", :force => true do |t|
+ t.integer "question_id"
+ t.integer "question_group_id"
+ t.string "rule"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "dependency_conditions", :force => true do |t|
+ t.integer "dependency_id"
+ t.string "rule_key"
+ t.integer "question_id"
+ t.string "operator"
+ t.integer "answer_id"
+ t.datetime "datetime_value"
+ t.integer "integer_value"
+ t.float "float_value"
+ t.string "unit"
+ t.text "text_value"
+ t.string "string_value"
+ t.string "response_other"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "question_groups", :force => true do |t|
+ t.text "text"
+ t.text "help_text"
+ t.string "reference_identifier"
+ t.string "data_export_identifier"
+ t.string "common_namespace"
+ t.string "common_identifier"
+ t.string "display_type"
+ t.string "custom_class"
+ t.string "custom_renderer"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "api_id"
+ end
+
+ create_table "questions", :force => true do |t|
+ t.integer "survey_section_id"
+ t.integer "question_group_id"
+ t.text "text"
+ t.text "short_text"
+ t.text "help_text"
+ t.string "pick"
+ t.string "reference_identifier"
+ t.string "data_export_identifier"
+ t.string "common_namespace"
+ t.string "common_identifier"
+ t.integer "display_order"
+ t.string "display_type"
+ t.boolean "is_mandatory"
+ t.integer "display_width"
+ t.string "custom_class"
+ t.string "custom_renderer"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "correct_answer_id"
+ t.string "api_id"
+ end
+
+ create_table "response_sets", :force => true do |t|
+ t.integer "user_id"
+ t.integer "survey_id"
+ t.string "access_code"
+ t.datetime "started_at"
+ t.datetime "completed_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.string "api_id"
+ end
+
+ add_index "response_sets", ["access_code"], :name => "response_sets_ac_idx", :unique => true
+
+ create_table "responses", :force => true do |t|
+ t.integer "response_set_id"
+ t.integer "question_id"
+ t.integer "answer_id"
+ t.datetime "datetime_value"
+ t.integer "integer_value"
+ t.float "float_value"
+ t.string "unit"
+ t.text "text_value"
+ t.string "string_value"
+ t.string "response_other"
+ t.string "response_group"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "survey_section_id"
+ t.string "api_id"
+ end
+
+ add_index "responses", ["survey_section_id"], :name => "index_responses_on_survey_section_id"
+
+ create_table "survey_sections", :force => true do |t|
+ t.integer "survey_id"
+ t.string "title"
+ t.text "description"
+ t.string "reference_identifier"
+ t.string "data_export_identifier"
+ t.string "common_namespace"
+ t.string "common_identifier"
+ t.integer "display_order"
+ t.string "custom_class"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "surveys", :force => true do |t|
+ t.string "title"
+ t.text "description"
+ t.string "access_code"
+ t.string "reference_identifier"
+ t.string "data_export_identifier"
+ t.string "common_namespace"
+ t.string "common_identifier"
+ t.datetime "active_at"
+ t.datetime "inactive_at"
+ t.string "css_url"
+ t.string "custom_class"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "display_order"
+ t.string "api_id"
+ end
+
+ add_index "surveys", ["access_code"], :name => "surveys_ac_idx", :unique => true
+
+ create_table "validation_conditions", :force => true do |t|
+ t.integer "validation_id"
+ t.string "rule_key"
+ t.string "operator"
+ t.integer "question_id"
+ t.integer "answer_id"
+ t.datetime "datetime_value"
+ t.integer "integer_value"
+ t.float "float_value"
+ t.string "unit"
+ t.text "text_value"
+ t.string "string_value"
+ t.string "response_other"
+ t.string "regexp"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ create_table "validations", :force => true do |t|
+ t.integer "answer_id"
+ t.string "rule"
+ t.string "message"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+end
View
158 surveys/README.md
@@ -0,0 +1,158 @@
+# Surveys On Rails
+
+Surveyor is a ruby gem and developer tool that brings surveys into Rails applications. Surveys are written in the Surveyor DSL (Domain Specific Language). Before Rails 2.3, it was implemented as a Rails Engine. It also existed previously as a plugin. Today it is a gem only.
+
+## Why you might want to use Surveyor
+
+If your Rails app needs to asks users questions as part of a survey, quiz, or questionnaire then you should consider using Surveyor. This gem was designed to deliver clinical research surveys to large populations, but it can be used for any type of survey.
+
+The Surveyor DSL defines questions, answers, question groups, survey sections, dependencies (e.g. if response to question 4 is A, then show question 5), and validations. Answers are the options available for each question - user input is called "responses" and are grouped into "response sets". A DSL makes it significantly easier to import long surveys (no more click/copy/paste). It also enables non-programmers to write out, edit, re-edit... any number of surveys.
+
+## DSL example
+
+The Surveyor DSL supports a wide range of question types (too many to list here) and complex dependency logic. Here are the first few questions of the "kitchen sink" survey which should give you and idea of how it works. The full example with all the types of questions available if you follow the installation instructions below.
+
+ survey "Kitchen Sink survey" do
+
+ section "Basic questions" do
+ # A label is a question that accepts no answers
+ label "These questions are examples of the basic supported input types"
+
+ # A basic question with radio buttons
+ question_1 "What is your favorite color?", :pick => :one
+ answer "red"
+ answer "blue"
+ answer "green"
+ answer "yellow"
+ answer :other
+
+ # A basic question with checkboxes
+ # "question" and "answer" may be abbreviated as "q" and "a"
+ q_2 "Choose the colors you don't like", :pick => :any
+ a_1 "red"
+ a_2 "blue"
+ a_3 "green"
+ a_4 "yellow"
+ a :omit
+
+ # A dependent question, with conditions and rule to logically join them
+ # the question's reference identifier is "2a", and the answer's reference_identifier is "1"
+ # question reference identifiers used in conditions need to be unique on a survey for the lookups to work
+ q_2a "Please explain why you don't like this color?"
+ a_1 "explanation", :text
+ dependency :rule => "A or B or C or D"
+ condition_A :q_2, "==", :a_1
+ condition_B :q_2, "==", :a_2
+ condition_C :q_2, "==", :a_3
+ condition_D :q_2, "==", :a_4
+
+ # ... other question, sections and such. See surveys/kitchen_sink_survey.rb for more.
+ end
+
+ end
+
+The first question is "pick one" (radio buttons) with "other". The second question is "pick any" (checkboxes) with the option to "omit". It also features a dependency with a follow up question. Notice the dependency rule is defined as a string. We support complex dependency such as "A and (B or C) and D" or "A or ((B and C) or D)". The conditions are evaluated separately using the operators "==","!=","<>", ">=","<" the substituted by letter into to the dependency rule and evaluated.
+
+# Installation
+
+1. Add surveyor to your Gemfile:
+
+ gem "surveyor"
+
+Then run:
+
+ bundle install
+
+2. Generate assets, run migrations:
+
+ script/rails generate surveyor:install
+ rake db:migrate
+
+3. Try out the "kitchen sink" survey. The rake task above generates surveys from our custom survey DSL (a good format for end users and stakeholders).
+
+ rake surveyor FILE=surveys/kitchen_sink_survey.rb
+
+4. Start up your app and visit http://localhost:3000/surveys
+
+Try taking the survey and compare it to the contents of the DSL file kitchen\_sink\_survey.rb. See how the DSL maps to what you see.
+
+There are two other useful rake tasks:
+
+* `surveyor:remove` removes all unused surveys.
+* `rake surveyor:unparse` exports a survey from the application into a
+ file in the surveyor DSL.
+
+# Customizing surveyor
+
+Surveyor's controller, models, and views may be customized via classes in your app/models, app/helpers and app/controllers directories. To generate a sample custom controller and layout, run:
+
+`script/rails generate surveyor:custom`
+
+and read surveys/EXTENDING\_SURVEYOR
+
+# PDF support
+
+* Add the following lines to your Gemfile:
+
+<pre>
+ gem 'pdfkit'
+ gem 'wkhtmltopdf'
+</pre>
+
+or on OSX:
+
+<pre>
+ gem 'pdfkit'
+ gem 'wkhtmltopdf-binary'
+</pre>
+
+* Add the following to your application.rb:
+
+<pre>
+ config.middleware.use PDFKit::Middleware
+</pre>
+
+* Create links with :format => 'pdf' in them, for example:
+
+<pre>
+ %li= link_to "PDF", view_my_survey_path(:survey_code => response_set.survey.access_code, :response_set_code => response_set.access_code, :format => 'pdf')
+</pre>
+
+# Requirements
+
+Surveyor depends on:
+
+* Ruby (1.8.7 - 1.9.2)
+* Rails 3.0-3.1
+* HAML
+* SASS
+* fastercsv (or CSV for ruby 1.9) for csv exports
+* formtastic
+* UUID
+
+Specific versions of the gem dependencies are listed in the gemspec.
+
+# Contributing, testing
+
+To work on the code, fork this github project. Install [bundler][] if
+you don't have it, then run
+
+ $ bundle update
+
+to install all the necessary gems. Then
+
+ $ bundle exec rake testbed
+
+to generate a test app in `testbed`. Now you can run
+
+ $ bundle exec rake spec
+
+to run the specs and
+
+ $ bundle exec rake cucumber
+
+to run the features and start writing tests!
+
+[bundler]: http://gembundler.com/
+
+Copyright (c) 2008-2011 Brian Chamberlain and Mark Yoon, released under the MIT license
View
15 surveys/date_survey.rb
@@ -0,0 +1,15 @@
+survey "Date Survey" do
+
+ section "Simple date questions" do
+
+ q "What is your birth date?"
+ a :date
+
+ q "At what time were you born?"
+ a :time
+
+ q "When would you like to schedule your next appointment?"
+ a :datetime
+
+ end
+end
View
243 surveys/kitchen_sink_survey.rb
@@ -0,0 +1,243 @@
+survey "Kitchen Sink survey" do
+
+ section "Basic questions" do
+ # A label is a question that accepts no answers
+ label "These questions are examples of the basic supported input types"
+
+ # A basic question with radio buttons
+ question_1 "What is your favorite color?", :pick => :one
+ answer "red"
+ answer "blue"
+ answer "green"
+ answer "yellow"
+ answer :other
+
+ # A basic question with checkboxes
+ # "question" and "answer" may be abbreviated as "q" and "a"
+ q_2 "Choose the colors you don't like", :pick => :any
+ a_1 "red"
+ a_2 "blue"
+ a_3 "green"
+ a_4 "yellow"
+ a :omit
+
+ # A dependent question, with conditions and rule to logically join them
+ # the question's reference identifier is "2a", and the answer's reference_identifier is "1"
+ # question reference identifiers used in conditions need to be unique on a survey for the lookups to work
+ q_2a "Please explain why you don't like this color?"
+ a_1 "explanation", :text, :help_text => "Please give an explanation for each color you don't like"
+ dependency :rule => "A or B or C or D"
+ condition_A :q_2, "==", :a_1
+ condition_B :q_2, "==", :a_2
+ condition_C :q_2, "==", :a_3
+ condition_D :q_2, "==", :a_4
+
+ # A dependant question demonstrating the count operator. The
+ # dependency condition checks the answer count for the referenced question.
+ # It understands conditions of the form count> count< count>= count<=
+ # count!=
+ q_2b "Please explain why you dislike so many colors?"
+ a_1 "explanation", :text
+ dependency :rule => "Z"
+ condition_Z :q_2, "count>2"
+
+ # When :pick isn't specified, the default is :none (no checkbox or radio button)
+ q_montypython3 "What... is your name? (e.g. It is 'Arthur', King of the Britons)"
+ a_1 :string
+
+ # Dependency conditions can refer to any value, not just answer_id. An answer_reference still needs to be specified, to know which answer you would like to check
+ q_montypython4 "What... is your quest? (e.g. To seek the Holy Grail)"
+ a_1 :string
+ dependency :rule => "A"
+ condition_A :q_montypython3, "==", {:string_value => "It is 'Arthur', King of the Britons", :answer_reference => "1"}
+
+ # http://www.imdb.com/title/tt0071853/quotes
+ q_montypython5 "What... is the air-speed velocity of an unladen swallow? (e.g. What do you mean? An African or European swallow?)"
+ a_1 :string
+ dependency :rule => "A"
+ condition_A :q_montypython4, "==", {:string_value => "To seek the Holy Grail", :answer_reference => "1"}
+
+ label "Huh? I-- I don't know that! Auuuuuuuugh!"
+ dependency :rule => "A"
+ condition_A :q_montypython5, "==", {:string_value => "What do you mean? An African or European swallow?", :answer_reference => "1"}
+
+ # Surveys, sections, questions, groups, and answers all take the following reference arguments
+ # :reference_identifier # usually from paper
+ # :data_export_identifier # data export
+ # :common_namespace # maping to a common vocab
+ # :common_identifier # maping to a common vocab
+ q "Get me started on an improv sketch", :reference_identifier => "improv_start", :data_export_identifier => "i_s", :common_namespace => "sketch comedy questions", :common_identifier => "get me started"
+ a "who", :string
+ a "what", :string
+ a "where", :string
+
+ # Various types of responses can be accepted, and validated
+ q "How many pets do you own?"
+ a :integer
+ validation :rule => "A"
+ condition_A ">=", :integer_value => 0
+
+ # Surveys, sections, questions, groups, and answers also take a custom css class for covenience in custom styling
+ q "What is your address?", :custom_class => 'address'
+ a :text, :custom_class => 'mapper'
+ # validations can use regexp values
+ validation :rule => "A"
+ condition_A "=~", :regexp => "[0-9a-zA-z\. #]"
+
+ # Questions, groups, and answers take a custom renderer (a partial in the application's views dir)
+ # defaults are "/partials/question_group", "/partials/question", "/partials/answer", so the custom renderers should have a different name
+ q "Pick your favorite date AND time" #, :custom_renderer => "/partials/custom_question"
+ a :datetime
+
+ q_time_lunch "What time do you usually take a lunch break?"
+ a_1 :time
+
+ q "When would you like to meet for dinner?"
+ a :date
+
+ # Sliders deprecate to select boxes when javascript is off
+ # Valid Ruby code may be used to shorted repetitive tasks
+ q "Adjust the slider to reflect your level of awesomeness", :pick => :one, :display_type => :slider
+ (1..10).to_a.each{|num| a num.to_s}
+
+ q "How much do you like Ruby?", :pick => :one, :display_type => :slider
+ ["not at all", "a little", "some", "a lot", "a ton"].each{|level| a level}
+
+ # The "|" pipe is used to place labels before or after the input elements
+ q "How much money do you want?"
+ a "$|USD", :float
+
+ # Questions may be grouped
+ group "How much oil do you use per day?", :display_type => :inline do
+ q "Quantity"
+ a :float
+
+ q "Unit", :pick => :one, :display_type => :dropdown
+ a "Barrels"
+ a "Gallons"
+ a "Quarts"
+ end
+
+ q "Choose your Illinois county", :pick => :one, :display_type => :dropdown
+ ["Adams","Alexander","Bond", "Boone",
+ "Brown","Bureau","Calhoun","Carroll","Cass",
+ "Champaign", "Christian", "Clark","Clay",
+ "Clinton", "Coles", "Cook", "Crawford","Cumberland","DeKalb",
+ "De Witt","Douglas","DuPage","Edgar", "Edwards",
+ "Effingham","Fayette", "Ford","Franklin","Fulton",
+ "Gallatin","Greene", "Grundy", "Hamilton","Hancock",
+ "Hardin","Henderson","Henry","Iroquois","Jackson",
+ "Jasper", "Jefferson","Jersey","Jo Daviess","Johnson",
+ "Kane","Kankakee","Kendall","Knox", "La Salle",
+ "Lake", "Lawrence", "Lee","Livingston","Logan",
+ "Macon","Macoupin","Madison","Marion","Marshall", "Mason",
+ "Massac","McDonough","McHenry","McLean","Menard",
+ "Mercer","Monroe", "Montgomery","Morgan","Moultrie",
+ "Ogle","Peoria","Perry","Piatt","Pike","Pope",
+ "Pulaski","Putnam","Randolph","Richland","Rock Island",
+ "Saline","Sangamon","Schuyler","Scott","Shelby",
+ "St. Clair","Stark", "Stephenson","Tazewell","Union",
+ "Vermilion","Wabash","Warren","Washington","Wayne",
+ "White","Whiteside","Will","Williamson","Winnebago","Woodford"].each{ |county| a county}
+
+ # When an is_exclusive answer is checked, it unchecks all other options and disables them (using Javascript)
+ q "Choose your favorite meats", :display_type => :inline, :pick => :any
+ a "Chicken"
+ a "Pork"
+ a "Beef"
+ a "Shellfish"
+ a "Fish"
+ a "I don't eat meats!!!", :is_exclusive => true
+
+ q "All out of ideas for questions?", :pick => :one, :display_type => :inline
+ a "yes"
+ a "maybe"
+ a "no"
+ a "I don't know"
+ end
+
+ section "Complicated questions" do
+
+ # Grids are useful for repeated questions with the same set of answers, which are specified before the questions
+ grid "Tell us how often do you cover these each day" do
+ a "1"
+ a "2"
+ a "3"
+ q "Head", :pick => :one
+ q "Knees", :pick => :one
+ q "Toes", :pick => :one
+ end
+
+ # "grid" is a shortcut for group with :display_type => :grid
+ # The answers will be repeated every 10 rows, but long grids aren't recommended as they are generally tedious
+ grid "Tell us how you feel today day" do
+ a "-2"
+ a "-1"
+ a "0"
+ a "1"
+ a "2"
+ q "down|up" , :pick => :one
+ q "sad|happy", :pick => :one
+ q "limp|perky", :pick => :one
+ end
+
+ grid "For each of the car types checked, what type of options would you prefer?" do
+ a "Leather seats"
+ a "Shiny rims"
+ a "Subwoofer"
+ a "Sunroof"
+ a "Turbocharger"
+ q "SUV", :pick => :any
+ q "Sedan", :pick => :any
+ q "Coupe", :pick => :any
+ q "Convertible", :pick => :any
+ q "Minivan", :pick => :any
+ q "Crossover", :pick => :any
+ q "Wagon", :pick => :any
+ q "Other", :pick => :any
+ end
+
+ q "Please rank the following foods based on how much you like them"
+ a "|pizza", :integer
+ a "|salad", :integer
+ a "|sushi", :integer
+ a "|ice cream", :integer
+ a "|breakfast ceral", :integer
+
+ # :other, :string allows someone to specify both the "other" and some other information
+ q "Choose your favorite utensils and enter frequency of use (daily, weekly, monthly, etc...)", :pick => :any
+ a "spoon", :string
+ a "fork", :string
+ a "knife", :string
+ a :other, :string
+
+ q_car "Do you own a car?", :pick => :one
+ a_y "Yes"
+ a_n "No"
+
+ # Repeaters allow multiple responses to a question or set of questions
+ repeater "Tell us about the cars you own" do
+ dependency :rule => "A"
+ condition_A :q_car, "==", :a_y
+ q "Make", :pick => :one, :display_type => :dropdown
+ a "Toyota"
+ a "Ford"
+ a "GMChevy"
+ a "Ferrari"
+ a "Tesla"
+ a "Honda"
+ a "Other weak brand"
+ q "Model"
+ a :string
+ q "Year"
+ a :string
+ end
+
+ repeater "Tell us the name and age of your siblings" do
+ q "Sibling"
+ a "Name", :string
+ a "Age|years", :integer
+ end
+
+ end
+end
View
10 surveys/quiz.rb
@@ -0,0 +1,10 @@
+survey "Favorites" do
+ section "Foods" do
+ # In a quiz, both the questions and the answers need to have reference identifiers
+ # Here, the question has reference_identifier: "1", and the answers: "oint", "tweet", and "moo"
+ question_1 "What is the best meat?", :pick => :one, :correct => "oink"
+ a_oink "bacon"
+ a_tweet "chicken"
+ a_moo "beef"
+ end
+end
View
BIN  vendor/assets/images/surveyor/next.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  vendor/assets/images/surveyor/prev.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
1,277 vendor/assets/javascripts/surveyor/jquery-ui-timepicker-addon.js
@@ -0,0 +1,1277 @@
+/*
+* jQuery timepicker addon
+* By: Trent Richardson [http://trentrichardson.com]
+* Version 0.9.7
+* Last Modified: 10/02/2011
+*
+* Copyright 2011 Trent Richardson
+* Dual licensed under the MIT and GPL licenses.
+* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt
+* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt
+*
+* HERES THE CSS:
+* .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
+* .ui-timepicker-div dl { text-align: left; }
+* .ui-timepicker-div dl dt { height: 25px; }
+* .ui-timepicker-div dl dd { margin: -25px 10px 10px 65px; }
+* .ui-timepicker-div td { font-size: 90%; }
+* .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
+*/
+
+(function($) {
+
+$.extend($.ui, { timepicker: { version: "0.9.7" } });
+
+/* Time picker manager.
+ Use the singleton instance of this class, $.timepicker, to interact with the time picker.
+ Settings for (groups of) time pickers are maintained in an instance object,
+ allowing multiple different settings on the same page. */
+
+function Timepicker() {
+ this.regional = []; // Available regional settings, indexed by language code
+ this.regional[''] = { // Default regional settings
+ currentText: 'Now',
+ closeText: 'Done',
+ ampm: false,
+ amNames: ['AM', 'A'],
+ pmNames: ['PM', 'P'],
+ timeFormat: 'hh:mm tt',
+ timeSuffix: '',
+ timeOnlyTitle: 'Choose Time',
+ timeText: 'Time',
+ hourText: 'Hour',
+ minuteText: 'Minute',
+ secondText: 'Second',
+ millisecText: 'Millisecond',
+ timezoneText: 'Time Zone'
+ };
+ this._defaults = { // Global defaults for all the datetime picker instances
+ showButtonPanel: true,
+ timeOnly: false,
+ showHour: true,
+ showMinute: true,
+ showSecond: false,
+ showMillisec: false,
+ showTimezone: false,
+ showTime: true,
+ stepHour: 0.05,
+ stepMinute: 0.05,
+ stepSecond: 0.05,
+ stepMillisec: 0.5,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ millisec: 0,
+ timezone: '+0000',
+ hourMin: 0,
+ minuteMin: 0,
+ secondMin: 0,
+ millisecMin: 0,
+ hourMax: 23,
+ minuteMax: 59,
+ secondMax: 59,
+ millisecMax: 999,
+ minDateTime: null,
+ maxDateTime: null,
+ onSelect: null,
+ hourGrid: 0,
+ minuteGrid: 0,
+ secondGrid: 0,
+ millisecGrid: 0,
+ alwaysSetTime: true,
+ separator: ' ',
+ altFieldTimeOnly: true,
+ showTimepicker: true,
+ timezoneIso8609: false,
+ timezoneList: null
+ };
+ $.extend(this._defaults, this.regional['']);
+}
+
+$.extend(Timepicker.prototype, {
+ $input: null,
+ $altInput: null,
+ $timeObj: null,
+ inst: null,
+ hour_slider: null,
+ minute_slider: null,
+ second_slider: null,
+ millisec_slider: null,
+ timezone_select: null,
+ hour: 0,
+ minute: 0,
+ second: 0,
+ millisec: 0,
+ timezone: '+0000',
+ hourMinOriginal: null,
+ minuteMinOriginal: null,
+ secondMinOriginal: null,
+ millisecMinOriginal: null,
+ hourMaxOriginal: null,
+ minuteMaxOriginal: null,
+ secondMaxOriginal: null,
+ millisecMaxOriginal: null,
+ ampm: '',
+ formattedDate: '',
+ formattedTime: '',
+ formattedDateTime: '',
+ timezoneList: null,
+
+ /* Override the default settings for all instances of the time picker.
+ @param settings object - the new settings to use as defaults (anonymous object)
+ @return the manager object */
+ setDefaults: function(settings) {
+ extendRemove(this._defaults, settings || {});
+ return this;
+ },
+
+ //########################################################################
+ // Create a new Timepicker instance
+ //########################################################################
+ _newInst: function($input, o) {
+ var tp_inst = new Timepicker(),
+ inlineSettings = {};
+
+ for (var attrName in this._defaults) {
+ var attrValue = $input.attr('time:' + attrName);
+ if (attrValue) {
+ try {
+ inlineSettings[attrName] = eval(attrValue);
+ } catch (err) {
+ inlineSettings[attrName] = attrValue;
+ }
+ }
+ }
+ tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, {
+ beforeShow: function(input, dp_inst) {
+ if ($.isFunction(o.beforeShow))
+ o.beforeShow(input, dp_inst, tp_inst);
+ },
+ onChangeMonthYear: function(year, month, dp_inst) {
+ // Update the time as well : this prevents the time from disappearing from the $input field.
+ tp_inst._updateDateTime(dp_inst);
+ if ($.isFunction(o.onChangeMonthYear))
+ o.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst);
+ },
+ onClose: function(dateText, dp_inst) {
+ if (tp_inst.timeDefined === true && $input.val() != '')
+ tp_inst._updateDateTime(dp_inst);
+ if ($.isFunction(o.onClose))
+ o.onClose.call($input[0], dateText, dp_inst, tp_inst);
+ },
+ timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker');
+ });
+ tp_inst.amNames = $.map(tp_inst._defaults.amNames, function(val) { return val.toUpperCase() });
+ tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function(val) { return val.toUpperCase() });
+
+ if (tp_inst._defaults.timezoneList === null) {
+ var timezoneList = [];
+ for (var i = -11; i <= 12; i++)
+ timezoneList.push((i >= 0 ? '+' : '-') + ('0' + Math.abs(i).toString()).slice(-2) + '00');
+ if (tp_inst._defaults.timezoneIso8609)
+ timezoneList = $.map(timezoneList, function(val) {
+ return val == '+0000' ? 'Z' : (val.substring(0, 3) + ':' + val.substring(3));
+ });
+ tp_inst._defaults.timezoneList = timezoneList;
+ }
+
+ tp_inst.hour = tp_inst._defaults.hour;
+ tp_inst.minute = tp_inst._defaults.minute;
+ tp_inst.second = tp_inst._defaults.second;
+ tp_inst.millisec = tp_inst._defaults.millisec;
+ tp_inst.ampm = '';
+ tp_inst.$input = $input;
+
+ if (o.altField)
+ tp_inst.$altInput = $(o.altField)
+ .css({ cursor: 'pointer' })
+ .focus(function(){ $input.trigger("focus"); });
+
+ if(tp_inst._defaults.minDate==0 || tp_inst._defaults.minDateTime==0)
+ {
+ tp_inst._defaults.minDate=new Date();
+ }
+ if(tp_inst._defaults.maxDate==0 || tp_inst._defaults.maxDateTime==0)
+ {
+ tp_inst._defaults.maxDate=new Date();
+ }
+
+ // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime..
+ if(tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date)
+ tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime());
+ if(tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date)
+ tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime());
+ if(tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date)
+ tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime());
+ if(tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date)
+ tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime());
+ return tp_inst;
+ },
+
+ //########################################################################
+ // add our sliders to the calendar
+ //########################################################################
+ _addTimePicker: function(dp_inst) {
+ var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ?
+ this.$input.val() + ' ' + this.$altInput.val() :
+ this.$input.val();
+
+ this.timeDefined = this._parseTime(currDT);
+ this._limitMinMaxDateTime(dp_inst, false);
+ this._injectTimePicker();
+ },
+
+ //########################################################################
+ // parse the time string from input value or _setTime
+ //########################################################################
+ _parseTime: function(timeString, withDate) {
+ var regstr = this._defaults.timeFormat.toString()
+ .replace(/h{1,2}/ig, '(\\d?\\d)')
+ .replace(/m{1,2}/ig, '(\\d?\\d)')
+ .replace(/s{1,2}/ig, '(\\d?\\d)')
+ .replace(/l{1}/ig, '(\\d?\\d?\\d)')
+ .replace(/t{1,2}/ig, this._getPatternAmpm())
+ .replace(/z{1}/ig, '(z|[-+]\\d\\d:?\\d\\d)?')
+ .replace(/\s/g, '\\s?') + this._defaults.timeSuffix + '$',
+ order = this._getFormatPositions(),
+ ampm = '',
+ treg;
+
+ if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]);
+
+ if (withDate || !this._defaults.timeOnly) {
+ // the time should come after x number of characters and a space.
+ // x = at least the length of text specified by the date format
+ var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat');
+ // escape special regex characters in the seperator
+ var specials = new RegExp("[.*+?|()\\[\\]{}\\\\]", "g");
+ regstr = '.{' + dp_dateFormat.length + ',}' + this._defaults.separator.replace(specials, "\\$&") + regstr;
+ }
+
+ treg = timeString.match(new RegExp(regstr, 'i'));
+
+ if (treg) {
+ if (order.t !== -1) {
+ if (treg[order.t] === undefined || treg[order.t].length === 0) {
+ ampm = '';
+ this.ampm = '';
+ } else {
+ ampm = $.inArray(treg[order.t].toUpperCase(), this.amNames) !== -1 ? 'AM' : 'PM';
+ this.ampm = this._defaults[ampm == 'AM' ? 'amNames' : 'pmNames'][0];
+ }
+ }
+
+ if (order.h !== -1) {
+ if (ampm == 'AM' && treg[order.h] == '12')
+ this.hour = 0; // 12am = 0 hour
+ else if (ampm == 'PM' && treg[order.h] != '12')
+ this.hour = (parseFloat(treg[order.h]) + 12).toFixed(0); // 12pm = 12 hour, any other pm = hour + 12
+ else this.hour = Number(treg[order.h]);
+ }
+
+ if (order.m !== -1) this.minute = Number(treg[order.m]);
+ if (order.s !== -1) this.second = Number(treg[order.s]);
+ if (order.l !== -1) this.millisec = Number(treg[order.l]);
+ if (order.z !== -1 && treg[order.z] !== undefined) {
+ var tz = treg[order.z].toUpperCase();
+ switch (tz.length) {
+ case 1: // Z
+ tz = this._defaults.timezoneIso8609 ? 'Z' : '+0000';
+ break;
+ case 5: // +hhmm
+ if (this._defaults.timezoneIso8609)
+ tz = tz.substring(1) == '0000'
+ ? 'Z'
+ : tz.substring(0, 3) + ':' + tz.substring(3);
+ break;
+ case 6: // +hh:mm
+ if (!this._defaults.timezoneIso8609)
+ tz = tz == 'Z' || tz.substring(1) == '00:00'
+ ? '+0000'
+ : tz.replace(/:/, '');
+ else if (tz.substring(1) == '00:00')
+ tz = 'Z';
+ break;
+ }
+ this.timezone = tz;
+ }
+
+ return true;
+
+ }
+ return false;
+ },
+
+ //########################################################################
+ // pattern for standard and localized AM/PM markers
+ //########################################################################
+ _getPatternAmpm: function() {
+ var markers = [];
+ o = this._defaults;
+ if (o.amNames)
+ $.merge(markers, o.amNames);
+ if (o.pmNames)
+ $.merge(markers, o.pmNames);
+ markers = $.map(markers, function(val) { return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&') });
+ return '(' + markers.join('|') + ')?';
+ },
+
+ //########################################################################
+ // figure out position of time elements.. cause js cant do named captures
+ //########################################################################
+ _getFormatPositions: function() {
+ var finds = this._defaults.timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|t{1,2}|z)/g),
+ orders = { h: -1, m: -1, s: -1, l: -1, t: -1, z: -1 };
+
+ if (finds)
+ for (var i = 0; i < finds.length; i++)
+ if (orders[finds[i].toString().charAt(0)] == -1)
+ orders[finds[i].toString().charAt(0)] = i + 1;
+
+ return orders;
+ },
+
+ //########################################################################
+ // generate and inject html for timepicker into ui datepicker
+ //########################################################################
+ _injectTimePicker: function() {
+ var $dp = this.inst.dpDiv,
+ o = this._defaults,
+ tp_inst = this,
+ // Added by Peter Medeiros:
+ // - Figure out what the hour/minute/second max should be based on the step values.
+ // - Example: if stepMinute is 15, then minMax is 45.
+ hourMax = (o.hourMax - ((o.hourMax - o.hourMin) % o.stepHour)).toFixed(0),
+ minMax = (o.minuteMax - ((o.minuteMax - o.minuteMin) % o.stepMinute)).toFixed(0),
+ secMax = (o.secondMax - ((o.secondMax - o.secondMin) % o.stepSecond)).toFixed(0),
+ millisecMax = (o.millisecMax - ((o.millisecMax - o.millisecMin) % o.stepMillisec)).toFixed(0),
+ dp_id = this.inst.id.toString().replace(/([^A-Za-z0-9_])/g, '');
+
+ // Prevent displaying twice
+ //if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0) {
+ if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0 && o.showTimepicker) {
+ var noDisplay = ' style="display:none;"',
+ html = '<div class="ui-timepicker-div" id="ui-timepicker-div-' + dp_id + '"><dl>' +
+ '<dt class="ui_tpicker_time_label" id="ui_tpicker_time_label_' + dp_id + '"' +
+ ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' +
+ '<dd class="ui_tpicker_time" id="ui_tpicker_time_' + dp_id + '"' +
+ ((o.showTime) ? '' : noDisplay) + '></dd>' +
+ '<dt class="ui_tpicker_hour_label" id="ui_tpicker_hour_label_' + dp_id + '"' +
+ ((o.showHour) ? '' : noDisplay) + '>' + o.hourText + '</dt>',
+ hourGridSize = 0,
+ minuteGridSize = 0,
+ secondGridSize = 0,
+ millisecGridSize = 0,
+ size;
+
+ // Hours
+ if (o.showHour && o.hourGrid > 0) {
+ html += '<dd class="ui_tpicker_hour">' +
+ '<div id="ui_tpicker_hour_' + dp_id + '"' + ((o.showHour) ? '' : noDisplay) + '></div>' +
+ '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
+
+ for (var h = o.hourMin; h <= hourMax; h += parseInt(o.hourGrid,10)) {
+ hourGridSize++;
+ var tmph = (o.ampm && h > 12) ? h-12 : h;
+ if (tmph < 10) tmph = '0' + tmph;
+ if (o.ampm) {
+ if (h == 0) tmph = 12 +'a';
+ else if (h < 12) tmph += 'a';
+ else tmph += 'p';
+ }
+ html += '<td>' + tmph + '</td>';
+ }
+
+ html += '</tr></table></div>' +
+ '</dd>';
+ } else html += '<dd class="ui_tpicker_hour" id="ui_tpicker_hour_' + dp_id + '"' +
+ ((o.showHour) ? '' : noDisplay) + '></dd>';
+
+ html += '<dt class="ui_tpicker_minute_label" id="ui_tpicker_minute_label_' + dp_id + '"' +
+ ((o.showMinute) ? '' : noDisplay) + '>' + o.minuteText + '</dt>';
+
+ // Minutes
+ if (o.showMinute && o.minuteGrid > 0) {
+ html += '<dd class="ui_tpicker_minute ui_tpicker_minute_' + o.minuteGrid + '">' +
+ '<div id="ui_tpicker_minute_' + dp_id + '"' +
+ ((o.showMinute) ? '' : noDisplay) + '></div>' +
+ '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';
+
+ for (var m = o.minuteMin; m <= minMax; m += parseInt(o.minuteGrid,10)) {
+ minuteGridSize++;
+ html += '<td>' + ((m < 10) ? '0' : '') + m + '</td>';
+ }
+
+ html += '</tr></table></div>' +
+ '</dd>';
+ } else html += '<dd class="ui_tpicker_minute" id="ui_tpicker_minute_' + dp_id + '"' +
+ ((o.showMinute) ? '' : noDisplay) + '></dd>';
+
+ // Seconds
+ html += '<dt class="ui_tpicker_second_label" id="ui_tpicker_second_label_' + dp_id + '"' +
+ ((o.showSecond) ? '' : noDisplay) + '>' + o.secondText + '</dt>';
+
+ if (o.showSecond && o.secondGrid > 0) {
+ html += '<dd class="ui_tpicker_second ui_tpicker_second_' + o.secondGrid + '">' +
+ '<div id="ui_tpicker_second_' + dp_id + '"' +
+ ((o.showSecond) ? '' : noDisplay) + '></div>' +
+ '<div style="padding-left: 1px"><table><tr>';
+
+ for (var s = o.secondMin; s <= secMax; s += parseInt(o.secondGrid,10)) {
+ secondGridSize++;
+ html += '<td>' + ((s < 10) ? '0' : '') + s + '</td>';
+ }
+
+ html += '</tr></table></div>' +
+ '</dd>';
+ } else html += '<dd class="ui_tpicker_second" id="ui_tpicker_second_' + dp_id + '"' +
+ ((o.showSecond) ? '' : noDisplay) + '></dd>';
+
+ // Milliseconds
+ html += '<dt class="ui_tpicker_millisec_label" id="ui_tpicker_millisec_label_' + dp_id + '"' +
+ ((o.showMillisec) ? '' : noDisplay) + '>' + o.millisecText + '</dt>';
+
+ if (o.showMillisec && o.millisecGrid > 0) {
+ html += '<dd class="ui_tpicker_millisec ui_tpicker_millisec_' + o.millisecGrid + '">' +
+ '<div id="ui_tpicker_millisec_' + dp_id + '"' +
+ ((o.showMillisec) ? '' : noDisplay) + '></div>' +
+ '<div style="padding-left: 1px"><table><tr>';
+
+ for (var l = o.millisecMin; l <= millisecMax; l += parseInt(o.millisecGrid,10)) {
+ millisecGridSize++;
+ html += '<td>' + ((l < 10) ? '0' : '') + s + '</td>';
+ }
+
+ html += '</tr></table></div>' +
+ '</dd>';
+ } else html += '<dd class="ui_tpicker_millisec" id="ui_tpicker_millisec_' + dp_id + '"' +
+ ((o.showMillisec) ? '' : noDisplay) + '></dd>';
+
+ // Timezone
+ html += '<dt class="ui_tpicker_timezone_label" id="ui_tpicker_timezone_label_' + dp_id + '"' +
+ ((o.showTimezone) ? '' : noDisplay) + '>' + o.timezoneText + '</dt>';
+ html += '<dd class="ui_tpicker_timezone" id="ui_tpicker_timezone_' + dp_id + '"' +
+ ((o.showTimezone) ? '' : noDisplay) + '></dd>';
+
+ html += '</dl></div>';
+ $tp = $(html);
+
+ // if we only want time picker...
+ if (o.timeOnly === true) {
+ $tp.prepend(
+ '<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' +
+ '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' +
+ '</div>');
+ $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide();
+ }
+
+ this.hour_slider = $tp.find('#ui_tpicker_hour_'+ dp_id).slider({
+ orientation: "horizontal",
+ value: this.hour,
+ min: o.hourMin,
+ max: hourMax,
+ step: o.stepHour,
+ slide: function(event, ui) {
+ tp_inst.hour_slider.slider( "option", "value", ui.value);
+ tp_inst._onTimeChange();
+ }
+ });
+
+ // Updated by Peter Medeiros:
+ // - Pass in Event and UI instance into slide function
+ this.minute_slider = $tp.find('#ui_tpicker_minute_'+ dp_id).slider({
+ orientation: "horizontal",
+ value: this.minute,
+ min: o.minuteMin,
+ max: minMax,
+ step: o.stepMinute,
+ slide: function(event, ui) {
+ // update the global minute slider instance value with the current slider value
+ tp_inst.minute_slider.slider( "option", "value", ui.value);
+ tp_inst._onTimeChange();
+ }
+ });
+
+ this.second_slider = $tp.find('#ui_tpicker_second_'+ dp_id).slider({
+ orientation: "horizontal",
+ value: this.second,
+ min: o.secondMin,
+ max: secMax,
+ step: o.stepSecond,
+ slide: function(event, ui) {
+ tp_inst.second_slider.slider( "option", "value", ui.value);
+ tp_inst._onTimeChange();
+ }
+ });
+
+ this.millisec_slider = $tp.find('#ui_tpicker_millisec_'+ dp_id).slider({
+ orientation: "horizontal",
+ value: this.millisec,
+ min: o.millisecMin,
+ max: millisecMax,
+ step: o.stepMillisec,
+ slide: function(event, ui) {
+ tp_inst.millisec_slider.slider( "option", "value", ui.value);
+ tp_inst._onTimeChange();
+ }
+ });
+
+ this.timezone_select = $tp.find('#ui_tpicker_timezone_'+ dp_id).append('<select></select>').find("select");
+ $.fn.append.apply(this.timezone_select,
+ $.map(o.timezoneList, function(val, idx) {
+ return $("<option />")
+ .val(typeof val == "object" ? val.value : val)
+ .text(typeof val == "object" ? val.label : val);
+ })
+ );
+ this.timezone_select.val((typeof this.timezone != "undefined" && this.timezone != null && this.timezone != "") ? this.timezone : o.timezone);
+ this.timezone_select.change(function() {
+ tp_inst._onTimeChange();
+ });
+
+ // Add grid functionality
+ if (o.showHour && o.hourGrid > 0) {
+ size = 100 * hourGridSize * o.hourGrid / (hourMax - o.hourMin);
+
+ $tp.find(".ui_tpicker_hour table").css({
+ width: size + "%",
+ marginLeft: (size / (-2 * hourGridSize)) + "%",
+ borderCollapse: 'collapse'
+ }).find("td").each( function(index) {
+ $(this).click(function() {
+ var h = $(this).html();
+ if(o.ampm) {
+ var ap = h.substring(2).toLowerCase(),
+ aph = parseInt(h.substring(0,2), 10);
+ if (ap == 'a') {
+ if (aph == 12) h = 0;
+ else h = aph;
+ } else if (aph == 12) h = 12;
+ else h = aph + 12;
+ }
+ tp_inst.hour_slider.slider("option", "value", h);
+ tp_inst._onTimeChange();
+ tp_inst._onSelectHandler();
+ }).css({
+ cursor: 'pointer',
+ width: (100 / hourGridSize) + '%',
+ textAlign: 'center',
+ overflow: 'hidden'
+ });
+ });
+ }
+
+ if (o.showMinute && o.minuteGrid > 0) {
+ size = 100 * minuteGridSize * o.minuteGrid / (minMax - o.minuteMin);
+ $tp.find(".ui_tpicker_minute table").css({
+ width: size + "%",
+ marginLeft: (size / (-2 * minuteGridSize)) + "%",
+ borderCollapse: 'collapse'
+ }).find("td").each(function(index) {
+ $(this).click(function() {
+ tp_inst.minute_slider.slider("option", "value", $(this).html());
+ tp_inst._onTimeChange();
+ tp_inst._onSelectHandler();
+ }).css({
+ cursor: 'pointer',
+ width: (100 / minuteGridSize) + '%',
+ textAlign: 'center',
+ overflow: 'hidden'
+ });
+ });
+ }
+
+ if (o.showSecond && o.secondGrid > 0) {
+ $tp.find(".ui_tpicker_second table").css({
+ width: size + "%",
+ marginLeft: (size / (-2 * secondGridSize)) + "%",
+ borderCollapse: 'collapse'
+ }).find("td").each(function(index) {
+ $(this).click(function() {
+ tp_inst.second_slider.slider("option", "value", $(this).html());
+ tp_inst._onTimeChange();
+ tp_inst._onSelectHandler();
+ }).css({
+ cursor: 'pointer',
+ width: (100 / secondGridSize) + '%',
+ textAlign: 'center',
+ overflow: 'hidden'
+ });
+ });
+ }
+
+ if (o.showMillisec && o.millisecGrid > 0) {
+ $tp.find(".ui_tpicker_millisec table").css({
+ width: size + "%",
+ marginLeft: (size / (-2 * millisecGridSize)) + "%",
+ borderCollapse: 'collapse'
+ }).find("td").each(function(index) {
+ $(this).click(function() {
+ tp_inst.millisec_slider.slider("option", "value", $(this).html());
+ tp_inst._onTimeChange();
+ tp_inst._onSelectHandler();
+ }).css({
+ cursor: 'pointer',
+ width: (100 / millisecGridSize) + '%',
+ textAlign: 'center',
+ overflow: 'hidden'
+ });
+ });
+ }
+
+ var $buttonPanel = $dp.find('.ui-datepicker-buttonpane');
+ if ($buttonPanel.length) $buttonPanel.before($tp);
+ else $dp.append($tp);
+
+ this.$timeObj = $tp.find('#ui_tpicker_time_'+ dp_id);
+
+ if (this.inst !== null) {
+ var timeDefined = this.timeDefined;
+ this._onTimeChange();
+ this.timeDefined = timeDefined;
+ }
+
+ //Emulate datepicker onSelect behavior. Call on slidestop.
+ var onSelectDelegate = function() {
+ tp_inst._onSelectHandler();
+ };
+ this.hour_slider.bind('slidestop',onSelectDelegate);
+ this.minute_slider.bind('slidestop',onSelectDelegate);
+ this.second_slider.bind('slidestop',onSelectDelegate);
+ this.millisec_slider.bind('slidestop',onSelectDelegate);
+ }
+ },
+
+ //########################################################################
+ // This function tries to limit the ability to go outside the
+ // min/max date range
+ //########################################################################
+ _limitMinMaxDateTime: function(dp_inst, adjustSliders){
+ var o = this._defaults,
+ dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay);
+
+ if(!this._defaults.showTimepicker) return; // No time so nothing to check here
+
+ if($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date){
+ var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'),
+ minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0);
+
+ if(this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null){
+ this.hourMinOriginal = o.hourMin;
+ this.minuteMinOriginal = o.minuteMin;
+ this.secondMinOriginal = o.secondMin;
+ this.millisecMinOriginal = o.millisecMin;
+ }
+
+ if(dp_inst.settings.timeOnly || minDateTimeDate.getTime() == dp_date.getTime()) {
+ this._defaults.hourMin = minDateTime.getHours();
+ if (this.hour <= this._defaults.hourMin) {
+ this.hour = this._defaults.hourMin;
+ this._defaults.minuteMin = minDateTime.getMinutes();
+ if (this.minute <= this._defaults.minuteMin) {
+ this.minute = this._defaults.minuteMin;
+ this._defaults.secondMin = minDateTime.getSeconds();
+ } else if (this.second <= this._defaults.secondMin){
+ this.second = this._defaults.secondMin;
+ this._defaults.millisecMin = minDateTime.getMilliseconds();
+ } else {
+ if(this.millisec < this._defaults.millisecMin)
+ this.millisec = this._defaults.millisecMin;
+ this._defaults.millisecMin = this.millisecMinOriginal;
+ }
+ } else {
+ this._defaults.minuteMin = this.minuteMinOriginal;
+ this._defaults.secondMin = this.secondMinOriginal;
+ this._defaults.millisecMin = this.millisecMinOriginal;
+ }
+ }else{
+ this._defaults.hourMin = this.hourMinOriginal;
+ this._defaults.minuteMin = this.minuteMinOriginal;
+ this._defaults.secondMin = this.secondMinOriginal;
+ this._defaults.millisecMin = this.millisecMinOriginal;
+ }
+ }
+
+ if($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date){
+ var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'),
+ maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0);
+
+ if(this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null){
+ this.hourMaxOriginal = o.hourMax;
+ this.minuteMaxOriginal = o.minuteMax;
+ this.secondMaxOriginal = o.secondMax;
+ this.millisecMaxOriginal = o.millisecMax;
+ }
+
+ if(dp_inst.settings.timeOnly || maxDateTimeDate.getTime() == dp_date.getTime()){
+ this._defaults.hourMax = maxDateTime.getHours();
+ if (this.hour >= this._defaults.hourMax) {
+ this.hour = this._defaults.hourMax;
+ this._defaults.minuteMax = maxDateTime.getMinutes();
+ if (this.minute >= this._defaults.minuteMax) {
+ this.minute = this._defaults.minuteMax;
+ this._defaults.secondMax = maxDateTime.getSeconds();
+ } else if (this.second >= this._defaults.secondMax) {
+ this.second = this._defaults.secondMax;
+ this._defaults.millisecMax = maxDateTime.getMilliseconds();
+ } else {
+ if(this.millisec > this._defaults.millisecMax) this.millisec = this._defaults.millisecMax;
+ this._defaults.millisecMax = this.millisecMaxOriginal;
+ }
+ } else {
+ this._defaults.minuteMax = this.minuteMaxOriginal;
+ this._defaults.secondMax = this.secondMaxOriginal;
+ this._defaults.millisecMax = this.millisecMaxOriginal;
+ }
+ }else{
+ this._defaults.hourMax = this.hourMaxOriginal;
+ this._defaults.minuteMax = this.minuteMaxOriginal;
+ this._defaults.secondMax = this.secondMaxOriginal;
+ this._defaults.millisecMax = this.millisecMaxOriginal;
+ }
+ }
+
+ if(adjustSliders !== undefined && adjustSliders === true){
+ var hourMax = (this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)).toFixed(0),
+ minMax = (this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)).toFixed(0),
+ secMax = (this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)).toFixed(0),
+ millisecMax = (this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)).toFixed(0);
+
+ if(this.hour_slider)
+ this.hour_slider.slider("option", { min: this._defaults.hourMin, max: hourMax }).slider('value', this.hour);
+ if(this.minute_slider)
+ this.minute_slider.slider("option", { min: this._defaults.minuteMin, max: minMax }).slider('value', this.minute);
+ if(this.second_slider)
+ this.second_slider.slider("option", { min: this._defaults.secondMin, max: secMax }).slider('value', this.second);
+ if(this.millisec_slider)
+ this.millisec_slider.slider("option", { min: this._defaults.millisecMin, max: millisecMax }).slider('value', this.millisec);
+ }
+
+ },
+
+
+ //########################################################################
+ // when a slider moves, set the internal time...
+ // on time change is also called when the time is updated in the text field
+ //########################################################################
+ _onTimeChange: function() {
+ var hour = (this.hour_slider) ? this.hour_slider.slider('value') : false,
+ minute = (this.minute_slider) ? this.minute_slider.slider('value') : false,
+ second = (this.second_slider) ? this.second_slider.slider('value') : false,
+ millisec = (this.millisec_slider) ? this.millisec_slider.slider('value') : false,
+ timezone = (this.timezone_select) ? this.timezone_select.val() : false,
+ o = this._defaults;
+
+ if (typeof(hour) == 'object') hour = false;
+ if (typeof(minute) == 'object') minute = false;
+ if (typeof(second) == 'object') second = false;
+ if (typeof(millisec) == 'object') millisec = false;
+ if (typeof(timezone) == 'object') timezone = false;
+
+ if (hour !== false) hour = parseInt(hour,10);
+ if (minute !== false) minute = parseInt(minute,10);
+ if (second !== false) second = parseInt(second,10);
+ if (millisec !== false) millisec = parseInt(millisec,10);
+
+ var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0];
+
+ // If the update was done in the input field, the input field should not be updated.
+ // If the update was done using the sliders, update the input field.
+ var hasChanged = (hour != this.hour || minute != this.minute
+ || second != this.second || millisec != this.millisec
+ || (this.ampm.length > 0
+ && (hour < 12) != ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1))
+ || timezone != this.timezone);
+
+ if (hasChanged) {
+
+ if (hour !== false)this.hour = hour;
+ if (minute !== false) this.minute = minute;
+ if (second !== false) this.second = second;
+ if (millisec !== false) this.millisec = millisec;
+ if (timezone !== false) this.timezone = timezone;
+
+ if (!this.inst) this.inst = $.datepicker._getInst(this.$input[0]);
+
+ this._limitMinMaxDateTime(this.inst, true);
+ }
+ if (o.ampm) this.ampm = ampm;
+
+ this._formatTime();
+ if (this.$timeObj) this.$timeObj.text(this.formattedTime + o.timeSuffix);
+ this.timeDefined = true;
+ if (hasChanged) this._updateDateTime();
+ },
+
+ //########################################################################
+ // call custom onSelect.
+ // bind to sliders slidestop, and grid click.
+ //########################################################################
+ _onSelectHandler: function() {
+ var onSelect = this._defaults.onSelect;
+ var inputEl = this.$input ? this.$input[0] : null;
+ if (onSelect && inputEl) {
+ onSelect.apply(inputEl, [this.formattedDateTime, this]);
+ }
+ },
+
+ //########################################################################
+ // format the time all pretty...
+ //########################################################################
+ _formatTime: function(time, format, ampm) {
+ if (ampm == undefined) ampm = this._defaults.ampm;
+ time = time || { hour: this.hour, minute: this.minute, second: this.second, millisec: this.millisec, ampm: this.ampm, timezone: this.timezone };
+ var tmptime = (format || this._defaults.timeFormat).toString();
+
+ var hour = parseInt(time.hour, 10);
+ if (ampm) {
+ if (!$.inArray(time.ampm.toUpperCase(), this.amNames) !== -1)
+ hour = hour % 12;
+ if (hour === 0)
+ hour = 12;
+ }
+ tmptime = tmptime.replace(/(?:hh?|mm?|ss?|[tT]{1,2}|[lz])/g, function(match) {
+ switch (match.toLowerCase()) {
+ case 'hh': return ('0' + hour).slice(-2);
+ case 'h': return hour;
+ case 'mm': return ('0' + time.minute).slice(-2);
+ case 'm': return time.minute;
+ case 'ss': return ('0' + time.second).slice(-2);
+ case 's': return time.second;
+ case 'l': return ('00' + time.millisec).slice(-3);
+ case 'z': return time.timezone;
+ case 't': case 'tt':
+ if (ampm) {
+ var _ampm = time.ampm;
+ if (match.length == 1)
+ _ampm = _ampm.charAt(0);
+ return match.charAt(0) == 'T' ? _ampm.toUpperCase() : _ampm.toLowerCase();
+ }
+ return '';
+ }
+ });
+
+ if (arguments.length) return tmptime;
+ else this.formattedTime = tmptime;
+ },
+
+ //########################################################################
+ // update our input with the new date time..
+ //########################################################################
+ _updateDateTime: function(dp_inst) {
+ dp_inst = this.inst || dp_inst,
+ dt = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay),
+ dateFmt = $.datepicker._get(dp_inst, 'dateFormat'),
+ formatCfg = $.datepicker._getFormatConfig(dp_inst),
+ timeAvailable = dt !== null && this.timeDefined;
+ this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg);
+ var formattedDateTime = this.formattedDate;
+ if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0))
+ return;
+
+ if (this._defaults.timeOnly === true) {
+ formattedDateTime = this.formattedTime;
+ } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) {
+ formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix;
+ }
+
+ this.formattedDateTime = formattedDateTime;
+
+ if(!this._defaults.showTimepicker) {
+ this.$input.val(this.formattedDate);
+ } else if (this.$altInput && this._defaults.altFieldTimeOnly === true) {
+ this.$altInput.val(this.formattedTime);
+ this.$input.val(this.formattedDate);
+ } else if(this.$altInput) {
+ this.$altInput.val(formattedDateTime);
+ this.$input.val(formattedDateTime);
+ } else {
+ this.$input.val(formattedDateTime);
+ }
+
+ this.$input.trigger("change");
+ }
+
+});
+
+$.fn.extend({
+ //########################################################################
+ // shorthand just to use timepicker..
+ //########################################################################
+ timepicker: function(o) {
+ o = o || {};