form_input 0.9.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/LICENSE +19 -0
  4. data/README.md +3160 -0
  5. data/Rakefile +19 -0
  6. data/example/controllers/ramaze/press_release.rb +104 -0
  7. data/example/controllers/ramaze/profile.rb +38 -0
  8. data/example/controllers/sinatra/press_release.rb +114 -0
  9. data/example/controllers/sinatra/profile.rb +39 -0
  10. data/example/forms/change_password_form.rb +17 -0
  11. data/example/forms/login_form.rb +14 -0
  12. data/example/forms/lost_password_form.rb +14 -0
  13. data/example/forms/new_password_form.rb +15 -0
  14. data/example/forms/password_form.rb +18 -0
  15. data/example/forms/press_release_form.rb +153 -0
  16. data/example/forms/profile_form.rb +21 -0
  17. data/example/forms/signup_form.rb +25 -0
  18. data/example/views/press_release.slim +65 -0
  19. data/example/views/profile.slim +28 -0
  20. data/example/views/snippets/form_block.slim +27 -0
  21. data/example/views/snippets/form_chunked.slim +25 -0
  22. data/example/views/snippets/form_hidden.slim +21 -0
  23. data/example/views/snippets/form_panel.slim +89 -0
  24. data/form_input.gemspec +32 -0
  25. data/lib/form_input/core.rb +1165 -0
  26. data/lib/form_input/localize.rb +49 -0
  27. data/lib/form_input/r18n/cs.yml +97 -0
  28. data/lib/form_input/r18n/en.yml +70 -0
  29. data/lib/form_input/r18n/pl.yml +122 -0
  30. data/lib/form_input/r18n/sk.yml +120 -0
  31. data/lib/form_input/r18n.rb +163 -0
  32. data/lib/form_input/steps.rb +365 -0
  33. data/lib/form_input/types.rb +176 -0
  34. data/lib/form_input/version.rb +12 -0
  35. data/lib/form_input.rb +5 -0
  36. data/test/helper.rb +21 -0
  37. data/test/localize/en.yml +63 -0
  38. data/test/r18n/cs.yml +60 -0
  39. data/test/r18n/xx.yml +51 -0
  40. data/test/reference/cs.txt +352 -0
  41. data/test/reference/cs.yml +14 -0
  42. data/test/reference/en.txt +76 -0
  43. data/test/reference/en.yml +8 -0
  44. data/test/reference/pl.txt +440 -0
  45. data/test/reference/pl.yml +16 -0
  46. data/test/reference/sk.txt +352 -0
  47. data/test/reference/sk.yml +14 -0
  48. data/test/test_core.rb +1272 -0
  49. data/test/test_localize.rb +27 -0
  50. data/test/test_r18n.rb +373 -0
  51. data/test/test_steps.rb +482 -0
  52. data/test/test_types.rb +307 -0
  53. metadata +145 -0
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ # Rake makefile.
2
+
3
+ require 'rake/testtask'
4
+
5
+ task :default => :test
6
+
7
+ desc 'Run tests'
8
+ task :test do
9
+ sh "bacon --automatic --quiet"
10
+ end
11
+
12
+ desc 'Run tests with coverage'
13
+ task :cov do
14
+ sh "rm -rf coverage"
15
+ ENV['COVERAGE'] = 'on'
16
+ Rake::Task[:test].execute
17
+ end
18
+
19
+ # EOF #
@@ -0,0 +1,104 @@
1
+ # The press release controller - example of a complex multistep form handling in Ramaze.
2
+
3
+ class PRController < Controller
4
+
5
+ # Create press release.
6
+ def create
7
+
8
+ # Start with fresh form.
9
+
10
+ unless request.post?
11
+ @form = PressReleaseForm.new(
12
+ user.active_plan,
13
+ date: Time.now,
14
+ city: user.profile.city,
15
+ state: user.profile.state,
16
+ country: user.profile.country,
17
+ contact_name: user.full_name,
18
+ contact_organization: user.profile.company,
19
+ contact_phone: user.profile.phone,
20
+ contact_email: user.email,
21
+ )
22
+ return
23
+ end
24
+
25
+ # Process the data from the post.
26
+
27
+ @form = PressReleaseForm.new( user.active_plan, request )
28
+ return unless process_form( @form )
29
+
30
+ # Save the press release.
31
+
32
+ unless user.create_press_release( @form )
33
+ @form.step = :summary
34
+ @state = :create_failed
35
+ return
36
+ end
37
+
38
+ redirect r
39
+ end
40
+
41
+ # Edit press release.
42
+ def edit( hid )
43
+
44
+ # Fetch the existing press release.
45
+ # Note that it can be edited by admins in addition to its owner.
46
+
47
+ not_found unless pr = PressRelease.from_hid( hid )
48
+
49
+ owner = ( pr.user_id == user.id )
50
+ forbidden unless owner or user.admin?
51
+
52
+ # Start by filling the form from the press release.
53
+
54
+ unless request.post?
55
+ @form = PressReleaseForm.new( pr.user.active_plan, pr.form_hash ).unlock_steps
56
+ return
57
+ end
58
+
59
+ # Process the data from the post.
60
+
61
+ pr = get_press_release( hid )
62
+ @form = PressReleaseForm.new( pr.user.active_plan, request )
63
+ return unless process_form( @form )
64
+
65
+ # Update the press release.
66
+
67
+ unless pr.user.update_press_release( pr, @form, user )
68
+ @form.step = :summary
69
+ @state = :update_failed
70
+ return
71
+ end
72
+
73
+ redirect owner ? r : AdminController.r( :press_releases )
74
+ end
75
+
76
+ alias_view :edit, :create
77
+
78
+ # Common logic for processing the form steps.
79
+ def process_form( form )
80
+
81
+ # Report errors whenever there are some in the currently finished step.
82
+
83
+ @state = :report if form.incomplete_step?
84
+
85
+ # That's all until the last steps are reached.
86
+
87
+ return if form.step_before?( :summary )
88
+
89
+ # In case the form is still invalid, return to the appropriate step.
90
+
91
+ unless form.valid?
92
+ form.step = form.incorrect_step
93
+ @state = :report
94
+ return
95
+ end
96
+
97
+ # That's all until the last step is reached.
98
+
99
+ return form.last_step?
100
+ end
101
+
102
+ end
103
+
104
+ # EOF #
@@ -0,0 +1,38 @@
1
+ # Example of simple form handling in Ramaze.
2
+
3
+ class HomeController < Controller
4
+
5
+ # Profile.
6
+ def profile
7
+
8
+ # Prefill the form with current user profile.
9
+
10
+ unless request.post?
11
+ @form = ProfileForm.new( user.profile_hash )
12
+ return
13
+ end
14
+
15
+ # Check new data when the form is posted.
16
+
17
+ @form = ProfileForm.new( request )
18
+ unless @form.valid?
19
+ @state = :report
20
+ return
21
+ end
22
+
23
+ # Attempt to update the profile.
24
+
25
+ unless user.update_profile( @form )
26
+ @state = :update_failed
27
+ return
28
+ end
29
+
30
+ # Refetch the profile just in case the model changed something, and report success.
31
+
32
+ @form = ProfileForm.new( user.profile_hash )
33
+ @state = :done
34
+ end
35
+
36
+ end
37
+
38
+ # EOF #
@@ -0,0 +1,114 @@
1
+ # The press release controller - example of a complex multistep form handling in Sinatra.
2
+
3
+ class App < Sinatra::Base
4
+
5
+ # Create press release - start.
6
+ get '/pr/create' do
7
+
8
+ # Start with fresh form, prefilled with some user info.
9
+
10
+ @form = PressReleaseForm.new(
11
+ user.active_plan,
12
+ date: Time.now,
13
+ city: user.profile.city,
14
+ state: user.profile.state,
15
+ country: user.profile.country,
16
+ contact_name: user.full_name,
17
+ contact_organization: user.profile.company,
18
+ contact_phone: user.profile.phone,
19
+ contact_email: user.email,
20
+ )
21
+ slim :press_release
22
+ end
23
+
24
+ # Create press release - creation.
25
+ post '/pr/create' do
26
+
27
+ # Process the data from the post.
28
+
29
+ @form = PressReleaseForm.new( user.active_plan, request )
30
+ unless process_form( @form )
31
+ return slim :press_release
32
+ end
33
+
34
+ # Save the press release.
35
+
36
+ unless user.create_press_release( @form )
37
+ @form.step = :summary
38
+ @state = :create_failed
39
+ return slim :press_release
40
+ end
41
+
42
+ redirect '/'
43
+ end
44
+
45
+ # Edit press release - start.
46
+ get '/pr/edit/:hid' do |hid|
47
+
48
+ # Fetch the existing press release.
49
+ # Note that it can be edited by admins in addition to its owner.
50
+
51
+ pr = get_press_release( hid )
52
+ @form = PressReleaseForm.new( pr.user.active_plan, pr.form_hash ).unlock_steps
53
+ slim :press_release
54
+ end
55
+
56
+ # Edit press release - update.
57
+ post '/pr/edit/:hid' do |hid|
58
+
59
+ # Process the data from the post.
60
+
61
+ pr = get_press_release( hid )
62
+ @form = PressReleaseForm.new( pr.user.active_plan, request )
63
+ unless process_form( @form )
64
+ return slim :press_release
65
+ end
66
+
67
+ # Update the press release.
68
+
69
+ unless pr.user.update_press_release( pr, @form, user )
70
+ @form.step = :summary
71
+ @state = :update_failed
72
+ return slim :press_release
73
+ end
74
+
75
+ redirect '/'
76
+ end
77
+
78
+ # Get the press release for given hashed id.
79
+ def get_press_release( hid )
80
+ not_found unless pr = PressRelease.from_hid( hid )
81
+
82
+ owner = ( pr.user_id == user.id )
83
+ forbidden unless owner or user.admin?
84
+
85
+ pr
86
+ end
87
+
88
+ # Common logic for processing the form steps.
89
+ def process_form( form )
90
+
91
+ # Report errors whenever there are some in the currently finished step.
92
+
93
+ @state = :report if form.incomplete_step?
94
+
95
+ # That's all until the last steps are reached.
96
+
97
+ return if form.step_before?( :summary )
98
+
99
+ # In case the form is still invalid, return to the appropriate step.
100
+
101
+ unless form.valid?
102
+ form.step = form.incorrect_step
103
+ @state = :report
104
+ return
105
+ end
106
+
107
+ # That's all until the last step is reached.
108
+
109
+ return form.last_step?
110
+ end
111
+
112
+ end
113
+
114
+ # EOF #
@@ -0,0 +1,39 @@
1
+ # Example of simple form handling in Sinatra.
2
+
3
+ class App < Sinatra::Application
4
+
5
+ get '/profile' do
6
+
7
+ # Prefill the form with current user profile.
8
+
9
+ @form = ProfileForm.new( user.profile_hash )
10
+ slim :profile
11
+ end
12
+
13
+ post '/profile' do
14
+
15
+ # Validate the posted data.
16
+
17
+ @form = ProfileForm.new( request )
18
+ unless @form.valid?
19
+ @state = :report
20
+ return slim :profile
21
+ end
22
+
23
+ # Attempt to update the profile.
24
+
25
+ unless user.update_profile( @form )
26
+ @state = :update_failed
27
+ return slim :profile
28
+ end
29
+
30
+ # Refetch the profile just in case the model changed something, and report success.
31
+
32
+ @form = ProfileForm.new( user.profile_hash )
33
+ @state = :done
34
+ slim :profile
35
+ end
36
+
37
+ end
38
+
39
+ # EOF #
@@ -0,0 +1,17 @@
1
+ # Change password form.
2
+
3
+ require_relative 'new_password_form'
4
+
5
+ class ChangePasswordForm < FormInput
6
+
7
+ # Note that we intentionally don't impose any format on this,
8
+ # as we don't want to assume what they have registered with.
9
+ # Only strip the password newline in case they cut&paste it from somewhere.
10
+
11
+ param! :old_password, "Old password", type: :password, filter: ->{ chomp }
12
+
13
+ copy NewPasswordForm
14
+
15
+ end
16
+
17
+ # EOF #
@@ -0,0 +1,14 @@
1
+ # Login form.
2
+
3
+ class LoginForm < FormInput
4
+
5
+ # Note that we intentionally don't impose any format of these,
6
+ # as we don't want to assume what they have registered with.
7
+ # Only strip the password newline in case they cut&paste it from somewhere.
8
+
9
+ param! :login, :email, "Email"
10
+ param! :password, "Password", type: :password, filter: ->{ chomp }
11
+
12
+ end
13
+
14
+ # EOF #
@@ -0,0 +1,14 @@
1
+ # Lost password form.
2
+
3
+ class LostPasswordForm < FormInput
4
+
5
+ # Note that we intentionally don't impose any format of the email,
6
+ # as we don't want to assume what they have registered with.
7
+ # We will let them know in case we don't find it anyway.
8
+ # We merely check if it looks like an email at all.
9
+
10
+ param! :email, "Email", match: /@/
11
+
12
+ end
13
+
14
+ # EOF #
@@ -0,0 +1,15 @@
1
+ # New password form.
2
+
3
+ require_relative 'password_form'
4
+
5
+ class NewPasswordForm < FormInput
6
+
7
+ copy PasswordForm, title: "New password"
8
+
9
+ param! :password_check, "Repeated password", type: :password,
10
+ filter: self[ :password ].filter,
11
+ check: ->{ report( "%p must match password" ) unless value == form.password }
12
+
13
+ end
14
+
15
+ # EOF #
@@ -0,0 +1,18 @@
1
+ # Password form.
2
+
3
+ # Separate form with only the password parameter to make it easy to include it wherever necessary.
4
+ class PasswordForm < FormInput
5
+
6
+ # Enforce size and format of the password.
7
+ # Note that we strip the password newline in case they cut&paste it from somewhere.
8
+
9
+ param! :password, "Password", type: :password, min_size: 6,
10
+ reject: /\P{ASCII}|[\t\r\n]/u,
11
+ reject_msg: "%p may contain only ASCII characters and spaces",
12
+ match: [ /[a-z]/i, /\d/ ],
13
+ msg: "%p must contain at least one digit and one letter",
14
+ filter: ->{ chomp }
15
+
16
+ end
17
+
18
+ # EOF #
@@ -0,0 +1,153 @@
1
+ # Press Release Form - example of a complex multistep form.
2
+
3
+ class PressReleaseForm < FormInput
4
+
5
+ ### Parameters ###
6
+
7
+ # Pricing plan we use for evaluating things.
8
+
9
+ attr_reader :plan
10
+
11
+ # Creation process steps.
12
+
13
+ define_steps(
14
+ content: "Content",
15
+ date: "Date",
16
+ location: "Location",
17
+ contact: "Contact",
18
+ keywords: "Keywords",
19
+ quote: "Quote",
20
+ images: "Images",
21
+ videos: "Videos",
22
+ website: "Web Site",
23
+ release: "Release Date",
24
+ channels: "Channels",
25
+ summary: "Summary",
26
+ post: nil,
27
+ )
28
+
29
+ # Press release itself.
30
+
31
+ param! :title, "Title", 120, tag: :content,
32
+ subtitle: "(Maximum 120 characters, recommended max 80 characters)",
33
+ help: "The main title of your press release. It should be brief, clear and to the point."
34
+ param :subtitle, "Subtitle", 160, tag: :content,
35
+ subtitle: "(Maximum 160 characters)",
36
+ help: "Optional but recommended. If used, make sure it's descriptive and builds on the headline."
37
+ param! :text, "Text", 65000, max_bytesize: 65535, type: :textarea, size: 16, tag: :content,
38
+ subtitle: ->{ "(Maximum #{plural( form.limit( :words ), 'word' )})" },
39
+ filter: ->{ strip },
40
+ check: ->{
41
+ count = form.feature_count( :words, value )
42
+ limit = form.limit( :words )
43
+ report "Your text has #{plural( count, 'word' )}, but the limit is #{delimited( limit )}." if count > limit
44
+ }
45
+
46
+ # Date.
47
+
48
+ param! :date, "Date", DATE_ARGS, tag: :date
49
+
50
+ # Location.
51
+
52
+ param! :city, "City", 100, tag: :location
53
+ param :state, "State", STATE_ARGS, tag: :location
54
+ param! :country, "Country", COUNTRY_ARGS, tag: :location
55
+
56
+ # Contact info.
57
+
58
+ param! :contact_name, "Full Name", 60, tag: :contact
59
+ param! :contact_organization, "Organization Name", 60, tag: :contact
60
+ param! :contact_phone, "Phone", 30, PHONE_ARGS, tag: :contact
61
+ param! :contact_email, "Email Address", 60, EMAIL_ARGS, tag: :contact
62
+
63
+ # Keywords.
64
+
65
+ array :keywords, "Keywords", 35, PRUNED_ARGS, tag: :keywords,
66
+ row: :keywords, cols: 5,
67
+ max_count: ->{ form.limit( :keywords ) }
68
+ array :keywords_urls, "Keywords URL", PRUNED_ARGS, WEB_URL_ARGS, max_count: 30, tag: :keywords,
69
+ row: :keywords, cols: 7,
70
+ max_count: ->{ form.limit( :keywords ) }
71
+
72
+ def balance_keywords
73
+ self.keywords ||= []
74
+ self.keywords_urls ||= []
75
+
76
+ report( :keywords, "Keywords must match the keyword URLs" ) if keywords.count < keywords_urls.count
77
+ report( :keywords_urls, "Keyword URLs must match the keywords" ) if keywords.count > keywords_urls.count
78
+
79
+ self.keywords << "" while keywords.count < keywords_urls.count
80
+ self.keywords_urls << "" while keywords.count > keywords_urls.count
81
+ end
82
+
83
+ # Quote.
84
+
85
+ param :quote, "Quote", 200, tag: :quote,
86
+ subtitle: "(Maximum 200 characters)",
87
+ disabled: ->{ form.disabled?( :quote ) }
88
+ param :quote_author, "Quote Author", 100, tag: :quote,
89
+ subtitle: "(Maximum 100 characters)",
90
+ disabled: ->{ form.disabled?( :quote ) }
91
+
92
+ # Images.
93
+
94
+ array :images, "Images", PRUNED_ARGS, tag: :images,
95
+ max_count: ->{ form.limit( :images ) }
96
+
97
+ # Video.
98
+
99
+ array :videos, "Videos", PRUNED_ARGS, tag: :videos,
100
+ max_count: ->{ form.limit( :videos ) }
101
+
102
+ # Website.
103
+
104
+ param :website_url, "URL", WEB_URL_ARGS, tag: :website,
105
+ disabled: ->{ form.disabled?( :website ) }
106
+
107
+ # Release date.
108
+
109
+ param :release_date, "Release Date", US_DATE_ARGS, tag: :release,
110
+ disabled: ->{ form.disabled?( :scheduling ) }
111
+ param :release_time, "Release Time", HOURS_ARGS, tag: :release,
112
+ disabled: ->{ form.disabled?( :scheduling ) }
113
+
114
+ # Distribution.
115
+
116
+ array! :channels, "Channels", type: :select, tag: :channels,
117
+ size: 25,
118
+ max_count: ->{ form.limit( :channels ) },
119
+ data: ->{ Channel.all.map{ |x| [ x.code, x.name ] } },
120
+ filter: ->{ self if Channel[ self ] }
121
+
122
+ ### Methods ###
123
+
124
+ # Initialize new instance.
125
+ def initialize( plan, *args )
126
+ @plan = plan
127
+ super( *args )
128
+ balance_keywords
129
+ end
130
+
131
+ # Get the items to show in the sidebar.
132
+ def sidebar_items
133
+ { base: 'Base Price' }.merge( step_names )
134
+ end
135
+
136
+ # Get limit for given feature.
137
+ def limit( feature )
138
+ plan.extra_limit( feature )
139
+ end
140
+
141
+ # Get count for given feature and value.
142
+ def feature_count( feature, value )
143
+ plan.feature_count( feature, value )
144
+ end
145
+
146
+ # Test if given feature is disabled for current plan.
147
+ def disabled?( feature )
148
+ not plan.has_feature?( feature )
149
+ end
150
+
151
+ end
152
+
153
+ # EOF #
@@ -0,0 +1,21 @@
1
+ # Profile form.
2
+
3
+ require_relative 'signup_form'
4
+
5
+ class ProfileForm < FormInput
6
+
7
+ copy SignupForm[ :first_name, :last_name ]
8
+
9
+ param :company, "Company"
10
+ param :street, "Street"
11
+ param :city, "City"
12
+ param :state, "State", STATE_ARGS
13
+ param :country, "Country", COUNTRY_ARGS
14
+ param :zip, "ZIP code", ZIP_ARGS
15
+ param :phone, "Phone number", PHONE_ARGS
16
+ param :fax, "Fax number", PHONE_ARGS
17
+ param :url, "URL", WEB_URL_ARGS
18
+
19
+ end
20
+
21
+ # EOF #
@@ -0,0 +1,25 @@
1
+ # Signup form.
2
+
3
+ require_relative 'password_form'
4
+
5
+ class SignupForm < FormInput
6
+
7
+ # Currently we restrict names to latin based alphabets.
8
+
9
+ param! :first_name, "First name", match: LATIN_NAMES_RE,
10
+ msg: "Sorry, only names using latin alphabet are allowed"
11
+ param! :last_name, "Last name", match: LATIN_NAMES_RE,
12
+ msg: "Sorry, only surnames using latin alphabet are allowed"
13
+
14
+ # Note that we only enforce relaxed format of the emails.
15
+ # Real problems will be found out by verification anyway.
16
+
17
+ param! :login, :email, "Email", EMAIL_ARGS
18
+
19
+ # Enforce size and format of the password the same way the standard password form does.
20
+
21
+ copy PasswordForm
22
+
23
+ end
24
+
25
+ # EOF #
@@ -0,0 +1,65 @@
1
+ // Press Release editing form - example of a complex multi-step form.
2
+
3
+ - @title = "Press Release: #{@form.step_name}"
4
+
5
+ - edit_mode = ( page_action == 'edit' )
6
+
7
+ form *form_attrs
8
+
9
+ // Invisible first submit button to send users to the next page
10
+ // when they press enter in the text field.
11
+ button.invisible type='submit' name='next' tabindex='-1' value=@form.next_step
12
+
13
+ .row
14
+ .col-md-3 role='navigation'
15
+
16
+ == snippet :sidebar,
17
+ title: "#{edit_mode ? 'Edit' : 'Create'} Press Release",
18
+ items: @form.sidebar_items,
19
+ accessible: @form.accessible_steps,
20
+ valid: @form.good_steps,
21
+ invalid: @form.bad_steps,
22
+ current: @form.step
23
+
24
+ .col-md-9 role='main'
25
+
26
+ .panel.panel-default
27
+ .panel-heading
28
+ = @title
29
+ .panel-body
30
+
31
+ // Additional info derived from @state or @form.step will usually go here.
32
+
33
+ fieldset
34
+ == snippet :form_hidden,
35
+ params: @form.other_params
36
+ == snippet :form_chunked,
37
+ params: @form.chunked_params( @form.current_params ),
38
+ report: @state == :report,
39
+ focus: true
40
+
41
+ - unless @form.extra_step?
42
+ .btn-toolbar.pull-left
43
+ button.btn.btn-default type='submit'
44
+ Update
45
+
46
+ .btn-toolbar.pull-right
47
+ a.btn.btn-default href=r
48
+ Cancel
49
+ - if @form.step == :summary
50
+ button.btn.btn-primary type='submit' name='next' value='post'
51
+ Save
52
+ - else
53
+ button.btn.btn-default type='submit' name='next' value='summary'
54
+ Finish
55
+ - unless edit_mode
56
+ button.btn.btn-primary.pull-right type='submit' name='next' value=@form.next_step
57
+ Next
58
+
59
+ // These make it easy to debug the form if needed:
60
+ //
61
+ // pre = @form.params.map{|x| [ x.name, x.value ] }.inspect
62
+ // pre = @form.errors.inspect
63
+ // pre = @state
64
+
65
+ // EOF //
@@ -0,0 +1,28 @@
1
+ // Update user profile - example of rendering simple form page.
2
+
3
+ - @title = "Profile"
4
+
5
+ .panel.panel-default
6
+ .panel-heading
7
+ = @title
8
+ .panel-body
9
+ form *form_attrs
10
+
11
+ - case @state
12
+ - when :done
13
+ .alert.alert-success.alert-block
14
+ Your profile was successfully updated.
15
+ - when :update_failed
16
+ .alert.alert-danger.alert-block
17
+ strong Profile update failed!
18
+ br
19
+ We apologize for such inconvenience. Please try again later.
20
+
21
+ fieldset
22
+ == snippet :form_panel,
23
+ params: @form.params,
24
+ report: @state == :report
25
+ focus: @state == :report
26
+ button.btn.btn-default type='submit' Update Profile
27
+
28
+ // EOF //