bmichotte / ProMotion-XLForm

ProMotion-XLForm is a ProMotion plugin for XLForm
MIT License
20 stars 21 forks source link

How to create a multi-check list? #4

Closed andrewhavens closed 9 years ago

andrewhavens commented 9 years ago

I want to create a section of options that allow multiple rows to be checked. For the resulting value, I would like an array of values:

{
  "people" => [
    "john",
    "sue"
  ]
}

However, the only way I can figure out how to get this to work looks like this:

{
  title: "Select some people",
  cells: [
    { type: :check, name: "people_john", title: 'John', },
    { type: :check, name: "people_sue", title: 'Sue', },
  ]
}

...and returns the following hash:

{
  "people_john" => true,
  "people_sue" => true
}

Is there a way that I can display this list so that the selected values are returned as an array?

bmichotte commented 9 years ago

This is not in XLForm, but I had to do this in an app. Here's how I managed it... I guess it could be done internally using something like multiple: true... Could be cool

# MyFormScreen
{
  title: 'Some title',
  name: :multiple_cell,
  type: :selector_push,
  view_controller_class: MySubFormScreen,
  value_transformer: MySubFormTransformer,
}
# MySubFormScreen
class MySubFormScreen < PM::TableScreen
   attr_accessor :rowDescriptor # don't forget this one !

   def table_data
        SomeArray.map do |stuff|
          {
              title: stuff.name,
              action: 'select:',
              arguments: { stuff: stuff.name },
              accessory_type: rowDescriptor.value && rowDescriptor.value.include?(stuff.name) ? :checkmark : :none
            }
   end

  def select(arguments, index_path)
    rowDescriptor.value ||= []
    if rowDescriptor.value.include?(arguments[:skill])
      rowDescriptor.value.delete(arguments[:skill])
    else
      rowDescriptor.value << arguments[:skill]
    end
    update_table_data([index_path])
  end
end
# MySubFormTransformer < PM::ValueTransformer
  def transformed_value(value)
    return nil if value.nil?

    value.join(', ')
  end
end

and if you call values after selecting multiple items, you will get something like

{ 
   multiple_cell: [ 'one', 'two' ]
}
bmichotte commented 9 years ago

So, it was way easier than that... Simply use :multiple_selector or :multiple_selector_inline and you are done !

Ex: https://github.com/bmichotte/ProMotion-XLForm/blob/master/app/screens/test_form_screen.rb#L118

andrewhavens commented 9 years ago

@bmichotte Nice! :+1: Unfortunately, that still would not work for me because I needed each option to be a custom table cell. Is there a way to specify a custom cell instead of a title? Something like...

rose = FlowerModel.new(name: 'rose', color: '#FF0000')
violet = FlowerModel.new(name: 'violet', color: '#0000FF')
{
  title: 'Multiple',
  name: :multiple,
  type: :multiple_selector,
  options: {
    :roses => { cell_class: FlowerTableCell, properties: { flower: rose } },
    :violets => { cell_class: FlowerTableCell, properties: { flower: violet } },
    # etc...
  }
},
andrewhavens commented 9 years ago

The code that I ended up with looked something like this. It seemed like a lot of work to create a custom table cell class, but I was not able to subclass XLFormBaseCell. I also could not figure out how to pass the model instance to the cell.

# table data
cells: flowers.map do |flower|
  {
    type: :check,
    cell_class: FlowerTableCell,
    name: "flower_#{flower.id}",
    title: flower.name,
  }
end

# custom cell class
class FlowerTableCell < UITableViewCell

  attr_accessor :flower, :rowDescriptor

  def setRowDescriptor(rowDescriptor)
    @rowDescriptor = rowDescriptor
    update
  end

  def formDescriptorCellCanBecomeFirstResponder
    false
  end

  def formViewController
    return @formViewController if @formViewController
    responder = self
    while (responder)
      if (responder.isKindOfClass(UIViewController))
        @formViewController = responder
        return @formViewController
      end
      responder = responder.nextResponder
    end
    return nil
  end

  # Update based on cell data
  def update
    rowDescriptor.value = false if rowDescriptor.value == 0
    rowDescriptor.value = true if rowDescriptor.value == 1
    # TODO: figure out how to fetch data from model instance
    imageView.image = rmq.image.resource('icons/flower')
    textLabel.text = rowDescriptor.title
    display_checkmark
  end

  def layoutSubviews
    super
    imageView.bounds = [[0, 0], [35, 35]]
  end

  def formDescriptorCellDidSelectedWithFormController(controller)
    toggle_checked_value
    display_checkmark
    formViewController.tableView.selectRowAtIndexPath(nil, animated: true, scrollPosition: UITableViewScrollPositionNone)
  end

  def toggle_checked_value
    rowDescriptor.value = rowDescriptor.value == true ? false : true
  end

  def display_checkmark
    if rowDescriptor.value == true
      self.accessoryType = UITableViewCellAccessoryCheckmark
    else
      self.accessoryType = UITableViewCellAccessoryNone
    end
  end
end
bmichotte commented 9 years ago

if you use gem "ProMotion-XLForm", path: '/Users/benjamin/workspace/ProMotion-XLForm'

you could simplify FlowerTableCell by extending PM::XLFormCell (which include PM::TableViewCellModule) with something like

# table data
cells: flowers.map do |flower|
  {
    type: :check,
    cell_class: FlowerTableCell,
    name: "flower_#{flower.id}",
    title: flower.name,
    flower: flower
  }
end

class FlowerTableCell < PM::XLFormCell
   attr_accessor :flower
   def setup(data_cell, screen)
      super
      self.flower = data_cell[:flower]
   end
end

The rest of the cell is quite "specific" and should stay (you can remove setRowDescriptor, formViewController). I think you can also remove layoutSubviews and move this on setup

Instead of using self.rowDescriptor.value, you can use self.value