kiss 1.1 → 1.7
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/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
|