hat-trick 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -28,7 +28,7 @@ module HatTrick
28
28
  end
29
29
 
30
30
  def model_class
31
- return ht_wizard.model.class if ht_wizard.model
31
+ return hat_trick_wizard.model.class if hat_trick_wizard.model
32
32
 
33
33
  # if that didn't work, try to grab it from the params hash
34
34
  model_name = params_model_name
@@ -46,10 +46,21 @@ module HatTrick
46
46
 
47
47
  def setup_validation_group_for(wizard_step)
48
48
  klass = model_class
49
- return if klass.nil?
49
+ if klass.nil?
50
+ Rails.logger.warn "model class was nil when setting up validation group for #{wizard_step}"
51
+ return
52
+ end
50
53
  step_name = wizard_step.name
51
54
  validation_groups = ::ActiveRecord::Base.validation_group_classes[klass] || []
52
- unless validation_groups.include?(step_name)
55
+ dynamic_group_exists = HatTrick::ModelMethods.dynamic_validation_groups.include?(step_name)
56
+ static_validation_group_exists = validation_groups.include?(step_name) && !dynamic_group_exists
57
+ dynamic_validation_group = false
58
+
59
+ if static_validation_group_exists
60
+ Rails.logger.info "Not creating dynamic validation group for #{step_name} because a static one exists"
61
+ else
62
+ Rails.logger.info "Creating a dynamic validation group for #{step_name}"
63
+ dynamic_validation_group = true
53
64
  validation_fields = params.keys # TODO: Try it without these (so only the model keys below)
54
65
  model = model_key
55
66
  if model
@@ -58,7 +69,10 @@ module HatTrick
58
69
  validation_fields = validation_fields.map(&:to_sym)
59
70
  klass.validation_group(step_name, :fields => validation_fields)
60
71
  end
61
- HatTrick::ModelMethods.set_current_validation_group_for(model_class, step_name)
72
+ Rails.logger.info "Setting current validation group for model class #{model_class} to #{step_name}"
73
+ HatTrick::ModelMethods.set_current_validation_group_for(model_class,
74
+ step_name,
75
+ dynamic_validation_group)
62
76
  end
63
77
  end
64
78
  end
@@ -12,62 +12,76 @@ module HatTrick
12
12
 
13
13
  def self.def_action_method_aliases(action_methods)
14
14
  action_methods.each do |meth|
15
- # Rails.logger.info "Aliasing #{meth}"
16
- module_eval <<-RUBY_EVAL
17
- def #{meth}_with_hat_trick(*args)
18
- Rails.logger.info "#{meth}_with_hat_trick called"
19
- #{meth}_hook(*args) if respond_to?("#{meth}_hook", :include_private)
20
- common_hook(*args) if respond_to?(:common_hook, :include_private)
21
- #{meth}_without_hat_trick(*args)
22
- end
23
- private "#{meth}_with_hat_trick"
24
- RUBY_EVAL
15
+ unless respond_to?(:"#{meth}_with_hat_trick")
16
+ Rails.logger.info "Defining #{meth}_with_hat_trick"
17
+ module_eval <<-RUBY_EVAL
18
+ def #{meth}_with_hat_trick(*args)
19
+ Rails.logger.info "#{meth}_with_hat_trick called"
20
+ if respond_to?("#{meth}_hook", :include_private)
21
+ #{meth}_hook(*args)
22
+ end
23
+ common_hook(*args) if respond_to?(:common_hook, :include_private)
24
+ #{meth}_without_hat_trick(*args)
25
+ end
26
+ private "#{meth}_with_hat_trick"
27
+ RUBY_EVAL
28
+ end
25
29
  end
26
30
  true
27
31
  end
28
32
 
29
33
  private
30
34
 
35
+ def common_hook(*args)
36
+ Rails.logger.info "common_hook wizard instance: #{hat_trick_wizard.object_id}"
37
+ end
38
+
31
39
  def create_hook(*args)
32
- setup_validation_group_for(ht_wizard.current_step)
40
+ setup_validation_group_for(hat_trick_wizard.current_step)
33
41
  end
34
42
 
35
43
  def update_hook(*args)
36
- setup_validation_group_for(ht_wizard.current_step)
44
+ setup_validation_group_for(hat_trick_wizard.current_step)
37
45
  end
38
46
 
39
47
  def render_with_hat_trick(*args, &block)
40
48
  rendered = args.first
41
49
  if rendered && rendered.has_key?(:json)
42
- model = rendered[:json]
43
- ht_wizard.model = model
50
+ hat_trick_wizard.model = rendered[:json]
51
+ else
52
+ Rails.logger.warn "No model found in render args #{args.inspect}; model is #{hat_trick_wizard.model.inspect}"
44
53
  end
45
54
 
46
55
  if params.has_key?('_ht_meta')
47
- next_step = params['_ht_meta']['next_step']
48
- ht_wizard.advance_step(next_step)
56
+ next_step = params['_ht_step_link']
57
+ hat_trick_wizard.advance_step(next_step)
49
58
  end
50
59
 
51
60
  wizard_metadata = {
52
- :url => ht_wizard.current_form_url,
53
- :method => ht_wizard.current_form_method,
54
- :currentStep => ht_wizard.current_step,
61
+ :externalRedirectURL => hat_trick_wizard.external_redirect_url,
62
+ :url => hat_trick_wizard.current_form_url,
63
+ :method => hat_trick_wizard.current_form_method,
64
+ :currentStep => hat_trick_wizard.current_step,
65
+ :percentComplete => hat_trick_wizard.percent_complete,
55
66
  }
56
67
 
57
- include_data = ht_wizard.include_data
68
+ include_data = hat_trick_wizard.include_data
58
69
 
59
70
  # this sets the gon data (JS hatTrick object) for the initial page load
60
71
  gon.metadata = wizard_metadata
61
72
  gon.data = include_data
62
- gon.model = ht_wizard.model
73
+ gon.model = hat_trick_wizard.model
63
74
 
64
75
  # this sets the wizard metadata for subsequent AJAX requests
65
- if ht_wizard.model && rendered.has_key?(:json)
66
- args[0][:json] = { :model => ht_wizard.model,
76
+ if hat_trick_wizard.model && rendered.has_key?(:json)
77
+ args[0][:json] = { :model => hat_trick_wizard.model,
67
78
  :metadata => wizard_metadata }
68
79
  args[0][:json].merge!( :data => include_data )
69
80
  end
70
81
 
82
+ # unset redirects for subsequent steps
83
+ hat_trick_wizard.external_redirect_url = nil
84
+
71
85
  render_without_hat_trick(*args, &block)
72
86
  end
73
87
  end
data/lib/hat_trick/dsl.rb CHANGED
@@ -6,37 +6,66 @@ module HatTrick
6
6
  module DSL
7
7
  extend ActiveSupport::Concern
8
8
 
9
- attr_accessor :ht_wizard, :configure_callback, :_ht_config
9
+ attr_accessor :hat_trick_wizard
10
10
 
11
- delegate :model, :previously_visited_step, :to => :ht_wizard
12
-
13
- included do
14
- alias_method_chain :initialize, :hat_trick
15
- end
16
-
17
- def initialize_with_hat_trick(*args)
18
- @_ht_config = HatTrick::Config.new(self.class.wizard_def)
19
- if configure_callback.is_a?(Proc)
20
- ht_wizard.controller.instance_exec(@_ht_config, &configure_callback)
21
- end
22
- initialize_without_hat_trick(*args)
23
- end
11
+ delegate :model, :previously_visited_step, :to => :hat_trick_wizard
24
12
 
25
13
  def next_step(name=nil)
26
14
  if name.nil?
27
15
  # getter
28
- ht_wizard.next_step
16
+ hat_trick_wizard.next_step
29
17
  else
30
18
  # setter
31
- step = ht_wizard.find_step(name)
19
+ step = hat_trick_wizard.find_step(name)
32
20
  # explicitly set steps should not be skipped
33
21
  step.skipped = false
34
- ht_wizard.current_step.next_step = step
22
+ hat_trick_wizard.current_step.next_step = step
23
+ end
24
+ end
25
+
26
+ def previously_visited_step_name
27
+ if previously_visited_step.nil?
28
+ ''
29
+ else
30
+ previously_visited_step.name
35
31
  end
36
32
  end
37
33
 
38
34
  def skip_this_step
39
- ht_wizard.skip_step(ht_wizard.current_step)
35
+ hat_trick_wizard.skip_step(hat_trick_wizard.current_step)
36
+ end
37
+
38
+ def button_to(step_name, options={})
39
+ button = self.class.send(:create_button_to, step_name, options)
40
+ hat_trick_wizard.current_step.add_button button
41
+ end
42
+
43
+ def remaining_step_count
44
+ hat_trick_wizard.steps_after_current
45
+ end
46
+
47
+ def adjust_remaining_step_count_by(diff)
48
+ hat_trick_wizard.steps_remaining += diff
49
+ end
50
+
51
+ def change_remaining_step_count_to(count)
52
+ hat_trick_wizard.steps_remaining = count
53
+ end
54
+
55
+ def total_step_count
56
+ hat_trick_wizard.total_step_count
57
+ end
58
+
59
+ def reset_step_count
60
+ hat_trick_wizard.override_step_count = nil
61
+ end
62
+
63
+ def redirect_to_step(step_name)
64
+ hat_trick_wizard.redirect_to_step step_name
65
+ end
66
+
67
+ def redirect_to_external_url(url)
68
+ hat_trick_wizard.external_redirect_url = url
40
69
  end
41
70
 
42
71
  module ClassMethods
@@ -47,7 +76,10 @@ module HatTrick
47
76
  include HatTrick::DSL::ControllerInstanceMethods
48
77
  include HatTrick::ControllerHooks
49
78
 
50
- @wizard_def = HatTrick::WizardDefinition.new
79
+ ::ActiveRecord::Base.send(:include, HatTrick::ModelMethods)
80
+
81
+ config = HatTrick::Config.new
82
+ @wizard_def = HatTrick::WizardDefinition.new(config)
51
83
 
52
84
  yield
53
85
 
@@ -58,7 +90,15 @@ module HatTrick
58
90
 
59
91
  def configure(&block)
60
92
  raise "Must pass a block to configure" unless block_given?
61
- @config_callback = block
93
+ self.configure_callback = block
94
+ end
95
+
96
+ def button_label(type, label)
97
+ wizard_def.config.send("#{type}_button_label=", label)
98
+ end
99
+
100
+ def button_label_i18n_key(type, i18n_key)
101
+ wizard_def.config.send("#{type}_button_label_i18n_key=", i18n_key)
62
102
  end
63
103
 
64
104
  def step(name, args={}, &block)
@@ -67,19 +107,6 @@ module HatTrick
67
107
  instance_eval &block if block_given?
68
108
  end
69
109
 
70
- def repeat_step(name)
71
- raise "repeat_step must be called from within a wizard block" unless wizard_def
72
- repeated_step = wizard_def.find_step(name)
73
- raise ArgumentError, "Couldn't find step named #{name}" unless repeated_step
74
- new_step = repeated_step.dup
75
- # use the repeated step's fieldset id
76
- new_step.fieldset = repeated_step.fieldset
77
- # but use the current step's name
78
- new_step.name = wizard_def.last_step.name
79
- # replace the step we're in the middle of defining w/ new_step
80
- wizard_def.replace_step(wizard_def.last_step, new_step)
81
- end
82
-
83
110
  def skip_this_step
84
111
  # skip_this_step in wizard definition (class) context means the step
85
112
  # can be explicitly jumped to, but won't be visited in the normal flow
@@ -87,27 +114,38 @@ module HatTrick
87
114
  wizard_def.last_step.skipped = true
88
115
  end
89
116
 
90
- def button_to(name, options=nil)
91
- raise "button_to must be called from within a wizard block" unless wizard_def
92
- label = options[:label] if options
93
- label ||= name.to_s.humanize
117
+ def last_step
118
+ raise "skip_this_step must be called from within a wizard block" unless wizard_def
119
+ wizard_def.last_step.last = true
120
+ end
94
121
 
95
- id = options[:id] if options
122
+ def button_to(step_name, options={})
123
+ raise "button_to must be called from within a wizard block" unless wizard_def
124
+ wizard_def.last_step.add_button create_button_to(step_name, options)
125
+ end
96
126
 
127
+ def hide_button(button_type)
128
+ raise "before must be called from within a wizard block" unless wizard_def
97
129
  step = wizard_def.last_step
98
- button = { :label => label }
99
- button[:id] = id unless id.nil?
100
- step.buttons = step.buttons.merge(name => button)
130
+ step.delete_button button_type
101
131
  end
102
132
 
103
- def before(&block)
133
+ def before(scope=:current_step, &block)
104
134
  raise "before must be called from within a wizard block" unless wizard_def
105
- wizard_def.last_step.before_callback = block
135
+ if scope == :each
136
+ wizard_def.before_callback_for_all_steps = block
137
+ else
138
+ wizard_def.last_step.before_callback = block
139
+ end
106
140
  end
107
141
 
108
- def after(&block)
142
+ def after(scope=:current_step, &block)
109
143
  raise "after must be called from within a wizard block" unless wizard_def
110
- wizard_def.last_step.after_callback = block
144
+ if scope == :each
145
+ wizard_def.after_callback_for_all_steps = block
146
+ else
147
+ wizard_def.last_step.after_callback = block
148
+ end
111
149
  end
112
150
 
113
151
  def include_data(key, &block)
@@ -117,10 +155,39 @@ module HatTrick
117
155
 
118
156
  def set_contents(&block)
119
157
  raise "set_contents must be called from within a wizard block" unless wizard_def
120
- current_step_name = wizard_def.last_step.to_sym
121
- include_data "hat_trick_step_contents" do |wiz, model|
122
- { current_step_name => instance_exec(wiz, model, &block) }
158
+ wizard_def.last_step.step_contents_callback = block
159
+ end
160
+
161
+ private
162
+
163
+ def configure_callback
164
+ @configure_callback
165
+ end
166
+
167
+ def configure_callback=(block)
168
+ @configure_callback = block
169
+ end
170
+
171
+ def create_button_to(to_step_name, options={})
172
+ label = options[:label]
173
+ label ||= to_step_name.to_s.humanize
174
+
175
+ name = options[:name]
176
+ name ||= to_step_name.to_s.parameterize
177
+
178
+ value = options[:value]
179
+ value ||= to_step_name.to_s.parameterize
180
+
181
+ if options
182
+ id = options[:id]
183
+ css_class = options[:class]
123
184
  end
185
+
186
+ button = { :name => name, :value => value, :label => label }
187
+ button[:id] = id unless id.nil?
188
+ button[:class] = css_class unless css_class.nil?
189
+
190
+ { to_step_name => button }
124
191
  end
125
192
  end
126
193
 
@@ -131,15 +198,42 @@ module HatTrick
131
198
  before_filter :setup_wizard
132
199
  end
133
200
 
201
+ def back_to_start
202
+ respond_to do |format|
203
+ format.json { }
204
+ end
205
+ end
206
+
134
207
  private
135
208
 
136
209
  def setup_wizard
137
210
  wizard_def = self.class.instance_variable_get("@wizard_def")
138
- @ht_wizard = wizard_def.get_wizard(self)
211
+ @hat_trick_wizard ||= wizard_def.make_wizard_for(self)
212
+
213
+ Rails.logger.info "setup_wizard wizard instance: #{@hat_trick_wizard.object_id}"
214
+
215
+ config_callback = self.class.send(:configure_callback)
216
+ if config_callback.is_a?(Proc)
217
+ instance_exec(wizard_def.config, &config_callback)
218
+ end
139
219
 
140
220
  if params.has_key?('_ht_meta')
141
221
  step_name = params['_ht_meta']['step']
142
- @ht_wizard.current_step = step_name if step_name
222
+ end
223
+
224
+ # TODO: Setup the route that enables this in hat-trick automatically.
225
+ # Currently done manually in the app.
226
+ if params.has_key?('step')
227
+ step_name = params['step']
228
+ end
229
+
230
+ if step_name.present?
231
+ Rails.logger.info "Setting current step to: #{step_name}"
232
+ begin
233
+ @hat_trick_wizard.current_step = step_name
234
+ rescue StepNotFound => e
235
+ raise ActionController::RoutingError, e.message
236
+ end
143
237
  end
144
238
  end
145
239
  end
@@ -4,6 +4,12 @@ module HatTrick
4
4
  class Engine < ::Rails::Engine
5
5
  # just defining this causes Rails to look for assets inside this gem
6
6
 
7
+ initializer 'hat-trick.dsl' do
8
+ ActiveSupport.on_load(:action_controller) do
9
+ include HatTrick::DSL
10
+ end
11
+ end
12
+
7
13
  initializer 'hat-trick.form_helpers' do
8
14
  ActiveSupport.on_load(:action_view) do
9
15
  include HatTrick::FormHelper
@@ -4,7 +4,7 @@ module HatTrick
4
4
  options = args.extract_options!
5
5
  options[:html] = { :class => 'wizard' }
6
6
 
7
- wizard = controller.send(:ht_wizard)
7
+ wizard = controller.send(:hat_trick_wizard)
8
8
  wizard.start unless wizard.started?
9
9
 
10
10
  options[:url] = wizard.current_form_url
@@ -2,6 +2,7 @@ module HatTrick
2
2
  module ModelMethods
3
3
  extend ActiveSupport::Concern
4
4
  mattr_accessor :validation_groups
5
+ mattr_accessor :dynamic_validation_groups
5
6
 
6
7
  attr_accessor :_dummy # so the dummy field will have something to set
7
8
 
@@ -10,9 +11,14 @@ module HatTrick
10
11
  alias_method_chain :as_json, :model_name if instance_methods.include?(:as_json)
11
12
  end
12
13
 
13
- def self.set_current_validation_group_for(klass, validation_group_name)
14
+ def self.dynamic_validation_groups
15
+ @dynamic_validation_groups ||= []
16
+ end
17
+
18
+ def self.set_current_validation_group_for(klass, validation_group_name, dynamic)
14
19
  self.validation_groups ||= {}
15
20
  validation_groups[klass.to_s.underscore] = validation_group_name
21
+ dynamic_validation_groups << validation_group_name if dynamic
16
22
  end
17
23
 
18
24
  def self.current_validation_group_for(klass)
@@ -33,14 +39,19 @@ module HatTrick
33
39
  private
34
40
 
35
41
  # don't call this method 'current_validation_group', it conflicts with
36
- # the gem
42
+ # the validation_group gem
37
43
  def current_step_validation_group
38
44
  HatTrick::ModelMethods.current_validation_group_for(self.class)
39
45
  end
40
46
 
41
47
  def enable_current_validation_group
42
48
  validation_group = current_step_validation_group
43
- enable_validation_group validation_group if validation_group
49
+ if validation_group
50
+ Rails.logger.info "Enabling validation group #{validation_group}"
51
+ enable_validation_group validation_group
52
+ else
53
+ Rails.logger.info "NOT enabling a validation group"
54
+ end
44
55
  end
45
56
  end
46
57
  end
@@ -3,12 +3,16 @@ require 'hat_trick/step_definition'
3
3
  module HatTrick
4
4
  class Step
5
5
  attr_reader :step_def, :wizard
6
+ attr_accessor :next_step, :redirect_from
6
7
  attr_writer :skipped
7
- attr_accessor :next_step
8
8
 
9
- delegate :name, :fieldset, :buttons, :repeat_of, :to_sym, :to_s, :as_json,
10
- :run_after_callback, :run_before_callback, :repeat?,
11
- :run_include_data_callback, :include_data_key, :to => :step_def
9
+ delegate :name, :fieldset, :add_button, :buttons, :to_sym, :to_s,
10
+ :run_after_callback, :run_before_callback, :include_data,
11
+ :run_include_data_callback, :run_step_contents_callback,
12
+ :include_data_key, :config, :step_contents, :last?, :first?,
13
+ :to => :step_def
14
+
15
+ delegate :visited_steps, :skipped_steps, :to => :wizard
12
16
 
13
17
  def initialize(step_def, wizard)
14
18
  @step_def = step_def
@@ -16,20 +20,29 @@ module HatTrick
16
20
  @skipped = step_def.skipped?
17
21
  end
18
22
 
19
- def session
20
- wizard.session
23
+ def skipped?
24
+ @skipped || (skipped_steps.include?(self.to_sym) && !visited?)
21
25
  end
22
26
 
23
- def skipped?
24
- not visited? and @skipped
27
+ def redirect?
28
+ redirect_from.present?
25
29
  end
26
30
 
27
31
  def visited?
28
- session["hat-trick.steps_visited"].include? self.to_sym
32
+ visited_steps.include? self.to_sym
33
+ end
34
+
35
+ def mark_as_visited
36
+ visited_steps << self.to_sym
29
37
  end
30
38
 
31
- def visited=(_visited)
32
- session["hat-trick.steps_visited"] << self.to_sym
39
+ def as_json(options = nil)
40
+ json = { :name => name, :fieldset => fieldset }
41
+ json[:buttons] = buttons.empty? ? [] : buttons
42
+ json[:first] = first?
43
+ json[:redirect] = redirect?
44
+ json[:redirectFrom] = redirect_from
45
+ json
33
46
  end
34
47
  end
35
48
  end