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
};
Related
I am working on new angular 6 application and i am new to angular 6.
I have to implement the new function like.i am having one html table with multiple column some of them have checkbox along with id in hiddenfield. Now i have one button which is outside html table.
Now on the hutton click i have to get selected rows of one checkbox column. I have tried to search same on google but not getting any good solution for this.
Can anyone help to achive these functionality.
Thanks in advance.
Omkar
you can do this stuff without the need of button by following :
checkbox :
<input type='checkbox' ng-repeat="fruit in fruits"
ng-checked="checkedFruits.indexOf(fruit) != -1" ng-click="toggleCheck(fruit)">
the function get called :
function SomeCtrl ($scope) {
$scope.fruits = ["apple, orange, pear, naartjie"];
$scope.checkedFruits = [];
$scope.toggleCheck = function (fruit) {
if ($scope.checkedFruits.indexOf(fruit) === -1) {
$scope.checkedFruits.push(fruit);
} else {
$scope.checkedFruits.splice($scope.checkedFruits.indexOf(fruit), 1);
}
};
}
I am trying to use a pair of Kendo dropdown lists to filter a dataset in AngularJS. One uses a static ObservableArray datasource to filter the dataset by status; the other datasource is a set of distinct values from the "regionID" column of the dataset, also stored as an ObservableArray. When the status dropdown changes, the region dropdown is supposed to reload the list of regions from the newly-filtered data. This works, but the selected value ends up blanked out, even though the model that's supposed to represent the selected value still has the correct value, and the data is still correctly filtered by that same value. Opening the dropdown and then clicking off of it causes it to then display that value as it should.
Here is what the HTML looks like:
<select name="filterByRegion" style="width: 180px;" class="form-control" ng-model="selectedRegion" ng-change="onRegionFilterChange(selectedRegion)" kendo-drop-down-list k-options="regionFilterOptions"></select>
<select name="accountStatus" style="width: 180px;" class="form-control" ng-model="status" ng-change="onAccountStatusChange(status)" kendo-drop-down-list k-options="accountStatusOptions"></select>
Here are the two "onChange" methods from the controller:
$scope.onAccountStatusChange = function(status) {
$scope.status = status;
updateRegionFilterList();
};
$scope.onRegionFilterChange = function(selectedRegion) {
$scope.selectedRegion = selectedRegion;
};
And lastly, here is the updateRegionFilterList method:
var updateRegionFilterList = function () {
$scope.regions.empty();
angular.forEach($scope.accounts, function(account) {
if (account.reviewStatus === $scope.status) {
if ($scope.regions.indexOf(account.regionID) <= -1) {
$scope.regions.push(account.regionID);
}
}
});
$scope.regions.sort();
$scope.regions.unshift("Filter By Region:");
if ($scope.regions.indexOf(tempRegion) <= -1) {
$scope.selectedRegion = "Filter By Region";
}
};
I have tried many ways of working around this, including trying to preserve and reload the selectedRegion value, splicing out unneeded regions instead of emptying the ObservableArray, etc., and I have checked everything in the debugger and all of the values are correct (no obvious race conditions either).
Any ideas?
I figured it out. I had mixed kendo and angular configurations with the dropdowns and was relying on an angular process to update the data, which wasn't updating the Kendo MVVM stuff properly. Switching it to pure Kendo did the trick.
there is one filter functionality in my demo I will explain my problem I have one table in which i use infinite scroll is implemented In other words when user moves to bottom it load more data.There is search input field in top .Using this I am able to filter item in table .but I don't know why it is not working
When you search "ubs" and "ing" first time .it works perfectly .But when you load more data other words when user scroll to bottom and load more data the again it try to filter "ubs" and "ing" it not give any result why ?
<label class="item item-input">
<img src="https://dl.dropboxusercontent.com/s/n2s5u9eifp3y2rz/search_icon.png?dl=0">
<input type="text" placeholder="Search" ng-model="query">
</label>
secondly Actually I am implementing infinite scroll so only 100 element display .can we search element from 2000 (which I am getting from service )and display data the search result ?
Update :
Here's a Plunker with everything working together. I have separated all of the pieces into individual JS files, as it was getting unruly:
Plunker
Search
The built in filter will only return results from the current view data that the ng-repeat is displaying. Because you're not loading all of the data into the view at once, you'll have to create your own search functionality.
In the demo, click the search icon to show the search box, then type your search value and press the ENTER key or click the search button to return the results.
Since you want to check whether the user pressed ENTER you have to pass both the event and the querystring to the function, so you can check for the enter keycode. The function should also run when someone clicks or taps the search button. I set ng-model="query" on the input, so query is the reference in the view. Therefore, you'll add ng-click="searchInvoices($event, query)" to your search button, and ng-keyup="searchInvoices($event, query)" to the input. And, finally, to make it easy to clear the input field, add a button that displays when the input is not empty with ng-show="query" and attach a click event with ng-click="query=null; resetGrid()".
Add the searchInvoices function to your controller. It will only run the search if either the query is empty (because you need to reset the view if the person uses the backspace key to empty the input) OR if the user pressed ENTER OR if the event was a click event in case the user clicks the search button. The inner if statement, prevents the search from running if the query is empty and just resets the view. If the query is not empty, against the total dataset and builds an array of matching results, which is used to update the view.
The last line sets the scroll position to the top of the scrollview container. This makes sure that the user sees the results without having to click somewhere in the scrollview container. Make sure you inject the $ionicScrollDelegate into your controller for this to work and set delegate-handle="invoicegrid" on your ion-scroll directive.
$scope.searchInvoices = function(evt, queryval) {
if (queryval.length === 0 || evt.keyCode === 13 || evt.type === 'click') {
if (queryval.length === 0) {
$scope.invoice_records = $scope.total_invoice_records;
} else {
var recordset = $scope.total_invoice_records;
results = [];
var recordsetLength = recordset.length;
var searchVal = queryval.toLowerCase();
var i, j;
for (i = 0; i < recordsetLength; i++) {
var record = recordset[i].columns;
for (j = 0; j < record.length; j++) {
var invoice = record[j].value.toLowerCase();
if (invoice.indexOf(searchVal) >= 0) {
results.push(recordset[i]);
}
}
}
$scope.invoice_records = results;
$ionicScrollDelegate.$getByHandle('invoicegrid').scrollTop();
}
}
};
Lastly, you need to modify the loadMore() function that is used by the infinite scroll directive, so that it doesn't try to load additional data when scrolling through the search results. To do this, you can just pass the query into loadMore on the directive like: on-infinite="loadMore(query)", then in your function, you can just run the broadcast event when the query exists. Also, removing the ngIf will ensure that the list remains dynamic.
$scope.loadMore = function(query) {
if (query || counter >= $scope.total_invoice_records.length) {
$scope.$broadcast('scroll.infiniteScrollComplete');
} else {
$scope.counter = $scope.counter + showitems;
$scope.$broadcast('scroll.infiniteScrollComplete');
}
};
You used filter in wrong way inside ng-repeat like ng-repeat="column in invoice_records | filter:query" instead of ng-repeat="column in invoice_records | query"
<div class="row" ng-repeat="column in invoice_records |filter:query">
<div class="col col-center brd collapse-sm" ng-repeat="field in column.columns" ng-show="data[$index].checked && data[$index].fieldNameOrPath===field.fieldNameOrPath">{{field.value}}</div>
<div class="col col-10 text-center brd collapse-sm"></div>
</div>
Demo Plunkr
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
I am implementing typeahead using AngularUI-Bootstrap. I need to show the results grouped based on some values coming from the database. Here's a sample scenario
There are some users in the database, each user has a "Department". One user name can be available in multiple departments.
The end-user types in the names to search users from the database and retrieves the list in the typeahead list. Since one user name can belong to multiple departments, the requirement is to show the user names grouped by different departments. Something like this:
Then the user can select the desired user name and proceed.
As per the Typeahead documentation present here, I don't see any option to cater to my requirement.
I have tried the this workaround: Whenever the typeahead array is getting formed, I appended the user department to the array element:
$scope.fetchUsers = function(val) {
console.log("Entered fetchUsers function");
return $http.get("http://localhost:8080/TestWeb/users", {
params : {
username : val
}
}).then(function(res) {
console.log("Response:",res);
var users = [];
angular.forEach(res.data, function(item) {
users.push(item.UserName + " - " + item.UserDepartment);
});
console.log("users=",users);
return users;
});
};
This way, at least the end user sees the department. But when I select the record, the selected value is the full content of the array element. Below is sample screenshot to elaborate:
HTML
Users from local service
<pre>Model: {{userList | json}}</pre>
<input type="text" ng-model="userList" placeholder="Users loaded from local database"
typeahead="username for username in fetchUsers($viewValue)"
typeahead-loading="loadingUsers" class="form-control">
<i ng-show="loadingUsers" class="glyphicon glyphicon-refresh"></i>
User types in the string
User selects one record
I want to avoid the department (in this case, string - Desc 4 ) when user selects a record.
Is there any way I can achieve this grouping without any workaround? Or is there any way I can enhance my workaround?
I used to have a similar requirement and here is how I did it that time.
Example Plunker: http://plnkr.co/edit/zujdouvB4bz7tFX8HaNu?p=preview
The trick is to set the typeahead-template-url to a custom item template:
<input type="text" class="form-control" placeholder="Users loaded from local database"
ng-model="selectedUser"
typeahead="user as user.name for user in getUsers($viewValue)"
typeahead-template-url="typeahead-item.html" />
The item template, this represent each item in a dropdown:
<div class="typeahead-group-header" ng-if="match.model.firstInGroup">Desc {{match.model.group}}</div>
<a>
<span ng-bind-html="match.label | typeaheadHighlight:query"></span>
</a>
As you can see, there is an ng-if to show a group header if that item has a property firstInGroup set to true.
The firstInGroup properties are populated like this using lodashjs:
$scope.getUsers = function (search) {
var filtered = filterFilter(users, search);
var results = _(filtered)
.groupBy('group')
.map(function (g) {
g[0].firstInGroup = true; // the first item in each group
return g;
})
.flatten()
.value();
return results;
}
Hope this fit to your requirement too.
please see here http://plnkr.co/edit/DmoEWzAUHGEXuHILLPBp?p=preview
instead of creating new objects here:
angular.forEach(res.data, function(item) {
users.push(item.UserName + " - " + item.UserDepartment);
});
use create template :
<script type="text/ng-template" id="customTemplate.html">
<a> {{ match.model.name}} - department : {{match.model.dept}}</a>
</script>
and use it in your Typeahead directive
<input type="text" ng-model="selected"
typeahead="user.name as user for user in users | filter:$viewValue | limitTo:8" class="form-control"
typeahead-template-url="customTemplate.html">