-
Notifications
You must be signed in to change notification settings - Fork 0
/
sequel_crushyform.rb
162 lines (159 loc) · 8.48 KB
/
sequel_crushyform.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
module ::Sequel::Plugins::Crushyform
module ClassMethods
def crushyform_version; [0,1,0]; end
# Schema
def crushyform_schema
@crushyform_schema ||= default_crushyform_schema
end
def default_crushyform_schema
out = {}
db_schema.each do |k,v|
out[k] = if v[:db_type]=='text'
{:type=>:text}
else
{:type=>v[:type]}
end
end
association_reflections.each{|k,v|out[v[:key]]={:type=>:parent} if v[:type]==:many_to_one}
@schema.columns.each{|c|out[c[:name]]=out[c[:name]].update(c[:crushyform]) if c.has_key?(:crushyform)} if respond_to?(:schema)
out
end
# Types
def crushyform_types
@crushyform_types ||= {
:none => proc{''},
:string => proc do |m,c,o|
"<input type='%s' name='%s' value=\"%s\" id='%s' class='%s' %s />%s\n" % [o[:input_type]||'text', o[:input_name], o[:input_value], m.crushyid_for(c), o[:input_class], o[:required]&&'required', o[:required]]
end,
:boolean => proc do |m,c,o|
crushid = m.crushyid_for(c)
s = ['checked', nil]
s.reverse! unless o[:input_value]
out = "<span class='%s'>"
out += "<input type='radio' name='%s' value='true' id='%s' %s /> <label for='%s'>Yes</label> "
out += "<input type='radio' name='%s' value='false' id='%s-no' %s /> <label for='%s-no'>No</label>"
out += "</span>\n"
out % [o[:input_class], o[:input_name], crushid, s[0], crushid, o[:input_name], crushid, s[1], crushid]
end,
:text => proc do |m,c,o|
"<textarea name='%s' id='%s' class='%s' %s>%s</textarea>%s\n" % [o[:input_name], m.crushyid_for(c), o[:input_class], o[:required]&&'required', o[:input_value], o[:required]]
end,
:date => proc do |m,c,o|
o[:input_value] = "%s-%s-%s" % [o[:input_value].year, o[:input_value].month, o[:input_value].day] if o[:input_value].is_a?(Sequel.datetime_class)
o[:required] = "%s Format: yyyy-mm-dd" % [o[:required]]
crushyform_types[:string].call(m,c,o)
end,
:time => proc do |m,c,o|
o[:input_value] = "%s:%s:%s" % [o[:input_value].hour, o[:input_value].min, o[:input_value].sec] if o[:input_value].is_a?(Sequel.datetime_class)
o[:required] = "%s Format: hh:mm:ss" % [o[:required]]
crushyform_types[:string].call(m,c,o)
end,
:datetime => proc do |m,c,o|
o[:input_value] = "%s-%s-%s %s:%s:%s" % [o[:input_value].year, o[:input_value].month, o[:input_value].day, o[:input_value].hour, o[:input_value].min, o[:input_value].sec] if o[:input_value].is_a?(Sequel.datetime_class)
o[:required] = "%s Format: yyyy-mm-dd hh:mm:ss" % [o[:required]]
crushyform_types[:string].call(m,c,o)
end,
:parent => proc do |m,c,o|
parent_class = association_reflection(c.to_s.sub(/_id$/,'').to_sym).associated_class
option_list = parent_class.to_dropdown(o[:input_value])
"<select name='%s' id='%s' class='%s'>%s</select>\n" % [o[:input_name], m.crushyid_for(c), o[:input_class], option_list]
end,
:attachment => proc do |m,c,o|
"%s<input type='file' name='%s' id='%s' class='%s' />%s\n" % [m.to_thumb(c), o[:input_name], m.crushyid_for(c), o[:input_class], o[:required]]
end,
:select => proc do |m,c,o|
out = "<select name='%s' id='%s' class='%s'>\n" % [o[:input_name], m.crushyid_for(c), o[:input_class]]
o[:select_options] = m.__send__(o[:select_options]) unless o[:select_options].kind_of?(Array)
if o[:select_options].kind_of?(Array)
o[:select_options].each do |op|
key,val = op.kind_of?(Array) ? [op[0],op[1]] : [op,op]
selected = 'selected' if val==o[:input_value]
out << "<option value='%s' %s>%s</option>\n" % [val,selected,key]
end
end
out << "</select>%s\n" % [o[:required]]
end
}
end
# What represents a required field
# Can be overriden
def crushyfield_required; "<span class='crushyfield-required'> *</span>"; end
# Stolen from ERB
def html_escape(s)
s.to_s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/</, "<")
end
# Cache dropdown options for children classes to use
# Meant to be reseted each time an entry is created, updated or destroyed
# So it is only rebuild once required after the list has changed
# Maintaining an array and not rebuilding it all might be faster
# But it will not happen much so that it is fairly acceptable
def to_dropdown(selection=nil, nil_name='** UNDEFINED **')
dropdown_cache.inject("<option value=''>#{nil_name}</option>\n") do |out, row|
selected = 'selected' if row[0]==selection
"%s%s%s%s" % [out, row[1], selected, row[2]]
end
end
def dropdown_cache
@dropdown_cache ||= label_dataset.inject([]) do |out,row|
out.push([row.id, "<option value='#{row.id}' ", ">#{row.to_label}</option>\n"])
end
end
def reset_dropdown_cache; @dropdown_cache = nil; end
# Generic column names for label
LABEL_COLUMNS = [:title, :label, :fullname, :full_name, :surname, :lastname, :last_name, :name, :firstname, :first_name, :caption, :reference, :file_name, :body]
# Column used as a label
def label_column; @label_column ||= LABEL_COLUMNS.find{|c|columns.include?(c)}; end
def label_column=(n); @label_column=n; end
# Dataset selecting only columns used for building names
def label_dataset; select(:id, label_column); end
# Human readable name
def human_name; self.name.gsub(/([A-Z]+)([A-Z][a-z])/,'\1 \2').gsub(/([a-z\d])([A-Z])/,'\1 \2'); end
end
module InstanceMethods
def crushyform(columns=model.crushyform_schema.keys, action=nil, meth='POST')
columns.delete(:id)
fields = columns.inject(""){|out,c|out+crushyfield(c.to_sym)}
enctype = fields.match(/type='file'/) ? "enctype='multipart/form-data'" : ''
action.nil? ? fields : "<form action='%s' method='%s' %s>%s</form>\n" % [action, meth, enctype, fields]
end
# crushyfield is crushyinput but with label+error
def crushyfield(col, o={})
return '' if (o[:type]==:none || model.crushyform_schema[col][:type]==:none)
field_name = o[:name] || model.crushyform_schema[col][:name] || col.to_s.sub(/_id$/, '').tr('_', ' ').capitalize
error_list = errors.on(col).map{|e|" - #{e}"} if !errors.on(col).nil?
"<p class='crushyfield %s'><label for='%s'>%s</label><span class='crushyfield-error-list'>%s</span><br />\n%s</p>\n" % [error_list&&'crushyfield-error', crushyid_for(col), field_name, error_list, crushyinput(col, o)]
end
def crushyinput(col, o={})
o = model.crushyform_schema[col].dup.update(o)
o[:input_name] ||= "model[#{col}]"
o[:input_value] = o[:input_value].nil? ? self.__send__(col) : o[:input_value]
o[:input_value] = model.html_escape(o[:input_value]) if (o[:input_value].is_a?(String) && o[:html_escape]!=false)
o[:required] = o[:required]==true ? model.crushyfield_required : o[:required]
crushyform_type = model.crushyform_types[o[:type]] || model.crushyform_types[:string]
crushyform_type.call(self,col,o)
end
# This ID is used to have a unique reference for the input field.
#
# Format: <id>-<class>-<column>
#
# If you plan to have more than one form for a new entry in the same page
# you'll have to override this method because records without an id
# have just 'new' as a prefix.
# Which means there could be a colision.
def crushyid_for(col); "%s-%s-%s" % [id||'new',self.class.name,col]; end
# Used to determine a humanly readable representation of the entry on one line of text
def to_label; model.label_column.nil?||self.new? ? [self.new? ? 'New' : nil, model.human_name, id].compact!.join(' ') : self.__send__(model.label_column).to_s.tr("\n\r", ' '); end
# Provide a thumbnail for the column
def to_thumb(c)
current = self.__send__(c)
if model.respond_to?(:stash_reflection) && model.stash_reflection.key?(c)
!current.nil? && current[:type][/^image\//] ? "<img src='#{file_url(c, 'stash_thumb.gif')}?#{::Time.now.to_i.to_s}' /><br />\n" : ''
else
"<img src='#{current}?#{::Time.now.to_i.to_s}' width='100' onerror=\"this.style.display='none'\" />\n"
end
end
# Reset dropdowns on hooks
def after_save; model.reset_dropdown_cache; super; end
def after_destroy; model.reset_dropdown_cache; super; end
end
end