Discussion and Questions on EJuiAutoCompleteFkField

Hi everyone. Thanks for this useful extension.

I’m trying to use multiple autocomplete fields on a single page. Is there a way to give it a uinque DOM ID so they can all function separately?

Currently only the first one works cause it’s not unique.




<?php 

 $this->widget('EJuiAutoCompleteFkField', array(

  'model'=>$smpNull, 

  'attribute'=>'cancer_id', //the FK field (from CJuiInputWidget)

  'sourceUrl'=>Yii::app()->createUrl('/samples/cancerAutoComplete'), 

  'showFKField'=>false,

  'FKFieldSize'=>10, 

  'relName'=>'cancer', // the relation name defined above

  'displayAttr'=>'CancerName',  // attribute or pseudo-attribute to display

  'autoCompleteLength'=>50,

  'options'=>array(

    'minLength'=>3,

   ),

  'htmlOptions'=>array(

   'id'=>'Events_cancer_id',

  ),

 ));

?>

I was hoping for something like ‘htmloptions’ above to set the ID.

Thanks !

I modified it to enable multiple autocomplete fields in a single page. This requires unique DOM ids.

The updated extension:




<?php

/* 

 * EJuiAutoCompleteFkField class file

 *

 * @author Jeremy Dunn <jeremy.j.dunn@gmail.com>

 * @link http://www.yiiframework.com/

 * @version 1.5

 */


/*

 * The EJuiAutoCompleteFKField extension renders a CJuiAutoComplete field plus supporting form fields for a FK field.

 * Typically it is used for a model with a foreign key field to a child table that has too many records

 * for a drop-down list to be practical.

 *

 * For example it could be used in a Contact table, with a foreign key PostCodeId to a PostCode table

 * with thousands of records, one for each city / postcode combination.  The user would type the city name in the AutoCompleter,

 * and the PostCodeId would be stored in the correct PostCodeId column; while the display attribute (e.g. City, Province) is shown

 * in the form.

 *

 * The extension renders the following form objects:

 * 1) the model field itself, which may optionally be hidden or visible

 * 2) a hidden field that holds the description field of the FK record, for redisplay if the user

 *    fails to choose a value from the autoCompleter

 * 3) the AutoComplete field itself, which also displays the existing value from the related record

 * 4) a 'delete' icon to clear fields 1-3 above

 * 5) javascript to tie everything together

 *

 * Typical usage:

 * 1) unzip the extension into ../extensions/

 *

 * 2) make sure config/main.php has:

 *    <pre>

 *      ...

 *      import=>array(

 *          ...

 *          'application.extensions.*',

 *          ...

 *      ),

 *    </pre>

 *

 * 3) ensure the relationship exists in the model:

 * in Contacts.php (example):

 * <pre>

 * ...

 * 'relations'=>array(

 *      'Postcode'=>array('self::BELONGS_TO, 'PostCodes', 'PostCodeId'),

 * ...

 * </pre>

 *

 * 4) in the related table, optionally create a pseudo-attribute for display purposes

 * in PostCodes.php (model) for example:

 * <pre>

 * ...

 * public function getPostCodeAndProvince() {

 *      return $this->PostCodeId . ', ' . $this->Province;

 * }

 * </pre>

 *

 * 5) in the _form.php for the main record (e.g. Contacts)

 * <pre>

 * ...

 * echo $form->labelEx($model, 'PostCodeId);

 * $this->widget('EJuiAutoCompleteFkField', array(

 *      'model'=>$model, //  e.g. the Contact model (from CJuiInputWidget)

 *      // attribute must have double-quotes if using form "[$i]FieldName" for child-rows

 *      'attribute'=>"PostCodeId",  // the FK field (from CJuiInputWidget)

 *      'sourceUrl'=>'findPostCode', // name of the controller method to return the autoComplete data (see below)  (from CJuiAutoComplete)

 *      'showFKField'=>true, // defaults to false.  set 'true' to display the FK value in the form with 'readonly' attribute.

 *      'FKFieldSize=>15, // display size of the FK field.  only matters if not hidden.  defaults to 10

 *      'relName'=>'Postcode', // the relation name defined above

 *      'displayAttr'=>'PostCodeAndProvince',  // attribute or pseudo-attribute to display

 *      'autoCompleteLength'=>60, // length of the AutoComplete/display field, defaults to 50

 *      // any attributes of CJuiAutoComplete and jQuery JUI AutoComplete widget may also be defined.  read the code and docs for all options

 *      'options'=>array(

 *          'minLength'=>3, // number of characters that must be typed before autoCompleter returns a value, defaults to 2

 *      ),

 * ));

 * echo $form->error($model, 'PostCodeId');

 * ...

 * </pre>

 *

 * 6) in the Controller for the model, create a method to return the autoComplete data.

 *    NOTE: make sure to give users the correct permission to execute this method, according to your security scheme

 * 

 * in ContactsController.php (for example):

 * </pre>

 *   // data provider for EJuiAutoCompleteFkField for PostCodeId field

 *   public function actionFindPostCode() {

 *       $q = $_GET['term'];

 *       if (isset($q)) {

 *           $criteria = new CDbCriteria;

 *           $criteria->condition = '...', //condition to find your data, using q1 as the parameter field

 *           $criteria->order = '...'; // correct order-by field

 *           $criteria->limit = ...; // probably a good idea to limit the results

 *           $criteria->params = array(':q' => trim($q) . '%'); // with trailing wildcard only; probably a good idea for large volumes of data

 *           $PostCodes = PostCodes::model()->findAll($criteria);

 *

 *           if (!empty($PostCodes)) {

 *               $out = array();

 *               foreach ($PostCodes as $p) {

 *                   $out[] = array(

 *                       'label' => $p->PostCodeAndProvince,  // expression to give the string for the autoComplete drop-down

 *                       'value' => $p->PostCodeAndProvince, // probably the same expression as above

 *                       'id' => $p->PostCodeId, // return value from autocomplete

 *                   );

 *               }

 *               echo CJSON::encode($out);

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

 *           }

 *       }

 *   }

 * </pre>

 *

 * 7) in the Controller loadModel() method, return the related record

 * in ContactsController.php (for example)

 * <pre>

 * public function loadModel() {

 *      ...

 *      if (isset($_GET['id']))

 *               $this->_model=Contacts::model()->with('Postcode')->findbyPk($_GET['id']);  // <====  NOTE 'with()'

 *      ...

 * }

 * </pre>

 */


Yii::import('zii.widgets.jui.CJuiAutoComplete');

class EJuiAutoCompleteFkField extends CJuiAutoComplete {


	/**

	 * @var boolean whether to show the FK field.

	 */

	public $showFKField = false;


	/**

	 * @var integer length of the FK field if visible

	 */

	public $FKFieldSize = 10;

	/**

	 * @var string the relation name to the FK table

	 */

	public $relName;

    /**

     * @var string the relation name to the FK table

     */

    public $idSuffix;

	/**

	 * @var string extension to append to id fields for uniqneness

	 */

	public $displayAttr;


	/**

	 * @var integer width of the AutoComplete field

	 */

	public $autoCompleteLength = 50;


	/**

	 * @var string the ID of the FK field

	 */

	private $_fieldID;

        

	/**

	 * @var string the ID of the hidden field to save the display value

	 */

	private $_saveID;

        

	/**

	 * @var string the ID of the AutoComplete field

	 */

	private $_lookupID;

        

    /**

     * @var string the ID for the hidden field

     */

    private $_hiddenID;


	/**

	 * @var string the initial display value

	 */

	private $_display;

    /**

     * @var string name of the lookup field

     */

    private $_name;

    

    public function init() {

        parent::init(); // ensure necessary assets are loaded

        

        // JJD 8/3/11 make EJuiAutoCompleteFkField work for child rows where attribute like [$i]FieldName

        // get the ID which will be created for the actual field when it is rendered.

        // don't let resolveNameID() change $this->attribute which is needed to generate the actual field

        $attr = $this->attribute;

        $tempHtmlOpts = array();

        CHtml::resolveNameID($this->model, $attr, $tempHtmlOpts);

        $id = $tempHtmlOpts['id'].'_'.$this->idSuffix;

        $this->_fieldID = $id;

        $this->_saveID = $id . '_save';

        $this->_lookupID = $id .'_lookup';

        $this->_hiddenID = $id .'_hidden';

        $this->_name = $tempHtmlOpts['id'].'_lookup';


        $related = $this->model->{$this->relName}; // get the related record

        $value = CHtml::resolveValue($this->model, $this->attribute);

        $this->_display=(!empty($value) ? $related->{$this->displayAttr} : '');

        

        if (!isset($this->options['minLength']))

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


        if (!isset($this->options['maxHeight']))

            $this->options['maxHeight']='100';


        $this->htmlOptions['size'] = $this->autoCompleteLength;

        // fix problem with Chrome 10 validating maxLength for the auto-complete field

        $this->htmlOptions['maxlength'] = $this->autoCompleteLength;

        

        // setup javascript to do the work

        $this->options['create']="js:function(event, ui){\$(this).val('".addslashes($this->_display)."');}";  // show initial display value

        // after user picks from list, save the ID in model/attr field, and Value in _save field for redisplay

        $this->options['select']="js:function(event, ui){\$('#".$this->_fieldID."').val(ui.item.id);\$('#".$this->_saveID."').val(ui.item.value);}";

        // when the autoComplete field loses focus, refresh the field with current value of _save

        // this is either the previous value if user didn't pick anything; or the new value if they did

        // $this->htmlOptions['onblur']="$(this).val($('#".$this->_saveID."').val());";

    }


    public function run() {

        // first render the FK field.  This is the actual data field, populated by autocomplete.select()

        if ($this->showFKField) {

            echo CHtml::activeTextField($this->model, $this->attribute, array('size'=>$this->FKFieldSize, 'readonly'=>'readonly'));

        } else {

            echo CHtml::activeHiddenField($this->model,$this->attribute, array('id'=>$this->_hiddenID));

        }

        

        // second, the hidden field used to refresh the display value

        echo CHtml::hiddenField($this->_saveID,$this->_display, array('id'=>$this->_saveID)); 


        // third, the autoComplete field itself

        $this->htmlOptions['id'] = $this->_lookupID;

        $this->htmlOptions['name'] = $this->_name;       

        parent::run();


        // fouth, an image button to empty all three fields

        /*

        $label=Yii::t('DR','Remove '). ucfirst($this->relName); // TODO: how to translate relname?

        $deleteImageURL = '/images/text_field_remove.png'; 

        echo CHtml::image($deleteImageURL, $label,

            array('title'=>$label,

                'name' => 'remove'.$this->_fieldID,

                'style'=>'margin-left:6px;',

                // JJD 4/27/12 #1350 trigger onchange event for display field, in case there's an event attached (e.g. unsaved-changes-warning)

                'onclick'=>"$('#".$this->_fieldID."').val('').trigger('change');$('#".$this->_saveID."').val('');$('#".$this->_lookupID."').val('');",

            )

        );

         * 

         */

    }

}

?>



usage in the view:




 <?php 

		             $this->widget('EJuiAutoCompleteFkField', array(

		             'model'=>$smpCancerDefault, 

		             'attribute'=>'cancer_id', //the FK field (from CJuiInputWidget)

		             'sourceUrl'=>Yii::app()->createUrl('/samples/cancerAutoComplete'), 

		             'showFKField'=>false,

		             'FKFieldSize'=>10, 

		             'relName'=>'cancer', // the relation name defined above

		             'displayAttr'=>'CancerName',  // attribute or pseudo-attribute to display

		             'autoCompleteLength'=>50,

                     'idSuffix'=>$gridArr['uniq'], 

		             'options'=>array(

		                 'minLength'=>3,

		             ),

		        ));

		         ?>

Feel free to incorporate this into the next version if you like.

What if the remove button were optional?

In EJuiAutoCompleteFkField.php:




	// ...


	/**

	 * @var integer width of the AutoComplete field

	 */

	public $autoCompleteLength = 50;

        

        /***

         * 

         */

        public $showRemoveButton = false;


	// ...



And:




	if ($this->showRemoveButton)

        {

        	$label=Yii::t('DR','Remove '). ucfirst($this->relName); // TODO: how to translate relname?


		// ...


	}



Thank you for this great extension. Works like a charm.

But what if we need to add a new value instead of choosing one that exists?

What do we need to change in order to add new values if they don’t exist?

Thanks in advance.

I know this reply may be too late, but I use this codes to add data for widgets that automatically retrieve their source from database, like EJuiAutoCOmpleteFKField, or emultiselect.

Basically, this code will generate pop-up window using CJuiDialog with window id=addpublisher. Inside pop-up window I display add-form using <iframe>. This add-form is just an actionCreate copied into new action ("actionAddDialog") and use blank theme layout, so no header and footer displayed.

Then I use a link (as a plus icon) to show pop-up window.


		<?php

		// ============== GENERATE WINDOW AND BUTTON TO ADD DATA =========================


		// WINDOW

		$this->beginWidget('zii.widgets.jui.CJuiDialog',array(

		    'id'=>'addpublisher',

		    // additional javascript options for the dialog plugin

		    'options'=>array(

		        'title'=>'Add New Publisher',

		        'autoOpen'=>false,

		        'modal'=>true,

		        'draggable'=>false,

		        'width'=>880,

		        'height'=>560,

		    ),

		    'htmlOptions'=>array('style'=>'background:rgb(255,255,255)'),

		));

		echo '<iframe width=850 height=490 src="'.Yii::app()->createUrl('publisher/addDialog').'"></iframe> ';

		$this->endWidget('zii.widgets.jui.CJuiDialog');

		

		// BUTTON to DISPLAY POP-UP WINDOW

		echo CHtml::link('<img style="margin-left:5px" src="'.Yii::app()->request->baseUrl.'/images/plus_16.png">', 

			'#', array('onclick'=>'$("#addpublisher").dialog("open"); return false;',));

	

		// =======================================

		?>

For widgets that don’t refresh their content automatically everytime they were accessed like simple dropdownlist, I add javascript below to refresh it so new data will be shown.

Book_langcode is an id for select input form that needs to be refreshed.

<select id="Book_langcode">…</select>


		<!-- Javascript to refresh dropdownlist if clicked -->

		<script type="text/javascript" charset="utf-8">

		$(function(){

		    $("#Book_langcode").focus(function(){


			  	var old_options = document.getElementById("Book_langcode").options;


			  	// get new data from json

			    $.getJSON("<?php echo Yii::app()->createUrl('language/lookup') ?>",{id: $(this).val(), ajax: 'true'}, function(j){

			    	var option = document.createElement("option");


					for (var x = 0; x < j.length; x++) {

						var ada = false;

						for (var i = 0; i < old_options.length; i++) {

							if (j[x].id == old_options[i].value) ada = true;

						}

						if (!ada) {

							option.value = j[x].id;

							option.text  = j[x].value;

							var select=document.getElementById("Book_langcode");

							select.add(option, null)

						}

					}

			    })

		    })

		})

		</script>

Change line 237 to


$deleteImageURL = Yii::app()->createUrl('/images/text_field_remove.png'); 



If you want your Images folder to be within your Yii app instead of off your "home" url…

It saved me a lot of time. I used CJuiAutoComplete in many places, but I got trouble when I have two related tables, which linked with FK (foreign key). Finally I found this extension, and it solved my problem in a easy way.

Please note: You cannot leave “relName”, “displayAttr” as empty, because they all indicate FK field information. And again it’s an extension, not a replacement of CJuiAutoComplete.

Thanks to jeremy.

Is it possible to show on the text box a default text that disappears on click?

I tested your solution but I can’t see to populate the Foreign Key Value.

When I use ‘showFKField’=>true I can’t see the ID. If I use the latest extension available everything works, but with your modifications I can see the auto-complete working but no foreign key is retrieved.

Any ideas?

Hi,

i used EJuiAutoCompleteFkField extension in cgridview its working fine but getting issue after

ajaxupdate of cgridview.

can any body help,here how to call this extension after Ajax update cgridview.

--------------Someone suggest like this---------

‘afterAjaxUpdate’=>'function(){

            UpdateStock()


            


    }',

<script>

function UpdateStock(){

}

</script>


But how to call EJuiAutoCompleteFkField in this function .

thanks

hi,

EJuiAutoCompleteFkField is not working after pagination in cgridview

thanks

After an update of an cgridview (or whatever) you need to re-initialise any jQuery UI event triggers that you included in the cgridview, because the items in that view will be new and will not have triggers associated to them for the jQuery UI events. The items that were told to trigger events are no longer present on the page. When you load the page for the first time a jQuery script is called to link event triggers to certain items that are lost when you update the content of the page without reloading the page.

You will have to write your own javascript function to do that for you and you can use afterAjaxUpload to call that function.

EJuiAutoCompleteFkField is not the problem.

Hi Jeremy,

How are you?

Do you intend to convert this great extension to Yii 2?

Thanks

Can I use this plugin on a regular <input type="text"> tag instead of a $form object?

Hi Fabio,

Sorry I didn’t get notified of this post, three months ago.

Unfortunately I am not using Yii 2 yet, so I have no plans to convert the extension. Feel free to do so !

Jeremy

probably. I have never tried it, but since you are only passing a model into the widget, it should work.

DarkSlash asked:

> How I make this field to be required? I tried in the main array and in the options array to put ‘required’ => true, but it doesn’t work.

use the "required" validator in your model, for the field on which you have the auto-complete widget.

Also please post questions here, rather than on the extension page.

Jeremy

Hi Jeremy,

Thank you for your reply. I used this extension to solve my problem.

Best regards!

Hi Jeremy,

The widget is working well. I need to disable it after a selection is made, but still allow it the field to be copied. If it were a textField I could make it ‘readonly’ (instead of ‘disabled’), but this option does not work for the extension.

Do you have a suggestion as to how to proceed? Should I hide the widget and show a readonly text field in its place? Or is there a better option?

Thanks,

Tom

one idea: declare the widget with a ‘change’ handler, for example:




'options' => [....,

   'change'=>"js:function(event,ui){\$('#<FIELDID>').prop('readonly','readonly').autocomplete.disable();}",

]



replace <FIELDID> with the actual field id

this event fires after the user selects a value from the drop-down; and it 1) disables the field; 2) disables the autocompleter