Build a list using Bootstrap ui-typeahead - angularjs

I'm trying to build a tags input field exactly like the one on this site. The user can start typing tags, the ui-typeahead returns a list from which the user selects a single result, adding it to the list of tags already in the input field.
I started by simplifying the problem to a string concatenation, instead of a list. I can't even get that to work. This is where I've got to, but it doesn't concatenate with the existing value of the field, it replaces it:
<input class="form-control" type="text" ng-model="program.Demographs"
typeahead="d for d in program.providers.Demographs"
typeahead-min-length='3' typeahead-items='10'
typeahead-editable="false"
typeahead-on-select="addItem(program.Demographs)">
Here is the function that should concatenate the string:
$scope.addItem = function (item) {
$scope.program.Demographs = $scope.program.Demographs + item;
};
Can anyone offer any hints or advice?

Try this instead of concatenating;
$scope.program.Demographs = [];
$scope.addItem = function (item) {
$scope.program.Demographs = $scope.program.Demographs.push(item);
};

I thought I'd answer this myself after figuring out that it is not possible to add items to the existing selection in the typeahead input field. Instead, you have to add to a different string or array, not the one bound to the typeahead. Here is an example:
$scope.addDemograph = function (item) {
if ($scope.program.Demographs.indexOf(item) == -1) {
$scope.program.Demographs.push(item);
}
$scope.demographs_input = [];
};
<input class="form-control" type="text" placeholder="Demographs"
ng-model="demographs_input"
typeahead="d for d in program.providers.Demographs"
typeahead-on-select="addDemograph(demographs_input)"> // adds input val to list
<ul class="list-inline">
<li ng-repeat="d in program.Demographs"></li> <!--this list is added to automagically-->
</ul>

Related

AngularJs Auto Complete Search

So this works with static data, but when I push data with a $http this autocomplete does not work. The data pushes to the empty array of airport_list but something is happening when I try to use airport_list in for the autocomplete. Not sure what is is. I can only find answers which pertain to static data.
This is updated per everyones help.
Here is the controller
app.controller('selectCtrl', function($scope, $http) {
$scope.airport_list = null;
$http({
url: 'someUrl.com',
method: 'GET'
})
.then((response) => {
angular.forEach(response.data.airports, function(value, key) {
$scope.airport_list = response.data.airports;
})
$scope.airports = $scope.airport_list;
});
$scope.selectAirport = function(string) {
$scope.airport = string;
$scope.hidelist = true;
};
})
Here is the template
<div class="control">
<div>
<input
type="text"
name="airport"
id="airport"
ng-model="airport"
ng-change="searchFor(airport)"
placeholder="From..."
/>
<div class="airport-container-dropdown" ng-hide="hidelist">
<div
class="airport-list"
ng-repeat="airport in airports"
ng-click="selectAirport(airport)"
>
{{ airport.name }}
</div>
</div>
</div>
</div>
I really would like to do this without using bootstrap typeahead.
Thank you for looking at this.
I have made changes as recommended by below answers and the $http request is feeding into the autocomplete as a whole list but searching by name does not work and clicking on name sets [object, object]
this would be the code which is specific to that functionality.
$scope.searchFor = function(string) {
$scope.hidelist = false;
const output = [];
angular.forEach($scope.airport_list, function(airport) {
if (airport[0].toLowerCase().indexOf(string.toLowerCase(airport)) >=
0) {
output.push(airport);
}
});
$scope.airports = output;
};
$scope.selectAirport = function(string) {
$scope.airport = string;
$scope.hidelist = true;
};
Try this:
$scope.airport_list = response.data.airports;
What I am seeing is that you have an array: $scope.airport_list = [];
When you make your http request, you push what I would understand to be an array of airports into that array. So you end up with your airport array from the backend at the first position of $scope.airport_list, vs. $scope.airport_list being the actual list.
For your search method, you should change the following:
In your HTML:
ng-change="searchFor(airport.name)"
In your JS:
I've renamed your function and changed the input variable to be more clear. You were passing in a full airport, but treating it as a string. You need to compare your provided airport name to that of the airports in the array. So you iterate over the array, and compare each element's name property to what you pass in.
$scope.searchFor = function(airportName) {
$scope.hidelist = false;
const output = [];
angular.forEach($scope.airport_list, function(airport) {
if (airport.name.toLowerCase() === airportName) {
output.push(airport);
}
});
$scope.airports = output;
console.log($scope.airports);
};
I have provided minimal changes to your code to implement this, however I suggest you look at this SO post to filter drop down data more appropriately.
Angularjs Filter data with dropdown
If you want to simply filter out what is displayed in the UI, you can try this in your HTML template. It will provide a text field where you supply a partial of the airport name. If at least one character is entered in that box, the list will display on the page, with the appropriate filtering applied. This will avoid having to call functions on change, having a separate array, etc.
<input type="text" name="airport" id="airport" ng-model="airportSearch.name" placeholder="From..." />
<div class="airport-container-dropdown" ng-hide="!airportSearch.name">
<div class="airport-list"
ng-repeat="airport in airport_list | filter:airportSearch"
ng-click="selectAirport(airport)">
{{ airport.name }}
</div>
</div>

AngularJS search multiple keywords using one field

I am new to AngularJS. I have 3 input fields to enter up to 3 keywords for a single post:
<input type="text" ng-model="Keyword1"></input>
<input type="text" ng-model="Keyword2"></input>
<input type="text" ng-model="Keyword3"></input>
I am displaying the posts using ng-repeat:
ng-repeat="post in posts | filter: searchKeyword"
...
and searching by:
Search Keyword: <input ng-model="searchKeyword.Keyword1">
As you can see, this currently only searches the first keyword of every post. How can I have only one search field that searches all three keywords of a post? I know I cannot just do
<input ng-model="searchKeyword.Keyword1.Keyword2.Keyword3">
Thanks!
Not sure if the intention is to return all posts that match at least one keyword or all posts that match all keywords. Here is how you could create a custom filter for matching at least one word:
<div ng-controller="theController">
<input ng-model="inputText"/>
<!-- Use a custom filter to determine which posts should be visible -->
<ul>
<li ng-repeat="post in posts | filter:myFilter">{{post}}</li>
</ul>
</div>
angular.module('theApp', [])
.controller('theController', ['$scope', function($scope) {
$scope.inputText = 'test';
$scope.posts = [
'test test2;lksdf asdf one asdf poo',
'arm test test2 asdf',
'test head arm chest'
];
$scope.myFilter = function(post) {
// default to no match
var isMatch = false;
if ($scope.inputText) {
// split the input by space
var parts = $scope.inputText.split(' ');
// iterate each of the words that was entered
parts.forEach(function(part) {
// if the word is found in the post, a set the flag to return it.
if (new RegExp(part).test(post)) {
isMatch = true;
}
});
} else {
// if nothing is entered, return all posts
isMatch = true;
}
return isMatch;
};
}]);
And here is the plunkr: http://plnkr.co/edit/d17dZDrlhY2KIfWCbKyZ?p=preview
NOTE: this solution does not limit the keywords to 3. Any number of keywords can be entered, separate by the space character. If nothing is entered (or just spaces), everything is returned.
Here is what i will do
write a custom filter (https://docs.angularjs.org/guide/filter)
in filter split the input by space (which will give you separate words)
search all words in input one by one

Repeat an item in ng-repeat

I have an array of objects whereby there is a need to allow the user to add an item that is a duplicate of another. It is triggered when they increase the quantity where a property called 'HasPers' is true.
The HTML markup looks like:
<td width="10%"><input type="number" value="{{productdetails.Quantity}}" ng-model="productdetails.Quantity" min="1" ng-change="change(productdetails)" /></td>
and the function is:
$scope.change = function (item) {
if (item.HasPers) {
item.Quantity = 1;
$scope.items.push(item);
}
};
Initially this had a problem in the repeater wherby it knew it was a duplicat object but this was solved by adding 'track by $index' in the repeater
<tr ng-repeat="productdetails in items track by $index">
http://mutablethought.com/2013/04/25/angular-js-ng-repeat-no-longer-allowing-duplicates/
but the item is clearly still associated with the original since when I change some properties on the original one they change on the duplicate one too. I really want to avoid typing out all of the properties again as in
How to add new key in existing array in angular js?
is there any way to create a new item that is identical to the one being passed in but is a different object?
try
$scope.change = function (item) {
if (item.HasPers) {
var copyItem=angular.copy(item);
copyItem.Quantity = 1;
$scope.items.push(copyItem);
}
};
Try by using angular.copy
$scope.change = function (item) {
if (item.HasPers) {
var newItem = angular.copy(item);
newItem.Quantity = 1;
$scope.items.push(newItem);
}
};

Angular-UI typeahead show on certain character

I would like to use Angular-UI typeahead directive to do something like Twitter's Tweet composer: show it only when the user inputs a certain character, like # or {, and when a match is selected, append only the selected value, not replace the entire model.
Is it possible with the current Angular-UI implementation?
This is what I achieved using a custom directive:
http://plnkr.co/edit/9eEq6fOZgVWlhBqUXpV5?p=preview
All you need to do is:
<input type="text" ng-model="model"
typeahead-on="{" typeahead-append="}"
typeahead="suggestion for suggestion in ['second', 'minute', 'hour', 'day','week', 'month', 'year']" />
I don't think this is available out the box but the typeahead API allows custom get and format functions to be specified.
Here is a Plunker loosely based on the async example in the angular-ui documentation. My version only kicks in when an # symbol is present in the input value. A custom search is then carried out using the substring after the # sign.
HTML:
<input type="text" ng-model="selected" typeahead="address for address in getLocation($viewValue)" typeahead-input-formatter="formatResult($model)" class="form-control">
Controller:
var prefix = "";
$scope.getLocation = function(val) {
var pos = val.indexOf("#");
if(pos <=0 || pos == val.length-1) {
return [];
}
prefix = val.substr(0,pos); //cache the prefix
var search = val.substr(pos+1); //get the search string
//filter the results
return $filter('filter')(states, search)
};
$scope.formatResult = function(model) {
if(!model) {
return prefix;
}
return prefix + "#"+model;
}
UPDATE
Updated plunk which allows multiple tokens. You can use whatever token matching scheme you want here. This is just an example:
http://plnkr.co/edit/RjSZ2wgI1POtNfbQ6tvy?p=preview

Typeahead Value in Bootstrap UI

I'm working on an app using AngularJS and Bootstrap UI. I've been fumbling my way through using the Typeahead control in Bootstrap UI.
Here's my Plunker
My challenge is I want the user to have the option of choosing an item, but not required to do so. For instance, right now, if you type Test in the text field and press "Enter", Test will be replaced with Alpha. However, I really want to use Test. The only time I want the text to be replaced is when someone chooses the item from the drop down list. My markup looks like the following:
<input type="text" class="form-control" placeholder="Search..."
ng-model="query"
typeahead="result as result.name for result in getResults($viewValue)"
typeahead-template-url="result.html" />
How do I give the user the option of choosing an item, but allow them to still enter their own text?
The issue is that both Enter and Tab confirm the selection of the currently highlighted item and Typeahead automatically selects an item as soon as you start to type.
If you want, you can click off the control to lose focus or hit Esc to exit out of typeahead, but those might be difficult to communicate to your users.
There's an open request in Bootstrap Ui to not auto select / highlight the first item
One solution is to populate the first item with the contents of the query thus far, so tabbing or entering will only confirm selection of the current query:
JavaScript:
angular.module('plunker', ['ui.bootstrap'])
.filter('concat', function() {
return function(input, viewValue) {
if (input && input.length) {
if (input.indexOf(viewValue) === -1) {
input.unshift(viewValue);
}
return input;
} else {
return [];
}};})
HTML:
<input type="text"
ng-model="selected"
typeahead="state for state in states | filter:$viewValue | limitTo:8 | concat:$viewValue"
class="form-control">
Demo in Plunker
I came across this same situation and found no good answers so I implemented it myself in ui-bootstrap Here is the relevant answer. This is probably not the best route to take, but it does get the job done. It makes the first result in the typeahead to be what you're currently typing, so if you tab or enter off of it, it's selected -- you must arrow-down or select another option to get it.
Here is the modified ui-bootstrap-tpls.js file
I added a mustMouseDownToMatch property/attribute to the directive, like:
<input type="text" ng-model="selected" typeahead="item for item in typeaheadOptions | filter:$viewValue" typeahead-arrow-down-to-match="true">
And the javascript:
var mustArrowDownToMatch = originalScope.$eval(attrs.typeaheadArrowDownToMatch) ? originalScope.$eval(attrs.typeaheadArrowDownToMatch) : false;
I also added this function which will put the current text into the first item of the typeahead list, and make it the selected item:
var setFirstResultToViewValue = function (inputValue) {
scope.matches.splice(0, 0, {
id: 0,
label: inputValue,
model: inputValue
});
}
And that is called in the getMatchesAsync call in the typeahead directive:
var getMatchesAsync = function(inputValue) {
// do stuff
$q.when(parserResult.source(originalScope, locals)).then(function(matches) {
// do stuff
if (matches.length > 0) {
// do stuff
}
if (mustArrowDownToMatch) {
setFirstResultToViewValue(inputValue);
scope.activeIdx = 0;
setTypeaheadPosition();
}
// do stuff
};

Resources