class Showoff::Compiler::Form

Adds form processing to the compiler

@see github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff.rb#L848-L1022

Public Class Methods

form_checked?(modifier) click to toggle source
# File lib/showoff/compiler/form.rb, line 232
def self.form_checked?(modifier)
  modifier.downcase.include?('x') ? "checked='checked'" : ''
end
form_classes(modifier) click to toggle source
# File lib/showoff/compiler/form.rb, line 224
def self.form_classes(modifier)
  modifier.downcase!
  classes = ['response']
  classes << 'correct' if modifier.include?('=')

  classes.join(' ')
end
form_element(id, code, name, required, rhs, text) click to toggle source

Generates markup for any supported form element type

@param id [String]

The HTML ID for the generated markup

@param code [String]

The question code; used for indexing

@param name [String]

The full text of the question

@param required [Boolean]

Whether the rendered element should be marked as required

@param rhs [String]

The right hand side of the question specification, if on one line.

@param text [String]

The full content of the content, used for recursive multiline calls

@return [String]

The HTML markup for all the HTML nodes that the full element renders to.

@see

https://github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff.rb#L880-L903
# File lib/showoff/compiler/form.rb, line 89
def self.form_element(id, code, name, required, rhs, text)
  required = required ? 'required' : ''
  str =  "<div class='form element #{required}' id='#{id}' data-name='#{code}'>"
  str << "<label class='question' for='#{id}'>#{name}</label>"
  case rhs
  when /^\[\s+(\d*)\]$$/             # value = [    5]                                     (textarea)
    str << self.form_element_textarea(id, code, $1)
  when /^___+(?:\[(\d+)\])?$/        # value = ___[50]                                     (text)
    str << self.form_element_text(id, code, $1)
  when /^\(.?\)/                     # value = (x) option one (=) opt2 () opt3 -> option 3 (radio)
    str << self.form_element_radio(id, code, rhs.scan(/\((.?)\)\s*([^()]+)\s*/))
  when /^\[.?\]/                     # value = [x] option one [=] opt2 [] opt3 -> option 3 (checkboxes)
    str << self.form_element_checkboxes(id, code, rhs.scan(/\[(.?)\] ?([^\[\]]+)/))
  when /^\{(.*)\}$/                  # value = {BOS, [SFO], (NYC)}                         (select shorthand)
    str << self.form_element_select(id, code, rhs.scan(/[(\[]?\w+[)\]]?/))
  when /^\{$/                        # value = {                                           (select)
    str << self.form_element_select_multiline(id, code, text)
  when ''                            # value =                                             (radio/checkbox list)
    str << self.form_element_multiline(id, code, text)
  else
    Showoff::Logger.warn "Unmatched form element: #{rhs}"
  end
  str << '</div>'
end
form_element_check_or_radio(type, id, code, value, label, modifier) click to toggle source
# File lib/showoff/compiler/form.rb, line 214
def self.form_element_check_or_radio(type, id, code, value, label, modifier)
  # yes, value and id are conflated, because this is the id of the parent widget
  checked = self.form_checked?(modifier)
  classes = self.form_classes(modifier)

  name = (type == 'checkbox') ? "#{code}[]" : code
  str  =  "<input type='#{type}' name='#{name}' id='#{id}_#{value}' value='#{value}' class='#{classes}' #{checked} />"
  str << "<label for='#{id}_#{value}' class='#{classes}'>#{label}</label>"
end
form_element_check_or_radio_set(type, id, code, items) click to toggle source
# File lib/showoff/compiler/form.rb, line 197
def self.form_element_check_or_radio_set(type, id, code, items)
  str = ''
  items.each do |item|
    modifier = item[0]

    if item[1] =~ /^(\w*) -> (.*)$/
      value = $1
      label = $2
    else
      value = label = item[1].strip
    end

    str << self.form_element_check_or_radio(type, id, code, value, label, modifier)
  end
  str
end
form_element_checkboxes(id, code, items) click to toggle source
# File lib/showoff/compiler/form.rb, line 127
def self.form_element_checkboxes(id, code, items)
  self.form_element_check_or_radio_set('checkbox', id, code, items)
end
form_element_multiline(id, code, text) click to toggle source
# File lib/showoff/compiler/form.rb, line 173
def self.form_element_multiline(id, code, text)
  str = '<ul>'

  text.split("\n")[1..-1].each do |item|
    case item
    when /\((.?)\)\s*(\w+)\s*(?:->\s*(.*)?)?/
      modifier = $1
      type     = 'radio'
      value    = $2
      label    = $3 || $2
    when /\[(.?)\]\s*(\w+)\s*(?:->\s*(.*)?)?/
      modifier = $1
      type     = 'checkbox'
      value    = $2
      label    = $3 || $2
    end

    str << '<li>'
    str << self.form_element_check_or_radio(type, id, code, value, label, modifier)
    str << '</li>'
  end
  str << '</ul>'
end
form_element_radio(id, code, items) click to toggle source
# File lib/showoff/compiler/form.rb, line 123
def self.form_element_radio(id, code, items)
  self.form_element_check_or_radio_set('radio', id, code, items)
end
form_element_select(id, code, items) click to toggle source
# File lib/showoff/compiler/form.rb, line 131
def self.form_element_select(id, code, items)
  str =  "<select id='#{id}_response' name='#{code}'>"
  str << '<option value="">----</option>'

  items.each do |item|
    selected = classes = ''
    case item
    when /\((\w+)\)/
      item     = $1
      selected = 'selected'
    when /\[(\w+)\]/
      item     = $1
      classes  = 'correct'
    end
    str << "<option value='#{item}' class='#{classes}' #{selected}>#{item}</option>"
  end
  str << '</select>'
end
form_element_select_multiline(id, code, text) click to toggle source
# File lib/showoff/compiler/form.rb, line 150
def self.form_element_select_multiline(id, code, text)
  str =  "<select id='#{id}_response' name='#{code}'>"
  str << '<option value="">----</option>'

  text.split("\n")[1..-1].each do |item|
    case item
    when /^   +\((\w+) -> (.+)\),?$/         # (NYC -> New York City)
      str << "<option value='#{$1}' selected>#{$2}</option>"
    when /^   +\[(\w+) -> (.+)\],?$/         # [NYC -> New York City]
      str << "<option value='#{$1}' class='correct'>#{$2}</option>"
    when /^   +(\w+) -> (.+),?$/             # NYC -> New, York City
      str << "<option value='#{$1}'>#{$2}</option>"
    when /^   +\((.+)\)$/                    # (Boston)
      str << "<option value='#{$1}' selected>#{$1}</option>"
    when /^   +\[(.+)\]$/                    # [Boston]
      str << "<option value='#{$1}' class='correct'>#{$1}</option>"
    when /^   +([^\(].+[^\),]),?$/           # Boston
      str << "<option value='#{$1}'>#{$1}</option>"
    end
  end
  str << '</select>'
end
form_element_text(id, code, length) click to toggle source
# File lib/showoff/compiler/form.rb, line 114
def self.form_element_text(id, code, length)
  "<input type='text' id='#{id}_response' name='#{code}' size='#{length}' />"
end
form_element_textarea(id, code, rows) click to toggle source
# File lib/showoff/compiler/form.rb, line 118
def self.form_element_textarea(id, code, rows)
  rows = 3 if rows.empty?
  "<textarea id='#{id}_response' name='#{code}' rows='#{rows}'></textarea>"
end
render!(doc, options={}) click to toggle source

Add the form markup to the slide and then render all elements

@todo UI elements to translate once i18n is baked in. @todo Someday this should be rearchitected into the markdown renderer.

@return [Nokogiri::HTML::DocumentFragment]

The slide DOM with all form elements rendered.

@see

https://github.com/puppetlabs/showoff/blob/3f43754c84f97be4284bb34f9bc7c42175d45226/lib/showoff.rb#L849-L878
# File lib/showoff/compiler/form.rb, line 16
def self.render!(doc, options={})
  title = options[:form]
  return unless title

  begin
    tools = Nokogiri::XML::Node.new('div', doc)
      tools.add_class('tools')
      doc.add_child(tools)

    button = Nokogiri::XML::Node.new('input', doc)
      button.add_class('display')
      button.set_attribute('type', 'button')
      button.set_attribute('value', I18n.t('forms.display'))
      tools.add_child(button)

    submit = Nokogiri::XML::Node.new('input', doc)
      submit.add_class('save')
      submit.set_attribute('type', 'submit')
      submit.set_attribute('value', I18n.t('forms.save'))
      submit.set_attribute('disabled', 'disabled')
      tools.add_child(submit)

    form = Nokogiri::XML::Node.new('form', doc)
      form.set_attribute('id', title)
      form.set_attribute('action', "form/#{title}")
      form.set_attribute('method', 'POST')
      doc.add_child(form)

    doc.children.each do |elem|
      next if elem == form
      elem.parent = form
    end

    doc.css('p').each do |p|
      if p.text =~ /^(\w*) ?(?:->)? ?(.*)? (\*?)= ?(.*)?$/
        code     = $1
        id       = "#{title}_#{code}"
        name     = $2.empty? ? code : $2
        required = ! $3.empty?
        rhs      = $4

        p.replace self.form_element(id, code, name, required, rhs, p.text)
      end
    end

  rescue Exception => e
    Showoff::Logger.warn "Form parsing failed: #{e.message}"
    Showoff::Logger.debug "Backtrace:\n\t#{e.backtrace.join("\n\t")}"
  end

  doc
end