bmichotte / ProMotion-XLForm

ProMotion-XLForm is a ProMotion plugin for XLForm
MIT License
20 stars 21 forks source link
ios promotion promotion-xlform rubymotion xlform

ProMotion-XLForm

ProMotion-XLForm provides a PM::XLFormScreen for ProMotion powered by the CocoaPod XLForm.

Build Status Gem Version

Installation

gem 'ProMotion-XLForm'

Then:

$ bundle
$ rake pod:install

Usage

PM::XLFormScreen includes PM::ScreenModule so you'll have all the same ProMotion screen methods available.

To create a form screen, subclass PM::XLFormScreen and define a form_data method.

class TestFormScreen < PM::XLFormScreen
  def form_data
    genders = [
      { id: :male, name: 'Male' },
      { id: :female, name: 'Female' },
      { id: :other,  name: 'Other' },
    ]

    [
      {
        title:  'Account information',
        cells: [
          {
            title:       'Email',
            name:        :email,
            type:        :email,
            placeholder: 'Enter your email',
            required:    true
          },
          {
            title: 'Name',
            name:  :name,
            type:  :text,
            value: 'Default value'
          },
          {
            title: 'Gender',
            name: :gender,
            type: :selector_push,
            options: Hash[genders.map do |gender|
              [gender[:id], gender[:name]]
            end]
          }
        ]
      }
    ]
  end
end

Available types

Form Options

The form_options class method allows you to customize the default behavior of the form.

class TestFormScreen < PM::XLFormScreen

  form_options on_save:   :save_form,   # adds a "Save" button in the nav bar and calls this method
               on_cancel: :cancel_form, # adds a "Cancel" button in the nav bar and calls this method
               required:  :asterisks,   # display an asterisk next to required fields
               auto_focus: true         # the form will focus on the first focusable field

  def save_form(values)
    dismiss_keyboard
    mp values
  end

  def cancel_form
  end
end

Save & Cancel Buttons

By default, no buttons are displayed in the nav bar unless you configure the on_save or on_cancel options.

You can either pass the name of the method that you want to call when that button is tapped, or you can pass a hash of options, allowing you to configure the title of the button.

Hash Options:

form_options on_cancel: { system_item: :trash, action: :cancel_form },
             on_save: { title: 'Continue', action: :continue }

system_item can be any UIBarButtonSystemItem constant or one of the following symbols:

:done, :cancel, :edit, :save, :add, :flexible_space, :fixed_space, :compose,
:reply, :action, :organize, :bookmarks, :search, :refresh, :stop, :camera,
:trash, :play, :pause, :rewind, :fast_forward, :undo, :redo

If you would like to display a button as part of your form, you could do something like this:

form_options on_save: :my_save_method

def form_data
  [
    {
      title: 'Save',
      name: :save,
      type: :button,
      on_click: -> (cell) {
        on_save(nil)
      }
    }
  ]
end

def my_save_method(values)
  mp values
end

Getting values

You can get the values of your form with values. You can call dismiss_keyboard before before calling values to ensure you capture the input from the currently focused form element. You can also get validation errors with validation_errors and check if the form is valid with valid?. You can also get a specific value with value_for_cell(:my_cell).

Events

on_change, on_add and on_remove are available for cells, on_add and on_remove are available for sections.

{
  title: 'Sex',
  name: :sex,
  type: :selector_push,
  options: {
    male: 'Male',
    female: 'Female',
    other: 'Other'
  },
  # An optional row paramater may be passed |old_value, new_value|
  on_change: lambda do |old_value, new_value|
    puts "Changed from #{old_value} to #{new_value}"
  end
}
# An optional row paramater may be passed to on_change:
#  on_change: lambda do |old_value, new_value, row|
#    puts "Changed from #{old_value} to #{new_value}"
#    row.setTitle(new_value)
#    self.reloadFormRow(row) if old_value != new_value
#  end

Multivalued Sections (Insert, Delete, Reorder rows)

{
  title:  'Multiple value',
  name:   :multi_values,
  options: [:insert, :delete, :reorder],
  cells: [
    {
      title: 'Add a new tag',
      name:  :tag,
      type:  :text
    }
  ]
}

Custom Selectors

You can create a custom subform with a few options

{
  title:  'Custom section',
  cells: [
    {
      title: 'Custom',
      name:  :custom,
      cells: [
        {
            title: 'Some text',
            name:  :some_text,
            type:  :text
        },
        {
            title: 'Other text',
            name:  :some_other_text,
            type:  :text
        }
      ]
    }
  ]
}

By default, the cell will print a Hash.inspect of your subcells. You can change this by creating a valueTransformer and set value_transformer:.

{
  title: 'Custom',
  name:  :custom,
  value_transformer: MyValueTransformer
  cells: []
}

class MyValueTransformer < PM::ValueTransformer
  def transformedValue(value)
    return nil if value.nil?

    str = []
    str << value['some_text'] if value['some_text']
    str << value['some_other_text'] if value['some_other_text']

    str.join(',')
  end
end

Custom Selector View Controller

For a more advanced custom selector, you can set view_controller_class:.

{
  title: 'Person',
  name: 'person',
  type: :selector_push,
  view_controller_class: PeopleListScreen
}

Here is an example of a table screen. Note that the rowDescriptor setter must be implemented. In order to pass the value back to the previous form screen, update the value of the rowDescriptor. Note that XLForm will set the rowDescriptor later in your view controller's initialization process than you might expect.

class PeopleListScreen < PM::TableScreen
  attr_accessor :rowDescriptor

  def table_data
    [{
      title: @rowDescriptor.title,
      cells: People.all.map do |person|
        {
          title: person.name,
          action: -> {
            rowDescriptor.value = person.id
            close # go back to the previous screen
          }
        }
      end
    }]
  end
end

Screenshot of Map Custom Selector

Screenshot of Dynamic Custom Selector

See XLForm documentation for more information.

Cell

You can use your own cell by providing cell_class

{
  title: 'MyCustomCell',
  name: :custom_cell,
  cell_class: MyCustomCell
}

class MyCustomCell < PM::XLFormCell
  def initWithStyle(style, reuseIdentifier: reuse_identifier)
    super.tap do
      @label = UILabel.new
      self.contentView.addSubview(@label)
    end
  end

  def update
    super

    @label.text = value
    @label.sizeToFit
  end
end

In your cell, you can set the value with self.value= and get the value with self.value

Validators

You can add validators to cells.

{
  title:       'Email',
  name:        :email,
  type:        :email,
  required:    true,
  validators: {
    email: true
  }
}

:email and :url are available out of the box, as well as :regex. You will have to provide a valid regex and a message.

{
  title:       'Only letters',
  name:        :letters,
  type:        :text,
  required:    true,
  validators: {
    regex: { regex: /^[a-zA-Z]+$/, message: "Only letters please !" }
  }
}

Finally, you can provide a PM::Validator with a valid?(cell) method.

Make a row or section invisible depending on other rows values

You can show/hide cells depending on a cell value with a predicate

{
  title: 'Hide and seek',
  cells: [
    {
      title: 'Switch me',
      type: :switch,
      name: :hide_and_seek,
      value: true
    },
    {
      title: 'Appear when switch is on',
      name: :show_me,
      type: :info,
      hidden: {
        # the cell name wich will "trigger" the visibility
        name: :hide_and_seek,

        # the operand. Valid operands are :equal, :not_equal, :contains, :not_contains
        is: :equal,

        # the value which trigger the visibility
        value: true }
    },
    {
      title: 'Appear when switch is off',
      name: :hide_me,
      type: :info,

      # you can also write it this way
      hidden: ':hide_and_seek == false'
      # also valid ':some_text contains "a text"'
      #            ':some_text not contains "a text"'
    }
  ]
}

Buttons and click

You can add :on_click on :button which accepts 0 or 1 argument (the cell).

{
  title: 'Click me',
  name: :click_me,
  type: :button,
  on_click: -> (cell) {
    mp "You clicked me"
  }
}

Appearance

You can change the appearance of the cell using the appearance hash

{
  title:   'Options',
  name:    'options',
  type:    :selector_push,
  appearance: {
    font: UIFont.fontWithName('Helvetica Neue', size: 15.0),
    detail_font: UIFont.fontWithName('Helvetica Neue', size: 12.0),
    color: UIColor.greenColor,
    detail_color: UIColor.blueColor,
    background_color: UIColor.grayColor
  },
  options: {
    "value_1" => "Value 1",
    "value_2" => "Value 2",
    "value_3" => "Value 3",
    "value_4" => "Value 4",
  }
},
{
  title: 'Alignment',
  name: :align,
  type: :text,
  appearance: {
    alignment: :right # or NSTextAlignmentRight
  }
}

You can also pass any key-value to configure your cell. Take a look at this for more information

{
  appearance: {
    "slider.tintColor" => UIColor.grayColor
  }
}

Keyboard

For the text based cells (like :text, :password, :number, :integer, :decimal), you can specify a keyboard_type. The following keyboard types are available :

RMQ / RedPotion

If you use RMQ or RedPotion, you can style the screen with

def form_view(st)

end

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Make some specs pass
  5. Push to the branch (git push origin my-new-feature)
  6. Create new Pull Request

License

Released under the MIT license.