Xtabularinput

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

https://github.com/erikuus/Yii-Playground/blob/master/protected/extensions/actions/XTabularInputAction.php

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]

        );

    }




Hi!, i need some help for implement this extension that i’ve implemented in my project in default way but the remove’s option dosen’t work. I discovered the error and his happend into the handler’s click event rutine on the javascript. I’ve tested change the code and 've tried many alternative and still not working

Carlos

You need to provide more information to be able to help efficiently.

The code in the extension responsible for deleting an item is:


	$("#{$this->id}").on("click",".{$this->removeCssClass}",function(event) {

		event.preventDefault();

		$(this).parents(".{$this->inputCssClass}:first").remove();

		$('.{$this->inputContainerCssClass}').filter(function(){return $.trim($(this).text())===''}).siblings('.{$this->headerCssClass}').hide();

	});

The code finds the parent element that the delete button belongs to and removes that parent element.

This is using jQuery and should be "easy" to debug.

What error do you get?

le_top,

the extension gerenates this html code by default




<div id="yw0" class="tabular-container">

    <div class="tabular-input-container">

        <div class="tabular-input">

            <div class="simple">

            <div class="action">

                <a class="tabular-input-remove" href="#">Remove</a>

                <input class="tabular-input-index" type="hidden" value="0">

            </div>

        </div>

    </div>

<div class="action">

</div>



and generates finally this jquery script to handle click event on remove link




$("#yw0 .tabular-input-remove").click(function(event){

		event.preventDefault();

		$(this).parents(".tabular-input:first").remove();

		$('.tabular-input-container').filter(function(){return $.trim($(this).text())==='' && $(this).children().length == 0}).siblings('.tabular-header').hide();

	});



but it dosen’t work, seems the name of the class of link element were another. I’ve tested many alternatives such as changing the name of the class in php code or in html code and i made my own jquery rutine to handler click event

I suggest to temporarily change the extension and add ‘debugger;’ just after ‘[color=#000088][size=2]event[/size][/color][color=#666600][size=2].[/size][/color][size=2]preventDefault[/size][color=#666600]size=2;[/size][/color][size=2]’.[/size]

[size=2]In the navigator, make sure to open the debugger window and then click the remove button.[/size]

[size=2]

[/size]

[size=2]The debugger should stop right inside the code.[/size]

[size=2]

[/size]

[size=2]Verify interactively that ‘[/size][size=2]$[/size][color=#666600]size=2.[/size][/color][size=2]parents[/size][color=#666600]size=2[/size][/color][size=2]’ exists in that context, etc. Verify what ‘this’ corresponds to.[/size]

[size=2]

[/size]

Maybe you have multiple items on your page that are called ‘yw0’ - give your widget another name in that case.

[size=2]

[/size]

[size=2]

[/size]

I have some good news, i resolved part of the problema. The models array added in parametre of the widget was empty,then the page is loaded, the click event handler was not registered properly because was not the element with the class name "tabular-input-remove". I solve this part by initializing the array.

Now, the error continues when i add a new element, the click event handler doesn’t work in that situation.

This is my code in the controller




public function actionAgregar(){

            

            if(Yii::app()->request->isAjaxRequest && isset($_GET['index'])){

                

                $model=new Convenios;

                

                $this->renderPartial('_prueba', array(

                            'model'=>$model,

                            'index'=>$_GET['index']

                    ));

            }

            else

                throw new CHttpException(400,'Solicitud invalida, cheque el controlador');

        }




and this code is "_prueba"




<div class="simple">

	<?php echo CHtml::activeLabelEx($model,"year"); ?>

	<?php echo CHtml::activeTextField($model,"[$index]year"); ?>

	<?php echo CHtml::error($model,"[$index]year"); ?>

</div>



PD: I’m using debugger of navigator to test

Ok, your code says “.click”, while the code on github says “.live” and my code above says “.on” which is the better alternative for ‘.live’ (which is deprecated).

‘live’ or ‘on’ are needed to apply the event to new elements too.

I’ve changed the code ‘click( function(event) {’ to ‘on(“click”, function(event) {’ but still not working, when i add a new element and try click remove

Hi, I had some vacation ;-).

Add ‘debugger;’ to the code like this: ‘[color=#1C2837][size=2]function(event) {debugger;’ and enable debug to see if you get some reaction on the click.[/size][/color]

[color=#1C2837][size=2]

[/size][/color]

[color=#1C2837][size=2]What is your resulting code ([/size][/color][size=2]$[/size][color=#666600][size=2]([/size][/color][color=#008800][size=2]"#yw0 .tabular-input-remove"[/size][/color][color=#666600][size=2]).on(‘click’) ) ? If you still get ‘#yw0’, make sure that you add an ‘id’ for the widget that generated ‘#yw0’ in order to get a stable reference.[/size][/color]