wizardly 0.1.8.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/CHANGELOG.rdoc +33 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +645 -0
  4. data/init.rb +1 -0
  5. data/lib/jeffp-wizardly.rb +1 -0
  6. data/lib/validation_group.rb +147 -0
  7. data/lib/wizardly.rb +31 -0
  8. data/lib/wizardly/action_controller.rb +36 -0
  9. data/lib/wizardly/wizard.rb +16 -0
  10. data/lib/wizardly/wizard/button.rb +35 -0
  11. data/lib/wizardly/wizard/configuration.rb +194 -0
  12. data/lib/wizardly/wizard/configuration/methods.rb +422 -0
  13. data/lib/wizardly/wizard/dsl.rb +27 -0
  14. data/lib/wizardly/wizard/page.rb +62 -0
  15. data/lib/wizardly/wizard/text_helpers.rb +16 -0
  16. data/lib/wizardly/wizard/utils.rb +11 -0
  17. data/rails_generators/wizardly_app/USAGE +6 -0
  18. data/rails_generators/wizardly_app/templates/wizardly.rake +37 -0
  19. data/rails_generators/wizardly_app/wizardly_app_generator.rb +41 -0
  20. data/rails_generators/wizardly_controller/USAGE +3 -0
  21. data/rails_generators/wizardly_controller/templates/controller.rb.erb +34 -0
  22. data/rails_generators/wizardly_controller/templates/helper.rb.erb +14 -0
  23. data/rails_generators/wizardly_controller/wizardly_controller_generator.rb +57 -0
  24. data/rails_generators/wizardly_scaffold/USAGE +4 -0
  25. data/rails_generators/wizardly_scaffold/templates/form.html.erb +23 -0
  26. data/rails_generators/wizardly_scaffold/templates/form.html.haml.erb +22 -0
  27. data/rails_generators/wizardly_scaffold/templates/helper.rb.erb +30 -0
  28. data/rails_generators/wizardly_scaffold/templates/images/back.png +0 -0
  29. data/rails_generators/wizardly_scaffold/templates/images/cancel.png +0 -0
  30. data/rails_generators/wizardly_scaffold/templates/images/finish.png +0 -0
  31. data/rails_generators/wizardly_scaffold/templates/images/next.png +0 -0
  32. data/rails_generators/wizardly_scaffold/templates/images/skip.png +0 -0
  33. data/rails_generators/wizardly_scaffold/templates/layout.html.erb +15 -0
  34. data/rails_generators/wizardly_scaffold/templates/layout.html.haml.erb +10 -0
  35. data/rails_generators/wizardly_scaffold/templates/style.css +54 -0
  36. data/rails_generators/wizardly_scaffold/wizardly_scaffold_generator.rb +109 -0
  37. metadata +90 -0
@@ -0,0 +1,422 @@
1
+ module Wizardly
2
+ module Wizard
3
+ class Configuration
4
+
5
+ def print_callback_macros
6
+ macros = [
7
+ %w(on_post _on_post_%s_form),
8
+ %w(on_get _on_get_%s_form),
9
+ %w(on_errors _on_invalid_%s_form)
10
+ ]
11
+ self.buttons.each do |id, button|
12
+ macros << ['on_'+ id.to_s, '_on_%s_form_'+ id.to_s ]
13
+ end
14
+ mb = StringIO.new
15
+ macros.each do |macro|
16
+ mb << <<-MACRO
17
+ def self.#{macro.first}(*args, &block)
18
+ self._define_action_callback_macro('#{macro.first}', '#{macro.last}', *args, &block)
19
+ end
20
+ MACRO
21
+ end
22
+ mb << <<-DEFMAC
23
+ def self._define_action_callback_macro(macro_first, macro_last, *args, &block)
24
+ return if args.empty?
25
+ all_forms = #{page_order.inspect}
26
+ if args.include?(:all)
27
+ forms = all_forms
28
+ else
29
+ forms = args.map do |fa|
30
+ unless all_forms.include?(fa)
31
+ raise(ArgumentError, ":"+fa.to_s+" in callback '" + macro_first + "' is not a form defined for the wizard", caller)
32
+ end
33
+ fa
34
+ end
35
+ end
36
+ forms.each do |form|
37
+ self.send(:define_method, sprintf(macro_last, form.to_s), &block )
38
+ hide_action macro_last.to_sym
39
+ end
40
+ end
41
+
42
+ DEFMAC
43
+
44
+ [
45
+ %w(on_completed _after_wizard_save)
46
+ ].each do |macro|
47
+ mb << <<-EVENTS
48
+ def self.#{macro.first}(&block)
49
+ self.send(:define_method, :#{macro.last}, &block )
50
+ end
51
+ EVENTS
52
+ end
53
+ mb.string
54
+ end
55
+
56
+ def print_page_action_methods
57
+ mb = StringIO.new
58
+ self.pages.each do |id, p|
59
+ mb << <<-COMMENT
60
+
61
+ # #{id} action method
62
+ #{self.print_page_action_method(id)}
63
+ COMMENT
64
+ end
65
+ mb << <<-INDEX
66
+ def index
67
+ redirect_to :action=>:#{self.page_order.first}
68
+ end
69
+
70
+ INDEX
71
+ mb.string
72
+ end
73
+
74
+ def initial_referer_key
75
+ @initial_referer_key ||= "#{self.controller_path.sub(/\//, '')}_irk".to_sym
76
+ end
77
+ def persist_key;
78
+ @persist_key ||= "#{self.controller_path.sub(/\//, '')}_dat".to_sym
79
+ end
80
+ def progression_key
81
+ @progression_key ||= "#{self.controller_path.sub(/\//, '')}_prg".to_sym
82
+ end
83
+
84
+ def print_page_action_method(id)
85
+ page = @pages[id]
86
+ finish_button = self.button_for_function(:finish).id
87
+ next_button = self.button_for_function(:next).id
88
+
89
+ (mb = StringIO.new) << <<-ONE
90
+ def #{page.name}
91
+ begin
92
+ @step = :#{id}
93
+ @wizard = wizard_config
94
+ @title = '#{page.title}'
95
+ @description = '#{page.description}'
96
+ _build_wizard_model
97
+ if request.post? && callback_performs_action?(:_on_post_#{id}_form)
98
+ raise CallbackError, "render or redirect not allowed in :on_post(:#{id}) callback", caller
99
+ end
100
+ button_id = check_action_for_button
101
+ return if performed?
102
+ if request.get?
103
+ return if callback_performs_action?(:_on_get_#{id}_form)
104
+ render_wizard_form
105
+ return
106
+ end
107
+
108
+ # @#{self.model}.enable_validation_group :#{id}
109
+ unless @#{self.model}.valid?(:#{id})
110
+ return if callback_performs_action?(:_on_invalid_#{id}_form)
111
+ render_wizard_form
112
+ return
113
+ end
114
+
115
+ @do_not_complete = false
116
+ ONE
117
+ if self.last_page?(id)
118
+ mb << <<-TWO
119
+ callback_performs_action?(:_on_#{id}_form_#{finish_button})
120
+ complete_wizard unless @do_not_complete
121
+ TWO
122
+ elsif self.first_page?(id)
123
+ mb << <<-THREE
124
+ if button_id == :#{finish_button}
125
+ callback_performs_action?(:_on_#{id}_form_#{finish_button})
126
+ complete_wizard unless @do_not_complete
127
+ return
128
+ end
129
+ return if callback_performs_action?(:_on_#{id}_form_#{next_button})
130
+ redirect_to :action=>:#{self.next_page(id)}
131
+ THREE
132
+ else
133
+ mb << <<-FOUR
134
+ if button_id == :#{finish_button}
135
+ callback_performs_action?(:_on_#{id}_form_#{finish_button})
136
+ complete_wizard unless @do_not_complete
137
+ return
138
+ end
139
+ return if callback_performs_action?(:_on_#{id}_form_#{next_button})
140
+ redirect_to :action=>:#{self.next_page(id)}
141
+ FOUR
142
+ end
143
+
144
+ mb << <<-ENSURE
145
+ ensure
146
+ _preserve_wizard_model
147
+ end
148
+ end
149
+ ENSURE
150
+ mb.string
151
+ end
152
+
153
+ def print_callbacks
154
+ finish = self.button_for_function(:finish).id
155
+ skip = self.button_for_function(:skip).id
156
+ back = self.button_for_function(:back).id
157
+ cancel = self.button_for_function(:cancel).id
158
+ <<-CALLBACKS
159
+ protected
160
+ def _on_wizard_#{finish}
161
+ return if @wizard_completed_flag
162
+ @#{self.model}.save_without_validation! if @#{self.model}.changed?
163
+ @wizard_completed_flag = true
164
+ reset_wizard_form_data
165
+ _wizard_final_redirect_to(:completed)
166
+ end
167
+ def _on_wizard_#{skip}
168
+ self.progression = self.progression - [@step]
169
+ redirect_to(:action=>wizard_config.next_page(@step)) unless self.performed?
170
+ end
171
+ def _on_wizard_#{back}
172
+ redirect_to(:action=>(previous_in_progression_from(@step) || :#{self.page_order.first})) unless self.performed?
173
+ end
174
+ def _on_wizard_#{cancel}
175
+ _wizard_final_redirect_to(:canceled)
176
+ end
177
+ def _wizard_final_redirect_to(type)
178
+ init = (type == :canceled && wizard_config.form_data_keep_in_session?) ?
179
+ self.initial_referer :
180
+ reset_wizard_session_vars
181
+ unless self.performed?
182
+ redir = (type == :canceled ? wizard_config.canceled_redirect : wizard_config.completed_redirect) || init
183
+ return redirect_to(redir) if redir
184
+ raise Wizardly::RedirectNotDefinedError, "No redirect was defined for completion or canceling the wizard. Use :completed and :canceled options to define redirects.", caller
185
+ end
186
+ end
187
+ hide_action :_on_wizard_#{finish}, :_on_wizard_#{skip}, :_on_wizard_#{back}, :_on_wizard_#{cancel}, :_wizard_final_redirect_to
188
+ CALLBACKS
189
+ end
190
+
191
+ def print_helpers
192
+ next_id = self.button_for_function(:next).id
193
+ finish_id = self.button_for_function(:finish).id
194
+ first_page = self.page_order.first
195
+ finish_button = self.button_for_function(:finish).id
196
+ guard_line = self.guard? ? '' : 'return check_progression #guard entry disabled'
197
+ mb = StringIO.new
198
+ mb << <<-PROGRESSION
199
+ protected
200
+ def do_not_complete; @do_not_complete = true; end
201
+ def previous_in_progression_from(step)
202
+ po = #{self.page_order.inspect}
203
+ p = self.progression
204
+ p -= po[po.index(step)..-1]
205
+ self.progression = p
206
+ p.last
207
+ end
208
+ def check_progression
209
+ p = self.progression
210
+ a = params[:action].to_sym
211
+ return if p.last == a
212
+ po = #{self.page_order.inspect}
213
+ return unless (ai = po.index(a))
214
+ p -= po[ai..-1]
215
+ p << a
216
+ self.progression = p
217
+ end
218
+ PROGRESSION
219
+ if self.form_data_keep_in_session?
220
+ mb << <<-SESSION
221
+ # for :form_data=>:session
222
+ def guard_entry
223
+ if (r = request.env['HTTP_REFERER'])
224
+ begin
225
+ h = ::ActionController::Routing::Routes.recognize_path(URI.parse(r).path, {:method=>:get})
226
+ rescue
227
+ else
228
+ return check_progression if (h[:controller]||'') == '#{self.controller_path}'
229
+ self.initial_referer = h unless self.initial_referer
230
+ end
231
+ end
232
+ # coming from outside the controller or no route for GET
233
+ #{guard_line}
234
+ if (params[:action] == '#{first_page}' || params[:action] == 'index')
235
+ return check_progression
236
+ elsif self.wizard_form_data
237
+ p = self.progression
238
+ return check_progression if p.include?(params[:action].to_sym)
239
+ return redirect_to(:action=>(p.last||:#{first_page}))
240
+ end
241
+ redirect_to :action=>:#{first_page}
242
+ end
243
+ hide_action :guard_entry
244
+
245
+ SESSION
246
+ else
247
+ mb << <<-SANDBOX
248
+ # for :form_data=>:sandbox
249
+ def guard_entry
250
+ if (r = request.env['HTTP_REFERER'])
251
+ begin
252
+ h = ::ActionController::Routing::Routes.recognize_path(URI.parse(r).path, {:method=>:get})
253
+ rescue
254
+ else
255
+ return check_progression if (h[:controller]||'') == '#{self.controller_path}'
256
+ self.initial_referer = h
257
+ end
258
+ else
259
+ self.initial_referer = nil
260
+ end
261
+ # coming from outside the controller
262
+ reset_wizard_form_data
263
+ #{guard_line}
264
+ return redirect_to(:action=>:#{first_page}) unless (params[:action] || '') == '#{first_page}'
265
+ check_progression
266
+ end
267
+ hide_action :guard_entry
268
+
269
+ SANDBOX
270
+ end
271
+ mb << <<-HELPERS
272
+ def render_and_return
273
+ return if callback_performs_action?('_on_get_'+@step.to_s+'_form')
274
+ render_wizard_form
275
+ render unless self.performed?
276
+ end
277
+
278
+ def complete_wizard(redirect = nil)
279
+ unless @wizard_completed_flag
280
+ @#{self.model}.save_without_validation!
281
+ callback_performs_action?(:_after_wizard_save)
282
+ end
283
+ redirect_to redirect if (redirect && !self.performed?)
284
+ return if @wizard_completed_flag
285
+ _on_wizard_#{finish_button}
286
+ redirect_to(#{Utils.formatted_redirect(self.completed_redirect)}) unless self.performed?
287
+ end
288
+ def _build_wizard_model
289
+ if self.wizard_config.persist_model_per_page?
290
+ h = self.wizard_form_data
291
+ if (h && model_id = h['id'])
292
+ _model = #{self.model_class_name}.find(model_id)
293
+ _model.attributes = params[:#{self.model}]||{}
294
+ @#{self.model} = _model
295
+ return
296
+ end
297
+ @#{self.model} = #{self.model_class_name}.new(params[:#{self.model}])
298
+ else # persist data in session or flash
299
+ h = (self.wizard_form_data||{}).merge(params[:#{self.model}] || {})
300
+ @#{self.model} = #{self.model_class_name}.new(h)
301
+ end
302
+ end
303
+ def _preserve_wizard_model
304
+ return unless (@#{self.model} && !@wizard_completed_flag)
305
+ if self.wizard_config.persist_model_per_page?
306
+ @#{self.model}.save_without_validation!
307
+ if request.get?
308
+ @#{self.model}.errors.clear
309
+ else
310
+ @#{self.model}.reject_non_validation_group_errors
311
+ end
312
+ self.wizard_form_data = {'id'=>@#{self.model}.id}
313
+ else
314
+ self.wizard_form_data = @#{self.model}.attributes
315
+ end
316
+ end
317
+ hide_action :_build_wizard_model, :_preserve_wizard_model
318
+
319
+ def initial_referer
320
+ session[:#{self.initial_referer_key}]
321
+ end
322
+ def initial_referer=(val)
323
+ session[:#{self.initial_referer_key}] = val
324
+ end
325
+ def progression=(array)
326
+ session[:#{self.progression_key}] = array
327
+ end
328
+ def progression
329
+ session[:#{self.progression_key}]||[]
330
+ end
331
+ hide_action :progression, :progression=, :initial_referer, :initial_referer=
332
+
333
+ def wizard_form_data=(hash)
334
+ if wizard_config.form_data_keep_in_session?
335
+ session[:#{self.persist_key}] = hash
336
+ else
337
+ if hash
338
+ flash[:#{self.persist_key}] = hash
339
+ else
340
+ flash.discard(:#{self.persist_key})
341
+ end
342
+ end
343
+ end
344
+
345
+ def reset_wizard_form_data; self.wizard_form_data = nil; end
346
+ def wizard_form_data
347
+ wizard_config.form_data_keep_in_session? ? session[:#{self.persist_key}] : flash[:#{self.persist_key}]
348
+ end
349
+ hide_action :wizard_form_data, :wizard_form_data=, :reset_wizard_form_data
350
+
351
+ def render_wizard_form
352
+ end
353
+ hide_action :render_wizard_form
354
+
355
+ def performed?; super; end
356
+ hide_action :performed?
357
+
358
+ def underscore_button_name(value)
359
+ value.to_s.strip.squeeze(' ').gsub(/ /, '_').downcase
360
+ end
361
+ hide_action :underscore_button_name
362
+
363
+ def reset_wizard_session_vars
364
+ self.progression = nil
365
+ init = self.initial_referer
366
+ self.initial_referer = nil
367
+ init
368
+ end
369
+ hide_action :reset_wizard_session_vars
370
+
371
+ def check_action_for_button
372
+ button_id = nil
373
+ case
374
+ when params[:commit]
375
+ button_name = params[:commit]
376
+ button_id = underscore_button_name(button_name).to_sym
377
+ when ((b_ar = self.wizard_config.buttons.find{|k,b| params[k]}) && params[b_ar.first] == b_ar.last.name)
378
+ button_name = b_ar.last.name
379
+ button_id = b_ar.first
380
+ end
381
+ if button_id
382
+ unless [:#{next_id}, :#{finish_id}].include?(button_id)
383
+ action_method_name = "_on_" + params[:action].to_s + "_form_" + button_id.to_s
384
+ callback_performs_action?(action_method_name)
385
+ unless ((btn_obj = self.wizard_config.buttons[button_id]) == nil || btn_obj.user_defined?)
386
+ method_name = "_on_wizard_" + button_id.to_s
387
+ if (self.method(method_name))
388
+ self.__send__(method_name)
389
+ else
390
+ raise MissingCallbackError, "Callback method either '" + action_method_name + "' or '" + method_name + "' not defined", caller
391
+ end
392
+ end
393
+ end
394
+ end
395
+ button_id
396
+ end
397
+ hide_action :check_action_for_button
398
+
399
+ @wizard_callbacks ||= []
400
+ def self.wizard_callbacks; @wizard_callbacks; end
401
+
402
+ def callback_performs_action?(methId)
403
+ cache = self.class.wizard_callbacks
404
+ return false if cache.include?(methId)
405
+
406
+ if self.respond_to?(methId, true)
407
+ self.send(methId)
408
+ else
409
+ cache << methId
410
+ return false
411
+ end
412
+
413
+ self.performed?
414
+ end
415
+ hide_action :callback_performs_action?
416
+
417
+ HELPERS
418
+ mb.string
419
+ end
420
+ end
421
+ end
422
+ end
@@ -0,0 +1,27 @@
1
+ module Wizardly
2
+ module Wizard
3
+ class DSL
4
+
5
+ def initialize(config)
6
+ @config = config
7
+ end
8
+
9
+ # DSL methods
10
+ def when_completed_redirect_to(redir); @config._when_completed_redirect_to(redir); end
11
+ def when_canceled_redirect_to(redir); @config._when_canceled_redirect_to(redir); end
12
+ def change_button(name)
13
+ @config._change_button(name)
14
+ end
15
+ def create_button(name, opts)
16
+ @config._create_button(name, opts)
17
+ end
18
+ def set_page(name);
19
+ @config._set_page(name)
20
+ end
21
+ def mask_passwords(passwords)
22
+ @config._mask_passwords(passwords)
23
+ end
24
+ alias_method :mask_password, :mask_passwords
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,62 @@
1
+ require 'wizardly/wizard/text_helpers'
2
+
3
+ module Wizardly
4
+ module Wizard
5
+ class Page
6
+ include TextHelpers
7
+
8
+ attr_reader :id, :title, :description
9
+ attr_accessor :buttons, :fields
10
+
11
+ def initialize(config, id, fields)
12
+ @buttons = []
13
+ @title = symbol_to_button_name(id)
14
+ @id = id
15
+ @description = ''
16
+ @fields = fields
17
+ @config = config
18
+ end
19
+
20
+ def name; id.to_s; end
21
+
22
+ def buttons_to(*args)
23
+ buttons = @config.buttons
24
+ @buttons = args.map do |button_id|
25
+ raise(WizardConfigurationError, ":#{button_id} not defined as a button id in :button_to() call", caller) unless buttons.key?(button_id)
26
+ buttons[button_id]
27
+ end
28
+ end
29
+ def title_to(name)
30
+ @title = name.strip.squeeze(' ')
31
+ end
32
+ def description_to(name)
33
+ @description = name.strip.squeeze(' ')
34
+ end
35
+ end
36
+
37
+ class PageField
38
+ attr_reader :name, :column_type
39
+
40
+ def initialize(name, type)
41
+ @name = name
42
+ @column_type = type.to_sym
43
+ @field_type = nil
44
+ end
45
+
46
+ def field_type
47
+ @field_type ||= case @column_type
48
+ when :string then :text_field
49
+ when :password then :password_field
50
+ when :enum then :enum_select
51
+ when :text then :text_area
52
+ when :boolean then :check_box
53
+ when :integer, :float, :decimal then :text_field
54
+ when :datetime, :timestamp, :time then :datetime_select
55
+ when :date then :date_select
56
+ else
57
+ :text_field
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end