Usage Of Cactiveform In A Layout!

I have only recently begun working with Yii and so far I have managed to pick it up nicely, but now I am running into ‘problem’. Acually more a question on how to do things properly :)

Yii of course works with layout files where you can define the header, logo, footer, etc etc. Anything that you want to be visible troughout your application.

In my case, I want to add a single field CJuiAutoComplete widget within a form into the main.php layout file. This field is used to quicksearch for records in a persons database, I got this working and now rises the problem of doing something with the actually selected item. I do this as follows:




<div id="header_search-area">

<?php $form=$this->beginWidget('CActiveForm', array(

        'id'=>'searchperson',

        'action'=>'../persons/search',

        'clientOptions'=>array(

                'validateOnSubmit'=>true,

        ),

    ));

    $this->widget('zii.widgets.jui.CJuiAutoComplete',array(

        'name'=>'search_person',

        'value'=> $model->Persons,

        'source'=>$this->createUrl('persons/searchperson'),

        // additional javascript options for the autocomplete plugin

        'options'=>array(

            'minLength'=>'3',

            'select'=>'js:function(event, ui) { console.log(ui.item.id +":"+ui.item.value); $("#searchperson").submit();}',

        ),

        'htmlOptions'=>array(

            'style'=>'height:20px; width: 250px;',

            'id'=>'search_person',

            'rel'=>'val',

        ),

    ));

    echo $form->hiddenField($model,'person_id');

    $this->endWidget();

    ?>

</div>



So I have created a Form ‘searchperson’ through the use of Gii and added the ‘search_person’ inputfield to it which has the autocomplete action attached to it. This function is currently setup so that on ‘select’ it will display the item that was selected and then it will submit the form, causing the user to be forwarded to the persons/search page.

However, because this is a single field form (for the eye) with just somebodies name in it, I need to create a hidden field where i can insert the persons ID upon select, so that this will end up in the POST data. Now, the proper way to do this of course would be the way I have currently added near the end of the form widget, this:


echo $form->hiddenField($model,'person_id');

However, because this form is not displayed in the ‘persons/search’ view, but rather in the main layout file my application has no idea what ‘$model’ is.

What is the best course of action to do now? The easiest way out would be to simply replace the CActiveForm’s hiddenField method with a manual html input field like this:


<input type="hidden" name="person_id" id="person_id" value="" />

In the CJuiAutoComplete widget i could then target this field, add the person_id returned from the search in it and then submit the form. Et voila, case closed.

But somehow I have the feeling this is not the best way to go about it. So what do I have to do here and how is the best way to handle this situation?

instead of $form->hiddenField just use:




echo CHtml::hiddenField('person_id', '', array( 'id'=>'person_id' ) );



you can also use CForm widget instead of CActiveForm (‘Active’ in its name means it is mainly used with CActiveRecord).

would it be an option if you create a portlet against person model, then you could use anywhere in your view?

@redguy: Tnx, I suppose I could switch to CForm. Gii form generator seems to create CActiveForm setups by default, but there is really not much to validate here anyway.

@rootbear: I am not sure what you mean, I do not know yet what a portlet is. Also, I do not wish to use this form within a single view, but in the layout file in which the different views are rendered.

Also, could anybody explain to me what the benefit is of using the CHtml (or CForm) helper:


echo CHtml::hiddenField('person_id', '', array( 'id'=>'person_id' ) );

VS just typing the field element:


<input type="hidden" name="person_id" id="person_id" value="" />

Dear Beittil

What our friend rootbear suggested is the right approach I feel.

Following is one implementation exacting your needs.

components/PersonAutoComplete.php




<?php


Yii::import('zii.widgets.CPortlet');

    

class PersonAutoComplete extends CPortlet

{

    public $title='Choose a Person';

    

    protected function renderContent()

    {   

		$model=new Person;

		$data=$this->getPersonList();

		$this->render('autoComplete',array('model'=>$model,'data'=>$data));

    }

    public function getPersonList() 

    {  

		$persons=Person::model()->findAll();

		return CHtml::listData($persons,'id','name');

    }

    

}

?>



components/views/autoComplete.php




<div class="form">


<?php $form=$this->beginWidget('CActiveForm', array(

	'id'=>'person-form',

	'action'=>array('test/fourteen'),//I am collection the form values in this "controller/action"

	'enableAjaxValidation'=>false,

)); ?>

	<div class="row">

	<?php echo $form->labelEx($model,'name'); ?>

	<?php	$this->widget('zii.widgets.jui.CJuiAutoComplete',array(

        'model'=>$model,

        'attribute'=> 'name',

        'source'=>array_values($data),

        'options'=>array(

            'minLength'=>'2',

            "change"=> 'js:function( event, ui ) 

                      {

				var obj='.json_encode(array_flip($data)).';

				var name=$(this).val();

				$("#hiddenField").val(obj[name]);

		     }'


        ),

       

    ));?>

        <?php echo $form->error($model,'name'); ?>

	<?php echo $form->hiddenField($model,'id',array('id'=>"hiddenField")); ?>		

	</div>

	<div class="row buttons">

		<?php echo CHtml::submitButton('Submit'); ?>

	</div>


<?php $this->endWidget(); ?>

</div><!-- form -->



TestContoller.php




public function actionFourteen()

{  

      print_r($_POST["Person"]);

      //Do something with $_POST["Person"] which include the id and name of the Person.

}



Just insert the following line in the main layout wherever you want.




<?php $this->widget('PersonAutoComplete'); ?> //this way main layout looks cleaner ever.



I hope I helped a bit.

Regards.

@sinivasan, I have been toying with your example a bit. But when i appyl my code to it the autocomplete funtion does not work anymore.

In your portlet view you use a bit of code that simply fetches all the records from the persons table and use that to feed the CJuiAutoComplete field. Is this really what you mean to happen? because the field is supposed to search in the Persons model as the user is typing and displaying/narrowing down the results as the user goes on.

Your code:




<?php


Yii::import('zii.widgets.CPortlet');

    

class PersonAutoComplete extends CPortlet

{

    public $title='Choose a Person';

    

    protected function renderContent()

    {   

                $model=new Person;

                $data=$this->getPersonList();

                $this->render('autoComplete',array('model'=>$model,'data'=>$data));

    }

    public function getPersonList() 

    {  

                $persons=Person::model()->findAll();

                return CHtml::listData($persons,'id','name');

    }

    

}

?>



When I try to move this functionality into a portlet it simply stops working. Because my searchfunction relays on user input, whereas yours simply seems to pre-fetch all data. Is this correctly assumed?

How can I correct this?

Dear Friend

This is an autocomplete.

I am prefetching all the data to view as $data.

It is not going to display all the persons name.

It is going display dynamically the relevant names as user types.

I tested the code in my localhost prior to posting.

The idea is just keep the mainlayout simple.

Regards.

I understand that it will not display all the fetched data, but doesn’t it kind of defeat the purpose of an autocomplete to fetch all the data first and then narrow it down as the user types?

In some cases were talking about 20.000+ records with persons in it, which adds up to a LOT of content for $data.

The method I have now, where the entire form is in the main layout, also uses autocomplete. But in this case it executes a new MySQL search with each letter typed by the user, so that the fetch data return is ‘pre-narrowed down’.

I tried to adapt the portlet so that the autocomplete would use my own search function as the user types, but it just will not display a list of results :(

Dear Friend

My response in a way that we can make use of portlets.

Regarding using a source for autocomplete, my conviction is that we can effectively make use of even larger php arrays rather than sending multiple ajax requests.

Regards.