From 80b41392c7a7d5a4399f1a252193dac6abe25391 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Thu, 12 May 2016 01:34:23 -0700 Subject: [PATCH 01/16] Update BlueForm to HTML5 and add 'arrangements'. Created a new "tag" method which creates all the tags. The earlier methods, such as input_text, et.al., now just call "tag". Also added an "arrangement" option to allow the label-control fields to be wrapped in paragraphs (the default), or a table for nicer formatting, or no wrapping at all. --- lib/ramaze/helper/blue_form.rb | 895 ++++++++++++++++++++++----------- 1 file changed, 606 insertions(+), 289 deletions(-) diff --git a/lib/ramaze/helper/blue_form.rb b/lib/ramaze/helper/blue_form.rb index 5560be1e..0dd2b4c2 100644 --- a/lib/ramaze/helper/blue_form.rb +++ b/lib/ramaze/helper/blue_form.rb @@ -23,6 +23,7 @@ module Helper # # form_for(@data, :method => :post) do |f| # f.input_text 'Username', :username + # f.input_password 'Password', :password # end # # The object comes handy when you want to do server-side form validation: @@ -82,15 +83,29 @@ module BlueForm # called using a block and it's return value should be manually sent to # the browser (since it does not echo the value). # - # @param [Object] form_values Object containing the values for each form - # field. + # @param [Object] form_object Object containing the values for each form + # field. If the object contains a hash of the form {:field=>"error"} it + # will be used to generate error messages in the BlueForm output. # @param [Hash] options Hash containing any additional form attributes - # such as the method, action, enctype and so on. + # such as the method, action, enctype and so on. To choose an + # arrangement of paragraph, table, or none, use + # :arrangement=>:paragraph, et.al. # @param [Block] block Block containing the elements of the form such as # password fields, textareas and so on. # - def form_for(form_values, options = {}, &block) - form = Form.new(form_values, options) + def form_for(form_object, options = {}, &block) + form = Form.new(form_object, options) + case + when form_object.nil? + # There is no form object, therefore, no errors + when !form_object.respond_to?(:errors) + # There is a form object, but it has no errors field + when !form_object.errors.is_a?(Hash) + # There is an errors object, but it's not a Hash so ignore it + else + # There is a form object, and it has a Hash errors field + form_errors.merge!(form_object.errors) + end form.build(form_errors, &block) form end @@ -145,6 +160,47 @@ def form_errors_from_model(obj) end end + ## + # Class BlueFormModel contains a mass copy method like Sequel's that + # can be used to create objects for 'form_for' that are not + # database models, but have this one mass assignment + # method in them. + # + # @example + # class Login < BlueFormModel + # :attr_accessor :username, :password, :confirm + # end + # + # login = Login.new + # login.set_fields(session.request.params, [:username,:password,:confirm]) + # + # @caveat + # Any use of 'BlueFormModel' must FOLLOW the 'helper :blue_form' + # statement in your code (which loads it). + # + class BlueFormModel + attr_accessor :errors + def initialize + @errors = {} + end + def valid? + @errors.empty? + end + def set_fields(hash, fields, opts={}) + fields.each do |f| + if hash.has_key?(f) + instance_variable_set("@#{f}", hash[f]) + elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s) + instance_variable_set("@#{sf}", hash[sf]) + else + raise NoMethodError.new("undefined method `#{f.to_s}=' for #{self.inspect}") \ + if opts[:missing]!=:skip + end + end + self + end + end + ## # Main form class that contains all the required methods to generate form # specific tags, such as textareas and select boxes. Do note that this @@ -153,22 +209,31 @@ def form_errors_from_model(obj) # class Form attr_reader :g - attr_reader :form_values + attr_reader :form_object ## # Constructor method that generates an instance of the Form class. # - # @param [Object] form_values Object containing the values for each form + # @param [Object] form_object Object containing the values for each form # field. # @param [Hash] options A hash containing any additional form attributes. # @return [Object] An instance of the Form class. # - def initialize(form_values, options) - @form_values = form_values + def initialize(form_object, options) + @form_object = form_object + @arrangement = options.delete(:arrangement) + @arrangement = :paragraph if ([:table,:paragraph,:none].index(@arrangement)).nil? @form_args = options.dup @g = Gestalt.new end + ## + # Placeholder when no wrapper tag is used + # + def nul(*args) + yield + end + ## # Builds the form by generating the opening/closing tags and executing # the methods in the block. @@ -181,16 +246,31 @@ def build(form_errors = {}) @form_errors = {} form_errors.each do |key, value| - if value.respond_to?(:first) - value = value.first - end - + value = value.first if value.respond_to?(:first) @form_errors[key.to_s] = value end @g.form(@form_args) do if block_given? - yield self + case @arrangement + when :paragraph + @table_wrapper = self.method('nul') + @paragraph_wrapper = @g.method('p') + @label_wrapper = self.method('nul') + @input_wrapper = self.method('nul') + when :table + @table_wrapper = @g.method('table') + @paragraph_wrapper = @g.method('tr') + @label_wrapper = @g.method('th') + @input_wrapper = @g.method('td') + when :none + @table_wrapper = self.method('nul') + @paragraph_wrapper = self.method('nul') + @label_wrapper = self.method('nul') + @input_wrapper = self.method('nul') + end + @hidden_wrapper = self.method('nul') + @table_wrapper.call { yield(self) } end end end @@ -224,87 +304,22 @@ def fieldset(&block) end ## - # Generate an input tag with a type of "text" along with a label tag. - # This method also has the alias "text" so feel free to use that one - # instead of input_text. - # - # @param [String] label The text to display inside the label tag. - # @param [String Symbol] name The name of the text field. - # @param [Hash] args Any additional HTML attributes along with their - # values. - # @example - # form_for(@data, :method => :post) do |f| - # f.input_text 'Username', :username - # end - # - def input_text(label, name, args = {}) - # The ID can come from 2 places, id_for and the args hash - id = args[:id] ? args[:id] : id_for(name) - args = args.merge(:type => :text, :name => name, :id => id) - - if !args[:value] and @form_values.respond_to?(name) - args[:value] = @form_values.send(name) - end - - @g.p do - label_for(id, label, name) - @g.input(args) - end - end - alias text input_text - - ## - # Generate an input tag with a type of "password" along with a label. - # Password fields are pretty much the same as text fields except that - # the content of these fields is replaced with dots. This method has the - # following alias: "password". - # - # @param [String] label The text to display inside the label tag. - # @param [String Symbol] name The name of the password field. - # @param [Hash] args Any additional HTML attributes along with their - # values. - # @example - # form_for(@data, :method => :post) do |f| - # f.input_password 'My password', :password - # end - # - def input_password(label, name, args = {}) - # The ID can come from 2 places, id_for and the args hash - id = args[:id] ? args[:id] : id_for(name) - args = args.merge(:type => :password, :name => name, :id => id) - - if !args[:value] and @form_values.respond_to?(name) - args[:value] = @form_values.send(name) - end - - @g.p do - label_for(id, label, name) - @g.input(args) - end - end - alias password input_password - - ## - # Generate a submit tag (without a label). A submit tag is a button that - # once it's clicked will send the form data to the server. + # Generate a button tag (without a label). A button tag is a button that + # once it's clicked will call a javascript function. # # @param [String] value The text to display in the button. # @param [Hash] args Any additional HTML attributes along with their # values. # @example # form_for(@data, :method => :post) do |f| - # f.input_submit 'Save' + # f.input_button 'Press', :onclick=>"msg()" # end # - def input_submit(value = nil, args = {}) - args = args.merge(:type => :submit) - args[:value] = value unless value.nil? - - @g.p do - @g.input(args) - end - end - alias submit input_submit + def input_button(value = nil, args = {}) + args[:value] = value if value + tag(:button, nil, args) + end # def input_button + alias button input_button ## # Generate an input tag with a type of "checkbox". @@ -353,112 +368,167 @@ def input_submit(value = nil, args = {}) # to false will hide it. # def input_checkbox(label, name, checked = nil, args = {}) - id = args[:id] ? args[:id] : "#{id_for(name)}_0" - - # Determine whether or not to show the value of the checkbox - if args.key?(:show_value) - show_value = args.delete(:show_value) - else - show_value = true - end - - # Determine whether or not to show the label - if args.key?(:show_label) - show_label = args.delete(:show_label) - else - show_label = true - end - - # Get the checkbox value from either the args hash or from - # the form object (as specified in the form_for() method). - if !args[:values] and @form_values.respond_to?(name) - args[:values] = @form_values.send(name) - end - - # That class for each element wrapper (a span tag) can be customized - # using :span_class => "a_class". - if args[:span_class] - span_class = args[:span_class] - args.delete(:span_class) - else - span_class = "checkbox_wrap" - end - - # Get the type from the args hash instead of pre-defining it. Doing so - # means we can use this method for the input_radio method. - args[:type] = :checkbox if !args[:type] + opts = {} + opts[:label] = label if label + opts[:checked] = checked if checked + opts[:values] = args.delete(:values) if args.has_key?(:values) + opts[:show_value] = args.delete(:show_value) if args.has_key?(:show_value) + opts[:show_label] = args.delete(:show_label) if args.has_key?(:show_label) + opts[:span_class] = args.delete(:span_class) if args.has_key?(:span_class) + type = if args[:type]==:radio then :radio else :checkbox end + tag(type, name, args, opts) + end + alias checkbox input_checkbox - # Convert the values to an array if it's something we can't use in a loop - # (e.g. a string). - if args[:values].class != Hash and args[:values].class != Array - args[:values] = [args[:values]] - end + ## + # Generate an input tag with a type of "color" along with a label tag. + # This method also has the alias "color" so feel free to use that one + # instead of input_color. + # + # @param [String] label The text to display inside the label tag. + # @param [String Symbol] name The name of the color field. + # @param [Hash] args Any additional HTML attributes along with their + # values. + # @example + # form_for(@data, :method => :post) do |f| + # f.input_color 'Color', :car_color + # end + # + def input_color(label, name, args = {}) + tag(:color, name, args, :label=>label) + end # def input_color + alias color input_color - # Create a checkbox for each value - if !args[:values].empty? - @g.p do - # Let's create the label and the hidden field - if show_label === true - label_for(id, label, name) - end + ## + # Generate a select tag with a size=1, along with the option tags + # and a label. A size=1 attribute creates a dropdown box; otherwise, + # it's the same as a select call. + # + # @param [String] label The text to display inside the label tag. + # @param [String Symbol] name The name of the select tag. + # @param [Hash] args Hash containing additional HTML attributes. + # @example + # form_for(@data, :method => :post) do |f| + # f.dropdown 'Country', :country_list + # end + # + def input_dropdown(label, name, args = {}) + opts = {} + opts[:label] = label if label + opts[:selected] = args.delete(:selected) if args.has_key?(:selected) + opts[:values] = args.delete(:values) if args.has_key?(:values) + args[:size] = 1 + tag(:select, name, args, opts) + end + alias dropdown input_dropdown - # Loop through all the values. Each checkbox will have an ID of - # "form-NAME-INDEX". Each name will be NAME followed by [] to - # indicate it's an array (since multiple values are possible). - args[:values].each_with_index do |value, index| - id = args[:id] ? args[:id] : "#{id_for(name)}_#{index}" + ## + # Generate a email text box. + # + # @param [String] label The text to display inside the label tag. + # @param [String Symbol] name The name of the email. + # @param [Hash] args Any additional HTML attributes along with their + # values. + # @example + # form_for(@data, :method => :post) do |f| + # f.email 'E-Mail', :email + # end + # + def input_email(label, name, args = {}) + tag(:email, name, args, :label=>label) + end # def email + alias email input_email - if args[:type] == :checkbox - checkbox_name = "#{name}[]" - else - checkbox_name = name - end + ## + # Generate a field for uploading files. + # + # @param [String] label The text to display inside the label tag. + # @param [String Symbol] name The name of the radio tag. + # @param [Hash] args Any additional HTML attributes along with their + # values. + # @example + # form_for(@data, :method => :post) do |f| + # f.input_file 'Image', :image + # end + # + def input_file(label, name, args = {}) + tag(:file, name, args, :label=>label) + end + alias file input_file - # Copy all additional attributes and their values except the - # values array. - opts = args.clone - opts.delete(:values) + ## + # Generate a hidden field. Hidden fields are essentially the same as + # text fields except that they aren't displayed in the browser. + # + # @param [String Symbol] name The name of the hidden field tag. + # @param [String] value The value of the hidden field + # @param [Hash] args Any additional HTML attributes along with their + # values. + # @example + # form_for(@data, :method => :post) do |f| + # f.input_hidden :user_id + # end + # + def input_hidden(name, value = nil, args = {}) + args[:value] = value unless value.nil? + tag(:hidden, name, args) + end + alias hidden input_hidden - # Get the value and text to display for each checkbox - if value.class == Array - checkbox_text = value[0] - checkbox_value = value[1] - else - checkbox_text = checkbox_value = value - end + ## + # Generate a image tag. An image tag is a submit button that + # once it's clicked will send the form data to the server. + # + # @param [String] value The text to display in the button. + # @param [Hash] args Any additional HTML attributes along with their + # values. + # @example + # form_for(@data, :method => :post) do |f| + # f.input_image 'Save' + # end + # + def input_image(src, args = {}) + args[:src] = src unless src.nil? + tag(:image, nil, args) + end + alias image input_image - # Let's see if the current item is checked - if checked.class == Array - if checked.include?(checkbox_value) - opts[:checked] = 'checked' - end - else - if checkbox_value == checked - opts[:checked] = 'checked' - end - end + ## + # Generate a number in a click box. + # + # @param [String] label The text to display inside the label tag. + # @param [String Symbol] name The name of the number. + # @param [Hash] args Any additional HTML attributes along with their + # values. + # @example + # form_for(@data, :method => :post) do |f| + # f.number 'Age', :age, :min=>1, :max=>120 + # end + # + def input_number(label, name, args = {}) + tag(:number, name, args, :label=>label) + end # def number + alias number input_number - # And we're done, easy wasn't it? - opts = opts.merge( - :name => checkbox_name, :id => id, :value => checkbox_value - ) - - # Generate the following HTML: - # - # - # #{value} - # - # - @g.span(:class => span_class) do - @g.input(opts) - " #{checkbox_text}" if show_value === true - end - end - end - end + ## + # Generate an input tag with a type of "password" along with a label. + # Password fields are pretty much the same as text fields except that + # the content of these fields is replaced with dots. This method has the + # following alias: "password". + # + # @param [String] label The text to display inside the label tag. + # @param [String Symbol] name The name of the password field. + # @param [Hash] args Any additional HTML attributes along with their + # values. + # @example + # form_for(@data, :method => :post) do |f| + # f.input_password 'My password', :password + # end + # + def input_password(label, name, args = {}) + tag(:password, name, args, :label=>label) end - alias checkbox input_checkbox + alias password input_password ## # Generate an input tag with a type of "radio". @@ -498,63 +568,103 @@ def input_checkbox(label, name, checked = nil, args = {}) def input_radio(label, name, checked = nil, args = {}) # Force a type of "radio" args[:type] = :radio - - if !args[:span_class] - args[:span_class] = "radio_wrap" - end - + args[:span_class] = "radio_wrap" unless args[:span_class] self.input_checkbox(label, name, checked, args) end alias radio input_radio ## - # Generate a field for uploading files. + # Generate a range with a slider bar. # # @param [String] label The text to display inside the label tag. - # @param [String Symbol] name The name of the radio tag. + # @param [String Symbol] name The name of the range. # @param [Hash] args Any additional HTML attributes along with their # values. # @example # form_for(@data, :method => :post) do |f| - # f.input_file 'Image', :image + # f.range 'Age', :age, :min=>1, :max=>120 # end # - def input_file(label, name, args = {}) - id = args[:id] ? args[:id] : id_for(name) - args = args.merge(:type => :file, :name => name, :id => id) + def input_range(label, name, args = {}) + tag(:range, name, args, :label=>label) + end # def range + alias range input_range - @g.p do - label_for(id, label, name) - @g.input(args) - end + ## + # Generate a reset tag (without a label). A reset tag is a button that + # once it's clicked will reset the form data in the form + # back to it's initial state. + # + # @param [String] value The text to display in the button. + # @param [Hash] args Any additional HTML attributes along with their + # values. + # @example + # form_for(@data, :method => :post) do |f| + # f.input_reset 'Reset! Beware: you will lose the data in your form.' + # end + # + def input_reset(value = nil, args = {}) + args[:value] = value if value + tag(:reset, nil, args) + end # def input_reset + alias reset input_reset + + ## + # Generate a select tag along with the option tags and a label. + # + # @param [String] label The text to display inside the label tag. + # @param [String Symbol] name The name of the select tag. + # @param [Hash] args Hash containing additional HTML attributes. + # @example + # form_for(@data, :method => :post) do |f| + # f.select 'Country', :country_list + # end + # + def input_select(label, name, args = {}) + opts = {} + opts[:label] = label if label + opts[:selected] = args.delete(:selected) if args.has_key?(:selected) + opts[:values] = args.delete(:values) if args.has_key?(:values) + tag(:select, name, args, opts) end - alias file input_file + alias select input_select ## - # Generate a hidden field. Hidden fields are essentially the same as - # text fields except that they aren't displayed in the browser. + # Generate a submit tag (without a label). A submit tag is a button that + # once it's clicked will send the form data to the server. # - # @param [String Symbol] name The name of the hidden field tag. - # @param [String] value The value of the hidden field + # @param [String] value The text to display in the button. # @param [Hash] args Any additional HTML attributes along with their # values. # @example # form_for(@data, :method => :post) do |f| - # f.input_hidden :user_id + # f.input_submit 'Save' # end # - def input_hidden(name, value = nil, args = {}) - args = args.merge(:type => :hidden, :name => name) - - if !value and @form_values.respond_to?(name) - args[:value] = @form_values.send(name) - else - args[:value] = value - end + def input_submit(value = nil, args = {}) + args[:value] = value unless value.nil? + tag(:submit, nil, args) + end + alias submit input_submit - @g.input(args) + ## + # Generate an input tag with a type of "text" along with a label tag. + # This method also has the alias "text" so feel free to use that one + # instead of input_text. + # + # @param [String] label The text to display inside the label tag. + # @param [String Symbol] name The name of the text field. + # @param [Hash] args Any additional HTML attributes along with their + # values. + # @example + # form_for(@data, :method => :post) do |f| + # f.input_text 'Username', :username + # end + # + def input_text(label, name, args = {}) + tag(:text, name, args, :label=>label) end - alias hidden input_hidden + alias text input_text ## # Generate a text area. @@ -568,101 +678,299 @@ def input_hidden(name, value = nil, args = {}) # f.textarea 'Description', :description # end # - def textarea(label, name, args = {}) - id = args[:id] ? args[:id] : id_for(name) - - # Get the value of the textarea - if !args[:value] and @form_values.respond_to?(name) - value = @form_values.send(name) - else - value = args[:value] - args.delete(:value) - end + def input_textarea(label, name, args = {}) + opts = {} + opts[:label] = label if label + opts[:value] = args.delete(:value) if args.has_key?(:value) + tag(:textarea, name, args, opts) + end + alias textarea input_textarea - args = args.merge(:name => name, :id => id) + ## + # Method used for converting the results of the BlueForm helper to a + # string + # + # @return [String] The form output + # + def to_s + @g.to_s + end - @g.p do - label_for(id, label, name) - @g.textarea(args){ value } + ## + # Method used for converting the results of the BlueForm helper to a + # human readable string. This isn't recommended for production because + # it requires much more time to generate the HTML output than to_s. + # + # @return [String] The formatted form output + # + def to_html + # Combine the sub-parts to form whole tags or whole in-between texts + parts = [] + tag = "" + @g.out.each do |fragment| + case + when fragment[0] == '<' + if tag.empty? + tag << fragment + else + parts << tag + tag = fragment + end + when fragment[-1] == '>' + tag << fragment + parts << tag + tag = "" + else + tag << fragment + end # case end + parts << tag if tag + + # output the segments, but adjust the indentation + indent = 0 + html = "" + parts.each do |part| + case + when part[0..1] == '') + # self terminating tag -- no change in indent + when (part[0] == '<') && (part[1] != '/') + indent += 1 + end + end + + # return the formatted string + return html end ## - # Generate a select tag along with the option tags and a label. + # Generate a URL. # # @param [String] label The text to display inside the label tag. - # @param [String Symbol] name The name of the select tag. - # @param [Hash] args Hash containing additional HTML attributes. + # @param [String Symbol] name The name of the url. + # @param [Hash] args Any additional HTML attributes along with their + # values. # @example # form_for(@data, :method => :post) do |f| - # f.select 'Country', :country_list + # f.url 'Description', :description # end # - def select(label, name, args = {}) - id = args[:id] ? args[:id] : id_for(name) - multiple, size = args.values_at(:multiple, :size) + def input_url(label, name, args = {}) + tag(:url, name, args, :label=>label) + end # def url + alias url input_url + +#-------------------------------------------------------------------------------# +#--- GENERATE THE HTML HERE ----------------------------------------------------# +#-------------------------------------------------------------------------------# + + def tag(type, name, args={}, opts={}) + paragraph_wrapper = if type==:hidden then @hidden_wrapper else @paragraph_wrapper end + paragraph_wrapper.call do + + case type + + when :color, :email, :file, :number, :password, :range, :text, :url + args[:type] = type + args[:name] = name unless args.has_key?(:name) || name.nil? + args[:id] = id_for(name) unless args.has_key?(:id) || name.nil? + value = extract_values_from_object(name, args) unless args.has_key?(:value) + args[:value] = value if value + error = if name then @form_errors.delete(name.to_s) else nil end + + @label_wrapper.call { @g.label(opts[:label], :for => args[:id]) } if opts.has_key?(:label) + @input_wrapper.call do + if opts.has_key?(:span_class) + @g.span(accept(opts, [:span_class])) do + @g.input(args) + end + else + @g.input(args) + end + end + @label_wrapper.call { @g.span(:class=>"error") { " #{error}" } } if error + + when :button, :image, :reset, :submit + args[:type] = type + args[:name] = name unless args.has_key?(:name) || name.nil? + @g.input(args) + + when :textarea + args[:name] = name unless args.has_key?(:name) || name.nil? + args[:id] = id_for(name) unless args.has_key?(:id) + value = extract_values_from_object(name, args) unless opts.has_key?(:value) + opts[:value] = value if value + error = if name then @form_errors.delete(name.to_s) else nil end + + @label_wrapper.call { @g.label(opts[:label], :for => args[:id]) } if opts.has_key?(:label) + @input_wrapper.call do + if opts.has_key?(:span_class) + @g.span(accept(opts, [:span_class])) do + @g.textarea(args) {opts[:value]} + end + else + @g.textarea(args) {opts[:value]} + end + end + @label_wrapper.call { @g.span(:class=>"error") { " #{error}" } } if error + + when :hidden + args[:type] = type + args[:name] = name unless args.has_key?(:name) || name.nil? + args[:value] = extract_values_from_object(name, args) unless args.has_key?(:value) + @g.input(args) + + when :checkbox, :radio + args[:type] = type + args[:name] = nil + args[:id] = "#{id_for(name)}_0" unless args.has_key?(:id) + error = if name then @form_errors.delete(name.to_s) else nil end + + # Get the options or their defaults + span_class = if opts.has_key?(:span_class) then opts[:span_class] else "checkbox_wrap" end + show_label = if opts.has_key?(:show_label) then opts[:show_label] else true end + show_value = if opts.has_key?(:show_value) then opts[:show_value] else true end + + # Get all the values or checked from the form object + has_values = opts.has_key?(:values) + has_checked = opts[:checked] + if has_values + values = opts[:values] + if has_checked + checked = opts[:checked] + else + checked = extract_values_from_object(name, args) + end + else + values = extract_values_from_object(name, args) + values = [] if values.nil? + if has_checked + checked = opts[:checked] + else + checked = [] + end + end + values = [values] unless [Array,Hash].index(values.class) + checked = [checked] unless [Array,Hash].index(checked.class) - # Get all the values - if !args[:values] and @form_values.respond_to?(name) - values = @form_values.send(name) - else - values = args[:values] - args.delete(:values) - end + # Loop through all the values. Each checkbox will have an ID of + # "form-NAME-INDEX". Each name will be NAME followed by [] to + # indicate it's an array (since multiple values are possible). + @label_wrapper.call { @g.label(opts[:label], :for => args[:id]) } if opts.has_key?(:label) && show_label + @input_wrapper.call do + values.each_with_index do |value,index| + args[:id] = "#{id_for(name)}_#{index}" + + # The id is an array for checkboxes, and elemental for radio buttons + checkbox_name = if type == :checkbox then "#{name}[]" else name end + args[:name] = checkbox_name + + # Get the value and text to display for each checkbox + if value.class == Array + # It's a hash in inverted ([value,key]) order + checkbox_text = value[0] + checkbox_value = value[1] + else + # It's one value of an array + checkbox_text = checkbox_value = value + end + args[:value] = checkbox_value - args[:multiple] = 'multiple' if multiple - args[:size] = (size || values.count || 1).to_i - args[:name] = multiple ? "#{name}[]" : name - args = args.merge(:id => id) - - # Retrieve the selected value - has_selected, selected = args.key?(:selected), args[:selected] - selected = [selected] if !selected.is_a?(Array) - args.delete(:selected) - - @g.p do - label_for(id, label, name) - @g.select args do - values.each do |value, o_name| - o_name ||= value - o_args = {:value => value} - - if has_selected and selected.include?(value) - o_args[:selected] = 'selected' - end + # Let's see if the current item is checked + if checked.include?(checkbox_value) + args[:checked] = 'checked' + else + args.delete(:checked) + end - @g.option(o_args){ o_name } + @g.span(:class=>span_class) do + @g.input(args) + " #{checkbox_text}" if show_value == true + end + end + end # @input_wrapper + @label_wrapper.call { @g.span(:class=>"error") { " #{error}" } } if error + + when :select + id = args[:id] ? args[:id] : id_for(name) + multiple, size = args.values_at(:multiple, :size) + error = if name then @form_errors.delete(name.to_s) else nil end + + # Get all the values or selected from the form object + has_values = opts.has_key?(:values) + has_selected = opts[:selected] + if has_values + values = opts[:values] + if has_selected + selected = opts[:selected] + else + selected = extract_values_from_object(name, args) + end + else + values = extract_values_from_object(name, args) + values = [] if values.nil? + if has_selected + selected = opts[:selected] + else + selected = [] + end end - end - end - end - - ## - # Method used for converting the results of the BlueForm helper to a - # string - # - # @return [String] The form output - # - def to_s - @g.to_s - end + values = [values] unless [Array,Hash].index(values.class) + selected = [selected] unless [Array,Hash].index(selected.class) + + args[:multiple] = 'multiple' if multiple + args[:size] = (size || values.count || 1).to_i + args[:name] = multiple ? "#{name}[]" : name + args = args.merge(:id => id) + + @label_wrapper.call { @g.label(opts[:label], :for => args[:id]) } if opts.has_key?(:label) + @input_wrapper.call do + @g.select(args) do + values.each do |value, option_name| + option_name ||= value + option_args = {:value => value} + option_args[:selected] = 'selected' if selected.include?(value) + @g.option(option_args){ option_name } + end # opts[:values].each + end # @g.select + end # @input_wrapper.call + @label_wrapper.call { @g.span(:class=>"error") { " #{error}" } } if error + + else + raise ArgumentError.new("Blueform doesn't support HTML5 type '#{type}'") + end # case + + end # paragraph_wrapper.call + end # tag private ## - # Generate a label based on the id and value. - # - # @param [String] id The ID to which the label belongs. - # @param [String] value The text to display inside the label tag. - # @param [String] name The name of the field to which the label belongs. + # If possible, extract the data from the form object. # - def label_for(id, value, name) - if error = @form_errors.delete(name.to_s) - @g.label("#{value} ", :for => id){ @g.span(:class => :error){ error } } - else - @g.label(value, :for => id) + # @param [String] field_name The name of the field. + # @return [Array] The args parameter. Extract looks + # for the :value=>:name parameter in order to + # extract data from the form object. + # + def extract_values_from_object(name, args) + # If conditions are right, get the value from the input object. + case + when name.nil? + # This control doesn't use value look up + when args.has_key?(:value) + # Don't override given value. + when @form_object.nil? + # No structure to look up a value. + when @form_object.respond_to?(name) + # There's a data element, so get the value. + @form_object.send(name) end - end + end # extract_values_from_object ## # Generate a value for an ID tag based on the field's name. @@ -671,12 +979,21 @@ def label_for(id, value, name) # @return [String] The ID for the specified field name. # def id_for(field_name) + raise ArgumentError.new("No field name passed to id_for") if field_name.nil? if name = @form_args[:name] "#{name}_#{field_name}".downcase.gsub(/-/, '_') else "form_#{field_name}".downcase.gsub(/-/, '_') end - end + end # id_for + + ## + # Create a new hash with only the elements which have listed keys + # + def accept(hash, keys=[]) + hash.select { |k,v| keys.index(k) } + end # accept + end # Form end # BlueForm end # Helper From 25952e17887ca327d7fac85b480fc8260d9ff417 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Thu, 12 May 2016 01:46:07 -0700 Subject: [PATCH 02/16] Update ramaze/lib/ramaze/gestalt.rb to add some tags that had to be real methods in the code for the BlueForm HTML5 changes to work. --- lib/ramaze/gestalt.rb | 47 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/lib/ramaze/gestalt.rb b/lib/ramaze/gestalt.rb index caa1ee03..9427b2be 100644 --- a/lib/ramaze/gestalt.rb +++ b/lib/ramaze/gestalt.rb @@ -69,11 +69,39 @@ def p(*args, &block) end ## - # Workaround for Kernel#select to make work. + # Workaround for @g.table in BlueForm using method/call. + # This is needed in order to use m = g.method("table") etc. # - # @param [Array] args Extra arguments that should be processed before - # creating the select tag. - # @param [Proc] block + def table(*args, &block) + _gestalt_call_tag :table, args, &block + end + + ## + # Workaround for @g.tr in BlueForm using method/call. + # This is needed in order to use m = g.method("tr") etc. + # + def tr(*args, &block) + _gestalt_call_tag :tr, args, &block + end + + ## + # Workaround for @g.th in BlueForm using method/call. + # This is needed in order to use m = g.method("th") etc. + # + def th(*args, &block) + _gestalt_call_tag :th, args, &block + end + + ## + # Workaround for @g.td in BlueForm using method/call. + # This is needed in order to use m = g.method("td") etc. + # + def td(*args, &block) + _gestalt_call_tag :td, args, &block + end + + ## + # Workaround for Kernel#select to make work. # def select(*args, &block) _gestalt_call_tag(:select, args, &block) @@ -137,7 +165,7 @@ def _gestalt_escape_entities(s) end ## - # Shortcut for building tags, + # Shortcut for building tags. # # @param [String] name # @param [Array] args @@ -147,6 +175,15 @@ def tag(name, *args, &block) _gestalt_call_tag(name.to_s, args, &block) end + ## + # A way to append text to the output of Gestalt. + # + # @param [String] text + # + def <<(str) + @out << str + end + ## # Convert the final output of Gestalt to a string. # This method has the following alias: "to_str". From 58eea236fbe3dd274a7e9d5f0222f7bd07225478 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Thu, 12 May 2016 01:52:29 -0700 Subject: [PATCH 03/16] Update spec/ramaze/helper/blue_form.rb to add additional tests for new BlueForm updates. Numbered the tests so I could figure out which was which. The tests below 100 are the original tests, and the ones over 100 are the new tests. If all the tests under 100 pass, the new BlueForm is completely backwards compatible. --- spec/ramaze/helper/blue_form.rb | 1277 +++++++++++++++++++++++++++++-- 1 file changed, 1205 insertions(+), 72 deletions(-) diff --git a/spec/ramaze/helper/blue_form.rb b/spec/ramaze/helper/blue_form.rb index 6f50d39d..63fb1a56 100644 --- a/spec/ramaze/helper/blue_form.rb +++ b/spec/ramaze/helper/blue_form.rb @@ -1,16 +1,18 @@ require File.expand_path('../../../../spec/helper', __FILE__) require 'ramaze/helper/blue_form' -describe BF = Ramaze::Helper::BlueForm do +describe BF = Ramaze::Helper::BlueForm do # original tests extend BF # Generate some dummy data @data = Class.new do + attr_accessor :person attr_reader :username attr_reader :password attr_reader :assigned attr_reader :assigned_hash attr_reader :message + attr_accessor :server attr_reader :servers_hash attr_reader :servers_array attr_accessor :errors @@ -30,67 +32,981 @@ def initialize end end.new + class BlueFormModel + attr_accessor :errors + def set_fields(hash, fields, opts={}) + @errors = {} + fields.each do |f| + if hash.has_key?(f) + instance_variable_set("@#{f}", hash[f]) + elsif f.is_a?(Symbol) && hash.has_key?(sf = f.to_s) + instance_variable_set("@#{sf}", hash[sf]) + else + raise NoMethodError.new("undefined method `#{f.to_s}=' for #{self.inspect}") \ + if opts[:missing]!=:skip + end + end + self + end + end + + class UserX < BlueFormModel + attr_accessor :username, :password, :gender, :country, :errors + def initialize + @errors={} + end + end + # very strange comparision, sort all characters and compare, so we don't have # order issues. def assert(expected, output) left = expected.to_s.gsub(/\s+/, ' ').gsub(/>\s+<').strip right = output.to_s.gsub(/\s+/, ' ').gsub(/>\s+<').strip - left.scan(/./).sort.should == right.scan(/./).sort + lsort = left.scan(/./).sort + rsort = right.scan(/./).sort + lsort.should == rsort + end + + # ------------------------------------------------ + # Basic forms + it 'Make a basic form' do + out = form_for(@data, :method => :post) + assert(<<-FORM, out) +
+ FORM + end + + it 'Make a form with the method and action attributes specified' do + out = form_for(@data, :method => :post, :action => '/') + assert(<<-FORM, out) +
+ FORM + end + + it 'Make a form with a method, action and a name attribute' do + out = form_for(@data, :method => :post, :action => '/', :name => :spec) + assert(<<-FORM, out) +
+
+ FORM + end + + it 'Make a form with a class and an ID' do + out = form_for(@data, :class => :foo, :id => :bar) + assert(<<-FORM, out) +
+
+ FORM + end + + it 'Make a form with a fieldset and a legend' do + out = form_for(@data, :method => :get) do |f| + f.fieldset do + f.legend('The Form') + end + end + + assert(<<-FORM, out) +
+
+ The Form +
+
+ FORM + end + + # + # ------------------------------------------------ + # tag forms + it '1. Make a :text form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:text, :username, {}, {:label=>"Username"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '2. Make a :text form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:text, :username, {:value=>"mrboo"}, {:label=>"Username"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '3. Make a :text form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:text, :username, {:size=>10, :id=>"my_id"}, {:label=>"Username"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '4. Make a :password form with name and opts' do + out = form_for(nil, :method => :get) do |f| + f.tag(:password, :password, {}, {:label=>"Password"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '5. Make a :password form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:password, :password, {:value=>"super-secret-password", :class=>"password_class"}, {:label=>"Password"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '6. Make a :submit form' do + out = form_for(@data, :method => :get) do |f| + f.tag(:submit, nil, {}, {}) + end + + assert(<<-FORM, out) +
+

+ +

+
+ FORM + end + + it '7. Make a :submit form with args' do + out = form_for(@data, :method => :get) do |f| + f.tag(:submit, nil, {:value=>"Send"}, {}) + end + + assert(<<-FORM, out) +
+

+ +

+
+ FORM + end + + it '8. Make a :checkbox form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:checkbox, :assigned, {}, {:label=>"Assigned"}) + end + + assert(<<-FORM, out) +
+

+ + bacon + steak +

+
+ FORM + end + + it '9. Make a :checkbox form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:checkbox, :assigned, {}, {:label=>"Assigned", :checked=>"bacon"}) + end + + assert(<<-FORM, out) +
+

+ + bacon + steak +

+
+ FORM + end + + it '10. Make a :checkbox form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:checkbox, :assigned, {}, {:label=>"Assigned", :checked=>"boo", :values=>["boo"]}) + end + + assert(<<-FORM, out) +
+

+ + boo +

+
+ FORM + end + + it '11. Make a :checkbox form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:checkbox, :assigned, {}, {:label=>"Assigned", :checked=>["boo"], :values=>["boo", "foo"]}) + end + + assert(<<-FORM, out) +
+

+ + boo + foo +

+
+ FORM + end + + it '12. Make a :checkbox form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:checkbox, :assigned, {}, {:label=>"Assigned", :checked=>["boo"], :values=>{"Boo"=>"boo"}}) + end + + assert(<<-FORM, out) +
+

+ + Boo +

+
+ FORM + end + + it '13. Make a :checkbox form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:checkbox, :assigned, {}, {:label=>"Assigned", :show_value=>false}) + end + + assert(<<-FORM, out) +
+

+ + + +

+
+ FORM + end + + it '14. Make a :checkbox form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:checkbox, :assigned, {}, {:label=>"Assigned", :show_label=>false}) + end + + assert(<<-FORM, out) +
+

+ bacon + steak +

+
+ FORM + end + + it '15. Make a :checkbox form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:checkbox, :assigned_hash, {}, {:label=>"Assigned"}) + end + + assert(<<-FORM, out) +
+

+ + Bacon + Steak +

+
+ FORM + end + + it '16. Make a :checkbox form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:checkbox, :assigned_hash, {}, {:label=>"Assigned", :checked=>"bacon"}) + end + + assert(<<-FORM, out) +
+

+ + Bacon + Steak +

+
+ FORM + end + + it '17. Make a :radio form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:radio, :assigned, {:type=>:radio}, {:label=>"Assigned", :span_class=>"radio_wrap"}) + end + + assert(<<-FORM, out) +
+

+ + bacon + steak +

+
+ FORM + end + + it '18. Make a :radio form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:radio, :assigned, {:type=>:radio}, {:label=>"Assigned", :checked=>"bacon", :span_class=>"radio_wrap"}) + end + + assert(<<-FORM, out) +
+

+ + bacon + steak +

+
+ FORM + end + + it '19. Make a :radio form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:radio, :assigned, {:type=>:radio}, {:label=>"Assigned", :checked=>"boo", :values=>["boo"], :span_class=>"radio_wrap"}) + end + + assert(<<-FORM, out) +
+

+ + boo +

+
+ FORM + end + + it '20. Make a :radio form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:radio, :assigned, {:type=>:radio}, {:label=>"Assigned", :show_value=>false, :span_class=>"radio_wrap"}) + end + + assert(<<-FORM, out) +
+

+ + + +

+
+ FORM + end + + it '21. Make a :radio form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:radio, :assigned, {:type=>:radio}, {:label=>"Assigned", :show_label=>false, :span_class=>"radio_wrap"}) + end + + assert(<<-FORM, out) +
+

+ bacon + steak +

+
+ FORM + end + + it '22. Make a :radio form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:radio, :assigned_hash, {:type=>:radio}, {:label=>"Assigned", :span_class=>"radio_wrap"}) + end + + assert(<<-FORM, out) +
+

+ + Bacon + Steak +

+
+ FORM + end + + it '23. Make a :radio form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:radio, :assigned_hash, {:type=>:radio}, {:label=>"Assigned", :checked=>"bacon", :span_class=>"radio_wrap"}) + end + + assert(<<-FORM, out) +
+

+ + Bacon + Steak +

+
+ FORM + end + + it '24. Make a :file form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:file, :file, {}, {:label=>"File"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '24. Make a :file form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:file, :file, {:id=>"awesome_file"}, {:label=>"File"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '25. Make a :hidden form with name' do + out = form_for(@data, :method => :get) do |f| + f.tag(:hidden, :username, {}, {}) + end + + assert(<<-FORM, out) +
+ +
+ FORM + end + + it '26. Make a :hidden form with name and args' do + out = form_for(@data, :method => :get) do |f| + f.tag(:hidden, :username, {:value=>"Bob Ross"}, {}) + end + + assert(<<-FORM, out) +
+ +
+ FORM + end + + it '27. Make a :hidden form with name and args' do + out = form_for(@data, :method => :get) do |f| + f.tag(:hidden, :username, {:id=>"test", :value=>"Bob Ross"}, {}) + end + + assert(<<-FORM, out) +
+ +
+ FORM + end + + it '28. Make a :textarea form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:textarea, :message, {}, {:label=>"Message"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '29. Make a :textarea form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:textarea, :message, {}, {:label=>"Message", :value=>"stuff"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '30. Make a :select form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :servers_hash, {}, {:label=>"Server"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '31. Make a :select form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :servers_hash, {}, {:label=>"Server", :selected=>:mongrel}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '32. Make a :select form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :servers_array, {}, {:label=>"Server"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '33. Make a :select form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :servers_array, {}, {:label=>"Server", :selected=>"Mongrel"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '34. Make a :select form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :people_hash, {}, {:label=>"People", :values=>{:chuck=>"Chuck", :bob=>"Bob"}}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '35. Make a :select form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :people_hash, {}, {:label=>"People", :selected=>:chuck, :values=>{:chuck=>"Chuck", :bob=>"Bob"}}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '36. Make a :select form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :people_array, {}, {:label=>"People", :values=>["Chuck", "Bob"]}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '37. Make a :select form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :people_array, {}, {:label=>"People", :selected=>"Chuck", :values=>["Chuck", "Bob"]}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '38. Make a :select form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :servers_hash, {:multiple=>:multiple}, {:label=>"Server"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '39. Make a :select form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :servers_hash, {:multiple=>:multiple}, {:label=>"Server", :selected=>:webrick}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '40. Make a :select form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :servers_hash, {:multiple=>:multiple}, {:label=>"Server", :selected=>[:webrick, :mongrel]}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '41. Make a :button form with args' do + out = form_for(@data, :method => :get) do |f| + f.tag(:button, nil, {:onclick=>"fcn()", :value=>"Accept"}, {}) + end + + assert(<<-FORM, out) +
+

+
+ FORM + end + + it '42. Make a :color form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:color, :my_color, {}, {:label=>"Choose a color"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '43. Make a :email form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:email, :email, {}, {:label=>"Email"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '44. Make a :image form with args' do + out = form_for(@data, :method => :get) do |f| + f.tag(:image, nil, {:alt=>"Submit", :src=>"http://www.w3schools.com/tags/img_submit.gif"}, {}) + end + + assert(<<-FORM, out) +
+

+ +

+
+ FORM + end + + it '45. Make a :number form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:number, :age, {:min=>1, :max=>120}, {:label=>"Age"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '46. Make a :range form with name, args, and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:range, :cost, {:min=>0, :max=>100}, {:label=>"Cost"}) + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM end - # ------------------------------------------------ - # Basic forms + it '47. Make a :reset form' do + out = form_for(@data, :method => :get) do |f| + f.tag(:reset, nil, {}, {}) + end - it 'Make a basic form' do - out = form_for(@data, :method => :post) assert(<<-FORM, out) -
+
+

+ +

+
FORM end - it 'Make a form with the method and action attributes specified' do - out = form_for(@data, :method => :post, :action => '/') + it '48. Make a :reset form with args' do + out = form_for(@data, :method => :get) do |f| + f.tag(:reset, nil, {:value=>"Reset"}, {}) + end + assert(<<-FORM, out) -
+
+

+ +

+
FORM end - it 'Make a form with a method, action and a name attribute' do - out = form_for(@data, :method => :post, :action => '/', :name => :spec) + it '49. Make a :url form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:url, :url, {}, {:label=>"URL"}) + end + assert(<<-FORM, out) -
-
+
+

+ + +

+
FORM end - it 'Make a form with a class and an ID' do - out = form_for(@data, :class => :foo, :id => :bar) + it '50. Make a :select form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:select, :person, {}, {:label=>"Person", :selected=>"chuck", :values=>{"chuck"=>"Chuck", "bob"=>"Bob"}}) + end + assert(<<-FORM, out) -
-
+
+

+ + +

+
FORM end - it 'Make a form with a fieldset and a legend' do + it '51. Make a :select form with name and opts' do out = form_for(@data, :method => :get) do |f| - f.fieldset do - f.legend('The Form') - end + f.tag(:select, :person, {}, {:label=>"Person", :selected=>"chuck",:values=>{"chuck"=>"Chuck", "bob"=>"Bob"}}) end assert(<<-FORM, out)
-
- The Form -
+

+ + +

+
+ FORM + end + + it '60. Make a :text/:password form with name and opts' do + params = {:username=>"gladys", :password=>"abc", :gender=>"F", :country=>"SV"} + user = UserX.new + user.set_fields(params, [:username, :password, :gender, :country], :missing=>:skip) + user.errors[:username] = "User not in system" + user.errors[:password] = "The username/password combination is not on our system" + out = form_for(user, :method => :post, :action=>"login2") do |f| + f.tag(:text, :username, {}, {:label=>"Username: "}) + f.tag(:password, :password, {}, {:label=>"Password: "}) + end + + assert(<<-FORM, out) +
+

+ + +  User not in system +

+

+ + +  The username/password combination is not on our system +

+
+ FORM + end + + it '98. Make a :text form with name and opts' do + form_error :username, 'May not be empty' + out = form_for(@data, :method => :get) do |f| + f.tag(:text, :username, {}, {:label=>"Username"}) + end + + assert(<<-FORM, out) +
+

+ + +  May not be empty +

+
+ FORM + end + + it '99. Make a :text form with name and opts' do + out = form_for(@data, :method => :get) do |f| + f.tag(:text, :username, {}, {:label=>"Username"}) + end + + assert(<<-FORM, out) +
+

+ + +  May not be empty +

FORM end + + # ------------------------------------------------ + # Clear out previous simulated errors + @form_errors = {} + # ------------------------------------------------ # Text fields - it 'Make a form with input_text(label, value)' do + it '101. Make a form with input_text(label, value)' do out = form_for(@data, :method => :get) do |f| f.input_text 'Username', :username end @@ -105,7 +1021,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_text(username, label, value)' do + it '102. Make a form with input_text(username, label, value)' do out = form_for(@data, :method => :get) do |f| f.input_text 'Username', :username, :value => 'mrboo' end @@ -120,7 +1036,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_text(label, name, size, id)' do + it '103. Make a form with input_text(label, name, size, id)' do out = form_for(@data, :method => :get) do |f| f.input_text 'Username', :username, :size => 10, :id => 'my_id' end @@ -138,7 +1054,7 @@ def assert(expected, output) # ------------------------------------------------ # Password fields - it 'Make a form with input_password(label, name)' do + it '104. Make a form with input_password(label, name)' do out = form_for(nil , :method => :get) do |f| f.input_password 'Password', :password end @@ -153,7 +1069,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_password(label, name, value, class)' do + it '105. Make a form with input_password(label, name, value, class)' do out = form_for(@data, :method => :get) do |f| f.input_password 'Password', :password, :value => 'super-secret-password', :class => 'password_class' end @@ -171,7 +1087,7 @@ def assert(expected, output) # ------------------------------------------------ # Submit buttons - it 'Make a form with input_submit()' do + it '106. Make a form with input_submit()' do out = form_for(@data, :method => :get) do |f| f.input_submit end @@ -185,7 +1101,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_submit(value)' do + it '107. Make a form with input_submit(value)' do out = form_for(@data, :method => :get) do |f| f.input_submit 'Send' end @@ -202,7 +1118,7 @@ def assert(expected, output) # ------------------------------------------------ # Checkboxes - it 'Make a form with input_checkbox(label, name)' do + it '108. Make a form with input_checkbox(label, name)' do out = form_for(@data, :method => :get) do |f| f.input_checkbox 'Assigned', :assigned end @@ -218,7 +1134,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_checkbox(label, name, checked)' do + it '109. Make a form with input_checkbox(label, name, checked)' do out = form_for(@data, :method => :get) do |f| f.input_checkbox 'Assigned', :assigned, 'bacon' end @@ -234,7 +1150,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_checkbox(label, name, checked, values, default)' do + it '110. Make a form with input_checkbox(label, name, checked, values, default)' do out = form_for(@data, :method => :get) do |f| f.input_checkbox 'Assigned', :assigned, 'boo', :values => ['boo'] end @@ -249,7 +1165,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_checkbox and check multiple values using an array' do + it '111. Make a form with input_checkbox and check multiple values using an array' do out = form_for(@data, :method => :get) do |f| f.input_checkbox 'Assigned', :assigned, ['boo'], :values => ['boo', 'foo'] end @@ -265,7 +1181,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_checkbox and check multiple values using a hash' do + it '112. Make a form with input_checkbox and check multiple values using a hash' do out = form_for(@data, :method => :get) do |f| f.input_checkbox 'Assigned', :assigned, ['boo'], :values => {'Boo' => 'boo'} end @@ -280,7 +1196,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_checkbox(label, name) but hide the value of the checkbox' do + it '113. Make a form with input_checkbox(label, name) but hide the value of the checkbox' do out = form_for(@data, :method => :get) do |f| f.input_checkbox 'Assigned', :assigned, nil, :show_value => false end @@ -296,7 +1212,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_checkbox(label, name) but hide thelabel' do + it '114. Make a form with input_checkbox(label, name) but hide the label' do out = form_for(@data, :method => :get) do |f| f.input_checkbox 'Assigned', :assigned, nil, :show_label => false end @@ -314,7 +1230,7 @@ def assert(expected, output) # ------------------------------------------------ # Checkboxes using a hash - it 'Make a form with input_checkbox(label, name) using a hash' do + it '115. Make a form with input_checkbox(label, name) using a hash' do out = form_for(@data, :method => :get) do |f| f.input_checkbox 'Assigned', :assigned_hash end @@ -330,7 +1246,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_checkbox(label, name, checked) using a hash' do + it '116. Make a form with input_checkbox(label, name, checked) using a hash' do out = form_for(@data, :method => :get) do |f| f.input_checkbox 'Assigned', :assigned_hash, 'bacon' end @@ -349,7 +1265,7 @@ def assert(expected, output) # ------------------------------------------------ # Radio buttons - it 'Make a form with input_radio(label, name)' do + it '117. Make a form with input_radio(label, name)' do out = form_for(@data, :method => :get) do |f| f.input_radio 'Assigned', :assigned end @@ -365,7 +1281,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_radio(label, name, checked)' do + it '118. Make a form with input_radio(label, name, checked)' do out = form_for(@data, :method => :get) do |f| f.input_radio 'Assigned', :assigned, 'bacon' end @@ -381,7 +1297,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_radio(label, name, checked, values, default)' do + it '119. Make a form with input_radio(label, name, checked, values, default)' do out = form_for(@data, :method => :get) do |f| f.input_radio 'Assigned', :assigned, 'boo', :values => ['boo'] end @@ -396,7 +1312,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_radio(label, name) but hide the value' do + it '120. Make a form with input_radio(label, name) but hide the value' do out = form_for(@data, :method => :get) do |f| f.input_radio 'Assigned', :assigned, nil, :show_value => false end @@ -412,7 +1328,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_radio(label, name) but hide the label' do + it '121. Make a form with input_radio(label, name) but hide the label' do out = form_for(@data, :method => :get) do |f| f.input_radio 'Assigned', :assigned, nil, :show_label => false end @@ -431,7 +1347,7 @@ def assert(expected, output) # ------------------------------------------------ # Radio buttons using a hash - it 'Make a form with input_radio(label, name) using a hash' do + it '122. Make a form with input_radio(label, name) using a hash' do out = form_for(@data, :method => :get) do |f| f.input_radio 'Assigned', :assigned_hash end @@ -447,7 +1363,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_radio(label, name, checked) using a hash' do + it '123. Make a form with input_radio(label, name, checked) using a hash' do out = form_for(@data, :method => :get) do |f| f.input_radio 'Assigned', :assigned_hash, 'bacon' end @@ -466,7 +1382,7 @@ def assert(expected, output) # ------------------------------------------------ # File uploading - it 'Make a form with input_file(label, name)' do + it '124. Make a form with input_file(label, name)' do out = form_for(@data, :method => :get) do |f| f.input_file 'File', :file end @@ -481,7 +1397,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_file(label, name)' do + it '125. Make a form with input_file(label, name)' do out = form_for(@data, :method => :get) do |f| f.input_file 'File', :file, :id => 'awesome_file' end @@ -499,7 +1415,19 @@ def assert(expected, output) # ------------------------------------------------ # Hidden fields - it 'Make a form with input_hidden(name, value)' do + it '125. Make a form with input_hidden(name)' do + out = form_for(@data, :method => :get) do |f| + f.input_hidden :username + end + + assert(<<-FORM, out) +
+ +
+ FORM + end + + it '126. Make a form with input_hidden(name, value)' do out = form_for(@data, :method => :get) do |f| f.input_hidden :username, 'Bob Ross' end @@ -511,7 +1439,7 @@ def assert(expected, output) FORM end - it 'Make a form with input_hidden(name, value, id)' do + it '127. Make a form with input_hidden(name, value, id)' do out = form_for(@data, :method => :get) do |f| f.input_hidden :username, 'Bob Ross', :id => 'test' end @@ -526,7 +1454,7 @@ def assert(expected, output) # ------------------------------------------------ # Textarea elements - it 'Make a form with textarea(label, name)' do + it '128. Make a form with textarea(label, name)' do out = form_for(@data, :method => :get) do |f| f.textarea 'Message', :message end @@ -541,7 +1469,7 @@ def assert(expected, output) FORM end - it 'Make a form with textarea(label, name, value)' do + it '129. Make a form with textarea(label, name, value)' do out = form_for(@data, :method => :get) do |f| f.textarea 'Message', :message, :value => 'stuff' end @@ -559,7 +1487,7 @@ def assert(expected, output) # ------------------------------------------------ # Select elements - it 'Make a form with select(label, name) from a hash' do + it '130. Make a form with select(label, name) from a hash' do out = form_for(@data, :method => :get) do |f| f.select 'Server', :servers_hash end @@ -578,7 +1506,7 @@ def assert(expected, output) FORM end - it 'Make a form with select(label, name, selected) from a hash' do + it '131. Make a form with select(label, name, selected) from a hash' do out = form_for(@data, :method => :get) do |f| f.select 'Server', :servers_hash, :selected => :mongrel end @@ -597,7 +1525,7 @@ def assert(expected, output) FORM end - it 'Make a form with select(label, name) from an array' do + it '132. Make a form with select(label, name) from an array' do out = form_for(@data, :method => :get) do |f| f.select 'Server', :servers_array end @@ -616,7 +1544,7 @@ def assert(expected, output) FORM end - it 'Make a form with select(label, name, selected) from an array' do + it '133. Make a form with select(label, name, selected) from an array' do out = form_for(@data, :method => :get) do |f| f.select 'Server', :servers_array, :selected => 'Mongrel' end @@ -638,7 +1566,7 @@ def assert(expected, output) # ------------------------------------------------ # Select elements with custom values - it 'Make a form with select(label, name) from a hash using custom values' do + it '134. Make a form with select(label, name) from a hash using custom values' do out = form_for(@data, :method => :get) do |f| f.select 'People', :people_hash, :values => {:chuck => 'Chuck', :bob => 'Bob'} end @@ -656,7 +1584,7 @@ def assert(expected, output) FORM end - it 'Make a form with select(label, name, selected) from a hash using custom values' do + it '135. Make a form with select(label, name, selected) from a hash using custom values' do out = form_for(@data, :method => :get) do |f| f.select 'People', :people_hash, :values => {:chuck => 'Chuck', :bob => 'Bob'}, :selected => :chuck end @@ -674,7 +1602,7 @@ def assert(expected, output) FORM end - it 'Make a form with select(label, name) from an array using custom values' do + it '136. Make a form with select(label, name) from an array using custom values' do out = form_for(@data, :method => :get) do |f| f.select 'People', :people_array, :values => ['Chuck', 'Bob'] end @@ -692,7 +1620,7 @@ def assert(expected, output) FORM end - it 'Make a form with select(label, name, selected) from an array using custom values' do + it '137. Make a form with select(label, name, selected) from an array using custom values' do out = form_for(@data, :method => :get) do |f| f.select 'People', :people_array, :values => ['Chuck', 'Bob'], :selected => 'Chuck' end @@ -710,7 +1638,7 @@ def assert(expected, output) FORM end - it 'Make a form with multiple select(label, name) from a hash' do + it '138. Make a form with multiple select(label, name) from a hash' do out = form_for(@data, :method => :get) do |f| f.select 'Server', :servers_hash, :multiple => :multiple end @@ -729,7 +1657,7 @@ def assert(expected, output) FORM end - it 'Make a form with multiple select(label, name, selected) from a hash' do + it '139. Make a form with multiple select(label, name, selected) from a hash' do out = form_for(@data, :method => :get) do |f| f.select 'Server', :servers_hash, :multiple => :multiple, :selected => :webrick end @@ -748,7 +1676,7 @@ def assert(expected, output) FORM end - it 'Make a form with multiple select(label, name, selected) from a hash' do + it '140. Make a form with multiple select(label, name, selected) from a hash' do out = form_for(@data, :method => :get) do |f| f.select 'Server', :servers_hash, :multiple => :multiple, :selected => [:webrick, :mongrel] end @@ -767,10 +1695,214 @@ def assert(expected, output) FORM end + # ------------------------------------------------ + # HTML5 Extensions + + it '141. Make a form with input_button(label, value) with JavaScript call' do + out = form_for(@data, :method => :get) do |f| + f.input_button 'Accept', :onclick=>'fcn()' + end + + assert(<<-FORM, out) +
+

+
+ FORM + end + + it '142. Make a form with input_color(label, name)' do + out = form_for(@data, :method => :get) do |f| + f.input_color 'Choose a color', :my_color + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '142. Make a form with input_email(label, value)' do + out = form_for(@data, :method => :get) do |f| + f.input_email 'Email', :email + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '144. Make a form with input_image()' do + out = form_for(@data, :method => :get) do |f| + f.image "http://www.w3schools.com/tags/img_submit.gif", :alt=>"Submit" + end + + assert(<<-FORM, out) +
+

+ +

+
+ FORM + end + + it '145. Make a form with input_number(label, value)' do + out = form_for(@data, :method => :get) do |f| + f.input_number 'Age', :age, :min=>1, :max=>120 + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '146. Make a form with input_range(label, value)' do + out = form_for(@data, :method => :get) do |f| + f.input_range 'Cost', :cost, :min=>0, :max=>100 + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '147. Make a form with input_reset()' do + out = form_for(@data, :method => :get) do |f| + f.input_reset + end + + assert(<<-FORM, out) +
+

+ +

+
+ FORM + end + + it '148. Make a form with input_reset(value)' do + out = form_for(@data, :method => :get) do |f| + f.input_reset 'Reset' + end + + assert(<<-FORM, out) +
+

+ +

+
+ FORM + end + + it '149. Make a form with input_url(label, value)' do + out = form_for(@data, :method => :get) do |f| + f.input_url 'URL', :url + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + # ------------------------------------------------ + # Verify that select boxes reload from the form object + + it '150. Make a form with select(label, name, selected) take from form object' do + @data.person = "chuck" + out = form_for(@data, :method => :get) do |f| + f.select 'Person', :person, :values => {'chuck' => 'Chuck', 'bob' => 'Bob'} + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + it '151. Make a form with select(label, name, selected) take from form object' do + @data.person = "chuck" + out = form_for(@data, :method => :get) do |f| + form_error(:person, "This person has not validated his email.") + f.select 'Person', :person, :values => {'chuck' => 'Chuck', 'bob' => 'Bob'} + end + + assert(<<-FORM, out) +
+

+ + +

+
+ FORM + end + + # ------------------------------------------------ + # Code used in documentation + + it '160. Test error posting' do + params = {:username=>"gladys", :password=>"abc", :gender=>"F", :country=>"SV"} + user = UserX.new + user.set_fields(params, [:username, :password, :gender, :country], :missing=>:skip) + user.errors[:username] = "User not in system" + user.errors[:password] = "The username/password combination is not on our system" + out = form_for(user, :method=>:post, :action=>:login2) do |f| + f.text("Username: ", :username) + f.password("Password: ", :password) + end.to_html + + assert(<<-FORM, out) +
+

+ + +  User not in system +

+

+ + +  The username/password combination is not on our system +

+
+ FORM + end + # ------------------------------------------------ # Error messages - it 'Insert an error message' do + it '198. Insert an error message' do form_error :username, 'May not be empty' out = form_for(@data, :method => :get) do |f| f.input_text 'Username', :username @@ -779,17 +1911,17 @@ def assert(expected, output) assert(<<-FORM, out)

- - + + +  May not be empty

FORM end - it 'Retrieve all errors messages from the model' do + it '199. Retrieve all errors messages from the model' do @data.errors = {:username => "May not be empty"} form_errors_from_model(@data) - out = form_for(@data, :method => :get) do |f| f.input_text 'Username', :username end @@ -797,8 +1929,9 @@ def assert(expected, output) assert(<<-FORM, out)

- - + + +  May not be empty

FORM From 39f3276ed217e35a9ecb8ab55b06e6f380d4ba92 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Mon, 16 May 2016 17:50:05 +0000 Subject: [PATCH 04/16] Changed BlueForm to also encapsulate button controls with the arrangement code. Updated the version. --- lib/ramaze/helper/blue_form.rb | 4 +++- lib/ramaze/version.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/ramaze/helper/blue_form.rb b/lib/ramaze/helper/blue_form.rb index 0dd2b4c2..1014fcac 100644 --- a/lib/ramaze/helper/blue_form.rb +++ b/lib/ramaze/helper/blue_form.rb @@ -797,7 +797,9 @@ def tag(type, name, args={}, opts={}) when :button, :image, :reset, :submit args[:type] = type args[:name] = name unless args.has_key?(:name) || name.nil? - @g.input(args) + @input_wrapper.call do + @g.input(args) + end when :textarea args[:name] = name unless args.has_key?(:name) || name.nil? diff --git a/lib/ramaze/version.rb b/lib/ramaze/version.rb index 4b5b716a..c8473f9c 100644 --- a/lib/ramaze/version.rb +++ b/lib/ramaze/version.rb @@ -1,3 +1,3 @@ module Ramaze - VERSION = '2012.12.08' + VERSION = '2016.05.14' end From f3bdaae217c5b7607b4bd514d958ffac8d7d3ace Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Tue, 17 May 2016 21:20:07 +0000 Subject: [PATCH 05/16] This is the proto directory to add MySQL database to the created project. --- lib/proto-mysql2/app.rb | 10 ++++++ lib/proto-mysql2/controller/main.rb | 30 ++++++++++++++++ lib/proto-mysql2/database.yml | 34 ++++++++++++++++++ lib/proto-mysql2/model/init.rb | 15 ++++++++ lib/proto-mysql2/view/index.xhtml | 55 +++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+) create mode 100644 lib/proto-mysql2/app.rb create mode 100644 lib/proto-mysql2/controller/main.rb create mode 100644 lib/proto-mysql2/database.yml create mode 100644 lib/proto-mysql2/model/init.rb create mode 100644 lib/proto-mysql2/view/index.xhtml diff --git a/lib/proto-mysql2/app.rb b/lib/proto-mysql2/app.rb new file mode 100644 index 00000000..75c5fc8e --- /dev/null +++ b/lib/proto-mysql2/app.rb @@ -0,0 +1,10 @@ +# This file contains your application, it requires dependencies and necessary +# parts of the application. +require 'rubygems' +require 'ramaze' + +# Make sure that Ramaze knows where you are +Ramaze.options.roots = [__DIR__] + +require __DIR__('model/init') +require __DIR__('controller/init') diff --git a/lib/proto-mysql2/controller/main.rb b/lib/proto-mysql2/controller/main.rb new file mode 100644 index 00000000..a795d026 --- /dev/null +++ b/lib/proto-mysql2/controller/main.rb @@ -0,0 +1,30 @@ +# Default url mappings are: +# +# * a controller called Main is mapped on the root of the site: / +# * a controller called Something is mapped on: /something +# +# If you want to override this, add a line like this inside the class: +# +# map '/otherurl' +# +# this will force the controller to be mounted on: /otherurl. +class MainController < Controller + # the index action is called automatically when no other action is specified + def index + @title = 'Welcome to Ramaze!' + begin + @database = DB["select database() as db"].first + rescue => e + @database = {:db => nil, :error => e.to_s} + end + end + + # the string returned at the end of the function is used as the html body + # if there is no template for the action. if there is a template, the string + # is silently ignored + def notemplate + @title = 'Welcome to Ramaze!' + + return 'There is no \'notemplate.xhtml\' associated with this action.' + end +end diff --git a/lib/proto-mysql2/database.yml b/lib/proto-mysql2/database.yml new file mode 100644 index 00000000..8374e0ee --- /dev/null +++ b/lib/proto-mysql2/database.yml @@ -0,0 +1,34 @@ +dev: + adapter: {adapter} + host: {server} + database: {dbname} + username: {username} + password: {password} + encoding: utf8 + reconnect: false + port: 3306 + +# Other Options (See https://github.com/brianmario/mysql2/blob/master/README.md) +# socket: '/path/to/mysql.sock' +# flags: REMEMBER_OPTIONS | LONG_PASSWORD | LONG_FLAG | TRANSACTIONS | PROTOCOL_41 | SECURE_CONNECTION | MULTI_STATEMENTS +# encoding: 'utf8' +# read_timeout: seconds +# write_timeout: seconds +# connect_timeout: seconds +# reconnect: true/false +# local_infile: true/false +# secure_auth: true/false +# default_file: '/path/to/my.cfg' +# default_group: 'my.cfg section' +# init_command => sql + +live: + adapter: {adapter} + host: {server} + database: {dbname} + username: {username} + password: {password} + encoding: utf8 + reconnect: false + port: 3306 + diff --git a/lib/proto-mysql2/model/init.rb b/lib/proto-mysql2/model/init.rb new file mode 100644 index 00000000..7bccae54 --- /dev/null +++ b/lib/proto-mysql2/model/init.rb @@ -0,0 +1,15 @@ +# Open the database +require 'ramaze' +require 'mysql2' +require 'sequel' +require 'yaml' + +# open up the appropriate database and log it +Host = YAML.load_file("#{Ramaze.options.roots[0]}/database.yml")[ENV['MODE']] +DB = Sequel.connect(Host) +Ramaze::Log.info "Database \"#{Host['database']}\" opened" + +# Require all models in the models folder +Dir.glob('model/*.rb').each do |model| + require("#{Ramaze.options.roots[0]}/#{model}") +end diff --git a/lib/proto-mysql2/view/index.xhtml b/lib/proto-mysql2/view/index.xhtml new file mode 100644 index 00000000..ec9ae75f --- /dev/null +++ b/lib/proto-mysql2/view/index.xhtml @@ -0,0 +1,55 @@ +

+ + Congratulations, Ramaze is running fine and your database "#{@database[:db]}" is open on MySQL.
+ You can start working on your application. + + Sorry, but your database isn't working properly.
+ The error message MySQL returned was:
+   #{@database[:error]}. + +

+ +

+ You can play around with this prototype by changing the following: +

+ +
    +
  • + view/index.xhtml: the content of this page. +
  • +
  • + layout/default.xhtml: the layout for this page. +
  • +
  • + controller/main.rb: the controller responsible for server this page. +
  • +
  • + database.yml: the database settings. +
  • +
+ +

+ For more information, check out ramaze.net and + the documentation. +
+ You can also read the + YARD documentation + or browse around the Ramaze source code. +

+ +

+ For help with Ramaze, visit + #ramaze on irc.freenode.net. +
+ You can use Mibbit, + an AJAX based IRC client or + + the official freenode irc java applet + . +

+ +

+ Feel free to post to the + Ramaze Google Group, your + first mail has to go through moderation, so please be patient. +

From 5f54c4f75855db1998fa2035d8b9a512ff0df84f Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Tue, 17 May 2016 21:21:26 +0000 Subject: [PATCH 06/16] Fixed a problem where 'ramaze create -h' wouldn't work. --- lib/ramaze/bin/runner.rb | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/ramaze/bin/runner.rb b/lib/ramaze/bin/runner.rb index ff73150d..356fbf16 100644 --- a/lib/ramaze/bin/runner.rb +++ b/lib/ramaze/bin/runner.rb @@ -34,7 +34,10 @@ module Runner ramaze [COMMAND] [OPTIONS] Example: - ramaze create blog + ramaze --help # this message + ramaze create -h # help for the create command + ramaze create blog # create a project named blog + ramaze create blog -a mysql2 -d blog_dev -u bloguser -p dFLaWp3uts97pFwcdz7 # same, but with database TXT ## @@ -50,6 +53,7 @@ module Runner # ARGV by default. # def self.run(argv=ARGV) + help = false op = OptionParser.new do |opt| opt.banner = Banner opt.summary_indent = ' ' @@ -65,9 +69,9 @@ def self.run(argv=ARGV) exit end + # Show the help message opt.on('-h', '--help', 'Shows this help message') do - puts op - exit + help = true end end @@ -85,7 +89,12 @@ def self.run(argv=ARGV) cmd = Commands[cmd].new cmd.run(argv) else - abort 'The specified command is invalid' + if help + puts op + exit + else + abort 'The specified command is invalid' + end end end From 09e202e0a28f5f99a4568bb0751de1fd9536b841 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Tue, 17 May 2016 21:26:12 +0000 Subject: [PATCH 07/16] Added the capability of creating a database connection at the same time the project is created. The create function copies in a directory named 'proto- false + :force => false, + :adapter => nil, + :server => 'localhost', + :dbname => 'your_dbname', + :username => 'your_username', + :password => 'your_password' } @opts = OptionParser.new do |opt| @@ -46,12 +60,32 @@ def initialize opt.separator "\nOptions:\n" + opt.on('-a', '--adapter adapter', 'Specifies the database adapter name [no default]') do |adapter| + @options[:adapter] = adapter + end + + opt.on('-s', '--server server', 'Specifies the database server(host) name [default: localhost]') do |server| + @options[:server] = server + end + + opt.on('-d', '--dbname dbname', 'Specifies the database dbname [default: your_dbname]') do |dbname| + @options[:dbname] = dbname + end + + opt.on('-u', '--username username', 'Specifies the database username [default: your_username]') do |username| + @options[:username] = username + end + + opt.on('-p', '--password password', 'Specifies the database password [default: your_password]') do |password| + @options[:password] = password + end + opt.on('-f', '--force', 'Overwrites existing directories') do @options[:force] = true end opt.on('-h', '--help', 'Shows this help message') do - puts @opts + puts @opts.to_s exit end end @@ -68,10 +102,12 @@ def run(argv = []) @opts.parse!(argv) path = argv.delete_at(0) - proto = __DIR__('../../proto') - abort 'You need to specify a name for your application' if path.nil? + proto = __DIR__('../../proto') + proto_adapter = if @options[:adapter] then "#{proto}-#{@options[:adapter]}" else nil end + abort "The #{@options[:adapter]} adapter is not supported--See the documentation" if proto_adapter && Dir[proto_adapter].empty? + if File.directory?(path) and @options[:force] === false abort 'The specified application already exists, use -f to overwrite it' end @@ -82,9 +118,24 @@ def run(argv = []) begin FileUtils.cp_r(proto, path) + + if proto_adapter + # copy whatever is in the proto-adapter directory + FileUtils.cp_r(Dir.glob("#{proto_adapter}/**"), path) + + # update the database.yml file in the new project + yml = nil + File::open("#{path}/database.yml",'r') { |f| yml = f.read } + [:adapter, :server, :dbname, :username, :password].each do |opt| + yml.gsub!("{#{opt.to_s}}",@options[opt]) + end + File::open("#{path}/database.yml",'w') { |f| yml = f.write(yml) } + end + puts "The application has been generated and saved in #{path}" - rescue - abort 'The application could not be generated' + rescue => e + puts e.backtrace[0..5] + abort "#{e}\nThe application could not be generated" end end end # Create From f04de60c7b472606f017fbfc14fe4f9ba6863ea6 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Tue, 17 May 2016 21:27:27 +0000 Subject: [PATCH 08/16] Modified the controller init to load all controllers. --- lib/proto/controller/init.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/proto/controller/init.rb b/lib/proto/controller/init.rb index 0725c9bc..43a82eb7 100644 --- a/lib/proto/controller/init.rb +++ b/lib/proto/controller/init.rb @@ -8,11 +8,7 @@ class Controller < Ramaze::Controller engine :etanni end -# Here you can require all your other controllers. Note that if you have multiple -# controllers you might want to do something like the following: -# -# Dir.glob('controller/*.rb').each do |controller| -# require(controller) -# end -# -require __DIR__('main') +# Require all controllers in the controllers folder +Dir.glob('controller/*.rb').each do |controller| + require("#{Ramaze.options.roots[0]}/#{controller}") +end From a9ba6345d9287cb37f48f255c93d2f40f8580ac8 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Tue, 17 May 2016 21:28:09 +0000 Subject: [PATCH 09/16] Updated the version date for testing. --- lib/ramaze/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ramaze/version.rb b/lib/ramaze/version.rb index c8473f9c..72bdb57e 100644 --- a/lib/ramaze/version.rb +++ b/lib/ramaze/version.rb @@ -1,3 +1,3 @@ module Ramaze - VERSION = '2016.05.14' + VERSION = '2016.05.17' end From 3093b867feb5eeae063e9676de0df9ada316fa36 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Wed, 18 May 2016 04:06:44 +0000 Subject: [PATCH 10/16] This is the proto-sqlite for using the Sqlite3 database with Ramaze. --- lib/proto-sqlite/app.rb | 10 ++++++ lib/proto-sqlite/controller/main.rb | 30 ++++++++++++++++ lib/proto-sqlite/database.yml | 8 +++++ lib/proto-sqlite/model/init.rb | 15 ++++++++ lib/proto-sqlite/view/index.xhtml | 55 +++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 lib/proto-sqlite/app.rb create mode 100644 lib/proto-sqlite/controller/main.rb create mode 100644 lib/proto-sqlite/database.yml create mode 100644 lib/proto-sqlite/model/init.rb create mode 100644 lib/proto-sqlite/view/index.xhtml diff --git a/lib/proto-sqlite/app.rb b/lib/proto-sqlite/app.rb new file mode 100644 index 00000000..75c5fc8e --- /dev/null +++ b/lib/proto-sqlite/app.rb @@ -0,0 +1,10 @@ +# This file contains your application, it requires dependencies and necessary +# parts of the application. +require 'rubygems' +require 'ramaze' + +# Make sure that Ramaze knows where you are +Ramaze.options.roots = [__DIR__] + +require __DIR__('model/init') +require __DIR__('controller/init') diff --git a/lib/proto-sqlite/controller/main.rb b/lib/proto-sqlite/controller/main.rb new file mode 100644 index 00000000..a4421d6c --- /dev/null +++ b/lib/proto-sqlite/controller/main.rb @@ -0,0 +1,30 @@ +# Default url mappings are: +# +# * a controller called Main is mapped on the root of the site: / +# * a controller called Something is mapped on: /something +# +# If you want to override this, add a line like this inside the class: +# +# map '/otherurl' +# +# this will force the controller to be mounted on: /otherurl. +class MainController < Controller + # the index action is called automatically when no other action is specified + def index + @title = 'Welcome to Ramaze!' + begin + @database = DB["select random() as db"].first + rescue => e + @database = {:db => nil, :error => e.to_s} + end + end + + # the string returned at the end of the function is used as the html body + # if there is no template for the action. if there is a template, the string + # is silently ignored + def notemplate + @title = 'Welcome to Ramaze!' + + return 'There is no \'notemplate.xhtml\' associated with this action.' + end +end diff --git a/lib/proto-sqlite/database.yml b/lib/proto-sqlite/database.yml new file mode 100644 index 00000000..28191671 --- /dev/null +++ b/lib/proto-sqlite/database.yml @@ -0,0 +1,8 @@ +dev: + adapter: {adapter} + database: {dbname} + +live: + adapter: {adapter} + database: {dbname} + diff --git a/lib/proto-sqlite/model/init.rb b/lib/proto-sqlite/model/init.rb new file mode 100644 index 00000000..8e586fc3 --- /dev/null +++ b/lib/proto-sqlite/model/init.rb @@ -0,0 +1,15 @@ +# Open the database +require 'ramaze' +require 'sqlite3' +require 'sequel' +require 'yaml' + +# open up the appropriate database and log it +Host = YAML.load_file("#{Ramaze.options.roots[0]}/database.yml")[ENV['MODE']] +DB = Sequel.connect("#{Host['adapter']}://#{Host['database']}") +Ramaze::Log.info "Database \"#{Host['database']}\" opened" + +# Require all models in the models folder +Dir.glob('model/*.rb').each do |model| + require("#{Ramaze.options.roots[0]}/#{model}") +end diff --git a/lib/proto-sqlite/view/index.xhtml b/lib/proto-sqlite/view/index.xhtml new file mode 100644 index 00000000..4b303f35 --- /dev/null +++ b/lib/proto-sqlite/view/index.xhtml @@ -0,0 +1,55 @@ +

+ + Congratulations, Ramaze is running fine and your database is open on MySQL.
+ You can start working on your application. + + Sorry, but your database isn't working properly.
+ The error message MySQL returned was:
+   #{@database[:error]}. + +

+ +

+ You can play around with this prototype by changing the following: +

+ +
    +
  • + view/index.xhtml: the content of this page. +
  • +
  • + layout/default.xhtml: the layout for this page. +
  • +
  • + controller/main.rb: the controller responsible for server this page. +
  • +
  • + database.yml: the database settings. +
  • +
+ +

+ For more information, check out ramaze.net and + the documentation. +
+ You can also read the + YARD documentation + or browse around the Ramaze source code. +

+ +

+ For help with Ramaze, visit + #ramaze on irc.freenode.net. +
+ You can use Mibbit, + an AJAX based IRC client or + + the official freenode irc java applet + . +

+ +

+ Feel free to post to the + Ramaze Google Group, your + first mail has to go through moderation, so please be patient. +

From 05a9cfcc9f9a7ce435d195911225afb8a2e3ef0a Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Mon, 4 Jul 2016 17:58:57 +0000 Subject: [PATCH 11/16] Added 'to_html' to gestalt. By being here, it can be used for both gestalst and blue_form. --- lib/ramaze/gestalt.rb | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/lib/ramaze/gestalt.rb b/lib/ramaze/gestalt.rb index 9427b2be..43c6e4a5 100644 --- a/lib/ramaze/gestalt.rb +++ b/lib/ramaze/gestalt.rb @@ -194,5 +194,55 @@ def to_s @out.join end alias to_str to_s + + ## + # Method used for converting the results of the Gestalt helper to a + # human readable string. This isn't recommended for production because + # it requires much more time to generate the HTML output than to_s. + # + # @return [String] The formatted form output + # + def to_html + # Combine the sub-parts to form whole tags or whole in-between texts + parts = [] + tag = "" + @out.each do |fragment| + case + when fragment[0] == '<' + if tag.empty? + tag << fragment + else + parts << tag + tag = fragment + end + when fragment[-1] == '>' + tag << fragment + parts << tag + tag = "" + else + tag << fragment + end # case + end + parts << tag if tag + # output the segments, but adjust the indentation + indent = 0 + html = "" + parts.each do |part| + case + when part[0..1] == '') + # self terminating tag -- no change in indent + when (part[0] == '<') && (part[1] != '/') + indent += 1 + end + end + # return the formatted string + return html + end # to_html + end # Gestalt end # Ramaze From 1bb5daefbb436cc0b44a92d4f4bccde974335640 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Mon, 4 Jul 2016 18:04:32 +0000 Subject: [PATCH 12/16] Removed 'to_html' and put it into gestalt where it an be available for both. --- lib/ramaze/helper/blue_form.rb | 51 ---------------------------------- 1 file changed, 51 deletions(-) diff --git a/lib/ramaze/helper/blue_form.rb b/lib/ramaze/helper/blue_form.rb index 1014fcac..d917bb71 100644 --- a/lib/ramaze/helper/blue_form.rb +++ b/lib/ramaze/helper/blue_form.rb @@ -696,57 +696,6 @@ def to_s @g.to_s end - ## - # Method used for converting the results of the BlueForm helper to a - # human readable string. This isn't recommended for production because - # it requires much more time to generate the HTML output than to_s. - # - # @return [String] The formatted form output - # - def to_html - # Combine the sub-parts to form whole tags or whole in-between texts - parts = [] - tag = "" - @g.out.each do |fragment| - case - when fragment[0] == '<' - if tag.empty? - tag << fragment - else - parts << tag - tag = fragment - end - when fragment[-1] == '>' - tag << fragment - parts << tag - tag = "" - else - tag << fragment - end # case - end - parts << tag if tag - - # output the segments, but adjust the indentation - indent = 0 - html = "" - parts.each do |part| - case - when part[0..1] == '') - # self terminating tag -- no change in indent - when (part[0] == '<') && (part[1] != '/') - indent += 1 - end - end - - # return the formatted string - return html - end - ## # Generate a URL. # From ca5376a441881bfeec49a0a9d07638713b5fceff Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Mon, 4 Jul 2016 18:12:21 +0000 Subject: [PATCH 13/16] Added back a stub for 'to_html'. --- lib/ramaze/helper/blue_form.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/ramaze/helper/blue_form.rb b/lib/ramaze/helper/blue_form.rb index d917bb71..5398d77f 100644 --- a/lib/ramaze/helper/blue_form.rb +++ b/lib/ramaze/helper/blue_form.rb @@ -696,6 +696,17 @@ def to_s @g.to_s end + ## + # Method used for converting the results of the BlueForm helper to a + # human readable string. This isn't recommended for production because + # it requires much more time to generate the HTML output than to_s. + # + # @return [String] The formatted form output + # + def to_html + @g.to_html + end + ## # Generate a URL. # From e9981bd56e8af0a1915c7347f15b5aee43c98003 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Mon, 4 Jul 2016 20:26:10 +0000 Subject: [PATCH 14/16] If you try to use BlueForm and Gestalt in the same controller, 'form_for' fails because when you use 'helper :gestalt, :blue_form', the Gestalt.new in BlueForm sees the Gestalt as Module rather than Class. The fix is to use 'Ramaze::Gestalt.new' in BlueForm. --- lib/ramaze/helper/blue_form.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ramaze/helper/blue_form.rb b/lib/ramaze/helper/blue_form.rb index 5398d77f..a252eabc 100644 --- a/lib/ramaze/helper/blue_form.rb +++ b/lib/ramaze/helper/blue_form.rb @@ -224,7 +224,7 @@ def initialize(form_object, options) @arrangement = options.delete(:arrangement) @arrangement = :paragraph if ([:table,:paragraph,:none].index(@arrangement)).nil? @form_args = options.dup - @g = Gestalt.new + @g = Ramaze::Gestalt.new end ## From 1ad2033c8f785562029c8a229082a4089eea30ca Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Wed, 6 Jul 2016 02:01:25 +0000 Subject: [PATCH 15/16] The 'to_html' added to Gestalt was modifying the @out array causing a problem when @g.to_html was called, then @g.to_s was called after that. Fixed by using a String.new to copy the pieces before manipulating them. --- lib/ramaze/gestalt.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ramaze/gestalt.rb b/lib/ramaze/gestalt.rb index 43c6e4a5..a7981c97 100644 --- a/lib/ramaze/gestalt.rb +++ b/lib/ramaze/gestalt.rb @@ -206,7 +206,8 @@ def to_html # Combine the sub-parts to form whole tags or whole in-between texts parts = [] tag = "" - @out.each do |fragment| + @out.each do |frag| + fragment = String.new(frag) case when fragment[0] == '<' if tag.empty? From a8fe1b36f76a4b7f6b9ab98afe90b2dfcb278e62 Mon Sep 17 00:00:00 2001 From: "Michael J. Welch, Ph.D" Date: Tue, 12 Jul 2016 01:38:15 +0000 Subject: [PATCH 16/16] Scaffolding is an 'on-the-fly' scaffold generator for use during development and debugging. --- lib/ramaze/scaffolding.rb | 428 +++++++++++++++++++++++++++++++++++++ spec/ramaze/scaffolding.rb | 268 +++++++++++++++++++++++ 2 files changed, 696 insertions(+) create mode 100644 lib/ramaze/scaffolding.rb create mode 100644 spec/ramaze/scaffolding.rb diff --git a/lib/ramaze/scaffolding.rb b/lib/ramaze/scaffolding.rb new file mode 100644 index 00000000..9cd56ca9 --- /dev/null +++ b/lib/ramaze/scaffolding.rb @@ -0,0 +1,428 @@ +require "ramaze/gestalt" + +module Ramaze + ## + # The scaffolding class is a generator that builds a simple CRUD controller for any database table. + # This capability is meant only for development, as the resulting controllers have NO SECURITY + # code built into them. These controllers make it easy to manipulate the database tables during + # development. + # + # The controllers are encapsulated in ... tags so that you can write + # CSS statements that will only apply to scaffolding. + # + # The way it works is that it reads the database table to get the list of fields in the table. + # Next, it takes a prewritten CRUD controller and tailors it based on the fields in the table. + # It's more complicated to do than than it seems, but the output of this generator is the + # controller in the form of a String object. + # + # The common way to use it is to call it to create the controller, the use class_eval to install it + # on the fly. You can do this in your 'controller/init.rb' module if you want it to be permanent, + # but be sure you only generate the controllers in 'dev' mode because, again, they have NO SECURITY + # built into them. + # + # These controllers are created once during load time, so the cost of using them is negligible + # while Ramaze is running. All the configuring is done at load time, so the generated code does + # not need to look at the table's schema to operate. + # + # If these controllers are in your production version, any idiot hacker can use them to examine + # and modify your database. So let me repeat: + # + # THESE CONTROLLERS ARE FOR DEVELOPMENT USE ONLY. + # + # The test table is: + # DB::drop_table?(:coltypes) + # DB::create_table(:coltypes) do # common database type used + # primary_key :id # int(11) + primary key + # Integer :int11 # int(11) + # String :vc255 # varchar(255) + # String :vc50, :size=>50 # varchar(50) + # String :c255, :fixed=>true # char(255) + # String :c50, :fixed=>true, :size=>50 # char(50) + # String :text, :text=>true # text + # File :blob # blob + # Fixnum :fixnum # int(11) + # Bignum :bignum # bigint(20) + # Float :dblflt # double + # BigDecimal :bigdec # decimal(10,0) + # BigDecimal :big6dec, :size=>6 # decimal(6,0) + # BigDecimal :big10dec2, :size=>[10, 2] # decimal(10,2) + # Date :justdate # date + # DateTime :datetime # datetime + # Time :justtime # datetime + # Time :timeonly, :only_time=>true # time + # Numeric :numeric # decimal(10,0) + # TrueClass :booltrue # tinyint(1) + # FalseClass :boolfalse # tinyint(1) + # DateTime :created_at # datetime + # DateTime :updated_at # datetime + # end + # + class Scaffolding + + Sequel.extension :inflector # http://sequel.jeremyevans.net/rdoc-plugins/classes/String.html + + ## + # This is the core CRUD controller. It has no comments for efficiency. There's no magic + # here: it's just simple Ruby/Sequel programming. + def initialize + @prototype = [ + "class NilClass", :eol, + " def strftime(pattern)", :eol, + " \"\"", :eol, + " end", :eol, + "end", :eol, + :eol, + "class ", :model_string_singular_camel, "Controller < Controller", :eol, + :eol, + " map '/", :model_string_singular, "'", :eol, + :eol, + " def initialize", :eol, + " @columns = ", :columns_list, :eol, + " @index_columns = ", :index_columns_list, :eol, + " @new_columns = ", :new_columns_list, :eol, + " @show_columns = ", :show_columns_list, :eol, + " @edit_columns = ", :edit_columns_list, :eol, + " end", :eol, + :eol, + " def index", :eol, + " @g = Ramaze::Gestalt.new", :eol, + " @title = \"List of ", :model_string_plural_camel, "\"", :eol, + " @g.scaffolding do", :eol, + " rows = ", :model_string_singular_camel, ".select(*@index_columns).all", :eol, + " @g.h3 { @title }", :eol, + " @g.p do", :eol, + " @g.a(:href=>\"/", :model_string_singular, "/new\") { \"new\" }", :eol, + " end", :eol, + " @g.table do", :eol, + " # create the heading", :eol, + " @g.tr do", :eol, + " @index_columns.each do |col|", :eol, + " @g.td do", :eol, + " @g.strong { col.to_s.titleize }", :eol, + " end", :eol, + " end", :eol, + " end", :eol, + " # list all the rows", :eol, + " rows.each do |row|", :eol, + " @g.tr do", :eol, + " row.each do |col,value|", :eol, + " @g.td { value.to_s }", :eol, + " end", :eol, + " @g.td do", :eol, + " @g.a(:href=>\"/", :model_string_singular, "/show?id=%s\"%row[:id]) { \"show\" }", :eol, + " @g << \" | \"", :eol, + " @g.a(:href=>\"/", :model_string_singular, "/edit?id=%s\"%row[:id]) { \"edit\" }", :eol, + " @g << \" | \"", :eol, + " @g.a(:href=>\"/", :model_string_singular, "/show?id=%s&delete\"%row[:id]) { \"delete\" }", :eol, + " end", :eol, + " end", :eol, + " end", :eol, + " end", :eol, + " end", :eol, + " @g.to_s", :eol, + " end", :eol, + :eol, + " def new", :eol, + " @g = Ramaze::Gestalt.new", :eol, + " @title = \"New ", :model_string_singular_camel, "\"", :eol, + " @g.scaffolding do", :eol, + " @g.h3 { @title }", :eol, + " row = ", :model_string_singular_camel, ".new", :eol, + " @g.form(:method=>:post, :action=>:save_new) do", :eol, + " @g.table do", :eol, + :new_columns, + " end", :eol, + " @g.br", :eol, + " @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Back\")", :eol, + " @g << \" \"", :eol, + " @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Save\")", :eol, + " end", :eol, + " end", :eol, + " @g.to_s", :eol, + " end", :eol, + :eol, + " def show", :eol, + " @g = Ramaze::Gestalt.new", :eol, + " @title = \"Show ", :model_string_singular_camel, "\"", :eol, + " @g.scaffolding do", :eol, + " @g.h3 { @title }", :eol, + " row = ", :model_string_singular_camel, ".where(:id=>session.request.params['id']).first", :eol, + " @g.form(:method=>:post, :action=>:save_show) do", :eol, + " @g.input(:type=>:hidden, :name=>:id, :value=>row.id)", :eol, + " @g.table do", :eol, + :show_columns, + " end", :eol, + " @g.br", :eol, + " @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Back\")", :eol, + " if session.request.params.has_key?('delete')", :eol, + " @g << \" \"", :eol, + " @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Delete\")", :eol, + " end", :eol, + " end", :eol, + " end", :eol, + " @g.to_s", :eol, + " end", :eol, + :eol, + " def edit", :eol, + " @g = Ramaze::Gestalt.new", :eol, + " @title = \"Edit ", :model_string_singular_camel, "\"", :eol, + " @g.scaffolding do", :eol, + " @g.h3 { @title }", :eol, + " row = ", :model_string_singular_camel, ".where(:id=>session.request.params['id']).first", :eol, + " row.updated_at = Time.now", :eol, + " @g.form(:method=>:post, :action=>:save_edit) do", :eol, + " @g.input(:type=>:hidden, :name=>:id, :value=>row.id)", :eol, + " @g.table do", :eol, + :edit_columns, + " end", :eol, + " @g.br", :eol, + " @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Back\")", :eol, + " @g << \" \"", :eol, + " @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Save\")", :eol, + " end", :eol, + " end", :eol, + " @g.to_s", :eol, + " end", :eol, + :eol, + " def save_new", :eol, + " if session.request.params['goto']==\"Save\"", :eol, + " row = ", :model_string_singular_camel, ".new", :eol, + " row.set_fields(session.request.params, @new_columns, :missing=>:skip)", :eol, + " row.id = nil", :eol, + " row.created_at = Time.now if @columns.index(:created_at)", :eol, + " row.updated_at = Time.now if @columns.index(:updated_at)", :eol, + " row.save", :eol, + " end", :eol, + " redirect(\"/", :model_string_singular, "/index\")", :eol, + " end", :eol, + :eol, + " def save_show", :eol, + " if session.request.params['goto']==\"Delete\"", :eol, + " row = ", :model_string_singular_camel, ".where(:id=>session.request.params['id']).first", :eol, + " row.delete", :eol, + " end", :eol, + " redirect(\"/", :model_string_singular, "/index\")", :eol, + " end", :eol, + :eol, + " def save_edit", :eol, + " if session.request.params['goto']==\"Save\"", :eol, + " row = ", :model_string_singular_camel, ".where(:id=>session.request.params['id']).first", :eol, + " row.set_fields(session.request.params, @edit_columns, :missing=>:skip)", :eol, + " row.updated_at = Time.now if @columns.index(:updated_at)", :eol, + " row.save", :eol, + " redirect(\"/", :model_string_singular, "/index\")", :eol, + " end", :eol, + " redirect(\"/", :model_string_singular, "/index\")", :eol, + " end", :eol, + :eol, + "end", :eol ] + end + + ## + # This method builds the controller. + # + # @param Hash args A has containing the options. + # + # @param HashElement :model The name of the database table for + # which the controller will be built. This name must be lower + # case singular symbol and there must be a table in the database + # which has this name as lower case plural. + # + # @example A table named 'coltypes'. The table must exist in the DB. + # + # :model => :coltype + # + # @param HashElement :index_columns => [ ... ] A list of the + # columns to display in the 'index' method as a list of Symbols. + # Since fields are listed horizontally, be careful not to list + # too many. + # + # @example A list of fields. + # + # :index_columns=>[:id, :vc255, :int11] + # + # @param HashElement :new_columns => [ ... ] A list of the columns + # to be displayed on the 'new' page. DON'T include the table's + # primary key in this list. Usually you would not include :created_at + # or :updated_at, if the table has those fields. The controller + # updates those automatically. + # + # @param HashElement :show_columns => [ ... ] A list of columns + # to be displayed on the 'show' page. Just omit this option to + # show all the fields (recommended). + # + # @option HashElement :edit_columns => [ ... ] A list of columns + # to permit editing on the 'edit' page. DON'T include the table's + # primary key in this list. Usually you would not include :created_at + # or :updated_at, if the table has those fields. The controller + # updates those automatically. + # + # BUILDING A CONTROLLER + # If you do not have a Sequel::Model for the table, create an empty one. + # + # @example Create an empty 'coltypes' table model. + # + # class Coltype < Sequel::Model + # end + # + # Call the build routine, and direct the output to 'class_eval'. + # + # In 'controller/init.rb' (just a suggestion), add this code: + # + # Object::class_eval(Scaffolding.new.build_controller( + # :model=>:coltype, + # :index_columns=>[:id, :vc255, :int11], + # :new_columns=>[:id, :vc255, :int11], + # :show_columns=>[:id, :vc255, :int11], + # :edit_columns=>[:id, :vc255, :int11] + # )) + # + def build_controller(args) + # Prepare the substitution parameters + @constant = constant = {} + + constant[:model_symbol_singular] = args[:model] + constant[:model_string_singular] = args[:model].to_s + constant[:model_string_plural] = args[:model].to_s.pluralize + constant[:model_string_plural_camel] = constant[:model_string_singular].pluralize.camelize + constant[:model_string_singular_camel] = constant[:model_string_singular].camelize + + @schema = DB.schema(constant[:model_string_plural]).to_h + @columns = @schema.keys + @index_columns = if args.has_key?(:index_columns) then args[:index_columns] else @columns end + @new_columns = if args.has_key?(:new_columns) then args[:new_columns] else @columns end + @show_columns = if args.has_key?(:show_columns) then args[:show_columns] else @columns end + @edit_columns = if args.has_key?(:edit_columns) then args[:edit_columns] else @columns end + + @schema.each do |name,properties| + # see if it has something like decimal[10,2], decimal[2], or just decimal + db_type = properties[:db_type] + case + when m = db_type.match(/^([a-z]*)\(([0-9]*),([0-9]*)\)$/) + a = m[2].to_i + b = m[3].to_i + when m = db_type.match(/^([a-z]*)\(([0-9]*)\)$/) + a = m[2].to_i + b = 0 + else + a = 0 + b = 0 + end + + case properties[:type] + when :integer + a = 11 if a==0 + width = a + boxtype = :number + when :string + a = 32 if a==0 || a>80 + width = a + boxtype = if properties[:db_type]=='text' then :textarea else :text end + when :blob + width = 80 + boxtype = :textarea + when :float + a = 16 if a==0 + width = a + boxtype = :text + when :decimal + a = 12 if a==0 + width = a + boxtype = :text + when :date + width = 10 + boxtype = :date + when :datetime + width = 17 + boxtype = :datetime + when :time + width = 8 + boxtype = :time + when :boolean + width = 0 + boxtype = :checkbox + end + + properties[:width] = width + properties[:boxtype] = boxtype + end + + @source = [] + source(@prototype) + @source.join + end + +private + + # As the prototype code above is passed to this method, it copies Strings to the + # output, and processes the Symbols as appropriate. It cannot be called by the user. + def source(objs) + objs.each do |obj| + case + when obj==:new_columns + @new_columns.each do |col| + field(:new, col, @schema[col]) + end + when obj==:show_columns + @show_columns.each do |col| + field(:show, col, @schema[col]) + end + when obj==:edit_columns + @edit_columns.each do |col| + field(:edit, col, @schema[col]) + end + when obj==:columns_list + @source << @columns.inspect + when obj==:index_columns_list + @source << @index_columns.inspect + when obj==:new_columns_list + @source << @new_columns.inspect + when obj==:show_columns_list + @source << @show_columns.inspect + when obj==:edit_columns_list + @source << @edit_columns.inspect + when obj==:eol + @source << "\n" + when obj.class==String + @source << obj + when obj.class==Symbol + @source << @constant[obj] + end + end + end + + ## + # This method is used by 'build_controller' to generate each set of + # code for each field specified. It cannot be called by the user. + def field(type, col, properties) + disabled = if type==:show then ", :disabled=>true" else "" end + source([" @g.tr do", :eol]) + source([" @g.td { @g.strong { \"#{col.to_s.humanize}\" } }", :eol]) + source([" @g.td do", :eol]) + case properties[:boxtype] + when :checkbox + source([ + " @g.input(:type=>:hidden, :name=>#{col.inspect}, :id=>\"form_#{col.to_s}\", :value=>0)", :eol, + " opts = {:type=>:checkbox, :name=>#{col.inspect}, :id=>\"form_#{col.to_s}\", :value=>1#{disabled}}", :eol, + " opts[:checked] = true if row[#{col.inspect}]", :eol, + " @g.input(opts)", :eol ]) + when :number + source([" opts = @g.input(:type=>:number, :name=>#{col.inspect}, :value=>row[#{col.inspect}], :size=>#{properties[:width]}, :id=>\"form_#{col}\"#{disabled})", :eol]) + when :text + source([" @g.input(:type=>:text, :name=>#{col.inspect}, :value=>row[#{col.inspect}], :size=>#{properties[:width]}, :id=>\"form_#{col}\"#{disabled})", :eol]) + when :date + source([" @g.input(:type=>:text, :name=>#{col.inspect}, :value=>row[#{col.inspect}].strftime(\"%Y-%m-%d\"), :size=>#{properties[:width]}, :id=>\"form_#{col}\"#{disabled})", :eol]) + when :datetime + source([" @g.input(:type=>:text, :name=>#{col.inspect}, :value=>row[#{col.inspect}].strftime(\"%Y-%m-%d %H:%M:%S\"), :size=>#{properties[:width]}, :id=>\"form_#{col}\"#{disabled})", :eol]) + when :time + source([" @g.input(:type=>:text, :name=>#{col.inspect}, :value=>row[#{col.inspect}].strftime(\"%H:%M:%S\"), :size=>#{properties[:width]}, :id=>\"form_#{col}\"#{disabled})", :eol]) + when :textarea + source([" @g.textarea(:name=>#{col.inspect}, :rows=>5, :cols=>#{properties[:width]}, :id=>\"form_#{col}\"#{disabled}) { row[#{col.inspect}] }", :eol]) + end + source([" end", :eol]) + source([" end", :eol]) + end + + end + +end diff --git a/spec/ramaze/scaffolding.rb b/spec/ramaze/scaffolding.rb new file mode 100644 index 00000000..ac524baa --- /dev/null +++ b/spec/ramaze/scaffolding.rb @@ -0,0 +1,268 @@ +# * Encoding: UTF-8 +# +# Copyright (c) 2009 Michael J. Welch, Ph.D. mjwelchphd@gmail.com +# All files in this distribution are subject to the terms of the MIT license. + +require 'sqlite3' +require 'sequel' + +require File.expand_path('../../../spec/helper', __FILE__) + +require 'ramaze/scaffolding' + +# create some test data +DB = Sequel.sqlite +DB::drop_table?(:coltypes) +DB::create_table(:coltypes) do # common database type used + primary_key :id # int(11) + primary key + Integer :int11 # int(11) + String :vc255 # varchar(255) + String :vc50, :size=>50 # varchar(50) + String :c255, :fixed=>true # char(255) + String :c50, :fixed=>true, :size=>50 # char(50) + String :text, :text=>true # text + File :blob # blob + Fixnum :fixnum # int(11) + Bignum :bignum # bigint(20) + Float :dblflt # double + BigDecimal :bigdec # decimal(10,0) + BigDecimal :big6dec, :size=>6 # decimal(6,0) + BigDecimal :big10dec2, :size=>[10, 2] # decimal(10,2) + Date :justdate # date + DateTime :datetime # datetime + Time :justtime # datetime + Time :timeonly, :only_time=>true # time + Numeric :numeric # decimal(10,0) + TrueClass :booltrue # tinyint(1) + FalseClass :boolfalse # tinyint(1) + DateTime :created_at # datetime + DateTime :updated_at # datetime +end +dataset = DB[:coltypes] +dataset.insert(:vc255=>"Coco", :text=>"Species: Cocosaurus Rex", :blob=>"Good bird!", :created_at=>Time.now, :updated_at=>Time.now) + +describe 'Ramaze::Scaffolding' do + +known_code = <\"/coltype/new\") { \"new\" } + end + @g.table do + # create the heading + @g.tr do + @index_columns.each do |col| + @g.td do + @g.strong { col.to_s.titleize } + end + end + end + # list all the rows + rows.each do |row| + @g.tr do + row.each do |col,value| + @g.td { value.to_s } + end + @g.td do + @g.a(:href=>\"/coltype/show?id=%s\"%row[:id]) { \"show\" } + @g << \" | \" + @g.a(:href=>\"/coltype/edit?id=%s\"%row[:id]) { \"edit\" } + @g << \" | \" + @g.a(:href=>\"/coltype/show?id=%s&delete\"%row[:id]) { \"delete\" } + end + end + end + end + end + @g.to_s + end + + def new + @g = Ramaze::Gestalt.new + @title = \"New Coltype\" + @g.scaffolding do + @g.h3 { @title } + row = Coltype.new + @g.form(:method=>:post, :action=>:save_new) do + @g.table do + @g.tr do + @g.td { @g.strong { \"Id\" } } + @g.td do + opts = @g.input(:type=>:number, :name=>:id, :value=>row[:id], :size=>11, :id=>\"form_id\") + end + end + @g.tr do + @g.td { @g.strong { \"Vc255\" } } + @g.td do + @g.input(:type=>:text, :name=>:vc255, :value=>row[:vc255], :size=>32, :id=>\"form_vc255\") + end + end + @g.tr do + @g.td { @g.strong { \"Int11\" } } + @g.td do + opts = @g.input(:type=>:number, :name=>:int11, :value=>row[:int11], :size=>11, :id=>\"form_int11\") + end + end + end + @g.br + @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Back\") + @g << \" \" + @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Save\") + end + end + @g.to_s + end + + def show + @g = Ramaze::Gestalt.new + @title = \"Show Coltype\" + @g.scaffolding do + @g.h3 { @title } + row = Coltype.where(:id=>session.request.params['id']).first + @g.form(:method=>:post, :action=>:save_show) do + @g.input(:type=>:hidden, :name=>:id, :value=>row.id) + @g.table do + @g.tr do + @g.td { @g.strong { \"Id\" } } + @g.td do + opts = @g.input(:type=>:number, :name=>:id, :value=>row[:id], :size=>11, :id=>\"form_id\", :disabled=>true) + end + end + @g.tr do + @g.td { @g.strong { \"Vc255\" } } + @g.td do + @g.input(:type=>:text, :name=>:vc255, :value=>row[:vc255], :size=>32, :id=>\"form_vc255\", :disabled=>true) + end + end + @g.tr do + @g.td { @g.strong { \"Int11\" } } + @g.td do + opts = @g.input(:type=>:number, :name=>:int11, :value=>row[:int11], :size=>11, :id=>\"form_int11\", :disabled=>true) + end + end + end + @g.br + @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Back\") + if session.request.params.has_key?('delete') + @g << \" \" + @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Delete\") + end + end + end + @g.to_s + end + + def edit + @g = Ramaze::Gestalt.new + @title = \"Edit Coltype\" + @g.scaffolding do + @g.h3 { @title } + row = Coltype.where(:id=>session.request.params['id']).first + row.updated_at = Time.now + @g.form(:method=>:post, :action=>:save_edit) do + @g.input(:type=>:hidden, :name=>:id, :value=>row.id) + @g.table do + @g.tr do + @g.td { @g.strong { \"Id\" } } + @g.td do + opts = @g.input(:type=>:number, :name=>:id, :value=>row[:id], :size=>11, :id=>\"form_id\") + end + end + @g.tr do + @g.td { @g.strong { \"Vc255\" } } + @g.td do + @g.input(:type=>:text, :name=>:vc255, :value=>row[:vc255], :size=>32, :id=>\"form_vc255\") + end + end + @g.tr do + @g.td { @g.strong { \"Int11\" } } + @g.td do + opts = @g.input(:type=>:number, :name=>:int11, :value=>row[:int11], :size=>11, :id=>\"form_int11\") + end + end + end + @g.br + @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Back\") + @g << \" \" + @g.input(:type=>:submit, :id=>\"goto\", :name=>\"goto\", :value=>\"Save\") + end + end + @g.to_s + end + + def save_new + if session.request.params['goto']==\"Save\" + row = Coltype.new + row.set_fields(session.request.params, @new_columns, :missing=>:skip) + row.id = nil + row.created_at = Time.now if @columns.index(:created_at) + row.updated_at = Time.now if @columns.index(:updated_at) + row.save + end + redirect(\"/coltype/index\") + end + + def save_show + if session.request.params['goto']==\"Delete\" + row = Coltype.where(:id=>session.request.params['id']).first + row.delete + end + redirect(\"/coltype/index\") + end + + def save_edit + if session.request.params['goto']==\"Save\" + row = Coltype.where(:id=>session.request.params['id']).first + row.set_fields(session.request.params, @edit_columns, :missing=>:skip) + row.updated_at = Time.now if @columns.index(:updated_at) + row.save + redirect(\"/coltype/index\") + end + redirect(\"/coltype/index\") + end + +end +END_TEXT + + it "should create a CRUD controller for ColtypeController" do + test_code = Ramaze::Scaffolding.new.build_controller( + :model=>:coltype, + :index_columns=>[:id, :vc255, :int11], + :new_columns=>[:id, :vc255, :int11], + :show_columns=>[:id, :vc255, :int11], + :edit_columns=>[:id, :vc255, :int11] + ) + known_code_lines = known_code.split("\n") + test_code_lines = test_code.split("\n") + + n = known_code_lines.size + 0.upto(n-1) do |i| + known_code_lines[i].should == test_code_lines[i] + end + + end + +end