# encoding: utf-8 module PopulateMe module Mongo module Crushyform # This module adds to mutated models the ability # to build forms using the settings of the schema def self.included(base) base.extend(ClassMethods) end module ClassMethods def crushyform_types @crushyform_types ||= { :none => proc{''}, :string => proc do |m,c,o| if o[:autocompleted] values = o[:autocomplete_options] || m.class.collection.distinct(c) js = <<-EOJS EOJS end tag = "%s\n" % [o[:input_type]||'text', o[:input_name], o[:input_value], m.field_id_for(c), o[:input_class], o[:required]&&'required', o[:required]] "#{tag}#{js}" end, :slug => proc do |m,c,o| crushyform_types[:string].call(m,c,o) end, :price => proc do |m,c,o| crushid = m.field_id_for(c) price_main, price_cents = o[:input_value].to_i.divmod(100) out = " • " out << "" out % [o[:input_name], crushid, o[:input_class], price_main, o[:input_name], crushid, o[:input_class], "%02d" % price_cents] end, :boolean => proc do |m,c,o| crushid = m.field_id_for(c) checked = 'checked' if o[:input_value] out = "\n" out += "\n" out % [o[:input_name], crushid, o[:input_name], crushid, o[:input_class], checked] end, :text => proc do |m,c,o| "%s\n" % [o[:input_name], m.field_id_for(c), o[:input_class], o[:required]&&'required', o[:input_value], o[:required]] end, :date => proc do |m,c,o| o[:input_value] = o[:input_value].strftime("%Y-%m-%d") if o[:input_value].respond_to?(:strftime) 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] = o[:input_value].strftime("%T") if o[:input_value].respond_to?(:strftime) 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] = o[:input_value].strftime("%Y-%m-%d %T") if o[:input_value].respond_to?(:strftime) 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 = o[:parent_class].nil? ? Kernel.const_get(c.sub(/^id_/, '')) : m.resolve_class(o[:parent_class]) option_list = parent_class.to_dropdown(o[:input_value]) "\n" % [o[:input_name], m.field_id_for(c), o[:input_class], option_list] end, :children => proc do |m,c,o| children_class = o[:children_class].nil? ? Kernel.const_get(c.sub(/^ids_/, '')) : m.resolve_class(o[:children_class]) opts = o.update({ :multiple=>true, :select_options=>children_class.dropdown_cache }) @crushyform_types[:select].call(m,c,opts) end, :attachment => proc do |m,c,o| deleter = " Delete this file
" unless m.doc[c].nil? "%s%s%s\n" % [m.to_thumb(c), deleter, o[:input_name], m.field_id_for(c), o[:input_class], o[:required]] end, :select => proc do |m,c,o| out = "%s\n" % [o[:required]] end, :string_list => proc do |m,c,o| if o[:autocompleted] values = o[:autocomplete_options] || m.class.collection.distinct(c) js = <<-EOJS EOJS o[:autocompleted] = false # reset so that it does not autocomplete for :string type below end tag = @crushyform_types[:string].call(m,c,o.update({:input_value=>(o[:input_value]||[]).join(',')})) "#{tag}#{js}" end, :permalink => proc do |instance, column_name, options| values = "\n" tag = @crushyform_types[:string].call(instance, column_name, options) return tag if options[:permalink_classes].nil? options[:permalink_classes].each do |sym| c = Kernel.const_get sym entries = c.find unless entries.count==0 values << "\n" entries.each do |e| values << "\n" end values << "\n" end end "#{tag}
\n\n" end } end # What represents a required field # Can be overriden def crushyfield_required; " *"; end # Stolen from ERB def html_escape(s) s.to_s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/#{nil_name}\n") do |out, row| selected = 'selected' if row[1]==selection "%s%s%s%s" % [out, row[2], selected, row[3]] end end def dropdown_cache @dropdown_cache ||= self.find({},:fields=>['_id',label_column]).inject([]) do |out,row| out.push([row.to_label, row.id.to_s, "\n"]) end end def reset_dropdown_cache; @dropdown_cache = nil; end end # Instance Methods def crushyform(columns=model.schema.keys, action=nil, meth='POST') columns.delete('_id') fields = columns.inject(""){|out,c|out+crushyfield(c)} enctype = fields.match(/type='file'/) ? "enctype='multipart/form-data'" : '' action.nil? ? fields : "
%s
\n" % [action, meth, enctype, fields] end # crushyfield is crushyinput but with label+error def crushyfield(col, o={}) return '' if (o[:type]==:none || model.schema[col][:type]==:none) return crushyinput(col,o) if (o[:input_type]=='hidden' || model.schema[col][:input_type]=='hidden') default_field_name = col[/^id_/] ? Kernel.const_get(col.sub(/^id_/, '')).human_name : col.tr('_', ' ').capitalize field_name = o[:name] || model.schema[col][:name] || default_field_name error_list = errors_on(col).map{|e|" - #{e}"} if !errors_on(col).nil? "

%s
\n%s

\n" % [error_list&&'crushyfield-error', field_id_for(col), field_name, error_list, crushyinput(col, o)] end def crushyinput(col, o={}) o = model.schema[col].dup.update(o) o[:input_name] ||= "model[#{col}]" o[:input_value] = o[:input_value].nil? ? self[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 # Provide a thumbnail for the column def to_thumb(c) current = @doc[c] if current.respond_to?(:[]) "Thumb\n" end end # Reset dropdowns on hooks def after_save; model.reset_dropdown_cache; super; end def after_delete; model.reset_dropdown_cache; super; end # Fix types def fix_type_string_list(k,v); @doc[k] = v.to_s.strip.split(/\s*,\s*/).compact if v.is_a?(String); end end end end