I have an SPA that uses select lists to filter down to a location by region, state/province/EU country, city.
I've got filtering working somewhat, but for my location lat/lng values (which will ultimately be powering a Google Maps widget), it's not working right. I'm not getting filtering beyond Region, and even on Region, I'm getting hits of locations that don't belong to the Region selected.
var app = angular.module('plunker', ['angular.filter']);
app.controller('MainCtrl', function($scope, $anchorScroll, $location, $http) {
$scope.cart = [];
$scope.addToCart = function(index) {
$scope.cart.push(index);
$scope.cartCount = $scope.cart.length;
}
$scope.activeRow = function(index) {
$scope.selectedRow = index;
$location.hash();
$anchorScroll('anchor-' + index);
}
$scope.gotoAnchor = function(x) {
var newHash = 'anchor' + x;
}
// GET data
$scope.dataObject = data.List;
$scope.locationObject = data.Locations;
}).filter('byCountry', function() {
return function(items, location) {
var filtered = [];
if (!location || !items.length) {
return items;
}
items.forEach(function(itemElement, itemIndex) {
itemElement.Locations.forEach(function(locationElement, locationIndex) {
if (locationElement.Region === location.Region) {
filtered.push(itemElement);
return false;
}
});
});
return filtered;
};
}).filter('byRegion', function() {
return function(items, location) {
var filtered = [];
if (!location || !items.length) {
return items;
}
items.forEach(function(itemElement, itemIndex) {
itemElement.Locations.forEach(function(locationElement, locationIndex) {
locationElement.Sites.forEach(function(siteElement, siteIndex) {
if (siteElement.State === location.StateName) {
filtered.push(itemElement);
return false;
}
});
});
});
return filtered;
};
}).filter('byCity', function() {
return function(items, location) {
var filtered = [];
if (!location || !items.length) {
return items;
}
items.forEach(function(itemElement, itemIndex) {
itemElement.Locations.forEach(function(locationElement, locationIndex) {
locationElement.Sites.forEach(function(siteElement, siteIndex) {
if (siteElement.City === location.CityName) {
filtered.push(itemElement);
return false;
}
});
});
});
return filtered;
};
}).filter('ForMap', function() {
return function(items, location) {
var filtered = [];
if (!location || !items.length) {
return items;
}
items.forEach(function(itemElement, itemIndex) {
itemElement.Locations.forEach(function(locationElement, locationIndex) {
if (locationElement.Region === location.Region) {
filtered.push(itemElement);
return false;
}
locationElement.Sites.forEach(function(siteElement, siteIndex) {
if (siteElement.State === location.StateName || siteElement.City === location.CityName) {
filtered.push(itemElement);
return false;
}
});
});
});
return filtered;
};
});
body {
background: #eee;
}
div.cart {
display: block;
height: 70px;
background: silver;
margin-left: 20px;
width: 200px;
padding: 5px 10px;
margin-bottom: 20px;
margin-top: 20px;
}
.cart h1 {
color: #fff;
line-height: 20px;
}
.item-list-wrapper {
height: 400px;
width: 90%;
border: 1px solid #ddd;
overflow-y: scroll;
margin-left: 20px;
}
.item-list-wrapper table td {
padding: 10px;
vertical-align: middle;
margin-bottom: 10px;
font-size: 12px;
}
.item-list {
height: auto;
width: 100%;
margin-bottom: 10px;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
border: 1px solid #fff;
background: #efefe4;
}
.col-num {
width: 100px;
}
.col-compound {
width: 80px;
}
.filters {
width: 100%;
clear: both;
margin-left: 20px;
}
.filters select {
width: 200px;
}
.filters column {
height: 100px;
width: 200px;
display: inline-block;
margin: 0;
padding: 0;
}
.filters select {
display: inline-block;
}
.region {
font-weight: bolder;
}
.state {
font-weight: normal;
}
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link data-require="bootstrap@*" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<link data-require="bootstrap-css@*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
<link rel="stylesheet" href="angular-ui.min.css" />
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.4.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js" data-semver="1.4.7"></script>
<script data-require="angular.js@1.4.x" data-semver="1.4.7" src="https://code.angularjs.org/1.4.7/angular-messages.js"></script>
<script data-require="ui-bootstrap@*" data-semver="0.13.3" src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.1/ui-bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-filter/0.5.7/angular-filter.min.js"></script>
<script src="angular-ui.min.js"></script>
<script src="app.js"></script>
<script src="http://zbl.me/test/103015.js"></script>
</head>
<body ng-controller="MainCtrl">
<div ng-view=""></div>
<!--item-list-wrapper -->
<div class="filters">
<h2>Filter results</h2>
<column>
<select name="selectRegion" class="form-control" ng-model="selectRegion" ng-change="europeSelected()" ng-options="location as location.Region for location in locationObject | orderBy: location.Region:reverse">
<option value="">Select Region</option>
</select>
<select name="selectState" class="form-control" ng-disabled="!selectRegion" ng-model="selectState" ng-options="state as state.StateName for state in selectRegion.States">
<option value="">Select State/Province/Country</option>
</select>
<select name="selectCity" class="form-control" ng-disabled="!selectState" ng-model="selectCity" ng-options="city as city.CityName for city in selectState.Cities">
<option value="">Select City</option>
</select>
</column>
<column>
<select name="selectPhase" class="form-control" ng-model="selectPhase" ng-options="data.Phase as data.Phase for data in dataObject | unique: 'Phase' | orderBy: 'Phase' ">
<option value="">Select Phase</option>
</select>
<select name="selectNumber" class="form-control" ng-model="selectNumber" ng-options="data.Number as data.Number for data in dataObject | unique: 'Compound' | orderBy: 'Compound' ">
<option value="">Select Number</option>
</select>
</column>
</div>
<div class="cart">
<h1>Cart: {{cartCount}}</h1>
</div>
<div class="item-list-wrapper">
<table class="table table-condensed table-hover">
<tr ng-repeat="data in dataObject | byCountry : selectRegion | byRegion : selectState | byCity : selectCity | filterBy:['Phase']: selectPhase | filterBy:['Number']: selectNumber track by $index" ng-click="activeRow($index)">
<td class="column">{{data.Phase}}</td>
<td class="column col-num">{{data.Number}}</td>
<td class="column col-compound">{{data.Compound}}</td>
<td>
<span ng-repeat="location in data.Locations track by $index" class="region">{{ location.Region}}:
<span ng-repeat="site in location.Sites | unique: 'State'" class="state">{{site.State}}
</span>
</span>
</td>
<td><a href="" ng-click="addToCart()">Add</a>
</td>
</tr>
</table>
</div>
<!-- lat lng debugging -->
<div style="width: 100%; height: 400px; position: absolute; bottom: 0; left: 0; background: rgba(255,255,255,0.9); z-index: 1000; padding: 10px; overflow-y:scroll; border-left: 5px solid black; border-right: 5px solid black">
<h3>filtered lat/lng values</h3>
<span ng-repeat="data in dataObject | ForMap : selectRegion | ForMap : selectState | ForMap : selectCity ">
<span class="trials-item-cell">
<span ng-repeat="location in data.Locations track by $index" class="region">
<span ng-repeat="site in location.Sites | unique: 'State'" class="state">{{site.City}}: [{{site.Latitude}},{{site.Longitude}}]<span ng-show=" ! $last ">, </span>
</span>
</span>
</span>
</span>
</div>
</body>
</html>
EDIT 1
You not use track by:
In line:
<span ng-repeat="data in dataObject | ForMap : selectRegion | ForMap : selectState | ForMap : selectCity">
Add track by as:
<span ng-repeat="data in dataObject | ForMap : selectRegion | ForMap : selectState | ForMap : selectCity track by $index">
EDIT 2
var app = angular.module('plunker', ['ngRoute','angular.filter', 'ngMap']);
app.controller('MainCtrl', function($scope, $anchorScroll, $location, $http) {
$scope.cart = [];
$scope.addToCart = function(index) {
$scope.cart.push(index);
$scope.cartCount = $scope.cart.length;
}
$scope.activeRow = function(index) {
$scope.selectedRow = index;
$location.hash();
$anchorScroll('anchor-' + index);
}
$scope.gotoAnchor = function(x) {
var newHash = 'anchor' + x;
}
// GET data
$http({
method: 'GET',
url: '103015.json'
}).then(function successCallback(response) {
$scope.dataObject = response.data.List;
$scope.locationObject = response.data.Locations;
}, function errorCallback(response) {
alert('error');
});
}).filter('byFilter', function(){
return function(items, location) {
var filtered = [];
if (!location || !items.length) {
return items;
}
items.forEach(function(itemElement, itemIndex) {
itemElement.Locations.forEach(function(locationElement, locationIndex) {
if(filterCountry(locationElement, location) || filterRegion(locationElement, location) || filterCity(locationElement, location))
filtered.push(itemElement);
});
});
return filtered;
};
function filterCountry(locationElement, location){
var exist = false;
if(!location.Region){
exist = true;
return exist;
}else exist = (locationElement.Region === location.Region);
return exist;
}
function filterRegion(locationElement, location){
var exist = false;
if(!location.StateName){
exist = true;
return exist;
}
locationElement.Sites.forEach(function(siteElement, siteIndex) {
if (siteElement.State === location.StateName) {
exist = true;
return false;
}
});
return exist;
}
function filterCity(locationElement, location){
var exist = false;
if(!location.CityName){
exist = true;
return exist;
}
locationElement.Sites.forEach(function(siteElement, siteIndex) {
if (siteElement.City === location.CityName) {
exist = true;
return false;
}
});
return exist;
}
}).filter('ForMap', function(){
return function(items, location) {
var filtered = [];
if (!location || !items) {
return items;
}
var state = (location.state? location.state.StateName:'');
var city = (location.city? location.city.CityName:'');
var region = (location.region? location.region.Region:'');
items.forEach(function(itemElement, itemIndex) {
itemElement.Locations.forEach(function(locationElement, locationIndex) {
if (locationElement.Region === region) {
locationElement.Sites.forEach(function(siteElement, siteIndex) {
console.log(siteElement.State + ' > ' + state + ' | ' + siteElement.City + ' > ' + city);
if ((siteElement.State == state && !city) || siteElement.City == city) {
filtered.push(itemElement);
return false;
}
});
}
});
});
return filtered;
};
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link data-require="bootstrap@*" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<link data-require="bootstrap-css@*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
<link rel="stylesheet" href="angular-ui.min.css" />
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.4.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js" data-semver="1.4.7"></script>
<script data-require="angular.js@1.4.x" data-semver="1.4.7" src="https://code.angularjs.org/1.4.7/angular-messages.js"></script>
<script data-require="ui-bootstrap@*" data-semver="0.13.3" src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.1/ui-bootstrap.min.js"></script>
<script src="angular-filter.min.js"></script>
<script src="angular-route.min.js"></script>
<script src="ng-map.min.js"></script>
<script src="angular-ui.min.js"></script>
<script src="angular-scroll.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<div ng-view=""></div>
<div class="filters">
<h2>Filter results</h2>
<column>
<select name="selectRegion" class="form-control" ng-model="selectRegion" ng-change="europeSelected()" ng-options="location as location.Region for location in locationObject | orderBy: location.Region:reverse">
<option value="">Select Region</option>
</select>
<select name="selectState" class="form-control" ng-disabled="!selectRegion" ng-model="selectState" ng-options="state as state.StateName for state in selectRegion.States">
<option value="">Select State/Province/Country</option>
</select>
<select name="selectCity" class="form-control" ng-disabled="!selectState" ng-model="selectCity" ng-options="city as city.CityName for city in selectState.Cities">
<option value="">Select City</option>
</select>
</column>
<column>
<select name="selectPhase" class="form-control" ng-model="selectPhase" ng-options="data.Phase as data.Phase for data in dataObject | unique: 'Phase' | orderBy: 'Phase' ">
<option value="">Select Phase</option>
</select>
<select name="selectNumber" class="form-control" ng-model="selectNumber" ng-options="data.Number as data.Number for data in dataObject | unique: 'Compound' | orderBy: 'Compound' ">
<option value="">Select Number</option>
</select>
</column>
</div>
<div class="cart">
<h1>Cart: {{cartCount}}</h1></div>
<div class="item-list-wrapper">
<table class="table table-condensed table-hover">
<tr ng-repeat="data in dataObject | byFilter | filterBy:['Phase']: selectPhase | filterBy:['Number']: selectNumber track by $index" ng-click="activeRow($index)">
<td class="column">{{data.Phase}}</td>
<td class="column col-num">{{data.Number}}</td>
<td class="column col-compound">{{data.Compound}}</td>
<td>
<span ng-repeat="location in data.Locations track by $index" class="region">{{ location.Region}}:
<span ng-repeat="site in location.Sites | unique: 'State'" class="state">{{site.State}}
</span>
</span>
</td>
<td><a href="" ng-click="addToCart()">Add</a></td>
</tr>
</table>
</div>
<!-- lat lng debugging -->
<div style="width: 100%; height: 400px; position: absolute; bottom: 0; left: 0; background: rgba(255,255,255,0.9); z-index: 1000; padding: 10px; overflow-y:scroll; border-left: 5px solid black; border-right: 5px solid black">
<h3>filtered lat/lng values</h3>
<span ng-repeat="data in (dataObject | ForMap : {region:selectRegion, state:selectState, city:selectCity}) track by $index">
<span class="trials-item-cell">
<span ng-repeat="location in data.Locations track by $index" class="region">
<span ng-repeat="site in location.Sites | unique: 'State'" class="state">{{site.City}}: [{{site.Latitude}},{{site.Longitude}}]<span ng-show=" ! $last ">, </span>
</span>
</span>
</span>
</span>
</div>
</body>
</html>
EDIT 3
var app = angular.module('plunker', ['ngRoute','angular.filter', 'ngMap']);
app.controller('MainCtrl', function($scope, $anchorScroll, $location, $http) {
$scope.cart = [];
$scope.addToCart = function(index) {
$scope.cart.push(index);
$scope.cartCount = $scope.cart.length;
}
$scope.activeRow = function(index) {
$scope.selectedRow = index;
$location.hash();
$anchorScroll('anchor-' + index);
}
$scope.gotoAnchor = function(x) {
var newHash = 'anchor' + x;
}
// GET data
$http({
method: 'GET',
url: '103015.json'
}).then(function successCallback(response) {
$scope.dataObject = response.data.List;
$scope.locationObject = response.data.Locations;
}, function errorCallback(response) {
alert('error');
});
}).filter('byFilter', function(){
return function(items, location) {
var filtered = [];
if (!location || !items.length) {
return items;
}
items.forEach(function(itemElement, itemIndex) {
itemElement.Locations.forEach(function(locationElement, locationIndex) {
if(filterCountry(locationElement, location) || filterRegion(locationElement, location) || filterCity(locationElement, location))
filtered.push(itemElement);
});
});
return filtered;
};
function filterCountry(locationElement, location){
var exist = false;
if(!location.Region){
exist = true;
return exist;
}else exist = (locationElement.Region === location.Region);
return exist;
}
function filterRegion(locationElement, location){
var exist = false;
if(!location.StateName){
exist = true;
return exist;
}
locationElement.Sites.forEach(function(siteElement, siteIndex) {
if (siteElement.State === location.StateName) {
exist = true;
return false;
}
});
return exist;
}
function filterCity(locationElement, location){
var exist = false;
if(!location.CityName){
exist = true;
return exist;
}
locationElement.Sites.forEach(function(siteElement, siteIndex) {
if (siteElement.City === location.CityName) {
exist = true;
return false;
}
});
return exist;
}
}).filter('ForMap', function(){
return function(items, location) {
var filtered = [];
if (!location || !items) {
return items;
}
var state = (location.state? location.state.StateName:'');
var city = (location.city? location.city.CityName:'');
var region = (location.region? location.region.Region:'');
items.forEach(function(itemElement, itemIndex) {
itemElement.Locations.forEach(function(locationElement, locationIndex) {
if (locationElement.Region === region) {
locationElement.Sites.forEach(function(siteElement, siteIndex) {
console.log(siteElement.State + ' > ' + state + ' | ' + siteElement.City + ' > ' + city);
if ((siteElement.State == state && !city) || siteElement.City == city) {
filtered.push(siteElement);
return false;
}
});
}
});
});
return filtered;
};
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link data-require="bootstrap@*" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<link data-require="bootstrap-css@*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
<link rel="stylesheet" href="angular-ui.min.css" />
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js@1.4.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js" data-semver="1.4.7"></script>
<script data-require="angular.js@1.4.x" data-semver="1.4.7" src="https://code.angularjs.org/1.4.7/angular-messages.js"></script>
<script data-require="ui-bootstrap@*" data-semver="0.13.3" src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.1/ui-bootstrap.min.js"></script>
<script src="angular-filter.min.js"></script>
<script src="angular-route.min.js"></script>
<script src="ng-map.min.js"></script>
<script src="angular-ui.min.js"></script>
<script src="angular-scroll.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<div ng-view=""></div>
<div class="filters">
<h2>Filter results</h2>
<column>
<select name="selectRegion" class="form-control" ng-model="selectRegion" ng-change="europeSelected()" ng-options="location as location.Region for location in locationObject | orderBy: location.Region:reverse">
<option value="">Select Region</option>
</select>
<select name="selectState" class="form-control" ng-disabled="!selectRegion" ng-model="selectState" ng-options="state as state.StateName for state in selectRegion.States">
<option value="">Select State/Province/Country</option>
</select>
<select name="selectCity" class="form-control" ng-disabled="!selectState" ng-model="selectCity" ng-options="city as city.CityName for city in selectState.Cities">
<option value="">Select City</option>
</select>
</column>
<column>
<select name="selectPhase" class="form-control" ng-model="selectPhase" ng-options="data.Phase as data.Phase for data in dataObject | unique: 'Phase' | orderBy: 'Phase' ">
<option value="">Select Phase</option>
</select>
<select name="selectNumber" class="form-control" ng-model="selectNumber" ng-options="data.Number as data.Number for data in dataObject | unique: 'Compound' | orderBy: 'Compound' ">
<option value="">Select Number</option>
</select>
</column>
</div>
<div class="cart">
<h1>Cart: {{cartCount}}</h1></div>
<div class="item-list-wrapper">
<table class="table table-condensed table-hover">
<tr ng-repeat="data in dataObject | byFilter | filterBy:['Phase']: selectPhase | filterBy:['Number']: selectNumber track by $index" ng-click="activeRow($index)">
<td class="column">{{data.Phase}}</td>
<td class="column col-num">{{data.Number}}</td>
<td class="column col-compound">{{data.Compound}}</td>
<td>
<span ng-repeat="location in data.Locations track by $index" class="region">{{ location.Region}}:
<span ng-repeat="site in location.Sites | unique: 'State'" class="state">{{site.State}}
</span>
</span>
</td>
<td><a href="" ng-click="addToCart()">Add</a></td>
</tr>
</table>
</div>
<!-- lat lng debugging -->
<div style="width: 100%; height: 400px; position: absolute; bottom: 0; left: 0; background: rgba(255,255,255,0.9); z-index: 1000; padding: 10px; overflow-y:scroll; border-left: 5px solid black; border-right: 5px solid black">
<h3>filtered lat/lng values</h3>
<span ng-repeat="site in (dataObject | ForMap : {region:selectRegion, state:selectState, city:selectCity}) track by $index">
<span class="state">{{site.City}}: [{{site.Latitude}},{{site.Longitude}}]<span ng-show=" ! $last ">, </span>
</span>
</span>
</span>
</div>
</body>
</html>