Zend 2 - Form on every page - forms

I am new to Zend Framework 2
I want to add a form to every page (a login box for example) that functions as it does when in its own module ie. so it validates and there is no need to redirect back from the module after the action.
I have looked at various things such as view helpers and action helpers, I have not posted any code as it may just add confusion
I am looking for a guide on how to achieve this as I currently am confused as to how this would be best implemented

This is certainly a use case for a view helper. This provides a piece of logic reusable in multiple views across different modules.
Take for example the login form,. you might want to return a Zend\Form\Form instance when you call the helper. So first, create the helper:
namespace MyLogin\View\Helper;
use Zend\View\Helper\AbstractHelper;
use Zend\Form\Form;
class LoginForm extends AbstractHelper
{
public function __invoke()
{
$form = new Form;
$url = $this->getView()->url('user/login');
$form->setAttribute('action', $url);
$form->add([
'name' => 'username',
]);
$form->add([
'type' => 'password',
'name' => 'password',
]);
return $form;
}
}
Then you register this view helper under the name "loginForm" in your config:
'view_helpers' => [
'invokables' => [
'loginForm' => 'MyLogin\View\Helper\LoginForm',
],
],
Now you can use the helper in your views:
<?php $form = $this->loginForm() ?>
<?= $this->form()->openTag($form)?>
<?= $this->formRow($form->get('username'))?>
<?= $this->formRow($form->get('password'))?>
<button type="submit" value="Login">
<?= $this->form()->closeTag()?>
Of course you can apply any login in your form, whatever you need to be reusable:
Return a form instance so your view can render the form
Return a rendered view already in the helper so your view does not need to render
Set all kind of options to the form
Etc

Related

Yii2 One field in different tabs of buttflattery\yii2-formwizard

Now I am workin on complex quiz App on Yii2. Here is MCQ test that contains more than 100 questions. I want to separate this questions into 5 form tabs (so that questions from 1 to 20 in tab1, from 21 to 40 in tab2 etc). Could someone explain what is the way to do this? So, there is only one model and one form submit.
I thought about using the buttflattery\yii2-formwizard. In the documentation, I have found the Single Model Across Steps tutorial, but it is not really suitable for my case because all questions are written in one field as many rows.
For now Answers Model is following:
class Answers extends ActiveRecord
{
public function rules(){
return[
[['id','question_id', 'option_id', 'user_id'], 'required'],
];
}
}
index view:
//start form
<?php $form = ActiveForm::begin([
'id' => 'my-form-id',
'action' => ['answers/save'],
'options' =>['class'=>['t-form']]
]);
?>
//foreach question:
<?php for ($i=0; $i<count($questions); $i++): ?>
<div class="input-title">
<?= Html::encode("{$questions[$i]->title}") ?>
</div>
<?php $options = Options::find()-> where
(['question_id'=>$questions[$i]->id]) ->all();
$options = ArrayHelper::map($options,'id', 'title');?>
//print options:
<div class="radio__wrapper">
<?= $form->field($model, 'option_id')->radioList(
$options,
['name'=>'Questions['.$questions[$i]->id.']',
'separator' => '<br>',
'required'=>true],)->label(false) ?>
</div>
//submit form
<?= Html::submitButton('Save', ['class' => 'submit']) ?>
<?php ActiveForm::end(); }
AnswersController:
public function actionSave(){
$request = \Yii::$app->request;
foreach($request->post('Questions') as $key=>$value) {
$model = new Answers();
$model->load($request->post());
$model->option_id = $value;
$model->question_id = $key;
$model->user_id = \Yii::$app->user->id;
$model->save(false);
}
if( $model->save(false)){
return $this->redirect(['result/index']);
}
}
If FormWizard is not suitable variant please explain me what is the most efficient way?
yii2-formwizard does provide you with a lot of options, which create a form wizard using the ActiveForm and Models.
Prominent Features
You can use a single model across all steps.
Separate model dedicated to every step.
Multiple models to a single step.
Disable/Enable validation.
Customize & order form fields.
Tabular Steps with Add Row button to add fields dynamically like Adress book.
Form Persistence (saves the un-saved form to be restored later using localstorage).
Preview Step ( Previews all the form input with labels as the last step, and navigates to the step when clicked).
Multiple Themes
Demos
You can see the DEMOS with all available variations and for Documentation use the Wiki
SETUP
use composer to install the extension
php composer.phar require buttflattery/yii2-formwizard "#dev"
or add into the composer.json file under require section
"buttflattery/yii2-formwizard":"#dev"
Sample Code
use buttflattery\formwizard\FormWizard;
$shootsModel = new Shoots();
$shootTagModel = new ShootTag();
echo FormWizard::widget([
'steps'=>[
[
'model'=>$shootsModel,
'title'=>'My Shoots',
'description'=>'Add your shoots',
'formInfoText'=>'Fill all fields'
],
[
'model'=> $shootTagModel,
'title'=>'My Shoots',
'description'=>'Add your shoots',
'formInfoText'=>'Fill all fields'
],
]
]);

CSRF field is missing when I embed my form with a requestAction in CakePHP 3

I want to embed a contact form in multiple places on my website.
I developed a contact form in a contact() function within my MessagesController.php:
// MessagesController.php
public function contact()
{
$this->set('title', 'Contact');
$message = $this->Messages->newEntity();
... // shortened for brevity
$this->set(compact('message'));
$this->set('_serialize', ['message']);
}
I loaded the CSRF component in the initialize() function of the AppController.php:
// AppController.php
public function initialize()
{
parent::initialize();
$this->loadComponent('Csrf');
... // shortened for brevity
}
The form is rendered with a contact.ctp and it works fine.
I followed CakePHP's cookbook which suggests using requestAction() within an element, then echoing the element where I want it:
// contact_form.ctp
<?php
echo $this->requestAction(
['controller' => 'Messages', 'action' => 'contact']
);
?>
And:
// home.ctp
<?= $this->element('contact_form'); ?>
The problem is that the form is rendered fine, but the CSRF hidden field is missing. It should be automatically added to the form since the CSRF component is called in the AppController.php.
I guess either using an element with a requestAction() isn't the solution for this particular case, or I am doing something wrong.
Any ideas? Thanks in advance for the input!
Request parameters need to be passed manually
requestAction() uses a new \Cake\Network\Request instance, and it doesn't pass the _Token and _csrf parameters to it, so that's why things break.
While you could pass them yourself via the $extra argument, like
$this->requestAction(
['controller' => 'Messages', 'action' => 'contact'],
[
'_Token' => $this->request->param('_Token'),
'_csrf' => $this->request->param('_csrf')
]
);
Use a cell instead
I would suggest using a cell instead, which is way more lightweight than requesting an action, also it operates in the current request and thus will work with the CSRF component out of the box.
You'd pretty much just need to copy your controller action code (as far as the code is concerned that you are showing), and add a loadModel() call to load the Messages table, something like
src/View/Cell/ContactFormCell.php
namespace App\View\Cell;
use Cake\View\Cell;
class ContactFormCell extends Cell
{
public function display()
{
$this->loadModel('Messages');
$this->set('title', 'Contact');
$message = $this->Messages->newEntity();
// ... shortened for brevity
$this->set(compact('message'));
$this->set('_serialize', ['message']);
}
}
Create the form in the corresponding cell template
src/Template/Cell/ContactForm/display.ctp
<?php
echo $this->Form->create(
/* ... */,
// The URL needs to be set explicitly, as the form is being
// created in the context of the current request
['url' => ['controller' => 'Messages', 'action' => 'contact']]
);
// ...
And then wherever you want to place the form, just use <?= $this->cell('ContactForm') ?>.
See also
API > \Cake\Routing\RequestActionTrait::requestAction()
Cookbook > Views > Cells

Zend\Form: Call to a member function insert() on a non-object in Zend/Form/Fieldset.php

I am learning how to use Zend Framework 2 (2.1.4) forms and running into this error.
Call to a member function insert() on a non-object in ... /Zend/Form/Fieldset.php on line 178
I don't want use the form to automatically connect to a database, in fact I only want to use the form to help validate and will pull from and populate it with an array of values. How do I turn off the database connectivity in the form objects?
I am used to dealing with the ZF1 forms so this new form system is confusing. Once I thought about it though, the way we can use the form elements in our view scripts for formatting is going to be nice. Those old decorators were a pain. Anyway, for me, it would be nice to use the forms without dealing with bound database objects. Is this possible? It just seems so overly complicated to need a model class using InputFilterAwareInterface classes in addition to a simple form. One step at a time though, I can't even get the form to display.
I appreciate any help.
Below are my controller, form, and view scripts:
Form class:
namespace FBWeb\Form;
use Zend\Form\Form;
use Zend\Form\Element;
class ClientForm extends Form
{
public function __construct()
{
$this->setAttribute('method', 'post');
$this->add(array(
'name' => 'client',
'type' => 'Zend\Form\Element\Text',
'options' => array(
'label' => 'Client Name',
),
'attributes' => array(
'type' => 'text',
),
));
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Add'
),
));
}
}
Controller class:
namespace FBWeb\Controller;
use Zend\Debug\Debug;
use Zend\Mvc\MvcEvent;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Session\Container;
use Zend\Http\Request;
use FBWeb\Form\ClientForm;
class ClientController extends AbstractActionController
{
public function indexAction()
{
$clientform = new ClientForm();
return array('form' => $clientform);
}
}
index.phtml view script:
<div id="clientformtable">
<?php
$form = $this->form;
$form->setAttribute('action','/app/client/add');
$form->prepare();
echo $this->form()->openTag($form);
$client = $form->get('client');
echo $this->formRow($client);
echo $this->form()->closeTag();
?>
</div>
This, and similar error messages, happen due to the fact that the form isn't properly set up. As you can see within the code above the __construct() function doesn't call the parents constructor. Therefore the internal "bootstrapping" doesn't happen and the error occurs.
You have to make sure to always call the parents constructor when dealing with Zend\Form\Form and/or Zend\Form\Fieldset.
parent::__construct('client-form');

How to get information from my controller to my form?

For a form I'm trying to add a selectbox which contains a list of items from my database.
My form is situated in /application/forms/News/Edit.php
In my controller I want to fetch this list which I want to use in my form.
How can I add that list from my controller to my form?
This is how my code in Edit.php looks like:
$this->addElement(
'select',
'view_status',
array(
'label' => 'View status',
'multioptions' => array(
//THIS SHOULD BE FILLED WITH DYNAMIC CONTENT FROM MY CONTROLLER
)
)
);
You can pass the options of your select in the first param of the form constructor. When initialising the form, Zend_Form look for a set method postfixed by the option name:
class App_Form_News_Edit extends Zend_Form
{
public function setViewStatusOptions($options)
{
$this->view_status->setMultioptions($options);
}
}
$form = new App_Form_News_Edit(array('viewStatusOptions' => array(..)));
Just use the constructor or _init function of your forms class to set any custom values you would have.
It would get you something looking like (in your controller):
$myForm = new form_News_Edit($myArrayOfValues);
Then in your forms class :
public function __construct($myArrayOfValue){
....
$this->addElement(
'select',
'view_status',
array(
'label' => 'View status',
'multioptions' => $myArrayOfValue
)
);
}
yvoyer's Solution is good too but takes out some of the business logic for the form out of it.
In your controller's action, you could initialize the options you wants depending on the action. You should set the optoions before sending the form to the view.
$Form = new form_News_Edit();
$Form->getElement('view_status')
->setMultioptions($arrayOptions);
$this->view->assign('Form', $Form);

Zend Form instantiation failing silently in white screen of death

Zend newbie trying to configure and use Zend_Form.
Just for the record I'm on Zend Framework Version: 1.11.1 on Win XP running Apache 2.something. I'm working on a site which for the most part works just fine. (Somebody else started it. I have to extend it).
I am having trouble in the area of forms and am trying to introduce Zend_Form in the hope that this will somehow simplify matters. But trying to use Zend_Form is presenting problems of it's own.
When I try to instantiate the first test form, I'm getting the white screen of death -- without even an error message.
Data as follows:
Dir Structure:
MYAPPNAME
....controllers
....forms
....models
....services
....views
Bootstrap.php contains:
protected function _initAutoLoading()
{
$loader = new Zend_Loader_Autoloader_Resource(array(
'namespace' => 'MYAPPNAME',
'basePath' => APPLICATION_PATH . '/modules/MYAPPNAME',
));
$loader->addResourceTypes(array(
'model' => array(
'path' => 'models',
'namespace' => 'Model'),
'form' => array(
'path' => 'forms',
'namespace' => 'Form'),
'service' => array(
'path' => 'services',
'namespace' => 'Service')));
}
This works fine for models with names like:
class MYAPPNAME_Model_DataRecordName extends Doctrine_Record
{
etc...
But it seems to be failing miserably for forms ... although mind you, this is my first pass at using Zend_Form.
My form is defined in file MYAPPNAME/forms/Formtest.php:
<?php
class MYAPPNAME_Form_Formtest extends Zend_Form
{
public function init($action){
$this->setAction($action)
->setMethod('post')
->setAttrib('id', 'formtestForm');
$email = $this->addElement( 'text', 'email',
array('label', => 'EMail'));
)
$submit = $this->addElement('submit', 'Submit and Be Free!');
}// End init
} // End class def
The form is being displayed in a view defined as:
<div class=""testForm">
<p style="margin-top:20px; margin-bottom:10px"">Explanatory Text</p>
<h2>This is a Form Test</h2>
<?php echo $this->formResponse; ?>
<?php echo $this->form; ?>
<hr>
<p>FORM ABOVE THIS BAR</p>
</div>
The view works just fine.
It is being managed by an action (in a working controller) defined as:
public function formtestAction(){
echo "formtestAction: ENTERED";
$form = new MYAPPNAME_Form_Formtest('ThisController/formtest2');
//$form = "<p>GARBAGE DATA</p>";
if(!empty($form)){$this->view->form = $form;}
else{
$form = "<p>THE FORM VAR IS EMPTY</p>";
$this->view->form = $form;
$formResponse = "<p>INSTANTIATION FAILED</p>";
$this->view->formResponse = $formResponse;
}
}
public function formtest2Action(){
echo "formtest2Action: ENTERED";
}
If I comment out both the form instantiation and the garbage data lines, I get valid output in the view. If I set $form to "GARBAGE DATA" I also get valid predictable output.
However when I try to instantiate the form object I get the white screen of death containing only "formtestAction: ENTERED" (from the echo statement at the top.)
I am going slowly mad.
I can't figure out if this is an autoloader problem, a routing problem, an object instantiation problem, or what.
I'd be very much obliged for any advice.
Thanks for reading.
With Zends, I've run into that several times, and it usually is something annoying as a superflous comma. In
... 'basePath' => APPLICATION_PATH . '/modules/MYAPPNAME',));
it looks just like on of those. Only a quick look, but you might check it anyway.
HTH,
Marcus
mtoepper: Very close. Good catch!
It was indeed an extra comma, only it was in the Form class definition -- preventing successful object instantiation.
These silent failures are VERY annoying.