Filter either ONLY one property or all fields - angularjs

I'm creating a site in which I have objects that all have a 'tags' property, which is a list of strings.
I'm creating a search functionality that filters all elements in a list. If the user enters '#something here', then I want to ONLY match the user input to the tags of each property. If the user just enters a string in the search box, then I want to search all object properties.
I have a form defined like so:
<form class="navbar-form navbar-left" role="search">
<div class="form-group">
<input data-ng-model="$root.searchText" type="text" class="form-control" placeholder="#hashtag or just a string">
</div>
</form>
I know that the default filter can be used in the way I want it to if I define the data-ng-model with the field I want. So if I wanted to only search tags, I'd do data-ng-model='$root.searchText.tags', and user input will only match that. If I want to search all, then I'd do data-ng-model='$root.searchText.$'.
But how can I make the model switch based on whether or not a user types in a string with '#' at the beginning or not?
I've tried creating a custom filter, but that got confusing. There should be some kind of conditional statement that either sets the model to be $root.searchText.tags or $root.searchText.$, but that's more difficult that I thought. Does anyone know how to structure that conditional statement?

I have a solution for you, although it might not be the best workaround.
You watch the search filter and update the lists based on your logic:
1.filter by tags if searchText starts with '#'
2.fitler by properties values if searchText do not starts with '#'
$scope.$watch('model.search', function(search){
var tagFilter,results;
if(search.startsWith('#')) { //handle tag filter logic
tagFilters = search.split('#');
console.log(tagFilters);
results = _.filter($scope.model.lists, function(obj){
return _.intersection(obj.tags, tagFilters).length > 0;
});
} else { //handle property filter logic
results = _.filter($scope.model.lists, function (obj) {
return _.values(obj).some(function (el) {
return el.indexOf(search) > -1;
});
});
}
console.log(results);
$scope.model.displayItems = results;
}, true);
plnkr

Related

Angularjs filter object parameters

I just get stuck with my code when I am trying to make a filter for my ng repeat div element
I am trying to use one input
<input type="text" class="search_input" ng-model="search">
But in filter I would like to use that search parameter for username and user id.
<div ng-repeat="customer in userList | filter : {customer.username : search, customer.user_id : search}">
So it should show me te correct user with that username or ID what I put to input. How to make it? I am new in angular and I couldn't find for it till now some answers.
You can use a custom filter, take a look at this answer for further information:
AngularJS filter on multiple values of one field
I would comment this, but my rep is too low
create a function in your controller which will return you true/false depending upon your filter criteria :
$scope.fnFilter = function(customer){
return customer.username === $scope.search || customer.user_id === $scope.search;
};
use it in your html as :
<div ng-repeat="customer in userList | filter : fnFilter">

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

Post full form data to a service in Angular

I have a form that contains a lot of fields and I want to post all the form fields to a service using a post method. But I would like to send the whole form object and not to write one property by one. If I try to post the object that contains all my fields $scope.formData it also contains all the angular stuff inside like errors. What I need is a collection of field names and values. How can I achieve this with minimum coding?
Edit:
I ended up writing my own function:
function getAngularFormFields(form) {
var dictionary = { form: {} };
for (var key in form) {
if (form.hasOwnProperty(key) && !key.indexOf('$') == 0) {
dictionary.form[key] = form[key].$modelValue;
}
}
return dictionary;
}
Normally if you need to post a form you could just use the default method provided by your browser. This will send the form data, via POST, to your URL.
<form action="yourUrlHere" method="POST">
First name: <input type="text" name="fname">
Last name: <input type="text" name="lname">
<input type="submit" value="Submit">
</form>

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
};

Grouping results in ui-bootstrap typeahead [duplicate]

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">

Resources