35

I am using a select box from ui-select. All is working fine, but I want to allow manually entered text and do not want to restrict the user from the values available in the list. If I type in text it filters my list correctly. But when I not click on an element and move on to the next field my text will get discarded.

Any ideas?

Thanks and regards, Alex

I did not want to show my code because I think it is not correct, but it was requested:

<ui-select ng-model="formData[field.id].selected" theme="bootstrap">
    <ui-select-match placeholder="{{ lists[field.id].placeholder }}">{{$select.selected.text}}</ui-select-match>
    <ui-select-choices repeat="item in lists[field.id].list | filter: $select.search">
        <div ng-bind-html="item.text | highlight: $select.search"></div>
    </ui-select-choices>
</ui-select>

The data is stored in formData[field.id].selected. field.id is the number of the current field to display (I am generating my form dynamically). Just assume that it stores an unique int value.

Edit 08.04.2015 My solution: I found out that it seems as if there is no equivalent to a C# combobox. So I went ahead and used two separate fields. It is not what I wanted, but it works for now:

<ui-select ng-model="formData[field.id].selected" theme="bootstrap">
    <ui-select-match placeholder="{{ lists[field.id].placeholder }}">{{$select.selected.text}}</ui-select-match>
    <ui-select-choices repeat="item in lists[field.id].list | filter: $select.search">
        <div ng-bind-html="item.text | highlight: $select.search"></div>
    </ui-select-choices>
</ui-select>
<?php echo __('Create a new element if value is not in list'); ?>
<div class="input-group">
    <span class="input-group-addon">
        <input type="checkbox" ng-model="disabled[field.id]">
    </span>
    <input type="text" value="" ng-disabled="!disabled[field.id]" class="form-control" ng-model="formData[field.id].newValue" />
</div>
AlexWerz
  • 739
  • 2
  • 9
  • 21
  • 2
    Well, I am not sure why I am being downvoted. It would be enough if someone could point me to some documentation. The ui-select github offers some examples, but none mit editable text. – AlexWerz Apr 07 '15 at 11:15
  • Can you share some code aswell ? – nalinc Apr 07 '15 at 11:36
  • I did not want to share code because I am pretty sure it is not correct. I'll add some if it helps. – AlexWerz Apr 07 '15 at 11:48
  • 1
    I needed this feature in select2 and scratched my head to make it accept free text but then I realised typeahead is made for this exact case. So if you do not need multiple selection typeahead is what you need. – roshan Nov 10 '16 at 17:43

5 Answers5

46

here is a solution:

HTML -

<ui-select ng-model="superhero.selected">
  <ui-select-match placeholder="Select or search a superhero ...">{{$select.selected}}</ui-select-match>
  <ui-select-choices repeat="hero in getSuperheroes($select.search) | filter: $select.search">
    <div ng-bind="hero"></div>
  </ui-select-choices>
</ui-select>

CONTROLLER -

$scope.getSuperheroes = function(search) {
 var newSupes = $scope.superheroes.slice();
  if (search && newSupes.indexOf(search) === -1) {
    newSupes.unshift(search);
  }
  return newSupes;
}

Here is the CodePen solution.

silverArc
  • 938
  • 1
  • 11
  • 19
13

I think I found a way to allow the user to create new entries. Use the "on-select" attribute to pass a function that takes $select as a parameter as below:

<ui-select ng-model="person.selected"
      theme="select2"
      on-select="peopleSel($select)"
      tagging
      reset-search-input="false"
      >

    <ui-select-match placeholder="Enter a name...">{{$select.selected.name}}</ui-select-match>
    <ui-select-choices repeat="sel in people | filter: {name: $select.search}">
    <div ng-bind-html="sel.name | highlight: $select.search"></div>
    </ui-select-choices>
  </ui-select>

Then create the function that adds a new entry when the clickTriggeredSelect variable is false:

$scope.peopleSel= function(sel) {
  if ( sel.search && ! sel.clickTriggeredSelect ) {
    if ( ! sel.selected || sel.selected.name != sel.search ) {
      //Search for an existing entry for the given name
      var newOne= personSearch( sel.search ); 
      if ( newOne === null ) {
        //Create a new entry since one does not exist
        newOne= { name: sel.search, email: 'none', country: 'unknown' };
        $scope.people.push( newOne );
      }
      //Make the found or created entry the selected one
      sel.selected= newOne;
    }
  }
  sel.search= ''; //optional clearing of search pattern
};

Note that personSearch definition is not provided here. Also this approach of testing the clickTriggeredSelect can be used to allow the user to unselect the field if a blank entry is the preference.

PVC

Paul Van Camp
  • 261
  • 3
  • 6
  • ui-select v0.19 introduced a new [`uis-open-close` directive](https://github.com/angular-ui/ui-select/wiki/uis-open-close). If you put the function in there instead of in `on-select`, entries will be added even on blur. –  Nov 04 '16 at 01:29
8

You could use the tagging attribute as explained in the documentation: https://github.com/angular-ui/ui-select/wiki/ui-select

<ui-select multiple tagging tagging-label="(custom 'new' label)" ng-model="multipleDemo.colors">
...
</ui-select>
rave
  • 1,022
  • 1
  • 12
  • 23
  • 2
    Thank you so much for pointing me to the wiki. I was searching for documentation or examples but totally missed the wiki. Tagging is not what I need though. Tagging only works when using `multiple`. I want a select which acts like a select and a text input in one item. If your text is already in the list you can click on it, if not your text will be stored. – AlexWerz Apr 07 '15 at 14:06
  • @AlexWerz : Have you got the solution ? If so, can you please share it. Thanks. – Mithun Sasidharan Jun 19 '15 at 04:21
  • No sorry. I used the solution I provided in the edit above. But there is no real equivalent to a C# Combobox. I switched some of my GUIs to angular.js which has some more options and possibilities. Maybe you should have a look at it too. – AlexWerz Jun 19 '15 at 08:25
  • 2
    As per official sample of `angular-ui` `tagging` does not works with single select. It only works with multiple Link to [Official Angular UI Github sample](https://github.com/angular-ui/ui-select/blob/master/docs/examples/demo-tagging.html) – M. Atif Riaz Jun 10 '19 at 11:34
4

I've forked the ui-select project to allow this functionality through the allow-free-text attribute

<ui-select allow-free-text="true" ng-model="ctrl.freeTextDemo.color2" theme="bootstrap" style="width: 800px;" title="Choose a color">
    <ui-select-match placeholder="Select color...">{{$select.selected}}</ui-select-match>
    <ui-select-choices repeat="color in ctrl.availableColors | filter: $select.search">
      <div ng-bind-html="color | highlight: $select.search"></div>
    </ui-select-choices>
</ui-select>

Here is the plnker

Until my pull request is accepted by the angular-ui team, you can get the ui-select build that includes my patch on my own repo

Anthony Hocquet
  • 348
  • 1
  • 3
  • 9
0

I use keyup to add a new option represents the text entered. With this approach, if user press enter, they can select what they have input so far (the default active item is the first item).

This supports both cases when the data list contains plain text or object (attribute value-prop is required).

Directive:

commonApp.directive("uiSelectFreeText", function () {
    return {
        restrict: "A",
        require: "uiSelect",
        link: function (scope, element, attrs, $select) {
            var searchInput = element.querySelectorAll("input.ui-select-search");

            if (searchInput.length !== 1) {
                console.log("Error! Input not found.");
                return;
            }

            searchInput.on("keyup", function () {
                var valueProp = attrs["valueProp"];
                var addNewObjOption = function () {
                    // add new item represent free text option
                    var newOption = { isFreeText: true };
                    newOption[valueProp] = $select.search;
                    $select.items.unshift(newOption);
                };

                if ($select.items.length > 0) {
                    var firstItem = $select.items[0];

                    if (valueProp) {
                        // items is list of Object
                        if (firstItem.isFreeText) {
                            firstItem[valueProp] = $select.search;
                        } else {
                            addNewObjOption();
                        }
                    } else {
                        // items is list of string
                        if (firstItem === $select.search) {
                            return;
                        } else {
                            $select.items.push($select.search);
                        }
                    }
                } else {
                    if (valueProp) {
                        addNewObjOption();
                    } else {
                        // items is list of string
                        $select.items.push($select.search);
                    }
                }
            });
        }
    };
});

HTML:

<ui-select class="ui-select-control"
           ui-select-free-text value-prop="Label"
           ng-model="keyword">
</ui-select>
Hp93
  • 1,349
  • 3
  • 14
  • 23