kiss 1.1 → 1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +1 -1
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/kiss +151 -34
- data/data/scaffold.tgz +0 -0
- data/lib/kiss.rb +389 -742
- data/lib/kiss/accessors/controller.rb +47 -0
- data/lib/kiss/accessors/request.rb +106 -0
- data/lib/kiss/accessors/template.rb +23 -0
- data/lib/kiss/action.rb +502 -132
- data/lib/kiss/bench.rb +14 -5
- data/lib/kiss/debug.rb +14 -6
- data/lib/kiss/exception_report.rb +22 -299
- data/lib/kiss/ext/core.rb +700 -0
- data/lib/kiss/ext/rack.rb +33 -0
- data/lib/kiss/ext/sequel_database.rb +47 -0
- data/lib/kiss/ext/sequel_mysql_dataset.rb +23 -0
- data/lib/kiss/form.rb +404 -179
- data/lib/kiss/form/field.rb +183 -307
- data/lib/kiss/form/field_types.rb +239 -0
- data/lib/kiss/format.rb +88 -70
- data/lib/kiss/html/exception_report.css +222 -0
- data/lib/kiss/html/exception_report.html +210 -0
- data/lib/kiss/iterator.rb +14 -12
- data/lib/kiss/login.rb +8 -8
- data/lib/kiss/mailer.rb +68 -66
- data/lib/kiss/model.rb +323 -36
- data/lib/kiss/rack/bench.rb +16 -8
- data/lib/kiss/rack/email_errors.rb +25 -15
- data/lib/kiss/rack/errors_ok.rb +2 -2
- data/lib/kiss/rack/facebook.rb +6 -6
- data/lib/kiss/rack/file_not_found.rb +10 -8
- data/lib/kiss/rack/log_exceptions.rb +3 -3
- data/lib/kiss/rack/recorder.rb +2 -2
- data/lib/kiss/rack/show_debug.rb +2 -2
- data/lib/kiss/rack/show_exceptions.rb +2 -2
- data/lib/kiss/request.rb +435 -0
- data/lib/kiss/sequel_session.rb +15 -14
- data/lib/kiss/static_file.rb +20 -13
- data/lib/kiss/template.rb +327 -0
- metadata +60 -25
- data/lib/kiss/controller_accessors.rb +0 -81
- data/lib/kiss/hacks.rb +0 -188
- data/lib/kiss/sequel_mysql.rb +0 -25
- data/lib/kiss/template_methods.rb +0 -167
@@ -0,0 +1,33 @@
|
|
1
|
+
module Rack
|
2
|
+
autoload :Bench, 'kiss/rack/bench'
|
3
|
+
autoload :ErrorsOK, 'kiss/rack/errors_ok'
|
4
|
+
autoload :EmailErrors, 'kiss/rack/email_errors'
|
5
|
+
autoload :Facebook, 'kiss/rack/facebook'
|
6
|
+
autoload :FileNotFound, 'kiss/rack/file_not_found'
|
7
|
+
autoload :LogExceptions, 'kiss/rack/log_exceptions'
|
8
|
+
autoload :Recorder, 'kiss/rack/recorder'
|
9
|
+
autoload :ShowDebug, 'kiss/rack/show_debug'
|
10
|
+
autoload :ShowExceptions, 'kiss/rack/show_exceptions'
|
11
|
+
|
12
|
+
class Request
|
13
|
+
def server
|
14
|
+
url = scheme + "://"
|
15
|
+
url << host
|
16
|
+
|
17
|
+
if scheme == "https" && port != 443 ||
|
18
|
+
scheme == "http" && port != 80
|
19
|
+
url << ":#{port}"
|
20
|
+
end
|
21
|
+
|
22
|
+
url
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Response
|
27
|
+
def prepend_html(*args)
|
28
|
+
b = body
|
29
|
+
(b = b.join) if b.is_a?(Array)
|
30
|
+
b.prepend_html(*args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Kiss
|
2
|
+
# This module is included into Sequel database class to provide Kiss-specific
|
3
|
+
# fnctionality to database objects.
|
4
|
+
module SequelDatabase
|
5
|
+
|
6
|
+
def self.append_features(mod)
|
7
|
+
mod.class_eval do
|
8
|
+
alias_method :execute_old, :execute
|
9
|
+
_attr_accessor :kiss_controller, :kiss_request, :kiss_model_cache
|
10
|
+
|
11
|
+
def execute(sql, *args, &block) #:nodoc:
|
12
|
+
@_last_query = sql
|
13
|
+
execute_old(sql, *args, &block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
@_last_query = nil
|
20
|
+
def last_query #:nodoc:
|
21
|
+
@_last_query
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns Sequel dataset to evolution_number table, which specifies
|
25
|
+
# app's current evolution number.
|
26
|
+
# Creates evolution_number table if it does not exist.
|
27
|
+
def evolution_number_table
|
28
|
+
unless self.table_exists?(:evolution_number)
|
29
|
+
self.create_table :evolution_number do
|
30
|
+
column :version, :integer, :null=> false
|
31
|
+
end
|
32
|
+
self[:evolution_number].insert(:version => 0)
|
33
|
+
end
|
34
|
+
self[:evolution_number]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns app's current evolution number.
|
38
|
+
def evolution_number
|
39
|
+
evolution_number_table.first.version
|
40
|
+
end
|
41
|
+
|
42
|
+
# Sets app's current evolution number.
|
43
|
+
def evolution_number=(version)
|
44
|
+
evolution_number_table.update(:version => version)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Kiss
|
2
|
+
module SequelMySQLDataset
|
3
|
+
# Returns results from dataset query as array of arrays,
|
4
|
+
# instead of array of hashes.
|
5
|
+
def all_arrays(opts = nil, &block)
|
6
|
+
a = []
|
7
|
+
fetch_arrays(select_sql()) {|r| a << r}
|
8
|
+
a.each(&block) if block
|
9
|
+
a
|
10
|
+
end
|
11
|
+
|
12
|
+
# Fixes bug in Sequel 1.5; shouldn't be needed for Sequel 2.x
|
13
|
+
# (need to double-check, however).
|
14
|
+
def fetch_arrays(sql)
|
15
|
+
execute(sql) do |r|
|
16
|
+
while row = r.fetch_row
|
17
|
+
yield row
|
18
|
+
end
|
19
|
+
end
|
20
|
+
self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/kiss/form.rb
CHANGED
@@ -1,146 +1,228 @@
|
|
1
|
-
|
1
|
+
require 'kiss/form/field';
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
def set_attributes(attrs_original,required = [])
|
7
|
-
unless attrs_original.is_a?(Hash) then
|
8
|
-
raise "first parameter must be a hash of attributes: #{attrs_original}"
|
9
|
-
end
|
10
|
-
|
11
|
-
attrs = attrs_original.clone
|
12
|
-
|
13
|
-
required.each do |key|
|
14
|
-
raise "missing required parameter '#{key}'" unless attrs[key]
|
15
|
-
send("#{key}=", attrs[key])
|
16
|
-
attrs.delete(key)
|
17
|
-
end
|
18
|
-
|
19
|
-
attrs.each_pair do |key,value|
|
20
|
-
send("#{key}=", value)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
include Kiss::Form::AttributesSetter
|
3
|
+
# Part Hash, part Array... it's a Hashay.
|
4
|
+
class Hashay < Hash
|
5
|
+
_attr_accessor :keys, :values
|
25
6
|
|
26
|
-
|
7
|
+
def initialize(*args, &block)
|
8
|
+
super
|
9
|
+
@_keys = []
|
10
|
+
@_values = []
|
11
|
+
end
|
27
12
|
|
28
|
-
|
29
|
-
|
30
|
-
|
13
|
+
def each_key(&block)
|
14
|
+
@_keys.each &block
|
15
|
+
end
|
31
16
|
|
17
|
+
def each_value(&block)
|
18
|
+
@_values.each &block
|
19
|
+
end
|
20
|
+
alias_method :each, :each_value
|
32
21
|
|
33
|
-
|
22
|
+
def []=(key, value)
|
23
|
+
unless has_key?(key)
|
24
|
+
@_keys << key
|
25
|
+
@_values << value
|
26
|
+
end
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](key)
|
31
|
+
key.is_a?(Numeric) ? @_values[key] : super
|
32
|
+
end
|
33
|
+
|
34
|
+
include Enumerable
|
35
|
+
end
|
36
|
+
|
37
|
+
class Kiss
|
38
|
+
class Form
|
39
|
+
_attr_accessor :fields, :params, :submitted, :has_field_errors, :has_required_fields,
|
40
|
+
:delegate, :controller, :components, :form, :new_object_index
|
41
|
+
dsl_accessor :name, :url, :action, :method, :enctype, :errors, :cancel, :mark_required,
|
42
|
+
:id, :class, :style, :html, :error_class, :field_error_class, :objects_save_order,
|
43
|
+
:object, :prepend_html, :append_html, :year, :timezone
|
44
|
+
|
45
|
+
@@component_types = {
|
34
46
|
:text => TextField,
|
35
47
|
:hidden => HiddenField,
|
36
48
|
:textarea => TextAreaField,
|
37
49
|
:password => PasswordField,
|
38
|
-
#:multitext => MultiTextField,
|
39
50
|
:boolean => BooleanField,
|
40
51
|
:file => FileField,
|
41
52
|
:select => SelectField,
|
42
53
|
:radio => RadioField,
|
43
54
|
:checkbox => CheckboxField,
|
44
55
|
:multiselect => MultiSelectField,
|
45
|
-
:submit => SubmitField
|
56
|
+
:submit => SubmitField
|
46
57
|
}
|
58
|
+
|
59
|
+
# Create DSL methods for component types
|
60
|
+
self.class_eval(
|
61
|
+
@@component_types.keys.map do |type|
|
62
|
+
"def #{type}(*args, &block); add_component(:#{type}, *args, &block); end; "
|
63
|
+
end.join
|
64
|
+
)
|
65
|
+
|
66
|
+
def add_component(type, name, *args, &block)
|
67
|
+
attrs = args.to_attrs
|
68
|
+
add_field({
|
69
|
+
:type => type,
|
70
|
+
:name => name
|
71
|
+
}.merge(attrs), &block)
|
72
|
+
end
|
47
73
|
|
74
|
+
def debug(*args)
|
75
|
+
@_delegate.request.debug(args.first, Kernel.caller[0])
|
76
|
+
end
|
48
77
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
raise "Missing required option 'name'" unless @name && (@name != '')
|
54
|
-
|
55
|
-
@method ||= 'post'
|
78
|
+
def method_missing(method, *args, &block)
|
79
|
+
delegate.send method, *args, &block
|
80
|
+
end
|
56
81
|
|
57
|
-
|
58
|
-
|
59
|
-
|
82
|
+
def unique(*val)
|
83
|
+
if val.empty?
|
84
|
+
@_unique
|
85
|
+
else
|
86
|
+
@_unique << val
|
87
|
+
end
|
88
|
+
end
|
60
89
|
|
61
|
-
|
62
|
-
|
63
|
-
@
|
64
|
-
@
|
90
|
+
# Creates a new form object with specified attributes.
|
91
|
+
def initialize(*args, &block)
|
92
|
+
@_attrs = args.to_attrs
|
93
|
+
_instance_variables_set_from_attrs(@_attrs)
|
65
94
|
|
66
|
-
@
|
95
|
+
@_method ||= 'post'
|
96
|
+
|
97
|
+
@_components = []
|
98
|
+
@_fields = Hashay.new
|
99
|
+
@_object_fields = {}
|
100
|
+
@_default_values ||= {}
|
101
|
+
@_params = {}
|
102
|
+
@_with = nil
|
103
|
+
@_field_name_prefix = ''
|
104
|
+
@_objects_add_order = []
|
105
|
+
@_objects_save_order = []
|
106
|
+
@_new_object_index = -1
|
107
|
+
@_object_fields = {}
|
108
|
+
@_unique = []
|
109
|
+
|
110
|
+
@_prepend_html = ''
|
111
|
+
@_append_html = ''
|
112
|
+
|
113
|
+
(@_attrs[:fields] || []).each do |field|
|
67
114
|
# create field here
|
68
115
|
add_field(field)
|
69
116
|
end
|
70
117
|
|
71
|
-
@
|
72
|
-
@
|
118
|
+
@_errors = []
|
119
|
+
@_field_errors = {}
|
120
|
+
|
121
|
+
import_instance_variables(@_delegate) if @_delegate
|
122
|
+
|
123
|
+
instance_eval(&block) if block_given?
|
124
|
+
|
125
|
+
raise "form name required" unless @_name
|
126
|
+
raise "form delegate required" unless @_delegate
|
73
127
|
end
|
74
128
|
|
75
|
-
|
76
|
-
|
77
|
-
|
129
|
+
def context
|
130
|
+
@_context ||= begin
|
131
|
+
@_timezone ||= (fields['timezone'] && (params['timezone'] || (object && object[:timezone]))) || nil
|
132
|
+
{
|
133
|
+
:timezone => @_timezone,
|
134
|
+
:year => @_year
|
135
|
+
}
|
136
|
+
end
|
78
137
|
end
|
79
138
|
|
80
|
-
|
81
|
-
|
139
|
+
# Creates and adds a field to the form, according to specified attributes.
|
140
|
+
def add_field(attrs = {}, &block)
|
141
|
+
attrs = @_with.merge(attrs) if @_with
|
142
|
+
name = attrs[:name].to_s
|
143
|
+
key = (attrs[:key] || name).to_sym
|
144
|
+
|
145
|
+
name = @_field_name_prefix + name unless @_field_name_prefix.empty?
|
146
|
+
|
147
|
+
type = attrs[:type] ? attrs[:type].to_sym : :text
|
148
|
+
raise "invalid field type '#{type}'" unless @@component_types.has_key?(type)
|
82
149
|
|
83
|
-
|
84
|
-
|
85
|
-
|
150
|
+
field = @@component_types[type].new(self, attrs.merge(
|
151
|
+
:name => name,
|
152
|
+
:key => key,
|
153
|
+
:type => type
|
154
|
+
), &block)
|
86
155
|
|
87
|
-
|
88
|
-
|
156
|
+
field.object = @_object if @_object && !field.object
|
157
|
+
obj = field.object
|
158
|
+
# must hash @_object_fields by Ruby object id; if by object, weird lookup errors result,
|
159
|
+
# even when lookup object has same Ruby object id as the hash key object!
|
160
|
+
ruby_obj_id = obj.object_id
|
161
|
+
unless @_object_fields[ruby_obj_id]
|
162
|
+
@_object_fields[ruby_obj_id] = {}
|
163
|
+
@_objects_add_order << object
|
164
|
+
end
|
165
|
+
if @_object_fields[ruby_obj_id][field.name]
|
166
|
+
raise "duplicate form field name '#{attrs[:name]}'#{" on #{obj.class.name} object" if obj}"
|
167
|
+
end
|
168
|
+
@_object_fields[ruby_obj_id][field.name] = field
|
89
169
|
|
90
|
-
|
91
|
-
|
170
|
+
@_fields[name] = field
|
171
|
+
@_components << field
|
92
172
|
|
93
173
|
while true
|
94
174
|
other_field = field.other_field
|
95
175
|
break unless other_field
|
96
176
|
other_field.form = self
|
97
|
-
@
|
177
|
+
@_other_field = @_form.create_field( { :name => @_name + '.other' }.merge(@_other) )
|
98
178
|
end
|
99
179
|
|
100
|
-
@
|
101
|
-
|
102
|
-
@enctype = 'multipart/form-data' if field.type == :file
|
180
|
+
@_enctype = 'multipart/form-data' if field.type == :file
|
103
181
|
|
104
182
|
field
|
105
183
|
end
|
184
|
+
alias_method :create_field, :add_field
|
106
185
|
|
107
|
-
# Creates and adds
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
186
|
+
# Creates and adds set of submit buttons to the form, per specified attributes.
|
187
|
+
def submit(*args, &block)
|
188
|
+
if args.size > 0
|
189
|
+
raise 'submit already defined' if @_submit
|
190
|
+
|
191
|
+
attrs = {
|
192
|
+
:type => :submit,
|
193
|
+
:name => 'submit',
|
194
|
+
:save => false,
|
195
|
+
:cancel => 'Cancel'
|
196
|
+
}
|
197
|
+
attrs.merge!(args.pop) if args.last.is_a?(Hash)
|
198
|
+
attrs[:options] = args if args.size > 0
|
199
|
+
attrs = @_with.merge(attrs) if @_with
|
200
|
+
@_submit = @@component_types[:submit].new(self, attrs, &block)
|
201
|
+
else
|
202
|
+
@_submit
|
203
|
+
end
|
119
204
|
end
|
205
|
+
alias_method :add_submit, :submit
|
120
206
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
end
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
# hash = {}
|
134
|
-
# @fields.each {|field| hash[field.name] = field.sequel_value unless field == :section_break }
|
135
|
-
# hash
|
136
|
-
# end
|
137
|
-
# end
|
207
|
+
def reset
|
208
|
+
@_params = {}
|
209
|
+
@_fields.each {|field| field.reset }
|
210
|
+
end
|
211
|
+
|
212
|
+
def field(name)
|
213
|
+
@_fields[name]
|
214
|
+
end
|
215
|
+
|
216
|
+
def object_field(obj, name)
|
217
|
+
@_object_fields[obj.object_id][name.to_s]
|
218
|
+
end
|
138
219
|
|
139
220
|
# Gets hash of form values.
|
140
221
|
def values
|
141
|
-
@
|
222
|
+
@_values ||= begin
|
142
223
|
hash = {}
|
143
|
-
@
|
224
|
+
@_fields.each {|field| hash[field.name] = field.value }
|
225
|
+
hash['submit'] = @_submit.value = params[@_submit.name.to_s]
|
144
226
|
hash
|
145
227
|
end
|
146
228
|
end
|
@@ -149,27 +231,26 @@ class Kiss
|
|
149
231
|
# If multiple args given, first arg is field name, and second arg (error message)
|
150
232
|
# will render next to specified field.
|
151
233
|
def add_error(*args)
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
if args[0].is_a?(String)
|
161
|
-
@errors << args[0]
|
162
|
-
return
|
234
|
+
case args.size
|
235
|
+
when 0
|
236
|
+
raise 'at least one argument required'
|
237
|
+
when 1
|
238
|
+
arg = args.first
|
239
|
+
if arg.is_a?(Hash)
|
240
|
+
field = arg.field || arg.field_name
|
241
|
+
message = arg.message
|
163
242
|
else
|
164
|
-
|
165
|
-
|
243
|
+
@_errors << arg
|
244
|
+
return
|
166
245
|
end
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
246
|
+
when 2
|
247
|
+
field, message = args
|
248
|
+
else
|
249
|
+
raise 'too many arguments'
|
171
250
|
end
|
172
|
-
|
251
|
+
|
252
|
+
field = @_fields[field] if field.is_a?(String)
|
253
|
+
field.add_error(message)
|
173
254
|
end
|
174
255
|
|
175
256
|
# Validates form values against fields' format and required attributes,
|
@@ -177,92 +258,211 @@ class Kiss
|
|
177
258
|
def validate
|
178
259
|
return nil unless submitted
|
179
260
|
|
180
|
-
@
|
261
|
+
@_fields.each {|field| field.validate }
|
262
|
+
@_unique.each {|field_set| validate_uniqueness_of(field_set) }
|
181
263
|
|
182
|
-
has_errors ? nil : values
|
264
|
+
has_errors? ? nil : values
|
183
265
|
end
|
184
266
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
next if field.name =~ /\A\_/
|
191
|
-
# don't save 'ignore' fields to the database
|
192
|
-
next if field.ignore || !field.save
|
193
|
-
|
194
|
-
value = (field.value != nil || object.class.db_schema[field.name.to_sym].allow_null) ?
|
195
|
-
field.value : (object.class.db_schema[field.name.to_sym][:default] ||= field.format.default)
|
196
|
-
|
197
|
-
object[field.name.to_sym] = field.save == true ? value :
|
198
|
-
(digest_class = Kiss.digest_class(field.save)) ? digest_class.hexdigest(value) : value
|
267
|
+
def validate_uniqueness_of(column_set)
|
268
|
+
column_set_labels_values = column_set.map do |column_name|
|
269
|
+
(field = fields[column_name.to_s]) ?
|
270
|
+
[field.label, field.value] :
|
271
|
+
[column_name.to_s.sub(/_id\Z/, '').titlecase, object[column_name]]
|
199
272
|
end
|
273
|
+
conditions = column_set.zip column_set_labels_values.map {|lv| lv[1] }
|
200
274
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
275
|
+
dataset = @_object.model.filter(conditions)
|
276
|
+
unless (@_object.new? ? dataset : dataset.exclude(@_object.pk_hash)).empty?
|
277
|
+
labels = column_set_labels_values.map {|lv| lv[0] }
|
278
|
+
message = "There is already another #{@_object.model.name.singularize.gsub('_', ' ')} with the same #{labels.conjoin}."
|
279
|
+
return (
|
280
|
+
(column_set.length == 1) &&
|
281
|
+
(field_name = column_set[0].to_str) &&
|
282
|
+
fields[field_name]
|
283
|
+
) ? add_error(field_name, message) : add_error(message)
|
284
|
+
end
|
207
285
|
end
|
208
286
|
|
209
287
|
# Checks whether form was submitted and accepted by user and, if so,
|
210
288
|
# whether form validates.
|
211
289
|
# If form validates, saves form values to form's Sequel::Model object
|
212
290
|
# and returns true. Otherwise, returns false.
|
213
|
-
def process(
|
291
|
+
def process(*objs)
|
214
292
|
return false unless submitted
|
215
293
|
|
216
294
|
if accepted
|
217
295
|
validate
|
218
|
-
return false if has_errors
|
296
|
+
return false if has_errors?
|
219
297
|
|
220
|
-
save(
|
298
|
+
save(*objs)
|
221
299
|
end
|
222
300
|
|
223
301
|
return true
|
224
302
|
end
|
225
303
|
|
304
|
+
def process_or_render
|
305
|
+
self.render unless self.process
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
|
310
|
+
def extract_objects_from_args(objs)
|
311
|
+
objs = args.first if objs.first.is_a?(Array)
|
312
|
+
if objs.size == 0
|
313
|
+
objects_save_order.push(@_object) if @_object && !objects_save_order.include?(@_object)
|
314
|
+
objs = objects_save_order
|
315
|
+
end
|
316
|
+
objs
|
317
|
+
end
|
318
|
+
|
319
|
+
public
|
320
|
+
|
321
|
+
def set_object_data(obj = @_object)
|
322
|
+
@_object_fields[obj.object_id].values.each do |field|
|
323
|
+
# ignore fields whose name starts with underscore
|
324
|
+
next if field.name =~ /\A\_/
|
325
|
+
# don't save 'ignore' fields to the database
|
326
|
+
next if field.ignore || !field.save
|
327
|
+
# ignore file fields
|
328
|
+
next if field.type == :file
|
329
|
+
|
330
|
+
key = field.key.to_sym
|
331
|
+
value = (field.value != nil || obj.class.db_schema[key].allow_null) ?
|
332
|
+
field.value : (obj.class.db_schema[key][:default] ||= field.format.default)
|
333
|
+
|
334
|
+
if field.digest
|
335
|
+
value = Digest.const_get(field.digest.to_sym).hexdigest(value)
|
336
|
+
end
|
337
|
+
|
338
|
+
obj[key] = value if field.save
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def set_objects_data(*args)
|
343
|
+
extract_objects_from_args(args).each do |obj|
|
344
|
+
next unless obj
|
345
|
+
set_object_data(obj)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# Saves form input data associated with specified objects, or
|
350
|
+
# all form objects if objects are not specified here.
|
351
|
+
def save(*args)
|
352
|
+
db.transaction do
|
353
|
+
extract_objects_from_args(args).each do |obj|
|
354
|
+
set_object_data(obj)
|
355
|
+
obj.save
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
def require_values(*names)
|
361
|
+
names.each {|name| fields[name.to_s].require_value }
|
362
|
+
end
|
363
|
+
|
364
|
+
def with(*args, &block)
|
365
|
+
if block_given?
|
366
|
+
attrs = args.to_attrs
|
367
|
+
old_with = @_with
|
368
|
+
@_with = (@_with || {}).merge(attrs)
|
369
|
+
|
370
|
+
instance_eval(&block)
|
371
|
+
|
372
|
+
@_with = old_with
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
def object(obj = nil, options = {}, &block)
|
377
|
+
if block_given?
|
378
|
+
raise 'missing object arg' unless obj
|
379
|
+
|
380
|
+
if obj.is_a?(Symbol)
|
381
|
+
parent = @_object
|
382
|
+
raise 'missing parent object for object symbol reference' unless parent
|
383
|
+
obj = parent.send(obj) #||
|
384
|
+
#parent.set_associated_object(parent.class.association_reflection(object).associated_class.new)
|
385
|
+
end
|
386
|
+
raise 'object parameter must reference a model object' unless obj.is_a?(Kiss::Model)
|
387
|
+
|
388
|
+
# if new, save early to give object a primary key, needed to save assoc children
|
389
|
+
@_objects_save_order << obj if obj.new?
|
390
|
+
prefix = options[:prefix]
|
391
|
+
|
392
|
+
old_obj = @_object
|
393
|
+
old_prefix = @_field_name_prefix
|
394
|
+
|
395
|
+
@_object = obj
|
396
|
+
@_field_name_prefix = (@_field_name_prefix + prefix.to_s + '.') if prefix
|
397
|
+
instance_eval(&block)
|
398
|
+
@_object = old_obj
|
399
|
+
@_field_name_prefix = old_prefix
|
400
|
+
|
401
|
+
# save again to pick up any assoc ids from children
|
402
|
+
@_objects_save_order << obj
|
403
|
+
obj
|
404
|
+
elsif obj
|
405
|
+
@_object = obj
|
406
|
+
else
|
407
|
+
@_object
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
# Returns true if form has errors.
|
412
|
+
def has_errors?
|
413
|
+
(@_errors.size > 0 || @_has_field_errors)
|
414
|
+
end
|
415
|
+
|
226
416
|
# Returns true if user submitted form with non-cancel submit button
|
227
417
|
# (non-nil submit param, not equal to cancel submit button value).
|
228
418
|
def accepted
|
229
|
-
raise 'form missing submit field' unless @
|
230
|
-
return params[@
|
419
|
+
raise 'form missing submit field' unless @_submit
|
420
|
+
return params[@_submit.name] != @_submit.cancel
|
231
421
|
end
|
232
422
|
|
233
423
|
# Renders error HTML block for top of form, and returns as string.
|
234
424
|
def errors_html
|
235
|
-
return nil unless has_errors
|
425
|
+
return nil unless has_errors?
|
236
426
|
|
237
|
-
@
|
427
|
+
@_errors << "Please correct the #{@_errors.empty? ? '' : 'other '}errors highlighted below." if @_has_field_errors
|
238
428
|
|
239
|
-
if @
|
240
|
-
content = @
|
429
|
+
if @_errors.size == 1
|
430
|
+
content = @_errors[0]
|
241
431
|
else
|
242
|
-
content = "<ul>" + @
|
432
|
+
content = "<ul>" + @_errors.map {|e| "<li>#{e}</li>"}.join + "</ul>"
|
243
433
|
end
|
244
434
|
|
245
|
-
@
|
435
|
+
@_errors.pop if @_has_field_errors
|
246
436
|
|
247
|
-
plural = @
|
248
|
-
%Q(<
|
437
|
+
plural = @_errors.size > 1 || @_has_field_errors ? 's' : nil
|
438
|
+
%Q(<table class="kiss_error"><tr><td>#{content}</td></tr></table>)
|
249
439
|
end
|
250
440
|
|
251
441
|
# Renders current action using form's HTML as action render content.
|
252
442
|
def render(options = {})
|
253
|
-
@
|
443
|
+
@_delegate.render options.merge(:content => html)
|
444
|
+
end
|
445
|
+
|
446
|
+
def form_name_hidden_tag_html
|
447
|
+
%Q(<input type=hidden name="form" value="#{@_name}">)
|
254
448
|
end
|
255
449
|
|
256
450
|
# Renders beginning of form (form open tag and form/field/error styles).
|
257
451
|
def html_open
|
258
|
-
@
|
259
|
-
@
|
452
|
+
@_error_class ||= 'kiss_form_error_message'
|
453
|
+
@_field_error_class ||= @_error_class
|
260
454
|
|
261
455
|
# form tag
|
262
|
-
form_attrs = ['method','enctype','class','style'].map do |attr|
|
456
|
+
form_attrs = ['id', 'method', 'enctype', 'class', 'style'].map do |attr|
|
457
|
+
next if (value = send attr).blank?
|
263
458
|
"#{attr}=\"#{send attr}\""
|
264
|
-
end
|
265
|
-
|
459
|
+
end
|
460
|
+
if @_html
|
461
|
+
@_html.each_pair do |k, v|
|
462
|
+
form_attrs.push("#{k}=\"#{v}\"")
|
463
|
+
end
|
464
|
+
end
|
465
|
+
form_tag = %Q(<form action="#{@_action}" #{form_attrs.join(' ')}>#{form_name_hidden_tag_html})
|
266
466
|
|
267
467
|
# style tag
|
268
468
|
styles = []
|
@@ -275,15 +475,16 @@ table.kiss_form {
|
|
275
475
|
vertical-align: middle;
|
276
476
|
}
|
277
477
|
.kiss_form .kiss_error {
|
478
|
+
margin-bottom: 4px;
|
479
|
+
}
|
480
|
+
.kiss_form .kiss_error td {
|
278
481
|
background-color: #ff8;
|
279
482
|
padding: 2px 4px;
|
280
483
|
line-height: 135%;
|
281
|
-
float: left;
|
282
484
|
color: #900;
|
283
485
|
border: 1px solid #ea4;
|
284
|
-
margin-bottom: 4px;
|
285
486
|
}
|
286
|
-
.kiss_form .kiss_error ul {
|
487
|
+
.kiss_form .kiss_error td ul {
|
287
488
|
padding-left: 16px;
|
288
489
|
margin: 0;
|
289
490
|
}
|
@@ -312,11 +513,34 @@ table.kiss_form {
|
|
312
513
|
.kiss_form tr.kiss_submit td.kiss_submit {
|
313
514
|
padding: 6px 3px;
|
314
515
|
}
|
315
|
-
.kiss_form input[type="text"]
|
516
|
+
.kiss_form input[type="text"],
|
517
|
+
.kiss_form input[type=password],
|
518
|
+
.kiss_form textarea {
|
316
519
|
width: 250px;
|
317
520
|
margin-left: 0;
|
318
521
|
margin-right: 0;
|
319
522
|
}
|
523
|
+
.kiss_form table.kiss_field_columns {
|
524
|
+
border-spacing: 0;
|
525
|
+
border-collapse: collapse;
|
526
|
+
width: 100%;
|
527
|
+
}
|
528
|
+
.kiss_form table.kiss_field_columns td {
|
529
|
+
padding: 0 16px 2px 0;
|
530
|
+
vertical-align: top;
|
531
|
+
}
|
532
|
+
.kiss_form .kiss_checkbox .kiss_label {
|
533
|
+
vertical-align: top;
|
534
|
+
padding-top: 3px;
|
535
|
+
}
|
536
|
+
.kiss_form .kiss_radio .kiss_label {
|
537
|
+
vertical-align: top;
|
538
|
+
padding-top: 4px;
|
539
|
+
}
|
540
|
+
.kiss_form .kiss_textarea .kiss_label {
|
541
|
+
vertical-align: top;
|
542
|
+
padding-top: 3px;
|
543
|
+
}
|
320
544
|
EOT
|
321
545
|
)
|
322
546
|
styles.push( <<-EOT
|
@@ -349,39 +573,35 @@ tr.kiss_form_error_row .kiss_form_error_message {
|
|
349
573
|
top: -2px;
|
350
574
|
margin-bottom: 0;
|
351
575
|
}
|
352
|
-
.kiss_form table.kiss_field_columns {
|
353
|
-
border-spacing: 0;
|
354
|
-
border-collapse: collapse;
|
355
|
-
}
|
356
|
-
.kiss_form table.kiss_field_columns td {
|
357
|
-
padding: 0 0 2px 0;
|
358
|
-
}
|
359
576
|
EOT
|
360
|
-
) if @
|
577
|
+
) if @_error_class == 'kiss_form_error_message'
|
361
578
|
style_tag = styles.size == 0 ? '' : "<style>" + styles.join('') + "</style>"
|
362
579
|
|
363
580
|
# combine
|
364
|
-
return %Q(#{form_tag}#{style_tag}<div class="kiss_form">#{errors_html})
|
581
|
+
return %Q(#{@_prepend_html}#{form_tag}#{style_tag}<div class="kiss_form">#{errors_html})
|
365
582
|
end
|
366
583
|
|
367
584
|
# Renders end of form (form close tag).
|
368
585
|
def html_close
|
369
|
-
|
586
|
+
"</div></form>#{@_append_html}"
|
370
587
|
end
|
371
588
|
|
372
589
|
# Renders form fields HTML.
|
373
|
-
def
|
374
|
-
@
|
375
|
-
|
376
|
-
table_html_close + table_html_open
|
377
|
-
end : field_html(field)
|
590
|
+
def components_html
|
591
|
+
@_components.map do |component|
|
592
|
+
component_html(component)
|
378
593
|
end.join
|
379
594
|
end
|
595
|
+
alias_method :fields_html, :components_html
|
380
596
|
|
381
597
|
# Renders open of form table.
|
382
598
|
def table_html_open
|
383
|
-
%Q(<table class="kiss_form" border=0 cellspacing=0>)
|
384
|
-
|
599
|
+
%Q(<table class="kiss_form" border=0 cellspacing=0>)
|
600
|
+
end
|
601
|
+
|
602
|
+
def required_legend_html
|
603
|
+
(@_has_required_fields && @_mark_required) ?
|
604
|
+
%Q( <tr><td></td><td class="kiss_help">Required fields marked by <span class="kiss_required">*</span></td></tr> ) : ''
|
385
605
|
end
|
386
606
|
|
387
607
|
# Renders close of form table.
|
@@ -391,7 +611,7 @@ EOT
|
|
391
611
|
|
392
612
|
# Renders form submit buttons.
|
393
613
|
def submit_html
|
394
|
-
@
|
614
|
+
@_submit ? field_html(@_submit) : ''
|
395
615
|
end
|
396
616
|
|
397
617
|
# Renders complete form HTML.
|
@@ -399,7 +619,8 @@ EOT
|
|
399
619
|
return [
|
400
620
|
html_open,
|
401
621
|
table_html_open,
|
402
|
-
|
622
|
+
required_legend_html,
|
623
|
+
components_html,
|
403
624
|
submit_html,
|
404
625
|
table_html_close,
|
405
626
|
html_close
|
@@ -407,18 +628,21 @@ EOT
|
|
407
628
|
end
|
408
629
|
|
409
630
|
# Renders HTML for specified form field.
|
410
|
-
def
|
631
|
+
def component_html(field)
|
632
|
+
field = fields[field.to_s] if (field.is_a?(Symbol) || field.is_a?(String))
|
633
|
+
return field.element_html if field.type == :hidden
|
634
|
+
|
411
635
|
type = field.type
|
412
636
|
prompt = field.prompt
|
413
637
|
label = field.label
|
414
638
|
errors = field.errors_html
|
415
|
-
required = field.required ? %Q(<span class="kiss_required">#{@
|
639
|
+
required = field.required ? %Q(<span class="kiss_required">#{@_mark_required}</span> ) : ''
|
416
640
|
|
417
641
|
([
|
418
642
|
prompt ? %Q(<tr class="kiss_prompt"><td class="kiss_label">#{required}</td><td>#{prompt.to_s}</td></tr>) : '',
|
419
643
|
|
420
644
|
%Q(<tr class="kiss_#{type}"><td class="kiss_label#{errors ? ' error' : ''}">),
|
421
|
-
!prompt ? (required + (label ? label.to_s + ':'
|
645
|
+
!prompt ? (required + (label.blank? ? '' : label.to_s + ':' )) : '',
|
422
646
|
%Q(</td><td class="kiss_#{type}">),
|
423
647
|
field.element_html, "</td></tr>"
|
424
648
|
] + (errors ? [
|
@@ -427,5 +651,6 @@ EOT
|
|
427
651
|
'</td></tr>'
|
428
652
|
] : [])).join
|
429
653
|
end
|
654
|
+
alias_method :field_html, :component_html
|
430
655
|
end
|
431
656
|
end
|