- 
                Notifications
    You must be signed in to change notification settings 
- Fork 365
Forms (Rails)
This describes an old version of forms, for current forms, go to Forms. For other kinds of forms, go to Forms (kinds)
- the whole form is rendered in Rails
- each input field's valueattribute is pre-filled with the existing object's data if editing
- form data is stored server-side, in session[:edit](which becomes@edit)
- each change triggers a POST request to form_field_changed(sometimes prefixed), which updates the session- on the client side, this happens via the miq_observejQuery plugin (data-miq_observe)
- 
except for new selects, where we use the js function miqSelectPickerEvent, right aftermiqInitSelectPicker
- these also handle re-rendering the form buttons (buttons_onvs.buttons_offviajavascript_showandjavascript_hideruby-side) when the form becomes submittable (some validation, but usually just compares@edit[:new]to@edit[:current]to see if somethingchanged)
- such change requests may re-render the form or parts of it (eg. a select's value change triggers an additional fieldset to be added)
 
- on the client side, this happens via the 
- submitting a form triggers a POST request to createorupdate, but submits just the button name, no data- some validation usually happens here, and can render a flash message ruby-side (add_flash&render_flash)
- success usually triggers a redirect to show_list/explorerin response, but sometimes alsoreplace_right_cell
 
- some validation usually happens here, and can render a flash message ruby-side (
- some forms also use tabs, tab change also propagates to the server and replaces the current tab with a new one
Many of these action names will be prefixed with an entity type or a form name in real code (especially in explorer controllers with multiple entities) but not all of them. This is also true for the names of haml partials. In some rarer cases, suffixes or infixes are used instead (all of foo_form_field_changed, form_field_changed_foo, form_foo_field_changed, form_field_changed and foo_field_changed exist).
For simplicity, I'm omitting any such afixes and using a foo_ prefix when complete omission would be confusing.
- 
open a new/edit form - prefixed with entity type, neworedit- calls set_form_vars- loads data from db
- populates @edit
 
- renders the form (app/views/foo/_form.html.haml)
 
- calls 
- 
change a field - form_field_changed- calls get_form_vars- loads @editfrom session
- updates from params
 
- loads 
- checks for changes
- updates form buttons (enabled/disabled)
- renders any partils
 
- calls 
- 
save - createorupdate- calls set_record_vars- updates the actual record from session
 
- form validations
- flash message on fail
- actually creates/updates the model on success
- redirects elsewhere
 
- calls 
- 
cancel & reset - handled by the same createorupdate
def foo_new
  assert_privileges('foo_new')
  @record = Model.new
  foo_new_edit
end
def foo_edit
  assert_privileges('foo_edit')
  @record = find_record_with_rbac(Model, from_cid(params[:id]))
  foo_new_edit
end
def foo_new_edit
  set_form_vars
  @in_a_form = true
  session[:changed] = @changed = false
  replace_right_cell
end
def set_form_vars
  @edit = {}
  @edit[:id] = @record.id
  @edit[:new] = {}
  @edit[:current] = {}
  @edit[:new][:name] = @record.name
  @edit[:new][:choice] = @record.choice
  @choices = [["bar", "Bar"], ["baz", "Baz"]]
  @edit[:current] = copy_hash(@edit[:new])
end
def form_field_changed
  return unless load_edit("foo_edit__#{params[:id]}")
  get_form_vars
  session[:changed] = @changed = (@edit[:new] != @edit[:current])
  render :update do |page|
    page << javascript_prologue
    page.replace_html('dynamic_div', r[:partial => "dynamic"]) if params[:choice]
    page << javascript_for_miq_button_visibility(changed)
  end
end
def get_form_vars
  @edit[:new][:name] = params[:name] if params[:name]
  @edit[:new][:choice] = params[:choice] if params[:choice]
end
def foo_create
  assert_privileges('foo_new')
  @record = Model.new
  create_update
end
def foo_update
  assert_privileges('foo_edit')
  @record = find_record_with_rbac(Model, from_cid(params[:id]))
  create_update
end
def create_update
  return unless load_edit("foo_edit__#{params[:id]}")
  case params[:button]
  when 'cancel'
    add_flash(_("Cancelled"))
    @record = nil
    replace_right_cell
  when 'add', 'save'
    validate
    set_record_vars
    if @flash_array.nil? && @record.save
      add_flash(_('Saved'))
      @edit = nil
      replace_right_cell
    else
      javascript_flash
    end
  when 'reset', nil
    if params[:button] == 'reset'
      @edit[:new] = copy_hash(@edit[:current])
      add_flash(_('All changes have been reset'))
    end
    replace_right_cell
  end
end
def set_record_vars
  @record[:name] = @edit[:new][:name]
  @record[:choice] = @edit[:new][:choice]
end
def validate
  add_flash(_("Name must be shorter than 16 characters"), :error) if @edit[:new][:name].length >= 16
  add_flash(_("A choice is required"), :error) unless @edit[:new][:choice]
end- url = url_for_only_path(:action => "form_field_changed", :id => @record.id || "new")
#form_div
  = render :partial => "layouts/flash_msg"
  %h3
    = _("Foo")
  .form-horizontal
    .form-group
      %label.col-md-2.control-label
        = _('Name')
      .col.md-8
        = text_field_tag('name',
                         @edit[:new][:name],
                         :class             => 'form-control',
                         'data-miq_observe' => {:url => url}.to_json)
    .form-group
      %label.col-md-2.control-label
        = _('Choice')
      .col.md-8
        = select_tag('choice',
                     options_for_select([["<#{_('Choose')}>", nil]] + @choices),
                     "data-miq_sparkle_on" => true,
                     :class                => "selectpicker")
        :javascript
          miqInitSelectPicker();
          miqSelectPickerEvent('choice', '#{url}')
    #dynamic_div
      = render :partial => "dynamic"- if @edit[:current][:choice] === "foo"
  = _("This could be a few more inputs")
- if @edit[:current][:choice] === "bar"
  = _("Or just a harmless message")A recommended migration from here is to go to the newest version which already exists:
- don't want to reimplement the wheel together with the migration
- but this involves a rewrite of most of actual code involved (ruby to js), so no sense in going step by step
The idea is to:
- stop the view from having anything to do with actual form state
- provide a data-only JSON endpoint to query for the object's data
- unless such already exists in the API or is not special enough not to try to implement there instead (these are often needed, but always create tech debt)
 
- write a current form component which:
- takes an id parameter (empty when new)
- queries the server for the data (if editing)
- later this will be done in the router
 
- keeps all the form state in its model object
- does all the validation that makes sense
- submits that object on save
- doesn't talk to the server unless it has to
 
- replace the original view with the use of such a component, with any necessary options coming from server-side
- handle only the initial rendering, data, and submit/cancel ruby side