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.
- data/.gitignore +1 -0
- data/Changelog +17 -3
- data/README.md +44 -19
- data/app/assets/javascripts/hat-trick.js.coffee +214 -123
- data/lib/hat-trick.rb +0 -2
- data/lib/hat_trick/config.rb +10 -11
- data/lib/hat_trick/controller_helpers.rb +18 -4
- data/lib/hat_trick/controller_hooks.rb +37 -23
- data/lib/hat_trick/dsl.rb +144 -50
- data/lib/hat_trick/engine.rb +6 -0
- data/lib/hat_trick/form_helper.rb +1 -1
- data/lib/hat_trick/model_methods.rb +14 -3
- data/lib/hat_trick/step.rb +24 -11
- data/lib/hat_trick/step_definition.rb +162 -36
- data/lib/hat_trick/version.rb +1 -1
- data/lib/hat_trick/wizard.rb +105 -46
- data/lib/hat_trick/wizard_definition.rb +18 -9
- data/lib/hat_trick/wizard_steps.rb +12 -3
- data/spec/lib/hat_trick/dsl_spec.rb +11 -10
- data/spec/lib/hat_trick/step_definition_spec.rb +106 -0
- data/spec/lib/hat_trick/wizard_definition_spec.rb +18 -9
- data/spec/lib/hat_trick/wizard_spec.rb +22 -17
- data/spec/spec_helper.rb +6 -0
- data/vendor/assets/javascripts/array.filter.js +22 -0
- data/vendor/assets/javascripts/history.html4.js +658 -0
- data/vendor/assets/javascripts/history.js +1991 -0
- data/vendor/assets/javascripts/jquery.form.wizard.js +162 -145
- data/vendor/assets/javascripts/jquery.history.js +77 -1
- data/vendor/assets/javascripts/json.js +480 -0
- metadata +10 -4
data/.gitignore
CHANGED
data/Changelog
CHANGED
@@ -1,5 +1,19 @@
|
|
1
|
-
* 0.1.
|
1
|
+
* 0.1.3 - The "production" release. TurboVote.org runs on this version.
|
2
|
+
- Handle non-root URLs correctly in pushState (Closes GH issue #5)
|
3
|
+
- Don't loop past the last step when looking for the next one (Closes GH issue #6)
|
4
|
+
- Only run the include_data callback for the current step (Closes GH issue #8)
|
5
|
+
- Trigger ajaxErrors event on form element when we get errors back from an ajax call
|
6
|
+
- Trigger ajaxSuccess event on form element when ajax calls return successfully
|
7
|
+
- Don't add slashes to wizard step URLs
|
8
|
+
- Fix buggy radio button selection logic
|
9
|
+
- Use <button> elements for all buttons & improve button creation logic
|
10
|
+
- Allow HTML inside <button> elements; use button_to label: "[your html]"
|
11
|
+
- Don't focus the first input in fieldsets with the "no-focus" CSS class
|
12
|
+
- Allow multiple next buttons on each step so they can have different labels, names, values
|
13
|
+
- SO many other changes that I stopped listing them here. Oops.
|
2
14
|
|
3
|
-
* 0.1.
|
15
|
+
* 0.1.2 - Closes GH issue #4 - Don't run skipped steps' include_data callbacks
|
4
16
|
|
5
|
-
* 0.1 -
|
17
|
+
* 0.1.1 - Added LICENSE file to license hat-trick under the MIT license.
|
18
|
+
|
19
|
+
* 0.1 - Initial working release
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Hat Trick
|
2
|
-
|
2
|
+
|
3
|
+
> Multi-step "wizard" forms in Rails using jQuery. Keeps your controller CRUD
|
4
|
+
> methods clean with a wizard DSL.
|
3
5
|
|
4
6
|
## Install
|
5
7
|
gem install hat-trick
|
@@ -10,7 +12,7 @@
|
|
10
12
|
Put this in your Gemfile:
|
11
13
|
|
12
14
|
gem 'hat-trick'
|
13
|
-
|
15
|
+
|
14
16
|
## Setup
|
15
17
|
In your controller:
|
16
18
|
|
@@ -20,6 +22,9 @@ In your controller:
|
|
20
22
|
step :third_step
|
21
23
|
end
|
22
24
|
|
25
|
+
*** Make sure your controller's CRUD methods know how to return JSON responses
|
26
|
+
containing the model instance you're building in the wizard. ***
|
27
|
+
|
23
28
|
In your view:
|
24
29
|
|
25
30
|
<%= wizard_form_for @model do |f| %>
|
@@ -27,9 +32,11 @@ In your view:
|
|
27
32
|
<fieldset id="second_step">...</fieldset>
|
28
33
|
<fieldset id="third_step">...</fieldset>
|
29
34
|
<% end %>
|
30
|
-
|
35
|
+
|
31
36
|
The id's of the fieldsets in your form should match the step names you define in your controller.
|
32
|
-
|
37
|
+
|
38
|
+
Each fieldset will be displayed as a step with Next and Back buttons.
|
39
|
+
|
33
40
|
## Controlling the wizard flow
|
34
41
|
Sometimes you need to specify different paths through a wizard based on certain conditions. The way you do that with hat-trick is in the wizard DSL in the controller. Here are some examples:
|
35
42
|
|
@@ -40,25 +47,16 @@ Jumping to a step based on logged in status:
|
|
40
47
|
# after defines a callback to run after the current step is completed by the user
|
41
48
|
after do
|
42
49
|
# code in this block will be exec'd in the context of your controller
|
43
|
-
if
|
50
|
+
if current_user?
|
44
51
|
next_step :third_step
|
45
52
|
end
|
46
53
|
end
|
47
54
|
end
|
48
|
-
|
55
|
+
|
49
56
|
step :second_step # wizard will go here after :first_step if user is not signed in
|
50
|
-
|
57
|
+
|
51
58
|
step :third_step # wizard will go here after :first_step if user is signed in
|
52
|
-
|
53
|
-
Repeating a previous step (for example, to show address sanitization results to the user):
|
54
|
-
|
55
|
-
wizard do
|
56
|
-
step :enter_address
|
57
|
-
|
58
|
-
step :confirm_santized_address do
|
59
|
-
repeat_step :enter_address
|
60
|
-
end
|
61
|
-
|
59
|
+
|
62
60
|
Skipping a step under certain conditions:
|
63
61
|
|
64
62
|
wizard do
|
@@ -67,7 +65,34 @@ Skipping a step under certain conditions:
|
|
67
65
|
# before defines a callback to run before the user sees this step
|
68
66
|
before do
|
69
67
|
# code in this block will be exec'd in the context of your controller
|
70
|
-
skip_this_step unless foo.present?
|
68
|
+
skip_this_step unless model.foo.present?
|
71
69
|
end
|
72
70
|
end
|
73
|
-
end
|
71
|
+
end
|
72
|
+
|
73
|
+
Using the model instance in before and after callbacks:
|
74
|
+
|
75
|
+
wizard do
|
76
|
+
step :first_step do
|
77
|
+
before do |model_instance|
|
78
|
+
if model_instance.attr.present?
|
79
|
+
next_step :third_step
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Customizing the button labels:
|
86
|
+
|
87
|
+
wizard do
|
88
|
+
button_label :next, "Onward!"
|
89
|
+
button_label :back, "Engines reverse full"
|
90
|
+
end
|
91
|
+
|
92
|
+
Adding a custom button to a step:
|
93
|
+
|
94
|
+
wizard do
|
95
|
+
step :first_step do
|
96
|
+
button_to :next, name: "model[button_name]", label: "Foobar"
|
97
|
+
end
|
98
|
+
end
|
@@ -3,58 +3,47 @@
|
|
3
3
|
class HatTrickWizard
|
4
4
|
constructor: (formElem, @wizard) ->
|
5
5
|
@form = $(formElem)
|
6
|
-
this.
|
6
|
+
this.enableFormwizard()
|
7
7
|
|
8
|
-
|
8
|
+
debug: true
|
9
9
|
|
10
10
|
linkClass: "_ht_link"
|
11
11
|
|
12
|
-
buttons:
|
12
|
+
buttons: []
|
13
13
|
|
14
14
|
stepsNeedUpdate: false
|
15
15
|
|
16
16
|
stepShownCallback: ->
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
currentStepId = this.currentStepId()
|
18
|
+
|
19
|
+
@lastButtonChanged = null
|
20
|
+
|
21
|
+
if hatTrick.stepMetadata[currentStepId]?
|
22
|
+
hatTrick.metadata.currentStep = hatTrick.stepMetadata[currentStepId]
|
23
|
+
else
|
24
|
+
this.requestMetadataFromServer()
|
25
|
+
return
|
26
|
+
|
23
27
|
this.updateStepFromMetadata()
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
this.
|
28
|
-
|
28
|
+
currentStepId = this.currentStepId()
|
29
|
+
# can't go back from the first step
|
30
|
+
if hatTrick.metadata.currentStep.first
|
31
|
+
this.buttons[currentStepId] = this.buttons[currentStepId].filter (button) ->
|
32
|
+
not button.back?
|
33
|
+
this.setupButtonsForCurrentStep()
|
34
|
+
if @stepsNeedUpdate
|
35
|
+
this.updateSteps()
|
36
|
+
@stepsNeedUpdate = false
|
29
37
|
else
|
30
|
-
this.
|
31
|
-
|
32
|
-
if @stepsNeedUpdate
|
33
|
-
this.updateSteps()
|
34
|
-
@stepsNeedUpdate = false
|
35
|
-
this.setCurrentStepField()
|
36
|
-
this.clearNextStepField()
|
38
|
+
this.updateButtons()
|
39
|
+
this.removeLinkFields()
|
37
40
|
this.setFormFields(hatTrick.model)
|
38
41
|
this.createDummyModelField() unless this.currentStepHasModelFields()
|
42
|
+
@form.trigger 'step_changed', { currentStep: this.currentStepId() }
|
39
43
|
|
40
44
|
addStepClass: ->
|
41
45
|
@form.find("fieldset").addClass("step")
|
42
46
|
|
43
|
-
addDefaultButtons: ($scope = @form.find("fieldset")) ->
|
44
|
-
hatTrick = this
|
45
|
-
$scope.each (index) ->
|
46
|
-
id = $(this).attr('id')
|
47
|
-
buttons =
|
48
|
-
next:
|
49
|
-
id: "#{id}_next_button"
|
50
|
-
label: "Next"
|
51
|
-
if index > 0 or hatTrick.formwizardEnabled
|
52
|
-
buttons['back'] =
|
53
|
-
id: "#{id}_back_button"
|
54
|
-
label: "Back"
|
55
|
-
|
56
|
-
hatTrick.buttons[id] = buttons
|
57
|
-
|
58
47
|
findStep: (stepId) ->
|
59
48
|
@form.find("fieldset##{stepId}")
|
60
49
|
|
@@ -63,7 +52,6 @@ class HatTrickWizard
|
|
63
52
|
|
64
53
|
setAction: (url, method) ->
|
65
54
|
methodLower = method.toLowerCase()
|
66
|
-
# log "Setting form action to #{methodLower} #{url}"
|
67
55
|
@form.attr("action", url)
|
68
56
|
@form.attr("method", "post")
|
69
57
|
@form.formwizard("option", remoteAjax: this.ajaxEvents())
|
@@ -73,7 +61,10 @@ class HatTrickWizard
|
|
73
61
|
@form.prepend(this.createMethodField(method))
|
74
62
|
|
75
63
|
currentStepId: ->
|
76
|
-
@form.formwizard("state").currentStep
|
64
|
+
stepId = @form.formwizard("state").currentStep
|
65
|
+
unless stepId? and stepId isnt ""
|
66
|
+
stepId = hatTrick.metadata.currentStep.name
|
67
|
+
stepId
|
77
68
|
|
78
69
|
currentStep: ->
|
79
70
|
stepId = this.currentStepId()
|
@@ -94,10 +85,16 @@ class HatTrickWizard
|
|
94
85
|
ajax =
|
95
86
|
url: @form.attr("action")
|
96
87
|
dataType: "json"
|
88
|
+
beforeSerialize: (form, options) =>
|
89
|
+
if options.data._ht_step_link == this.currentStepId()
|
90
|
+
log "Warning: Tried to link to the current step; this is probably not what you want."
|
91
|
+
return true;
|
97
92
|
# beforeSubmit: (data) =>
|
98
93
|
# log "Sending these data to the server: #{JSON.stringify data}"
|
99
94
|
success: (serverData) =>
|
95
|
+
this.clearErrors()
|
100
96
|
this.handleServerData serverData
|
97
|
+
@form.trigger 'ajaxSuccess', serverData
|
101
98
|
# log "Successful form POST; got #{JSON.stringify(serverData)}"
|
102
99
|
error: (event, status, errorThrown) =>
|
103
100
|
log "Error response: #{event.status} #{status} - #{errorThrown} - #{event.responseText}"
|
@@ -109,8 +106,12 @@ class HatTrickWizard
|
|
109
106
|
unknown: [
|
110
107
|
"There was an error communicating with the server. TurboVote staff have been notified."
|
111
108
|
]
|
109
|
+
status: status
|
110
|
+
event: event
|
112
111
|
this.clearErrors()
|
113
|
-
this.addErrorItem value[0] for key, value of appErrors.model
|
112
|
+
this.addErrorItem value[0] for key, value of appErrors.model when key isnt "__name__"
|
113
|
+
this.removeLinkFields()
|
114
|
+
@form.trigger 'ajaxErrors', appErrors
|
114
115
|
ajax
|
115
116
|
|
116
117
|
getErrorListElement: ->
|
@@ -127,6 +128,9 @@ class HatTrickWizard
|
|
127
128
|
$errorList.append("<li>#{message}</li>")
|
128
129
|
$errorList.show()
|
129
130
|
|
131
|
+
updateButtons: ->
|
132
|
+
@form.formwizard("update_buttons")
|
133
|
+
|
130
134
|
updateSteps: ->
|
131
135
|
@form.formwizard("update_steps")
|
132
136
|
@form.formwizard("option", remoteAjax: this.ajaxEvents())
|
@@ -135,50 +139,57 @@ class HatTrickWizard
|
|
135
139
|
this.setLinkField(stepId)
|
136
140
|
@form.formwizard("next")
|
137
141
|
|
138
|
-
|
139
|
-
|
140
|
-
repeatStep: (step) ->
|
141
|
-
if $("fieldset##{step.name}").length is 0
|
142
|
-
$sourceStep = this.findStep(step.repeatOf.fieldset)
|
143
|
-
$clonedStep = $sourceStep.clone(true, true)
|
144
|
-
$clonedStep.css("display", "none")
|
145
|
-
$clonedStep.attr("id", step.name)
|
146
|
-
$sourceStep.after($clonedStep)
|
147
|
-
this.updateSteps()
|
148
|
-
this.setLinkField step.name
|
149
|
-
|
150
|
-
removeLinkField: ->
|
151
|
-
this.currentStep().find("input.#{@linkClass}").remove()
|
142
|
+
removeLinkFields: ->
|
143
|
+
@form.find("input.#{@linkClass}").remove()
|
152
144
|
|
153
145
|
setLinkField: (stepId) ->
|
154
146
|
inputId = "_ht_link_to_#{stepId}"
|
155
147
|
this.setHiddenInput "_ht_step_link", stepId, inputId, @linkClass, this.currentStep()
|
156
148
|
|
157
|
-
|
149
|
+
linkFieldSet: ->
|
158
150
|
this.currentStep().find("input[name='_ht_step_link']").length > 0
|
159
151
|
|
160
152
|
addFakeLastStep: ->
|
161
153
|
@form.append """<fieldset id="_ht_fake_last_step" style="display: none;" class="step"></fieldset>"""
|
162
154
|
|
163
155
|
enableFormwizard: ->
|
156
|
+
this.addStepClass()
|
157
|
+
this.saveStepMetadata()
|
158
|
+
this.setAction(hatTrick.metadata.url, hatTrick.metadata.method)
|
159
|
+
# prevent submitting the step that happens to be the last fieldset
|
160
|
+
# TODO: Figure out a better way to do this
|
161
|
+
this.addFakeLastStep()
|
162
|
+
this.bindEvents()
|
163
|
+
firstStep = if hatTrick.metadata.currentStep?.redirect
|
164
|
+
hatTrick.metadata.currentStep.redirectFrom
|
165
|
+
else
|
166
|
+
hatTrick.metadata.currentStep.fieldset
|
164
167
|
@form.formwizard
|
165
168
|
formPluginEnabled: true,
|
166
169
|
validationEnabled: false,
|
167
170
|
focusFirstInput: true,
|
168
|
-
historyEnabled: true,
|
169
171
|
disableUIStyles: true,
|
170
|
-
|
172
|
+
outDuration: 200,
|
173
|
+
inDuration: 10, # don't set this to 0 or step_shown will be triggered too early
|
174
|
+
next: "button:submit",
|
175
|
+
back: "button:reset",
|
171
176
|
linkClass: ".#{@linkClass}",
|
172
177
|
remoteAjax: this.ajaxEvents(),
|
173
|
-
firstStep:
|
174
|
-
|
178
|
+
firstStep: firstStep
|
179
|
+
# see if we got a redirect & follow if so
|
180
|
+
currentStepId = this.currentStepId()
|
181
|
+
if hatTrick.metadata.currentStep?
|
182
|
+
currentStepData = hatTrick.metadata.currentStep
|
183
|
+
if currentStepData.redirect and currentStepData.redirectFrom is currentStepId
|
184
|
+
@form.formwizard("redirect", currentStepData.fieldset)
|
175
185
|
|
176
186
|
setHiddenInput: (name, value, id, classes = "", scope = @form) ->
|
177
187
|
$scope = $(scope)
|
178
|
-
$input = $scope.find("""input
|
188
|
+
$input = $scope.find("""input[name="#{name}"]""")
|
179
189
|
if $input.length is 0
|
180
190
|
$input = $(this.hiddenInputHTML(name, id, classes)).prependTo $scope
|
181
191
|
$input.val value
|
192
|
+
$input
|
182
193
|
|
183
194
|
hiddenInputHTML: (name, id, classes = "") ->
|
184
195
|
"""<input type="hidden" id="#{id}" name="#{name}" class="#{classes}" value="" />"""
|
@@ -193,11 +204,6 @@ class HatTrickWizard
|
|
193
204
|
stepId = this.currentStepId()
|
194
205
|
this.setHTMeta("step", stepId)
|
195
206
|
|
196
|
-
# TODO: See if we still need this "next_step" ht_meta field.
|
197
|
-
clearNextStepField: ->
|
198
|
-
this.clearHTMeta("next_step")
|
199
|
-
this.removeLinkField()
|
200
|
-
|
201
207
|
fieldRegex: /^([^\[]+)\[([^\]]+)\]$/
|
202
208
|
|
203
209
|
setFieldValues: (model, selector, callback) ->
|
@@ -223,88 +229,173 @@ class HatTrickWizard
|
|
223
229
|
this.setFieldValues model, "input:checkbox", ($checkbox, value) =>
|
224
230
|
$checkbox.attr("checked", "checked") if value
|
225
231
|
|
232
|
+
# TODO: DRY this up as much as possible. Radio buttons a little different
|
233
|
+
# than the other form controls since they share names and behave as a
|
234
|
+
# named group.
|
226
235
|
setRadioButtons: (model) ->
|
227
|
-
this.
|
228
|
-
|
236
|
+
$currentStep = this.currentStep()
|
237
|
+
selector = "input:radio"
|
238
|
+
radioGroups = {}
|
239
|
+
$currentStep.find(selector).each ->
|
240
|
+
radioGroups[$(this).attr("name")] = true
|
241
|
+
for radioGroup of radioGroups
|
242
|
+
do (radioGroup) =>
|
243
|
+
if radioGroup.search(@fieldRegex) isnt -1
|
244
|
+
[_, modelName, fieldName] = radioGroup.match(@fieldRegex)
|
245
|
+
if model['__name__'] is modelName and model[fieldName]?
|
246
|
+
fieldValue = model[fieldName]
|
247
|
+
$radioGroup = $("input:radio[name=\"#{radioGroup}\"]")
|
248
|
+
$radioGroup.removeAttr("checked")
|
249
|
+
$radioGroup.filter("[value=\"#{fieldValue}\"]").attr("checked", "checked")
|
229
250
|
|
230
251
|
setFormFields: (model) ->
|
231
|
-
# log "Setting form fields based on: #{JSON.stringify(model)}"
|
232
252
|
this.fillTextFields(model)
|
233
253
|
this.setSelectFields(model)
|
234
254
|
this.setCheckboxes(model)
|
235
255
|
this.setRadioButtons(model)
|
236
256
|
|
237
|
-
|
238
|
-
$
|
257
|
+
createButtonElement: (name, value, label, type="button") ->
|
258
|
+
$elem = $("""<button type="#{type}" class="wizard_button"></button>""")
|
259
|
+
$elem.attr "name", name if name?
|
260
|
+
$elem.html label
|
261
|
+
$elem.val value if value?
|
262
|
+
$elem
|
263
|
+
|
264
|
+
createButton: (toStep, button) ->
|
265
|
+
switch toStep
|
266
|
+
when "next"
|
267
|
+
if button.class is ""
|
268
|
+
button.class = "wizard_next"
|
269
|
+
else
|
270
|
+
button["class"] += " wizard_next"
|
271
|
+
type = "submit"
|
272
|
+
when "back"
|
273
|
+
if button.class is ""
|
274
|
+
button.class = "wizard_back"
|
275
|
+
else
|
276
|
+
button["class"] += " wizard_back"
|
277
|
+
button.name = "back"
|
278
|
+
unless button.id?
|
279
|
+
button.id = "#{this.currentStepId()}_back_button"
|
280
|
+
delete button["value"]
|
281
|
+
type = "reset"
|
282
|
+
else
|
283
|
+
type = "button"
|
284
|
+
$button = this.createButtonElement button.name, button.value, button.label, type
|
239
285
|
if button.id?
|
240
286
|
$button.attr("id", button.id)
|
241
|
-
else
|
242
|
-
$button.attr("id", "#{this.currentStepId()}_#{name}_#{button.
|
243
|
-
|
244
|
-
|
245
|
-
currentStep: this.currentStepId()
|
246
|
-
button: $button.attr "id"
|
247
|
-
@form.trigger "other_button_click", clickCallbackData
|
287
|
+
else if button.name? and button.value?
|
288
|
+
$button.attr("id", "#{this.currentStepId()}_#{button.name}_#{button.value}")
|
289
|
+
if button["class"]?
|
290
|
+
$button.addClass(button["class"])
|
248
291
|
$button
|
249
292
|
|
250
|
-
setButton: (stepId,
|
251
|
-
# log "Setting button for #{stepId} named #{name} to #{JSON.stringify(button)}"
|
293
|
+
setButton: (stepId, toStep, button) ->
|
252
294
|
$buttonsDiv = $("fieldset##{stepId}").find("div.buttons")
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
when "back"
|
262
|
-
# log "Setting reset button val to #{button.label}"
|
263
|
-
$button = $buttonsDiv.find('input:reset').val(button.label)
|
264
|
-
unless $button.length > 0
|
265
|
-
$button = $('<input class="wizard_button wizard_back" type="reset" />').appendTo $buttonsDiv
|
266
|
-
$button.val(button.label)
|
267
|
-
$button.attr("id", button.id) if button.id?
|
295
|
+
if $buttonsDiv.find("button").length > 0
|
296
|
+
@lastButtonChanged ?= $buttonsDiv.find("button:first")
|
297
|
+
buttonSelector = """button[name="#{button.name}"][value="#{button.value}"]"""
|
298
|
+
$existingButtons = $buttonsDiv.find(buttonSelector)
|
299
|
+
if $existingButtons.length is 0
|
300
|
+
$newButton = $(this.createButton(toStep, button))
|
301
|
+
if @lastButtonChanged?
|
302
|
+
@lastButtonChanged.after $newButton
|
268
303
|
else
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
$newButton.
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
setupButtonsForAllSteps: ->
|
279
|
-
this.setupButtonsForStep $(fieldset).attr('id') for fieldset in $('fieldset')
|
304
|
+
$buttonsDiv.append $newButton
|
305
|
+
@lastButtonChanged = $newButton
|
306
|
+
unless toStep is "next" or toStep is "back"
|
307
|
+
$newButton.click (event) =>
|
308
|
+
event.preventDefault()
|
309
|
+
fieldId = "button_#{$newButton.attr("name")}_#{$newButton.val()}_field"
|
310
|
+
this.setHiddenInput $newButton.attr("name"), $newButton.val(), fieldId, "", $buttonsDiv
|
311
|
+
this.goToStepId(toStep)
|
280
312
|
|
281
313
|
setupButtonsForCurrentStep: ->
|
282
|
-
|
314
|
+
$currentStep = this.currentStep()
|
315
|
+
$buttonsDiv = $currentStep.find("div.buttons")
|
316
|
+
$buttons = $buttonsDiv.find("button")
|
317
|
+
unless $buttonsDiv.data "buttonsAdded"
|
318
|
+
this.setupButtonsForStep this.currentStepId()
|
283
319
|
|
284
320
|
setupButtonsForStep: (stepId) ->
|
285
321
|
buttons = this.buttons[stepId]
|
286
322
|
if buttons?
|
287
|
-
|
323
|
+
for button in buttons
|
324
|
+
do (button) =>
|
325
|
+
this.setButton(stepId, toStep, buttonData) for toStep, buttonData of button
|
326
|
+
$("##{stepId} div.buttons").data "buttonsAdded", true
|
288
327
|
|
289
328
|
setContents: (stepPartials) ->
|
290
329
|
for stepName, partial of stepPartials
|
291
330
|
do (stepName, partial) =>
|
292
331
|
stepId = underscoreString stepName
|
293
332
|
$partial = $(partial)
|
294
|
-
fieldsetContents = $partial.
|
333
|
+
fieldsetContents = if $partial.filter("fieldset").length > 0
|
334
|
+
$partial.filter("fieldset").html()
|
335
|
+
else
|
336
|
+
$partial.find('fieldset').html()
|
295
337
|
$step = $("fieldset##{stepId}")
|
296
338
|
$step.html fieldsetContents
|
297
|
-
|
339
|
+
$step.filter("fieldset:not(.no-focus)").find(":input:not(input[type=hidden]):first").focus();
|
340
|
+
$step.data("contents", "loaded")
|
298
341
|
@stepsNeedUpdate = true
|
299
342
|
|
300
|
-
|
301
|
-
|
343
|
+
saveStepMetadata: (stepId=this.currentStepId(), metadata=hatTrick.metadata.currentStep) ->
|
344
|
+
hatTrick.stepMetadata = {} unless hatTrick.stepMetadata?
|
345
|
+
hatTrick.stepMetadata[stepId] = metadata
|
346
|
+
|
347
|
+
handleServerData: (data) =>
|
348
|
+
if data.metadata?.externalRedirectURL?
|
349
|
+
externalRedirectURL = data.metadata.externalRedirectURL
|
350
|
+
if externalRedirectURL isnt ""
|
351
|
+
location.href = data.metadata.externalRedirectURL
|
352
|
+
# TODO: pop out of window if in iframe
|
353
|
+
# if (top.location == self.location)
|
354
|
+
# location.href = data.metadata.externalRedirectURL
|
355
|
+
# else
|
356
|
+
# window.open(data.metadata.externalRedirectURL)
|
357
|
+
|
358
|
+
if data.metadata?.url? and data.metadata?.method?
|
302
359
|
this.setAction(data.metadata.url, data.metadata.method)
|
303
|
-
|
360
|
+
this.saveStepMetadata(data.metadata.currentStep.name, data.metadata.currentStep)
|
361
|
+
$.extend(hatTrick, data) # merge new data with hatTrick
|
304
362
|
this.updateStepFromMetadata()
|
305
363
|
|
364
|
+
metadataRequestCallback: (data) =>
|
365
|
+
stepId = this.currentStepId()
|
366
|
+
# set empty step contents if we didn't get any;
|
367
|
+
# this makes sure we can tell whether or not we've already requested metadata
|
368
|
+
emptyStepContents = { hatTrickStepContents: {} }
|
369
|
+
stepKey = camelizeString(stepId)
|
370
|
+
emptyStepContents["hatTrickStepContents"][stepKey] = ""
|
371
|
+
unless data.data.hatTrickStepContents? and data.data.hatTrickStepContents[stepKey]?
|
372
|
+
data.data = $.extend({}, data.data, emptyStepContents)
|
373
|
+
this.handleServerData(data)
|
374
|
+
this.removeLinkFields() # updateStepFromMetadata sets this to currentStep
|
375
|
+
this.setupButtonsForCurrentStep()
|
376
|
+
this.updateButtons()
|
377
|
+
this.setFormFields(hatTrick.model)
|
378
|
+
@form.trigger "step_changed", { currentStep: stepId }
|
379
|
+
|
380
|
+
requestMetadataFromServer: ->
|
381
|
+
metadataUrl = document.location.pathname
|
382
|
+
lastChar = metadataUrl.charAt(metadataUrl.length - 1)
|
383
|
+
metadataUrl += "/" unless lastChar is "/"
|
384
|
+
stepId = this.currentStepId()
|
385
|
+
metadataUrl += "#{stepId}/" if metadataUrl.search("#{stepId}/$") is -1
|
386
|
+
metadataUrl += "metadata"
|
387
|
+
$.ajax
|
388
|
+
type: "GET"
|
389
|
+
url: metadataUrl
|
390
|
+
success: this.metadataRequestCallback
|
391
|
+
dataType: "json"
|
392
|
+
|
306
393
|
updateStepContents: ->
|
307
|
-
this.
|
394
|
+
stepKey = camelizeString(this.currentStepId())
|
395
|
+
if hatTrick.data?.hatTrickStepContents?[stepKey]?
|
396
|
+
this.setContents(hatTrick.data.hatTrickStepContents)
|
397
|
+
else
|
398
|
+
this.requestMetadataFromServer()
|
308
399
|
|
309
400
|
setButtonMetadataForCurrentStep: ->
|
310
401
|
if hatTrick.metadata?.currentStep?
|
@@ -317,22 +408,23 @@ class HatTrickWizard
|
|
317
408
|
hatTrick.model['__name__']
|
318
409
|
|
319
410
|
createDummyModelField: ->
|
320
|
-
this.setHiddenInput "#{this.modelName()}[_dummy]", "1"
|
411
|
+
this.setHiddenInput "#{this.modelName()}[_dummy]", "1", "", this.currentStep()
|
321
412
|
|
322
413
|
currentStepHasModelFields: ->
|
323
414
|
this.currentStep().find("input[name^='#{this.modelName()}[']").length > 0
|
324
415
|
|
325
416
|
updateStepFromMetadata: ->
|
417
|
+
currentStepId = this.currentStepId()
|
418
|
+
|
419
|
+
if $("fieldset##{currentStepId}").data("contents") is "server"
|
420
|
+
this.updateStepContents()
|
421
|
+
|
326
422
|
if hatTrick.metadata?.currentStep?
|
327
|
-
|
423
|
+
currentStepData = hatTrick.metadata.currentStep
|
424
|
+
this.setCurrentStepField()
|
328
425
|
this.setButtonMetadataForCurrentStep()
|
329
426
|
this.createDummyModelField() unless this.currentStepHasModelFields()
|
330
|
-
|
331
|
-
currentStep = hatTrick.metadata.currentStep
|
332
|
-
if currentStep.repeatOf?
|
333
|
-
this.repeatStep(currentStep)
|
334
|
-
else
|
335
|
-
this.setLinkField(currentStep.fieldset) unless this.LinkFieldSet()
|
427
|
+
this.setLinkField(currentStepData.fieldset)
|
336
428
|
|
337
429
|
bindEvents: ->
|
338
430
|
@form.bind "step_shown", (event, data) =>
|
@@ -341,9 +433,8 @@ class HatTrickWizard
|
|
341
433
|
$ ->
|
342
434
|
if $("form.wizard").length > 0
|
343
435
|
$form = $("form.wizard")
|
344
|
-
window.hatTrick
|
436
|
+
window.hatTrick ?= {}
|
345
437
|
unless window.hatTrick.wizard?
|
346
|
-
# log "Creating new HatTrickWizard instance"
|
347
438
|
window.hatTrick.wizard = new HatTrickWizard($form, hatTrick.metadata)
|
348
439
|
|
349
440
|
camelizeString = (string) ->
|
@@ -363,5 +454,5 @@ underscoreString = (string) ->
|
|
363
454
|
result
|
364
455
|
|
365
456
|
log = (msg) ->
|
366
|
-
if window['console']?
|
457
|
+
if window['console']? and hatTrick.wizard.debug
|
367
458
|
console.log msg
|
data/lib/hat-trick.rb
CHANGED
data/lib/hat_trick/config.rb
CHANGED
@@ -1,17 +1,16 @@
|
|
1
1
|
module HatTrick
|
2
2
|
class Config
|
3
|
-
|
3
|
+
attr_accessor :create_url, :update_url, :back_button_label,
|
4
|
+
:next_button_label, :back_button_label_i18n_key,
|
5
|
+
:next_button_label_i18n_key
|
4
6
|
|
5
|
-
def initialize(
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def update_url=(url)
|
14
|
-
wizard_def.configured_update_url = url
|
7
|
+
def initialize(settings={})
|
8
|
+
settings.each do |k,v|
|
9
|
+
setter = "#{k}="
|
10
|
+
if respond_to?(setter)
|
11
|
+
send(setter, v)
|
12
|
+
end
|
13
|
+
end
|
15
14
|
end
|
16
15
|
end
|
17
16
|
end
|