Using the jamselect widget with min and max values

  1. The problem
  2. First put the widget in a form
  3. A few things to notice
  4. Lets look at the controller (create)
  5. Here is the function that formats the keywords
  6. Update record
  7. Rules
  8. The validation function

The problem

I needed to have users select from a list of keywords, in this example they must select at least two keywords but they cannot select more than six.

The keywords will be stored in a text field in a comma delimited format. The keywords will be created by the site administrator.

In a very timely fashion the jamselect widget showed up, thanks volkmar!

The widget does not provide min/max filtering so I will implement a custom rule.

First put the widget in a form

This is my implementation.

<?php	
$this->widget(
    'application.extensions.asmselect.JAMSelect',
    array(
        'selected'=>$this->getKeywords($model->keywords),
		'data'=>CHtml::listData(Keywords::model()->findAll(),'word', 'word'),
                'htmlOptions'=>array(
            'title'=>'Select up to six keywords',
            'name'=>'keywordArray'
        ),
        'animate'=>true
    ));
?>

A few things to notice

The widget is located in /protected/extensions not /protected/extensions/widgets, I only point this out for people who aren't sure where to put things. You have quite a bit of flexibility on where you locate widgets and components. It doesn't makes sense to me to create another folder in extensions but perhaps on a very large project it would.

The dropdown list data is being pulled from a table that has only two fields, id and word, since I want to select the word from the list and store exactly the same value in the database I need a key=>value array that looks like word'=>'word.

The $_POST data will contain an array of selected values, this will be in $_POST['keywordArray']

Lets look at the controller (create)

public function actionCreate()
{
    $model=new EventAbstract;

    $this->layout = '//layouts/column1';

    // Uncomment the following line if AJAX validation is needed
    // $this->performAjaxValidation($model);

    if(isset($_POST['EventAbstract']))
    {
        $model->attributes=$_POST['EventAbstract'];

        // I better do a check in case no keywords were selected
        $keywordArray = (!isset($_POST['keywordArray']) ? array() : $_POST['keywordArray']); 

        // now I either have an array of keywords or an empty array
        // send the array to a function to format the keywords in a 
        // comma delimited format
        $this->setKeywords($model,'keywords',$keywordArray);

        // it's all done but not yet validated
        if($model->save())
            $this->redirect(array('view','id'=>$model->id));
    }

    $this->render('create',array('model'=>$model));
}

Here is the function that formats the keywords

Remember the keywords are suppled by the admin so I am not doing any checking here for capitals or illegal characters.

public function setKeywords($model,$field,$keys)
{
    $model->$field = (empty($keys) ? null : implode(',',$keys));
}

Update record

And when we perform an update on the record we have to turn our comma delimited field back into an array, so similarly.

public function getKeywords($delimited)
{
    $keys = explode(',',$delimited);
    return $keys;
}

Rules

And now for the rules (in the model file)

public function rules()
{
    // NOTE: you should only define rules for those attributes that
    // will receive user inputs.
    return array(
        // other rules are here
        array('keywords', 'keywordCount', 'skipOnError'=>true, 'maxKeys'=>6, 'minKeys'=>2),
        // keywords is the field in the table currently holding the submitted value
        // $model->keywords
        // keywordCount is the function (in the model) that will do the validation
        //
        //skipOnError, maxKeys and minKeys are all parameters that will be 
        //sent to the validation function other rules
    );
}

The validation function

public function keywordCount($attribute,$params)
{
    $keys = explode(',',$this->$attribute);
    // check if there are keywords exist
    if(!$this->$attribute)
    {
        $this->addError($attribute,'<strong>If you need to add a keyword please contact the administrator</strong>');
    } 
    // check if there are enough keywords
    if(count($keys) < $params['minKeys'])
    {
        $this->addError($attribute,'Please choose at least '.$params['minKeys'].($params['minKeys'] > 1 ? ' keywords' :' keyword'));
    } 
    // check if there are too many keywords
    if(count($keys) > $params['maxKeys'])
    {
        $this->addError($attribute,'You have chosen too many keywords max = '.$params['maxKeys']);
    }	 
}	

Now the user can select between 2 and 6 keywords.

doodle