backlog 0.36.2 → 0.37.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +16 -4
- data/Gemfile.lock +130 -0
- data/History.txt +17 -0
- data/README.txt +0 -2
- data/Rakefile +17 -7
- data/app/controllers/absences_controller.rb +1 -2
- data/app/controllers/application_controller.rb +13 -14
- data/app/controllers/application_controller.rb.rails2 +186 -0
- data/app/controllers/estimates_controller.rb +1 -1
- data/app/controllers/groups_controller.rb +3 -1
- data/app/controllers/periods_controller.rb +61 -50
- data/app/controllers/{backlogs_controller.rb → projects_controller.rb} +35 -35
- data/app/controllers/search_controller.rb +2 -2
- data/app/controllers/tasks_controller.rb +11 -11
- data/app/controllers/user_controller.rb +6 -4
- data/app/controllers/welcome_controller.rb +4 -4
- data/app/controllers/work_locks_controller.rb +2 -2
- data/app/controllers/works_controller.rb +31 -31
- data/app/helpers/application_helper.rb +2 -2
- data/app/helpers/application_helper.rb.rails2 +118 -0
- data/app/helpers/periods_helper.rb +3 -3
- data/app/helpers/{backlogs_helper.rb → projects_helper.rb} +5 -5
- data/app/helpers/user_helper.rb +2 -2
- data/app/{models → mailers}/user_notify.rb +1 -0
- data/app/models/absence.rb +2 -2
- data/{lib → app/models}/clock.rb +0 -0
- data/app/models/estimate.rb +2 -2
- data/app/models/party.rb +1 -2
- data/app/models/period.rb +17 -18
- data/app/models/{backlog.rb → project.rb} +2 -2
- data/app/models/sidebar.rb +2 -2
- data/app/models/task.rb +28 -28
- data/app/models/user.rb +5 -4
- data/app/models/work.rb +29 -23
- data/app/models/work_lock_nagger.rb +1 -1
- data/app/models/works_report_filter.rb +4 -4
- data/app/views/customers/_name_list.rhtml +1 -1
- data/app/views/layouts/_headers.rhtml +2 -1
- data/app/views/layouts/_left_top.rhtml +32 -29
- data/app/views/layouts/_shortcuts.rhtml +15 -9
- data/app/views/layouts/_shortcuts_js.rhtml +4 -4
- data/app/views/layouts/mwrt002.html.erb +44 -0
- data/app/views/periods/_burn_down_chart.rhtml +1 -1
- data/app/views/periods/_show_active.rhtml +3 -3
- data/app/views/periods/_title.rhtml +1 -1
- data/app/views/{backlogs → projects}/_buttons.rhtml +4 -4
- data/app/views/projects/_form.rhtml +44 -0
- data/app/views/projects/_name_list.rhtml +5 -0
- data/app/views/projects/edit.rhtml +14 -0
- data/app/views/{backlogs → projects}/finish_task.rjs +0 -0
- data/app/views/projects/list.rhtml +16 -0
- data/app/views/{backlogs → projects}/move_task_to_period.rjs +0 -0
- data/app/views/{backlogs → projects}/new.rhtml +1 -1
- data/app/views/{backlogs → projects}/reopen_task.rjs +0 -0
- data/app/views/{backlogs → projects}/show.rhtml +6 -6
- data/app/views/search/results.rhtml +3 -3
- data/app/views/tasks/_backlog_header.rhtml +4 -4
- data/app/views/tasks/_completed.rhtml +2 -2
- data/app/views/tasks/_form.rhtml +13 -13
- data/app/views/tasks/_task.rhtml +10 -10
- data/app/views/tasks/edit.rhtml +1 -1
- data/app/views/tasks/list.rhtml +3 -3
- data/app/views/tasks/list_started.rhtml +4 -4
- data/app/views/tasks/start_work.rjs +1 -1
- data/app/views/user/login.rhtml +1 -1
- data/app/views/user/signup.rhtml +1 -1
- data/app/views/user/welcome.rhtml +1 -1
- data/app/views/works/_description_list.rhtml +1 -1
- data/app/views/works/_form.rhtml +5 -5
- data/app/views/works/_new_row.rhtml +8 -8
- data/app/views/works/_row.rhtml +1 -1
- data/app/views/works/_task_id_list.rhtml +1 -1
- data/app/views/works/daily_work_sheet.rhtml +1 -1
- data/app/views/works/list.rhtml +5 -5
- data/app/views/works/list_excel.rhtml +2 -2
- data/app/views/works/timeliste.rhtml +14 -14
- data/app/views/works/weekly_work_sheet.rhtml +5 -5
- data/app/views/works/weekly_work_sheet_details.rhtml +5 -5
- data/backlog.gemspec +44 -0
- data/bin/backlog +5 -1
- data/config.ru +4 -0
- data/config/application.rb +10 -0
- data/config/boot.rb +12 -116
- data/config/database.yml +3 -6
- data/config/database.yml~ +17 -0
- data/config/environment.rb +28 -49
- data/config/environments/development.rb +24 -20
- data/config/environments/development.rb.rails2 +26 -0
- data/config/environments/production.rb +26 -22
- data/config/environments/test.rb +20 -15
- data/config/environments/user_environment.rb +1 -1
- data/config/initializers/backtrace_silencers.rb +7 -0
- data/config/initializers/inflections.rb +10 -0
- data/config/initializers/jdbc.rb +1 -1
- data/config/initializers/mime_types.rb +5 -0
- data/config/initializers/secret_token.rb +7 -0
- data/config/initializers/session_store.rb +8 -0
- data/config/locales/en.yml +22 -11
- data/config/locales/no.yml +1 -0
- data/config/routes.rb +4 -5
- data/cruise_build.sh +6 -2
- data/db/migrate/004_add_period.rb +22 -22
- data/db/migrate/015_add_user_option.rb +5 -19
- data/db/migrate/017_increase_backlog_name_limit.rb +10 -0
- data/db/migrate/021_create_work_accounts.rb +0 -2
- data/db/migrate/20101006092700_rename_backlogs_to_projects.rb +22 -0
- data/db/migrate/20101006092700_rename_backlogs_to_projects.rb~ +22 -0
- data/db/schema.rb +27 -30
- data/db/seeds.rb +7 -0
- data/lib/array_helper.rb +0 -8
- data/lib/class_table_inheritance.rb +8 -7
- data/lib/tasks/backup.rake +3 -3
- data/lib/tasks/jdbc.rake +2 -2
- data/lib/version_from_history.rb +1 -1
- data/public/404.html +23 -7
- data/public/422.html +26 -0
- data/public/500.html +23 -6
- data/public/images/rails.png +0 -0
- data/public/javascripts/controls.js +5 -3
- data/public/javascripts/dragdrop.js +7 -6
- data/public/javascripts/effects.js +8 -13
- data/public/javascripts/prototype.js +3381 -1700
- data/public/javascripts/rails.js +175 -0
- data/public/robots.txt +5 -1
- data/script/rails +6 -0
- data/test/client/login.rb +0 -2
- data/test/client/login_test.rb +1 -1
- data/test/client/setup.rb +25 -24
- data/test/fixtures/{backlogs.yml → projects.yml} +2 -2
- data/test/fixtures/tasks.yml +9 -9
- data/test/fixtures/work_lock_subscriptions.yml +2 -2
- data/test/fixtures/works.yml +7 -7
- data/test/functional/periods_controller_test.rb +1 -1
- data/test/functional/{backlogs_controller_test.rb → projects_controller_test.rb} +22 -21
- data/test/functional/search_controller_test.rb +1 -1
- data/test/functional/tasks_controller_test.rb +17 -17
- data/test/functional/user_controller_test.rb +16 -21
- data/test/functional/welcome_controller_test.rb +4 -3
- data/test/functional/works_controller_test.rb +5 -5
- data/test/integration/user_system_test.rb +1 -1
- data/test/mocks/test/clock.rb +1 -1
- data/test/performance/browsing_test.rb +9 -0
- data/test/performance/common.rb +1 -1
- data/test/test_helper.rb +23 -6
- data/test/test_helper.rb~ +121 -0
- data/test/unit/user_test.rb +3 -3
- data/test/unit/work_test.rb +7 -7
- data/vendor/plugins/assert_cookie/lib/assert_cookie.rb +0 -2
- data/vendor/plugins/{foreign_key_migrations → dynamic_form}/MIT-LICENSE +1 -1
- data/vendor/plugins/dynamic_form/README +13 -0
- data/vendor/plugins/dynamic_form/Rakefile +10 -0
- data/vendor/plugins/dynamic_form/dynamic_form.gemspec +12 -0
- data/vendor/plugins/dynamic_form/init.rb +1 -0
- data/vendor/plugins/dynamic_form/lib/action_view/helpers/dynamic_form.rb +300 -0
- data/vendor/plugins/dynamic_form/lib/action_view/locale/en.yml +8 -0
- data/vendor/plugins/dynamic_form/lib/dynamic_form.rb +5 -0
- data/vendor/plugins/dynamic_form/test/dynamic_form_i18n_test.rb +42 -0
- data/vendor/plugins/dynamic_form/test/dynamic_form_test.rb +370 -0
- data/vendor/plugins/dynamic_form/test/test_helper.rb +9 -0
- data/vendor/plugins/prototype_legacy_helper/lib/prototype_legacy_helper.rb +432 -0
- data/vendor/plugins/prototype_legacy_helper/test/test_prototype_helper.rb +297 -0
- data/vendor/plugins/rails_time/test/debug.log +1 -0
- data/vendor/plugins/{redhillonrails_core → verification}/MIT-LICENSE +1 -1
- data/vendor/plugins/verification/README +34 -0
- data/vendor/plugins/verification/Rakefile +22 -0
- data/vendor/plugins/verification/init.rb +3 -0
- data/vendor/plugins/verification/lib/action_controller/verification.rb +132 -0
- data/vendor/plugins/verification/test/test_helper.rb +18 -0
- data/vendor/plugins/verification/test/verification_test.rb +270 -0
- data/vendor/plugins/will_paginate/lib/will_paginate/collection.rb +1 -1
- metadata +115 -134
- data/Gemfile~ +0 -4
- data/History.txt~ +0 -961
- data/LICENSE_LOCALIZATION +0 -20
- data/README_LOCALIZATION +0 -61
- data/README_RAILS +0 -180
- data/app/views/backlogs/_form.rhtml +0 -44
- data/app/views/backlogs/_name_list.rhtml +0 -5
- data/app/views/backlogs/edit.rhtml +0 -14
- data/app/views/backlogs/list.rhtml +0 -16
- data/app/views/layouts/mwrt002.rhtml +0 -43
- data/config/initializers/mongrel.rb +0 -83
- data/config/preinitializer.rb +0 -20
- data/config/warble.rb~ +0 -84
- data/db/migrate/017_insert_datek_projects.rb +0 -98
- data/lib/change_column_null_migration_fix.rb +0 -15
- data/no_test.rb~ +0 -6
- data/public/dispatch.cgi +0 -10
- data/public/dispatch.fcgi +0 -24
- data/public/dispatch.rb +0 -10
- data/script/about +0 -3
- data/script/breakpointer +0 -3
- data/script/console +0 -3
- data/script/dbconsole +0 -3
- data/script/destroy +0 -3
- data/script/generate +0 -3
- data/script/performance/benchmarker +0 -3
- data/script/performance/profiler +0 -3
- data/script/plugin +0 -3
- data/script/process/inspector +0 -3
- data/script/process/reaper +0 -3
- data/script/process/spawner +0 -3
- data/script/runner +0 -3
- data/script/server +0 -3
- data/test/client/login.rb~ +0 -33
- data/test/mocks/test/user_notify.rb +0 -16
- data/vendor/plugins/foreign_key_migrations/CHANGELOG +0 -103
- data/vendor/plugins/foreign_key_migrations/README +0 -87
- data/vendor/plugins/foreign_key_migrations/about.yml +0 -5
- data/vendor/plugins/foreign_key_migrations/init.rb +0 -1
- data/vendor/plugins/foreign_key_migrations/install.rb +0 -1
- data/vendor/plugins/foreign_key_migrations/lib/foreign_key_migrations.rb +0 -3
- data/vendor/plugins/foreign_key_migrations/lib/red_hill_consulting/foreign_key_migrations/active_record/base.rb +0 -22
- data/vendor/plugins/foreign_key_migrations/lib/red_hill_consulting/foreign_key_migrations/active_record/connection_adapters/abstract_adapter.rb +0 -22
- data/vendor/plugins/foreign_key_migrations/lib/red_hill_consulting/foreign_key_migrations/active_record/connection_adapters/table_definition.rb +0 -28
- data/vendor/plugins/lightwindow_helper/README +0 -33
- data/vendor/plugins/lightwindow_helper/assets/images/ajax-loading.gif +0 -0
- data/vendor/plugins/lightwindow_helper/assets/images/arrow-down.gif +0 -0
- data/vendor/plugins/lightwindow_helper/assets/images/arrow-up.gif +0 -0
- data/vendor/plugins/lightwindow_helper/assets/images/black-70.png +0 -0
- data/vendor/plugins/lightwindow_helper/assets/images/black.png +0 -0
- data/vendor/plugins/lightwindow_helper/assets/images/nextlabel.gif +0 -0
- data/vendor/plugins/lightwindow_helper/assets/images/prevlabel.gif +0 -0
- data/vendor/plugins/lightwindow_helper/assets/javascripts/lightwindow.js +0 -1921
- data/vendor/plugins/lightwindow_helper/assets/stylesheets/lightwindow.css +0 -376
- data/vendor/plugins/lightwindow_helper/init.rb +0 -1
- data/vendor/plugins/lightwindow_helper/install.rb +0 -7
- data/vendor/plugins/lightwindow_helper/lib/lightwindow_helper.rb +0 -31
- data/vendor/plugins/redhillonrails_core/CHANGELOG +0 -150
- data/vendor/plugins/redhillonrails_core/README +0 -124
- data/vendor/plugins/redhillonrails_core/init.rb +0 -19
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/base.rb +0 -54
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/abstract_adapter.rb +0 -31
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/column.rb +0 -21
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/foreign_key_definition.rb +0 -26
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/index_definition.rb +0 -11
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/mysql_adapter.rb +0 -74
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/mysql_column.rb +0 -8
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/postgresql_adapter.rb +0 -99
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/schema_statements.rb +0 -16
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/sqlite3_adapter.rb +0 -9
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/connection_adapters/table_definition.rb +0 -27
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/schema.rb +0 -27
- data/vendor/plugins/redhillonrails_core/lib/red_hill_consulting/core/active_record/schema_dumper.rb +0 -47
- data/vendor/plugins/transactional_migrations/CHANGELOG +0 -9
- data/vendor/plugins/transactional_migrations/MIT-LICENSE +0 -20
- data/vendor/plugins/transactional_migrations/README +0 -15
- data/vendor/plugins/transactional_migrations/about.yml +0 -5
- data/vendor/plugins/transactional_migrations/init.rb +0 -1
- data/vendor/plugins/transactional_migrations/lib/red_hill_consulting/transactional_migrations/active_record/migration.rb +0 -19
@@ -0,0 +1,370 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'action_view/template/handlers/erb'
|
3
|
+
|
4
|
+
class DynamicFormTest < ActionView::TestCase
|
5
|
+
tests ActionView::Helpers::DynamicForm
|
6
|
+
|
7
|
+
def form_for(*)
|
8
|
+
@output_buffer = super
|
9
|
+
end
|
10
|
+
|
11
|
+
silence_warnings do
|
12
|
+
class Post < Struct.new(:title, :author_name, :body, :secret, :written_on)
|
13
|
+
extend ActiveModel::Naming
|
14
|
+
include ActiveModel::Conversion
|
15
|
+
end
|
16
|
+
|
17
|
+
class User < Struct.new(:email)
|
18
|
+
extend ActiveModel::Naming
|
19
|
+
include ActiveModel::Conversion
|
20
|
+
end
|
21
|
+
|
22
|
+
class Column < Struct.new(:type, :name, :human_name)
|
23
|
+
extend ActiveModel::Naming
|
24
|
+
include ActiveModel::Conversion
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class DirtyPost
|
29
|
+
class Errors
|
30
|
+
def empty?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def count
|
35
|
+
1
|
36
|
+
end
|
37
|
+
|
38
|
+
def full_messages
|
39
|
+
["Author name can't be <em>empty</em>"]
|
40
|
+
end
|
41
|
+
|
42
|
+
def [](field)
|
43
|
+
["can't be <em>empty</em>"]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def errors
|
48
|
+
Errors.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup_post
|
53
|
+
@post = Post.new
|
54
|
+
def @post.errors
|
55
|
+
Class.new {
|
56
|
+
def [](field)
|
57
|
+
case field.to_s
|
58
|
+
when "author_name"
|
59
|
+
["can't be empty"]
|
60
|
+
when "body"
|
61
|
+
['foo']
|
62
|
+
else
|
63
|
+
[]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
def empty?() false end
|
67
|
+
def count() 1 end
|
68
|
+
def full_messages() [ "Author name can't be empty" ] end
|
69
|
+
}.new
|
70
|
+
end
|
71
|
+
|
72
|
+
def @post.persisted?() false end
|
73
|
+
def @post.to_param() nil end
|
74
|
+
|
75
|
+
def @post.column_for_attribute(attr_name)
|
76
|
+
Post.content_columns.select { |column| column.name == attr_name }.first
|
77
|
+
end
|
78
|
+
|
79
|
+
silence_warnings do
|
80
|
+
def Post.content_columns() [ Column.new(:string, "title", "Title"), Column.new(:text, "body", "Body") ] end
|
81
|
+
end
|
82
|
+
|
83
|
+
@post.title = "Hello World"
|
84
|
+
@post.author_name = ""
|
85
|
+
@post.body = "Back to the hill and over it again!"
|
86
|
+
@post.secret = 1
|
87
|
+
@post.written_on = Date.new(2004, 6, 15)
|
88
|
+
end
|
89
|
+
|
90
|
+
def setup_user
|
91
|
+
@user = User.new
|
92
|
+
def @user.errors
|
93
|
+
Class.new {
|
94
|
+
def [](field) field == "email" ? ['nonempty'] : [] end
|
95
|
+
def empty?() false end
|
96
|
+
def count() 1 end
|
97
|
+
def full_messages() [ "User email can't be empty" ] end
|
98
|
+
}.new
|
99
|
+
end
|
100
|
+
|
101
|
+
def @user.new_record?() true end
|
102
|
+
def @user.to_param() nil end
|
103
|
+
|
104
|
+
def @user.column_for_attribute(attr_name)
|
105
|
+
User.content_columns.select { |column| column.name == attr_name }.first
|
106
|
+
end
|
107
|
+
|
108
|
+
silence_warnings do
|
109
|
+
def User.content_columns() [ Column.new(:string, "email", "Email") ] end
|
110
|
+
end
|
111
|
+
|
112
|
+
@user.email = ""
|
113
|
+
end
|
114
|
+
|
115
|
+
def protect_against_forgery?
|
116
|
+
@protect_against_forgery ? true : false
|
117
|
+
end
|
118
|
+
attr_accessor :request_forgery_protection_token, :form_authenticity_token
|
119
|
+
|
120
|
+
def setup
|
121
|
+
super
|
122
|
+
setup_post
|
123
|
+
setup_user
|
124
|
+
|
125
|
+
@response = ActionController::TestResponse.new
|
126
|
+
end
|
127
|
+
|
128
|
+
def url_for(options)
|
129
|
+
options = options.symbolize_keys
|
130
|
+
[options[:action], options[:id].to_param].compact.join('/')
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_generic_input_tag
|
134
|
+
assert_dom_equal(
|
135
|
+
%(<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />), input("post", "title")
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_text_area_with_errors
|
140
|
+
assert_dom_equal(
|
141
|
+
%(<div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div>),
|
142
|
+
text_area("post", "body")
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_text_field_with_errors
|
147
|
+
assert_dom_equal(
|
148
|
+
%(<div class="fieldWithErrors"><input id="post_author_name" name="post[author_name]" size="30" type="text" value="" /></div>),
|
149
|
+
text_field("post", "author_name")
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_field_error_proc
|
154
|
+
old_proc = ActionView::Base.field_error_proc
|
155
|
+
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
|
156
|
+
%(<div class=\"fieldWithErrors\">#{html_tag} <span class="error">#{[instance.error_message].join(', ')}</span></div>).html_safe
|
157
|
+
end
|
158
|
+
|
159
|
+
assert_dom_equal(
|
160
|
+
%(<div class="fieldWithErrors"><input id="post_author_name" name="post[author_name]" size="30" type="text" value="" /> <span class="error">can't be empty</span></div>),
|
161
|
+
text_field("post", "author_name")
|
162
|
+
)
|
163
|
+
ensure
|
164
|
+
ActionView::Base.field_error_proc = old_proc if old_proc
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_form_with_string
|
168
|
+
assert_dom_equal(
|
169
|
+
%(<form action="create" method="post"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>),
|
170
|
+
form("post")
|
171
|
+
)
|
172
|
+
|
173
|
+
silence_warnings do
|
174
|
+
class << @post
|
175
|
+
def persisted?() true end
|
176
|
+
def to_param() id end
|
177
|
+
def id() 1 end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
assert_dom_equal(
|
182
|
+
%(<form action="update/1" method="post"><input id="post_id" name="post[id]" type="hidden" value="1" /><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Update" /></form>),
|
183
|
+
form("post")
|
184
|
+
)
|
185
|
+
end
|
186
|
+
|
187
|
+
def test_form_with_protect_against_forgery
|
188
|
+
@protect_against_forgery = true
|
189
|
+
@request_forgery_protection_token = 'authenticity_token'
|
190
|
+
@form_authenticity_token = '123'
|
191
|
+
assert_dom_equal(
|
192
|
+
%(<form action="create" method="post"><div style='margin:0;padding:0;display:inline'><input type='hidden' name='authenticity_token' value='123' /></div><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>),
|
193
|
+
form("post")
|
194
|
+
)
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_form_with_method_option
|
198
|
+
assert_dom_equal(
|
199
|
+
%(<form action="create" method="get"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>),
|
200
|
+
form("post", :method=>'get')
|
201
|
+
)
|
202
|
+
end
|
203
|
+
|
204
|
+
def test_form_with_action_option
|
205
|
+
output_buffer << form("post", :action => "sign")
|
206
|
+
assert_select "form[action=sign]" do |form|
|
207
|
+
assert_select "input[type=submit][value=Sign]"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_form_with_date
|
212
|
+
silence_warnings do
|
213
|
+
def Post.content_columns() [ Column.new(:date, "written_on", "Written on") ] end
|
214
|
+
end
|
215
|
+
|
216
|
+
assert_dom_equal(
|
217
|
+
%(<form action="create" method="post"><p><label for="post_written_on">Written on</label><br /><select id="post_written_on_1i" name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n<select id="post_written_on_2i" name="post[written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n<select id="post_written_on_3i" name="post[written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n</p><input name="commit" type="submit" value="Create" /></form>),
|
218
|
+
form("post")
|
219
|
+
)
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_form_with_datetime
|
223
|
+
silence_warnings do
|
224
|
+
def Post.content_columns() [ Column.new(:datetime, "written_on", "Written on") ] end
|
225
|
+
end
|
226
|
+
@post.written_on = Time.gm(2004, 6, 15, 16, 30)
|
227
|
+
|
228
|
+
assert_dom_equal(
|
229
|
+
%(<form action="create" method="post"><p><label for="post_written_on">Written on</label><br /><select id="post_written_on_1i" name="post[written_on(1i)]">\n<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n</select>\n<select id="post_written_on_2i" name="post[written_on(2i)]">\n<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n</select>\n<select id="post_written_on_3i" name="post[written_on(3i)]">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n</select>\n — <select id="post_written_on_4i" name="post[written_on(4i)]">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_written_on_5i" name="post[written_on(5i)]">\n<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30" selected="selected">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n</select>\n</p><input name="commit" type="submit" value="Create" /></form>),
|
230
|
+
form("post")
|
231
|
+
)
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_error_for_block
|
235
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post")
|
236
|
+
assert_equal %(<div class="errorDeathByClass" id="errorDeathById"><h1>1 error prohibited this post from being saved</h1><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :class => "errorDeathByClass", :id => "errorDeathById", :header_tag => "h1")
|
237
|
+
assert_equal %(<div id="errorDeathById"><h1>1 error prohibited this post from being saved</h1><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :class => nil, :id => "errorDeathById", :header_tag => "h1")
|
238
|
+
assert_equal %(<div class="errorDeathByClass"><h1>1 error prohibited this post from being saved</h1><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :class => "errorDeathByClass", :id => nil, :header_tag => "h1")
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_error_messages_for_escapes_html
|
242
|
+
@dirty_post = DirtyPost.new
|
243
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this dirty post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be <em>empty</em></li></ul></div>), error_messages_for("dirty_post")
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_error_messages_for_handles_nil
|
247
|
+
assert_equal "", error_messages_for("notthere")
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_error_message_on_escapes_html
|
251
|
+
@dirty_post = DirtyPost.new
|
252
|
+
assert_dom_equal "<div class=\"formError\">can't be <em>empty</em></div>", error_message_on(:dirty_post, :author_name)
|
253
|
+
end
|
254
|
+
|
255
|
+
def test_error_message_on_handles_nil
|
256
|
+
assert_equal "", error_message_on("notthere", "notthere")
|
257
|
+
end
|
258
|
+
|
259
|
+
def test_error_message_on
|
260
|
+
assert_dom_equal "<div class=\"formError\">can't be empty</div>", error_message_on(:post, :author_name)
|
261
|
+
end
|
262
|
+
|
263
|
+
def test_error_message_on_no_instance_variable
|
264
|
+
other_post = @post
|
265
|
+
assert_dom_equal "<div class=\"formError\">can't be empty</div>", error_message_on(other_post, :author_name)
|
266
|
+
end
|
267
|
+
|
268
|
+
def test_error_message_on_with_options_hash
|
269
|
+
assert_dom_equal "<div class=\"differentError\">beforecan't be emptyafter</div>", error_message_on(:post, :author_name, :css_class => 'differentError', :prepend_text => 'before', :append_text => 'after')
|
270
|
+
end
|
271
|
+
|
272
|
+
def test_error_message_on_with_tag_option_in_options_hash
|
273
|
+
assert_dom_equal "<span class=\"differentError\">beforecan't be emptyafter</span>", error_message_on(:post, :author_name, :html_tag => "span", :css_class => 'differentError', :prepend_text => 'before', :append_text => 'after')
|
274
|
+
end
|
275
|
+
|
276
|
+
def test_error_message_on_handles_empty_errors
|
277
|
+
assert_equal "", error_message_on(@post, :tag)
|
278
|
+
end
|
279
|
+
|
280
|
+
def test_error_messages_for_many_objects
|
281
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li><li>User email can't be empty</li></ul></div>), error_messages_for("post", "user")
|
282
|
+
|
283
|
+
# reverse the order, error order changes and so does the title
|
284
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this user from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for("user", "post")
|
285
|
+
|
286
|
+
# add the default to put post back in the title
|
287
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for("user", "post", :object_name => "post")
|
288
|
+
|
289
|
+
# symbols work as well
|
290
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :object_name => :post)
|
291
|
+
|
292
|
+
# any default works too
|
293
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this monkey from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :object_name => "monkey")
|
294
|
+
|
295
|
+
# should space object name
|
296
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this chunky bacon from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :object_name => "chunky_bacon")
|
297
|
+
|
298
|
+
# hide header and explanation messages with nil or empty string
|
299
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :header_message => nil, :message => "")
|
300
|
+
|
301
|
+
# override header and explanation messages
|
302
|
+
header_message = "Yikes! Some errors"
|
303
|
+
message = "Please fix the following fields and resubmit:"
|
304
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>#{header_message}</h2><p>#{message}</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for(:user, :post, :header_message => header_message, :message => message)
|
305
|
+
end
|
306
|
+
|
307
|
+
def test_error_messages_for_non_instance_variable
|
308
|
+
actual_user = @user
|
309
|
+
actual_post = @post
|
310
|
+
@user = nil
|
311
|
+
@post = nil
|
312
|
+
|
313
|
+
#explicitly set object
|
314
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :object => actual_post)
|
315
|
+
|
316
|
+
#multiple objects
|
317
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this user from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>), error_messages_for("user", "post", :object => [actual_user, actual_post])
|
318
|
+
|
319
|
+
#nil object
|
320
|
+
assert_equal '', error_messages_for('user', :object => nil)
|
321
|
+
end
|
322
|
+
|
323
|
+
def test_error_messages_for_model_objects
|
324
|
+
error = error_messages_for(@post)
|
325
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>),
|
326
|
+
error
|
327
|
+
|
328
|
+
error = error_messages_for(@user, @post)
|
329
|
+
assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>2 errors prohibited this user from being saved</h2><p>There were problems with the following fields:</p><ul><li>User email can't be empty</li><li>Author name can't be empty</li></ul></div>),
|
330
|
+
error
|
331
|
+
end
|
332
|
+
|
333
|
+
def test_form_with_string_multipart
|
334
|
+
assert_dom_equal(
|
335
|
+
%(<form action="create" enctype="multipart/form-data" method="post"><p><label for="post_title">Title</label><br /><input id="post_title" name="post[title]" size="30" type="text" value="Hello World" /></p>\n<p><label for="post_body">Body</label><br /><div class="fieldWithErrors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div></p><input name="commit" type="submit" value="Create" /></form>),
|
336
|
+
form("post", :multipart => true)
|
337
|
+
)
|
338
|
+
end
|
339
|
+
|
340
|
+
def test_default_form_builder_with_dynamic_form_helpers
|
341
|
+
form_for(@post, :as => :post, :url => {}) do |f|
|
342
|
+
concat f.error_message_on('author_name')
|
343
|
+
concat f.error_messages
|
344
|
+
end
|
345
|
+
|
346
|
+
expected = %(<form class="post_new" method="post" action="" id="post_new">) +
|
347
|
+
%(<div class="formError">can't be empty</div>) +
|
348
|
+
%(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>) +
|
349
|
+
%(</form>)
|
350
|
+
|
351
|
+
assert_dom_equal expected, output_buffer
|
352
|
+
end
|
353
|
+
|
354
|
+
def test_default_form_builder_no_instance_variable
|
355
|
+
post = @post
|
356
|
+
@post = nil
|
357
|
+
|
358
|
+
form_for(post, :as => :post, :url => {}) do |f|
|
359
|
+
concat f.error_message_on('author_name')
|
360
|
+
concat f.error_messages
|
361
|
+
end
|
362
|
+
|
363
|
+
expected = %(<form class="post_new" method="post" action="" id="post_new">) +
|
364
|
+
%(<div class="formError">can't be empty</div>) +
|
365
|
+
%(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>) +
|
366
|
+
%(</form>)
|
367
|
+
|
368
|
+
assert_dom_equal expected, output_buffer
|
369
|
+
end
|
370
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext'
|
5
|
+
require 'action_view'
|
6
|
+
require 'action_controller'
|
7
|
+
require 'action_controller/test_case'
|
8
|
+
require 'active_model'
|
9
|
+
require 'action_view/helpers/dynamic_form'
|
@@ -0,0 +1,432 @@
|
|
1
|
+
module PrototypeHelper
|
2
|
+
# Creates a button with an onclick event which calls a remote action
|
3
|
+
# via XMLHttpRequest
|
4
|
+
# The options for specifying the target with :url
|
5
|
+
# and defining callbacks is the same as link_to_remote.
|
6
|
+
def button_to_remote(name, options = {}, html_options = {})
|
7
|
+
button_to_function(name, remote_function(options), html_options)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+
|
11
|
+
# that will submit form using XMLHttpRequest in the background instead of a regular POST request that
|
12
|
+
# reloads the page.
|
13
|
+
#
|
14
|
+
# # Create a button that submits to the create action
|
15
|
+
# #
|
16
|
+
# # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
|
17
|
+
# # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
|
18
|
+
# # return false;" type="button" value="Create" />
|
19
|
+
# <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
|
20
|
+
#
|
21
|
+
# # Submit to the remote action update and update the DIV succeed or fail based
|
22
|
+
# # on the success or failure of the request
|
23
|
+
# #
|
24
|
+
# # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
|
25
|
+
# # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
|
26
|
+
# # return false;" type="button" value="Update" />
|
27
|
+
# <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
|
28
|
+
# :update => { :success => "succeed", :failure => "fail" }
|
29
|
+
#
|
30
|
+
# <tt>options</tt> argument is the same as in form_remote_tag.
|
31
|
+
def submit_to_remote(name, value, options = {})
|
32
|
+
options[:with] ||= 'Form.serialize(this.form)'
|
33
|
+
|
34
|
+
html_options = options.delete(:html) || {}
|
35
|
+
html_options[:name] = name
|
36
|
+
|
37
|
+
button_to_remote(value, options, html_options)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a link to a remote action defined by <tt>options[:url]</tt>
|
41
|
+
# (using the url_for format) that's called in the background using
|
42
|
+
# XMLHttpRequest. The result of that request can then be inserted into a
|
43
|
+
# DOM object whose id can be specified with <tt>options[:update]</tt>.
|
44
|
+
# Usually, the result would be a partial prepared by the controller with
|
45
|
+
# render :partial.
|
46
|
+
#
|
47
|
+
# Examples:
|
48
|
+
# # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
|
49
|
+
# # return false;">Delete this post</a>
|
50
|
+
# link_to_remote "Delete this post", :update => "posts",
|
51
|
+
# :url => { :action => "destroy", :id => post.id }
|
52
|
+
#
|
53
|
+
# # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
|
54
|
+
# # return false;"><img alt="Refresh" src="/images/refresh.png?" /></a>
|
55
|
+
# link_to_remote(image_tag("refresh"), :update => "emails",
|
56
|
+
# :url => { :action => "list_emails" })
|
57
|
+
#
|
58
|
+
# You can override the generated HTML options by specifying a hash in
|
59
|
+
# <tt>options[:html]</tt>.
|
60
|
+
#
|
61
|
+
# link_to_remote "Delete this post", :update => "posts",
|
62
|
+
# :url => post_url(@post), :method => :delete,
|
63
|
+
# :html => { :class => "destructive" }
|
64
|
+
#
|
65
|
+
# You can also specify a hash for <tt>options[:update]</tt> to allow for
|
66
|
+
# easy redirection of output to an other DOM element if a server-side
|
67
|
+
# error occurs:
|
68
|
+
#
|
69
|
+
# Example:
|
70
|
+
# # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
|
71
|
+
# # {asynchronous:true, evalScripts:true}); return false;">Delete this post</a>
|
72
|
+
# link_to_remote "Delete this post",
|
73
|
+
# :url => { :action => "destroy", :id => post.id },
|
74
|
+
# :update => { :success => "posts", :failure => "error" }
|
75
|
+
#
|
76
|
+
# Optionally, you can use the <tt>options[:position]</tt> parameter to
|
77
|
+
# influence how the target DOM element is updated. It must be one of
|
78
|
+
# <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
|
79
|
+
#
|
80
|
+
# The method used is by default POST. You can also specify GET or you
|
81
|
+
# can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
|
82
|
+
#
|
83
|
+
# Example:
|
84
|
+
# # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
|
85
|
+
# # return false;">Destroy</a>
|
86
|
+
# link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
|
87
|
+
#
|
88
|
+
# By default, these remote requests are processed asynchronous during
|
89
|
+
# which various JavaScript callbacks can be triggered (for progress
|
90
|
+
# indicators and the likes). All callbacks get access to the
|
91
|
+
# <tt>request</tt> object, which holds the underlying XMLHttpRequest.
|
92
|
+
#
|
93
|
+
# To access the server response, use <tt>request.responseText</tt>, to
|
94
|
+
# find out the HTTP status, use <tt>request.status</tt>.
|
95
|
+
#
|
96
|
+
# Example:
|
97
|
+
# # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
|
98
|
+
# # onComplete:function(request){undoRequestCompleted(request)}}); return false;">hello</a>
|
99
|
+
# word = 'hello'
|
100
|
+
# link_to_remote word,
|
101
|
+
# :url => { :action => "undo", :n => word_counter },
|
102
|
+
# :complete => "undoRequestCompleted(request)"
|
103
|
+
#
|
104
|
+
# The callbacks that may be specified are (in order):
|
105
|
+
#
|
106
|
+
# <tt>:loading</tt>:: Called when the remote document is being
|
107
|
+
# loaded with data by the browser.
|
108
|
+
# <tt>:loaded</tt>:: Called when the browser has finished loading
|
109
|
+
# the remote document.
|
110
|
+
# <tt>:interactive</tt>:: Called when the user can interact with the
|
111
|
+
# remote document, even though it has not
|
112
|
+
# finished loading.
|
113
|
+
# <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
|
114
|
+
# and the HTTP status code is in the 2XX range.
|
115
|
+
# <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
|
116
|
+
# and the HTTP status code is not in the 2XX
|
117
|
+
# range.
|
118
|
+
# <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
|
119
|
+
# (fires after success/failure if they are
|
120
|
+
# present).
|
121
|
+
#
|
122
|
+
# You can further refine <tt>:success</tt> and <tt>:failure</tt> by
|
123
|
+
# adding additional callbacks for specific status codes.
|
124
|
+
#
|
125
|
+
# Example:
|
126
|
+
# # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
|
127
|
+
# # on404:function(request){alert('Not found...? Wrong URL...?')},
|
128
|
+
# # onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a>
|
129
|
+
# link_to_remote word,
|
130
|
+
# :url => { :action => "action" },
|
131
|
+
# 404 => "alert('Not found...? Wrong URL...?')",
|
132
|
+
# :failure => "alert('HTTP Error ' + request.status + '!')"
|
133
|
+
#
|
134
|
+
# A status code callback overrides the success/failure handlers if
|
135
|
+
# present.
|
136
|
+
#
|
137
|
+
# If you for some reason or another need synchronous processing (that'll
|
138
|
+
# block the browser while the request is happening), you can specify
|
139
|
+
# <tt>options[:type] = :synchronous</tt>.
|
140
|
+
#
|
141
|
+
# You can customize further browser side call logic by passing in
|
142
|
+
# JavaScript code snippets via some optional parameters. In their order
|
143
|
+
# of use these are:
|
144
|
+
#
|
145
|
+
# <tt>:confirm</tt>:: Adds confirmation dialog.
|
146
|
+
# <tt>:condition</tt>:: Perform remote request conditionally
|
147
|
+
# by this expression. Use this to
|
148
|
+
# describe browser-side conditions when
|
149
|
+
# request should not be initiated.
|
150
|
+
# <tt>:before</tt>:: Called before request is initiated.
|
151
|
+
# <tt>:after</tt>:: Called immediately after request was
|
152
|
+
# initiated and before <tt>:loading</tt>.
|
153
|
+
# <tt>:submit</tt>:: Specifies the DOM element ID that's used
|
154
|
+
# as the parent of the form elements. By
|
155
|
+
# default this is the current form, but
|
156
|
+
# it could just as well be the ID of a
|
157
|
+
# table row or any other DOM element.
|
158
|
+
# <tt>:with</tt>:: A JavaScript expression specifying
|
159
|
+
# the parameters for the XMLHttpRequest.
|
160
|
+
# Any expressions should return a valid
|
161
|
+
# URL query string.
|
162
|
+
#
|
163
|
+
# Example:
|
164
|
+
#
|
165
|
+
# :with => "'name=' + $('name').value"
|
166
|
+
#
|
167
|
+
# You can generate a link that uses AJAX in the general case, while
|
168
|
+
# degrading gracefully to plain link behavior in the absence of
|
169
|
+
# JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL.
|
170
|
+
# Note the extra curly braces around the <tt>options</tt> hash separate
|
171
|
+
# it as the second parameter from <tt>html_options</tt>, the third.
|
172
|
+
#
|
173
|
+
# Example:
|
174
|
+
# link_to_remote "Delete this post",
|
175
|
+
# { :update => "posts", :url => { :action => "destroy", :id => post.id } },
|
176
|
+
# :href => url_for(:action => "destroy", :id => post.id)
|
177
|
+
def link_to_remote(name, options = {}, html_options = nil)
|
178
|
+
link_to_function(name, remote_function(options), html_options || options.delete(:html))
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns a form tag that will submit using XMLHttpRequest in the
|
182
|
+
# background instead of the regular reloading POST arrangement. Even
|
183
|
+
# though it's using JavaScript to serialize the form elements, the form
|
184
|
+
# submission will work just like a regular submission as viewed by the
|
185
|
+
# receiving side (all elements available in <tt>params</tt>). The options for
|
186
|
+
# specifying the target with <tt>:url</tt> and defining callbacks is the same as
|
187
|
+
# +link_to_remote+.
|
188
|
+
#
|
189
|
+
# A "fall-through" target for browsers that doesn't do JavaScript can be
|
190
|
+
# specified with the <tt>:action</tt>/<tt>:method</tt> options on <tt>:html</tt>.
|
191
|
+
#
|
192
|
+
# Example:
|
193
|
+
# # Generates:
|
194
|
+
# # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
|
195
|
+
# # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
|
196
|
+
# form_remote_tag :html => { :action =>
|
197
|
+
# url_for(:controller => "some", :action => "place") }
|
198
|
+
#
|
199
|
+
# The Hash passed to the <tt>:html</tt> key is equivalent to the options (2nd)
|
200
|
+
# argument in the FormTagHelper.form_tag method.
|
201
|
+
#
|
202
|
+
# By default the fall-through action is the same as the one specified in
|
203
|
+
# the <tt>:url</tt> (and the default method is <tt>:post</tt>).
|
204
|
+
#
|
205
|
+
# form_remote_tag also takes a block, like form_tag:
|
206
|
+
# # Generates:
|
207
|
+
# # <form action="/" method="post" onsubmit="new Ajax.Request('/',
|
208
|
+
# # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
|
209
|
+
# # return false;"> <div><input name="commit" type="submit" value="Save" /></div>
|
210
|
+
# # </form>
|
211
|
+
# <% form_remote_tag :url => '/posts' do -%>
|
212
|
+
# <div><%= submit_tag 'Save' %></div>
|
213
|
+
# <% end -%>
|
214
|
+
def form_remote_tag(options = {}, &block)
|
215
|
+
options[:form] = true
|
216
|
+
|
217
|
+
options[:html] ||= {}
|
218
|
+
options[:html][:onsubmit] =
|
219
|
+
(options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
|
220
|
+
"#{remote_function(options)}; return false;"
|
221
|
+
|
222
|
+
form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Creates a form that will submit using XMLHttpRequest in the background
|
226
|
+
# instead of the regular reloading POST arrangement and a scope around a
|
227
|
+
# specific resource that is used as a base for questioning about
|
228
|
+
# values for the fields.
|
229
|
+
#
|
230
|
+
# === Resource
|
231
|
+
#
|
232
|
+
# Example:
|
233
|
+
# <% remote_form_for(@post) do |f| %>
|
234
|
+
# ...
|
235
|
+
# <% end %>
|
236
|
+
#
|
237
|
+
# This will expand to be the same as:
|
238
|
+
#
|
239
|
+
# <% remote_form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
|
240
|
+
# ...
|
241
|
+
# <% end %>
|
242
|
+
#
|
243
|
+
# === Nested Resource
|
244
|
+
#
|
245
|
+
# Example:
|
246
|
+
# <% remote_form_for([@post, @comment]) do |f| %>
|
247
|
+
# ...
|
248
|
+
# <% end %>
|
249
|
+
#
|
250
|
+
# This will expand to be the same as:
|
251
|
+
#
|
252
|
+
# <% remote_form_for :comment, @comment, :url => post_comment_path(@post, @comment), :html => { :method => :put, :class => "edit_comment", :id => "edit_comment_45" } do |f| %>
|
253
|
+
# ...
|
254
|
+
# <% end %>
|
255
|
+
#
|
256
|
+
# If you don't need to attach a form to a resource, then check out form_remote_tag.
|
257
|
+
#
|
258
|
+
# See FormHelper#form_for for additional semantics.
|
259
|
+
def remote_form_for(record_or_name_or_array, *args, &proc)
|
260
|
+
options = args.extract_options!
|
261
|
+
|
262
|
+
case record_or_name_or_array
|
263
|
+
when String, Symbol
|
264
|
+
object_name = record_or_name_or_array
|
265
|
+
when Array
|
266
|
+
object = record_or_name_or_array.last
|
267
|
+
object_name = ActiveModel::Naming.singular(object)
|
268
|
+
apply_form_for_options!(record_or_name_or_array, options)
|
269
|
+
args.unshift object
|
270
|
+
else
|
271
|
+
object = record_or_name_or_array
|
272
|
+
object_name = ActiveModel::Naming.singular(record_or_name_or_array)
|
273
|
+
apply_form_for_options!(object, options)
|
274
|
+
args.unshift object
|
275
|
+
end
|
276
|
+
|
277
|
+
form_remote_tag options do
|
278
|
+
fields_for object_name, *(args << options), &proc
|
279
|
+
end
|
280
|
+
end
|
281
|
+
alias_method :form_remote_for, :remote_form_for
|
282
|
+
|
283
|
+
# Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function
|
284
|
+
# that +form_remote_tag+ can call in <tt>:complete</tt> to evaluate a multiple
|
285
|
+
# update return document using +update_element_function+ calls.
|
286
|
+
def evaluate_remote_response
|
287
|
+
"eval(request.responseText)"
|
288
|
+
end
|
289
|
+
|
290
|
+
# Observes the field with the DOM ID specified by +field_id+ and calls a
|
291
|
+
# callback when its contents have changed. The default callback is an
|
292
|
+
# Ajax call. By default the value of the observed field is sent as a
|
293
|
+
# parameter with the Ajax call.
|
294
|
+
#
|
295
|
+
# Example:
|
296
|
+
# # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
|
297
|
+
# # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})})
|
298
|
+
# <%= observe_field :suggest, :url => { :action => :find_suggestion },
|
299
|
+
# :frequency => 0.25,
|
300
|
+
# :update => :suggest,
|
301
|
+
# :with => 'q'
|
302
|
+
# %>
|
303
|
+
#
|
304
|
+
# Required +options+ are either of:
|
305
|
+
# <tt>:url</tt>:: +url_for+-style options for the action to call
|
306
|
+
# when the field has changed.
|
307
|
+
# <tt>:function</tt>:: Instead of making a remote call to a URL, you
|
308
|
+
# can specify javascript code to be called instead.
|
309
|
+
# Note that the value of this option is used as the
|
310
|
+
# *body* of the javascript function, a function definition
|
311
|
+
# with parameters named element and value will be generated for you
|
312
|
+
# for example:
|
313
|
+
# observe_field("glass", :frequency => 1, :function => "alert('Element changed')")
|
314
|
+
# will generate:
|
315
|
+
# new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
|
316
|
+
# The element parameter is the DOM element being observed, and the value is its value at the
|
317
|
+
# time the observer is triggered.
|
318
|
+
#
|
319
|
+
# Additional options are:
|
320
|
+
# <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
|
321
|
+
# this field will be detected. Not setting this
|
322
|
+
# option at all or to a value equal to or less than
|
323
|
+
# zero will use event based observation instead of
|
324
|
+
# time based observation.
|
325
|
+
# <tt>:update</tt>:: Specifies the DOM ID of the element whose
|
326
|
+
# innerHTML should be updated with the
|
327
|
+
# XMLHttpRequest response text.
|
328
|
+
# <tt>:with</tt>:: A JavaScript expression specifying the parameters
|
329
|
+
# for the XMLHttpRequest. The default is to send the
|
330
|
+
# key and value of the observed field. Any custom
|
331
|
+
# expressions should return a valid URL query string.
|
332
|
+
# The value of the field is stored in the JavaScript
|
333
|
+
# variable +value+.
|
334
|
+
#
|
335
|
+
# Examples
|
336
|
+
#
|
337
|
+
# :with => "'my_custom_key=' + value"
|
338
|
+
# :with => "'person[name]=' + prompt('New name')"
|
339
|
+
# :with => "Form.Element.serialize('other-field')"
|
340
|
+
#
|
341
|
+
# Finally
|
342
|
+
# :with => 'name'
|
343
|
+
# is shorthand for
|
344
|
+
# :with => "'name=' + value"
|
345
|
+
# This essentially just changes the key of the parameter.
|
346
|
+
#
|
347
|
+
# Additionally, you may specify any of the options documented in the
|
348
|
+
# <em>Common options</em> section at the top of this document.
|
349
|
+
#
|
350
|
+
# Example:
|
351
|
+
#
|
352
|
+
# # Sends params: {:title => 'Title of the book'} when the book_title input
|
353
|
+
# # field is changed.
|
354
|
+
# observe_field 'book_title',
|
355
|
+
# :url => 'http://example.com/books/edit/1',
|
356
|
+
# :with => 'title'
|
357
|
+
#
|
358
|
+
#
|
359
|
+
def observe_field(field_id, options = {})
|
360
|
+
if options[:frequency] && options[:frequency] > 0
|
361
|
+
build_observer('Form.Element.Observer', field_id, options)
|
362
|
+
else
|
363
|
+
build_observer('Form.Element.EventObserver', field_id, options)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Observes the form with the DOM ID specified by +form_id+ and calls a
|
368
|
+
# callback when its contents have changed. The default callback is an
|
369
|
+
# Ajax call. By default all fields of the observed field are sent as
|
370
|
+
# parameters with the Ajax call.
|
371
|
+
#
|
372
|
+
# The +options+ for +observe_form+ are the same as the options for
|
373
|
+
# +observe_field+. The JavaScript variable +value+ available to the
|
374
|
+
# <tt>:with</tt> option is set to the serialized form by default.
|
375
|
+
def observe_form(form_id, options = {})
|
376
|
+
if options[:frequency]
|
377
|
+
build_observer('Form.Observer', form_id, options)
|
378
|
+
else
|
379
|
+
build_observer('Form.EventObserver', form_id, options)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Periodically calls the specified url (<tt>options[:url]</tt>) every
|
384
|
+
# <tt>options[:frequency]</tt> seconds (default is 10). Usually used to
|
385
|
+
# update a specified div (<tt>options[:update]</tt>) with the results
|
386
|
+
# of the remote call. The options for specifying the target with <tt>:url</tt>
|
387
|
+
# and defining callbacks is the same as link_to_remote.
|
388
|
+
# Examples:
|
389
|
+
# # Call get_averages and put its results in 'avg' every 10 seconds
|
390
|
+
# # Generates:
|
391
|
+
# # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
|
392
|
+
# # {asynchronous:true, evalScripts:true})}, 10)
|
393
|
+
# periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
|
394
|
+
#
|
395
|
+
# # Call invoice every 10 seconds with the id of the customer
|
396
|
+
# # If it succeeds, update the invoice DIV; if it fails, update the error DIV
|
397
|
+
# # Generates:
|
398
|
+
# # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
|
399
|
+
# # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10)
|
400
|
+
# periodically_call_remote(:url => { :action => 'invoice', :id => customer.id },
|
401
|
+
# :update => { :success => "invoice", :failure => "error" }
|
402
|
+
#
|
403
|
+
# # Call update every 20 seconds and update the new_block DIV
|
404
|
+
# # Generates:
|
405
|
+
# # new PeriodicalExecuter(function() {new Ajax.Updater('news_block', 'update', {asynchronous:true, evalScripts:true})}, 20)
|
406
|
+
# periodically_call_remote(:url => 'update', :frequency => '20', :update => 'news_block')
|
407
|
+
#
|
408
|
+
def periodically_call_remote(options = {})
|
409
|
+
frequency = options[:frequency] || 10 # every ten seconds by default
|
410
|
+
code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})"
|
411
|
+
javascript_tag(code)
|
412
|
+
end
|
413
|
+
|
414
|
+
protected
|
415
|
+
def build_observer(klass, name, options = {})
|
416
|
+
if options[:with] && (options[:with] !~ /[\{=(.]/)
|
417
|
+
options[:with] = "'#{options[:with]}=' + encodeURIComponent(value)"
|
418
|
+
else
|
419
|
+
options[:with] ||= 'value' unless options[:function]
|
420
|
+
end
|
421
|
+
|
422
|
+
callback = options[:function] || remote_function(options)
|
423
|
+
javascript = "new #{klass}('#{name}', "
|
424
|
+
javascript << "#{options[:frequency]}, " if options[:frequency]
|
425
|
+
javascript << "function(element, value) {"
|
426
|
+
javascript << "#{callback}}"
|
427
|
+
javascript << ")"
|
428
|
+
javascript_tag(javascript)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
ActionController::Base.helper PrototypeHelper
|