Pass md-on-demand to md-autocomplete - angularjs

I would like display in md-autocomplete large list (around 50 000 records).
Autocomplete directive uses mdVirtualRepeat which provide infinity scrolling. I couldn't find way to pass md-on-demand option. Maybe someone find way to do that.
I really appreciate any help you can provide
UPDATE
I forget to share the code. I haven't problem with code performance but when list is rendering app is not responsive. In my opinion problem is in virtual rendering which still try to render whole list instead of visible part.
PS. I know $scope is bad but I'm using this example in angular-formly.
JS
$scope.to.options = [];
$scope.ctrl = {
selectedItem: null,
isDisabled: $scope.to.disabled,
noCache: $scope.to.noCache,
placeholder: $scope.to.placeholder || 'Wybierz element',
minLength: $scope.to.minLength || 0,
querySearch: querySearch,
searchTextChange: searchTextChange,
selectedItemChange: selectedItemChange,
delay: $scope.to.delay || 350,
options: []
};
if ($scope.to.dictId) {
dictionariesRepository.get($scope.to.dictId).then(function (res) {
$scope.ctrl.options = createOnDemandObject(res.Data.map(function (elem) {
return { value: elem[$scope.to.FieldVal], name: getLabel($scope.to.FieldFormula, elem) };
}));
var val;
if ((val = getValue())) {
var selected = $scope.ctrl.options.filter(function (elem) {
return elem.value == val;
})[0];
if (selected) {
$scope.ctrl.selectedItem = selected;
}
}
});
}
function createOnDemandObject(list) {
return {
list: list,
getLength: function () {
return this.list.length
},
getItemAtIndex: function (index) {
return this.list[index];
}
}
}
function searchTextChange(text) {
//$log.info('Text changed to ' + text);
}
function selectedItemChange(item) {
var getter = $parse($scope.options.key);
var setter = getter.assign;
setter($scope.model, item[$scope.to.FieldVal]);
}
function querySearch(query) {
var options = $scope.ctrl.options;
return query ? options.filter(createFilterFor(query)) : options;
}
function createFilterFor(query) {
var lowercaseQuery = angular.lowercase(query);
return function filterFn(elem) {
return (elem.name.indexOf(lowercaseQuery) === 0);
};
}
HTML
<md-autocomplete ng-disabled="ctrl.isDisabled" md-no-cache="ctrl.noCache" md-selected-item="ctrl.selectedItem" md-search-text-change="ctrl.searchTextChange(ctrl.searchText)"
md-delay="ctrl.delay"
md-search-text="ctrl.searchText" md-selected-item-change="ctrl.selectedItemChange(item)"
md-items="item in ctrl.querySearch(ctrl.searchText)"
md-on-demand
md-item-text="item.name" md-min-length="ctrl.minLength" placeholder="{{ctrl.placeholder}}">
<md-item-template>
<span md-highlight-text="ctrl.searchText" md-highlight-flags="^i">{{item.name}}</span>
</md-item-template>
<md-not-found>
Nie znaleziono pasującego wyniku dla "{{ctrl.searchText}}".
</md-not-found>
</md-autocomplete>

Related

Angular Materials md-select and trackBy allowing options to be selected

I'm trying to customise this Angular Material example code (https://material.angularjs.org/latest/api/directive/mdSelect) to my needs.
I have three groups of select options. If an option is selected in a group, it should unselect all options in other groups (but leave other options in own group as they are).
In my code I have managed to get the logic working right (as you will see from the console.log outputs at the bottom), but the actual select options do not interact with user input.
My JSFiddle: https://jsfiddle.net/e2LLLxnb/8/
My JS code:
var myModule = angular.module('BlankApp', ['ngMaterial']);
myModule.controller("FilterCtrl", function($scope, $element) {
$scope.categories = ["Any", "Target Category", "Option 1", "Option 2", "Option 3", "Option 4"];
$scope.mustCatSelected;
$scope.categoryObj = {};
// build the list of options with values and groups - create equivalent of $scope.data for <md-option ng-repeat="item in categoryObj.data.items">
var finGroup = [];
$scope.categories.forEach(function(value,key){
if(key>1){
finGroup.push(key);
};
});
$scope.categoryObj.data = {items: [], groups: [{
group: [0]
}, {
group: [1]
}, {
group: finGroup
}]};
$scope.categories.forEach(function(value,key){
$scope.categoryObj.data.items.push({name: value,
value: false,
id: (key + 1)});
});
$scope.clickOn = function(item, index) {
if(item.value == false){item.value = item.name;}
else {item.value = false;}
if (item.value === false) {
} else {
var thisGroup = [];
angular.forEach($scope.categoryObj.data.groups, function(value, key) {
if (value.group.indexOf(index) !== -1) {
thisGroup = value.group;
}
});
angular.forEach($scope.categoryObj.data.items, function(value, key) {
if (thisGroup.indexOf(key) !== -1) {
return;
} else {
value.value = false;
}
});
$scope.mustCatSelected = $scope.categoryObj.data.items.filter(function(e){
return e.value != false;
});
console.log($scope.mustCatSelected);
console.log($scope.categoryObj.data.items);
}
}
//search-term header
$scope.searchTerm;
$scope.clearSearchTerm = function() {
$scope.searchTerm = '';
};
// The md-select directive eats keydown events for some quick select
// logic. Since we have a search input here, we don't need that logic.
$element.find('input').on('keydown', function(ev) {
ev.stopPropagation();
});
});
Solved (finally!): https://jsfiddle.net/hqck87t1/4/
var myModule = angular.module('BlankApp', ['ngMaterial']);
myModule.controller("FilterCtrl", function($scope, $element) {
$scope.categories = ["Any", "None", "Option 1", "Option 2", "Option 3", "Option 4"];
$scope.mustCatSelected = [];
$scope.categoryObj = {};
$scope.categoryObj.items = [];
$scope.categories.forEach(function(value,key){
var grp;
if (key < 2){grp = key;}
if (key >= 2){grp = 2;}
$scope.categoryObj.items.push({
name: value,
id: (key + 1),
group: grp});
});
//set default
$scope.mustCatSelected.push($scope.categoryObj.items[0]);
$scope.clickOn = clickOn;
function clickOn(newValue, oldValue, type) {
//console.log($scope.categoryObj.items);
//console.log(oldValue);
if(oldValue.length == 0) {
return false;
}
//create arrays of new and old option ids
oldValue = JSON.parse(oldValue);
var newIds = [];
var oldIds = [];
newValue.forEach(function(value,key){
newIds.push(value.id);
});
oldValue.forEach(function(value,key){
oldIds.push(value.id);
});
//define and set the clicked value
var clickedValue;
newIds.forEach(function(value, key){
if(oldIds.indexOf(value) == -1) {
clickedValue = value;
}
});
var clickedGroup;
newValue.forEach(function(value,key){
if(value.id == clickedValue){
clickedGroup = value.group;
}
});
//console.log([clickedValue, clickedGroup]);
//console.log([newIds, oldIds, clickedValue]);
if(type == 'mustCat'){
$scope.mustCatSelected = $scope.mustCatSelected.filter(function(e){
return e.group == clickedGroup;
});
}
}
//search term above select
$scope.searchTerm;
$scope.clearSearchTerm = function() {
$scope.searchTerm = '';
};
// The md-select directive eats keydown events for some quick select
// logic. Since we have a search input here, we don't need that logic.
$element.find('input').on('keydown', function(ev) {
ev.stopPropagation();
});
});
There key to the solution lies in two things:
Using ng-change instead of ng-click. The former is used to distinguish the state of ng-model inline vs the specified state of ng-model after the change event. Whereas ng-click is not reliable for this.
Write the ng-change function in the html like this:
ng-change="clickOn(mustCatSelected, '{{mustCatSelected}}')"
where mustCatSelected is the ng-model and '{{mustCatSelected}}' the inline state of ng-model before the change event.
Now we have an multiple md-select with logic handling the selection of options / groups of options.

Angular: custom filter for recursive search in nested object

I have some problem with filtering nested object. In select I choose the filter parameter(object value) and in input I type some text that searches it in object key.
Tried to write custom filter with recursion for deep search but it doesn't work.
input is parameter for object in ng-repeat, param1 is for select's ng-model and param2 is for input's ng-model.
JS
.filter('personFilter', function($filter) {
return function(input, param1, param2) {
var output = {};
for (var prop in input) {
if (typeof input[prop] == 'object' || prop != param1 && input[prop] != param2) {
$filter('personFilter')(input[prop]);
} else {
output[key] = input[key];
}
}
return output;
}
Here's the plunker: http://plnkr.co/edit/83lPNRWFy6wa9U2FkMfH?p=preview
I hope someone give me some advice
sorry, just dirty solution, have no time to refactor. Mb it save time to you
.filter('personFilter', function($filter) {
return function(input, recursive, search) {
return input.filter(filterFn);
function filterFn(obj){
var val, res;
for (var prop in obj) {
val = obj[prop];
if (typeof val == 'object' && recursive) {
recursive = false;
res = res || val.filter(filterFn).length;
recursive = true;
} else if(!recursive){
res = res || val == search;
}
}
console.log(res, obj, recursive, search);
return res;
}
}
with following markup
<body ng-controller="appCtrl">
<select ng-model="selectParameter" ng-options="item.value as item.key for item in parameter track by item.value">
</select>
<input ng-model="query" />
<div ng-repeat="person in object.node1.node2.persons | personFilter:selectParameter:query track by $index">
<!-- -->
<p>Person: {{person.name}}, Children: <span ng-repeat="child in person.children track by $index">{{child.name}}, </span></p>
</div>
</body>
and controller init like:
$scope.parameter = [
{value: false, key: 'Person Name'},
{value: true, key: 'Child Name'}
];
$scope.selectParameter = true;

directive that controlls upvotes and downvotes

I'm developing a upvote/downvote controlling system for a dynamic bunch of cards.
I can controll if I click to the img the checked = true and checked = false value but The problem I've found and because my code doesn't work as expected is I can't update my value in the ng-model, so the next time the function is called I receive the same value. As well, I can't update and show correctly the new value. As well, the only card that works is the first one (it's not dynamic)
All in which I've been working can be found in this plunk.
As a very new angular guy, I tried to investigate and read as much as possible but I'm not even 100% sure this is the right way, so I'm totally open for other ways of doing this, attending to performance and clean code. Here bellow I paste what I've actually achieved:
index.html
<card-interactions class="card-element" ng-repeat="element in myIndex.feed">
<label class="rating-upvote">
<input type="checkbox" ng-click="rate('upvote', u[element.id])" ng-true-value="1" ng-false-value="0" ng-model="u[element.id]" ng-init="element.user_actions.voted === 'upvoted' ? u[element.id] = 1 : u[element.id] = 0" />
<ng-include src="'upvote.svg'"></ng-include>
{{ element.upvotes + u[1] }}
</label>
<label class="rating-downvote">
<input type="checkbox" ng-click="rate('downvote', d[element.id])" ng-model="d[element.id]" ng-true-value="1" ng-false-value="0" ng-init="element.user_actions.voted === 'downvoted' ? d[element.id] = 1 : d[element.id] = 0" />
<ng-include src="'downvote.svg'"></ng-include>
{{ element.downvotes + d[1] }}
</label>
<hr>
</card-interactions>
index.js
'use strict';
var index = angular.module('app.index', ['index.factory']);
index.controller('indexController', ['indexFactory', function (indexFactory) {
var data = this;
data.functions = {
getFeed: function () {
indexFactory.getJSON(function (response) {
data.feed = response.index;
});
}
};
this.functions.getFeed();
}
]);
index.directive('cardInteractions', [ function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
scope.rate = function(action, value) {
var check_up = element.find('input')[0];
var check_down = element.find('input')[1];
if (action === 'upvote') {
if (check_down.checked === true) {
check_down.checked = false;
}
} else {
if (action === 'downvote') {
if (check_up.checked === true) {
check_up.checked = false;
}
}
}
}
}
};
}]);
Hope you guys can help me with this.
Every contribution is appreciated.
Thanks in advice.
I have updated your directive in this plunker,
https://plnkr.co/edit/HvcBv8XavnDZTlTeFntv?p=preview
index.directive('cardInteractions', [ function () {
return {
restrict: 'E',
scope: {
vote: '='
},
templateUrl: 'vote.html',
link: function (scope, element, attrs) {
scope.vote.upValue = scope.vote.downValue = 0;
if(scope.vote.user_actions.voted) {
switch(scope.vote.user_actions.voted) {
case 'upvoted':
scope.vote.upValue = 1;
break;
case 'downvoted':
scope.vote.downValue = 1;
break;
}
}
scope.upVote = function() {
if(scope.vote.downValue === 1) {
scope.vote.downValue = 0;
scope.vote.downvotes--;
}
if(scope.vote.upValue === 1) {
scope.vote.upvotes++;
} else {
scope.vote.upvotes--;
}
};
scope.downVote = function() {
if(scope.vote.upValue === 1) {
scope.vote.upValue = 0;
scope.vote.upvotes--;
}
if(scope.vote.downValue === 1) {
scope.vote.downvotes++;
} else {
scope.vote.downvotes--;
}
};
}
};

Angular filter multiple columns with an array of filters

I am trying to be able to filter multiple columns on multiple values. So far I can filter multiple columns on 1 single value:
myApp.controller('MyCtrl', ['$scope', '$http', function($scope, $http) {
$scope.empList = [];
$http.get('getAllOnline.php')
.success(function(data) {
$scope.empList = data;
});
$scope.column1List = [];
$http.get('getAllSomething.php', {
params: {
wuk: "column1"
}
})
.success(function(data) {
$scope.column1 = data;
});
$scope.column2List = [];
$http.get('getAllSomething.php', {
params: {
wuk: "column2"
}
})
.success(function(data) {
$scope.column2 = data;
});
$scope.column3List = [];
$http.get('getAllSomething.php', {
params: {
wuk: "column3"
}
})
.success(function(data) {
$scope.column3 = data;
});
$scope.setColumn1Value = function(val) {
if ($scope.zelectedColumn1 == val) {
$scope.zelectedColumn1 = undefined;
} else {
$scope.zelectedColumn1 = val;
}
}
$scope.setColumn2Value = function(val) {
if ($scope.zelectedColumn2 == val) {
$scope.zelectedColumn2 = undefined;
} else {
$scope.zelectedColumn2 = val;
}
}
$scope.setColumn3Value = function(val) {
if ($scope.zelectedColumn3 == val) {
$scope.zelectedColumn3 = undefined;
} else {
$scope.zelectedColumn3 = val;
}
}
Then i use this to set my single value filters:
<ul id="Column1" class="collapse">
<li ng-repeat="emp in empList | unique:'Column1'">
<a ng-click="setColumn1Value(emp.Column1);" ng-class="{selected: emp.Column1 === zelectedColumn1}">
<div ng-repeat="someone in Column1List | filter:{Column1_id:emp.Column1}">
{{someone.value}}
</div>
</a>
</li>
</ul>
This works perfect! But now I want to be able to filter on multiple values in 1 column. So I changed my setter functions to:
$scope.zelectedColumn1=[];
$scope.setColumn1Value = function(val) {
var found = jQuery.inArray(val, $scope.zelectedColumn1);
if (found >= 0) {
// Element was found, remove it.
$scope.zelectedColumn1.splice(found, 1);
} else {
// Element was not found, add it.
$scope.zelectedColumn1.push(val);
}
console.log($scope.zelectedColumn1);
}
So now I add or remove indexes to an array instead of storing a single value. This also works, but how do I filter my columns on the contents of an array instead of on a single value as I do now:
<div class='row'>
<div class='col-lg-2 col-md-3 col-sm-6' ng-repeat="emp in empList | filter:{column1:zelectedColumn1,column2:zelectedColumn2,column3:zelectedColumn3} as res">
{{emp.column1}}
{{emp.column2}} <br>
{{emp.column3}} <br>
</div>
</div>
I have been struggling with this al day and hope someone can help me out here!

Field update after autocompletion with angularJS

I'm quite new to AngularJS and struggling a bit to have some input fields updated after an autocompletion event using google maps.
The idea is that when the user inputs his city/zip code, I would update 3 fields which are themselves linked to an object.
So far, I managed to have a working code except that sometimes the fields are not updated immediately : I have to autocomplete twice so that the good value will appear in the fields.
I've tweaked an existing angular directive in order to get what I want but since this is new to me, I dont know if I'm using the correct approach.
Below is the JS directive I use :
angular.module( "ngVilleAutocomplete", [])
.directive('ngAutocomplete', function($parse) {
return {
scope: {
details: '=',
ngAutocomplete: '=',
options: '=',
data: '='
},
link: function(scope, element, attrs, model) {
//options for autocomplete
var opts
//convert options provided to opts
var initOpts = function() {
opts = {}
if (scope.options) {
if (scope.options.types) {
opts.types = []
opts.types.push(scope.options.types)
}
if (scope.options.bounds) {
opts.bounds = scope.options.bounds
}
if (scope.options.country) {
opts.componentRestrictions = {
country: scope.options.country
}
}
}
}
initOpts()
//create new autocomplete
//reinitializes on every change of the options provided
var newAutocomplete = function() {
scope.gPlace = new google.maps.places.Autocomplete(element[0], opts);
google.maps.event.addListener(scope.gPlace, 'place_changed', function() {
scope.$apply(function() {
scope.details = scope.gPlace.getPlace();
//console.log(scope.details)
var HasCP = false;
for (var i=0 ; i<scope.details.address_components.length ; i++){
for (var j=0 ; j<scope.details.address_components[i].types.length ; j++){
if (scope.details.address_components[i].types[j] == 'postal_code' && scope.data.CP != 'undefined'){
scope.data.CP = scope.details.address_components[i].long_name;
HasCP = true;
} else if (scope.details.address_components[i].types[j] == 'locality' && scope.data.Ville != 'undefined') {
scope.data.Ville = scope.details.address_components[i].long_name;
} else if (scope.details.address_components[i].types[j] == 'country' && scope.data.Pays != 'undefined') {
scope.data.Pays = scope.details.address_components[i].long_name;
}
}
}
if (!HasCP){
var latlng = {lat: scope.details.geometry.location.lat(), lng: scope.details.geometry.location.lng()};
var geocoder = new google.maps.Geocoder;
geocoder.geocode({'location': latlng}, function(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
for (var i=0 ; i<results[0].address_components.length ; i++){
for (var j=0 ; j<results[0].address_components[i].types.length ; j++){
if (results[0].address_components[i].types[j] == 'postal_code' && scope.data.CP != 'undefined'){
scope.data.CP = results[0].address_components[i].long_name;
console.log('pc trouvé :' + scope.data.CP);
}
}
}
}
});
}
//console.log(scope.data)
scope.ngAutocomplete = element.val();
});
})
}
newAutocomplete()
//watch options provided to directive
scope.watchOptions = function () {
return scope.options
};
scope.$watch(scope.watchOptions, function () {
initOpts()
newAutocomplete()
element[0].value = '';
scope.ngAutocomplete = element.val();
}, true);
}
};
});
The matching HTML code is below :
<div class="form-group">
<lable>Code postal : </label>
<input type="text" id="Autocomplete" class="form-control" ng-autocomplete="cities_autocomplete" details="cities_autocomplete_details" options="cities_autocomplete_options" data="client" placeholder="Code postal" ng-model="client.CP" />
</div>
<div class="form-group">
<lable>Ville : </label>
<input type="text" id="Autocomplete" class="form-control" ng-autocomplete="cities_autocomplete" details="cities_autocomplete_details" options="cities_autocomplete_options" data="client" placeholder="Ville" ng-model="client.Ville" />
</div>
<div class="form-group">
<lable>Pays : </label>
<input type="text" class="form-control" name="Pays" ng-model="client.Pays" placeholder="Pays" />
</div>
You'll see that I pass the "client" object directly to my directive which then updates this object. I expected angular to update the html page as soon as the values of the client object are updated but I will not always be the case :
If I search twice the same city, the values are not updated
If I search a city, Google wont send me a zip code so I have to do another request to the geocoding service and I get the zipcode in return but while my client.CP field is correctly updated, changes are not visible in the CP input field until I do another search.
Thanks in advance for any advice on what I'm doing wrong.

Resources