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