ZF2 Select element usage - zend-form

I'm using Zend Framework 2 and I need a Dependent Dropdown. When user select an category (cat_id on my example) the system fills the subcategory (sca_id) with the correct elements.
I could do that by creating an application like this:
My form looks like:
$this->add(array(
'name' => 'cat_id',
'type' => 'Zend\Form\Element\Select',
'options' => array(
'label' => 'Categoria',
'value_options' => array(
'' => '',
),
),
));
$this->add(array(
'name' => 'sca_id',
'type' => 'Zend\Form\Element\Select',
'options' => array(
'label' => 'Sub Categoria',
'style' => 'display:none;', // Esse campo soh eh exibido qndo uma categoria for escolhida
'value_options' => array(
'' => '',
),
),
));
Note that I don't fill the value_options there, because I choose do that in my controller, where the Service Manager is avaliable:
$form = new ProdutoForm('frm');
$form->setAttribute('action', $this->url()->fromRoute('catalogo-admin', array( ... )));
// Alimenta as comboboxes...
$form->get('cat_id')->setValueOptions($this->getCategoriaService()->listarCategoriasSelect());
On the change event of cat_id I do an $.ajax to grab the elements from an Action and fill the sca_id.
That works fine!
The problem is on my validation:
$this->add(array(
'name' => 'cat_id',
'require' => true,
'filters' => array(
array('name' => 'Int'),
),
));
$this->add(array(
'name' => 'sca_id',
'require' => true,
'filters' => array(
array('name' => 'Int'),
),
));
When I submit my form it keeps saying : The input was not found in the haystack for both dropdowns...
What I'm doing wrong?
Extra questions : There's a better way to fill my dropdowns?
Ps.: I guess this question Disable notInArray Validator Zend Framework 2 asks something similar than me, but I wanted to detail more my problem.

Well, I realized that I should populate my select element before validate my form!
// SaveAction
$request = $this->getRequest();
if ($request->isPost())
{
$form = new ProdutoForm();
// Alimenta as comboboxes...
$form->get('cat_id')->setValueOptions($this->getCategoriaService()->listarCategoriasSelect());
$form->get('sca_id')->setValueOptions($this->getSubCategoriaService()->listarSubCategoriasSelect());
// If the form doesn't define an input filter by default, inject one.
$form->setInputFilter(new ProdutoFormFilter());
// Get the data.
$form->setData($request->getPost());
// Validate the form
if ($form->isValid())
{
// Valid!
}else{
// Invalid...
}
That code works nice. My form now validate perfectly!

Related

remove button from CGridView with condition

Hi I have CRUD generated CGridView in yii. I need to add a new button to CGridView rows and hide it if appointment_status(one of CGridView column) value equals 0
This is my code of CGridView,
$this->widget('zii.widgets.grid.CGridView', array(
'id' => 'bookings-grid',
'dataProvider' => $model->search(),
'filter' => $model,
'columns' => array(
'id',
'name',
'email',
'telephone',
'time',
'employee',
'appointment_status',
'client_ip',
'link' => array(
'header' => 'Confirmation',
'type' => 'raw',
'value' => 'CHtml::button("$data->appointment_status",array("onclick"=>"document.location.href=\'".Yii::app()->controller->createUrl("controller/action",array("id"=>$data->id))."\'"))',
'visible'=>$data->appointment_status==1,
),
array(
'class' => 'CButtonColumn',
),
),
));
But all I'm getting is error stating,
Undefined variable: data
It would be great help if someone can look into it.
you can use like this:
$this->widget('zii.widgets.grid.CGridView', array(
'id' => 'bookings-grid',
'dataProvider' => $model->search(),
'filter' => $model,
'columns' => array(
'id',
'name',
'email',
'telephone',
'time',
'employee',
'appointment_status',
'client_ip',
'link' => array(
'header' => 'Confirmation',
'type' => 'raw',
'value' => function ($data) {
if ($data->appointment_status == 1) {
return CHtml::button("$data->appointment_status", array("onclick" => "document.location.href=\'" . Yii::app()->controller->createUrl("controller/action", array("id" => $data->id)) . "\'"));
} else {
return;
}
}
),
array(
'class' => 'CButtonColumn',
),
),
));
Your 'visible' handling the column visibility and not the button, you can use custom attribute on model to create and handle the button visibility.
add to your model:
public function getConfirmationButton()
{
if ($data->appointment_status == 1) {
return CHtml::button($this->appointment_status,array("onclick"=>"document.location.href=\'".Yii::app()->controller->createUrl("controller/action",array("id"=>$this->id))."\'"));
} else {
return '';
}
}
and call it in your view:
..........
'link' => array(
'header' => 'Confirmation',
'type' => 'raw',
'value' => '$data->confirmationButton',
),
...........
visible is a boolean or a PHP expression that will be evaluated to give a boolean. During the evaluation $data is assigned to the current item from the dataProvider used. $data doesn't exist outside of the evaluation function evaluateExpression(). As such the implementation should be:
`visible` => '$data->appointment_status == 1',
You need to quote value of visible key in link array. So instead of this:
'visible'=>$data->appointment_status==1
It should be:
'visible'=>'$data->appointment_status==1'
it should work now.
You will get undefined variable because visible not allow any callback.
Try this solution, it's yii2 code and i don't know much of Yii.
'delete' => function ($url, $model) {
return ($model->isVisible($model)) ?
Html::a('<span class="glyphicon glyphicon-trash"></span>',
$url,
['title' => Yii::t('app', 'Delete')]) : '';
public static function isVisible($data)
{
return ($data->appointment_status == 1) ? true : false;
}

Nested Whgridview. Grids disappear on sort or pagination click

I'm a newbie in Yii programming.
I'm using boostrap library on Yii via Yiistrap/Yiiwheels
I've created a relation table view
The related view is a Whgridview itself
The first (master grid) has a TbRelationColum clicking it i display the second grid (detail grid).
When I click on the row to display the sub grid, everything appears ok. When I change the sort order or the page of the sub grid disappear both grid.
I understand we should differentiate the css class of the pager and the sort of sub grid from the main grid. How to do this specifically in Yii-Way?
Is This the problem?
This is the view of the main grid:
$this->widget('yiiwheels.widgets.grid.WhGridView',array(
'id'=>'masterGrid',
'dataProvider'=>$model->search(),
'filter'=>$model,
'template' => "{summary}{items}<div class=\"row-fluid\"><div class=\"pull-right\">{pager}</div></div>",
'type' => array(TbHtml::GRID_TYPE_BORDERED, TbHtml::GRID_TYPE_STRIPED),
'columns'=>array(
array(
'class' => 'yiiwheels.widgets.grid.WhRelationalColumn',
//'name' => 'multiMembers.id',
'type' => 'raw',
'header' => 'Sub Items',
'url' => $this->createUrl('multiGroup/ajaxSubItems'),
'cacheData' => false,
'value' => "CHtml::tag('button',array('class'=>'btn btn-primary'),'Sub Items')",
'htmlOptions'=>array('style'=>'width:90px;'),
'cssClass' => 'showSubItems',
),
'id',
'title',
array(
'class'=>'bootstrap.widgets.TbButtonColumn',
),
),
));
This is the sub-grid:
echo CHtml::tag('h3',array(),'Sub Items Group #"'.$id.'"');
$this->widget('yiiwheels.widgets.grid.WhGridView', array(
'id'=>'subGrid_'.$id,
'type'=>array(TbHtml::GRID_TYPE_BORDERED, TbHtml::GRID_TYPE_STRIPED),
'dataProvider' => $gridDataProvider,
'template' => "{summary}{items}<div class=\"row-fluid\"><div class=\"pull-right\">{pager}</div></div>",
'columns' => $gridColumns,
));
This is the controller:
/**
* Manages all models.
*/
public function actionAdmin()
{
$model=new MultiGroup('search');
$model->unsetAttributes(); // clear any default values
if (isset($_GET['MultiGroup'])) {
$model->attributes=$_GET['MultiGroup'];
}
$this->render('admin',array(
'model'=>$model,
));
}
public function actionAjaxSubItems()
{
$id = Yii::app()->getRequest()->getParam('id');
$model = $this->loadModel($id);
if($model->numSubItems > 0) {
$this->renderPartial('_child', array('id' => $id,
'gridDataProvider' => $this->getGridDataProvider($id),
'gridColumns' => $this->getGridColumns()
), false, true);
} else {
echo 'Non ci sono Sub Items.';
}
}
public function getGridDataProvider($id) {
$sql = 'SELECT * FROM multi_member WHERE groupid = :groupid ORDER BY lastname,firstname';
$cmd = Yii::app()->db->createCommand($sql);
$cmd->bindParam(':groupid', $id, PDO::PARAM_INT);
$result = $cmd->queryAll();
$dataProvider = new CArrayDataProvider(
$result, array(
'sort' => array(
'attributes' => array('id','groupid','firstname','lastname','membersince'),
'defaultOrder' => array('lastname' => CSort::SORT_ASC, 'firstname' => CSort::SORT_ASC),
),
'pagination' => array(
'pageSize' => 2,
),
));
return $dataProvider;
}
public function getGridColumns() {
return array('id', 'lastname', 'firstname', 'membersince');
}
How can I do?
thank you ..
If the extensions you're using all extend CGgridView, then you should be able to use option 'ajaxUpdate' (link to documentation).
Try setting 'ajaxUpdate'=>false in one of the grids (or in both of them) to see whether it helps you.
Sometimes setting ajaxUpdate to false is the only way I get get some grids to behave the way I want them to...

Attach inputFilter to dynamcially created field elements

Until now I have been binding input filters to the form in the module, in other words I have been creating elements in the form, adding input filters to the elements on the module side.
For example check this example
Right now im creating text field elements dynamically depending upon the requirements, like this in my form
//Form
public function addNamesTextFieldElement($names)
{
foreach($names as $name)
{
$nameTextField = new Element\Text($name->getName());
$nameTextField->setAttribute('type', "text");
$nameTextField->setLabel($name->getName());
$this->add($nameTextField );
}
}
What would be best approach where to add/attach input filters to such dynamically generated elements.
I probably wouldn't use this approach, but something like this would work, providing you have already assigned an InputFilter to the form:
public function addNamesTextFieldElement($names)
{
$factory = new InputFactory();
foreach($names as $name)
{
$nameTextField = new Element\Text($name->getName());
$nameTextField->setAttribute('type', "text");
$nameTextField->setLabel($name->getName());
$this->add($nameTextField );
$this->getInputFilter()->add(
$factory->createInput(array(
'name' => $name->getName(),
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
))
);
}
}

Change imageUrl with user_status field in user table

I have to model: User, UserFlag
In User/index added a column in CGridView:
array(
'class' => 'CButtonColumn',
'htmlOptions' => array("style" => 'width: 45px;'),
'template' => '{enable}',
'header' => 'management',
'buttons' => array(
'enable' => array(
'name' => 'enable',
'imageUrl' => Yii::app()->baseUrl."/images/ico/group.png",
'url' => '"#".$data->username',
'click' => 'js:function() {
if(confirm("Are you sure?")){
changeUserStatus($(this).attr("href").replace("#", ""));
}
}',
),
),
I will read user status from UserFlag Model and if status is active I show 1.png and if status is deactive I show 2.png.
Yeah, the $data var isn't accessible from imageUrl unfortunately. I would recommend extending from CButtonColumn like Stu's link suggested.
If you don't want to do that you could create two button columns and show them depending on the status. It would be something like this but you may need to adjust it if your active user_status value isn't 1 or you want the images reversed:
'enable' => array(
'name' => 'enable',
'visible'=>'$data->user_status == 1'
'imageUrl' => Yii::app()->baseUrl."/images/ico/1.png",
'url' => '"#".$data->username',
'click' => 'js:function() {
if(confirm("Are you sure?")){
changeUserStatus($(this).attr("href").replace("#", ""));
}
}',
),
'disable' => array(
'name' => 'disable',
'visible'=>'$data->user_status == 0'
'imageUrl' => Yii::app()->baseUrl."/images/ico/0.png",
'url' => '"#".$data->username',
'click' => 'js:function() {
if(confirm("Are you sure?")){
changeUserStatus($(this).attr("href").replace("#", ""));
}
}',
),
You would also need to add {disable} to your CButtonColumn template.
It's not ideal since you're repeating code, but at least you can do it without having to extend any classes.

ZF2: create url aliases in router

I'm new to Zend Framework 2 and i want to learn this framework. I want to create url aliases in router.
For example, I have defined something like this in module.config.php
'router' => array(
'routes' => array(
'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'Application\Controller\Index',
'action' => 'index',
),
),
),
'node' => array(
'type' => 'Application\Controller\AliasSegment',
'options' => array(
'route' => '/node[/:id]',
'constraints' => array(
'id' => '[0-9]+'
),
'defaults' => array(
'__NAMESPACE__' => 'Application\Controller',
'controller' => 'Index',
'action' => 'index',
'id' => '0'
),
),
'may_terminate' => true,
),
),
),
When i type www.myapp.local/node/1 it routes to the default action in default controller of my application. What i want is a router extension that can handle aliases for url paths. For example:
www.myapp.local/node/1 = www.myapp.local/aboutus
www.myapp.local/node/2 = www.myapp.local/company/gallery
I know that it was possible in ZF. Here is a link to tutorial how to achieve this in ZF:
friendly urls
I know that this is in Polish but code is self-explanatory i think :)
The idea is to use url helper to assembly valid url using aliases or normal segments (node/[:id])
I've already created AliasSegment class in my Application\Controller folder but it shows me an error:
Fatal error: Uncaught exception 'Zend\ServiceManager\Exception\ServiceNotFoundException' with message 'Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Application\Controller\AliasSegment' in C:\xampp\htdocs\industengine\vendor\zendframework\zendframework\library\Zend\ServiceManager\ServiceManager.php:450 Stack trace: #0
My AliasSegment class (incomplete):
<?php
namespace Zend\Mvc\Router\Http;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
class AliasSegment extends Segment
{
public function match(Request $request, $pathOffset = null)
{
}
}
I was looking for an answer for hours and i couldnt find anything. Please tell me at least what I'm doing wrong, where to insert a code or maybe You know better sollution?
I'm not looking for ready application. I want to learn something but i would appreciate if You can tell me an answer in details :)
Thanks in advance and sorry for my English :)
EDITED:
My custom router is working now. At this moment aliases are hardcoded but it works.
My AliasSegment class looks now:
<?php
namespace Application\Controller;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
class AliasSegment extends \Zend\Mvc\Router\Http\Segment
{
public function match(Request $request, $pathOffset = null)
{
$uri = $request->getUri();
$path = $uri->getPath();
//sample logic here
//for /about/gallery uri set node id to 1
//todo: get action, controller and module from navigation
if($path == '/about/gallery'){
$uri->setPath('/node/1');
$request->setUri($uri);
}
return parent::match($request, $pathOffset);
}
protected function buildPath(array $parts, array $mergedParams, $isOptional, $hasChild)
{
if(isset($mergedParams['link'])){
return $mergedParams['link'];
}
return parent::buildPath($parts, $mergedParams, $isOptional, $hasChild);
}
}
In this case /about/gallery is an alias to /node/1. Both adresses are correct. The buildPath function returns alias path correctly. Well, I hope this would be usefull for somebody :)
However i want to setup it in Zend_Navigation with additional parameter named 'link'.
I've done 50% of what i want to achieve however now I have problem to get Zend_Navigation from my router. I don't know how to pass it. I guess it should be something like this:
$sm = $this->getServiceLocator();
$auth = $sm->get('Navigation');
It works in my IndexController but doesnt work in my AliasSegment. I need to find in navigation array nodes with 'link' parameter.
EDIT
I've found solution. The answer is below.
unable to fetch or create an instance for Application\Controller\AliasSegment
if this is controller then I would expect in module.config.php to have:
'controllers' => array(
'invokables' => array(
'\Application\Controller\AliasSegment' => '\Application\Controller\AliasSegment',
)
),
also namespace of your class looks a bit weird:
namespace Zend\Mvc\Router\Http;
what about:
namespace Application\Controller;
OK, I've made it. The important thing for this Thread:
ZF2: How to get Zend\Navigation inside custom route?.
You can use any segment type route. But this may need a little modifications to match function.
If navigation's single page will have 'link' param, the url will be converted to 'link' string but other params will stay behind it. Just think of it as an overlay for default URI of current route.
I had to modify my custom route class a little bit. First of all, i had to change its namespace to Application\Router. Here is a full class:
// EDIT - file within ModuleName/src/Router/Alias.php
namespace Application\Router;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Mvc\Router\Http;
class Alias extends Http\Segment
{
private static $_navigation = null;
public function match(Request $request, $pathOffset = null)
{
$uri = $request->getUri();
$path = $uri->getPath();
$items = self::$_navigation->findAllBy('route', 'node');
$params = null;
if($items != null){
$t = sizeof($items);
for ($i=0; $i < $t; $i++) {
$item = $items[$i];
$params = $item->getParams();
if (isset($params['link']) && $params['link']==$path){
$uri->setPath('/'.$item->getRoute().'/'.$params['id']);
$request->setUri($uri);
break;
}
}
}
return parent::match($request, $pathOffset);
}
public function setNavigation($navigation){
self::$_navigation = $navigation;
}
protected function buildPath(array $parts,
array $mergedParams, $isOptional, $hasChild)
{
if(isset($mergedParams['link'])){
return $mergedParams['link'];
}
return parent::buildPath($parts, $mergedParams,
$isOptional, $hasChild);
}
}
here is sample part of module.config.php:
'navigation' => array(
// The DefaultNavigationFactory we configured in (1) uses 'default' as the sitemap key
'default' => array(
// And finally, here is where we define our page hierarchy
'account' => array(
'label' => 'Account',
'route' => 'node',
'params' => array(
'id' => '2',
),
'pages' => array(
'home' => array(
'label' => 'Dashboard',
'route' => 'node',
'params' => array(
'id' => '8',
'link' => '/about/gallery'
),
),
'login' => array(
'label' => 'Sign In',
'route' => 'node',
'params' => array(
'id' => '6',
'link' => '/signin'
),
),
'logout' => array(
'label' => 'Sign Out',
'route' => 'node',
'params' => array(
'id' => '3',
),
),
),
),
),
),
'router' => array(
'routes' => array(
'home' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/',
'defaults' => array(
'controller' => 'Application\Controller\Index',
'action' => 'index',
),
),
),
'node' => array(
'type' => 'Application\Router\Alias',
'options' => array(
'route' => '/node[/:id]',
'constraints' => array(
'id' => '[0-9]+'
),
'defaults' => array(
'__NAMESPACE__' => 'Application\Controller',
'controller' => 'Index',
'action' => 'index',
'id' => '0'
),
),
'may_terminate' => true,
),
),
),
If it is just for routes like /about and /about/galery then you can simply use literal routes with child routes
'about' => array(
'type' => 'literal',
'options' => array(
'route' => '/about',
'defaults' => array(
'controller' => 'module-controller-about',
'action' => 'index'
)
),
'may_terminate' => true,
'child_routes' => array(
'galery' => array(
'type' => 'literal',
'options' => array(
'route' => '/galery',
'defaults' => array(
'controller' => 'module-controller-galery'
)
)
)
)
)
When it comes to URLs like /blog/1-my-great-seo-title you probably have to set-up a Regex route (which is the slowest, literals are fastest).
Maybe check out DASPRiDs Slides from his Router Presentation