Hy,
I'm really new to Zend-Framework 3 and I'm practicing OOP, I can't find a simple explanation/tutorial on making a Zend Form with a fieldset and legend. Basically I'm trying to create this in HTML:
<form name="my_name">
<fieldset>
<legend>My legend value</legend>
<input type="checkbox" name="name_1" value="value_1">Value 1</input>
<input type="checkbox" name="name_2" value="value_2">Value_2</input>
<input type="checkbox" name="name_3" value="value_3">Value_3</input>
</fieldset>
<input type="button" value="Get values" id="btn"/>
</form>
I checked the official documentation about Zend Forms and Collections and Fieldsets, but it's really confusing me. Any help would be greatly appreciated.
First, I am sorry as it is going to be a bit long one. But this would describe the form in action. So be patient please!
Assuming you are known to ZF3 default Application module. Some folders are created in the Application module for separation of each element. You need to create them as follows.
Let's get started by creating your fieldsets first. Zend\Form\Fieldset component represents a reusable set of elements and is dependent on Zend\From\Form component. This means you need to attach this to Zend\Form\Form.
module/Application/src/Form/Fieldset/YourFieldset.php
<?php
namespace Application\Form\Fieldset;
use Zend\Form\Element;
use Zend\Form\Fieldset;
class YourFieldset extends Fieldset
{
public function __construct($name = null)
{
parent::__construct('your-fieldset');
$this->add([
'name' => 'name_1',
'type' => Element\Checkbox::class,
'options' => [
'label' => 'Value 1',
'use_hidden_element' => true,
'checked_value' => 'yes',
'unchecked_value' => 'no',
],
'attributes' => [
'value' => 'no',
],
]);
// Creates others as your needs
}
}
Now we would create the form using Zend\From\Form attaching the fieldset created from Zend\From\Fieldset.
module/Application/src/Form/YourForm.php
<?php
namespace Application\Form;
use Application\Form\Fieldset\YourFieldset;
use Zend\Form\Form;
class YourForm extends Form
{
public function __construct($name = null)
{
parent::__construct('your-form');
$this->add([
// This name will be used to fetch each checkbox from
// the CheckboxFieldset::class in the view template.
'name' => 'fieldsets',
'type' => YourFieldset::class
]);
$this->add([
'name' => 'submit',
'attributes' => [
'type' => 'submit',
'value' => 'Get Values',
'class' => 'btn btn-primary'
],
]);
}
}
Our from is almost ready. We need to make it serviceable if we want it to be used in any action of a controller. So let's do that.
Update your module config file. If service_manager key does not exist then add the following snippet of code, otherwise, update only factories and aliases key as the following.
Fix namespaces in module config file.
module/Application/config/module.config.php
'service_manager' => [
'factories' => [
// Form service
Form\YourForm::class => Zend\ServiceManager\Factory\InvokableFactory::class,
// Other services
],
'aliases' => [
// Make an alias for the form service
'YourForm' => Form\YourForm::class,
],
],
Now the form is ready to be used. This needs to be injected into our controller. As I am working on Application module, I would inject the form into the IndexController::class's constructor. Then we would be using that form inside IndexController::fieldsetAction() method.
module/Application/src/Controller/IndexController.php
<?php
namespace Application\Controller;
use Zend\Form\FormInterface;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController
{
protected $YourForm;
public function __construct(FormInterface $YourForm)
{
$this->YourForm = $YourForm;
}
public function fieldsetAction()
{
$request = $this->getRequest();
$viewModel = new ViewModel(['form' => $this->YourForm]);
if (! $request->isPost()) {
return $viewModel;
}
$this->YourForm->setData($request->getPost());
if (! $this->YourForm->isValid()) {
return $viewModel;
}
$data = $this->YourForm->getData()['fieldsets'];
echo '<pre>';
print_r($data);
echo '</pre>';
return $viewModel;
}
}
As this controller is taking argument in its constructor, we need to create a factory for this controller (a factory creates other objects).
module/Application/src/Factory/Controller/IndexControllerFactory.php
<?php
namespace Application\Factory\Controller;
use Application\Controller\IndexController;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class IndexControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// We get form service via service manager here
// and then inject controller's constructor
$YourForm = $container->get('YourForm');
return new IndexController($YourForm);
}
}
Once again, we need to update the module config file. We would add this time the factory under controllers key as follows
'controllers' => [
'factories' => [
Controller\IndexController::class => Factory\Controller\IndexControllerFactory::class,
],
],
At the end, echo the form in the view template as follows:
module/Application/view/application/index/fieldset.phtml
<h1>Checkbox Form</h1>
<?php
$form = $this->form;
$form->setAttribute('action', $this->url());
// Here is the catch, remember this name from the CheckboxForm::class
$fieldset = $form->get('fieldsets');
$name_1 = $fieldset->get('name_1');
$name_2 = $fieldset->get('name_2');
$name_3 = $fieldset->get('name_3');
$submit = $form->get('submit');
$submit->setAttribute('class', 'btn btn-primary');
$form->prepare();
echo $this->form()->openTag($form);
?>
<fieldset>
<legend>My legend value</legend>
<?= $this->formElement($name_1) ?>
<?= $this->formLabel($name_1) ?>
<?= $this->formElement($name_2) ?>
<?= $this->formLabel($name_2) ?>
<?= $this->formElement($name_3) ?>
<?= $this->formLabel($name_3) ?>
<?= $this->formSubmit($submit) ?>
</fieldset>
<?php
echo $this->form()->closeTag();
Hope this would help you!
Actually the example you are looking for is in "collections" part of zend form. Its not the exact one but kinda like.
Here you are a little example. I ignored namespaces and hope so there's no typo :)
class myFieldset extends Fieldset {
public function init(){
$this
->add([
'name' => 'name_1,
'type' => 'text',
])
->add([
'name' => 'name_2,
'type' => 'text',
])
->add([
'name' => 'name_3,
'type' => 'text',
]);
}
}
class MyForm extends Form {
public function init(){
$this->add([
'type' => myFieldset,
'name' => 'fieldset'
])->add([
'type' => 'button',
'name' => 'action'
]);
}
}
And in view file;
<?=$this-form($form);?>
Related
I would like to implement a layout in which a column is repeated several times and can be specified as dynamic content in the CMS. However, I cannot find an object type that would fit for this.
Do I have to specify the input fields individually for each column?
private static $db = [
'Intro_Headline' => 'Varchar',
'Intro_Subheadline' => 'Varchar',
'Intro_Text' => 'HTMLText',
'Intro_Headline2' => 'Varchar',
'Intro_Subheadline2' => 'Varchar',
'Intro_Text2' => 'HTMLText',
...
];
--
$fields = parent::getCMSFields();
//Intro Field 1
$fields->addFieldToTab('Root.Intro', TextField::create('Intro_Headline', 'Headline'), 'Content');
$fields->addFieldtoTab('Root.Intro', TextField::create('Intro_Subheadline', 'Subheadline'), 'Content');
$fields->addFieldToTab('Root.Intro', HTMLEditorField::create('Intro_Text', 'Text'), 'Content');
//Intro Field 2
$fields->addFieldToTab('Root.Intro', TextField::create('Intro_Headline2', 'Headline'), 'Content');
$fields->addFieldtoTab('Root.Intro', TextField::create('Intro_Subheadline2', 'Subheadline'), 'Content');
$fields->addFieldToTab('Root.Intro', HTMLEditorField::create('Intro_Text2', 'Text'), 'Content');
...
Or can someone tell me which field type I can't find?
Update:
Now I have a $has_many variable in addition to my $db variable in my page-model:
private static $has_many = [
'Intro_Columns' => IntroColumn::class,
];
In the getCMSFields() function I add them like this:
$fields->addFieldToTab('Root.Columns', GridField::create('Intro_Columns', 'Columns', IntroColumn::get()), 'Content');
And my data object looks like this:
class IntroColumn extends DataObject
{
private static $db = [
'img_url' => 'Text',
'headline' => 'Varchar',
'subheadline' => 'Varchar',
'text' => 'Text',
'link' => 'Text'
];
}
But the fields are not yet displayed in the CMS. How do I output the data fields from a data object?
For things to be repeatable, you will have to put them into a different object, and then link multiple of those objects to your current object/page.
GridFIeld
The default way of doing this in SilverStripe 4 is using the built in Database Relation ($has_many or $many_many instead of $db) and GridField` as the form field.
I'd recommend you go through this tutorial: https://docs.silverstripe.org/en/4/developer_guides/model/relations/
In paticular the section about $has_many will apply to your usecase. (Example where 1 Team has multiple Players or 1 Company multiple People)
$has_many/$many_many is a very generic option and can be used for any number of possible database relation (linking Categories, Images, Pages, ...)
elemental module
Another option is an officially supported module called elemental. This is very specifically built for repeatable content.
https://github.com/silverstripe/silverstripe-elemental
serialized-dataobject module
Probably not ideal for your usecase, but I maintain a module that provides an alternative to GridField, but it's more suitable for small form fields. The HTMLEditor is to large to be useful in this module.
https://github.com/Zauberfisch/silverstripe-serialized-dataobject
PS: regardless of what way you go, I highly recommend you go through the tutorial from (1.). It's a pretty important fundamental functionality of SilverStripe.
EDIT: response to your updated question:
If you are using GridField, I'd recommend the following:
class Page extends SiteTree {
private static $has_many = [
'Intro_Columns' => IntroColumn::class,
];
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Columns', new GridField(
'Intro_Columns',
'My Columns',
$this->Intro_Columns(),
new GridFieldConfig_RecordEditor()
), 'Content');
return $fields;
}
}
class IntroColumn extends DataObject {
private static $db = [
'headline' => 'Varchar',
'subheadline' => 'Varchar',
'text' => 'Text',
'link' => 'Text'
];
private static $has_one = [
'Image' => 'Image',
]
public function getCMSFields() {
$fields = new FieldList();
$fields->push(new TextField('headline', 'My Headline'));
$fields->push(new TextField('subheadline', 'My Subheadline'));
// ... and so on
$fields->push(new UploadField('Image', 'Upload an image'));
return $fields;
}
}
Note that I am using $this->Intro_Columns() as the Value for the GridField and not IntroColumn::get(). Because $this->Intro_Columns() is a automatically generated Method that returns all IntroColumn objects linked to the current page. But $this->Intro_Columns() would return all IntroColumns from all Pages
In the template, you cal also access this automatically generated method:
<!-- Page.ss -->
<h1>Page Title: $Title</h1>
<div class="intro-columns">
<% loop $Intro_Columns %>
<div class="intro-column">
<!-- here we are scoped into a single IntroColumn, so you can use all DB fields and methods of that object -->
<h2>$headline <br> $subheadline</h2>
$Image.URL <br>
...
</div>
<% end_loop %>
</div>
I am currenly creating a simple search form, using Symfony 4.1's form builder. Rendering works great, but I lack a bit of control on the way the request is formed.
The generated request looks like:
http://localhost:8000/fr/search?advanced_search%5Bquery%5D=test&advanced_search%5Bcategory%5D=1&advanced_search%5BverifiedOnly%5D=&advanced_search%5Bsave%5D=
And I want it to look like:
http://localhost:8000/fr/search?query=test&category=1&verifiedOnly=
The code I'm using:
$this->ff->create(
AdvancedSearchType::class,
null,
['csrf_protection' => false]
)->createView()
And
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setMethod('GET')
->add('query', SearchType::class,
[
'attr'=> [
'placeholder' => 'search.query'
]
]
)
->add('category', EntityType::class,
[
'class' => Category::class,
'label' => 'search.category',
'group_by' => function(Category $category, $key, $value) {
// Code...
return $category->getName();
}
]
)
->add('verifiedOnly', CheckboxType::class,
[
'label' => 'search.verifiedOnly',
'required' => false
]
)
->add('save', SubmitType::class,
[
'label' => 'search.submitButton'
]
);
}
The reason behind this is because it generates widgets like this
<input .. name="advanced_search[query]" ..>
When I want them like this
<input .. name="query" ..>
Is there any way to change this? Thank you!
When you are using the shortcut method provided by Symfony's ControllerTrait, the name of the generated form will be automatically derived from the form type's block prefix.
You can change the implicit name for all forms based on this type by overriding the getBlockPrefix() method in your form type:
public function getBlockPrefix()
{
return '';
}
Or you decide to change the name for just one particular form by giving its name explicitly making use of the form factory that is used under the hood by the controller trait's shortcut methods like this:
$form = $this->get('form.factory')
->createNamedBuilder('', AdvancedSearchType::class, null, [
'csrf_protection' => false,
])
->getForm();
But you need to be careful now. If there is more than one form without a name handled during the same request, the component is not able to decide which of these forms has been submitted and will act as if both had been so.
This used to work for me without fail when Drupal 8 first came out. However this does not seem to work anymore and I get an error. Drupal docs have always been horrid so no solution there.
custom.module
<?php
function custom_theme() {
$theme['home_page'] = [
'variables' => ['name' => NULL],
'template' => 'home_page'
];
return $theme;
}
function custom_menu(){
$items = array();
$items['admin/config/system/custom'] = array(
'title' => 'Custom',
'description' => 'Configuration Custom',
'route_name' => 'custom.settings'
);
return $items;
}
custom.routing.yml
custom.home:
path: /home
defaults:
_controller: Drupal\custom\Controller\RoutingController::home
requirements:
_permission: 'access content'
src/Controller/RoutingController.php
<?php
namespace Drupal\custom\Controller;
class RoutingController {
public function home(){
return array(
'#title' => 'Home',
'#theme' => 'home_page'
);
}
}
home_page.html.twig
<main>
<!-- some markup -->
</main>
your controller not extending the base controller class problem one
try this
namespace Drupal\custom\Controller;
use Drupal\Core\Controller\ControllerBase;
class RoutingController extends ControllerBase{
public function home(){
return array(
'#title' => 'Home',
'#theme' => 'home_page'
);
}
}
home_page.html.twig
<main>
<!-- some markup -->
{{ content }}
</main>
also try to extend you theme hook with path
'path' => drupal_get_path('module', 'custom') . '/templates',
and place your template twig file in your module/templates folder
I went to this tutorial. I got the example to work. But I don't get it because I can't customize it for my case.
I want to display a picture for each of my facebook friend and pass the adress of the profile picture from facebook to an image tag in front of the checkbox element.
Update
part of my action:
//the array is on part of my update.
$choice_test = array();
$choice_test[] = array('id' => 1, 'username' => 'test');
$choice_test[] = array('id' => 2, 'username' => 'test2');
->add('friend', 'choice', array(
'required' => true,
'expanded' => true,
'choice_list' => $choice_test, //this is my update
'choices' => $fb_friends_form, //$fb_friends_form[1]= 'First Lastane';
'multiple' => true,
'constraints' => array(new CheckChoicesFbFriends(array('fb_friends_form' => $fb_friends_form))),
'mapped' => true
))
return $this->render('FrontendChancesBundle::createrequest.html.php', array(
'form' => $form->createView()));
template:
<?php $view['form']->setTheme($form, array('FrontendDemoBundle:Form')) ;?>
<?php echo $view['form']->widget($form['friend'])?>
in FrontendDemoBundle/Ressources/views/Form/form_widget_pics.html.php:
<input
type="<?php echo isset($type) ? $view->escape($type) : 'checkbox' ?>"
<?php if (!empty($value)): ?>value="<?php echo $view->escape($value) ?>"<?php endif ?>
<?php echo $view['form']->block($form, 'checkbox_widget') ?>
/>
How can I pass even a variable, in my case the username (`$fb_username[0] = 'username' of facebook to from_widget_pics.html.php and how can I display it like this with the form bulider in my aciton:
<img src="www.facebook.com/+FirstLastname>
<input type="checkbox"
id="form_friend_0"
name="form[friend][]"
value="1"/>
<label for="form_friend_0" >First Lastname</label>
<img src="www.facebook.com/+nextfriend>
I had the very same exception message when trying to pass an array or collection of custom selected objects to a form using 'choice_list' option.
I solved it this way (simplified example):
Controller code:
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
class UsedCarsController extends Controller {
..... action code:
$usedCars = $carRepository->findAllUsed();
$choiceList = new SimpleChoiceList($usedCars);
$form = $this->createForm(new UsedCarsType($choiceList),null, array('label' => 'Used cars form'));
... now render view, etc.
UsedCarsType:
class UsedCarsType extends AbstractType
{
private $choicesList;
public function __construct($choicesList)
{
$this->choicesList = $choicesList;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('usedCars', 'choice',array('label'=>'Used cars',
'required'=>true,
'choice_list' => $this->choicesList,
'empty_value' => '-- used car --'))
;
}
Choice_list option must be an instance of SimpleChoiceList class, not an array.
A bit late but hope this helps.
BTW: I am using Symfony 2.3
I embed one form to another. I need to add HTML attributes to the fields embedded form.
Trying to do so:
$this->widgetschema['email'] = new sfWidgetFormInputText(array(), array('class' => 'email'));
but it does not work.
Did you try it this way?
creating a class
class YourForm extends sfForm {
$array= array('array');
$this->setWidgets(array(
'field1' => new sfWidgetFormSelect(array('choices' => $array), array('placeholder' => 'field1', 'required' => 'true', 'data-empty' => 'U did not enter field1!')),
'email_address' => new sfWidgetFormInputText(array('type' => 'email'), array())
));
}
You can even change the type in the first array. Then include your form in the actions by calling it like
$this->form = new YourForm();
Then echo your form in the template:
<?php echo $form; ?>
I hope this helps. I use it like this as well, to add extra attributes to use in javascript.