EchMultiSelect Widget

This is a simple Wrapper Widget for the jQuery UI MultiSelect Widget by Eric Hynds.

Details about the widget can be found here.

A Demo of the JQuery widget can be found here.

[size="5"]Requirements[/size]

Tested with Yii 1.1.8.

[size="5"]Usage[/size]

Place the files into your Yii application like this:


protected/extensions/widgets/EchMultiSelect.php

protected/extensions/widgets/assets/jquery.multiselect.js

protected/extensions/widgets/assets/jquery.multiselect.css

protected/extensions/widgets/assets/jquery.multiselect.filter.js

protected/extensions/widgets/assets/jquery.multiselect.filter.css

Make sure to import these files in your config file ‘protected/config/main.php’:




...

	'import'=>array(

		...

		'application.extensions.widgets.*',

	)



As an example, you could use the widget in your view file like this:


$data= CHtml::listData(Color::model()->findAll(), 'ID', 'Name');

$this->widget('ext.widgets.EchMultiselect', array(

	// the data model associated with this widget:

	'model' => $model,

	 // the attribute associated with drop down list of this widget:

	'dropDownAttribute' => 'color',		

	// data for generating the options of the drop down list:

	'data' => $data,	

	// the name of the drop down list (this must be set if 'model' and 'dropDownAttribute' are not set):

	'name' => 'model_color',	

	// additional HTML attributes for the drop down list: 

	'dropDownHtmlOptions'=> array(

		'style'=>'width:378px;',

		'multiple'=>true,

	),

	// options for the jQuery UI MultiSelect Widget

	// (see the project page for available options):

	'options' => array( 

		'selectedList'=>5,

		'header'=>true,

		// set this to true, if you want to use the Filter Plugin

		'filter'=>true,

	),

)); 

This Yii widget creates a ‘drop down list’ with the given data and applies the JQuery widget to it, which turns the ordinary HTML select control into an elegant MultiSelect list of checkboxes with themeroller support.

To avoid confusion: I refer to the initial select element (that is subsequently hidden) as the ‘drop down list’; and to the eventual input element created by the JQuery Widget as the ‘MultiSelect list’.

The drop down list will be hidden directly after it is created: I placed a Javascript code to hide the element directly after it, to prevent it from being displayed while the page is loading, only to be hidden after the page has been loaded (as suggested here). I truly dislike that appearing/disappearing on page load, so I didn’d wait for the MultiSelect widget to hide the drop down list.

The provided ‘dropDownHtmlOptions’ will be adopted by the MultiSelect list. If you for example provide a style for the drop down list, it will also applied to the resulting MiltiSelect list.

The available ‘options’ for the jQuery UI MultiSelect Widget can be found on the project page. I added an additional option:<br>

filter: If you want to use the Filter Plugin (see the last example on the demo page), set this attribute to true.

[size="5"]Resources[/size]

I was going to get around to this (eventually). Thank you for making this an extension! It is my favorite multiselect that I’ve found so far.

Yes, it’s my favorite also. I’m happy you find it useful.

Best regards…

Nice wrapper, but I want to note some things:

  • You’ve forgot to add the jquery.ui.widget.min.js file to the zip (check before upload ;) )

  • Is not necessary to put the import statement in the main.php file, it’s done automatically when you instance the widget with its full path.

  • You should add another parameter to the widget like:


    /**

    * @var array the options for the jQuery UI MultiSelect Filter Widget

    */

    public $filterOptions = array();

and then in the initialization:


        $joptions=CJavaScript::encode($this->options);

        $jfilterOptions=CJavaScript::encode($this->filterOptions);

        if($this->options['filter'] === true) {

            $jscode = "jQuery('#{$id}').multiselect({$joptions}).multiselectfilter({$jfilterOptions});";

            unset($this->options['filter']);

        }

this way you can pass parameters to the filter :)

Oh my, I seriously overlooked to incorporate the filter options correctly.

‘Check before upload’ is good advice, and ‘don’t do it while sleepy and don’t rush it’ is too :)

Thanks a lot for your tips!

I was thinking about editing this widget a bit…

What would you think about the following changes/additions to this widget:

  • Change the button element (generated by the Multiselect widget) into a textfield. Make it serve as the filter-textfield (that is: filter the options as text is added into this textfield).

Add the option to make the texfield un-editable if filter is set to false.

  • Replace the two text-options ‘Check All’ and ‘Uncheck All’ in the header with a checkbox, that controls all checkboxes (check->check all, uncheck->uncheck all).

  • Add the option to hide the checkboxes in multiple selection mode (in single select mode, they are hidden), and highlight the selected elements instead (like it is done in the single selection mode).

  • Add the option to enter a new text (different from the options) into the textfield (like the ‘allowText’ option of this combobox)

In general, I want the user to be able to fill out the whole form without using a mouse as easily as possible.

In its current form, I couldn’t achieve that ‘usability’ with this widget, because the key strokes do not behave as I would like them to. For example:

  • After closing the drop down menu with esc or tab, the focus does not go automatically onto the next input element.

Neither does it remain on the button itself, so I can move to the next element with another tab.

  • In single select mode, the menu closes autom. after selecting an item, but the focus is lost there as well.

  • If filter is on, I can’t jump into the filter textfield without using the mouse.

I would try to take care of these issues as well.

Does something speak against the changes mentioned? Any other suggestions for improvement?

Thanks c@cba this is bonnie again finally got it working


$this->widget('application.extensions.EchMultiselect.EchMultiselect')

but I have a question in my _form a pulling checkboxlist for my model called features in my previous code it used to be




<?php echo $form->checkBoxList($model, 'featureIds', $model->getFeaturesOptions()); 



but in your extension I can’t pass featureIds.

How can I do this?. Please help.

Hi bonnie,

— Error Message?

is there an error message?

— ‘data’ array:

Do you provide an array for the ‘data’ property for EchMultiSelect(data for generating the options of the drop down list : see the $data array in the example on the exntension page)?

According to the ‘Class Reference of checkBoxList’, it is used like this:




echo $form->checkBoxList($model, 'featureIds', $data, $model->getFeaturesOptions());



You don’t pass on the $data array in your example above. Did that checkBoxList work correctly?

— Location of the Files:

When you extract the .zip-file, there is this folder ‘EchMultiSelect/’ that contains the files ‘EchMultiSelect.php’ and the folder’assets/’.

If you place the folder ‘EchMultiSelect/’ into your [font=“Courier New”]protected/extensions/widgets/[/font] folder, then you cal the widget like you now do:


$this->widget('application.extensions.EchMultiselect.EchMultiselect')

If you place the file ‘EchMultiSelect.php’ and the folder ‘assets/’ directly under your [font=“Courier New”]protected/extensions/widgets/[/font] directory, then you can call the widget with


$this->widget('application.extensions.EchMultiselect')

This is what I suggested in the extionsion page. Sorry if it was not clear, I added a more detailed description there now.

Sorry I did not say it’s not working. What am saying is that am trying to use it in my that prevously this code




echo $form->checkBoxList($model, 'featureIds', $model->getFeaturesOptions());



Note that am passing it in partial render _form and my getFeaturesOptions() method is here.




/*

    * Get the features list

    */

    public function getFeaturesOptions()

    {

    return CHtml::listData(AutoFeatures::model()->findAll(), 'id', 'features');

    }



Here is the forum am following to solve this issue with many-to-many relationship.

http://www.yiiframework.com/forum/index.php?/topic/20320-working-with-a-checkbox-list/

I have three table autos, features, and auto-features the connecting table for the two where an auto can have many features and many features can belong to many autos.

This the error am getting Undefined index: featureIds

This is what I have as part of the extension. It clearly pulls the value for the getFeatureOptions fine but how can I pass in the featureIds as my previous code used to work fine. Again if you look in the forum I have given the link will give a clear view of what am talking about.




<?php

$this->widget('application.extensions.EchMultiselect.EchMultiselect', array(

//the data model associated with this widget:

'model' => $model,

// the attribute associated with drop down list of this widget:

//'dropDownAttribute' => 'color',     

// data for generating the options of the drop down list:

'data' => $model->getFeaturesOptions(),    

// the name of the drop down list (this must be set if 'model' and 'dropDownAttribute' are not set):

'name' => 'model_color',

// the selected input value(s) (used only if 'model' and 'dropDownAttribute' are not set)

'value' => array($model, 'featureIds'), 

// additional HTML attributes for the drop down list: 

'dropDownHtmlOptions'=> array(

'style'=>'width:240px;',

),

// options for the jQuery UI MultiSelect Widget

// (see the project page for available options):

'options' => array( 

'header'=>true,

'height'=>175,

'minWidth'=>225,

'checkAllText'=>Yii::t('application','Check all'),

'uncheckAllText'=>Yii::t('application','Uncheck all'),

'noneSelectedText'=>Yii::t('application','Select an Option'),

'selectedText'=>Yii::t('application','# selected'),

'selectedList'=>false,

'show'=>'',

'hide'=>'',

'autoOpen'=>false,

'multiple'=>true,

'classes'=>'',

'position'=>array(),

// set this to true, if you want to use the Filter Widget

'filter'=>false,


),

// options for the jQuery UI MultiSelect Filter Widget 

'filterOptions'=> array(

'label' => Yii::t('application','Filter:'),

'width'=>100,

'placeholder'=>Yii::t('application','Enter keywords'),

'autoReset'=>false,

),

));

?>

<?php //echo $form->checkBoxList($model, 'featureIds', $model->getFeaturesOptions()); 

?>


</div>

</div>

<div class="clear"></div>        

</div>

<div class="rowElem">

<div class="fleft">

<div class="row">

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

<?php echo $form->textArea($model,'description',array('rows'=>8, 'cols'=>35)); ?>

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

</div>

</div>

<div class="fleft marg_left">

<div class="row">


</div>

</div>

<div class="fleft marg_left">

<div class="row">

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

<?php echo $form->textField($model,'vin',array('size'=>17,'maxlength'=>17)); ?>

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

</div>

</div>

<div class="clear"></div>        

</div>

<div class="rowElem">

<div class="fleft">

<div class="row">

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

<?php $this->widget('CAutoComplete', array(

'model'=>$model,

'attribute'=>'tags',

'url'=>array('suggestTags'),

'multiple'=>true,

'htmlOptions'=>array('size'=>25),

)); ?>

<p class="hint">Please separate different tags with commas.</p>

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

</div>

</div>

<div class="fleft marg_left">

<div class="row">


</div>

</div>

<div class="fleft marg_left">

<div class="row">

Maximum of 8 images allowed

<?php

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

'name' => 'images[]',

'accept' => 'jpeg|jpg|gif|png', // useful for verifying files

'duplicate' => 'Duplicate file!', // useful, i think

'denied' => 'Invalid file type', // useful, i think

'max' => '8',

'remove' => 'Delete'

));

?>



–> ‘data’

Aaah, okay.

[font="Courier New"]getFeaturesOptions()[/font] returns the data, so I assume while calling the ExhMultiselect widget you type:


'data'=>$model->getFeaturesOptions(),

–> Undefined index: featureIds

Does your model have a relation named ‘featureIds’?

That is: In your model file ‘models/Auto.php’ there has to be a relation similar to this:




public function relations() { ...

   'featureIds' => array(self::MANY_MANY, 'Features', 'auto_feature(AutoID, FeaturesID)'),

   ...

}



@bonnie:

I could not reproduce your error "Undefined index: featureIds".

If there is no relation named’featureIds’ in your Auto.php model file, then you should get the error:

Property "Auto.featuresIds" is not defined.

Can you trace back the error and determine exactly which line of your _form.php file does produce that error and post it here?

I have three tables.

1.Autos

Table

autos.

2.Featues-

Table with fields Id and fields.

3.AutoFeatures

Table - auto-features fields (auto_id and feature_id)

In Auto Model I have.




class AutoListings extends CActiveRecord

{

/**

     * Array to manage the HAS_MANY relation. 

     * You can't assign values directly to $features: 

     * it's a relation, so you have to bypass it

     */

    public $featureIds = array();

public function relations()

{

'features' => array(self::MANY_MANY, 'AutoFeatures', '{{auto_listings_features}}(listing_id, feature_id)'),

}

protected function afterFind()

{

parent::afterFind();

/**

     * To sync the two "twins": the relation called 'features' 

     * and the public variable 'featureIds'. If you don't do that 

     * you will not see checkbox checked for the features 

     * that are currently assigned to the an auto

     */ 

    if(!empty($this->features))

    {

    foreach($this->features as $n => $feature)

    $this->featureIds[] = $feature->id;

    }

}

//Extension used for many-to-many relationship management

    public function behaviors()

    {

    return array('CAdvancedArBehavior' => array(

    'class' => 'application.extensions.CAdvancedArBehavior.CAdvancedArBehavior'));

    }

}




Here is my Features model




class AutoFeatures extends CActiveRecord

{

/**

    * @return array relational rules.

    */

    public function relations()

    {

    // NOTE: you may need to adjust the relation name and the related

    // class name for the relations automatically generated below.

    return array(

    'autoListing' => array(self::MANY_MANY, 'Autos', '{{auto_features}}(feature_id, auto_id)'),

    );

    }

/*

    * Get the features list

    */

    public function getFeaturesOptions()

    {

    return CHtml::listData(AutoFeatures::model()->findAll(), 'id', 'features');

    }

}




In my controller for autos where everything is happening.

For create action




public function actionCreate()

 {

$model->features = $_POST['Autos']['featureIds']  

    if($model->save())

    {

    $this->redirect(array('view','id'=>$model->id));

    }

}




In update section.




public function actionUpdate()

 {

$model->features = $_POST['Autos']['featureIds']  

    if($model->save())

    {

    $this->redirect(array('view','id'=>$model->id));

    }

}



Next come the form




<div class="form">

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

'id'=>'auto-form',

'enableAjaxValidation'=>true,

'enableClientValidation'=>true,

'focus'=>array($model,'type'),

'htmlOptions'=>array('enctype'=>'multipart/form-data'),

)); ?>

<div class="fleft marg_left">

<div class="row">

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

<?php

/*$this->widget('application.extensions.EchMultiselect.EchMultiselect', array(

//the data model associated with this widget:

'model' => $model,

// the attribute associated with drop down list of this widget:

//'dropDownAttribute' => 'featureIds',     

// data for generating the options of the drop down list:

'data' => $model->getFeaturesOptions(),    

// the name of the drop down list (this must be set if 'model' and 'dropDownAttribute' are not set):

'name' => 'model_color',

// the selected input value(s) (used only if 'model' and 'dropDownAttribute' are not set)

'value' => array($model, 'featureIds'), 

// additional HTML attributes for the drop down list: 

'dropDownHtmlOptions'=> array(

'style'=>'width:240px;',

),

// options for the jQuery UI MultiSelect Widget

// (see the project page for available options):

'options' => array( 

'header'=>true,

'height'=>175,

'minWidth'=>225,

'checkAllText'=>Yii::t('application','Check all'),

'uncheckAllText'=>Yii::t('application','Uncheck all'),

'noneSelectedText'=>Yii::t('application','Select an Option'),

'selectedText'=>Yii::t('application','# selected'),

'selectedList'=>false,

'show'=>'',

'hide'=>'',

'autoOpen'=>false,

'multiple'=>true,

'classes'=>'',

'position'=>array(),

// set this to true, if you want to use the Filter Widget

'filter'=>false,


),

// options for the jQuery UI MultiSelect Filter Widget 

'filterOptions'=> array(

'label' => Yii::t('application','Filter:'),

'width'=>100,

'placeholder'=>Yii::t('application','Enter keywords'),

'autoReset'=>false,

),

));*/

?>

<?php echo $form->checkBoxList($model, 'featureIds', $model->getFeaturesOptions()); 

?>

</div>

</div>




As in the above when I use my code I had previously




<?php echo $form->checkBoxList($model, 'featureIds', $model->getFeaturesOptions()); 

?>




Everything works fine.

But when I apply the extension the values are showing fine but when I select and submit the error is in the action update and create. Undefined value featureIds. The $model->getFeaturesOptions() is the one pulling values from the database for the features so that an auto can have as many values for the features as possible linked through the third table auto-features

As you can see the relationship is through features but featureIds takes in the afterfind method.

The problem is how should I place featureIds in your extension widget. If I do with my case and following the forum that had this issue my code is working fine creating and updating the database I don’t get any error.

When I view the source code on the form featureIds is shown using my line of code but the extension is not showing and the issue might be the where to call the featureIds as in my case.

Hi bonnie,

I tried something similar to your code, and the featureIds that were selected were succesfully posted.

Can you change the ‘actionCreate’ method of your AutosController like this:




public function actionCreate()

{

  print("<pre>".print_r($_POST['Autos'])."</pre>"); 

    /*if($model->save())

    {

    $this->redirect(array('view','id'=>$model->id));

    }*/

}



That is: Comment out the save() and redirect and print out the post variables to the screen to check if the featureIds arrive here as expected.

By the way: try to call the widget like this:




$this->widget('application.extensions.EchMultiselect.EchMultiselect', array(

  'model' => $model,

  'dropDownAttribute' => 'featureIds',     

  'data' => $model->getFeaturesOptions(),

  'dropDownHtmlOptions'=> array(

    'style'=>'width:240px;',

  ),

));




Don’t comment out the line with [font=“Courier New”]‘data’ => $model->getFeaturesOptions(),[/font]

Haha the following works perfect but if I notice something crazy I will let you know. I hope this will help someone.




$this->widget('application.extensions.EchMultiselect.EchMultiselect', array(

  'model' => $model,

  'dropDownAttribute' => 'featureIds',     

  'data' => $model->getFeaturesOptions(),

  'dropDownHtmlOptions'=> array(

    'style'=>'width:240px;',

  ),

));




Just wanted to let you know that following the instructions on the Extension download page, I had to add the ‘EchMultiSelect.EchMultiSelect’, as the instructions are a bit ambiguous there.

Also, with this standard installation instruction, am getting only a list without any errors/style or headings. Will see into this after, but right now is what I have.

EDIT: Found the problem on the ‘name’ attribute. Just removed it. This can be clarified on the installing instructions.

Thanks for the feedback.

I changed the instructions, I hope they are clearer now.

Hi, I’m a newbie and came across this widget, which I think is great - thanks very much for putting this together. However, I don’t fully understand how to properly POST to my database table. For example, if I have 10 items and the first three are checked, firebug shows me something like this:

ModelName[field][] 1

ModelName[field][] 2

ModelName[field][] 3

But if "field" is only one column in the table, how are 3 different values being POSTed to it? Is DropDownAttribute supposed to be an array, not a variable?

Also, what if I had a table set up with id, field_1, field_2, field_3, field_4, etc. Each field_X entry will be either "0" (if no posting) or "1" if that item was checked. How can I achieve that with this widget?

Thanks for your help.

How do you show your preselected data in the list?

On my update action I want to show what related data I have already selected. But it shows ‘0 selected’.

I am using my relation name as the dropdownAttribute. I have tried HAS_MANY and MANY_MANY and I cant get it to preselect my list for me.

One possibility is the following:

you define a new attribute/variable in the model file, at the beggining, directly after the class starts:




class Car extends CActiveRecord {

   $colors = array();

   ...

}



The name of the variable should not be equal to a column name. Here, your model/table should not have a column named ‘colors’. You then use this new attribute in the widget:




$this->widget('ext.widgets.EchMultiselect', array(

    'model' => $model,

    'dropDownAttribute' => 'colors',     

    'data' => $data,

});



In the controller file, you then have the array




$_POST['Cars']['colors']



that provides the id’s of the clicked/selected colors for further use.

To handle the field_1,…,field_N columns:

You could build an array, like




$data = array('field_1',...,'field_N');



and use it as the data source in the widget above.

Then in the controller you can loop from 1 to N and check for each, if the correspondig box was selected and set the field_X accordingly. Something like:




if( in_array(X,$_POST['Cars']['colors']) ) $model->field_X = 1;

else $model->field_X = 0;



Otherwise, you would have to provide a separate checkbox for each of the field_X columns in your form. And to combine a set of checkboxes into a dropdown of checkboxes, I think we would need a different widget.

Best regards…