UnionOfRAD / lithium

li₃ is the fast, flexible and most RAD development framework for PHP
http://li3.me
BSD 3-Clause "New" or "Revised" License
1.22k stars 237 forks source link

Render checkboxes and radios box groups #1155

Open binhwpo opened 9 years ago

binhwpo commented 9 years ago

Hello @nateabele and Team,

I'm working on a project using Lithium. Some pages require a list of checkboxes to be shown. We can just use HTML or individual Form->checkbox(...) and Form->radio(...) within foreach. But I find that's not very friendly for a template maker.

Therefore I made a working snippet of Form helper that can print out a list of checkboxes and radios (and anything) depending on what input type has been chosen.

I would like to contribute that to this project. I know it might not be perfect or following Lithium standards yet. But if consider we can optimize it then hopefully can be included as Lithium functions.

Here is the code:

<?php
namespace app\extensions\helper;

class Form extends \lithium\template\helper\Form {

    public function formlist($type, $name, $values = array(), $checks = array(), $options = array()) {

        $html = '';

        if ($prefix = @$options['prefix']) unset($options['prefix']);
        if ($suffix = @$options['suffix']) unset($options['suffix']);

        $id = str_replace(array('[]', ']', '['), array('', '', '-'), $name);

        foreach ($values as $value => $label) {
            $checked = is_array($checks) ? in_array($value, $checks) : ($value == $checks);
            $html .= "\n<label for=\"$id-$value\">";
            $html .= $prefix . $this->$type($name, array_merge($options, 
                array('id' => "$id-$value", 'value' => $value, 'checked' => $checked)));
            $html .= " $label</label>$suffix\n";
        }
        return $html;
    }

    public function checkboxes($name, $values = array(), $checks = array(), $options = array()) {
        $options = $options + array('hidden' => false);
        return $this->formlist('checkbox', $name, $values, $checks, $options);      
    }

    public function radios($name, $values = array(), $check = '', $options = array()) {
        return $this->formlist('radio', $name, $values, $check, $options);      
    }
}
?>
DrRoach commented 9 years ago

Modified the _fields method will do what you're looking for but using a lot less code. It's not perfect yet, you can only set options globally for the inputs but I'll fix that tomorrow if this is something that you're but maybe it's the sort of thing that you're looking for? Anyway, here's what I did if you want to use the code:

protected function _fields(array $fields, array $options = array()) {
     $result = array();

     foreach ($fields as $field => $label) {
         if (is_numeric($field)) {
             $field = $label;
             unset($label);
         }
         //Start of where I added
         if(is_array($label)) {
             foreach($label as $l => $opts) {
                 if(is_array($opts)) {
                     $result[] = $this->field($field, array('label' => $l) + $opts + $options);
                 } else {
                     $result[] = $this->field($field, array('label' => $opts) + $options);
                 }
             }
          } else {
             $result[] = $this->field($field, compact('label') + $options);
         }   
         //End of what I added
     }   
     return join("\n", $result);
 }

I added the if(is_array()) part which makes it so that you can add multiple inputs of any type using

$this->form->field(array('test' => array('Label' => array('id' => 'TestField', 'checked' => true), 'test')), array( 'type' => 'radio'))

This would create two radio buttons with the name test. The first field would have the id TestFIeld and be checked whereas the second field would use the default settings.

binhwpo commented 9 years ago

Hi @DrRoach,

Thanks for sharing your solution. I really appreciate your time to make some similar solution for my needs. I have been wondering how to use the field() and _fields() function too. It will be so great if we don't have to modify the _fields() function at all.

My function code seem to be a little longer then yours. However, it has a better benefit for a front-end developer. Most call to generate checkboxes and radios will be much easily understandable as example below:

$values = array("a" => "Option A", "b" => "Option B", "c" => "Option C");
echo $this->Form->checkboxes('somecheckboxes[]', $values, array("a", "c");); 
echo $this->Form->radios('someradios', $values, "b"); 

Results for checkboxes: [x] Option A [ ] Option B [x] Option C

Results for radios: (`) Option A (o) Option B ( `) Option C

In additions they can include some prefix and suffix in an array of options.

Your solution on the other hand, will work great if the _fields function is not modified. But anyway, it's a good kickstart for me to start off investigating how to use the field function.

Many thanks.

DrRoach commented 9 years ago

Fair enough, I see your point, if you or other people want I will expand on what you have got to allow for extra features like adding ids and classes?

binhwpo commented 9 years ago

@DrRoach The main idea here is to have:

  1. Checkboxes/radios group name. A checkboxes group name normally end with [](accept many checks). eg: checklist[]
  2. I could see your code might do the same work. But the call for it is rather hard-to-understand on the template (mostly HTML) developer point of view. If it's officially added I still would rather make shorter interface functions checkboxes and radios as these are very oftently used.
  3. Prefix and suffix for these list items. Sometimes they prefer simple , (comma) <br />, <li>...</li>, hence having these will help because people can't interfere with the loop.
  4. ID and classes are optional and could be passed into $options input of field() already. So no change is required.

Many thanks.

mariuswilms commented 9 years ago

Another idea would be to repurpose the 'list option as it was done for datalists and text inputs. Please see https://github.com/UnionOfRAD/lithium/commit/cedc4d19550a9e42ba5250b9e906e8f2012896d1

DrRoach commented 9 years ago

Using the list options means that you have to use $this->Form->field though doesn't it? While not really a issue this might seem strange to some developers when there is a radio option. I implemented a radios function which loops over the radio function in Form.php. I don't know if this is a potential solution?

public function radios($name, array $radios = array())
{
    $return = '';
    foreach($radios as $options) {
        $return .= $this->radio($name, $options);
    }

    return $return;
}

That's the code, shall I make a pull request?

binhwpo commented 9 years ago

@davidpersson Thanks for looking at this again. @DrRoach Thanks for providing a solution. I wonder how do we use this radios() function to print a list like mentioned above?

DrRoach commented 9 years ago

I don't know what the list functionality mentioned above does :/ So am not sure how to test that sorry.

mariuswilms commented 9 years ago

My idea would be to not create additional radios/checkboxes methods, but make people utilize field() in combination with list. Than any multiple-XYZ logic could be contained within that method.

$this->form->field('colors', array('type' => 'checkbox', 'list' => array('b' => 'blue', 'g' => 'green));

At the same time field() should not be bloated too much, we might need to extract some functionality into protected methods.