autocomplete widget for yii-user

You will find below an autocomplete widget to be added to yii-user (/components).

Everything works fine but as I couldn’t figure out the “relation” parameter, this part may need correction.

Enjoy!

<?php

class UWjuiAutoComplete {

/***--------------------------------------------------------------------------------------------------------------------------------------------***/

public &#036;params = array(


	'ui-theme'=&gt;'base',


	'modelName'=&gt;'',


	'optionName'=&gt;'',


	'emptyField'=&gt;array(


		'label'=&gt;'Not found',


		'id'=&gt;'0'),


	'relationName'=&gt;'',


	'minLength'=&gt;'',


);

/***--------------------------------------------------------------------------------------------------------------------------------------------***/

/*** Widget initialization @return array ***/

public function init() {


	return array(


		'name'=&gt;__CLASS__,


		'label'=&gt;Yii::t('default','jQueryUI autocomplete',array(),__CLASS__),


		'fieldType'=&gt;array('INTEGER'),


		'params'=&gt;&#036;this-&gt;params,


		'paramsLabels' =&gt; array(


			'modelName'=&gt;Yii::t('default','Model Name',array(),__CLASS__),


			'optionName'=&gt;Yii::t('default','Lable field name',array(),__CLASS__),


			'emptyField'=&gt;Yii::t('default','Empty item name',array(),__CLASS__),


			'relationName'=&gt;Yii::t('default','Profile model relation name',array(),__CLASS__),


			'minLength'=&gt;Yii::t('default','minimal start research length',array(),__CLASS__),


		),


	);


}

/***--------------------------------------------------------------------------------------------------------------------------------------------***/

/*** @param $value, @param $model, @param $field_varname, @return string ***/

public function setAttributes(&#036;value,&#036;model,&#036;field_varname) {


	return &#036;value;


}

/***--------------------------------------------------------------------------------------------------------------------------------------------***/

/*** @param $model - profile model, @param $field - profile fields model item, @return string ***/

public function viewAttribute(&#036;model,&#036;field) {


	&#036;relation = &#036;model-&gt;relations();


	if (&#036;this-&gt;params['relationName']&amp;&amp;isset(&#036;relation[&#036;this-&gt;params['relationName']])) {


		&#036;m = &#036;model-&gt;__get(&#036;this-&gt;params['relationName']);


	} else {


		&#036;m = CActiveRecord::model(&#036;this-&gt;params['modelName'])-&gt;findByPk(&#036;model-&gt;getAttribute(&#036;field-&gt;varname));


	}


	


	if (&#036;m)


		return ((&#036;this-&gt;params['optionName'])?&#036;m-&gt;getAttribute(&#036;this-&gt;params['optionName']):&#036;m-&gt;id);


	else


		return &#036;this-&gt;params['emptyField']['label'];	


}

/***--------------------------------------------------------------------------------------------------------------------------------------------***/

/*** @param $model - profile model, @param $field - profile fields model item, @param $params - htmlOptions, @return string ***/

public function editAttribute(&#036;model,&#036;field,&#036;htmlOptions=array()) {


	&#036;list = array();


	if (&#036;this-&gt;params['emptyField']) &#036;list[]=&#036;this-&gt;params['emptyField'];	


	&#036;models = CActiveRecord::model(&#036;this-&gt;params['modelName'])-&gt;findAll();


	foreach (&#036;models as &#036;m)


		&#036;list[] = ((&#036;this-&gt;params['optionName'])?array('label'=&gt;&#036;m-&gt;getAttribute(&#036;this-&gt;params['optionName']),'id'=&gt;&#036;m-&gt;id):array('label'=&gt;&#036;m-&gt;id,'id'=&gt;&#036;m-&gt;id));


	if (&#33;isset(&#036;htmlOptions['id'])) &#036;htmlOptions['id'] = &#036;field-&gt;varname;


	&#036;id = &#036;htmlOptions['id'];


	/*****/


	&#036;relation = &#036;model-&gt;relations();


	if (&#036;this-&gt;params['relationName']&amp;&amp;isset(&#036;relation[&#036;this-&gt;params['relationName']])) {


		&#036;m = &#036;model-&gt;__get(&#036;this-&gt;params['relationName']);


	} else {


		&#036;m = CActiveRecord::model(&#036;this-&gt;params['modelName'])-&gt;findByPk(&#036;model-&gt;getAttribute(&#036;field-&gt;varname));


	}


	


	if (&#036;m)


		&#036;default_value = ((&#036;this-&gt;params['optionName'])?&#036;m-&gt;getAttribute(&#036;this-&gt;params['optionName']):&#036;m-&gt;id);


	else


		&#036;default_value = '';	


	/*****/


	&#036;htmlOptions['value'] = &#036;default_value;


	&#036;options['source'] = &#036;list;

// $options[‘source’] = array(‘aaac1’, ‘aaac2’, ‘aaac3’);

	&#036;options['minLength'] = &#036;this-&gt;params['minLength'];


	&#036;options['showAnim'] = 'fold';


	&#036;options['select'] = &quot;js:function(event, ui) { &#036;('#&quot;.get_class(&#036;model).&quot;_&quot;.&#036;field-&gt;varname.&quot;').val(ui.item.id);}&quot;;


	&#036;options=CJavaScript::encode(&#036;options);


	&#036;basePath=Yii::getPathOfAlias('application.views.asset');


	&#036;baseUrl=Yii::app()-&gt;getAssetManager()-&gt;publish(&#036;basePath);


	&#036;cs = Yii::app()-&gt;getClientScript();


	&#036;cs-&gt;registerCssFile(&#036;baseUrl.'/css/'.&#036;this-&gt;params['ui-theme'].'/jquery-ui.css');


	&#036;cs-&gt;registerScriptFile(&#036;baseUrl.'/js/jquery-ui.min.js');


	&#036;js = &quot;jQuery('#{&#036;id}').autocomplete({&#036;options});&quot;;


	&#036;cs-&gt;registerScript('Autocomplete'.'#'.&#036;id, &#036;js);


	return CHtml::activeTextField(&#036;model,&#036;field-&gt;varname,&#036;htmlOptions).CHtml::activehiddenField(&#036;model,&#036;field-&gt;varname);


}

}

Bonjour Hermallorn! :)

Thanks for this widget! I just discovered Yii-user and was just sarting to wonder if it was possible to integrate CjuiAutoComplete to it…

I just tried it out, unfortunately, I get an CException :

The asset "C:\websites\myapp\protected\views\asset" to be published does not exist.

Any idea where that might come from?

Also, I was wondering if you might know how I could get one autocomplete to depend on the previous one in yii-user profile fields (Country -> State)

Thanks a lot!

Cheers

Never mind my first question, I got it working :)

it was simply a matter of replacing


$basePath=Yii::getPathOfAlias('application.views.asset');

by


$basePath = Yii::getPathOfAlias('application.modules.user.views.asset');

Ok, I’ve almost cracked it :)

I’ve created a new widget based on UWjuiAutoComplete :


<?php

/**

 * Description of UWjuiAutoCompleteDependent */

class UWjuiAutoCompleteDependent {


    public $params = array(

        'ui-theme' => 'base',

        'modelName' => '',

        'optionName' => '',

        'emptyField' => array(

            'label' => 'Not found',

            'id' => '0'),

        'relationName' => '',

        'parentName' => '',

        'parentFieldName' => '',

        'minLength' => '',

    );

    /*     * *--------------------------------------------------------------------------------------------------------------------------------------------** */

    /*     * * Widget initialization @return array ** */


    public function init() {

        return array(

            'name' => __CLASS__,

            'label' => Yii::t('default', 'jQueryUI autocomplete dependent', array(), __CLASS__),

            'fieldType' => array('INTEGER'),

            'params' => $this->params,

            'paramsLabels' => array(

                'modelName' => Yii::t('default', 'Model Name', array(), __CLASS__),

                'optionName' => Yii::t('default', 'Label field name', array(), __CLASS__),

                'emptyField' => Yii::t('default', 'Empty item name', array(), __CLASS__),

                'relationName' => Yii::t('default', 'Profile model relation name', array(), __CLASS__),

                'parentName' => Yii::t('default', 'Parent name', array(), __CLASS__),

                'parentFieldName' => Yii::t('default', 'Parent field name', array(), __CLASS__),

                'minLength' => Yii::t('default', 'minimal start research length', array(), __CLASS__),

            ),

        );

    }


    /*     * *--------------------------------------------------------------------------------------------------------------------------------------------** */

    /*     * * @param $value, @param $model, @param $field_varname, @return string ** */


    public function setAttributes($value, $model, $field_varname) {

        return $value;

    }


    /*     * *--------------------------------------------------------------------------------------------------------------------------------------------** */

    /*     * * @param $model - profile model, @param $field - profile fields model item, @return string ** */


    public function viewAttribute($model, $field) {

        $relation = $model->relations();

        if ($this->params['relationName'] && isset($relation[$this->params['relationName']])) {

            $m = $model->__get($this->params['relationName']);

        } else {

            $m = CActiveRecord::model($this->params['modelName'])->findByPk($model->getAttribute($field->varname));

        }


        if ($m)

            return (($this->params['optionName']) ? $m->getAttribute($this->params['optionName']) : $m->id);

        else

            return $this->params['emptyField']['label'];

    }


    /*     * *--------------------------------------------------------------------------------------------------------------------------------------------** */

    /*     * * @param $model - profile model, @param $field - profile fields model item, @param $params - htmlOptions, @return string ** */


    public function editAttribute($model, $field, $htmlOptions=array()) {

        $list = array();

        if (!isset($htmlOptions['id']))

            $htmlOptions['id'] = $field->varname;

        $id = $htmlOptions['id'];

        /*         * ** */

        $relation = $model->relations();

        if ($this->params['relationName'] && isset($relation[$this->params['relationName']])) {

            $m = $model->__get($this->params['relationName']);

        } else {

            $m = CActiveRecord::model($this->params['modelName'])->findByPk($model->getAttribute($field->varname));

        }


        if ($m)

            $default_value = (($this->params['optionName']) ? $m->getAttribute($this->params['optionName']) : $m->id);

        else

            $default_value = '';

        $htmlOptions['value'] = $default_value;

        $options['source'] = 'js:function( request, response ) {

                                    $.ajax({

                                        url: "' . Yii::app()->createUrl('user/profilefield/autocomplete') . '",

                                        dataType: "json",

                                        data: {

                                            term: request.term,

                                            parentId:$("#Profile_' . $this->params['parentFieldName'] . '").val(),

                                            parentFieldName:"' . $this->params['parentFieldName'] . '",

                                            modelName:"' . $this->params['modelName'] . '"

                                        },

                                        success: function(data) {

                                            response($.map(data, function(item) {

                                                return {

                                                    label: item.label,

                                                    value: item.label,

                                                    id: item.value

                                                }

                                            }))

                                        }

                                    });

                                }';

        $options['minLength'] = $this->params['minLength'];

        $options['showAnim'] = 'fold';

        $options['select'] = "js:function(event, ui) {

            $('#" . get_class($model) . "_" . $field->varname . "').val(ui.item.id);}";

        $options = CJavaScript::encode($options);

        $basePath = Yii::getPathOfAlias('application.modules.user.views.asset');

        $baseUrl = Yii::app()->getAssetManager()->publish($basePath);

        $cs = Yii::app()->getClientScript();

        $cs->registerCssFile($baseUrl . '/css/' . $this->params['ui-theme'] . '/jquery-ui.css');

        $cs->registerScriptFile($baseUrl . '/js/jquery-ui.min.js');

        $js = "jQuery('#{$id}').autocomplete({$options});";

        $cs->registerScript('Autocomplete' . '#' . $id, $js);

        return CHtml::activeTextField($model, $field->varname, $htmlOptions) . CHtml::activehiddenField($model, $field->varname);

    }




}




and added an action to the ProfileFieldsController :


    


...

        array('allow', 

            'actions'=>array('autocomplete'),

            'users'=>array('@'),

        ),

...

    public function actionAutocomplete() {

        $results = Array();

        $model = $_GET['modelName']::model();

        $parentId = (int) $_GET['parentId'];

        $db = Yii::app()->db;

        if (!isset($model->tableSchema->columns->{$_GET['parentFieldName']})) { throw new CHttpException(500,"Invalid Request"); }

        $parentFieldName = $db->quoteColumnName($_GET['parentFieldName']);


        if (isset($_GET['term'])) {

            $search = $_GET['term'];

            $criteria = new CDbCriteria();

            $criteria->condition = "$parentFieldName = :parentId AND name like :search";

            $criteria->order = 'name';

            $criteria->params = array(':parentId'=>$parentId, ':search'=>"%$search%");

            foreach ($model->findAll($criteria) as $child) {

                $results[] = array(

                    'label' => $child->name, // value for input field

                    'value' => $child->id, // return value from autocomplete

                );

            }

            echo CJSON::encode($results);

            Yii::app()->end();

        }

    }



Only problems left are :

  • the “select” option doesn’t seem to work so well :

the label isn’t displayed in the input, only the id is, which is why I added the “change” option, but that only works when the input loses focus.

  • there’s a need to add a new action to the controller. Can anyone see a way to have the widget work on it’s own?

Cheers

I made the necessary corrections to the above code to solve the problem about the id being displayed instead of the label. :)

Also, the action is now safer from SQL injections.

A new issue I’ve found is that if the user doesn’t select anything, but only types in what he wished to enter, this won’t work. I’ll look into it asap but if anyone has an idea, I’m all for it :)