merb-admin 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. data/LICENSE +20 -0
  2. data/README.markdown +58 -0
  3. data/Rakefile +64 -0
  4. data/app/controllers/application.rb +6 -0
  5. data/app/controllers/main.rb +145 -0
  6. data/app/helpers/application_helper.rb +64 -0
  7. data/app/helpers/main_helper.rb +122 -0
  8. data/app/views/layout/_message.html.erb +10 -0
  9. data/app/views/layout/dashboard.html.erb +34 -0
  10. data/app/views/layout/form.html.erb +48 -0
  11. data/app/views/layout/list.html.erb +42 -0
  12. data/app/views/main/_belongs_to.html.erb +29 -0
  13. data/app/views/main/_big_decimal.html.erb +12 -0
  14. data/app/views/main/_boolean.html.erb +7 -0
  15. data/app/views/main/_date.html.erb +12 -0
  16. data/app/views/main/_datetime.html.erb +12 -0
  17. data/app/views/main/_float.html.erb +12 -0
  18. data/app/views/main/_has_many.html.erb +16 -0
  19. data/app/views/main/_has_one.html.erb +30 -0
  20. data/app/views/main/_integer.html.erb +18 -0
  21. data/app/views/main/_properties.html.erb +18 -0
  22. data/app/views/main/_string.html.erb +12 -0
  23. data/app/views/main/_text.html.erb +11 -0
  24. data/app/views/main/_time.html.erb +12 -0
  25. data/app/views/main/_timestamp.html.erb +12 -0
  26. data/app/views/main/delete.html.erb +12 -0
  27. data/app/views/main/edit.html.erb +25 -0
  28. data/app/views/main/index.html.erb +22 -0
  29. data/app/views/main/list.html.erb +72 -0
  30. data/app/views/main/new.html.erb +22 -0
  31. data/lib/abstract_model.rb +67 -0
  32. data/lib/activerecord_support.rb +169 -0
  33. data/lib/datamapper_support.rb +157 -0
  34. data/lib/generic_support.rb +17 -0
  35. data/lib/merb-admin/merbtasks.rb +103 -0
  36. data/lib/merb-admin/slicetasks.rb +20 -0
  37. data/lib/merb-admin/spectasks.rb +53 -0
  38. data/lib/merb-admin.rb +99 -0
  39. data/public/images/arrow-down.gif +0 -0
  40. data/public/images/arrow-up.gif +0 -0
  41. data/public/images/changelist-bg.gif +0 -0
  42. data/public/images/changelist-bg_rtl.gif +0 -0
  43. data/public/images/chooser-bg.gif +0 -0
  44. data/public/images/chooser_stacked-bg.gif +0 -0
  45. data/public/images/default-bg-reverse.gif +0 -0
  46. data/public/images/default-bg.gif +0 -0
  47. data/public/images/deleted-overlay.gif +0 -0
  48. data/public/images/icon-no.gif +0 -0
  49. data/public/images/icon-unknown.gif +0 -0
  50. data/public/images/icon-yes.gif +0 -0
  51. data/public/images/icon_addlink.gif +0 -0
  52. data/public/images/icon_alert.gif +0 -0
  53. data/public/images/icon_calendar.gif +0 -0
  54. data/public/images/icon_changelink.gif +0 -0
  55. data/public/images/icon_clock.gif +0 -0
  56. data/public/images/icon_deletelink.gif +0 -0
  57. data/public/images/icon_error.gif +0 -0
  58. data/public/images/icon_searchbox.png +0 -0
  59. data/public/images/icon_success.gif +0 -0
  60. data/public/images/inline-delete-8bit.png +0 -0
  61. data/public/images/inline-delete.png +0 -0
  62. data/public/images/inline-restore-8bit.png +0 -0
  63. data/public/images/inline-restore.png +0 -0
  64. data/public/images/inline-splitter-bg.gif +0 -0
  65. data/public/images/nav-bg-grabber.gif +0 -0
  66. data/public/images/nav-bg-reverse.gif +0 -0
  67. data/public/images/nav-bg.gif +0 -0
  68. data/public/images/selector-add.gif +0 -0
  69. data/public/images/selector-addall.gif +0 -0
  70. data/public/images/selector-remove.gif +0 -0
  71. data/public/images/selector-removeall.gif +0 -0
  72. data/public/images/selector-search.gif +0 -0
  73. data/public/images/selector_stacked-add.gif +0 -0
  74. data/public/images/selector_stacked-remove.gif +0 -0
  75. data/public/images/tool-left.gif +0 -0
  76. data/public/images/tool-left_over.gif +0 -0
  77. data/public/images/tool-right.gif +0 -0
  78. data/public/images/tool-right_over.gif +0 -0
  79. data/public/images/tooltag-add.gif +0 -0
  80. data/public/images/tooltag-add_over.gif +0 -0
  81. data/public/images/tooltag-arrowright.gif +0 -0
  82. data/public/images/tooltag-arrowright_over.gif +0 -0
  83. data/public/javascripts/CollapsedFieldsets.js +85 -0
  84. data/public/javascripts/DateTimeShortcuts.js +255 -0
  85. data/public/javascripts/RelatedObjectLookups.js +96 -0
  86. data/public/javascripts/SelectBox.js +111 -0
  87. data/public/javascripts/SelectFilter2.js +113 -0
  88. data/public/javascripts/actions.js +39 -0
  89. data/public/javascripts/calendar.js +143 -0
  90. data/public/javascripts/core.js +176 -0
  91. data/public/javascripts/dateparse.js +233 -0
  92. data/public/javascripts/getElementsBySelector.js +167 -0
  93. data/public/javascripts/i18n.js +33 -0
  94. data/public/javascripts/master.js +0 -0
  95. data/public/javascripts/ordering.js +137 -0
  96. data/public/javascripts/timeparse.js +94 -0
  97. data/public/javascripts/urlify.js +140 -0
  98. data/public/stylesheets/base.css +746 -0
  99. data/public/stylesheets/changelists.css +269 -0
  100. data/public/stylesheets/dashboard.css +24 -0
  101. data/public/stylesheets/forms.css +327 -0
  102. data/public/stylesheets/global.css +142 -0
  103. data/public/stylesheets/ie.css +51 -0
  104. data/public/stylesheets/layout.css +29 -0
  105. data/public/stylesheets/login.css +54 -0
  106. data/public/stylesheets/master.css +2 -0
  107. data/public/stylesheets/null.css +1 -0
  108. data/public/stylesheets/patch-iewin.css +8 -0
  109. data/public/stylesheets/rtl.css +206 -0
  110. data/public/stylesheets/widgets.css +506 -0
  111. data/schema/migrations/001_create_divisions_migration.rb +15 -0
  112. data/schema/migrations/002_create_drafts_migration.rb +21 -0
  113. data/schema/migrations/003_create_leagues_migration.rb +13 -0
  114. data/schema/migrations/004_create_players_migration.rb +23 -0
  115. data/schema/migrations/005_create_teams_migration.rb +18 -0
  116. data/spec/controllers/main_spec.rb +25 -0
  117. data/spec/models/activerecord/division.rb +8 -0
  118. data/spec/models/activerecord/draft.rb +16 -0
  119. data/spec/models/activerecord/league.rb +6 -0
  120. data/spec/models/activerecord/player.rb +10 -0
  121. data/spec/models/activerecord/team.rb +11 -0
  122. data/spec/models/datamapper/division.rb +12 -0
  123. data/spec/models/datamapper/draft.rb +18 -0
  124. data/spec/models/datamapper/league.rb +11 -0
  125. data/spec/models/datamapper/player.rb +21 -0
  126. data/spec/models/datamapper/team.rb +15 -0
  127. data/spec/requests/main_spec.rb +475 -0
  128. data/spec/spec_helper.rb +112 -0
  129. metadata +209 -0
@@ -0,0 +1,157 @@
1
+ module MerbAdmin
2
+ class AbstractModel
3
+ module DatamapperSupport
4
+ def count(options = {})
5
+ model.count(options)
6
+ end
7
+
8
+ def get(id)
9
+ model.get(id).extend(InstanceMethods)
10
+ end
11
+
12
+ def first(options = {})
13
+ model.first(options).extend(InstanceMethods)
14
+ end
15
+
16
+ def all(options = {})
17
+ model.all(options)
18
+ end
19
+
20
+ def all_in(ids, options = {})
21
+ options[:id] = ids
22
+ model.all(options)
23
+ end
24
+
25
+ def paginated(options = {})
26
+ page = options.delete(:page) || 1
27
+ per_page = options.delete(:per_page) || MerbAdmin[:per_page]
28
+
29
+ page_count = (count(options).to_f / per_page).ceil
30
+
31
+ options.merge!({
32
+ :limit => per_page,
33
+ :offset => (page - 1) * per_page
34
+ })
35
+
36
+ [page_count, all(options)]
37
+ end
38
+
39
+ def create(params = {})
40
+ model.create(params)
41
+ end
42
+
43
+ def new(params = {})
44
+ model.new(params).extend(InstanceMethods)
45
+ end
46
+
47
+ def destroy_all!
48
+ model.all.destroy!
49
+ end
50
+
51
+ def has_many_associations
52
+ associations.select do |association|
53
+ association[:type] == :has_many
54
+ end
55
+ end
56
+
57
+ def has_one_associations
58
+ associations.select do |association|
59
+ association[:type] == :has_one
60
+ end
61
+ end
62
+
63
+ def belongs_to_associations
64
+ associations.select do |association|
65
+ association[:type] == :belongs_to
66
+ end
67
+ end
68
+
69
+ def associations
70
+ model.relationships.to_a.map do |name, association|
71
+ {
72
+ :name => name,
73
+ :pretty_name => name.to_s.gsub('_', ' ').capitalize,
74
+ :type => association_type_lookup(association),
75
+ :parent_model => association.parent_model,
76
+ :parent_key => association.parent_key.map{|r| r.name},
77
+ :child_model => association.child_model,
78
+ :child_key => association.child_key.map{|r| r.name},
79
+ }
80
+ end
81
+ end
82
+
83
+ def properties
84
+ model.properties.map do |property|
85
+ {
86
+ :name => property.name,
87
+ :pretty_name => property.name.to_s.gsub('_', ' ').capitalize,
88
+ :type => type_lookup(property),
89
+ :length => property.length,
90
+ :nullable? => property.nullable?,
91
+ :serial? => property.serial?,
92
+ :key? => property.key?,
93
+ :flag_map => property.type.respond_to?(:flag_map) ? property.type.flag_map : nil,
94
+ }
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def association_type_lookup(association)
101
+ if self.model == association.parent_model
102
+ association.options[:max] > 1 ? :has_many : :has_one
103
+ elsif self.model == association.child_model
104
+ :belongs_to
105
+ else
106
+ raise "Unknown association type"
107
+ end
108
+ end
109
+
110
+ def type_lookup(property)
111
+ type = {
112
+ BigDecimal => :big_decimal,
113
+ DataMapper::Types::Boolean => :boolean,
114
+ DataMapper::Types::ParanoidBoolean => :boolean,
115
+ DataMapper::Types::ParanoidDateTime => :datetime,
116
+ DataMapper::Types::Serial => :integer,
117
+ DataMapper::Types::Text => :text,
118
+ Date => :date,
119
+ DateTime => :datetime,
120
+ Fixnum => :integer,
121
+ Float => :float,
122
+ Integer => :integer,
123
+ String => :string,
124
+ Time => :time,
125
+ }
126
+ type[property.type] || type[property.primitive]
127
+ end
128
+
129
+ module InstanceMethods
130
+ def id
131
+ super
132
+ end
133
+
134
+ def save
135
+ super
136
+ end
137
+
138
+ def destroy
139
+ super
140
+ end
141
+
142
+ def update_attributes(attributes)
143
+ super
144
+ end
145
+
146
+ def errors
147
+ super
148
+ end
149
+
150
+ def clear_association(association)
151
+ association.clear
152
+ end
153
+ end
154
+
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,17 @@
1
+ module MerbAdmin
2
+ class AbstractModel
3
+ module GenericSupport
4
+ def singular_name
5
+ model.to_s.snake_case.to_sym
6
+ end
7
+
8
+ def plural_name
9
+ model.to_s.snake_case.pluralize.to_sym
10
+ end
11
+
12
+ def pretty_name
13
+ model.to_s.snake_case.gsub('_', ' ').capitalize
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,103 @@
1
+ namespace :slices do
2
+ namespace :"merb-admin" do
3
+
4
+ desc "Install MerbAdmin"
5
+ task :install => [:preflight, :setup_directories, :copy_assets, :migrate]
6
+
7
+ desc "Test for any dependencies"
8
+ task :preflight do # see slicetasks.rb
9
+ end
10
+
11
+ desc "Setup directories"
12
+ task :setup_directories do
13
+ puts "Creating directories for host application"
14
+ MerbAdmin.mirrored_components.each do |type|
15
+ if File.directory?(MerbAdmin.dir_for(type))
16
+ if !File.directory?(dst_path = MerbAdmin.app_dir_for(type))
17
+ relative_path = dst_path.relative_path_from(Merb.root)
18
+ puts "- creating directory :#{type} #{File.basename(Merb.root) / relative_path}"
19
+ mkdir_p(dst_path)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ # desc "Copy stub files to host application"
26
+ # task :stubs do
27
+ # puts "Copying stubs for MerbAdmin - resolves any collisions"
28
+ # copied, preserved = MerbAdmin.mirror_stubs!
29
+ # puts "- no files to copy" if copied.empty? && preserved.empty?
30
+ # copied.each { |f| puts "- copied #{f}" }
31
+ # preserved.each { |f| puts "! preserved override as #{f}" }
32
+ # end
33
+
34
+ # desc "Copy stub files and views to host application"
35
+ # task :patch => [ "stubs", "freeze:views" ]
36
+
37
+ desc "Copy public assets to host application"
38
+ task :copy_assets do
39
+ puts "Copying assets for MerbAdmin - resolves any collisions"
40
+ copied, preserved = MerbAdmin.mirror_public!
41
+ puts "- no files to copy" if copied.empty? && preserved.empty?
42
+ copied.each { |f| puts "- copied #{f}" }
43
+ preserved.each { |f| puts "! preserved override as #{f}" }
44
+ end
45
+
46
+ desc "Migrate the database"
47
+ task :migrate do # see slicetasks.rb
48
+ end
49
+
50
+ desc "Freeze MerbAdmin into your app (only merb-admin/app)"
51
+ task :freeze => [ "freeze:app" ]
52
+
53
+ namespace :freeze do
54
+
55
+ # desc "Freezes MerbAdmin by installing the gem into application/gems"
56
+ # task :gem do
57
+ # ENV["GEM"] ||= "merb-admin"
58
+ # Rake::Task['slices:install_as_gem'].invoke
59
+ # end
60
+
61
+ desc "Freezes MerbAdmin by copying all files from merb-admin/app to your application"
62
+ task :app do
63
+ puts "Copying all merb-admin/app files to your application - resolves any collisions"
64
+ copied, preserved = MerbAdmin.mirror_app!
65
+ puts "- no files to copy" if copied.empty? && preserved.empty?
66
+ copied.each { |f| puts "- copied #{f}" }
67
+ preserved.each { |f| puts "! preserved override as #{f}" }
68
+ end
69
+
70
+ desc "Freeze all views into your application for easy modification"
71
+ task :views do
72
+ puts "Copying all view templates to your application - resolves any collisions"
73
+ copied, preserved = MerbAdmin.mirror_files_for :view
74
+ puts "- no files to copy" if copied.empty? && preserved.empty?
75
+ copied.each { |f| puts "- copied #{f}" }
76
+ preserved.each { |f| puts "! preserved override as #{f}" }
77
+ end
78
+
79
+ desc "Freeze all models into your application for easy modification"
80
+ task :models do
81
+ puts "Copying all models to your application - resolves any collisions"
82
+ copied, preserved = MerbAdmin.mirror_files_for :model
83
+ puts "- no files to copy" if copied.empty? && preserved.empty?
84
+ copied.each { |f| puts "- copied #{f}" }
85
+ preserved.each { |f| puts "! preserved override as #{f}" }
86
+ end
87
+
88
+ desc "Freezes MerbAdmin as a gem and copies over merb-admin/app"
89
+ task :app_with_gem => [:gem, :app]
90
+
91
+ desc "Freezes MerbAdmin by unpacking all files into your application"
92
+ task :unpack do
93
+ puts "Unpacking MerbAdmin files to your application - resolves any collisions"
94
+ copied, preserved = MerbAdmin.unpack_slice!
95
+ puts "- no files to copy" if copied.empty? && preserved.empty?
96
+ copied.each { |f| puts "- copied #{f}" }
97
+ preserved.each { |f| puts "! preserved override as #{f}" }
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,20 @@
1
+ namespace :slices do
2
+ namespace :"merb-admin" do
3
+
4
+ # add your own merb-admin tasks here
5
+
6
+ # # Uncomment the following lines and edit the pre defined tasks
7
+ #
8
+ # # implement this to test for structural/code dependencies
9
+ # # like certain directories or availability of other files
10
+ # desc "Test for any dependencies"
11
+ # task :preflight do
12
+ # end
13
+ #
14
+ # # implement this to perform any database related setup steps
15
+ # desc "Migrate the database"
16
+ # task :migrate do
17
+ # end
18
+
19
+ end
20
+ end
@@ -0,0 +1,53 @@
1
+ namespace :slices do
2
+ namespace :"merb-admin" do
3
+
4
+ desc "Run slice specs within the host application context"
5
+ task :spec => [ "spec:explain", "spec:default" ]
6
+
7
+ namespace :spec do
8
+
9
+ slice_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
10
+
11
+ task :explain do
12
+ puts "\nNote: By running MerbAdmin specs inside the application context any\n" +
13
+ "overrides could break existing specs. This isn't always a problem,\n" +
14
+ "especially in the case of views. Use these spec tasks to check how\n" +
15
+ "well your application conforms to the original slice implementation."
16
+ end
17
+
18
+ Spec::Rake::SpecTask.new('default') do |t|
19
+ t.spec_opts = ["--format", "specdoc", "--colour"]
20
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
21
+ end
22
+
23
+ desc "Run all model specs, run a spec for a specific Model with MODEL=MyModel"
24
+ Spec::Rake::SpecTask.new('model') do |t|
25
+ t.spec_opts = ["--format", "specdoc", "--colour"]
26
+ if(ENV['MODEL'])
27
+ t.spec_files = Dir["#{slice_root}/spec/models/**/#{ENV['MODEL']}_spec.rb"].sort
28
+ else
29
+ t.spec_files = Dir["#{slice_root}/spec/models/**/*_spec.rb"].sort
30
+ end
31
+ end
32
+
33
+ desc "Run all request specs, run a spec for a specific request with REQUEST=MyRequest"
34
+ Spec::Rake::SpecTask.new('request') do |t|
35
+ t.spec_opts = ["--format", "specdoc", "--colour"]
36
+ if(ENV['REQUEST'])
37
+ t.spec_files = Dir["#{slice_root}/spec/requests/**/#{ENV['REQUEST']}_spec.rb"].sort
38
+ else
39
+ t.spec_files = Dir["#{slice_root}/spec/requests/**/*_spec.rb"].sort
40
+ end
41
+ end
42
+
43
+ desc "Run all specs and output the result in html"
44
+ Spec::Rake::SpecTask.new('html') do |t|
45
+ t.spec_opts = ["--format", "html"]
46
+ t.libs = ['lib', 'server/lib' ]
47
+ t.spec_files = Dir["#{slice_root}/spec/**/*_spec.rb"].sort
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end
data/lib/merb-admin.rb ADDED
@@ -0,0 +1,99 @@
1
+ if defined?(Merb::Plugins)
2
+
3
+ $:.unshift File.dirname(__FILE__)
4
+
5
+ dependency 'merb-slices', :immediate => true
6
+ Merb::Plugins.add_rakefiles "merb-admin/merbtasks", "merb-admin/slicetasks", "merb-admin/spectasks"
7
+
8
+ # Register the Slice for the current host application
9
+ Merb::Slices::register(__FILE__)
10
+
11
+ # Slice configuration - set this in a before_app_loads callback.
12
+ # By default a Slice uses its own layout, so you can swicht to
13
+ # the main application layout or no layout at all if needed.
14
+ #
15
+ # Configuration options:
16
+ # :layout - the layout to use; defaults to :merb-admin
17
+ # :mirror - which path component types to use on copy operations; defaults to all
18
+ Merb::Slices::config[:merb_admin][:layout] ||= :merb_admin
19
+ Merb::Slices::config[:merb_admin][:per_page] ||= 100
20
+
21
+ # All Slice code is expected to be namespaced inside a module
22
+ module MerbAdmin
23
+
24
+ # Slice metadata
25
+ self.description = "MerbAdmin is a Merb plugin that provides an easy-to-use interface for managing your data."
26
+ self.version = "0.4.3"
27
+ self.author = "Erik Michaels-Ober"
28
+
29
+ # Stub classes loaded hook - runs before LoadClasses BootLoader
30
+ # right after a slice's classes have been loaded internally.
31
+ def self.loaded
32
+ end
33
+
34
+ # Initialization hook - runs before AfterAppLoads BootLoader
35
+ def self.init
36
+ end
37
+
38
+ # Activation hook - runs after AfterAppLoads BootLoader
39
+ def self.activate
40
+ end
41
+
42
+ # Deactivation hook - triggered by Merb::Slices.deactivate(MerbAdmin)
43
+ def self.deactivate
44
+ end
45
+
46
+ def self.setup_router(scope)
47
+ scope.match("/", :method => :get).
48
+ to(:controller => "main", :action => "index").
49
+ name(:admin_dashboard)
50
+
51
+ scope.match("/:model_name", :method => :get).
52
+ to(:controller => "main", :action => "list").
53
+ name(:admin_list)
54
+
55
+ scope.match("/:model_name/new", :method => :get).
56
+ to(:controller => "main", :action => "new").
57
+ name(:admin_new)
58
+
59
+ scope.match("/:model_name/:id/edit", :method => :get).
60
+ to(:controller => "main", :action => "edit").
61
+ name(:admin_edit)
62
+
63
+ scope.match("/:model_name", :method => :post).
64
+ to(:controller => "main", :action => "create").
65
+ name(:admin_create)
66
+
67
+ scope.match("/:model_name/:id", :method => :put).
68
+ to(:controller => "main", :action => "update").
69
+ name(:admin_update)
70
+
71
+ scope.match("/:model_name/:id/delete", :method => :get).
72
+ to(:controller => "main", :action => "delete").
73
+ name(:admin_delete)
74
+
75
+ scope.match("/:model_name/:id(.:format)", :method => :delete).
76
+ to(:controller => "main", :action => "destroy").
77
+ name(:admin_destroy)
78
+ end
79
+
80
+ end
81
+
82
+ # Setup the slice layout for MerbAdmin
83
+ #
84
+ # Use MerbAdmin.push_path and MerbAdmin.push_app_path
85
+ # to set paths to merb-admin-level and app-level paths. Example:
86
+ #
87
+ # MerbAdmin.push_path(:application, MerbAdmin.root)
88
+ # MerbAdmin.push_app_path(:application, Merb.root / 'slices' / 'merb-admin')
89
+ # ...
90
+ #
91
+ # Any component path that hasn't been set will default to MerbAdmin.root
92
+ #
93
+ # Or just call setup_default_structure! to setup a basic Merb MVC structure.
94
+ MerbAdmin.setup_default_structure!
95
+
96
+ # Add dependencies for other MerbAdmin classes below. Example:
97
+ # dependency "merb-admin/other"
98
+
99
+ end
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,85 @@
1
+ // Finds all fieldsets with class="collapse", collapses them, and gives each
2
+ // one a "Show" link that uncollapses it. The "Show" link becomes a "Hide"
3
+ // link when the fieldset is visible.
4
+
5
+ function findForm(node) {
6
+ // returns the node of the form containing the given node
7
+ if (node.tagName.toLowerCase() != 'form') {
8
+ return findForm(node.parentNode);
9
+ }
10
+ return node;
11
+ }
12
+
13
+ var CollapsedFieldsets = {
14
+ collapse_re: /\bcollapse\b/, // Class of fieldsets that should be dealt with.
15
+ collapsed_re: /\bcollapsed\b/, // Class that fieldsets get when they're hidden.
16
+ collapsed_class: 'collapsed',
17
+ init: function() {
18
+ var fieldsets = document.getElementsByTagName('fieldset');
19
+ var collapsed_seen = false;
20
+ for (var i = 0, fs; fs = fieldsets[i]; i++) {
21
+ // Collapse this fieldset if it has the correct class, and if it
22
+ // doesn't have any errors. (Collapsing shouldn't apply in the case
23
+ // of error messages.)
24
+ if (fs.className.match(CollapsedFieldsets.collapse_re) && !CollapsedFieldsets.fieldset_has_errors(fs)) {
25
+ collapsed_seen = true;
26
+ // Give it an additional class, used by CSS to hide it.
27
+ fs.className += ' ' + CollapsedFieldsets.collapsed_class;
28
+ // (<a id="fieldsetcollapser3" class="collapse-toggle" href="#">Show</a>)
29
+ var collapse_link = document.createElement('a');
30
+ collapse_link.className = 'collapse-toggle';
31
+ collapse_link.id = 'fieldsetcollapser' + i;
32
+ collapse_link.onclick = new Function('CollapsedFieldsets.show('+i+'); return false;');
33
+ collapse_link.href = '#';
34
+ collapse_link.innerHTML = gettext('Show');
35
+ var h2 = fs.getElementsByTagName('h2')[0];
36
+ h2.appendChild(document.createTextNode(' ('));
37
+ h2.appendChild(collapse_link);
38
+ h2.appendChild(document.createTextNode(')'));
39
+ }
40
+ }
41
+ if (collapsed_seen) {
42
+ // Expand all collapsed fieldsets when form is submitted.
43
+ addEvent(findForm(document.getElementsByTagName('fieldset')[0]), 'submit', function() { CollapsedFieldsets.uncollapse_all(); });
44
+ }
45
+ },
46
+ fieldset_has_errors: function(fs) {
47
+ // Returns true if any fields in the fieldset have validation errors.
48
+ var divs = fs.getElementsByTagName('div');
49
+ for (var i=0; i<divs.length; i++) {
50
+ if (divs[i].className.match(/\berrors\b/)) {
51
+ return true;
52
+ }
53
+ }
54
+ return false;
55
+ },
56
+ show: function(fieldset_index) {
57
+ var fs = document.getElementsByTagName('fieldset')[fieldset_index];
58
+ // Remove the class name that causes the "display: none".
59
+ fs.className = fs.className.replace(CollapsedFieldsets.collapsed_re, '');
60
+ // Toggle the "Show" link to a "Hide" link
61
+ var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index);
62
+ collapse_link.onclick = new Function('CollapsedFieldsets.hide('+fieldset_index+'); return false;');
63
+ collapse_link.innerHTML = gettext('Hide');
64
+ },
65
+ hide: function(fieldset_index) {
66
+ var fs = document.getElementsByTagName('fieldset')[fieldset_index];
67
+ // Add the class name that causes the "display: none".
68
+ fs.className += ' ' + CollapsedFieldsets.collapsed_class;
69
+ // Toggle the "Hide" link to a "Show" link
70
+ var collapse_link = document.getElementById('fieldsetcollapser' + fieldset_index);
71
+ collapse_link.onclick = new Function('CollapsedFieldsets.show('+fieldset_index+'); return false;');
72
+ collapse_link.innerHTML = gettext('Show');
73
+ },
74
+
75
+ uncollapse_all: function() {
76
+ var fieldsets = document.getElementsByTagName('fieldset');
77
+ for (var i=0; i<fieldsets.length; i++) {
78
+ if (fieldsets[i].className.match(CollapsedFieldsets.collapsed_re)) {
79
+ CollapsedFieldsets.show(i);
80
+ }
81
+ }
82
+ }
83
+ }
84
+
85
+ addEvent(window, 'load', CollapsedFieldsets.init);