lapluviosilla-wizardly 0.1.8.8

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 +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