hat-trick 0.1.2 → 0.1.3

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.
@@ -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