Coder Social home page Coder Social logo

Checkbox binding about phalcon HOT 21 OPEN

phalcon avatar phalcon commented on May 31, 2024
Checkbox binding

from phalcon.

Comments (21)

scrnjakovic avatar scrnjakovic commented on May 31, 2024 1

@sergeyklay can we reopen this until it's fixed?

from phalcon.

tugrul avatar tugrul commented on May 31, 2024

What is type of ours field? Is a MySQL BIT?

if ($this->request->isPost() && $form->isValid($_POST, $customer)) {
    $customer->ours = isset($_POST['ours']) ? true : false;
    $customer->save();
}

from phalcon.

ranquild avatar ranquild commented on May 31, 2024

It is MYSQL TINYINT. The problem is not in ORM. It is form component that doesn't set 'ours' property to anything if checkbox is not checked. I understand that if checkbox is not checked, it's not in the $_POST. That will leave 'ours' property unchanged.

from phalcon.

tugrul avatar tugrul commented on May 31, 2024
if ($this->request->isPost() && $form->isValid($_POST, $customer)) {
    $customer->ours = empty($_POST['ours']) ? 0 : 1;
    $customer->save();
}

I changed isset to empty because there is a different behavior between isset and empty.

$fields = array('our' => '');

if (isset($fields['our']) == empty($fields['our']) {
    echo 'both isset and empty';
}

from phalcon.

ranquild avatar ranquild commented on May 31, 2024

When form with unchecked checkbox is submitted, $_POST will not contain 'ours' at all. That is standard behavior for any browser. Problem is that Form component is not aware of that. It would not set field to anything if it is not present in $_POST. The corrent behavior for checkboxes (as I think) would be to set corresponding field ('ours' in example) to zero.

from phalcon.

ranquild avatar ranquild commented on May 31, 2024

Example that shows that issue:

<?php

use Phalcon\Forms\Element\Check;
use Phalcon\Forms\Form;
use Phalcon\Tag;

class Entity
{
    public $field;
}

$di = new \Phalcon\DI\FactoryDefault();

$form = new Form();
$form->add(new Check('field', ['value' => 1]));

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $entity = new Entity();
    $entity->field = 1;
    $form->bind($_POST, $entity);

    var_dump($_POST);
    echo '<br>';
    var_dump($entity);
}

echo Tag::form();
echo $form->render('field');
echo Tag::submitButton('Submit');
echo Tag::endForm();

$entity->field is unchanged when checkbox is not checked.
Just to be clear, I didn't use controllers and other things just to show the issue in one file.

from phalcon.

FalkHe avatar FalkHe commented on May 31, 2024

No solution, until now? This Problem should affect everyone who uses Checkboxes, not?

Most Frameworks solve this by adding a hidden input with the default/unchecked value.

<input type="hidden" name="my_field" value="0" />
<input type="checkbox" name="my_field" value="1" />

from phalcon.

staticall avatar staticall commented on May 31, 2024

Not sure if it's helpful to someone or not, but i come up with this:

<?php
namespace Application\Forms\Element;

use Phalcon\Forms\Element\Check as PhalconCheck;

class Check extends PhalconCheck
{
    /**
     * Renders the element widget returning html
     *
     * @param array|null $attributes Element attributes
     *
     * @return string
     */
    public function render($attributes = null)
    {
        $attrs = array();

        foreach ($attributes as $attrName => $attrVal) {
            if (is_numeric($attrName) || in_array($attrName, array('id', 'name', 'placeholder'))) {
                continue;
            }

            $attrs[] = $attrName .'="'. $attrVal .'"';
        }

        $attrs = ' '. implode(' ', $attrs);

        $id      = $this->getAttribute('id', $this->getName());
        $name    = $this->getName();
        $checked = '';

        if ($this->getValue()) {
            $checked = ' checked';
        }

        return <<<HTML
<input type="hidden" name="{$name}" value="0" />
<input type="checkbox" id="{$id}" name="{$name}" value="1"{$attrs}{$checked} />
HTML;
    }
}

Seems to be working ok for me, but i might've missed something.

P. S.: I'm using this element with model and 'bind' form method

from phalcon.

valVk avatar valVk commented on May 31, 2024

@staticall

Thanks for that solution!

But only thing I would like to mention to add condition if $attributes is not null than do foreach in other case do nothing.

<?php
namespace Application\Forms\Element;

use Phalcon\Forms\Element\Check as PhalconCheck;

class Check extends PhalconCheck
{
    /**
     * Renders the element widget returning html
     *
     * @param array|null $attributes Element attributes
     *
     * @return string
     */
    public function render($attributes = null)
    {
        $attrs = array();

        if (!empty($attributes)) {
            foreach ($attributes as $attrName => $attrVal) {
                if (is_numeric($attrName) || in_array($attrName, array('id', 'name', 'placeholder'))) {
                    continue;
                }

                $attrs[] = $attrName .'="'. $attrVal .'"';
            }
        }

        $attrs = ' '. implode(' ', $attrs);

        $id      = $this->getAttribute('id', $this->getName());
        $name    = $this->getName();
        $checked = '';

        if ($this->getValue()) {
            $checked = ' checked';
        }

        return <<<HTML
<input type="hidden" name="{$name}" value="0" />
<input type="checkbox" id="{$id}" name="{$name}" value="1"{$attrs}{$checked} />
HTML;
    }
}

from phalcon.

staticall avatar staticall commented on May 31, 2024

@valVk Good catch, thank you!

from phalcon.

valVk avatar valVk commented on May 31, 2024

I think this issue has to have label BUG.
Because this is not obvious and very confusing behaviour.

from phalcon.

cbichis avatar cbichis commented on May 31, 2024

Yes, I confirm this as a bug, your fix seems to work fine for now.

from phalcon.

next-direction avatar next-direction commented on May 31, 2024

Just stumbled over that, I solved it a bit different than others and for me it took several workarounds here. Maybe someone finds this helpful since no fix seems available yet.

Two things I have done in my project that helped me to create a workaround.
First I've overwritten all Form Elements (because I needed some additional functionality). This allowed me to make a function to return the type of the element e.g. "Text" or "Check".
Second I've created a base form for all my other forms and have overwritten the bind method. I loop through all form elements and check for type "Check".
If I find a check element and it is not set in data, I set it to 0.

But that was only half of the fix because if a checkbox is rendered and was not checked before, it gets the value 0 assigned to it. So even if the checkbox is checked the browser transmits 0 and it can never be activated. So I need some javascript event handler too. It simply checks if the checkbox is checked and changes the value to 1.

Hope this helps.

from phalcon.

stale avatar stale commented on May 31, 2024

Thank you for contributing to this issue. As it has been 90 days since the last activity, we are automatically closing the issue. This is often because the request was already solved in some way and it just wasn't updated or it's no longer applicable. If that's not the case, please feel free to either reopen this issue or open a new one. We will be more than happy to look at it again! You can read more here: https://blog.phalconphp.com/post/github-closing-old-issues

from phalcon.

valVk avatar valVk commented on May 31, 2024

C'mon is that fixed?

from phalcon.

stale avatar stale commented on May 31, 2024

Thank you for contributing to this issue. As it has been 90 days since the last activity, we are automatically closing the issue. This is often because the request was already solved in some way and it just wasn't updated or it's no longer applicable. If that's not the case, please feel free to either reopen this issue or open a new one. We will be more than happy to look at it again! You can read more here: https://blog.phalconphp.com/post/github-closing-old-issues

from phalcon.

scrnjakovic avatar scrnjakovic commented on May 31, 2024

This hasn't been fixed yet.

Most "solutions" here are actually opinionated hacks. Why on earth would we need frontend logic (JavaScript) or hidden inputs. Plus, value of checkbox can be anything, not just 1 or 0. Therefore, assuming that lack of value for a checkbox field equals to 0 is wrong to begin with. We need additional option passed to Check, something similar to Select, something like:

$element = new Check(
    'myField',
    [
        'emptyValue' => 'nope'
    ]
);

But I suppose this would require almost complete rewrite of Phalcon\Form::bind method.

In the meantime, for those looking for a quick fix:

1. Extend Phalcon\Forms\Form and override it's bind method

This is native bind method translated to PHP and changed to allow behavior described above

public function bind(array $data, $entity, $whitelist = null) : Form
{
    if (empty($this->_elements)) {
        throw new \Exception("There are no elements in the form");
    }

    $filter = null;

    foreach ($this->_elements as $field => $element) {
        
        /**
         * If value is not present for current element and element is not a checkbox with defined emptyValue, skip
         */
        if (!isset($data[$field])) {
            if (is_a($element, \Phalcon\Forms\Element\Check::class) && $element->getUserOption('useEmpty', false)) {
                $data[$field] = $element->getUserOption('emptyValue', null);
            } else {
                continue;
            }
        }

        /**
         * Get value for the element
         */
        $value = $data[$field];

        /**
         * Check if the item is in the whitelist
         */
        if (is_array($whitelist) && !in_array($field, $whitelist)) {
            continue;
        }

        /**
         * Check if the element has filters
         */
        $filters = $element->getFilters();

        if ($filters) {
            if ($filter == null) {
                $dependencyInjector = $this->getDI();
                $filter             = $dependencyInjector->getShared("filter");
            }

            /**
             * Sanitize the filters
             */
            $filteredValue = $filter->sanitize($value, $filters);
        } else {
            $filteredValue = $value;
        }

        /**
         * Use the setter if any available
         */
        $method = "set" . \Phalcon\Text::camelize($field);
        if (method_exists($entity, $method)) {
            $entity->{$method}($filteredValue);
            continue;
        }

        /**
         * Use the public property if it doesn't have a setter
         */
        $entity->{$field} = $filteredValue;
    }

    $this->_data = $data;

    return $this;
}

2. Extend Phalcon\Forms\Element\Check

This is optional. You can just change getUserOption calls with getAttribute in the bind method above and use Phalcon's native Check. The downside is that useEmpty and emptyValue will be rendered as HTML element's attributes.

<?php

namespace My\Forms\Element;

use Phalcon\Forms\Element\Check as PhCheck;

class Check extends PhCheck
{
    /**
     * Phalcon\Forms\Element constructor
     *
     * @param string name
     * @param array attributes
     */
    public function __construct(string $name, $attributes = null)
    {
        if (array_key_exists('useEmpty', $attributes)) {
            $this->setUserOption('useEmpty', $attributes['useEmpty']);
            unset($attributes['useEmpty']);
        }
        
        if (array_key_exists('emptyValue', $attributes)) {
            $this->setUserOption('emptyValue', $attributes['emptyValue']);
            unset($attributes['emptyValue']);
        }

        parent::__construct($name, $attributes);
    }
}

3. Use familiar syntax

You can use 0's and 1's or whatever you want

// status field
$status = new Check(
    'status',
    [
        'useEmpty'   => true,
        'emptyValue' => 'unpublished',
        'value'      => 'published'
    ]
);
$status->setLabel('Status');
$this->add($status);

from phalcon.

stale avatar stale commented on May 31, 2024

Thank you for contributing to this issue. As it has been 90 days since the last activity, we are automatically closing the issue. This is often because the request was already solved in some way and it just wasn't updated or it's no longer applicable. If that's not the case, please feel free to either reopen this issue or open a new one. We will be more than happy to look at it again! You can read more here: https://blog.phalconphp.com/post/github-closing-old-issues

from phalcon.

stale avatar stale commented on May 31, 2024

Thank you for contributing to this issue. As it has been 90 days since the last activity, we are automatically closing the issue. This is often because the request was already solved in some way and it just wasn't updated or it's no longer applicable. If that's not the case, please feel free to either reopen this issue or open a new one. We will be more than happy to look at it again! You can read more here: https://blog.phalcon.io/post/github-closing-old-issues

from phalcon.

stale avatar stale commented on May 31, 2024

Thank you for contributing to this issue. As it has been 90 days since the last activity, we are automatically closing the issue. This is often because the request was already solved in some way and it just wasn't updated or it's no longer applicable. If that's not the case, please feel free to either reopen this issue or open a new one. We will be more than happy to look at it again! You can read more here: https://blog.phalcon.io/post/github-closing-old-issues

from phalcon.

StudioMaX avatar StudioMaX commented on May 31, 2024

Still relevant

from phalcon.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.