With Symfony 3.1 and KNP menu bundle I build a 2 level menu tree.
The selected page gets highlighted within the menu.
As I am using a drop-down menu, I additionaly want the top level of menu being highlighted - basically the parent entry of my selected child.
No idea how to achieve this. Docs did not help so far. Any help appreciated.
Thanks Wolfram
It is very late and I think you have already found the way, but in case someone is still looking for it, I am giving my answer.
Basically you can achieve this via the setCurrent() method. For example, I wanted to check it with my current routes. So, what I did was,
Added request_stack in my builder service. So my service was looking something like
app.menu_builder:
class: MyBundle\Menu\MenuBuilder
arguments: ["#knp_menu.factory", "#security.authorization_checker", '#request_stack']
tags:
- { name: knp_menu.menu_builder, method: createSideMenu, alias: side }
Now the constructor part in my MenuBuilder Class was looking something like
use Knp\Menu\FactoryInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class MenuBuilder
{
/**
* #var FactoryInterface
*/
private $factory;
/**
* #var TranslatorInterface
*/
private $translator;
/**
* #var RequestStack
*/
private $requestStack;
/**
* MenuBuilder constructor.
* #param FactoryInterface $factory
* #param TranslatorInterface $translator
*/
public function __construct(
FactoryInterface $factory,
TranslatorInterface $translator,
RequestStack $requestStack
) {
$this->factory = $factory;
$this->translator = $translator;
$this->requestStack = $requestStack;
$this->prefix = $requestStack->getCurrentRequest()->get('_prefix');
}
}
Then I created my menu for example:
$menu->addChild('level1_1', array(
'label' => "<span>Committee</span>",
'uri' => '#',
'extras' => array('safe_label' => true)
));
$menu['level1_1']->addChild('level2_1', array(
'label' => "<span>Level 2 Number 1</span>",
'route' => 'route_1',
'extras' => array('safe_label' => true)
));
$menu['level1_1']->addChild('level2_2', array(
'label' => "<span>Level 2 Number 2</span>",
'route' => 'route_2',
'extras' => array('safe_label' => true)
));
$menu->addChild('level1_2', array(
'label' => "<span>Committee</span>",
'uri' => '#',
'extras' => array('safe_label' => true)
));
$menu['level1_2']->addChild('level2_3', array(
'label' => "<span>Level 2 Number 3</span>",
'route' => 'route_3',
'extras' => array('safe_label' => true)
));
$menu['level1_2']->addChild('level2_4', array(
'label' => "<span>Level 2 Number 4</span>",
'route' => 'route_4',
'extras' => array('safe_label' => true)
));
Now comes the main part. After the menu object was ready for me, I mapped the routes with it's parent and set the parent active before returning the menu object.
$request = $this->requestStack->getCurrentRequest();
$routeName = $request->get('_route');
switch ($routeName)
{
case 'route_1':
case 'route_2':
$menu['level1_1']->setCurrent(true);
break;
case 'route_3':
case 'route_4':
$menu['level1_2']->setCurrent(true);
break;
}
In place of this switch section, you can use your own login according to your needs. Hope this is clear enough to understand.
Related
Is it possible to do while validate a form with an input filter to not just send an error message but also set the border colour change to red?
something like this:
$this->add(array(
'name' => 'test',
'required' => true,
'attributes' => array(
'style' => 'border-color:red'
),
'validators' => array(
array(
'name' => 'NotEmpty',
'options' => array(
'messages' => array(
\Zend\Validator\NotEmpty::IS_EMPTY => 'Please fill me out'
)
)
)
)
));
One way of doing this would be to create your own view helper to render a form element and in that add error classes if the field has errors.
To start you need to create your view helper and if the form element has errors, then you add a class.
The view helper:
namespace Application\View\Helper;
use Zend\Form\View\Helper\FormInput as ZendFormInput;
use Zend\Form\ElementInterface;
use Zend\Form\Exception;
class FormInput extends ZendFormInput
{
/**
* Render a form <input> element from the provided $element
*
* #param ElementInterface $element
* #throws Exception\DomainException
* #return string
*/
public function render(ElementInterface $element)
{
$name = $element->getName();
if ($name === null || $name === '') {
throw new Exception\DomainException(sprintf(
'%s requires that the element has an assigned name; none discovered',
__METHOD__
));
}
$attributes = $element->getAttributes();
$attributes['name'] = $name;
$attributes['type'] = $this->getType($element);
$attributes['value'] = $element->getValue();
return sprintf(
'<input %s class="form-control '.(count($element->getMessages()) > 0 ? 'error-class' : '').'" %s',
$this->createAttributesString($attributes),
$this->getInlineClosingBracket()
);
}
}
And to your module.config.php you will need to add
'view_helpers' => array(
'invokables'=> array(
'formInput' => 'Application\View\Helper\FormInput',
),
),
In my project I have a navigation, which is created from an array in a config.php file using the default factory. I want to add subpages to the current pages in the controller.
class IndexController extends AbstractActionController {
public function newpageAction() {
$navigation = $this->getServiceLocator()->get('navigation');
$currentPage = $navigation->findById('index');
$options = array(
'id' => 'newpage',
'label' => 'New Page',
'route' => 'my-route',
'controller' => 'index',
'action' => 'newpage',
'active' => true,
);
$newpage = new \Zend\Navigation\Page\Mvc($options);
$currentPage->addPage($newpage);
}
}
The page is added successfully but then I try to create the url for the page in the breadcrumbs view using the getHref() method of the page:
<?php foreach($this->pages as $page) {?>
<li>
<?php echo $page->getLabel();?>
</li>
<?php }?>
But I get the following error for the newly added pages:
Additional information:
Zend\Navigation\Exception\DomainException
File:
\vendor\zendframework\zendframework\library\Zend\Navigation\Page\Mvc.php:198
Message:
Zend\Navigation\Page\Mvc::getHref cannot execute as no Zend\Mvc\Router\RouteStackInterface instance is composed
I guess the problem is in the way I create and add the pages to the navigation. Is there another way to do that or how I fix this error?
I want to add the pages after the 3th level in the controller instead of in the config file because there are params in the urls of the pages and the labels are dynamic.
Any suggestions for accomplishing this task in any other way are welcome.
You could add the default router.
\Zend\Navigation\Page\Mvc::setDefaultRouter ($this->getServiceLocator ()->get ('router'));
The error is due to the MVC page having unmet dependencies (the router). It is the factory's job to inject these components (depending on a URI or MVC type).
To make sure each MVC page has router injected create a new factory that in turn uses another already provided factory Zend\Navigation\Service\ConstructedNavigationFactory to create your own navigation container and return it's pages. In your example this will be just one page.
EDIT
If you have to add the navigation pages in the controller, where you do not know the page config prior to the newpageAction(); You could extend the class to allow config to be set within the controller.
For example
public function MyCustomNavFactory extends ConstructedNavigationFactory
{
// make the config optional
public function __construct($config = array())
{
$this->config = $config;
}
// Allow config to be set outside the class
public function setConfig($config)
{
$this->config = $config;
}
}
Module.php
// Module
public function getServiceConfig() {
return array(
'invokables' => array(
// Create the factory as an invokable (as there are no __construct args)
'MyCustomNavFactory' => 'App\Navigation\Service\MyCustomNavFactory'
),
);
}
The controller call would then just be simply just use
// Controller
public function newpageAction()
{
$serviceManager = $this->getServiceLocator();
$navigation = $serviceManager->get('MyCustomNavFactory');
$options = array(
'id' => 'newpage',
'label' => 'New Page',
'route' => 'my-route',
'controller' => 'index',
'action' => 'newpage',
'active' => true,
);
$navigation->setConfig($options);
$pages = $navigation->getPages($serviceManager);
}
The answer of #AlexP is correct.
But there error into Controller Action As when he call custom factory using ServiceLocator will get Object of type AbstractContainer Object Because ServiceLocator will call createService method into your custom factory (MyCustomNavFactory) which extends AbstractNavigationFactory So the next line will call setConfig method into AbstractContainer Object not into your custom factory (MyCustomNavFactory).
The correct Way to Set Breadcrumb configuration from Controller Action is:
// Controller
public function newpageAction()
{
$serviceManager = $this->getServiceLocator();
$navigationFactory = new MyCustomNavFactory();
$options = array(
'id' => 'newpage',
'label' => 'New Page',
'route' => 'my-route',
'controller' => 'index',
'action' => 'newpage',
'active' => true,
);
$navigationFactory->setConfig($options);
$pages = $navigationFactory->getPages($serviceManager);
}
OR
Remove setConfig method form custom factory and set configuration using it's Constructor
// custom Factory
public function MyCustomNavFactory extends ConstructedNavigationFactory
{
// make the config optional
public function __construct($config = array())
{
parent::__construct($config);
}
}
Then Controller will be:
// Controller
public function newpageAction()
{
$serviceManager = $this->getServiceLocator();
$options = array(
array(
'id' => 'newpage',
'label' => 'New Page',
'route' => 'my-route',
'controller' => 'index',
'action' => 'newpage',
'active' => true,
)
);
$navigationFactory = new MyCustomNavFactory($options);
$pages = $navigationFactory->getPages($serviceManager);
}
Please help me with Zend framework 2:)
I want to create a form with collection of fieldsets using Form Element Manager (absolutely like in official documentation).
My FormElementManager configuration:
'form_elements' => array(
'factories' => array(
'Admin\Form\TaskForm' => function($sm) {
$form = new TaskForm();
$doctrimeEntityManager = $sm->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$form -> setEntityManager($doctrimeEntityManager);
$form -> init();
return $form;
},
'Admin\Form\TaskbrandFieldset' => function($sm) {
$doctrimeEntityManager = $sm->get('Doctrine\ORM\EntityManager');
$form = new TaskbrandFieldset();
$form->setEntityManager($doctrimeEntityManager);
return $form;
},
)
),
Admin\Form\TaskForm (only problem part):
namespace Admin\Form;
use Doctrine\ORM\EntityManager;
use Zend\Form\Form;
class TaskForm extends Form {
protected $entityManager;
public function init() {
$this->setAttribute('method', 'post');
// Id
$this->add(array(
'name' => 'id',
'attributes' => array(
'type' => 'hidden',
),
));
// My fieldset
$this->add(array(
'type' => 'Zend\Form\Element\Collection',
'name' => 'taskbrands',
'options' => array(
'label' => 'Brand of the product',
'count' => 0,
'should_create_template' => true,
'allow_add' => true,
'target_element' => array(
'type'=>'Admin\Form\TaskbrandFieldset'
),
),
'attributes' => array(
'id' => 'addressFieldset'
)
));
}
}
Admin\Form\TaskbrandFieldset:
namespace Admin\Form;
use Admin\Entity\Taskbrand;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Stdlib\Hydrator\ClassMethods as ClassMethodsHydrator;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class TaskbrandFieldset extends Fieldset implements InputFilterProviderInterface, ServiceLocatorAwareInterface {
protected $entityManager;
protected $serviceLocator;
public function init() {
$this->setName('TaskbrandFieldset');
$this->setHydrator(new ClassMethodsHydrator(false))
->setObject(new Taskbrand());
$this->setLabel('Taskbrand');
$this->add(array(
'type' => 'DoctrineModule\Form\Element\ObjectSelect',
'name' => 'brand',
'options' => array(
'object_manager' => $this->getEntityManager(),
'target_class' => 'Module\Entity\Brand',
'property' => 'name',
),
));
}
}
And, finally, my controller:
$Task = $this->getServiceLocator()->get('Admin\Model\Task')->findByPk($id);
$formManager = $this->getServiceLocator()->get('FormElementManager');
$form = $formManager->create('Admin\Form\TaskForm');
$form->bind($Task);
The problem is that form Admin\Form\TaskForm instantiates in factory described in form_elements configuration section, but Admin\Form\TaskbrandFieldset does not. It just invokes.
Trying to understand this problem I found that Admin\Form\TaskForm and Admin\Form\TaskbrandFieldset instantiates with different instances of FormElementManager, first one have my config inside (including factories description), but second has nothing.
Please help me :)
The problem is in your controller. Use
$form = $formManager->get('Admin\Form\TaskForm');
instead of
$form = $formManager->create('Admin\Form\TaskForm');
Remember that you don't have to use $form->init(). It's automatically called, same like in zf1. There is a good tutorial on zf2 site
I have form with some attributes:
class ToraForm extends Form
{
public function __construct($name = null)
{
parent::__construct('tora');
$this->setAttribute('method', 'post');
$this->add(array(
'name' => 'id',
'attributes' => array(
'type' => 'hidden',
),
));
$this->add(array(
'name' => 'name',
'attributes' => array(
'type' => 'text',
'required' => true,
),
'options' => array(
'label' => 'name',
),
));
}
but I want add drop-down list with data taken from another model. How to do it?
There are several different approaches you can take. Ultimately your Form has a dependency, which needs to be injected. I have written an in-depth blog-post about the three most common use-cases for Form-Dependencies for a Select-List.
Zend\Form\Element\Select and Database-Values
My BlogPost covers the following scenarios:
Zend\Form\Element\Select via DbAdapter
Zend\Form\Element\Select via TableGateway
DoctrineModule\Form\Element\DoctrineObject via Doctrine2
Here i will demonstrate only the DbAdapter approach without much explanation. Please refer to my blogpost for the in-depth explanations.
public function formDbAdapterAction()
{
$vm = new ViewModel();
$vm->setTemplate('form-dependencies/form/form-db-adapter.phtml');
$dbAdapter = $this->getServiceLocator()->get('Zend\Db\Adapter\Adapter');
$form = new DbAdapterForm($dbAdapter);
return $vm->setVariables(array(
'form' => $form
));
}
Then the respective Form Class:
class DbAdapterForm extends Form
{
protected $dbAdapter;
public function __construct(AdapterInterface $dbAdapter)
{
$this->setDbAdapter($dbAdapter);
parent::__construct('db-adapter-form');
$this->add(array(
'name' => 'db-select',
'type' => 'Zend\Form\Element\Select',
'options' => array(
'label' => 'Dynamic DbAdapter Select',
'value_options' => $this->getOptionsForSelect(),
'empty_option' => '--- please choose ---'
)
));
}
// more later...
// Also: create SETTER and GETTER for $dbAdapter!
}
And last but not least the DataProvider function:
public function getOptionsForSelect()
{
$dbAdapter = $this->getDbAdapter();
$sql = 'SELECT t0.id, t0.title FROM selectoptions t0 ORDER BY t0.title ASC';
$statement = $dbAdapter->query($sql);
$result = $statement->execute();
$selectData = array();
foreach ($result as $res) {
$selectData[$res['id']] = $res['title'];
}
return $selectData;
}
My use is like that:
Class Useful{
/**
* All languages
* #return array
*/
public static function getLanguages(){
return array(
'fr_DZ'=>'Algeria - Français',
'es_AR'=>'Argentina - Español',
'en_AU'=>'Australia - English',
'nl_BE'=>'België - Nederlands',
'fr_BE'=>'Belgique - Français',
'es_BO'=>'Bolivia - Español',
'bs_BA'=>'Bosna i Hercegovina - Hrvatski',
...
);
}
}
After I use like that:
$this->add(array(
'type' => 'Zend\Form\Element\Select',
'name' => 'languages',
'attributes'=>array(
'multiple'=>"multiple",
),
'options' => array(
'label' => 'My languages I speak',
'description' => '',
'value_options' => Useful::getLanguages()
),
));
I want to put a dropdown in my project which is made in zf2... I wasted all day but I only got a static dropdown, not dynamic. Can anyone help me with this problem??
UserForm.php
$this->add(array(
'name' => 'group_name',
'type' => 'select',
'attributes' => array(
'id'=>'group_name',
'class'=>'large',
'options' => array('1=>php','2'=>'java'),
),
'options' => array(
'label' => '',
),
));
Thanks in advance for your valuabe answer.
Try this:
$this->add(array(
'name' => 'group_name',
'type' => 'select',
'attributes' => array(
'id'=>'group_name',
'class'=>'large',
),
'options' => array(
'label' => '',
'value_options' => array(
'1' => 'php',
'2' => 'java'
),
),
));
This is what i did:
In my constructor for my form
$this->add(array(
'type' => 'Zend\Form\Element\Select',
'name' => 'color',
'options' => array(
'empty_option' => 'Select a Color',
'value_options' => self::getColors(),
'label' => 'Color',
),
));
In the form class yet, i created this method:
public static function getColors() {
// access database here
//example return
return array(
'blue' => 'Blue',
'red' => 'Red',
);
}
In my view script:
<div class="form_element">
<?php $element = $form->get('color'); ?>
<label>
<?php echo $element->getOption('label'); ?>
</label>
<?php echo $this->formSelect($element); ?>
</div>
Think about it from a abstract level.
You have one Form
The Form needs Data from the outside
So ultimately your Form has a Dependency. Since we've learned from the official docs, there's two types of Dependency-Injection aka DI. Setter-Injection and Constructor-Injection. Personally(!) i use one or the other in those cases:
Constructor-Injection if the dependency is an absolute requirement for the functionality to work
Setter-Injection if the dependencies are more or less optional to extend already working stuff
In the case of your Form, it is a required dependency (because without it there is no populated Select-Element) hence i'll be giving you an example for Constructor-Injection.
Some action of your controller:
$sl = $this->getServiceLocator();
$dbA = $sl->get('Zend\Db\Adapter\Adapter');
$form = new SomeForm($dbA);
That's all for the form. The population now happens inside your Form. This is only an example and may need some fine-tuning, but you'll get the idea:
class SomeForm extends \Zend\Form
{
public function __construct(\Zend\Db\Adapter\Adapter $dbA)
{
parent::__construct('my-form-name');
// Create all the form elements and stuff
// Get Population data
$popData = array();
$result = $dbA->query('SELECT id, title FROM Categories', $dbA::QUERY_MODE_EXECUTE)->toArray();
foreach ($result as $cat) {
$popData[$cat['id'] = $cat['title'];
}
$selectElement = $this->getElement('select-element-name');
$selectElement->setValueOptions($popData);
}
}
Important: I HAVE NO CLUE ABOUT Zend\Db the above code is only for how i think it would work going by the docs! This is the part that would need some optimization probably. But all in all you'll get the idea of how it's done.
In your controller you can do something like below;
On my first example assuming that you have a Group Table. Then we're going to fetchAll the data in group table;
We need the id and name to be display in select options;
public function indexAction()
{
$groupTable = new GroupTable();
$groupList = $groupTable->fetchAll();
$groups = array();
foreach ($groupList as $list) {
$groups[$list->getId()] = $list->getName();
}
$form = new UserForm();
$form->get('group_name')->setAttributes(array(
'options' => $groups,
));
}
OR
in this example the grouplist is hardcoded;
public function indexAction()
{
$groupList = array('1' => 'PHP', '2' => 'JAVA', '3' => 'C#');
$groups = array();
foreach ($groupList as $id => $list) {
$groups[$id] = $list;
}
$form = new UserForm();
$form->get('group_name')->setAttributes(array(
'options' => $groups,
));
}
Then in your view script;
<?php
$form = $this->form;
echo $this->formRow($form->get('group_name'));
?>
Or you can right a controller helper, you may check this link http://www.resourcemode.com/me/?p=327
Just came across the same problem and had to take a look into zf2 source.
Here's a more OOP solution:
Inside the form constructor:
$this->add(array(
'name' => 'customer',
'type' => 'Zend\Form\Element\Select',
'attributes' => array(
'options' => array(
0 => 'Kunde',
)
),
'options' => array(
'label' => 'Kunde'
)));
inside the controller:
$view->form = new SearchForm();
$customers = $view->form->get('customer')->getValueOptions();
$customers[] = 'Kunde1';
$customers[] = 'Kunde2';
$customers[] = 'Kunde3';
$customers[] = 'Kunde4';
$view->form->get('customer')->setValueOptions($customers);