lapluviosilla-wizardly 0.1.8.8

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 +414 -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 +100 -0
@@ -0,0 +1,414 @@
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
+ h = ::ActionController::Routing::Routes.recognize_path(URI.parse(r).path)
225
+ return check_progression if (h[:controller]||'') == '#{self.controller_name}'
226
+ self.initial_referer = h unless self.initial_referer
227
+ end
228
+ # coming from outside the controller
229
+ #{guard_line}
230
+ if (params[:action] == '#{first_page}' || params[:action] == 'index')
231
+ return check_progression
232
+ elsif self.wizard_form_data
233
+ p = self.progression
234
+ return check_progression if p.include?(params[:action].to_sym)
235
+ return redirect_to(:action=>(p.last||:#{first_page}))
236
+ end
237
+ redirect_to :action=>:#{first_page}
238
+ end
239
+ hide_action :guard_entry
240
+
241
+ SESSION
242
+ else
243
+ mb << <<-SANDBOX
244
+ # for :form_data=>:sandbox
245
+ def guard_entry
246
+ if (r = request.env['HTTP_REFERER'])
247
+ h = ::ActionController::Routing::Routes.recognize_path(URI.parse(r).path)
248
+ return check_progression if (h[:controller]||'') == '#{self.controller_name}'
249
+ self.initial_referer = h
250
+ else
251
+ self.initial_referer = nil
252
+ end
253
+ # coming from outside the controller
254
+ reset_wizard_form_data
255
+ #{guard_line}
256
+ return redirect_to(:action=>:#{first_page}) unless (params[:action] || '') == '#{first_page}'
257
+ check_progression
258
+ end
259
+ hide_action :guard_entry
260
+
261
+ SANDBOX
262
+ end
263
+ mb << <<-HELPERS
264
+ def render_and_return
265
+ return if callback_performs_action?('_on_get_'+@step.to_s+'_form')
266
+ render_wizard_form
267
+ render unless self.performed?
268
+ end
269
+
270
+ def complete_wizard(redirect = nil)
271
+ unless @wizard_completed_flag
272
+ @#{self.model}.save_without_validation!
273
+ callback_performs_action?(:_after_wizard_save)
274
+ end
275
+ redirect_to redirect if (redirect && !self.performed?)
276
+ return if @wizard_completed_flag
277
+ _on_wizard_#{finish_button}
278
+ redirect_to(#{Utils.formatted_redirect(self.completed_redirect)}) unless self.performed?
279
+ end
280
+ def _build_wizard_model
281
+ if self.wizard_config.persist_model_per_page?
282
+ h = self.wizard_form_data
283
+ if (h && model_id = h['id'])
284
+ _model = #{self.model_class_name}.find(model_id)
285
+ _model.attributes = params[:#{self.model}]||{}
286
+ @#{self.model} = _model
287
+ return
288
+ end
289
+ @#{self.model} = #{self.model_class_name}.new(params[:#{self.model}])
290
+ else # persist data in session or flash
291
+ h = (self.wizard_form_data||{}).merge(params[:#{self.model}] || {})
292
+ @#{self.model} = #{self.model_class_name}.new(h)
293
+ end
294
+ end
295
+ def _preserve_wizard_model
296
+ return unless (@#{self.model} && !@wizard_completed_flag)
297
+ if self.wizard_config.persist_model_per_page?
298
+ @#{self.model}.save_without_validation!
299
+ if request.get?
300
+ @#{self.model}.errors.clear
301
+ else
302
+ @#{self.model}.reject_non_validation_group_errors
303
+ end
304
+ self.wizard_form_data = {'id'=>@#{self.model}.id}
305
+ else
306
+ self.wizard_form_data = @#{self.model}.attributes
307
+ end
308
+ end
309
+ hide_action :_build_wizard_model, :_preserve_wizard_model
310
+
311
+ def initial_referer
312
+ session[:#{self.initial_referer_key}]
313
+ end
314
+ def initial_referer=(val)
315
+ session[:#{self.initial_referer_key}] = val
316
+ end
317
+ def progression=(array)
318
+ session[:#{self.progression_key}] = array
319
+ end
320
+ def progression
321
+ session[:#{self.progression_key}]||[]
322
+ end
323
+ hide_action :progression, :progression=, :initial_referer, :initial_referer=
324
+
325
+ def wizard_form_data=(hash)
326
+ if wizard_config.form_data_keep_in_session?
327
+ session[:#{self.persist_key}] = hash
328
+ else
329
+ if hash
330
+ flash[:#{self.persist_key}] = hash
331
+ else
332
+ flash.discard(:#{self.persist_key})
333
+ end
334
+ end
335
+ end
336
+
337
+ def reset_wizard_form_data; self.wizard_form_data = nil; end
338
+ def wizard_form_data
339
+ wizard_config.form_data_keep_in_session? ? session[:#{self.persist_key}] : flash[:#{self.persist_key}]
340
+ end
341
+ hide_action :wizard_form_data, :wizard_form_data=, :reset_wizard_form_data
342
+
343
+ def render_wizard_form
344
+ end
345
+ hide_action :render_wizard_form
346
+
347
+ def performed?; super; end
348
+ hide_action :performed?
349
+
350
+ def underscore_button_name(value)
351
+ value.to_s.strip.squeeze(' ').gsub(/ /, '_').downcase
352
+ end
353
+ hide_action :underscore_button_name
354
+
355
+ def reset_wizard_session_vars
356
+ self.progression = nil
357
+ init = self.initial_referer
358
+ self.initial_referer = nil
359
+ init
360
+ end
361
+ hide_action :reset_wizard_session_vars
362
+
363
+ def check_action_for_button
364
+ button_id = nil
365
+ case
366
+ when params[:commit]
367
+ button_name = params[:commit]
368
+ button_id = underscore_button_name(button_name).to_sym
369
+ when ((b_ar = self.wizard_config.buttons.find{|k,b| params[k]}) && params[b_ar.first] == b_ar.last.name)
370
+ button_name = b_ar.last.name
371
+ button_id = b_ar.first
372
+ end
373
+ if button_id
374
+ unless [:#{next_id}, :#{finish_id}].include?(button_id)
375
+ action_method_name = "_on_" + params[:action].to_s + "_form_" + button_id.to_s
376
+ callback_performs_action?(action_method_name)
377
+ unless ((btn_obj = self.wizard_config.buttons[button_id]) == nil || btn_obj.user_defined?)
378
+ method_name = "_on_wizard_" + button_id.to_s
379
+ if (self.method(method_name))
380
+ self.__send__(method_name)
381
+ else
382
+ raise MissingCallbackError, "Callback method either '" + action_method_name + "' or '" + method_name + "' not defined", caller
383
+ end
384
+ end
385
+ end
386
+ end
387
+ button_id
388
+ end
389
+ hide_action :check_action_for_button
390
+
391
+ @wizard_callbacks ||= []
392
+ def self.wizard_callbacks; @wizard_callbacks; end
393
+
394
+ def callback_performs_action?(methId)
395
+ cache = self.class.wizard_callbacks
396
+ return false if cache.include?(methId)
397
+
398
+ begin
399
+ self.send(methId)
400
+ rescue NoMethodError
401
+ cache << methId
402
+ return false
403
+ end
404
+
405
+ self.performed?
406
+ end
407
+ hide_action :callback_performs_action?
408
+
409
+ HELPERS
410
+ mb.string
411
+ end
412
+ end
413
+ end
414
+ 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