Yii 1.1: tokeninput

Integrates the jQuery Tokeninput plugin into an active text field.
23 followers

Description

TokenInput displays a text input field for a model attribute and applies the jQuery Tokeninput plugin on it. From the jQuery Tokeninput plugin site: "Tokeninput is a jQuery plugin which allows your users to select multiple items from a predefined list, using autocompletion as they type to find each item.".

Important notes

  • Currently only string attributes are supported.
  • Currently only server-backed search is supported, no local data search.
  • Tokens are added using the token value for both tokenValue (default: 'id') and propertyToSearch (default: 'name'), i.e. {'id': value, 'name': value}.
  • In production (YII_DEBUG not defined or set to false), a minified version of the JS file is loaded (minified with http://jscompress.com).
  • The included Tokeninput plugin is version 1.6.0 enhanced with the pull request 'Allow creation of tokens on the fly' (https://github.com/loopj/jquery-tokeninput/pull/219).

The widget will automatically pre-populate the token input with the value of the attribute.

The 'cssFile' property can be defined to use a custom CSS file. If it is not defined, one of the default plugin CSS files will be used based on the value of $options['theme']. If this option is not defined, 'token-input.css' will be used, otherwise 'token-input-[theme].css' will be used. Look at the css files in the extension's 'css' directory for available themes.

Requirements

Yii 1.0.1 or above.

Usage

Extract the content of the package in the extensions directory, create a controller action to handle search requests and insert the widget in a view.

Sample controller action

This sample retrieves 10 tokens from a MongoDB database and sorts them alphabetically (uses the directmongosuite extension):

public function actionSearch($q)
{
    $term = trim($q);
    $result = array();
 
    if (!empty($term))
    {
        $cursor = Tag::model()->query()->findCursor(array('name' => new MongoRegex('/' . $term . '/i')), array('name'), array('name' => 1), 10);
 
        if (!empty($cursor) && $cursor->count())
        {
            foreach ($cursor as $id => $value)
            {
                $result[] = array('id' => $value['name'], 'name' => $value['name']);
            }
        }
    }
 
    header('Content-type: application/json');
    echo CJSON::encode($result);
    Yii::app()->end();
}

Sample usage in a form

<div class="row">
    <?php echo $form->labelEx($model,'tags'); ?>
    <?php $this->widget('ext.tokeninput.TokenInput', array(
        'model' => $model,
        'attribute' => 'tags',
        'url' => array('tag/search'),
        'options' => array(
            'allowCreation' => true,
            'preventDuplicates' => true,
            'resultsFormatter' => 'js:function(item){ return “<li><p>” + item.name + “</p></li>” }',
            'theme' => 'facebook',
        )
    )); ?>
    <?php echo $form->error($model,'tags'); ?>
</div>

Properties

  • 'model' (required): CModel, the data model associated with this widget.
  • 'attribute' (required): string, the attribute associated with this widget.
  • 'url' (required): mixed, URL or an action route that can be used to create the URL to handle search requests.
  • 'options' (optional): array, the initial JavaScript options that should be passed to the jQuery Tokeninput plugin. Please see the plugin's homepage for available options.
  • 'cssFile' (optional): string, the CSS file to use. Please see description above for more details.

Resources

Changes

v0.3 - 24 Jan. 2012:

  • Use 'CJavaScript:encode' instead of 'CJSON:encode' for encoding the JS options. This preserves value types and allows adding JS functions by pre-pending them with 'js:' (see example).
  • When pre-populating, use the JS options 'tokenValue' and 'propertyToSearch' if defined, otherwise use their default values 'id' and 'name' respectively.
  • Fix token creation code of the jQuery plugin to use 'propertyToSearch' where appropriate.

v0.2 - 17 Jan. 2012:

  • Do not process the attribute value with 'strtolower()' when pre-populating.
  • Added a helper static function 'tokenize()' that splits a value by a delimiter and use it when pre-populating. Can be helpful to use the same function used by the widget, outside of it (for consistency, e.g. when saving to a db as array).

Total 20 comments

#17146 report it
Ariel Braun at 2014/05/07 08:06am
Enable supporting url=function

I have changed the source function in order to enable using: url=>"function(){return "some/url";}"

public function registerInitScript()
    {
            $a=false;
            if(is_array($this->url))
                $a=true;
            if($this->hasModel())
        $selector = '#' . CHtml::activeId($this->model, $this->attribute);
            else {
                $selector = '#' . CHtml::getIdByName($this->name);
            }
        $js = '$("' .  $selector . '").tokenInput('.($a===true ? '"':'') . CHtml::normalizeUrl($this->url) . ($a===true ? '"':'').', ' . CJavaScript::encode($this->options) . ');';
 
        Yii::app()->getClientScript()->registerScript(__CLASS__.$selector, $js, CClientScript::POS_READY);
    }
#13792 report it
Haykel at 2013/06/26 08:51am
No time

Hi, I'm sorry but I currently don't have time to look at any issues. I would suggest that anyone who has bug fixes or enhancements to clone the repository and send pull requests.

#13621 report it
surajk at 2013/06/12 02:40am
Re: not works inside tabs?

Hi Haykel,

i used tab

$noticeboard='Notice Board';
        $teamdatafiles='Team DataFiles';
        $tabs = array();
 
        $tabs[$teamdatafiles] = $this->renderPartial('dataFilesPage',true);
 
        $this->widget('zii.widgets.jui.CJuiTabs', array(
                'tabs' => $tabs,
                'options'=>array(
//                 'selected'=>2,
                        'collapsible' => true,
                    ),
                    'htmlOptions'=>array(
                        'style'=>'width:750px;height:413px;'
                    ),
        ));

on dataFilesPage.php

$modelnn = new Team;
        $this->widget('application.extensions.tokeninput.TokenInput', array(
                       'model' => new Team,
                       'attribute' => 'team_name',
                       'viewname' => 'teams',//this parameter added by me to extract functionality same modified in TokenInput.php also
                       'url' => array('team/search'),
                       'options' => array(
                       'allowCreation' => true,
                       'preventDuplicates' => true,
                       'theme' => 'facebook',
                       'class'=>'search',
                       )
               ));

token dispayed 12 times

Thanks.

#13617 report it
Haykel at 2013/06/11 02:52pm
Re: not works inside tabs?

Hi, I did not test it inside CJuiTabs. Could you please put online a sample page with the issue? This would be very helpful. Thanks.

#13612 report it
surajk at 2013/06/11 07:26am
not works inside tabs?

hi,

gr8 work! its working fine on view page, but when I'm using inside CJuiTabs it shows

<ul class="token-input-list-facebook"> <li>.....</li></ul>

multiple times (12 times).

thanks in advance

#8981 report it
onkar lal at 2012/07/11 09:24am
yii tokeninput extension cursor focus on wrong place after same match?

I am having problem with yii tokeninput extension. When i search name it gives the user list and if i select any name and if that name is also selected previous than the cursor point after the selected item, it does not point at the end of all the the item in the input box.

I am using this configuration.

$this->widget('ext.tokeninput.TokenInput', array( 'model' => $model, 'attribute' => 'USER_ID', 'url'=>$this->createUrl('user/searchUserNames'), 'options' => array( 'allowCreation' => false, 'preventDuplicates' => true, // 'resultsFormatter' => 'js:function(item){ return “

” + item.name + “ ” }', 'theme' => 'facebook', //'hintText' => 'Type', 'prePopulate' => $prePopulate, 'processPrePopulate' => $processPrePopulate,
   )

));

I have also lookout at the examples but does not find the solution. can any one help me ? http://loopj.com/jquery-tokeninput/demo.html

I found the solution of this problem. We need to make some change in jquery.tokeninput.js on line 509 by making it commented. It will solve the problem.

  if(found_existing_token) {
            select_token(found_existing_token);

509 // input_token.insertAfter(found_existing_token); input_box.focus(); return; }

#7633 report it
Haykel at 2012/04/04 03:06am
Re: Change width
#7629 report it
Finzaiko at 2012/04/03 10:41pm
Change width

I like this extention what I looking for, Thanks to contribute but Im still confuse how to change textfield width ?

#7425 report it
Haykel at 2012/03/22 04:54am
Discussion thread

Hi,

I have created a discussion thread on the Yii forums for this extension. Please use it for discussions as it is better suited as comments.

Discussion thread

#7374 report it
Junior - df9 at 2012/03/18 03:21pm
Serialize

Hey, thanks for this great extension!

I'm trying to use it as a tag manager for models and would like to make a suggestion:

What about accepting and treating serialized array data in attribute's value? So we could retrieve "id" and "name" properties from a serialized array instead of a comma delimited list of words.

Thanks and regards!

#7356 report it
Milan Zivkovic at 2012/03/16 11:51am
Re: How to show 'prePopulate'...

Hi haykelbj,

Thanks for your answer. I also agree that since it work with module it need to use module attribute for populate. My application have connection between document and partners. On document can be connected with many partners. So in my Document model I have relation with Partner model, that have name 'partners' and it's HAS_MANY relation. In Document model I also have attribute 'partner' that is made only for collecting values from form. So in controller I check is 'partner' have some values (id's from Partner model) and if it have it I write it to database table that keep connection between Document and Partner. In edit form I can again populate 'partner' attribute and in that case I will complete request that extension need to module and its attribute. And what is need in that case is to take my part of code that I add to extension and use it like default. To fill 'partner' attribute with key=value I can use

CHtml::listData($model->partners, 'partner_id', 'partner.naziv')

So it will be array that can be really easy used to create 'prePopulate'. In this case you dont need to care about too big list of options because you will handle just previous selected options... I hope that this can help for next version of this extension...

#7348 report it
Haykel at 2012/03/16 03:18am
Re: How to show 'prePopulate'...

Hi zmilan,

as the widget works on a model attribute, it should only use the attribute's value for pre-populating. The problem you are having is related to the fact that the widget currently does not support distinct values for 'tokenValue' and 'propertyToSearch', so the best thing to do would be to add support for this feature.

One way to do it would be to add a property to the widget to hold an array of all possible 'tokenValue' => 'propertyToSearch' mappings. But the list could be very big, so perhaps it could be a reference to a function that takes 'tokenValue' as an argument and returns the corresponding 'propertyToSearch' value.

I will try to implement this functionality in the next version, so if there are any other suggestions, just make a comment!

#7334 report it
Milan Zivkovic at 2012/03/14 08:51pm
How to show 'prePopulate'...

Hi,

At first place, thanks for this extension. I need this jquery plugin to solve problem with multi select options from big table, so your extension really helps. I also had some problem in implementing extension and I'm really not sure do I miss something about how to use it, but at end I fix it on my way. So I will post here my solution to share, maybe it will help someone, or if its good maybe you can include it in next version... Problem was in using prePopulate. As I read it will be automatic from model attribute if its not empty. I try to do this like its described but I wasn't success to have both keys and values/titles. Only that I find how to do was to have one of them keys or values. So I do this changes to original code of extension:

public function init ()
    {
        if ( ! is_array ( $this->options ) ) $this->options = array ( );
 
        $tokenValue = 'id';
        if ( isset ( $this->options[ 'tokenValue' ] ) && strlen ( trim ( $this->options[ 'tokenValue' ] ) ) > 0 )
        {
            $tokenValue = trim ( $this->options[ 'tokenValue' ] );
            $this->options[ 'tokenValue' ] = $tokenValue;
        }
 
        $propertyToSearch = 'name';
        if ( isset ( $this->options[ 'propertyToSearch' ] ) && strlen ( trim ( $this->options[ 'propertyToSearch' ] ) ) > 0 )
        {
            $propertyToSearch = trim ( $this->options[ 'propertyToSearch' ] );
            $this->options[ 'propertyToSearch' ] = $propertyToSearch;
        }
 
        if (!isset($this->options[ 'prePopulate' ]))
        {
            $value = trim ( $this->model->{$this->attribute} );
            if ( ! empty ( $value ) )
            {
                $prePopulate = array ( );
                $tokenDelimiter = isset ( $this->options[ 'tokenDelimiter' ] ) ? $this->options[ 'tokenDelimiter' ] : null;
                $tokens = self::tokenize ( $value, $tokenDelimiter );
 
                if ( isset ( $this->options[ 'preventDuplicates' ] ) && $this->options[ 'preventDuplicates' ] === true )
                        $tokens = array_unique ( $tokens );
 
                foreach ( $tokens as $token )
                        $prePopulate[ ] = array ( $tokenValue => $token, $propertyToSearch => $token );
 
                if ( ! empty ( $prePopulate ) )
                        $this->options[ 'prePopulate' ] = $prePopulate;
            }
        }
        else if (  is_array ( $this->options[ 'prePopulate' ] ))
        {
            $prePopulate = array ( );
            foreach ($this->options[ 'prePopulate' ] as $key => $val)
            {
                $prePopulate[ ] = array ( $tokenValue => $key, $propertyToSearch => $val );
            }
            if ( ! empty ( $prePopulate ) )
                $this->options[ 'prePopulate' ] = $prePopulate;
        }
 
        parent::init ();
    }

Like You can see, I put original code for prePopulate in condition if prePopulate parameter wasn't sent and if its sent and its array I create prePopulate from it. With this modification it was easy to create widget like this:

<div class="row">
            <?php echo $form->labelEx($model,'partner'); ?>
            <?php $this->widget('application.extensions.tokeninput.TokenInput', array(
                'model' => $model,
                'attribute' => 'partner',
                'url' => array('partner/find'),
                'options' => array(
                    //'allowCreation' => true,
                    'prePopulate' => CHtml::listData($model->partners, 'partner_id', 'partner.naziv'),
                    'preventDuplicates' => true,
                    'resultsFormatter' => 'js:function(item){ return "<li><p>" + item.name + "</p></li>" }',
                    'theme' => 'facebook',
                )
            )); ?>
            <?php echo $form->error($model,'partner'); ?>
        </div>

Like You can see, now its easy to send prePopulate like parameter and in usual Yii fashion:

'prePopulate' => CHtml::listData($model->partners, 'partner_id', 'partner.naziv'),

I hope this will help someone. But also I will be happy to hear if there is some other way to do this...

Thanks again for this extension...

#7289 report it
Borales at 2012/03/10 09:35am
Problem solved

haykelbj, thanks. Basically, I need to get tags as a strings. So I set 'id' and 'name' fields to the same value ($tag->name). And problem gone :)

#7287 report it
Haykel at 2012/03/10 07:43am
Re: Some fixes

Thanks for the fix, seems the correct way to do it, I will include it in the next release.

  • But, when I got errors - it shows record IDs instead "name" field.

What kind of errors, form validation or server search errors? What do you mean with 'shows', you mean the displayed tokens in the dropdown list?

  • When I'm setting 'tokenValue' => 'name' - I can't create new tags ("create new token" is unavailable).

Doing so will make both 'tokenValue' and 'propertyToSearch' set to 'name'. This means that tokens will have only one property called 'name', used to get both the text to display and the value to send on submission. The functionality for creating new tokens sets 'propertyToSearch' to '[input text] (Create new token)' then 'tokenValue' to '[input text]', which overrides the first value if both properties have the same name.

For further investigation:

  1. Make sure the server search function returns the same value for both properties of the tokens (see the 3rd item in the section 'Important notes')
  2. Can you provide a sample project for testing?
#7286 report it
Borales at 2012/03/10 06:38am
Some fixes

Thanks for extension. I think the right way of getting model/attribute value is:

$value = CHtml::resolveValue($this->model, $this->attribute);
// instead of this
$value = trim($this->model->{$this->attribute});

And other problem. I'm not sure whose it's fault - but I get some strange issue with adding new tags with this extension.

I have this:

$this->widget(
            'TokenInput',
            array(
                'model' => $model,
                'attribute' => "[$key]tags",
                'url' => array('tag/suggest', 'lang_id' => $key),
                'options' => array(
                    'allowCreation'     => true,
                    'preventDuplicates' => true,
                    'searchDelay'       => 500,
                    'minChars'          => '2',
                    'theme'             => 'facebook',
                    //'tokenValue'        => 'name',
                ),
            )
        );

When I'm submitting form without errors - it works great. But, when I got errors (form validation) - it shows record IDs instead "name" field. When I'm setting 'tokenValue' => 'name' - I can't create new tags ("create new token" is unavailable)

Maybe you can help me

#6738 report it
Haykel at 2012/02/02 02:33am
Re: Implementing user input for more than one category.

Hi,

personally I would add a form with two fields for name and e-mail and a 'Add' button which would add the data to the token input through JavaScript. The form can be always visible, or become visible when the user clicks on a button/link, but I would not add the name and the e-mail separately (name in token input and the e-mail in dialog).

#6720 report it
Chief at 2012/01/31 10:33pm
Implementing user input for more than one category.

Hey Thank You for the update,

I had a question regarding adding a new token. So I'm displaying a couple 2 things in the drop down. The Name of a Contact, and their email. Now I want to allow "create a new contact", but I want them to be able to enter the name of the contact and email address. I was thinking of ways to implement this. Maybe a on select javascript to give a dialog that asks for email of the user.

Any ideas to implement this?

P.S Also its saying Undefined for the second thing, as its only creating the token via 1 property(name).

#6640 report it
Haykel at 2012/01/24 04:21am
Re: How Do I change the result formatter

The current version has an issue making it not possible to add functions to the JS options. It is however very easy to resolve:

1/ in 'TokenInput.php', replace

CJSON::encode($this->options)

with

CJavaScript::encode($this->options)

2/ to add a JS function, prepend it with 'js:' so that the encoder does not treat it as a string, e.g. (I have changed your code to only use one 'li' tag per item):

'resultsFormatter' => 'js:function(item){ return "<li>" + item[this.propertyToSearch] + " <b>" + item[this.secondProperty] + "</b></li>" }'

I will updload a fixed version asap, but in the meantime you can make the change manually.

#6637 report it
Chief at 2012/01/23 06:21pm
How Do I change the result formatter.

On the php side, I know how to do it in the .js with the resultsFormatter: function(item){ return "

" + item[this.propertyToSearch]+ " + item[this.secondProperty] +" },

but how do I do it on the php side, what would I put for item[this.propertyToSearch]

<?php $this->widget('ext.tokeninput.TokenInput', array(
        'model' => $model,
        'attribute' => 'guests',
        'url' => array('user/contacts'),
        //'value' => 'Terminal 5',
        'options' => array(
            'allowCreation' => true,
            'preventDuplicates' => true,
            'theme' => 'facebook',
            'resultsFormatter' => 'function(item){ return "<li>" + item[this.propertyToSearch]+ "</li><li> + item[this.secondProperty] +</li>" }'
        )
    )); ?>

Leave a comment

Please to leave your comment.

Create extension