wizardly 0.1.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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