I have an app that searches various tickets.
In the sidebar I have some filters so the user can filter down on their results(these include checkboxes and radio buttons like eBay/Amazon etc.).
So it looks similar to this in the sidebar:
How to Buy
Direct Debit (10)
Cash (2)
So the user knows from a glance that if they click on Direct Debit it will return 10 results. The problem I then have is that when the user clicks on Direct Debit, the radio buttons change to this...
How to Buy
Direct Debit (10)
Cash (0)
This would be correct if it were a checkbox, as click on Direct Debit AND Cash would return 0 results. However in this case (a radio button) the user should still see that there are 2 results if they click on Cash.
Here is my code...
HTML
<ul class="checkbox-list nga-fast nga-collapse" data-ng-show="ticketing.filterButtons.OperatorBtn">
<li data-ng-repeat="filter in ticketing.filterButtons.Operator | orderBy: filter" data-ng-if="filter != null">
<input id="operator{{$index}}" name="operator{{$index}}" type="radio" class="stylized" data-ng-model="ticketing.searchFilters.Operator" data-ng-change="ticketing.update()" value="{{filter}}" />
<label for="operator{{$index}}">{{filter}}
({{ (ticketing.filteredTickets | filter: {Operator: filter}).length}})
</label>
</li>
</ul>
JS
function update() {
var filtered = vm.all;
console.log(vm.searchFilters);
// For each filter in the search filters loop through and delete any that state false, this is so it doesn't explicitly match false and shows everything.
angular.forEach(vm.searchFilters, function(val, key) {
// if Key/Property contains 'Allow" and the value is true || if Key/Property doesn't contain 'Allow' and val is false (this is to make sure the oppposite/exclude filter values are deleted as the trues will be falses and vice versa)
if ((key.indexOf('Allow') !== -1 && val) || (val == false && key.indexOf('Allow') === -1)) {
// Delete the filter and value
delete vm.searchFilters[key];
}
});
// Filter results by the filters selected
filtered = $filter('filter')(filtered, vm.searchFilters);
// Sort results by selected option
vm.filteredTickets = $filter('orderBy')(filtered, vm.orderBy);
console.log(vm.filteredTickets);
console.log(vm.searchFilters);
vm.updateGrid();
}
If you require any more info or code then please let me know and i'll provide it.
This is a guess. There might be something go wrong in the below line
({{ (ticketing.filteredTickets | filter: { Operator:filter}).length}})
on Clicking of Direct Debit"; you might have updated "ticketing.filteredTickets" with only data of "Direct Debit" hence there is not data for "Cash"
Related
I created a nested category model. In order to select a category for a product, you see a dropdown with the main categories. When you select one, a new dropdown is added to the right with the child categories to the one you selected. This repeats until you reach the last level. When this happens $scope.product.category_id is set. I want to invalidate the whole set of fields when $scope.product.category_id is null.
I've been using angular-bootstrap-show-errors for validation and I tried to mix it with ui-utils to achieve this one, using a custom function: validate_category().
Here's the HTML I used:
<span ng-repeat="parent_id in category_dropdown_parents" show-errors="{ skipFormGroupCheck: true }">
<select class="form-control" name="category_id"
ng-init="selected_category[$index] = init_select($index);"
ng-model="selected_category[$index]"
ng-options="category.name for category in (categories | filter : { parent_id: parent_id } : true ) track by category.id "
ng-change="select_category(selected_category[$index], $index)"
ui-validate="'validate_category()'" // added this to work with ui-utils
>
</select>
<span ng-if="$index+1 != category_dropdown_parents.length">/</span>
</span>
And this is my simple validation function:
$scope.validate_category = function() {
return ( $scope.product.category_id !== null
&& $scope.product.category_id !== undefined);
}
But this is not working. Ideas?
EDIT: I just realized, that the problem with this is that the validation function on ui-validate is executed after the ng-change function, so it's never able to check the $scope.product.category_id update.
Your select is using
ng-model="selected_category[$index]"
but the validation function is using
$scope.product.category_id
Shouldn't it be using
ui-validate="'validate_category($index)'"
and
$scope.validate_category = function($index) {
return($scope.selected_category[$index] !== null
&& $scope.selected_category[$index] !== undefined);
}
This is not the ideal answer but it's the best I could get. Shame on me, this was too simple:
<select class="form-control" name="category_id"
ng-init="selected_category[$index] = init_select($index);"
ng-model="selected_category[$index]"
ng-options="category.name for category in (categories | filter : { parent_id: parent_id } : true ) track by category.id "
ng-change="select_category(selected_category[$index], $index)"
required // !!!
>
</select>
That's it, just added the required attribute. The problem with this is that since the I'm not validating product.category_id but just validating all the dropdowns to be not empty. I guess I
'll have to rely on the code on select_category().
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 currently have a form with two dynamic dropdowns (Location & Branch). When one of Location values is selected, Branch will automatically populate the corresponding branches with that location.
<select ng-model="formData.location"
ng-options="rg as rg.type for rg in region">
<option value="">Choose Location</option>
</select>
<select ng-model="formData.branches"
ng-options="c as c[formData.location.displayName] for c in formData.location.data | orderBy:'branch'">
<option value="">Choose Branch</option>
</select>
The Branch values are taken from this controller:
scope.metro = [
{"branch": "SM North EDSA", "alias": "northedsa"},
{"branch": "Trinoma", "alias": "trinoma"},
{"branch": "Robinsons Galleria", "alias": "robgalleria"},
// etc...
];
scope.region = [
{ type: 'Metro Manila', data:scope.metro, displayName:'branch', alias:'alias'},
{ type: 'Central Luzon', data:scope.central, displayName:'branch', alias:'alias'},
{ type: 'North Luzon', data:scope.north, displayName:'branch', alias:'alias'},
// etc...
];
Now, inside the form, on every option change in Branch, there will be a pre-generated code from the table in my database (assigned on each of its row), procured by ng-repeat like this:
<div ng-repeat="codes in response">
<span ng-if="((codes.branch == formData.branches.alias) && (codes.taken == 0))">
{{codes.code}}
</div>
My database table looks like this:
This works when it is left as it is (displaying 100 codes each iteration). But when I use a filter such as limitTo:1, I only get the first index of the row in the table of my database. What I need is to get the first element of the response array on every flip of Branch values.
For clearer explanation, this ng-repeat is done by having this function in my controller:
http.get("server/fetch.php").success(function(response){
scope.response = response;
// shuffleArray(scope.response);
}).error(function() {
scope.response = "error in fetching data";
});
I was told do this in a controller instead if I wanted to get the first element of each array, but I am not sure how to do that. I will post a plunker when I have the time. I just need this solve right now as I have deadlines to meet before the day ends.
I hope this question is all clear even without a plunker. Thanks in advance!
You could pipe multiple filters together, the first to filter based on branch, and next to limit the number of returned items:
<div ng-repeat="codes in response | filter: {branch: formData.branches.alias, taken: 0} | limitTo: 1">
{{codes.code}}
</div>
In this case it was possible to use the built-in filter filter since it allows specifying a predicate for multiple properties to match against with an AND condition. For any more complex condition, I recommend using a predicate function:
$scope.filterBy = function(a, b){
return function(item){
return item.foo === a || item.bar === b;
}
}
and use it like so:
<div ng-repeat="item in items | filter: filterBy('foo', 'bar')">
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
};
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">