alchemy_cms 2.2.4 → 2.3.rc5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (215) hide show
  1. data/.gitignore +1 -1
  2. data/.travis.yml +3 -4
  3. data/Gemfile +1 -0
  4. data/README.md +10 -6
  5. data/alchemy_cms.gemspec +5 -2
  6. data/app/assets/images/alchemy/icons.png +0 -0
  7. data/app/assets/images/sassy-ie-overlay.png +0 -0
  8. data/app/assets/javascripts/alchemy/alchemy.base.js +50 -59
  9. data/app/assets/javascripts/alchemy/alchemy.buttons.js +14 -4
  10. data/app/assets/javascripts/alchemy/alchemy.datepicker.js +8 -2
  11. data/app/assets/javascripts/alchemy/alchemy.elements_window.js +11 -3
  12. data/app/assets/javascripts/alchemy/alchemy.gui.js.coffee +1 -1
  13. data/app/assets/javascripts/alchemy/alchemy.link_overlay.js.coffee +14 -1
  14. data/app/assets/javascripts/alchemy/alchemy.preview.js +1 -1
  15. data/app/assets/javascripts/alchemy/alchemy.preview_window.js +12 -4
  16. data/app/assets/javascripts/alchemy/alchemy.uploader.js +4 -1
  17. data/app/assets/javascripts/alchemy/alchemy.windows.js +18 -8
  18. data/app/assets/stylesheets/alchemy/_defaults.scss +84 -120
  19. data/app/assets/stylesheets/alchemy/alchemy.css +2 -2
  20. data/app/assets/stylesheets/alchemy/archive.css.scss +288 -0
  21. data/app/assets/stylesheets/alchemy/base.css.scss +95 -390
  22. data/app/assets/stylesheets/alchemy/dashboard.css.scss +4 -4
  23. data/app/assets/stylesheets/alchemy/elements.css.scss +83 -118
  24. data/app/assets/stylesheets/alchemy/flash.css.scss +1 -1
  25. data/app/assets/stylesheets/alchemy/form_elements.css.scss +528 -0
  26. data/app/assets/stylesheets/alchemy/frame.css.scss +13 -39
  27. data/app/assets/stylesheets/alchemy/icons.css.scss +217 -228
  28. data/app/assets/stylesheets/alchemy/jquery-ui.alchemy.css.scss +48 -50
  29. data/app/assets/stylesheets/alchemy/jquery.Jcrop.css.scss +1 -1
  30. data/app/assets/stylesheets/alchemy/login.css.scss +1 -5
  31. data/app/assets/stylesheets/alchemy/menubar.css.scss +19 -29
  32. data/app/assets/stylesheets/alchemy/pagination.css.scss +3 -4
  33. data/app/assets/stylesheets/alchemy/sitemap.css.scss +81 -81
  34. data/app/assets/stylesheets/alchemy/tables.css.scss +63 -57
  35. data/app/assets/stylesheets/alchemy/tinymce_dialog.css.scss +57 -57
  36. data/app/assets/stylesheets/alchemy/upload.css.scss +6 -6
  37. data/app/assets/stylesheets/tiny_mce/plugins/inlinepopups/skins/alchemy/window.css.scss +6 -10
  38. data/app/controllers/alchemy/admin/attachments_controller.rb +5 -4
  39. data/app/controllers/alchemy/admin/base_controller.rb +1 -9
  40. data/app/controllers/alchemy/admin/contents_controller.rb +4 -6
  41. data/app/controllers/alchemy/admin/elements_controller.rb +2 -2
  42. data/app/controllers/alchemy/admin/pages_controller.rb +2 -2
  43. data/app/controllers/alchemy/admin/pictures_controller.rb +74 -15
  44. data/app/controllers/alchemy/attachments_controller.rb +8 -2
  45. data/app/controllers/alchemy/base_controller.rb +47 -5
  46. data/app/controllers/alchemy/elements_controller.rb +1 -1
  47. data/app/controllers/alchemy/messages_controller.rb +12 -12
  48. data/app/controllers/alchemy/pages_controller.rb +5 -1
  49. data/app/controllers/alchemy/pictures_controller.rb +9 -4
  50. data/app/controllers/alchemy/user_sessions_controller.rb +2 -4
  51. data/app/helpers/alchemy/admin/base_helper.rb +98 -19
  52. data/app/helpers/alchemy/admin/contents_helper.rb +2 -2
  53. data/app/helpers/alchemy/admin/elements_helper.rb +2 -3
  54. data/app/helpers/alchemy/base_helper.rb +6 -5
  55. data/app/helpers/alchemy/elements_helper.rb +2 -2
  56. data/app/helpers/alchemy/essences_helper.rb +4 -5
  57. data/app/helpers/alchemy/pages_helper.rb +15 -79
  58. data/app/helpers/alchemy/url_helper.rb +67 -0
  59. data/app/mailers/alchemy/messages.rb +1 -1
  60. data/app/mailers/alchemy/notifications.rb +1 -1
  61. data/app/models/alchemy/attachment.rb +11 -2
  62. data/app/models/alchemy/cell.rb +20 -10
  63. data/app/models/alchemy/content.rb +4 -3
  64. data/app/models/alchemy/element.rb +170 -178
  65. data/app/models/alchemy/language/code.rb +4 -1
  66. data/app/models/alchemy/message.rb +19 -3
  67. data/app/models/alchemy/page.rb +45 -40
  68. data/app/models/alchemy/picture.rb +24 -2
  69. data/app/models/alchemy/user.rb +2 -3
  70. data/app/views/alchemy/admin/attachments/_archive_overlay.html.erb +12 -12
  71. data/app/views/alchemy/admin/attachments/_attachment.html.erb +1 -1
  72. data/app/views/alchemy/admin/attachments/create.js.erb +1 -0
  73. data/app/views/alchemy/admin/attachments/edit.html.erb +9 -3
  74. data/app/views/alchemy/admin/attachments/index.html.erb +3 -2
  75. data/app/views/alchemy/admin/contents/_missing.html.erb +1 -1
  76. data/app/views/alchemy/admin/contents/create.js.erb +54 -0
  77. data/app/views/alchemy/admin/contents/new.html.erb +9 -4
  78. data/app/views/alchemy/admin/elements/{_add_content.html.erb → _add_picture.html.erb} +4 -4
  79. data/app/views/alchemy/admin/elements/_elements_select.html.erb +2 -1
  80. data/app/views/alchemy/admin/elements/_new_element_form.html.erb +1 -1
  81. data/app/views/alchemy/admin/elements/{_picture_editor.html.erb → _picture_gallery_editor.html.erb} +7 -11
  82. data/app/views/alchemy/admin/elements/fold.js.erb +46 -0
  83. data/app/views/alchemy/admin/elements/index.html.erb +24 -24
  84. data/app/views/alchemy/admin/elements/list.js.erb +11 -9
  85. data/app/views/alchemy/admin/essence_files/assign.js.erb +3 -1
  86. data/app/views/alchemy/admin/essence_pictures/destroy.js.erb +28 -0
  87. data/app/views/alchemy/admin/pages/_contactform_links.html.erb +8 -6
  88. data/app/views/alchemy/admin/pages/_external_link.html.erb +11 -9
  89. data/app/views/alchemy/admin/pages/_file_link.html.erb +10 -8
  90. data/app/views/alchemy/admin/pages/_internal_link.html.erb +14 -10
  91. data/app/views/alchemy/admin/pages/_new_page_form.html.erb +1 -1
  92. data/app/views/alchemy/admin/pages/_page.html.erb +1 -1
  93. data/app/views/alchemy/admin/pages/_page_for_links.html.erb +32 -21
  94. data/app/views/alchemy/admin/pages/configure.html.erb +2 -2
  95. data/app/views/alchemy/admin/pages/configure_external.html.erb +13 -13
  96. data/app/views/alchemy/admin/pages/edit.html.erb +2 -2
  97. data/app/views/alchemy/admin/pages/index.html.erb +26 -24
  98. data/app/views/alchemy/admin/pages/link.html.erb +2 -5
  99. data/app/views/alchemy/admin/partials/_upload_form.html.erb +28 -12
  100. data/app/views/alchemy/admin/pictures/_archive.html.erb +54 -0
  101. data/app/views/alchemy/admin/pictures/_archive_overlay.html.erb +10 -7
  102. data/app/views/alchemy/admin/pictures/_filter_and_size_bar.html.erb +21 -22
  103. data/app/views/alchemy/admin/pictures/_filter_bar.html.erb +31 -0
  104. data/app/views/alchemy/admin/pictures/_overlay_picture_list.html.erb +9 -0
  105. data/app/views/alchemy/admin/pictures/_picture.html.erb +36 -6
  106. data/app/views/alchemy/admin/pictures/_tag_list.html.erb +27 -0
  107. data/app/views/alchemy/admin/pictures/archive_overlay.js.erb +3 -1
  108. data/app/views/alchemy/admin/pictures/create.js.erb +4 -5
  109. data/app/views/alchemy/admin/pictures/edit.html.erb +26 -0
  110. data/app/views/alchemy/admin/pictures/edit_multiple.html.erb +39 -0
  111. data/app/views/alchemy/admin/pictures/index.html.erb +81 -70
  112. data/app/views/alchemy/admin/pictures/index.js.erb +3 -0
  113. data/app/views/alchemy/admin/pictures/new.html.erb +1 -0
  114. data/app/views/alchemy/admin/resources/index.html.erb +3 -1
  115. data/app/views/alchemy/admin/users/_table.html.erb +1 -1
  116. data/app/views/alchemy/admin/users/index.html.erb +27 -23
  117. data/app/views/alchemy/elements/_article_editor.html.erb +7 -2
  118. data/app/views/alchemy/elements/_bild_editor.html.erb +1 -1
  119. data/app/views/alchemy/elements/_bild_text_editor.html.erb +6 -1
  120. data/app/views/alchemy/elements/_bild_text_view.html.erb +3 -3
  121. data/app/views/alchemy/elements/_image_mosaic_editor.html.erb +1 -1
  122. data/app/views/alchemy/elements/_image_mosaic_view.html.erb +2 -2
  123. data/app/views/alchemy/elements/_intro_image_text_view.html.erb +4 -4
  124. data/app/views/alchemy/elements/_searchresult_editor.html.erb +4 -1
  125. data/app/views/alchemy/essences/_essence_boolean_editor.html.erb +1 -1
  126. data/app/views/alchemy/essences/_essence_picture_editor.html.erb +2 -3
  127. data/app/views/alchemy/essences/_essence_picture_tools.html.erb +1 -1
  128. data/app/views/alchemy/search/_form.html.erb +8 -0
  129. data/app/views/alchemy/search/_result.html.erb +3 -2
  130. data/app/views/alchemy/search/_results.html.erb +28 -0
  131. data/app/views/alchemy/user_sessions/leave.html.erb +4 -4
  132. data/app/views/alchemy/user_sessions/login.html.erb +1 -2
  133. data/app/views/layouts/alchemy/admin.html.erb +30 -10
  134. data/app/views/layouts/alchemy/login.html.erb +2 -39
  135. data/config/alchemy/elements.yml +1 -2
  136. data/config/alchemy/page_layouts.yml +8 -5
  137. data/config/authorization_rules.rb +27 -18
  138. data/config/initializers/localeapp.rb +9 -0
  139. data/config/locales/alchemy.de.yml +93 -56
  140. data/config/locales/alchemy.en.yml +73 -50
  141. data/config/routes.rb +3 -1
  142. data/db/migrate/20120704181529_add_upload_hash_to_alchemy_picture.rb +5 -0
  143. data/db/migrate/20120705214247_acts_as_taggable_on_migration.rb +28 -0
  144. data/db/migrate/20120728185830_add_cached_tag_list_to_alchemy_pictures.rb +5 -0
  145. data/db/migrate/20120831135441_set_alchemy_languages_country_code_default_to_empty_string.rb +9 -0
  146. data/lib/alchemy/capistrano.rb +2 -2
  147. data/lib/alchemy/essence.rb +14 -0
  148. data/lib/alchemy/page_layout.rb +0 -6
  149. data/lib/alchemy/resource.rb +9 -15
  150. data/lib/alchemy/upgrader.rb +18 -3
  151. data/lib/alchemy/version.rb +5 -1
  152. data/lib/alchemy_cms.rb +4 -1
  153. data/lib/rails/generators/alchemy/deploy_script/deploy_script_generator.rb +16 -6
  154. data/lib/rails/generators/alchemy/deploy_script/templates/deploy.rb.tt +17 -3
  155. data/lib/rails/generators/alchemy/elements/elements_generator.rb +6 -1
  156. data/lib/rails/generators/alchemy/elements/templates/editor.html.erb +10 -1
  157. data/lib/rails/generators/alchemy/elements/templates/view.html.erb +17 -18
  158. data/lib/rails/generators/alchemy/scaffold/files/pages.html.erb +4 -2
  159. data/lib/tasks/fleximage.rake +2 -2
  160. data/spec/controllers/admin/contents_controller_spec.rb +2 -2
  161. data/spec/controllers/admin/elements_controller_spec.rb +30 -1
  162. data/spec/controllers/admin/pages_controller_spec.rb +35 -18
  163. data/spec/controllers/admin/trash_controller_spec.rb +40 -16
  164. data/spec/controllers/attachments_controller_spec.rb +62 -0
  165. data/spec/controllers/base_controller_spec.rb +43 -42
  166. data/spec/controllers/elements_controller_spec.rb +30 -0
  167. data/spec/controllers/pages_controller_spec.rb +22 -5
  168. data/spec/controllers/pictures_controller_spec.rb +82 -0
  169. data/spec/dummy/app/models/event.rb +2 -1
  170. data/spec/dummy/config/database.yml +3 -2
  171. data/spec/dummy/db/schema.rb +51 -27
  172. data/spec/factories.rb +29 -8
  173. data/spec/helpers/admin/base_helper_spec.rb +134 -21
  174. data/spec/helpers/admin/contents_helper_spec.rb +2 -2
  175. data/spec/helpers/admin/elements_helper_spec.rb +17 -9
  176. data/spec/helpers/admin/essences_helper_spec.rb +7 -6
  177. data/spec/helpers/essences_helper_spec.rb +8 -7
  178. data/spec/helpers/pages_helper_spec.rb +208 -325
  179. data/spec/helpers/url_helper_spec.rb +171 -0
  180. data/spec/integration/admin/link_overlay_spec.rb +53 -0
  181. data/spec/integration/admin/modules_integration_spec.rb +22 -26
  182. data/spec/integration/admin/pages_controller_spec.rb +10 -19
  183. data/spec/integration/admin/picture_library_integration_spec.rb +52 -0
  184. data/spec/integration/admin/resources_integration_spec.rb +68 -75
  185. data/spec/integration/pages_controller_spec.rb +70 -61
  186. data/spec/integration/security_spec.rb +3 -5
  187. data/spec/integration/translation_integration_spec.rb +56 -0
  188. data/spec/libraries/essence_spec.rb +18 -0
  189. data/spec/libraries/resource_spec.rb +101 -79
  190. data/spec/libraries/resources_helper_spec.rb +3 -0
  191. data/spec/models/content_spec.rb +63 -60
  192. data/spec/models/element_spec.rb +203 -93
  193. data/spec/models/language_spec.rb +90 -65
  194. data/spec/models/page_layout_spec.rb +37 -0
  195. data/spec/models/page_spec.rb +181 -113
  196. data/spec/models/picture_spec.rb +73 -26
  197. data/spec/models/resource_spec.rb +52 -23
  198. data/spec/support/alchemy/specs_helpers.rb +2 -0
  199. data/spec/support/image.png +0 -0
  200. data/spec/{helpers/url_helpers_spec.rb → url_helpers_spec.rb} +0 -0
  201. data/vendor/assets/javascripts/jquery_plugins/jquery.selectBoxIt.js +1909 -0
  202. data/vendor/assets/javascripts/jquery_plugins/preloadCssImages.jQuery_v5.js +152 -0
  203. metadata +106 -33
  204. data/app/assets/stylesheets/alchemy/buttons.css.scss +0 -361
  205. data/app/assets/stylesheets/alchemy/jquery.sb.css.scss +0 -260
  206. data/app/views/alchemy/admin/contents/create.js.coffee +0 -49
  207. data/app/views/alchemy/admin/elements/fold.js.coffee +0 -37
  208. data/app/views/alchemy/admin/essence_pictures/destroy.js.coffee +0 -19
  209. data/app/views/alchemy/admin/pictures/_pictures_list.html.erb +0 -16
  210. data/app/views/alchemy/admin/pictures/update.js.erb +0 -3
  211. data/spec/dummy/config/locales/en.yml +0 -5
  212. data/spec/dummy/config/locales/fo.yml +0 -5
  213. data/spec/page_layout_spec.rb +0 -35
  214. data/vendor/assets/javascripts/jquery_plugins/jquery.in-place-edit.js +0 -172
  215. data/vendor/assets/javascripts/jquery_plugins/jquery.sb.min.js +0 -14
@@ -1,54 +1,101 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Alchemy::Picture do
3
+ module Alchemy
4
+ describe Picture do
4
5
 
5
- describe '#suffix' do
6
+ let :image_file do
7
+ File.new(File.expand_path('../../support/image.png', __FILE__))
8
+ end
6
9
 
7
- it "should return the suffix of original filename" do
8
- pic = stub_model(Alchemy::Picture, :image_filename => 'kitten.JPG')
9
- pic.suffix.should == "jpg"
10
+ it "is valid with valid attributes" do
11
+ picture = Picture.new(:image_file => image_file)
12
+ picture.should be_valid
10
13
  end
11
14
 
12
- context "image has no suffix" do
15
+ describe '#suffix' do
13
16
 
14
- before(:each) do
15
- @pic = stub_model(Alchemy::Picture, :image_filename => 'kitten')
17
+ it "should return the suffix of original filename" do
18
+ pic = stub_model(Picture, :image_filename => 'kitten.JPG')
19
+ pic.suffix.should == "jpg"
16
20
  end
17
21
 
18
- it "should return empty string" do
19
- @pic.suffix.should == ""
22
+ context "image has no suffix" do
23
+
24
+ before(:each) do
25
+ @pic = stub_model(Picture, :image_filename => 'kitten')
26
+ end
27
+
28
+ it "should return empty string" do
29
+ @pic.suffix.should == ""
30
+ end
31
+
20
32
  end
21
33
 
22
34
  end
23
35
 
24
- end
36
+ describe '#humanized_name' do
37
+
38
+ it "should return a humanized version of original filename" do
39
+ pic = stub_model(Picture, :image_filename => 'cute_kitten.JPG')
40
+ pic.humanized_name.should == "Cute kitten"
41
+ end
42
+
43
+ it "should not remove incidents of suffix from filename" do
44
+ pic = stub_model(Picture, :image_filename => 'cute_kitten_mo.jpgi.JPG')
45
+ pic.humanized_name.should == "Cute kitten mo.jpgi"
46
+ pic.humanized_name.should_not == "Cute kitten moi"
47
+ end
48
+
49
+ context "image has no suffix" do
50
+
51
+ before(:each) do
52
+ @pic = stub_model(Picture, :image_filename => 'cute_kitten')
53
+ @pic.stub!(:suffix).and_return("")
54
+ end
25
55
 
26
- describe '#humanized_name' do
56
+ it "should return humanized name" do
57
+ @pic.humanized_name.should == "Cute kitten"
58
+ end
59
+
60
+ end
27
61
 
28
- it "should return a humanized version of original filename" do
29
- pic = stub_model(Alchemy::Picture, :image_filename => 'cute_kitten.JPG')
30
- pic.humanized_name.should == "Cute kitten"
31
62
  end
32
63
 
33
- it "should not remove incidents of suffix from filename" do
34
- pic = stub_model(Alchemy::Picture, :image_filename => 'cute_kitten_mo.jpgi.JPG')
35
- pic.humanized_name.should == "Cute kitten mo.jpgi"
36
- pic.humanized_name.should_not == "Cute kitten moi"
64
+ describe '#self.last_upload' do
65
+
66
+ it "should return all pictures that have the same upload-hash as the most recent picture" do
67
+ other_upload = Picture.create!(:image_file => image_file, :upload_hash => '456')
68
+ same_upload = Picture.create!(:image_file => image_file, :upload_hash => '123')
69
+ most_recent = Picture.create!(:image_file => image_file, :upload_hash => '123')
70
+
71
+ Picture.last_upload.should include(most_recent)
72
+ Picture.last_upload.should include(same_upload)
73
+ Picture.last_upload.should_not include(other_upload)
74
+
75
+ [other_upload, same_upload, most_recent].each { |p| p.destroy }
76
+ end
77
+
37
78
  end
38
79
 
39
- context "image has no suffix" do
80
+ describe '#self.recent' do
40
81
 
41
- before(:each) do
42
- @pic = stub_model(Alchemy::Picture, :image_filename => 'cute_kitten')
43
- @pic.stub!(:suffix).and_return("")
82
+ before(:all) do
83
+ now = Time.now
84
+ @recent = Picture.create!(:image_file => image_file)
85
+ @old_picture = Picture.create!(:image_file => image_file)
86
+ @recent.update_column(:created_at, now-23.hours)
87
+ @old_picture.update_column(:created_at, now-10.days)
44
88
  end
45
89
 
46
- it "should return humanized name" do
47
- @pic.humanized_name.should == "Cute kitten"
90
+ it "should return all pictures that have been created in the last 24 hours" do
91
+ Picture.recent.should include(@recent)
92
+ end
93
+
94
+ it "should not return old pictures" do
95
+ Picture.recent.should_not include(@old_picture)
48
96
  end
49
97
 
50
98
  end
51
99
 
52
100
  end
53
-
54
101
  end
@@ -16,29 +16,18 @@ module Alchemy
16
16
  }
17
17
  end
18
18
 
19
- def resource_relations
20
- {
21
- "event" => {
22
- "location_id" => {
23
- "attr_method" => "location#name",
24
- "attr_type" => "string"
25
- }
26
- }
27
- }
28
- end
29
-
30
19
  let(:resource) { Resource.new("admin/events", module_definition) }
31
20
 
32
21
  describe "#initialize" do
33
- it "should set an instance variable wich holds the controller path" do
22
+ it "should set an instance variable that holds the controller path" do
34
23
  resource.instance_variable_get(:@controller_path).should == "admin/events"
35
24
  end
36
25
 
37
- it "should set an instance variable wich holds the module definition" do
26
+ it "should set an instance variable that holds the module definition" do
38
27
  resource.instance_variable_get(:@module_definition).should == module_definition
39
28
  end
40
29
 
41
- it "should set the standard rails database attributes to be skipped" do
30
+ it "should set the standard database attributes (rails defaults) to be skipped" do
42
31
  resource.skip_attributes.should == %W[id updated_at created_at creator_id updater_id]
43
32
  end
44
33
  end
@@ -62,7 +51,7 @@ module Alchemy
62
51
  end
63
52
 
64
53
  describe "#permission_scope" do
65
- it "should set an instance variable wich holds the permission scope for declarative authorization" do
54
+ it "should set an instance variable that holds the permission scope for declarative authorization" do
66
55
  resource.permission_scope
67
56
  resource.instance_variable_get(:@_permission).should == :admin_events
68
57
  end
@@ -75,23 +64,63 @@ module Alchemy
75
64
  end
76
65
 
77
66
  describe "#attributes" do
78
- it "should not return the to be skipped attributes" do
67
+ it "should not return the to-be-skipped attributes" do
79
68
  resource.class.const_get(:DEFAULT_SKIPPED_ATTRIBUTES).each do |skipped_attr|
80
- resource.attributes.detect{|a| a[:name] == skipped_attr }.should == nil
69
+ resource.attributes.detect { |a| a[:name] == skipped_attr }.should == nil
70
+ end
71
+ end
72
+
73
+ context "when skip_attributes is defined as class-method in the model" do
74
+ before do
75
+ Event.class_eval do
76
+ def self.skip_attributes
77
+ %W[hidden_name]
78
+ end
79
+ end
80
+ end
81
+ after do
82
+ Event.class_eval do
83
+ class << self
84
+ undef :skip_attributes
85
+ end
86
+ end
87
+ end
88
+
89
+ it "should not return the attributes returned by that method" do
90
+ resource.attributes.detect { |a| a[:name] == 'hidden_name' }.should be_nil
91
+ resource.attributes.detect { |a| a[:name] == 'name' }.should_not be_nil
81
92
  end
82
93
  end
83
94
 
84
- context "when resource relations defined in the config.yml" do
95
+ context "when resource_relations defined as class-method in the model" do
96
+ before do
97
+ Event.class_eval do
98
+ def self.resource_relations
99
+ {
100
+ :location_id => {:attr_method => "location#name", :attr_type => :string},
101
+ :organizer_id => {:attr_method => "organizer#name", :attr_type => :string}
102
+ }
103
+ end
104
+ end
105
+ end
106
+ after do
107
+ Event.class_eval do
108
+ class << self
109
+ undef :resource_relations
110
+ end
111
+ end
112
+ end
85
113
  it "should use the attribute location#name instead of location_id" do
86
- Config.stub(:get).with(:resource_relations).and_return(resource_relations)
87
- resource.attributes.detect{|a| a[:name] == "location#name" }.should == {:name=>"location#name", :type=> :string}
114
+ resource.attributes.detect { |a| a[:name] == "location#name" }.should == {:name => "location#name", :type => :string}
115
+ end
116
+ it "should use the attribute organizer#name instead of organizer_id" do
117
+ resource.attributes.detect { |a| a[:name] == "organizer#name" }.should == {:name => "organizer#name", :type => :string}
88
118
  end
89
119
  end
90
120
 
91
- context "when no resource relations defined in the config.yml" do
121
+ context "when resource_relation is not defined" do
92
122
  it "should use the attribute location_id" do
93
- Config.stub(:get).with(:resource_relations).and_return(nil)
94
- resource.attributes.detect{|a| a[:name] == "location_id" }.should == {:name=>"location_id", :type=> :integer}
123
+ resource.attributes.detect { |a| a[:name] == "location_id" }.should == {:name => "location_id", :type => :integer}
95
124
  end
96
125
  end
97
126
 
@@ -28,6 +28,8 @@ module Alchemy
28
28
  #
29
29
  def login_into_alchemy
30
30
  visit '/alchemy/admin/login'
31
+ # Giving phantomjs some time to start under ruby 1.8.7 on travis-ci
32
+ sleep(5) if ENV['CI'] && RUBY_VERSION == "1.8.7"
31
33
  fill_in('alchemy_user_session_login', :with => 'jdoe')
32
34
  fill_in('alchemy_user_session_password', :with => 's3cr3t')
33
35
  click_on('Login')
Binary file
@@ -0,0 +1,1909 @@
1
+ /* jquery Selectboxit - v0.9.0 - 2012-05-21
2
+ * http://www.gregfranko.com/jQuery.selectBoxIt.js/
3
+ * Copyright (c) 2012 Greg Franko; Licensed MIT */
4
+
5
+ // Immediately-Invoked Function Expression (IIFE) [Ben Alman Blog Post](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) that calls another IIFE that contains all of the plugin logic. I used this pattern so that anyone viewing this code would not have to scroll to the bottom of the page to view the local parameters that were passed to the main IIFE.
6
+
7
+ (function (selectBoxIt) {
8
+
9
+ //ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
10
+ "use strict";
11
+
12
+ // Calls the second IIFE and locally passes in the global jQuery, window, and document objects
13
+ selectBoxIt(jQuery, window, document);
14
+
15
+ }
16
+
17
+ // Locally passes in `jQuery`, the `window` object, the `document` object, and an `undefined` variable. The `jQuery`, `window` and `document` objects are passed in locally, to improve performance, since javascript first searches for a variable match within the local variables set before searching the global variables set. All of the global variables are also passed in locally to be minifier friendly. `undefined` can be passed in locally, because it is not a reserved word in JavaScript.
18
+
19
+ (function ($, window, document, undefined) {
20
+
21
+ // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
22
+ "use strict";
23
+
24
+ // Calling the jQueryUI Widget Factory Method
25
+ $.widget("selectBox.selectBoxIt", {
26
+
27
+ // Plugin version
28
+
29
+ version: "0.9.0",
30
+
31
+ // These options will be used as defaults
32
+ options: {
33
+
34
+ // **showEffect**: Accepts String: "none", "fadeIn", "show", "slideDown", or any of the jQueryUI show effects (i.e. "bounce")
35
+ showEffect: "none",
36
+
37
+ // **showEffectOptions**: Accepts an object literal. All of the available properties are based on the jqueryUI effect options
38
+ showEffectOptions: {},
39
+
40
+ // **showEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
41
+ showEffectSpeed: "medium",
42
+
43
+ // **hideEffect**: Accepts String: "none", "fadeOut", "hide", "slideUp", or any of the jQueryUI hide effects (i.e. "explode")
44
+ hideEffect: "none",
45
+
46
+ // **hideEffectOptions**: Accepts an object literal. All of the available properties are based on the jqueryUI effect options
47
+ hideEffectOptions: {},
48
+
49
+ // **hideEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
50
+ hideEffectSpeed: "medium",
51
+
52
+ // **showFirstOption**: Shows the first dropdown list option within the dropdown list options list
53
+ showFirstOption: true,
54
+
55
+ // **defaultText**: Overrides the text used by the dropdown list selected option to allow a user to specify custom text. Accepts a String.
56
+ defaultText: "",
57
+
58
+ // **defaultIcon**: Overrides the icon used by the dropdown list selected option to allow a user to specify a custom icon. Accepts a String (CSS class name(s)).
59
+ defaultIcon: "",
60
+
61
+ // **downArrowIcon**: Overrides the default down arrow used by the dropdown list to allow a user to specify a custom image. Accepts a String (CSS class name(s)).
62
+ downArrowIcon: ""
63
+
64
+ },
65
+
66
+ // _Create
67
+ // -------
68
+ // Sets the Plugin Instance variables and
69
+ // constructs the plugin. Only called once.
70
+ _create: function() {
71
+
72
+ // The original select box DOM element
73
+ this.originalElem = this.element[0];
74
+
75
+ // The original select box DOM element wrapped in a jQuery object
76
+ this.selectBox = this.element;
77
+
78
+ // All of the original select box options
79
+ this.selectItems = this.element.find("option");
80
+
81
+ // The first option in the original select box
82
+ this.firstSelectItem = this.element.find("option").slice(0, 1);
83
+
84
+ // The index of the currently selected dropdown list option
85
+ this.currentFocus = 0;
86
+
87
+ // Keeps track of which blur events will hide the dropdown list options
88
+ this.blur = true;
89
+
90
+ // The html document height
91
+ this.documentHeight = $(document).height();
92
+
93
+ // Array holding all of the original select box options text
94
+ this.textArray = [];
95
+
96
+ // Maintains search order in the `search` method
97
+ this.currentIndex = 0;
98
+
99
+ // Whether or not the dropdown list opens up or down (depending on how much room is on the page)
100
+ this.flipped = false;
101
+
102
+ // Creates the div elements that will become the dropdown
103
+ this._createDiv();
104
+
105
+ // Creates the ul element that will become the dropdown options list
106
+ this._createUnorderedList();
107
+
108
+ // Hides the original select box and adds the new plugin DOM elements to the page
109
+ this._replaceSelectBox();
110
+
111
+ // Adds event handlers to the new dropdown list
112
+ this._eventHandlers();
113
+
114
+ if(this.originalElem.disabled && this.disable) {
115
+
116
+ // Disables the dropdown list if the original dropdown list had the `disabled` attribute
117
+ this.disable();
118
+
119
+ }
120
+
121
+ if(this._ariaAccessibility) {
122
+
123
+ // Adds ARIA accessibillity tags to the dropdown list
124
+ this._ariaAccessibility();
125
+
126
+ }
127
+
128
+ // Adds jQueryUI classes to the dropdown list if the jqueryUI option is set to true
129
+ this._addClasses();
130
+
131
+ // Focus the selectbox div, if the original select tag has autofocus attribute
132
+ if (this.selectBox.attr("autofocus")) {
133
+
134
+ this.div.focus();
135
+
136
+ }
137
+
138
+ // Triggers a custom `create` event on the original dropdown list
139
+ this.selectBox.trigger("create");
140
+
141
+ },
142
+ // _Create Div
143
+ // -----------
144
+ // Creates new div and span elements to replace
145
+ // the original select box with a dropdown list
146
+ _createDiv: function() {
147
+
148
+ // Creates a span element that contains the dropdown list text value
149
+ this.divText = $("<span/>", {
150
+
151
+ // Dynamically sets the span `id` attribute
152
+ "id": this.originalElem.id + "SelectBoxItText",
153
+
154
+ "class": "selectboxit-text",
155
+
156
+ // IE specific attribute to not allow the element to be selected
157
+ "unselectable": "on",
158
+
159
+ // Sets the span `text` to equal the original select box default value
160
+ "text": this.firstSelectItem.text()
161
+
162
+ }).
163
+
164
+ // Sets the HTML5 data attribute on the divText `span` element
165
+ attr("data-val", this.originalElem.value);
166
+
167
+ // Creates a span element that contains the dropdown list text value
168
+ this.divImage = $("<span/>", {
169
+
170
+ // Dynamically sets the span `id` attribute
171
+ "id": this.originalElem.id + "SelectBoxItDefaultIcon",
172
+
173
+ "class": "selectboxit-default-icon",
174
+
175
+ // IE specific attribute to not allow the element to be selected
176
+ "unselectable": "on"
177
+
178
+ });
179
+
180
+ // Creates a div to act as the new dropdown list
181
+ this.div = $("<div/>", {
182
+
183
+ // Dynamically sets the div `id` attribute
184
+ "id": this.originalElem.id + "SelectBoxIt",
185
+
186
+ "class": "selectboxit" + (this.originalElem.className !== "" ? " " + this.originalElem.className : ""),
187
+
188
+ // Sets the div `name` attribute to be the same name as the original select box
189
+ "name": this.originalElem.name,
190
+
191
+ // Sets the div `tabindex` attribute to 0 to allow the div to be focusable
192
+ "tabindex": 0,
193
+
194
+ // IE specific attribute to not allow the element to be selected
195
+ "unselectable": "on"
196
+
197
+ }).
198
+
199
+ // Appends the default text to the inner dropdown list div element
200
+ append(this.divImage).append(this.divText);
201
+
202
+ // Create the div container that will hold all of the dropdown list dom elements
203
+ this.divContainer = $("<div/>", {
204
+
205
+ "id": this.originalElem.id + "SelectBoxItContainer",
206
+
207
+ "class": "selectboxit-container"
208
+ }).
209
+
210
+ // Appends the inner dropdown list div element to the dropdown list container div element
211
+ append(this.div);
212
+
213
+ // Maintains chainability
214
+ return this;
215
+ },
216
+
217
+ // _Create Unordered List
218
+ // ----------------------
219
+ // Creates an unordered list element to hold the
220
+ // new dropdown list options that directly match
221
+ // the values of the original select box options
222
+ _createUnorderedList: function() {
223
+
224
+ // Storing the context of the widget
225
+ var self = this,
226
+
227
+ dataDisabled,
228
+
229
+ optgroupClass = "",
230
+
231
+ optgroupElement = "",
232
+
233
+ iconClass,
234
+
235
+ // Declaring the variable that will hold all of the dropdown list option elements
236
+ currentItem = "",
237
+ // Creates an unordered list element
238
+ createdList = $("<ul/>", {
239
+
240
+ // Sets the unordered list `id` attribute
241
+ "id": this.originalElem.id + "SelectBoxItOptions",
242
+
243
+ "class": "selectboxit-options",
244
+
245
+ //Sets the unordered list `tabindex` attribute to -1 to prevent the unordered list from being focusable
246
+ "tabindex": -1
247
+
248
+ });
249
+
250
+ // Checks the `showFirstOption` plugin option to determine if the first dropdown list option should be shown in the options list.
251
+ if (!this.options.showFirstOption) {
252
+
253
+ // Excludes the first dropdown list option from the options list
254
+ this.selectItems = this.selectBox.find("option").slice(1);
255
+ }
256
+
257
+ // Loops through the original select box options list and copies the text of each
258
+ // into new list item elements of the new dropdown list
259
+ this.selectItems.each(function(index) {
260
+
261
+ dataDisabled = $(this).prop("disabled");
262
+
263
+ iconClass = $(this).data("icon") || "";
264
+
265
+ // If the current option being traversed is within an optgroup
266
+
267
+ if($(this).parent().is("optgroup")) {
268
+
269
+ optgroupClass = "selectboxit-optgroup-option";
270
+
271
+ if($(this).index() === 0) {
272
+
273
+ optgroupElement = '<div class="selectboxit-optgroup-header" data-disabled="true">' + $(this).parent().first().attr("label") + '</div>';
274
+
275
+ }
276
+
277
+ else {
278
+
279
+ optgroupElement = "";
280
+
281
+ }
282
+
283
+ }
284
+
285
+ // If the current option being traversed is not within an optgroup
286
+
287
+ else {
288
+
289
+ optgroupClass = "";
290
+
291
+ }
292
+
293
+ // Uses string concatenation instead of append for speed since the number of dropdown list options is unknown.
294
+ currentItem += optgroupElement + '<li id="' + index + '" data-val="' + this.value + '" data-disabled="' + dataDisabled + '" class="' + optgroupClass + '"><span class="' + iconClass + '"></span>' + $(this).text() + '</li>';
295
+
296
+ // Stores all of the original select box options text inside of an array
297
+ // (Used later in the `searchAlgorithm` method)
298
+ self.textArray[index] = $(this).text();
299
+
300
+ // Checks the original select box option for the `selected` attribute
301
+ if (this.selected) {
302
+
303
+ //Replace the default text with the selected option
304
+ self.divText.text($(this).text());
305
+
306
+ //Set the currently selected option
307
+ self.currentFocus = index;
308
+ }
309
+
310
+ });
311
+
312
+ // If the `defaultText` option is being used
313
+ if (self.options.defaultText) {
314
+
315
+ //Overrides the current dropdown default text with the value the user specifies in the `defaultText` option
316
+ self.divText.text(self.options.defaultText);
317
+ }
318
+
319
+ // If the `defaultText` HTML5 data attribute is being used
320
+ if (self.selectBox.data("text")) {
321
+
322
+ // Overrides the current dropdown default text with the value from the HTML5 `defaultText` value
323
+ self.divText.text(self.selectBox.data("text"));
324
+ self.options.defaultText = self.selectBox.data("text");
325
+
326
+ }
327
+
328
+ // Append the list item to the unordered list
329
+ createdList.append(currentItem);
330
+
331
+ // Stores the dropdown list options list inside of the `list` instance variable
332
+ this.list = createdList;
333
+
334
+ // Append the dropdown list options list to the div container element
335
+ this.divContainer.append(this.list);
336
+
337
+ // Stores the individual dropdown list options inside of the `listItems` instance variable
338
+ this.listItems = this.list.find("li");
339
+
340
+ // Set the disabled CSS class for select box options
341
+ this.list.find("li[data-disabled='true']").not(".optgroupHeader").addClass("ui-state-disabled");
342
+
343
+ // If the first select box option is disabled, and the user has chosen to not show the first select box option
344
+ if (this.currentFocus === 0 && !this.options.showFirstOption && this.listItems.eq(0).hasClass("ui-state-disabled")) {
345
+
346
+ //Sets the default value of the dropdown list to the first option that is not disabled
347
+ this.currentFocus = +this.listItems.not(".ui-state-disabled").first().attr("id");
348
+
349
+ }
350
+
351
+ this.divImage.addClass(this.selectBox.data("icon") || this.options.defaultIcon || this.listItems.eq(this.currentFocus).find("span").attr("class"));
352
+
353
+ //Maintains chainability
354
+ return this;
355
+ },
356
+
357
+ // _Replace Select Box
358
+ // -------------------
359
+ // Hides the original dropdown list and inserts
360
+ // the new DOM elements
361
+ _replaceSelectBox: function() {
362
+
363
+ // Hides the original select box
364
+ this.selectBox.css("display", "none").
365
+
366
+ // Adds the new dropdown list to the page directly after the hidden original select box element
367
+ after(this.divContainer);
368
+
369
+ // The height of the dropdown list
370
+ var height = this.div.height();
371
+
372
+ // The down arrow element of the dropdown list
373
+ this.downArrow = $("<span/>", {
374
+
375
+ // Dynamically sets the span `id` attribute of the dropdown list down arrow
376
+ "id": this.originalElem.id + "SelectBoxItArrow",
377
+
378
+ "class": "selectboxit-arrow",
379
+
380
+ // IE specific attribute to not allow the dropdown list text to be selected
381
+ "unselectable": "on"
382
+
383
+ });
384
+
385
+ // The down arrow container element of the dropdown list
386
+ this.downArrowContainer = $("<span/>", {
387
+
388
+ // Dynamically sets the span `id` attribute for the down arrow container element
389
+ "id": this.originalElem.id + "SelectBoxItArrowContainer",
390
+
391
+ "class": "selectboxit-arrow-container",
392
+
393
+ // IE specific attribute to not allow the dropdown list text to be selected
394
+ "unselectable": "on",
395
+
396
+ // The dynamic CSS of the down arrow container element
397
+ "style": "height:" + height + "px;"
398
+
399
+ }).
400
+
401
+ // Inserts the down arrow element inside of the down arrow container element
402
+ append(this.downArrow);
403
+
404
+ // Appends the down arrow element to the dropdown list
405
+ this.div.append(this.downArrowContainer);
406
+
407
+ this.divImage.css({
408
+
409
+ "margin-top": height / 4
410
+
411
+ });
412
+
413
+ this.listItems.find("span").css({
414
+
415
+ "margin-top": height / 4
416
+
417
+ });
418
+
419
+ // Maintains chainability
420
+ return this;
421
+ },
422
+
423
+ // _Scroll-To-View
424
+ // ---------------
425
+ // Updates the dropdown list scrollTop value
426
+ _scrollToView: function(type) {
427
+
428
+ // The current scroll positioning of the dropdown list options list
429
+ var listScrollTop = this.list.scrollTop(),
430
+
431
+ // The height of the currently selected dropdown list option
432
+ currentItemHeight = this.listItems.eq(this.currentFocus).height(),
433
+
434
+ // The relative distance from the currently selected dropdown list option to the the top of the dropdown list options list
435
+ currentTopPosition = this.listItems.eq(this.currentFocus).position().top,
436
+
437
+ // The height of the dropdown list option list
438
+ listHeight = this.list.height();
439
+
440
+ // Scrolling logic for a text search
441
+ if (type === "search") {
442
+
443
+ // Increases the dropdown list options `scrollTop` if a user is searching for an option
444
+ // below the currently selected option that is not visible
445
+ if (listHeight - currentTopPosition < currentItemHeight) {
446
+
447
+ // The selected option will be shown at the very bottom of the visible options list
448
+ this.list.scrollTop(listScrollTop + (currentTopPosition - (listHeight - currentItemHeight)));
449
+
450
+ }
451
+
452
+ // Decreases the dropdown list options `scrollTop` if a user is searching for an option above the currently selected option that is not visible
453
+ else if (currentTopPosition < -1) {
454
+
455
+ this.list.scrollTop(currentTopPosition - currentItemHeight);
456
+
457
+ }
458
+ }
459
+
460
+ // Scrolling logic for the `up` keyboard navigation
461
+ else if (type === "up") {
462
+
463
+ // Decreases the dropdown list option list `scrollTop` if a user is navigating to an element that is not visible
464
+ if (currentTopPosition < -1) {
465
+
466
+ this.list.scrollTop(listScrollTop - Math.abs(this.listItems.eq(this.currentFocus).position().top));
467
+
468
+ }
469
+ }
470
+
471
+ // Scrolling logic for the `down` keyboard navigation
472
+ else if (type === "down") {
473
+
474
+ // Increases the dropdown list options `scrollTop` if a user is navigating to an element that is not fully visible
475
+ if (listHeight - currentTopPosition < currentItemHeight) {
476
+
477
+ // Increases the dropdown list options `scrollTop` by the height of the current option item.
478
+ this.list.scrollTop((listScrollTop + (Math.abs(this.listItems.eq(this.currentFocus).position().top) - listHeight + currentItemHeight)));
479
+
480
+ }
481
+ }
482
+
483
+ // Maintains chainability
484
+ return this;
485
+ },
486
+
487
+ // _Callback
488
+ // ---------
489
+ // Call the function passed into the method
490
+ _callbackSupport: function(callback) {
491
+
492
+ // Checks to make sure the parameter passed in is a function
493
+ if ($.isFunction(callback)) {
494
+
495
+ // Calls the method passed in as a parameter and sets the current `SelectBoxIt` object that is stored in the jQuery data method as the context(allows for `this` to reference the SelectBoxIt API Methods in the callback function. The `div` DOM element that acts as the new dropdown list is also passed as the only parameter to the callback
496
+ callback.call(this.element.data(this.widgetName), this.div);
497
+
498
+ }
499
+ },
500
+
501
+ // Open
502
+ // ----
503
+ // Opens the dropdown list options list
504
+ open: function(callback) {
505
+
506
+ if(!this.list.is(":visible")) {
507
+
508
+ var self = this;
509
+
510
+ // Triggers a custom "open" event on the original select box
511
+ this.selectBox.trigger("open");
512
+
513
+ if (this._dynamicPositioning) {
514
+ // Dynamically positions the dropdown list options list
515
+ this._dynamicPositioning();
516
+ }
517
+
518
+ // Determines what jQuery effect to use when opening the dropdown list options list
519
+ switch (this.options.showEffect) {
520
+
521
+ // Uses `no effect`
522
+ case "none":
523
+
524
+ // Does not require a callback function because this animation will complete before the call to `scrollToView`
525
+ this.list.show();
526
+
527
+ // Updates the list `scrollTop` attribute
528
+ this._scrollToView("search");
529
+
530
+ break;
531
+
532
+ // Uses the jQuery `show` special effect
533
+ case "show":
534
+
535
+ // Requires a callback function to determine when the `show` animation is complete
536
+ this.list.show(this.options.showEffectSpeed, function() {
537
+
538
+ // Updates the list `scrollTop` attribute
539
+ self._scrollToView("search");
540
+
541
+ });
542
+
543
+ break;
544
+
545
+ // Uses the jQuery `slideDown` special effect
546
+ case "slideDown":
547
+
548
+ // Requires a callback function to determine when the `slideDown` animation is complete
549
+ this.list.slideDown(this.options.showEffectSpeed, function() {
550
+
551
+ // Updates the list `scrollTop` attribute
552
+ self._scrollToView("search");
553
+
554
+ });
555
+
556
+ break;
557
+
558
+ // Uses the jQuery `fadeIn` special effect
559
+ case "fadeIn":
560
+
561
+ // Does not require a callback function because this animation will complete before the call to `scrollToView`
562
+ this.list.fadeIn(this.options.showEffectSpeed);
563
+
564
+ // Updates the list `scrollTop` attribute
565
+ this._scrollToView("search");
566
+
567
+ break;
568
+
569
+ // If none of the above options were passed, then a `jqueryUI show effect` is expected
570
+ default:
571
+
572
+ // Allows for custom show effects via the [jQueryUI core effects](http://http://jqueryui.com/demos/show/)
573
+ this.list.show(this.options.showEffect, this.options.showEffectOptions, this.options.showEffectSpeed, function() {
574
+
575
+ // Updates the list `scrollTop` attribute
576
+ self._scrollToView("search");
577
+
578
+ });
579
+
580
+ break;
581
+
582
+ }
583
+
584
+ }
585
+
586
+ // Provide callback function support
587
+ this._callbackSupport(callback);
588
+
589
+ // Maintains chainability
590
+ return this;
591
+ },
592
+
593
+ // Close
594
+ // -----
595
+ // Closes the dropdown list options list
596
+ close: function(callback) {
597
+
598
+ if(this.list.is(":visible")) {
599
+
600
+ var self = this;
601
+
602
+ // Triggers a custom "close" event on the original select box
603
+ this.selectBox.trigger("close");
604
+
605
+ // Determines what jQuery effect to use when closing the dropdown list options list
606
+ switch (this.options.hideEffect) {
607
+
608
+ // Uses `no effect`
609
+ case "none":
610
+
611
+ // Does not require a callback function because this animation will complete before the call to `scrollToView`
612
+ this.list.hide();
613
+
614
+ // Updates the list `scrollTop` attribute
615
+ this._scrollToView("search");
616
+
617
+ break;
618
+
619
+ // Uses the jQuery `hide` special effect
620
+ case "hide":
621
+
622
+ this.list.hide(this.options.hideEffectSpeed);
623
+
624
+ break;
625
+
626
+ // Uses the jQuery `slideUp` special effect
627
+ case "slideUp":
628
+
629
+ this.list.slideUp(this.options.hideEffectSpeed);
630
+
631
+ break;
632
+
633
+ // Uses the jQuery `fadeOut` special effect
634
+ case "fadeOut":
635
+
636
+ this.list.fadeOut(this.options.hideEffectSpeed);
637
+
638
+ break;
639
+
640
+ // If none of the above options were passed, then a `jqueryUI hide effect` is expected
641
+ default:
642
+
643
+ // Allows for custom hide effects via the [jQueryUI core effects](http://http://jqueryui.com/demos/hide/)
644
+ this.list.hide(this.options.hideEffect, this.options.hideEffectOptions, this.options.hideEffectSpeed, function() {
645
+
646
+ //Updates the list `scrollTop` attribute
647
+ self._scrollToView("search");
648
+
649
+ });
650
+
651
+ break;
652
+ }
653
+
654
+ }
655
+
656
+ // Provide callback function support
657
+ this._callbackSupport(callback);
658
+
659
+ // Maintains chainability
660
+ return this;
661
+ },
662
+
663
+
664
+ // _Event Handlers
665
+ // ---------------
666
+ // Adds event handlers to the new dropdown list
667
+ _eventHandlers: function() {
668
+
669
+ // LOCAL VARIABLES
670
+ var self = this,
671
+
672
+ upKey = 38,
673
+
674
+ downKey = 40,
675
+
676
+ enterKey = 13,
677
+
678
+ backspaceKey = 8,
679
+
680
+ tabKey = 9,
681
+
682
+ spaceKey = 32,
683
+
684
+ escKey = 27;
685
+
686
+ // Select Box events
687
+ this.div.bind({
688
+
689
+ // `click` event with the `selectBoxIt` namespace
690
+ "click.selectBoxIt": function() {
691
+
692
+ if(!self.div.is(":focus")) {
693
+
694
+ $(this).focus();
695
+
696
+ }
697
+
698
+ // The `click` handler logic will only be applied if the dropdown list is enabled
699
+ if (!self.originalElem.disabled) {
700
+
701
+ // Triggers the `click` event on the original select box
702
+ self.selectBox.trigger("click");
703
+
704
+ // If the dropdown list options list is visible when a user clicks on the dropdown list
705
+ if (self.list.is(":visible")) {
706
+
707
+ // Closes the dropdown list options list
708
+ self.close();
709
+ }
710
+
711
+ // If the dropdown list options list is not visible when a user clicks on the dropdown list
712
+ else {
713
+
714
+ // Shows the dropdown list options list
715
+ self.open();
716
+ }
717
+ }
718
+ },
719
+
720
+ // `mousedown` event with the `selectBoxIt` namespace
721
+ "mousedown.selectBoxIt": function() {
722
+
723
+ // Stores data in the jQuery `data` method to help determine if the dropdown list gains focus from a click or tabstop. The mousedown event fires before the focus event.
724
+ $(this).data("mdown", true);
725
+ },
726
+
727
+ // `blur` event with the `selectBoxIt` namespace. Uses special blur logic to make sure the dropdown list closes correctly
728
+ "blur.selectBoxIt": function() {
729
+
730
+ // If `self.blur` property is true
731
+ if (self.blur) {
732
+
733
+ // Triggers both the `blur` and `focusout` events on the original select box.
734
+ // The `focusout` event was also triggered because the event bubbles
735
+ // This event has to be used when using event delegation (such as the jQuery `delegate` or `on` methods)
736
+ // Popular open source projects such as Backbone.js utilize event delegation to bind events, so if you are using Backbone.js, use the `focusout` event instead of the `blur` event
737
+ self.selectBox.trigger("blur").trigger("focusout");
738
+
739
+ //If the dropdown options list is visible
740
+ if (self.list.is(":visible")) {
741
+ //Closes the dropdown list options list
742
+ self.close();
743
+ }
744
+ }
745
+ },
746
+
747
+ "focus.selectBoxIt": function() {
748
+
749
+ // Stores the data associated with the mousedown event inside of a local variable
750
+ var mdown = $(this).data("mdown");
751
+
752
+ // Removes the jQuery data associated with the mousedown event
753
+ $(this).removeData('mdown');
754
+
755
+ // If a mousedown event did not occur and no data was passed to the focus event (this correctly triggers the focus event), then the dropdown list gained focus from a tabstop
756
+ if (!mdown) {
757
+
758
+ // Triggers the `tabFocus` custom event on the original select box
759
+ self.selectBox.trigger("tabFocus");
760
+ }
761
+
762
+ // Only trigger the `focus` event on the original select box if the dropdown list is hidden (this verifies that only the correct `focus` events are used to trigger the event on the original select box
763
+ if(!self.list.is(":visible")) {
764
+
765
+ //Triggers the `focus` default event on the original select box
766
+ self.selectBox.trigger("focus").trigger("focusin");
767
+
768
+ }
769
+ },
770
+
771
+ // `keydown` event with the `selectBoxIt` namespace. Catches all user keyboard navigations
772
+ "keydown.selectBoxIt": function(e) {
773
+
774
+ // Stores the `keycode` value in a local variable
775
+ var currentKey = e.keyCode;
776
+
777
+ // Performs keyboard events if the dropdown list is focused
778
+ if (self.div.is(":focus")) {
779
+
780
+ // Supports keyboard navigation
781
+ switch (currentKey) {
782
+
783
+ // If the user presses the `down key`
784
+ case downKey:
785
+
786
+ // Prevents the page from moving down
787
+ e.preventDefault();
788
+
789
+ // If the plugin options allow keyboard navigation
790
+ if (self.moveDown) {
791
+
792
+ //Opens the dropdown list if it's hidden
793
+ if (self.list.is(":hidden")) {
794
+ self.open();
795
+ }
796
+
797
+ // Moves the focus down to the dropdown list option directly beneath the currently selected selectbox option
798
+ self.moveDown();
799
+
800
+ }
801
+
802
+ break;
803
+
804
+ //If the user presses the `up key`
805
+ case upKey:
806
+
807
+ // Prevents the page from moving up
808
+ e.preventDefault();
809
+
810
+ // If the plugin options allow keyboard navgiation
811
+ if (self.moveUp) {
812
+
813
+ //Opens the dropdown list if it's hidden
814
+ if (self.list.is(":hidden")) {
815
+ self.open();
816
+ }
817
+
818
+ // Moves the focus up to the dropdown list option directly above the currently selected selectbox option
819
+ self.moveUp();
820
+
821
+ }
822
+
823
+ break;
824
+
825
+ // If the user presses the `enter key`
826
+ case enterKey:
827
+
828
+ // Prevents the default event from being triggered
829
+ e.preventDefault();
830
+
831
+ // Checks to see if the dropdown list options list is open
832
+ if (self.list.is(":visible")) {
833
+
834
+ // Closes the dropdown list options list
835
+ self.close();
836
+ }
837
+
838
+ // If the first dropdown list option is not shown in the options list, and the dropdown list has not been interacted with, then update the dropdown list value when the enter key is pressed
839
+ if (!self.options.showFirstOption && self.div.text() === self.firstSelectItem.text() && self.currentFocus === 0 || (self.options.showFirstOption && self.options.defaultText) || (!self.options.showFirstOption && !self.listItems.eq(0).not("[data-disabled='true']"))) {
840
+
841
+ // Updates the dropdown list value
842
+ self.selectBox.val(self.listItems.eq(self.currentFocus).attr("data-val")).
843
+
844
+ // Triggers a `change` event on the original select box
845
+ trigger("change");
846
+ }
847
+
848
+ // Triggers the `enter` events on the original select box
849
+ self.selectBox.trigger("enter");
850
+
851
+ break;
852
+
853
+ // If the user presses the `tab key`
854
+ case tabKey:
855
+
856
+ // Triggers the custom `tabBlur` events on the original select box
857
+ self.selectBox.trigger("tabBlur");
858
+
859
+ break;
860
+
861
+ // If the user presses the `backspace key`
862
+ case backspaceKey:
863
+
864
+ // Prevents the browser from navigating to the previous page in its history
865
+ e.preventDefault();
866
+
867
+ // Triggers the custom `backspace` event on the original select box
868
+ self.selectBox.trigger("backspace");
869
+
870
+ break;
871
+
872
+ // If the user presses the `escape key`
873
+ case escKey:
874
+
875
+ // Closes the dropdown options list
876
+ self.close();
877
+
878
+ break;
879
+
880
+ // Default is to break out of the switch statement
881
+ default:
882
+
883
+ break;
884
+
885
+ }
886
+ }
887
+ },
888
+
889
+ // `keypress` event with the `selectBoxIt` namespace. Catches all user keyboard text searches since you can only reliably get character codes using the `keypress` event
890
+ "keypress.selectBoxIt": function(e) {
891
+
892
+ // Performs a text search if the dropdown list is focused
893
+ if (self.div.is(":focus")) {
894
+
895
+ // Sets the current key to the `keyCode` value if `charCode` does not exist. Used for cross
896
+ // browser support since IE uses `keyCode` instead of `charCode`.
897
+ var currentKey = e.charCode || e.keyCode,
898
+
899
+ // Converts unicode values to characters
900
+ alphaNumericKey = String.fromCharCode(currentKey);
901
+
902
+ // If the user presses the `space bar`
903
+ if (currentKey === spaceKey) {
904
+
905
+ // Prevents the browser from scrolling to the bottom of the page
906
+ e.preventDefault();
907
+ }
908
+
909
+ // If the plugin options allow text searches
910
+ if (self.search) {
911
+
912
+ // Calls `search` and passes the character value of the user's text search
913
+ self.search(alphaNumericKey, true, "");
914
+ }
915
+ }
916
+ },
917
+
918
+ // `mousenter` event with the `selectBoxIt` namespace .The mouseenter JavaScript event is proprietary to Internet Explorer. Because of the event's general utility, jQuery simulates this event so that it can be used regardless of browser.
919
+ "mouseenter.selectBoxIt": function() {
920
+
921
+ // Trigger the `mouseenter` event on the original select box
922
+ self.selectBox.trigger("mouseenter");
923
+ },
924
+
925
+ // `mouseleave` event with the `selectBoxIt` namespace. The mouseleave JavaScript event is proprietary to Internet Explorer. Because of the event's general utility, jQuery simulates this event so that it can be used regardless of browser.
926
+ "mouseleave.selectBoxIt": function() {
927
+
928
+ // Trigger the `mouseleave` event on the original select box
929
+ self.selectBox.trigger("mouseleave");
930
+ }
931
+
932
+ });
933
+
934
+ // Select box options events that set the dropdown list blur logic (decides when the dropdown list gets
935
+ // closed)
936
+ this.list.bind({
937
+
938
+ // `mouseover` event with the `selectBoxIt` namespace
939
+ "mouseover.selectBoxIt": function() {
940
+
941
+ // Prevents the dropdown list options list from closing
942
+ self.blur = false;
943
+ },
944
+
945
+ // `mouseout` event with the `selectBoxIt` namespace
946
+ "mouseout.selectBoxIt": function() {
947
+
948
+ // Allows the dropdown list options list to close
949
+ self.blur = true;
950
+ },
951
+
952
+ // `focusin` event with the `selectBoxIt` namespace
953
+ "focusin.selectBoxIt": function() {
954
+
955
+ // Prevents the default browser outline border to flicker, which results because of the `blur` event
956
+ self.div.focus();
957
+ }
958
+
959
+ })
960
+
961
+ // Select box individual options events bound with the jQuery `delegate` method. `Delegate` was used because binding individual events to each list item (since we don't know how many there will be) would decrease performance. Instead, we bind each event to the unordered list, provide the list item context, and allow the list item events to bubble up (`event bubbling`). This greatly increases page performance because we only have to bind an event to one element instead of x number of elements. Delegates the `click` event with the `selectBoxIt` namespace to the list items
962
+ .delegate("li", "click.selectBoxIt", function() {
963
+
964
+ if (!$(this).data("disabled")) {
965
+
966
+ // Sets the original dropdown list value and triggers the `change` event on the original select box
967
+ self.originalElem.value = $(this).attr("data-val");
968
+
969
+ // Sets `currentFocus` to the currently focused dropdown list option.
970
+ // The unary `+` operator casts the string to a number
971
+ // [James Padolsey Blog Post](http://james.padolsey.com/javascript/terse-javascript-101-part-2/)
972
+ self.currentFocus = +this.id;
973
+
974
+ // Closes the list after selecting an option
975
+ self.close();
976
+
977
+ // Triggers the dropdown list `change` event if a value change occurs
978
+ if (self.originalElem.value !== self.divText.attr("data-val")) {
979
+
980
+ self.selectBox.trigger("change");
981
+
982
+ }
983
+ }
984
+ })
985
+
986
+ // Delegates the `focus` event with the `selectBoxIt` namespace to the list items
987
+ .delegate("li", "focus.selectBoxIt", function() {
988
+
989
+ if (!$(this).data("disabled")) {
990
+
991
+ // Sets the original select box current value and triggers the change event
992
+ self.originalElem.value = $(this).attr("data-val");
993
+
994
+ // Triggers the dropdown list `change` event if a value change occurs
995
+ if (self.originalElem.value !== self.divText.attr("data-val")) {
996
+
997
+ self.selectBox.trigger("change");
998
+
999
+ }
1000
+ }
1001
+ });
1002
+
1003
+ // Original dropdown list events
1004
+ this.selectBox.bind({
1005
+
1006
+ // `change` event handler with the `selectBoxIt` namespace
1007
+ "change.selectBoxIt": function() {
1008
+
1009
+ // Find list item entry with value of original select
1010
+ self.currentFocus = self.listItems.index($('li[data-val="' + self.originalElem.value + '"]'));
1011
+
1012
+ // Sets the new dropdown list text to the value of the original dropdown list
1013
+ self.divText.text(self.listItems.eq(self.currentFocus).text()).attr("data-val", self.originalElem.value);
1014
+
1015
+ if(self.listItems.eq(self.currentFocus).find("span").attr("class")) {
1016
+
1017
+ self.divImage.attr("class", self.listItems.eq(self.currentFocus).find("span").attr("class"));
1018
+ }
1019
+ },
1020
+
1021
+ // `disable` event with the `selectBoxIt` namespace
1022
+ "disable.selectBoxIt": function() {
1023
+
1024
+ // Adds the `disabled` CSS class to the new dropdown list to visually show that it is disabled
1025
+ self.div.addClass("ui-state-disabled");
1026
+ },
1027
+
1028
+ // `enable` event with the `selectBoxIt` namespace
1029
+ "enable.selectBoxIt": function() {
1030
+
1031
+ // Removes the `disabled` CSS class from the new dropdown list to visually show that it is enabled
1032
+ self.div.removeClass("ui-state-disabled");
1033
+ }
1034
+ });
1035
+
1036
+ // // Binding the original change event to the new custom one
1037
+ // .on("change", function(e) {
1038
+ // console.log(e)
1039
+ // self.selectBoxIt.trigger("change.selectBoxIt");
1040
+ // });
1041
+
1042
+ // Maintains chainability
1043
+ return this;
1044
+ },
1045
+
1046
+ // _addClasses
1047
+ // ---------
1048
+ // Adds SelectBoxIt CSS classes
1049
+ _addClasses: function() {
1050
+
1051
+ var self = this,
1052
+
1053
+ focusClass = "selectboxit-focus",
1054
+
1055
+ hoverClass = "selectboxit-hover";
1056
+
1057
+ this.downArrow.addClass(this.selectBox.data("downarrow") || this.options.downArrowIcon || "");
1058
+
1059
+ // Adds the default class to the dropdown list
1060
+ this.div.addClass("selectboxit-widget");
1061
+
1062
+ // Adds the default styling for the dropdown list options
1063
+ this.list.addClass("selectboxit-widget selectboxit-widget-content");
1064
+
1065
+ // Select box individual option events
1066
+ this.listItems.bind({
1067
+
1068
+ // `focus` event with the `selectBoxIt` namespace
1069
+ "focus.selectBoxIt": function() {
1070
+
1071
+ // Adds the focus CSS class to the currently focused dropdown list option
1072
+ $(this).addClass(focusClass);
1073
+
1074
+ },
1075
+
1076
+ // `blur` event with the `selectBoxIt` namespace
1077
+ "blur.selectBoxIt": function() {
1078
+
1079
+ // Removes the focus CSS class from the previously focused dropdown list option
1080
+ $(this).removeClass(focusClass);
1081
+
1082
+ }
1083
+
1084
+ });
1085
+
1086
+ // Select box events
1087
+ this.selectBox.bind({
1088
+
1089
+ // `click` event with the `selectBoxIt` namespace
1090
+ "open.selectBoxIt": function() {
1091
+
1092
+ // Removes the jQueryUI hover class from the dropdown list and adds the jQueryUI focus class for both the dropdown list and the currently selected dropdown list option
1093
+ self.div.removeClass(hoverClass).add(self.listItems.eq(self.currentFocus)).
1094
+
1095
+ addClass(focusClass);
1096
+ },
1097
+
1098
+ "blur.selectBoxIt": function() {
1099
+
1100
+ self.div.removeClass(focusClass);
1101
+
1102
+ },
1103
+
1104
+ // `mousenter` event with the `selectBoxIt` namespace
1105
+ "mouseenter.selectBoxIt": function() {
1106
+
1107
+ self.div.addClass(hoverClass);
1108
+
1109
+ },
1110
+
1111
+ // `mouseleave` event with the `selectBoxIt` namespace
1112
+ "mouseleave.selectBoxIt": function() {
1113
+
1114
+ // Removes the hover CSS class on the previously hovered dropdown list option
1115
+ self.div.removeClass(hoverClass);
1116
+
1117
+ }
1118
+
1119
+ });
1120
+
1121
+ this.listItems.bind({
1122
+
1123
+ "mouseenter.selectBoxIt": function() {
1124
+
1125
+ // Sets the dropdown list individual options back to the default state and sets the hover CSS class on the currently hovered option
1126
+ self.listItems.removeClass(focusClass);
1127
+
1128
+ $(this).addClass(hoverClass);
1129
+
1130
+ },
1131
+
1132
+ "mouseleave.selectBoxIt": function() {
1133
+
1134
+ $(this).removeClass(hoverClass);
1135
+
1136
+ }
1137
+
1138
+ });
1139
+
1140
+ // Maintains chainability
1141
+ return this;
1142
+
1143
+ }
1144
+
1145
+ });
1146
+
1147
+ })); // End of core module
1148
+ $(function() {
1149
+
1150
+ //_ARIA Accessibility
1151
+ // ------------------
1152
+ // Adds ARIA (Accessible Rich Internet Applications)
1153
+ // Accessibility Tags to the Select Box
1154
+
1155
+ $.selectBox.selectBoxIt.prototype._ariaAccessibility = function() {
1156
+
1157
+ var self = this;
1158
+
1159
+ //Adds `ARIA attributes` to the dropdown list
1160
+ this.div.attr({
1161
+
1162
+ //W3C `combobox` description: A presentation of a select; usually similar to a textbox where users can type ahead to select an option.
1163
+ "role": "combobox",
1164
+
1165
+ //W3C `aria-autocomplete` description: Indicates whether user input completion suggestions are provided.
1166
+ "aria-autocomplete": "list",
1167
+
1168
+ //W3C `aria-expanded` description: Indicates whether the element, or another grouping element it controls, is currently expanded or collapsed.
1169
+ "aria-expanded": "false",
1170
+
1171
+ //W3C `aria-owns` description: The value of the aria-owns attribute is a space-separated list of IDREFS that reference one or more elements in the document by ID. The reason for adding aria-owns is to expose a parent/child contextual relationship to assistive technologies that is otherwise impossible to infer from the DOM.
1172
+ "aria-owns": this.list.attr("id"),
1173
+
1174
+ //W3C `aria-activedescendant` description: This is used when a composite widget is responsible for managing its current active child to reduce the overhead of having all children be focusable. Examples include: multi-level lists, trees, and grids.
1175
+ "aria-activedescendant": this.listItems.eq(this.currentFocus).attr("id"),
1176
+
1177
+ //W3C `aria-label` description: It provides the user with a recognizable name of the object.
1178
+ "aria-label": $("label[for='" + this.originalElem.id + "']").text() || "",
1179
+
1180
+ //W3C `aria-live` description: Indicates that an element will be updated.
1181
+ //Use the assertive value when the update needs to be communicated to the user more urgently.
1182
+ "aria-live": "assertive"
1183
+ }).
1184
+
1185
+ //Dynamically adds `ARIA attributes` if the new dropdown list is enabled or disabled
1186
+ bind({
1187
+
1188
+ //Select box custom `disable` event with the `selectBoxIt` namespace
1189
+ "disable.selectBoxIt" : function() {
1190
+
1191
+ //W3C `aria-disabled` description: Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable.
1192
+ self.div.attr("aria-disabled", "true");
1193
+
1194
+ },
1195
+
1196
+ //Select box custom `enable` event with the `selectBoxIt` namespace
1197
+ "enable.selectBoxIt" : function() {
1198
+
1199
+ //W3C `aria-disabled` description: Indicates that the element is perceivable but disabled, so it is not editable or otherwise operable.
1200
+ self.div.attr("aria-disabled", "false");
1201
+
1202
+ }
1203
+
1204
+ });
1205
+
1206
+ //Adds ARIA attributes to the dropdown list options list
1207
+ self.list.attr({
1208
+
1209
+ //W3C `listbox` description: A widget that allows the user to select one or more items from a list of choices.
1210
+ "role": "listbox",
1211
+
1212
+ //Indicates that the dropdown list options list is currently hidden
1213
+ "aria-hidden": "true"
1214
+ });
1215
+
1216
+ //Adds `ARIA attributes` to the dropdown list options
1217
+ self.listItems.attr({
1218
+
1219
+ //This must be set for each element when the container element role is set to `listbox`
1220
+ "role": "option"
1221
+ });
1222
+
1223
+ //Dynamically updates the new dropdown list `aria-label` attribute after the original dropdown list value changes
1224
+ self.selectBox.bind({
1225
+
1226
+ //Custom `change` event with the `selectBoxIt` namespace
1227
+ "change.selectBoxIt": function() {
1228
+
1229
+ //Provides the user with a recognizable name of the object.
1230
+ self.divText.attr("aria-label", self.originalElem.value);
1231
+
1232
+ },
1233
+
1234
+ //Custom `open` event with the `selectBoxIt` namespace
1235
+ "open.selectBoxIt": function() {
1236
+
1237
+ //Indicates that the dropdown list options list is currently visible
1238
+ self.list.attr("aria-hidden", "false");
1239
+
1240
+ //Indicates that the dropdown list is currently expanded
1241
+ self.div.attr("aria-expanded", "true");
1242
+
1243
+ },
1244
+
1245
+ //Custom `close` event with the `selectBoxIt` namespace
1246
+ "close.selectBoxIt": function() {
1247
+
1248
+ //Indicates that the dropdown list options list is currently hidden
1249
+ self.list.attr("aria-hidden", "true");
1250
+
1251
+ //Indicates that the dropdown list is currently collapsed
1252
+ self.div.attr("aria-expanded", "false");
1253
+
1254
+ }
1255
+
1256
+ });
1257
+
1258
+ //Maintains chainability
1259
+ return this;
1260
+
1261
+ };
1262
+
1263
+ });
1264
+ $(function() {
1265
+
1266
+ //Destroy
1267
+ // ------
1268
+ // Delays execution by the amount of time
1269
+ // specified by the parameter
1270
+
1271
+ $.selectBox.selectBoxIt.prototype.destroy = function(callback) {
1272
+
1273
+ //Unbinds all of the dropdown list event handlers with the `selectBoxIt` namespace
1274
+ this.div.unbind(".selectBoxIt").
1275
+
1276
+ //Undelegates all of the dropdown list event handlers with the `selectBoxIt` namespace
1277
+ undelegate(".selectBoxIt");
1278
+
1279
+ //Remove all of the `selectBoxIt` DOM elements from the page
1280
+ this.divContainer.remove();
1281
+
1282
+ //Triggers the custom `destroy` event on the original select box and then shows the original dropdown list
1283
+ this.selectBox.trigger("destroy").show();
1284
+
1285
+ // Calls the jQueryUI Widget Factory destroy method
1286
+ $.Widget.prototype.destroy.call(this);
1287
+
1288
+ //Provides callback function support
1289
+ this._callbackSupport(callback);
1290
+
1291
+ //Maintains chainability
1292
+ return this;
1293
+
1294
+ };
1295
+
1296
+ });
1297
+ $(function() {
1298
+
1299
+ //Disable
1300
+ // ------
1301
+ // Disables the new dropdown list
1302
+
1303
+ $.selectBox.selectBoxIt.prototype.disable = function(callback) {
1304
+
1305
+ if(!this.options.disabled) {
1306
+
1307
+ //Makes sure the dropdown list is closed
1308
+ this.close();
1309
+
1310
+ //Triggers a `disable` custom event on the original select box
1311
+ this.selectBox.trigger("disable")
1312
+
1313
+ //Sets the `disabled` attribute on the original select box
1314
+ .attr("disabled", "disabled");
1315
+
1316
+ //Makes the dropdown list not focusable by removing the `tabindex` attribute
1317
+ this.div.removeAttr("tabindex").css("cursor", "default");
1318
+
1319
+ // Calls the jQueryUI Widget Factory disable method to make sure all options are correctly synced
1320
+ $.Widget.prototype.disable.call(this);
1321
+
1322
+ //Provides callback function support
1323
+ this._callbackSupport(callback);
1324
+
1325
+ //Maintains chainability
1326
+ return this;
1327
+
1328
+ }
1329
+
1330
+ };
1331
+
1332
+ //_Is Disabled
1333
+ // -----------
1334
+ // Checks the original select box for the
1335
+ // disabled attribute
1336
+
1337
+ $.selectBox.selectBoxIt.prototype._isDisabled = function(callback) {
1338
+
1339
+ //If the original select box is disabled
1340
+ if (this.originalElem.disabled) {
1341
+
1342
+ //Disables the dropdown list
1343
+ this.disable();
1344
+ }
1345
+
1346
+ //Maintains chainability
1347
+ return this;
1348
+
1349
+ };
1350
+
1351
+ });
1352
+ $(function() {
1353
+
1354
+ //_Dynamic positioning
1355
+ // ------------------
1356
+ // Dynamically positions the dropdown list options list
1357
+
1358
+ $.selectBox.selectBoxIt.prototype._dynamicPositioning = function() {
1359
+
1360
+ //Returns the x and y coordinates of the dropdown list options list relative to the document
1361
+ var listOffsetTop = this.div.offset().top,
1362
+
1363
+ //The height of the dropdown list options list
1364
+ listHeight = this.list.height(),
1365
+
1366
+ //The height of the dropdown list DOM element
1367
+ selectBoxHeight = this.div.height();
1368
+
1369
+ //Places the dropdown list options list on top of the dropdown list if the dropdown list options list does not fit on the page when opened
1370
+ if ((listOffsetTop + selectBoxHeight + listHeight >= $(window).height() + $(window).scrollTop()) && (listOffsetTop - listHeight >= 0)) {
1371
+
1372
+ //If the dropdown list currently opens downward
1373
+ if (!this.flipped) {
1374
+
1375
+ //Sets custom CSS properties to place the dropdown list options directly above the dropdown list
1376
+ this.list.css("top", (this.divContainer.position().top - this.list.height()) - 2);
1377
+
1378
+ //Sets the `flipped` instance variable to false to reflect that the dropdown list opens upward
1379
+ this.flipped = true;
1380
+
1381
+ }
1382
+
1383
+ }
1384
+
1385
+ //If the dropdown list options have enough room on the page to open downward
1386
+ else {
1387
+
1388
+ //If the dropdown list is currently opening upward
1389
+ if (this.flipped) {
1390
+
1391
+ //Sets custom CSS properties to place the dropdown list options directly below the dropdown list
1392
+ this.list.css("top", (this.divContainer.position().top + this.div.height()) + 2);
1393
+
1394
+ //Sets the `flipped` instance variable to false to reflect that the dropdown list opens downward
1395
+ this.flipped = false;
1396
+
1397
+ }
1398
+
1399
+ }
1400
+
1401
+ };
1402
+
1403
+ });
1404
+ $(function() {
1405
+
1406
+ //Enable
1407
+ // -----
1408
+ // Enables the new dropdown list
1409
+
1410
+ $.selectBox.selectBoxIt.prototype.enable = function(callback) {
1411
+
1412
+ if(this.options.disabled) {
1413
+
1414
+ //Triggers a `enable` custom event on the original select box
1415
+ this.selectBox.trigger("enable")
1416
+
1417
+ //Removes the `disabled` attribute from the original dropdown list
1418
+ .removeAttr("disabled");
1419
+
1420
+ //Make the dropdown list focusable
1421
+ this.div.attr("tabindex", 0).css("cursor", "pointer");
1422
+
1423
+ $.Widget.prototype.enable.call(this);
1424
+
1425
+ //Provide callback function support
1426
+ this._callbackSupport(callback);
1427
+
1428
+ }
1429
+
1430
+ //Maintains chainability
1431
+ return this;
1432
+
1433
+ };
1434
+
1435
+ });
1436
+ $(function() {
1437
+
1438
+ //Move Down
1439
+ // --------
1440
+ // Handles the down keyboard navigation logic
1441
+
1442
+ $.selectBox.selectBoxIt.prototype.moveDown = function(callback) {
1443
+
1444
+ //Increments `currentFocus`, which represents the currently focused list item `id` attribute.
1445
+ this.currentFocus += 1;
1446
+
1447
+ //Determines whether the dropdown option the user is trying to go to is currently disabled
1448
+ var disabled = this.listItems.eq(this.currentFocus).data("disabled"),
1449
+
1450
+ hasNextEnabled = this.listItems.eq(this.currentFocus).nextAll("li").not("[data-disabled='true']").first().length;
1451
+
1452
+ //If the user has reached the top of the list
1453
+ if (this.currentFocus === this.listItems.length) {
1454
+
1455
+ //Does not allow the user to continue to go up the list
1456
+ this.currentFocus -= 1;
1457
+
1458
+ }
1459
+
1460
+ //If the option the user is trying to go to is disabled, but there is another enabled option
1461
+ else if (disabled && hasNextEnabled) {
1462
+
1463
+ //Blur the previously selected option
1464
+ this.listItems.eq(this.currentFocus - 1).blur();
1465
+
1466
+ //Call the `moveDown` method again
1467
+ this.moveDown();
1468
+
1469
+ //Exit the method
1470
+ return;
1471
+
1472
+ }
1473
+
1474
+ //If the option the user is trying to go to is disabled, but there is not another enabled option
1475
+ else if (disabled && !hasNextEnabled) {
1476
+
1477
+ this.currentFocus -= 1;
1478
+
1479
+ }
1480
+
1481
+ //If the user has not reached the bottom of the unordered list
1482
+ else {
1483
+
1484
+ //Blurs the previously focused list item
1485
+ //The jQuery `end()` method allows you to continue chaining while also using a different selector
1486
+ this.listItems.eq(this.currentFocus - 1).blur().end().
1487
+
1488
+ //Focuses the currently focused list item
1489
+ eq(this.currentFocus).focus();
1490
+
1491
+ //Calls `scrollToView` to make sure the `scrollTop` is correctly updated. The `down` user action
1492
+ this._scrollToView("down");
1493
+
1494
+ //Triggers the custom `moveDown` event on the original select box
1495
+ this.selectBox.trigger("moveDown");
1496
+
1497
+ }
1498
+
1499
+ //Provide callback function support
1500
+ this._callbackSupport(callback);
1501
+
1502
+ //Maintains chainability
1503
+ return this;
1504
+ };
1505
+
1506
+ //Move Up
1507
+ // ------
1508
+ // Handles the up keyboard navigation logic
1509
+ $.selectBox.selectBoxIt.prototype.moveUp = function(callback) {
1510
+
1511
+ //Increments `currentFocus`, which represents the currently focused list item `id` attribute.
1512
+ this.currentFocus -= 1;
1513
+
1514
+ //Determines whether the dropdown option the user is trying to go to is currently disabled
1515
+ var disabled = this.listItems.eq(this.currentFocus).data("disabled"),
1516
+
1517
+ hasPreviousEnabled = this.listItems.eq(this.currentFocus).prevAll("li").not("[data-disabled='true']").first().length;
1518
+
1519
+ //If the user has reached the top of the list
1520
+ if (this.currentFocus === -1) {
1521
+
1522
+ //Does not allow the user to continue to go up the list
1523
+ this.currentFocus += 1;
1524
+ }
1525
+
1526
+ //If the option the user is trying to go to is disabled and the user is not trying to go up after the user has reached the top of the list
1527
+ else if (disabled && hasPreviousEnabled) {
1528
+
1529
+ //Blur the previously selected option
1530
+ this.listItems.eq(this.currentFocus + 1).blur();
1531
+
1532
+ //Call the `moveUp` method again
1533
+ this.moveUp();
1534
+
1535
+ //Exit the method
1536
+ return;
1537
+ }
1538
+
1539
+ else if (disabled && !hasPreviousEnabled) {
1540
+
1541
+ this.currentFocus += 1;
1542
+
1543
+ }
1544
+
1545
+ //If the user has not reached the top of the unordered list
1546
+ else {
1547
+
1548
+ //Blurs the previously focused list item
1549
+ //The jQuery `end()` method allows you to continue chaining while also using a different selector
1550
+ this.listItems.eq(this.currentFocus + 1).blur().end().
1551
+
1552
+ //Focuses the currently focused list item
1553
+ eq(this.currentFocus).focus();
1554
+
1555
+ //Calls `scrollToView` to make sure the `scrollTop` is correctly updated. The `down` user action
1556
+ this._scrollToView("up");
1557
+
1558
+ //Triggers the custom `moveDown` event on the original select box
1559
+ this.selectBox.trigger("moveUp");
1560
+
1561
+ }
1562
+
1563
+ //Provide callback function support
1564
+ this._callbackSupport(callback);
1565
+
1566
+ //Maintains chainability
1567
+ return this;
1568
+ };
1569
+
1570
+ });
1571
+ $(function() {
1572
+
1573
+ // _Set Current Search Option
1574
+ // -------------------------
1575
+ // Sets the currently selected dropdown list search option
1576
+
1577
+ $.selectBox.selectBoxIt.prototype._setCurrentSearchOption = function(currentOption) {
1578
+
1579
+ // Does not change the current option if `showFirstOption` is false and the matched search item is the hidden first option.
1580
+ // Otherwise, the current option value is updated
1581
+ if (!(currentOption === 0 && !this.options.showFirstOption) && this.listItems.eq(currentOption).data("disabled") !== true) {
1582
+
1583
+ //Updates the default dropdown list text
1584
+ this.divText.text(this.textArray[currentOption]);
1585
+
1586
+ //Calls the `blur` event of the currently selected dropdown list option
1587
+ this.listItems.eq(this.currentFocus).blur();
1588
+
1589
+ //Sets `currentIndex` to the currently selected dropdown list option
1590
+ this.currentIndex = currentOption;
1591
+
1592
+ //Sets `currentFocus` to the currently selected dropdown list option
1593
+ this.currentFocus = currentOption;
1594
+
1595
+ //Focuses the currently selected dropdown list option
1596
+ this.listItems.eq(this.currentFocus).focus();
1597
+
1598
+ //Updates the scrollTop so that the currently selected dropdown list option is visible to the user
1599
+ this._scrollToView("search");
1600
+
1601
+ //Triggers the custom `search` event on the original select box
1602
+ this.selectBox.trigger("search");
1603
+
1604
+ }
1605
+
1606
+ //Maintains chainability
1607
+ return this;
1608
+
1609
+ };
1610
+
1611
+ // _Search Algorithm
1612
+ // -----------------
1613
+ // Uses regular expressions to find text matches
1614
+ $.selectBox.selectBoxIt.prototype._searchAlgorithm = function(currentIndex, alphaNumeric) {
1615
+
1616
+ // Boolean to determine if a pattern match exists
1617
+ var matchExists = false,
1618
+
1619
+ //Iteration variable used in the outermost for loop
1620
+ x,
1621
+
1622
+ //Iteration variable used in the nested for loop
1623
+ y,
1624
+
1625
+ //Variable used to cache the length of the text array (Small enhancement to speed up traversing)
1626
+ arrayLength;
1627
+
1628
+ //Loops through the text array to find a pattern match
1629
+ for (x = currentIndex, arrayLength = this.textArray.length; x < arrayLength; x += 1) {
1630
+
1631
+ //Nested for loop to help search for a pattern match with the currently traversed array item
1632
+ for (y = 0; y < arrayLength; y += 1) {
1633
+
1634
+ //Searches for a match
1635
+ if (this.textArray[y].search(alphaNumeric) !== -1) {
1636
+
1637
+ //`matchExists` is set to true if there is a match
1638
+ matchExists = true;
1639
+
1640
+ //Exits the nested for loop
1641
+ y = arrayLength;
1642
+
1643
+ }
1644
+
1645
+ } //End nested for loop
1646
+
1647
+ //If a match does not exist
1648
+ if (!matchExists) {
1649
+
1650
+ //Sets the current text to the last entered character
1651
+ this.currentText = this.currentText.charAt(this.currentText.length - 1).
1652
+
1653
+ //Escapes the regular expression to make sure special characters are seen as literal characters instead of special commands
1654
+ replace(/[|()\[{.+*?$\\]/g, "\\$0");
1655
+
1656
+ //Resets the regular expression with the new value of `self.currentText`
1657
+ alphaNumeric = new RegExp(this.currentText, "gi");
1658
+
1659
+ }
1660
+
1661
+ //Searches based on the first letter of the dropdown list options text if the currentText < 2 characters
1662
+ if (this.currentText.length < 2) {
1663
+
1664
+ //If there is a match based on the first character
1665
+ if ((this.textArray[x].charAt(0).search(alphaNumeric) !== -1)) {
1666
+
1667
+ //Sets properties of that dropdown list option to make it the currently selected option
1668
+ this._setCurrentSearchOption(x);
1669
+
1670
+ //Increments the current index by one
1671
+ this.currentIndex += 1;
1672
+
1673
+ //Exits the search
1674
+ return false;
1675
+
1676
+ }
1677
+ }
1678
+
1679
+ // If `self.currentText` > 1 character
1680
+ else {
1681
+
1682
+ // If there is a match based on the entire string
1683
+ if ((this.textArray[x].search(alphaNumeric) !== -1)) {
1684
+
1685
+ // Sets properties of that dropdown list option to make it the currently selected option
1686
+ this._setCurrentSearchOption(x);
1687
+
1688
+ // Exits the search
1689
+ return false;
1690
+ }
1691
+ }
1692
+
1693
+ // If the current text search is an exact match
1694
+ if (this.textArray[x].toLowerCase() === this.currentText.toLowerCase()) {
1695
+
1696
+ // Sets properties of that dropdown list option to make it the currently selected option
1697
+ this._setCurrentSearchOption(x);
1698
+
1699
+ // Resets the current text search to a blank string to start fresh again
1700
+ this.currentText = "";
1701
+
1702
+ // Exits the search
1703
+ return false;
1704
+
1705
+ }
1706
+ }
1707
+
1708
+ //Returns true if there is not a match at all
1709
+ return true;
1710
+ };
1711
+
1712
+ // Search
1713
+ // ------
1714
+ // Calls searchAlgorithm()
1715
+ $.selectBox.selectBoxIt.prototype.search = function(alphaNumericKey, rememberPreviousSearch, callback) {
1716
+
1717
+ // If the search method is being called internally by the plugin, and not externally as a method by a user
1718
+ if (rememberPreviousSearch) {
1719
+
1720
+ // Continued search with history from past searches. Properly escapes the regular expression
1721
+ this.currentText += alphaNumericKey.replace(/[|()\[{.+*?$\\]/g, "\\$0");
1722
+
1723
+ }
1724
+
1725
+ else {
1726
+
1727
+ // Brand new search. Properly escapes the regular expression
1728
+ this.currentText = alphaNumericKey.replace(/[|()\[{.+*?$\\]/g, "\\$0");
1729
+
1730
+ }
1731
+
1732
+ // Wraps the current user text search in a regular expression that is case insensitive and searches globally
1733
+ var alphaNumeric = new RegExp(this.currentText, "gi"),
1734
+
1735
+ // Calls `searchAlgorithm` which searches an array that contains all of the dropdown list option values.
1736
+ notFound = this._searchAlgorithm(this.currentIndex, alphaNumeric);
1737
+
1738
+ // Searches the list again if a match is not found. This is needed, because the first search started at the array indece of the currently selected dropdown list option, and does not search the options before the current array indece.
1739
+ // If there are many similar dropdown list options, starting the search at the indece of the currently selected dropdown list option is needed to properly traverse the text array.
1740
+ if (notFound) {
1741
+
1742
+ // Searches the dropdown list values starting from the beginning of the text array
1743
+ this._searchAlgorithm(0, alphaNumeric);
1744
+
1745
+ }
1746
+
1747
+ // Provide callback function support
1748
+ this._callbackSupport(callback);
1749
+
1750
+ // Maintains chainability
1751
+ return this;
1752
+
1753
+ };
1754
+
1755
+ });
1756
+ $(function() {
1757
+
1758
+ //Set Option
1759
+ // ----------
1760
+ // Accepts an string key, a value, and a callback function to replace a single
1761
+ // property of the plugin options object
1762
+
1763
+ $.selectBox.selectBoxIt.prototype.setOption = function(key, value, callback) {
1764
+
1765
+ //If a user sets the `showFirstOption` to false
1766
+ if (key === "showFirstOption" && !value) {
1767
+
1768
+ //Hides the first option in the dropdown list
1769
+ this.listItems.eq(0).hide();
1770
+
1771
+ }
1772
+
1773
+ //If a user sets the `showFirstOption` to true
1774
+ else if (key === "showFirstOption" && value) {
1775
+
1776
+ //Shows the first option in the dropdown list
1777
+ this.listItems.eq(0).show();
1778
+
1779
+ }
1780
+
1781
+ else if(key === "defaultIcon" && value) {
1782
+
1783
+ this.divImage.attr("class", value);
1784
+
1785
+ }
1786
+
1787
+ else if(key === "downArrowIcon" && value) {
1788
+
1789
+ this.downArrow.attr("class", value);
1790
+
1791
+ }
1792
+
1793
+ //If a user sets the defaultText option
1794
+ else if (key === "defaultText") {
1795
+
1796
+ //Sets the new dropdown list default text
1797
+ this.divText.text(value);
1798
+
1799
+ }
1800
+
1801
+ $.Widget.prototype._setOption.apply(this, arguments);
1802
+
1803
+ //Provides callback function support
1804
+ this._callbackSupport(callback);
1805
+
1806
+ //Maintains chainability
1807
+ return this;
1808
+ };
1809
+
1810
+ });
1811
+ $(function() {
1812
+
1813
+ //Set Options
1814
+ // ----------
1815
+ // Accepts an object to replace plugin options
1816
+ // properties of the plugin options object
1817
+
1818
+ $.selectBox.selectBoxIt.prototype.setOptions = function(newOptions, callback) {
1819
+
1820
+ $.Widget.prototype._setOptions.apply(this, arguments);
1821
+
1822
+ //If the `showFirstOption` option is true
1823
+ if (this.options.showFirstOption) {
1824
+
1825
+ //Shows the first option in the dropdown list
1826
+ this.listItems.eq(0).show();
1827
+
1828
+ }
1829
+
1830
+ //If the `showFirstOption` option is false
1831
+ else {
1832
+
1833
+ //Hides the first option in the dropdown list
1834
+ this.listItems.eq(0).hide();
1835
+
1836
+ }
1837
+
1838
+ if(this.options.defaultIcon) {
1839
+
1840
+ this.divImage.attr("class", this.options.defaultIcon);
1841
+
1842
+ }
1843
+
1844
+ if(this.options.downArrowIcon) {
1845
+
1846
+ this.downArrow.attr("class", this.options.downArrowIcon);
1847
+
1848
+ }
1849
+
1850
+ //If the defaultText option is set, make sure the dropdown list default text reflects this value
1851
+ if (this.options.defaultText) {
1852
+
1853
+ this.divText.text(this.options.defaultText);
1854
+
1855
+ }
1856
+
1857
+ //Provide callback function support
1858
+ this._callbackSupport(callback);
1859
+
1860
+ return this;
1861
+
1862
+ };
1863
+
1864
+ });
1865
+ $(function() {
1866
+
1867
+ //Wait
1868
+ // ---
1869
+ // Delays execution by the amount of time
1870
+ // specified by the parameter
1871
+
1872
+ $.selectBox.selectBoxIt.prototype.wait = function(time, callback) {
1873
+
1874
+ var self = this,
1875
+
1876
+ //The timeout variable stores a Deferred Object, which will be resolved after the time specified in the parameter
1877
+ timeout = this.returnTimeout(time);
1878
+
1879
+ //Once the Deferred object is resolved, call the callback function
1880
+ timeout.then(function() {
1881
+
1882
+ //Provide callback function support
1883
+ self._callbackSupport(callback);
1884
+
1885
+ });
1886
+
1887
+ //Maintains chainability
1888
+ return this;
1889
+
1890
+ };
1891
+
1892
+ //Return timeout
1893
+ // -------------
1894
+ // Returns a Deferred Object after the time
1895
+ // specified by the parameter
1896
+
1897
+ $.selectBox.selectBoxIt.prototype.returnTimeout = function(time) {
1898
+
1899
+ //Returns a Deferred Object
1900
+ return $.Deferred(function(dfd) {
1901
+
1902
+ //Call the JavaScript `setTimeout function and resolve the Deferred Object
1903
+ setTimeout(dfd.resolve, time);
1904
+
1905
+ });
1906
+
1907
+ };
1908
+
1909
+ });