Radiant CMS Database Form Extension
With a few tweaks, the Radiant CMS Mailer Extension can be turned into an extension that allows form submissions (with
pre-post validation) to save to a database; e.g.
class DatabaseFormPage < Page class DatabaseFormError < StandardError; end attr_reader :form_name, :form_conf, :form_error, :form_data, :tag_attr # Page processing. If the page has posted-back, it will try to save to contacts table # and redirect to a different page, if specified. def process(request, response) @request, @response = request, response @form_name, @form_error = nil, nil if request.post? @form_data = request.parameters[:database].to_hash # remove certain fields from hash form_data.delete("Submit") form_data.delete("ignore") @form_name = request.parameters[:form_name] @form_conf = config['form'][form_name].symbolize_keys || {} if save_form and form_conf.has_key? :success_page response.redirect( form_conf[:success_page] ) else super(request, response) end else super(request, response) end end # Save form data def save_form() begin contact = Contact.new(form_data.to_hash) contact.save rescue @form_error = "Error encountered while trying to submit form. #{$!}" return false end true end def cache? false end # DatabaseForm Tags: desc %{ This is just for creating the <r:database/> namespace. } tag "database" do |tag| tag.expand end desc %{ The <r:database:form name="X">...</r:database:form> tag is required. It should encompass all of the helper tags for creating form fields.} tag "database:form" do |tag| @tag_attr = { :class=>get_class_name('form') }.update( tag.attr.symbolize_keys ) raise_error_if_name_missing 'database:form' # Build the html form tag... results = %Q(<script src="/javascripts/validation.js" type="text/javascript"></script>) results << %Q(<form id="contact-form-id" action="#{ url }" method="post" class="#{ tag_attr[:class] }" enctype="multipart/form-data">) results << %Q(<div><input type="hidden" name="form_name" value="#{ tag_attr[:name] }" /></div>) results << %Q(<div class="database-error">#{form_error}</div>) if form_error results << tag.expand results << %Q(</form>) results << %Q(<script>new Validation('contact-form-id');</script>) end # Build tags for all of the <input /> tags... %w(text password file submit reset checkbox radio hidden).each do |type| desc %{ Renders a #{type} form control for a database form.} tag "database:#{type}" do |tag| @tag_attr = tag.attr.symbolize_keys raise_error_if_name_missing "database:#{type}" unless %(submit reset).include? type input_tag_html( type ) end end desc %{ A <select>...</select> tag. This is compatible with the <r:database:option/> tag as well. } tag 'database:select' do |tag| @tag_attr = { :id=>tag.attr['name'], :class=>get_class_name('select'), :size=>'1' }.update( tag.attr.symbolize_keys ) raise_error_if_name_missing "database:select" tag.locals.parent_tag_name = tag_attr[:name] tag.locals.parent_tag_type = 'select' results = %Q(<select name="database[#{tag_attr[:name]}]" #{add_attrs_to("")}>) results << tag.expand results << "</select>" end desc %{ A <textarea>...</textarea> tag, of course } tag 'database:textarea' do |tag| @tag_attr = { :id=>tag.attr['name'], :class=>get_class_name('textarea'), :rows=>'5', :cols=>'35' }.update( tag.attr.symbolize_keys ) raise_error_if_name_missing "database:textarea" results = %Q(<textarea name="database[#{tag_attr[:name]}]" #{add_attrs_to("")}>) results << tag.expand results << "</textarea>" end %{ Special tag for radio groups. This one works with the <r:database:option/> tag } tag 'database:radiogroup' do |tag| @tag_attr = tag.attr.symbolize_keys raise_error_if_name_missing "database:radiogroup" tag.locals.parent_tag_name = tag_attr[:name] tag.locals.parent_tag_type = 'radiogroup' tag.expand end desc %{ This is a custom tag that will render an <option/> tag if the parent is a <select>...</select> tag, or it will render an <input type="radio"/> tag if the parent is a <r:database:radiogroup>...</r:database:radiogroup> } tag 'database:option' do |tag| @tag_attr = tag.attr.symbolize_keys raise_error_if_name_missing "database:option" result = "" if tag.locals.parent_tag_type == 'select' result << %Q|<option value="#{tag_attr.delete(:value) || tag_attr[:name]}" #{add_attrs_to("")}>#{tag_attr[:name]}</option>| elsif tag.locals.parent_tag_type == 'radiogroup' tag.globals.option_count = tag.globals.option_count.nil? ? 1 : tag.globals.option_count += 1 options = tag_attr.clone.update({ :id => "#{tag.locals.parent_tag_name}_#{tag.globals.option_count}", :value => tag_attr.delete(:value) || tag_attr[:name], :name => tag.locals.parent_tag_name }) result << input_tag_html( 'radio', options ) result << %Q|<label for="#{options[:id]}">#{tag_attr[:name]}</label>| end end desc %{ This is a custom tag that will render an obfuscated email address <option> using the email.js file. Use nested <r:address>...</r:address> to specify the email address and <r:label>...</r:label> to specify what the content of the tag should be. } tag 'database:email_option' do |tag| hash = tag.locals.params = {} contents = tag.expand address = hash['address'].blank? ? contents : hash['address'] label = hash['label'] if address =~ /([\w.%-]+)@([\w.-]+)\.([A-z]{2,4})/ user, domain, tld = $1, $2, $3 tld_num = TLDS.index(tld) unless label.blank? %{<script type="text/javascript"> // <![CDATA[ mail4('#{user}', '#{domain}', #{tld_num}, "#{label}"); // ]]> </script> } else %{<script type="text/javascript"> // <![CDATA[ mail4('#{user}', '#{domain}', #{tld_num}, '#{user}'); // ]]> </script> } end end end tag "database:email_option:label" do |tag| tag.locals.params['label'] = tag.expand.strip end tag "database:email_option:address" do |tag| tag.locals.params['address'] = tag.expand.strip end desc %{ For use with email template parts -- retrieves the data posted by the form } tag 'database:get' do |tag| name = tag.attr['name'] if name form_data[name].is_a?(Array) ? form_data[name].to_sentence : form_data[name] else form_data.to_hash.to_yaml.to_s end end protected # Since several form tags use the <input type="X" /> format, let's do that work in one place def input_tag_html(type, opts=tag_attr) options = { :id => tag_attr[:name], :value => "", :class=>get_class_name(type) }.update(opts) results = %Q(<input type="#{type}" ) results << %Q(name="database[#{options[:name]}]" ) if tag_attr[:name] results << "#{add_attrs_to("", options)}/>" end def add_attrs_to(results, tag_attrs=tag_attr) # Well, turns out I stringify the keys so I can sort them so I can test the tag output tag_attrs.stringify_keys.sort.each do |name, value| results << %Q(#{name.to_s}="#{value.to_s}" ) unless name == 'name' end results end # Get the default css class based on type def get_class_name(type, class_name=nil) class_name = 'database-form' if class_name.nil? and %(form).include? type class_name = 'database-field' if class_name.nil? and %(text password file select textarea).include? type class_name = 'database-button' if class_name.nil? and %(submit reset).include? type class_name = 'database-option' if class_name.nil? and %(checkbox radio).include? type class_name end # Raises a 'name missing' tag error def raise_name_error(tag_name) raise DatabaseFormTagError.new( "`#{tag_name}' tag requires a `name' attribute" ) end def raise_error_if_name_missing(tag_name) raise_name_error( tag_name ) if tag_attr[:name].nil? or tag_attr[:name].empty? end end
I
don’t like how it is hardcoded to use the contacts table. Any thoughts/ideas
on how to make the table more of dynamic, runtime setting? As you can imagine,
Contact is simply:
class Contact < ActiveRecord::Base end




