Adding custom form elements

classic Classic list List threaded Threaded
16 messages Options
Reply | Threaded
Open this post in threaded view
|

Adding custom form elements

robertbasic
Hey all!

I am working on the documentation now, and one thing I am unable to
get working is creating and using custom form elements.

OK, so, Album\Form\Element\CustomElement that extends
Zend\Form\Element\Email. The body of the class is empty, I just want
it to render a an Email input field.

I have both tried adding this in my module.config.php

    'form_elements' => array(
        'invokables' => array(
            'customelement' => 'Album\Form\Element\CustomElement'
        ),
    ),

as well as providing the same from the Module class

use Zend\ModuleManager\Feature\FormElementProviderInterface;
class Module implements FormElementProviderInterface
{
    public function getFormElementConfig()
    {
        return array(
            'invokables' => array(
                'customelement' => 'Album\Form\Element\CustomElement'
            )
        );
    }
}

$form = new Zend\Form\Form;
$form->add(array('name' => 'customemail', 'type' => 'customelement'));

and it dies with "Zend\ServiceManager\ServiceManager::get was unable
to fetch or create an instance for customelement"

But! This works:

$form = new Zend\Form\Form;
$form->add(array('name' => 'customemail', 'type' =>
'Album\Form\Element\CustomElement'));

If I do the same with a custom view helper (replacing the
form_elements module config key with view_helpers key, obviously) it
works as it should!

Someone help me out here, I am going crazy. Thanks.

--
~Robert Basic;
http://robertbasic.com/
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

robertbasic
Okay, this workflow is seriously broken, in my opinion.

So. If one wants to use a custom element with a short name, the
following is needed:

- You *must not* directly instantiate your form object, but you have
to get the FormElementManager through the service locator, and then
get your form from the form element manager.
- You *must not* override Zend\Form\Form's __construct-or in your form
class, you must override the init() method.

I'll send a PR for the documentation to make this *very* clear, but I
think this is workflow could use some improvement.

On 16 March 2013 18:27, Robert Basic <[hidden email]> wrote:

> Hey all!
>
> I am working on the documentation now, and one thing I am unable to
> get working is creating and using custom form elements.
>
> OK, so, Album\Form\Element\CustomElement that extends
> Zend\Form\Element\Email. The body of the class is empty, I just want
> it to render a an Email input field.
>
> I have both tried adding this in my module.config.php
>
>     'form_elements' => array(
>         'invokables' => array(
>             'customelement' => 'Album\Form\Element\CustomElement'
>         ),
>     ),
>
> as well as providing the same from the Module class
>
> use Zend\ModuleManager\Feature\FormElementProviderInterface;
> class Module implements FormElementProviderInterface
> {
>     public function getFormElementConfig()
>     {
>         return array(
>             'invokables' => array(
>                 'customelement' => 'Album\Form\Element\CustomElement'
>             )
>         );
>     }
> }
>
> $form = new Zend\Form\Form;
> $form->add(array('name' => 'customemail', 'type' => 'customelement'));
>
> and it dies with "Zend\ServiceManager\ServiceManager::get was unable
> to fetch or create an instance for customelement"
>
> But! This works:
>
> $form = new Zend\Form\Form;
> $form->add(array('name' => 'customemail', 'type' =>
> 'Album\Form\Element\CustomElement'));
>
> If I do the same with a custom view helper (replacing the
> form_elements module config key with view_helpers key, obviously) it
> works as it should!
>
> Someone help me out here, I am going crazy. Thanks.
>
> --
> ~Robert Basic;
> http://robertbasic.com/



--
~Robert Basic;
http://robertbasic.com/
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

robertbasic
Sent a pull request to tidy it up a bit
https://github.com/zendframework/zf2-documentation/pull/746

On 16 March 2013 19:03, Robert Basic <[hidden email]> wrote:

> Okay, this workflow is seriously broken, in my opinion.
>
> So. If one wants to use a custom element with a short name, the
> following is needed:
>
> - You *must not* directly instantiate your form object, but you have
> to get the FormElementManager through the service locator, and then
> get your form from the form element manager.
> - You *must not* override Zend\Form\Form's __construct-or in your form
> class, you must override the init() method.
>
> I'll send a PR for the documentation to make this *very* clear, but I
> think this is workflow could use some improvement.
>
> On 16 March 2013 18:27, Robert Basic <[hidden email]> wrote:
>> Hey all!
>>
>> I am working on the documentation now, and one thing I am unable to
>> get working is creating and using custom form elements.
>>
>> OK, so, Album\Form\Element\CustomElement that extends
>> Zend\Form\Element\Email. The body of the class is empty, I just want
>> it to render a an Email input field.
>>
>> I have both tried adding this in my module.config.php
>>
>>     'form_elements' => array(
>>         'invokables' => array(
>>             'customelement' => 'Album\Form\Element\CustomElement'
>>         ),
>>     ),
>>
>> as well as providing the same from the Module class
>>
>> use Zend\ModuleManager\Feature\FormElementProviderInterface;
>> class Module implements FormElementProviderInterface
>> {
>>     public function getFormElementConfig()
>>     {
>>         return array(
>>             'invokables' => array(
>>                 'customelement' => 'Album\Form\Element\CustomElement'
>>             )
>>         );
>>     }
>> }
>>
>> $form = new Zend\Form\Form;
>> $form->add(array('name' => 'customemail', 'type' => 'customelement'));
>>
>> and it dies with "Zend\ServiceManager\ServiceManager::get was unable
>> to fetch or create an instance for customelement"
>>
>> But! This works:
>>
>> $form = new Zend\Form\Form;
>> $form->add(array('name' => 'customemail', 'type' =>
>> 'Album\Form\Element\CustomElement'));
>>
>> If I do the same with a custom view helper (replacing the
>> form_elements module config key with view_helpers key, obviously) it
>> works as it should!
>>
>> Someone help me out here, I am going crazy. Thanks.
>>
>> --
>> ~Robert Basic;
>> http://robertbasic.com/
>
>
>
> --
> ~Robert Basic;
> http://robertbasic.com/



--
~Robert Basic;
http://robertbasic.com/
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

ThaDafinser
In reply to this post by robertbasic

Hello Robert,

Am 16.03.2013 19:03 schrieb "Robert Basic" <[hidden email]>:
>
> Okay, this workflow is seriously broken, in my opinion.
>
> So. If one wants to use a custom element with a short name, the
> following is needed:
>
> - You *must not* directly instantiate your form object, but you have
> to get the FormElementManager through the service locator, and then
> get your form from the form element manager.
> - You *must not* override Zend\Form\Form's __construct-or in your form
> class, you must override the init() method.

I think i got the same problem yesterday.....got a custom __constructor in form....

I wasted there much time :-/
I ll check tomorrow if its really this part which is wrong.

If this is the problem you saved my day :-)

> I'll send a PR for the documentation to make this *very* clear, but I
> think this is workflow could use some improvement.
>
> On 16 March 2013 18:27, Robert Basic <[hidden email]> wrote:
> > Hey all!
> >
> > I am working on the documentation now, and one thing I am unable to
> > get working is creating and using custom form elements.
> >
> > OK, so, Album\Form\Element\CustomElement that extends
> > Zend\Form\Element\Email. The body of the class is empty, I just want
> > it to render a an Email input field.
> >
> > I have both tried adding this in my module.config.php
> >
> >     'form_elements' => array(
> >         'invokables' => array(
> >             'customelement' => 'Album\Form\Element\CustomElement'
> >         ),
> >     ),
> >
> > as well as providing the same from the Module class
> >
> > use Zend\ModuleManager\Feature\FormElementProviderInterface;
> > class Module implements FormElementProviderInterface
> > {
> >     public function getFormElementConfig()
> >     {
> >         return array(
> >             'invokables' => array(
> >                 'customelement' => 'Album\Form\Element\CustomElement'
> >             )
> >         );
> >     }
> > }
> >
> > $form = new Zend\Form\Form;
> > $form->add(array('name' => 'customemail', 'type' => 'customelement'));
> >
> > and it dies with "Zend\ServiceManager\ServiceManager::get was unable
> > to fetch or create an instance for customelement"
> >
> > But! This works:
> >
> > $form = new Zend\Form\Form;
> > $form->add(array('name' => 'customemail', 'type' =>
> > 'Album\Form\Element\CustomElement'));
> >
> > If I do the same with a custom view helper (replacing the
> > form_elements module config key with view_helpers key, obviously) it
> > works as it should!
> >
> > Someone help me out here, I am going crazy. Thanks.
> >
> > --
> > ~Robert Basic;
> > http://robertbasic.com/
>
>
>
> --
> ~Robert Basic;
> http://robertbasic.com/

Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

bakura
This post has NOT been accepted by the mailing list yet.
In reply to this post by robertbasic
Hi,

I am the one behind this feature. The init method is a trick I had to used mainly because when I wrote this, I used initializers a lot to inject, for instance, Doctrine Object manager. So of course, as elements are most often added into the constructor, I didn't have the dependency and hence I introduced the init thing...

But I don't like it too. This is something that will need to be modified for ZF 3 (for instance by removing the concept of initializers, so that dependencies are only handled in constructor).

Concerning your problems for this not working:

$form = new Zend\Form\Form;
$form->add(array('name' => 'customemail', 'type' => 'customelement'));

But this working:

$form = new Zend\Form\Form;
$form->add(array('name' => 'customemail', 'type' =>
'Album\Form\Element\CustomElement'));

This is perfectly normal. Internally, when you fetch a form/fieldset/element from the form plugin manager, it injects itself into the factory that is used to construct object from array configuration.

When you directly instantiate a form (as with new Zend\Form\Form), it will just create a new FormElementManager that only contains pre-defined form elements ('email', 'url'...), not yours. But it's exactly the same for view_helpers.

Everything works because from beginning to end, the framework internally fetch the view helper manager from service locator.

Basically, the idea is that the service locator and the various plugin manager should be ALWAYS used (so a ZF 2 application nearly contains no new Something()).
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

ThaDafinser
In reply to this post by robertbasic
Hello Robert,

this was really the issue! Just changed from __construct() to init() and everything is fine.
Thanks for changing this :-)

Best regards
Martin

2013/3/16 Robert Basic <[hidden email]>
Sent a pull request to tidy it up a bit
https://github.com/zendframework/zf2-documentation/pull/746

On 16 March 2013 19:03, Robert Basic <[hidden email]> wrote:
> Okay, this workflow is seriously broken, in my opinion.
>
> So. If one wants to use a custom element with a short name, the
> following is needed:
>
> - You *must not* directly instantiate your form object, but you have
> to get the FormElementManager through the service locator, and then
> get your form from the form element manager.
> - You *must not* override Zend\Form\Form's __construct-or in your form
> class, you must override the init() method.
>
> I'll send a PR for the documentation to make this *very* clear, but I
> think this is workflow could use some improvement.
>
> On 16 March 2013 18:27, Robert Basic <[hidden email]> wrote:
>> Hey all!
>>
>> I am working on the documentation now, and one thing I am unable to
>> get working is creating and using custom form elements.
>>
>> OK, so, Album\Form\Element\CustomElement that extends
>> Zend\Form\Element\Email. The body of the class is empty, I just want
>> it to render a an Email input field.
>>
>> I have both tried adding this in my module.config.php
>>
>>     'form_elements' => array(
>>         'invokables' => array(
>>             'customelement' => 'Album\Form\Element\CustomElement'
>>         ),
>>     ),
>>
>> as well as providing the same from the Module class
>>
>> use Zend\ModuleManager\Feature\FormElementProviderInterface;
>> class Module implements FormElementProviderInterface
>> {
>>     public function getFormElementConfig()
>>     {
>>         return array(
>>             'invokables' => array(
>>                 'customelement' => 'Album\Form\Element\CustomElement'
>>             )
>>         );
>>     }
>> }
>>
>> $form = new Zend\Form\Form;
>> $form->add(array('name' => 'customemail', 'type' => 'customelement'));
>>
>> and it dies with "Zend\ServiceManager\ServiceManager::get was unable
>> to fetch or create an instance for customelement"
>>
>> But! This works:
>>
>> $form = new Zend\Form\Form;
>> $form->add(array('name' => 'customemail', 'type' =>
>> 'Album\Form\Element\CustomElement'));
>>
>> If I do the same with a custom view helper (replacing the
>> form_elements module config key with view_helpers key, obviously) it
>> works as it should!
>>
>> Someone help me out here, I am going crazy. Thanks.
>>
>> --
>> ~Robert Basic;
>> http://robertbasic.com/
>
>
>
> --
> ~Robert Basic;
> http://robertbasic.com/



--
~Robert Basic;
http://robertbasic.com/

Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

robertbasic
Hey Martin!

On 20 March 2013 08:42, Martin Keckeis <[hidden email]> wrote:
> Hello Robert,
>
> this was really the issue! Just changed from __construct() to init() and
> everything is fine.
> Thanks for changing this :-)

Awesome, glad it works for you as well!

Anyway, could I ask you to read the manual where custom form elements
are described? I've done some rewriting to that part, just want to
know if it's more clear now how to work with custom elements. You can
find the new text here
https://zf2.readthedocs.org/en/latest/modules/zend.form.advanced-use-of-forms.html#creating-custom-elements

Thanks!
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

ThaDafinser
Hello Robert,

Awesome, glad it works for you as well!

Anyway, could I ask you to read the manual where custom form elements
are described? I've done some rewriting to that part, just want to
know if it's more clear now how to work with custom elements. You can
find the new text here
https://zf2.readthedocs.org/en/latest/modules/zend.form.advanced-use-of-forms.html#creating-custom-elements

Thanks!

The part with custom elements is fine. Thank you!
For the note use init() instead of __construct() i think it would be better to hightlight it with a warning or note, that you can't overread it.


My problem was not exactly "custom form elements", but Fieldset/Form collections.

There is still the wrong doc:
class CreateAlbum extends Form
{
    public function __construct()
    {
        $this->add(array(
            'name' => 'album',
            'type' => 'AlbumFieldset'
        ));
    }
}
This have to be added to init() as well like in custom form elements. (When you want to use the getFormElementConfig)
Otherwise the "AlbumFieldset" won't be created from the getFormElementConfig / factories or invokables....

In this part it's also not mentioned, that you have to use init, when using getFormElementConfig().


Best regards
Martin
Best regards
Martin 
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

ThaDafinser
Hello Robert,

here is a example, how i implemented it (and how it works):

Controller:
$form = $this->getServiceLocator()
            ->get('FormElementManager')
            ->get('LispLogbook\Form\EventForm');

The complete form:

class EventForm extends Form
{
    public function init(){
        parent::setName('eventForm');
        
        $this->setHydrator(new ObjectProperty());
        
       
        $this->add(array(
            'type' => 'LispLogbook\Form\EntryTagFieldset'
        ));
    }
}
 
The fieldset:
class EntryTagFieldset extends Fieldset 
{
    /**
     * 
     * @var array
     */
    private $tagsList = array();
    
    public function setTagsList($tagsList){
        $this->tagsList = $tagsList;
    }
    
    public function init(){
        parent::setName('entryTagFieldset');
        
        $this->setHydrator(new ObjectProperty());
        $this->setObject(new EntryTagEntity());
        
        $pstEntry_entId = new Element\Hidden('pstEntry_entId');
        $this->add($pstEntry_entId);
        
        $sysCodePosition_cpoId = new Element\Select('sysCodePosition_cpoId');
        $sysCodePosition_cpoId->setLabel('Tags');
        $sysCodePosition_cpoId->setAttribute('multiple', 'multiple');
        $sysCodePosition_cpoId->setValueOptions($this->tagsList);
        $sysCodePosition_cpoId->setAttribute('id', 'sysCodePosition_cpoId');
        $sysCodePosition_cpoId->setAttribute('style', 'width: 100%');
        $sysCodePosition_cpoId->setAttribute('required', 'required');
        $this->add($sysCodePosition_cpoId);
    }
}

Module.php
    public function getFormElementConfig ()
    {
        return array(
            'factories' => array(
                'LispLogbook\Form\EntryTagFieldset' => function  (FormElementManager $serviceManager)
                {
                    $dbEntryTag = $serviceManager->getServiceLocator()->get('LispLogbook\Model\EntryTagTable');
                    
                    $fieldset = new EntryTagFieldset();
                    $fieldset->setTagsList($dbEntryTag->listTags());
                    return $fieldset;
                }
            )
        );
    }
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

samsonasik
This post has NOT been accepted by the mailing list yet.
In reply to this post by robertbasic
btw, what

    // Define your element…

is ? i think it need more clear
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

samsonasik
This post has NOT been accepted by the mailing list yet.
In reply to this post by robertbasic
btw, what

    // Define your element…

is ? i think it need more clear. maybe some example ?
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

Александр Шелковский
In reply to this post by ThaDafinser
One more note here:

If you want to share your forms (and not create new one each time you call FormElementManager::get('form'), you should know that manager will call init on your form, even if it is shared. So you may have some problems. To fix it you can add something like that in your form class:
public function init() 
{
   if ( $this->isInit ) {
      return;
   }
   // your code
}

Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

Oliver Koenig
This post has NOT been accepted by the mailing list yet.
In reply to this post by bakura
Hi,

sorry, I know, it's an old thread, but the problem persists still.
What would be the recommended procedure when creating the form via the Form\Factory?
I have my form spec in config style files and pass them to the Form\Factory.
The Form\Factory in its function create($spec) obtains an instance of a Form type from the FormElementManager:
$element = $this->getFormElementManager()->get($type);
So it should be fine, one would expect, but it is not working.
Any idea?
Should I override the Form\Factory and implement a workaround in the create() function?

Thanks and regards,
Oliver
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

Oliver Koenig
This post has NOT been accepted by the mailing list yet.
In reply to this post by bakura
Hi,

I think I found the problem.
The FormElementManager's __construct can be passed the configuration, specifically the 'form_elements' part of the Config.
So here's the problem: When the FormElementManager gets constructed by the FormElementManagerFactory, it never gets passed any configuration.
So the problem is the FormElementManagerFactory, it should make sure that the FormElementManager processes the Config.

Regards,
Oliver
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

Oliver Koenig
This post has NOT been accepted by the mailing list yet.
Hi,

that looked like the problem, but it is only one problem, because the other problem is that the Form\Factory doesn't obtain its FormElementManager via the ServiceManager, but just creates a new one:

    public function getFormElementManager()
    {
        if ($this->formElementManager === null) {
            $this->setFormElementManager(new FormElementManager());
        }

        return $this->formElementManager;
    }

So in order to workaround this, we better subclass Form\Factory and override the function getFormElementManager(), and also override the FormElementManager with an own version, whhich shall then have our custom form element short names in its $invocables array.

Regards,
Oliver
Reply | Threaded
Open this post in threaded view
|

Re: Adding custom form elements

Oliver Koenig
This post has NOT been accepted by the mailing list yet.
Hi,

short workaround:
- create a FormElementManagerConfig with the invokables short names;
- subclass Form\Factory and override getFormElementManager and pass the config to the FormelementManager constructor;

class FormElementManagerConfig implements ConfigInterface
{
    protected $invokables = array(
        'anything'          => 'YourModule\Form\Element\Anything',
    );

    public function configureServiceManager(ServiceManager $serviceManager)
    {
        foreach ($this->invokables as $name => $service) {
            $serviceManager->setInvokableClass($name, $service);
        }
    }
}

class Factory extends \Zend\Form\Factory
{
    public function getFormElementManager()
    {
        if ($this->formElementManager === null) {
            $this->setFormElementManager(new FormElementManager(new FormElementManagerConfig()));
        }
        return $this->formElementManager;
    }
}

Regards,
Oliver