Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tag: 0.0.8
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 150 lines (147 sloc) 8.041 kb
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
module ::Sequel::Plugins::Crushyform
  
  module ClassMethods
    def crushyform_version; [0,0,8]; 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
      @schema.columns.each{|c|out[c[:name]]=out[c[:name]].update(c[:crushyform]) if c.has_key?(:crushyform)} if respond_to?(:schema)
      association_reflections.each{|k,v|out[v[:key]]={:type=>:parent} if v[:type]==:many_to_one}
      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
      }
    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(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;")
    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
Something went wrong with that request. Please try again.