module Formize # Permits to not quote text in inspect method class Code < String def inspect self.to_s end end class Generator attr_accessor :form, :elements, :record_name, :partial, :controller def initialize(form, controller) @form = form @controller = controller @record_name = @form.record_name @elements = form.all_elements @partials = @elements.select{|e| !e.depend_on.nil? or e.options[:new]} end # Generates the controller method from a form object def controller_code code = "def #{form.controller_method_name}\n" # Mono_choice search/filter items = form.mono_choices if items.size > 0 code << " if params[:unroll]\n" events = form.mono_choices.collect do |mono_choice| event = "if params[:unroll] == '#{mono_choice.html_id}'\n" for depended in mono_choice.dependeds df = form.fields[depended[:name]] event << " #{df.name} = " << (df.reflection.nil? ? "params[:#{df.input_id}]" : "#{df.reflection.class_name}.find_by_id(params[:#{df.input_id}])") << "\n" # locals[df.name.to_sym] = Code.new(df.name) end event << mono_choice_search_code(mono_choice).strip.gsub(/^/, ' ') << "\n" event << "end\n" end code << events.collect{|e| e.gsub(/^/, ' ')}.join code << " end\n" end # Dependencies refresh items = @partials if items.size > 0 code << " if params[:refresh]\n" code << " #{record_name} = #{form.model.name}.find(params[:id]) if params[:id].to_i > 0\n" code << " #{record_name} ||= #{form.model.name}.new\n" code << " @#{record_name} = #{record_name}\n" events = items.collect do |dependent| event = "if params[:refresh] == '#{dependent.html_id}'\n" locals = {record_name.to_sym => Code.new(record_name)} for depended in dependent.dependeds df = form.fields[depended[:name]] event << " #{record_name}.#{df.name} = " << (df.reflection.nil? ? "params[:#{df.input_id}]" : "#{df.reflection.class_name}.find_by_id(params[:#{df.input_id}])") << "\n" # locals[df.name.to_sym] = Code.new(df.name) end event << " render(:inline=>'<%=#{dependent.prototype}-%>', :locals=>#{locals.inspect})\n" event << "end\n" end code << events.collect{|e| e.gsub(/^/, ' ')}.join code << " end\n" end # End code << "end\n" code.gsub!(/end\s*if/, 'elsif') # raise code # list = code.split("\n"); list.each_index{|x| puts((x+1).to_s.rjust(4)+": "+list[x])} return code end # Generates the view method from a form object def view_code code = "" varh = 'html' # Build view methods assimilated to partials for element in @partials code << "# #{element.class.name}: #{element.html_id}/#{element.name}\n" code << view_method_code(element, varh) end code << "\n" code << "def #{form.options[:view_fields_method_name]}(#{form.record_name}=nil)\n" code << " #{form.record_name} = @#{form.record_name} unless #{form.record_name}.is_a?(#{form.model.name})\n" code << " #{varh} = ''\n" for element in form.elements code << view_method_call(element, varh).strip.gsub(/^/, ' ') << "\n" # # code << " #{varh} << " << element.method_call_code.gsub(/^/, ' ').strip << "\n" end code << " return #{varh}\n" code << "end\n" code << "\n" code << "def #{form.options[:view_form_method_name]}(#{form.record_name}=nil)\n" code << " #{form.record_name} = @#{form.record_name} unless #{form.record_name}.is_a?(#{form.model.name})\n" code << " return form_for(#{form.record_name}) do\n" code << " #{form.options[:view_fields_method_name]}(#{form.record_name})\n" code << " end\n" code << "end\n" # raise code # list = code.split("\n"); list.each_index{|x| puts((x+1).to_s.rjust(4)+": "+list[x])} return code end def view_partial_code(element, varh='varh') # send("#{element.class.name.split('::')[-1].underscore}_#{__method__}", element, varh) special_method = "#{element.class.name.split('::')[-1].underscore}_#{__method__}".to_sym code = "" partial_code = send(special_method, element, varh) dependeds = element.dependeds.collect{|d| d[:name]} if dependeds.size > 0 for depended in dependeds depended_field = form.fields[depended] code << "#{depended_field.name} = #{form.record_name}.#{depended_field.name}\n" if depended_field.reflection code << "#{depended_field.name} ||= #{field_datasource(depended_field)}.first\n" end end code << "if #{dependeds.join(' and ')}\n" code << partial_code.strip.gsub(/^/, ' ') << "\n" code << "else\n" code << " #{varh} << content_tag(:div, '', #{wrapper_attrs(element).inspect})\n" code << "end\n" else code = partial_code end return code end def view_method_code(element, varh='varh') # send("#{element.class.name.split('::')[-1].underscore}_#{__method__}", element, varh) special_method = "#{element.class.name.split('::')[-1].underscore}_#{__method__}".to_sym return send(special_method, element, varh) if self.respond_to?(special_method) code = "def #{element.prototype}\n" code << " #{varh} = ''\n" code << view_partial_code(element, varh).strip.gsub(/^/, ' ') << "\n" code << " return #{varh}\n" code << "end\n" return code end def view_method_call(element, varh='varh') special_method = "#{element.class.name.split('::')[-1].underscore}_#{__method__}".to_sym return send(special_method, element, varh) if self.respond_to?(special_method) if @partials.include?(element) return "#{varh} << #{element.prototype}\n" else return view_partial_code(element, varh).strip << "\n" end end def wrapper_attrs(element) html_options = (element.respond_to?(:html_options) ? element.html_options : {}) html_options[:id] = element.html_id if @partials.include?(element) url = {:controller => controller.controller_name.to_sym, :action=>form.action_name.to_sym, :refresh=>element.html_id} for depended in element.dependeds df = form.fields[depended[:name]] url[df.input_id.to_sym] = Code.new(df.reflection.nil? ? df.name : "#{df.name}.id") end html_options["data-refresh"] = Code.new("url_for(#{url.inspect})") end special_method = "#{element.class.name.split('::')[-1].underscore}_#{__method__}".to_sym return send(special_method, element, html_options) if self.respond_to?(special_method) return html_options end ##################################################################################### # F I E L D _ S E T M E T H O D S # ##################################################################################### def field_set_view_partial_code(field_set, varh='varh') # Initialize html attributes html_options = wrapper_attrs(field_set) varc = field_set.html_id # "field_set_#{field_set.html_id}" code = "#{varh} << hard_content_tag(:fieldset, #{html_options.inspect}) do |#{varc}|\n" unless field_set.title.nil? code << " #{varc} << content_tag(:legend, ::I18n.translate('labels.#{field_set.title}'))\n" end for child in field_set.children code << view_method_call(child, varc).strip.gsub(/^/, ' ') << "\n" end code << "end\n" return code end # def field_set_view_method_code(field_set, varh='html') # code = "def #{field_set.prototype}\n" # code << " #{varh} = ''\n" # code << field_set_view_partial_code(field_set, varh).strip.gsub(/^/, ' ') << "\n" # code << " return #{varh}\n" # code << "end\n" # return code # end # def field_set_view_method_call(field_set, varh='varh') # code = "" # call = if @partials.include?(field_set) # "#{varh} << #{field_set.prototype}\n" # else # field_set_view_partial_code(field_set, varh).strip << "\n" # end # if field_set.depend_on # depended_field = form.fields[field_set.depend_on] # code << "#{field_set.depend_on} = #{form.record_name}.#{depended_field.name}\n" # if ref = depended_field.reflection # code << "#{field_set.depend_on} ||= #{field_datasource(depended_field)}.first\n" # end # code << "if #{field_set.depend_on}\n" # code << call.strip.gsub(/^/, ' ') << "\n" # code << "else\n" # opt = {:id=>field_set.html_id, :class=>"waiting", "data-refresh"=>Code.new("url_for(:controller=>:#{controller.controller_name}, :action=>:#{form.action_name}, :refresh=>'#{field_set.html_id}')")} # code << " #{varh} << tag(:div, #{opt.inspect})\n" # code << "end\n" # else # code = call # end # return code # end ##################################################################################### # F I E L D M E T H O D S # ##################################################################################### def field_view_partial_code(field, varh='varh') input_attrs = (field.options[:input_options].is_a?(Hash) ? field.options[:input_options] : {}) deps = form.dependents_on(field) if deps.size > 0 input_attrs["data-dependents"] = deps.collect{|d| d.html_id}.join(',') end # Initialize html attributes html_options = wrapper_attrs(field) varc = field.html_id code = "#{varh} << hard_content_tag(:div, #{html_options.inspect}) do |#{varc}|\n" code << " #{varc} << label(:#{form.record_name}, :#{field.name}, nil, :class=>'attr')\n" code << " #{form.record_name}.#{field.name} ||= #{field.default.inspect}\n" if field.default code << self.send("field_#{field.type}_input", field, input_attrs, varc).strip.gsub(/^/, ' ') << "\n" code << "end\n" return code end # def field_view_method_code(field, varh='html') # code = "def #{field.prototype}\n" # code << " #{varh} = ''\n" # code << field_view_partial_code(field, varh).strip.gsub(/^/, ' ') << "\n" # code << " return #{varh}\n" # code << "end\n" # return code # end # def field_view_method_call(field, varh='varh') # code = "" # call = if @partials.include?(field) # "#{varh} << #{field.prototype}\n" # else # field_view_partial_code(field, varh).strip << "\n" # end # if field.depend_on # depended_field = form.fields[field.depend_on] # code << "#{field.depend_on} = #{form.record_name}.#{depended_field.name}\n" # code << "if #{field.depend_on}\n" # code << call.strip.gsub(/^/, ' ') << "\n" # code << "else\n" # attrs = field_wrapper_attrs(field) # attrs[:class] = "#{attrs[:class]} waiting".strip # code << " #{varh} << tag(:div, #{attrs.inspect})\n" # code << "end\n" # else # code = call # end # return code # end def field_datasource(field) source = field.source source = Formize.default_source unless [Array, String, Symbol].include?(source.class) if source.is_a?(Array) return "#{source[0]}.#{field.choices}" elsif source == :foreign_class return "#{field.reflection.class_name}.#{field.choices}" elsif source == :class return "#{form.model.name}.#{field.choices}" else return "#{source}.#{field.choices}" end end def field_datasource_class_name(field) source = field.source source = Formize.default_source unless [Array, String, Symbol].include?(source.class) if source.is_a?(Array) return source[1] elsif source == :foreign_class return field.reflection.class_name elsif source == :class return form.model.name else return source.to_s.classify end end def field_input_options(field) end def field_wrapper_attrs(field, html_options={}) html_options[:class] = "field #{html_options[:class]}".strip html_options[:class] = "#{html_options[:class]} #{field.type.to_s.gsub('_', '-')}".strip html_options[:class] = "#{html_options[:class]} required".strip if field.required return html_options end def field_check_box_input(field, attrs={}, varc='varc') return "#{varc} << check_box(:#{field.record_name}, :#{field.method}, #{attrs.inspect})\n" end def field_choice_input(field, attrs={}, varc='varc') code = if field.choices.size <= Formize.radio_count_max field_radio_input(field, attrs, varc) else field_select_input(field, attrs, varc) end return code end def field_date_input(field, attrs={}, varc='varc') attrs[:size] ||= 16 return "#{varc} << date_field(:#{field.record_name}, :#{field.method}, #{attrs.inspect})\n" end def field_datetime_input(field, attrs={}, varc='varc') attrs[:size] ||= 16 return "#{varc} << datetime_field(:#{field.record_name}, :#{field.method}, #{attrs.inspect})\n" end def field_label_input(field, attrs={}, varc='varc') attrs[:class] = (attrs[:class]+" readonly").strip return "#{varc} << content_tag(:span, @:#{field.record_name}.#{field.method}, #{attrs.inspect})\n" end def field_numeric_input(field, attrs={}, varc='varc') attrs[:size] ||= 16 return "#{varc} << text_field(:#{field.record_name}, :#{field.method}, #{attrs.inspect})\n" end def field_password_input(field, attrs={}, varc='varc') attrs[:size] ||= 24 return "#{varc} << password_field(:#{field.record_name}, :#{field.method}, #{attrs.inspect})\n" end def field_radio_input(field, attrs={}, varc='varc') return "#{varc} << radio(:#{field.record_name}, :#{field.method}, #{field.choices.inspect}, #{attrs.inspect})\n" # return "#{varc} << " << field.choices.collect{|x| "content_tag(:span, radio_button(:#{field.record_name}, :#{field.method}, #{x[1].inspect}) << ' ' << content_tag(:label, #{x[0].inspect}, :for=>'#{field.input_id}_#{x[1]}'), :class=>'rad')"}.join(" << ") << "\n" end def field_select_input(field, attrs={}, varc='varc') if (include_blank = attrs.delete(:include_blank)).is_a? String field.choices.insert(0, [include_blank, '']) end return "#{varc} << select(:#{field.record_name}, :#{field.method}), #{field.choices.inspect}, #{attrs.inspect})\n" end def field_mono_choice_input(field, attrs={}, varc='varc') source_model = field_datasource_class_name(field).constantize reflection = source_model.reflections[field.choices] if reflection.nil? raise Exception.new("#{source_model.name} must have a reflection :#{field.choices}.") end count = "#{field.choices}_count" select_first_if_empty = " #{record_name}.#{field.name} ||= #{field_datasource(field)}.first\n" code = "#{count} = #{field_datasource(field)}.count\n" code << "if (#{count} == 0)\n" code << field_mono_select_input(field, attrs, varc).strip.gsub(/^/, ' ') << "\n" code << "elsif (#{count} <= #{Formize.radio_count_max})\n" code << select_first_if_empty code << field_mono_radio_input(field, attrs, varc).strip.gsub(/^/, ' ') << "\n" if reflection.options[:finder_sql].nil? code << "elsif (#{count} <= #{Formize.select_count_max})\n" code << select_first_if_empty code << field_mono_select_input(field, attrs, varc).strip.gsub(/^/, ' ') << "\n" code << "else\n" code << select_first_if_empty code << field_mono_unroll_input(field, attrs, varc).strip.gsub(/^/, ' ') << "\n" else code << "else\n" code << select_first_if_empty code << field_mono_select_input(field, attrs, varc).strip.gsub(/^/, ' ') << "\n" end code << "end\n" new_item_url = field.options.delete(:new) if new_item_url.is_a? Symbol new_item_url = {:controller=>new_item_url.to_s.pluralize.to_sym} elsif new_item_url.is_a? TrueClass new_item_url = {} end if new_item_url.is_a?(Hash) for k, v in new_item_url new_item_url[k] = Code.new(v) if v.is_a?(String) end edit_item_url = {} unless edit_item_url.is_a? Hash if field.method.to_s.match(/_id$/) and refl = form.model.reflections[field.method.to_s[0..-4].to_sym] new_item_url[:controller] ||= refl.class_name.underscore.pluralize edit_item_url[:controller] ||= new_item_url[:controller] end new_item_url[:action] ||= :new edit_item_url[:action] ||= :edit data = field.options.delete(:update)||field.html_id html_options = {"data-add-item"=>data, :class=>"icon im-new"} code << "#{varc} << content_tag(:span, content_tag(:span, link_to(tg(:new), #{new_item_url.inspect}, #{html_options.inspect}).html_safe, :class=>:tool).html_safe, :class=>\"toolbar mini-toolbar\") if authorized?(#{new_item_url.inspect})\n" end return code end def field_mono_radio_input(field, attrs={}, varc='varc') return "#{varc} << radio(:#{field.record_name}, :#{field.method}, #{field_datasource(field)}.collect{|item| [item.#{field.item_label}, item.id]}, {}, #{attrs.inspect})" end def field_mono_select_input(field, attrs={}, varc='varc') return "#{varc} << select(:#{field.record_name}, :#{field.method}, #{field_datasource(field)}.collect{|item| [item.#{field.item_label}, item.id]}, {}, #{attrs.inspect})" end def field_mono_unroll_input(field, attrs={}, varc='varc') options = {} options[:label] ||= Code.new("Proc.new{|r| \"#{mono_choice_label(field, 'r')}\"}") url = {:controller=>controller.controller_name, :action=>form.action_name, :unroll=>field.html_id} for depended in field.dependeds df = form.fields[depended[:name]] url[df.input_id.to_sym] = Code.new(df.reflection.nil? ? df.name : "#{df.name}.id") end return "#{varc} << unroll(:#{field.record_name}, :#{field.method}, url_for(#{url.inspect}), #{options.inspect}, #{attrs.inspect})" end def field_string_input(field, attrs={}, varc='varc') attrs[:size] ||= 24 if field.column and !field.column.limit.nil? attrs[:size] = field.column.limit if field.column.limit1 # query << ')' else raise Exception.new("First element of an Array can only be String or Symbol.") end end select = (model.table_name+".id AS id, "+attributes_hash.collect{|k,v| v+" AS "+k}.join(", ")).inspect joins = options[:joins] ? ", :joins=>"+options[:joins].inspect : "" code = "" code << "conditions = [#{query.join(' AND ').inspect+parameters}]\n" code << "search = params[:search]\n" code << "words = search.lower.split(/[\\s\\,]+/)\n" code << "if words.size > 0\n" code << " conditions[0] << '#{' AND ' if query.size>0}('\n" code << " words.each_index do |index|\n" code << " word = words[index].to_s\n" code << " conditions[0] << ') AND (' if index > 0\n" if ActiveRecord::Base.connection.adapter_name == "MySQL" code << " conditions[0] << "+attributes.collect{|key| "LOWER(CAST(#{key[0]} AS CHAR)) LIKE ?"}.join(' OR ').inspect+"\n" else code << " conditions[0] << "+attributes.collect{|key| "LOWER(CAST(#{key[0]} AS VARCHAR)) LIKE ?"}.join(' OR ').inspect+"\n" end code << " conditions += ["+attributes.collect{|key| key[1].inspect.gsub('X', '"+word+"').gsub(/(^\"\"\+|\+\"\"\+|\+\"\")/, '')}.join(", ")+"]\n" code << " end\n" code << " conditions[0] << ')'\n" code << "end\n" order = ", :order=>"+attributes.collect{|key| "#{key[0]} ASC"}.join(', ').inspect limit = ", :limit=>"+(options[:limit]||12).to_s partial = options[:partial] html = "' code << "#{foreign_records} = #{field_datasource(field)}.find(:all, :conditions=>conditions"+joins+order+limit+")\n" code << "render :inline=>#{html.inspect}, :locals=>{:#{foreign_records}=>#{foreign_records}, :search=>search}\n" return code end end end