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).
Function strtolower() and non latin symbols
haykelbj, thank you for the extension! This is the same I wanted!
I use it in the multilanguage application and have found that all non latin symbols (when I try to update some existing model with tokens) became damaged. The problem in the strtolower function. To resolve it I replace the row
$value = trim($this->model->{$this->attribute});
to
$value = mb_strtolower(trim($this->model->{$this->attribute}),'UTF-8');
(for my UTF-8 encoding) and remove strtolower from the
$tokens = preg_split('/\s*' . $tokenDelimiter . '\s*/', strtolower($value), -1, PREG_SPLIT_NO_EMPTY);
Remove strtolower()
Hi Alexey,
thanks for reporting this. I had some thoughts about it and I came to the conclusion that the call to strtolower() should actually be removed because:
Any comments before I make the change?
Remove strtolower()
I fully agree with you.
Version 0.2
Just uploaded version 0.2. See 'Changes' for details.
Using Prepopulate option
Hi,
I'm having problems using PrePopulate option. It populates the text box, but it says undefined, undefined, undefined many many times.
<?php $result = array('id' => $business->id, 'name' => $business->name); ?> <?php $this->widget('ext.tokeninput.TokenInput', array( 'model' => $model, 'attribute' => 'name', 'url' => array('account/find'), 'options' => array( 'allowCreation' => true, 'preventDuplicates' => true, 'theme' => 'facebook', 'prePopulate' => CJSON::encode($result), ) )); ?>
Re: Using Prepopulate option
The plugin options are converted to JSON by the widget, so you have to pass a PHP array to 'prePopulate', e.g.:
'prePopulate' => $result,
Please note that the widget is prepoluating automatically with the value of the attribute on intialization if it is not empty, which would override your value. If you want to pre-populate with this value only if the attribute is empty, then your code will work as expected, else, if you want to always pre-populate with this value, then you will have to set the model's attribute to the desired value before running the widget, e.g.:
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>" }' ) )); ?>
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.
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).
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).
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
Re: Some fixes
Thanks for the fix, seems the correct way to do it, I will include it in the next release.
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?
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:
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 :)
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...
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!
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...
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!
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
Change width
I like this extention what I looking for, Thanks to contribute but Im still confuse how to change textfield width ?
Re: Change width
Answer in forum thread: http://www.yiiframework.com/forum/index.php/topic/30070-extension-tokeninput
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 “<li><p>” + item.name + “</p></li>” }', '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; }
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
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.
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.
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.
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); }
If you have any questions, please ask in the forum instead.
Signup or Login in order to comment.