I am sharing this to show how I used XTabularInput in another Widget, but also to show how a Widget can provide its own actions.
XTabularInput allows you to let the user add more lines to a list, essentially for form entry. A demo is here:
http://www.eha.ee/labs/yiiplay/index.php/en/site/extension?view=tabular
XTabularInput is an extension that can not be found amongst the extensions on yiiframework.com, but is available on github:
https://github.com/erikuus/Yii-Playground/tree/master/protected/extensions/widgets/tabularinput
The extension is practical, but only works properly when used from a controller and not when used from a widget.
For normal rendering, the extension provides a widget, and to render the new row in the table through ajax, it provides an action.
How the new row is to be rendered has to be defined in view.
Rendering the view is where the issue is.
Any action is called from the controller. The straightforward way to render a view is to render it with something like
$this->getController()->renderPartial($view,...)
. However, the ‘$view’ is searched for relative to the Controller’s path.
The way I am using XTabularInput is inside another Widget providing more functionnality in which XTabularInput is just one part. That widget has a view where XTabularInput is used very much like this:
[/code]
The '_xxx' view is in the view directory of my widget (or in the appropriate view directory of the current theme). At first, XTabularInput called - just like the action - $this->getControler()->renderPartial($view,...). Aargh: the view is in my Widget, not in the controller. So I proposed a first modification to the Widget. The modification checks if the owner is a controller or a widget and calls the appropriate method to look for the view in the right location.
[url="https://github.com/erikuus/Yii-Playground/pull/4"]https://github.com/erikuus/Yii-Playground/pull/4[/url]
I thought that I was home-free. But I still ran into an issue when calling the action. What I eventually did is make the CWidget appear as a CAction (implementing IAction). That way the controller can call my Widget first, which becomes an instance and then my Widget can call the XTabularInput widget as usual. This trick actually makes the XTabularInput see my widget as its parent and it can therefore call its rendering method to find the view in the expected context. Furthermore, Yii is doing most of the work.
I also had to make an extra modfication of XTabularInput.
Here are the codes (extrations thereof):
Code from my widget. The widget implements IAction as said and declares itself in the 'actions()'. runWithParams will then call the run method of the Widget in which XTabularInput is rendered.
[code][size=2]Yii::import('ext.tabularinput.XTabularInput');[/size]
Yii::import('application.components.WeeklyWidget.*');
/**
* Provides widget to define and show weekly planning.
*/
class WeeklyWidget extends CWidget implements IAction {
/*
* Provide action interface for this widget in order
* to maintain a consistency in the view path
* for XTabularInput.
*/
/**
* Constructor when seen as action
*
* @param unknown_type $controller
* @param unknown_type $id
*/
public function __construct($controller,$id) {
parent::__construct($controller);
$this->setId($id);
}
public function runWithParams($params) {
if(!isset($this->model)) {
$this->model=array(new WeeklyModel());
}
$this->render($this->view,array());
}
/**
* Define the actions that this widget provides.
*/
public static function actions() {
return array(
'getinput'=>'WeeklyWidget',
);
}
/**
* (non-PHPdoc)
* @see CWidget::run()
*/
public function run() {
$this->render($this->view);
}
}
The widget’s view - renders XTabularInput.
$this->widget('ext.tabularinput.XTabularInput',
array(
'models'=>$this->model,
'actionPrefix'=>$this->actionPrefix,
'containerTagName'=>'table',
//'headerTagName'=>'thead',
//'header'=>'',
'inputContainerTagName'=>'tbody',
'inputTagName'=>'tr',
'inputView'=>'_period',
'inputUrl'=>Yii::app()->controller->createUrl('request/addTabularInputs'),
'removeTemplate'=>'<td>{link}</td>',
'removeLabel'=>Yii::t('app','lb.weeklydelete'),
'removeHtmlOptions'=>array('class'=>'weekly-delete'),
//'removeUrl'=>Yii::app()->controller->createUrl('request/removeTabularInputs'),
'addTemplate'=>'<tbody><tr><td colspan="6"></td><td>{link}</td></tr></tbody>',
'addHtmlOptions'=>array('class'=>'weekly-add'),
[size=2] 'addLabel'=>Yii::t('app','Add new weekly row'),[/size]
[size=2] )[/size]
[size=2] );[/size]
The ‘_period’ view referenced by the widget (just generating fields; not using the model yet). When called through AJAX, it will render only one line with an empty model.
<?php
$cells=array();
$cells[]=Yii::t('app','lb.weekly.period');
$weekDayNames=Yii::app()->locale->getWeekDayNames();
$cells[]=CHtml::dropDownList('[weekday_from]',0,$weekDayNames,array('prompt'=>Yii::t('app','Day')));
$cells[]=YHtml5::timeField('[time_from]',null,array());
$cells[]=Yii::t('app','lb.todate');
$cells[]=CHtml::dropDownList('[weekday_to]',0,$weekDayNames,array('prompt'=>Yii::t('app','Day')));
$cells[]=YHtml5::timeField('[time_to]',null,array());
print '<td>'.implode('</td><td>',$cells)."</td>";
Update run() method of XTabularInput (which thereby includes some code from the XTabularInputAction)
public function run($action=null)
{
if((Yii::app()->request->isAjaxRequest && isset($_GET['index'])))
{
$index=$_GET['index'];
$owner=$this->getOwner();
foreach($this->models as $index=>$model)
{
$params=array(
'model'=>$this->models,
'index'=>$index++,
);
if($owner instanceof CWidget) {
$owner->render($this->inputView, $params);
} else {
$owner->renderPartial($this->inputView, $params);
}
}
} else {
$this->registerClientScript();
echo CHtml::openTag($this->containerTagName, $this->containerHtmlOptions);
if($this->header)
echo CHtml::tag($this->headerTagName, $this->headerHtmlOptions, $this->header);
echo CHtml::openTag($this->inputContainerTagName, $this->inputContainerHtmlOptions);
$this->renderContent();
echo CHtml::closeTag($this->inputContainerTagName);
echo $this->getAddLink();
echo CHtml::closeTag($this->containerTagName);
}
}
This is what I defined in the controller:
public function accessRules()
{
return array(
array(
'allow',
'actions'=>array(
'weekly.getinput'),
'users'=>array('@')
),
);
public function actions() {
return array(
[size=2] "weekly."=>"application.components.WeeklyWidget.WeeklyWidget",[/size]
);
}