form_input 0.9.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/LICENSE +19 -0
- data/README.md +3160 -0
- data/Rakefile +19 -0
- data/example/controllers/ramaze/press_release.rb +104 -0
- data/example/controllers/ramaze/profile.rb +38 -0
- data/example/controllers/sinatra/press_release.rb +114 -0
- data/example/controllers/sinatra/profile.rb +39 -0
- data/example/forms/change_password_form.rb +17 -0
- data/example/forms/login_form.rb +14 -0
- data/example/forms/lost_password_form.rb +14 -0
- data/example/forms/new_password_form.rb +15 -0
- data/example/forms/password_form.rb +18 -0
- data/example/forms/press_release_form.rb +153 -0
- data/example/forms/profile_form.rb +21 -0
- data/example/forms/signup_form.rb +25 -0
- data/example/views/press_release.slim +65 -0
- data/example/views/profile.slim +28 -0
- data/example/views/snippets/form_block.slim +27 -0
- data/example/views/snippets/form_chunked.slim +25 -0
- data/example/views/snippets/form_hidden.slim +21 -0
- data/example/views/snippets/form_panel.slim +89 -0
- data/form_input.gemspec +32 -0
- data/lib/form_input/core.rb +1165 -0
- data/lib/form_input/localize.rb +49 -0
- data/lib/form_input/r18n/cs.yml +97 -0
- data/lib/form_input/r18n/en.yml +70 -0
- data/lib/form_input/r18n/pl.yml +122 -0
- data/lib/form_input/r18n/sk.yml +120 -0
- data/lib/form_input/r18n.rb +163 -0
- data/lib/form_input/steps.rb +365 -0
- data/lib/form_input/types.rb +176 -0
- data/lib/form_input/version.rb +12 -0
- data/lib/form_input.rb +5 -0
- data/test/helper.rb +21 -0
- data/test/localize/en.yml +63 -0
- data/test/r18n/cs.yml +60 -0
- data/test/r18n/xx.yml +51 -0
- data/test/reference/cs.txt +352 -0
- data/test/reference/cs.yml +14 -0
- data/test/reference/en.txt +76 -0
- data/test/reference/en.yml +8 -0
- data/test/reference/pl.txt +440 -0
- data/test/reference/pl.yml +16 -0
- data/test/reference/sk.txt +352 -0
- data/test/reference/sk.yml +14 -0
- data/test/test_core.rb +1272 -0
- data/test/test_localize.rb +27 -0
- data/test/test_r18n.rb +373 -0
- data/test/test_steps.rb +482 -0
- data/test/test_types.rb +307 -0
- 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 //
|