Reputation: 95
Aim:
A user should be able to type into the search field the name of a course, and see a list of students who are registered on that course.
Models:
course (has a course name and course code)
student (has a list of registered courses)
students (an array of student objects)
Method:
ng-repeat through a list of student objects. For each student object, pass object to a custom filter function filterByCourse(byCourse, student)
.
To be implemented: filterByCourse function iterates through student.courses. If a course with the same name or code as byCourse is found, this is added to a list of filteredStudents. pass the filteredStudents list to the view in place of the unfiltered list of students.
Here's the code:
<input class="form-control" type="text" ng-model="byCourse"
placeholder="Search by course id or course name..">
<ul ng-repeat="student in students | filter: filterByCourse(byCourse, student)">
<li ng-init="index=0; classes=['list--students_item', 'list--students_item--active']" ng-click="stageMeToCourse($index)"
ng-class="classes[index % classes.length]"
class="col-xs-24">
<img src="/img/profile_default.png">
<h4>{{ student.first_name }} {{ student.last_name }}
<span class="role">{{ student.role }}</span>
<i class="fa fa-chevron-right"></i>
</h4>
<p class="student_nr">{{ student._id }}</p>
</li>
</ul>
</div><!--list-students-->
The Problem:
When I pass the student object into filterByCourse, a console.log on the first line shows that student is undefined, so I can't iterate through the courses and return a list of filtered students as I'd like to.
scope.filterByCourse = function(course, student){
console.log(student); //student is undefined
}
Things I've tried:
I've tried to use the filter function in the ng-repeat to iterate through each student object's course list but cannot figure out how to do it.
I've tried hitting things with hammers.
Further problem:
I only wish to filter the results in this way if the user has entered some string into the input field. If the input field is empty the filter function should not be triggered. I have no idea how to do this bit!
Upvotes: 2
Views: 3627
Reputation: 5179
Option 1:
What you are trying to create is called a predicate function. You can read up on it here(just scroll to function(value, index, array)
)
A predicate function can be used to write arbitrary filters. The function is called for each element of the array, with the element, its index, and the entire array itself as arguments.
The final result is an array of those elements that the predicate returned true for
Please see working example here
If you type in "course" you will see both students, if you type "course1" you will only see the first student. The same counts for the course code "CC1"
HTML:
<div ng-controller="TestController" class="panel panel-primary">
<form class="form-inline">
<div class="form-group">
<input type="text" class="form-control" placeholder="Filter" ng-model="studentFilter">
</div>
</form>
<div class="panel-heading">Students</div>
<ul class="list-group">
<li class="list-group-item" ng-repeat="student in students | filter:onFilterStudents">
{{student.firstName}} {{student.surname}}
<ul class="list-group">
<li class="list-group-item" ng-repeat="course in student.courses">
{{course.courseName}}-{{course.courseCode}}
</li>
</ul>
</li>
</ul>
</div>
JS:
var myApp = angular.module('myApp', []);
myApp.controller('TestController', ['$scope', function($scope) {
$scope.students = [{
firstName: 'John',
surname: 'Doe',
courses: [{
courseName: 'course1',
courseCode: 'CC1'
}, {
courseName: 'course2',
courseCode: 'CC7'
}]
}, {
firstName: 'Jane',
surname: 'Doe',
courses: [{
courseName: 'course3',
courseCode: 'CC2'
}, {
courseName: 'course4',
courseCode: 'CC3'
}]
}];
$scope.onFilterStudents = function(value, index, array) {
if ($scope.studentFilter === '' || $scope.studentFilter === null || $scope.studentFilter === undefined) {
return true;
}
return value.courses.filter(function(course) {
console.log(course);
return course.courseName.toLowerCase().indexOf($scope.studentFilter.toLowerCase()) > -1 || course.courseCode.toLowerCase().indexOf($scope.studentFilter.toLowerCase()) > -1;
}).length > 0;
};
}]);
Option 2
You can use a object as the filter expression(scroll to Object: A pattern ob...), please see working example here
A pattern object can be used to filter specific properties on objects contained by array. For example {name:"M", phone:"1"} predicate will return an array of items which have property name containing "M" and property phone containing "1". A special property name $ can be used (as in {$:"text"}) to accept a match against any property of the object or its nested object properties. That's equivalent to the simple substring match with a string as described above. The predicate can be negated by prefixing the string with !. For example {name: "!M"} predicate will return an array of items which have property name not containing "M".
HTML:
<div ng-controller="TestController" class="panel panel-primary">
<form class="form-inline">
<div class="form-group">
<input type="text" class="form-control" placeholder="Filter" ng-model="studentFilter">
</div>
</form>
<div class="panel-heading">Students</div>
<ul class="list-group">
<li class="list-group-item" ng-repeat="student in students | filter:{courses:{$:studentFilter}}">
{{student.firstName}} {{student.surname}}
<ul class="list-group">
<li class="list-group-item" ng-repeat="course in student.courses">
{{course.courseName}}-{{course.courseCode}}
</li>
</ul>
</li>
</ul>
</div>
JS:
var myApp = angular.module('myApp', []);
myApp.controller('TestController', ['$scope', function($scope) {
$scope.students = [{
firstName: 'John',
surname: 'Doe',
courses: [{
courseName: 'course1',
courseCode: 'CC1'
}, {
courseName: 'course2',
courseCode: 'CC7'
}]
}, {
firstName: 'Jane',
surname: 'Doe',
courses: [{
courseName: 'course3',
courseCode: 'CC2'
}, {
courseName: 'course4',
courseCode: 'CC3'
}]
}];
}]);
If no custom code or advanced filtering is required, I opt for option 2
Upvotes: 2
Reputation: 175
The function
filterByCourse
should be called only with 'byCourse' in the html, and it's implementation should only receive the parameter 'byCourse'. This function also should return a function that receives a student object. so you're html should be
<ul ng-repeat="student in students | filter: filterByCourse(byCourse)">
and in the javascript it should be
$scope.filterByCourse = function (byCourse) {
return function (student) {
console.log(student);
}
};
(the function that the filter returns would have access to the byCourse parameter cuz its in the same context). here is a working jsFiddle
Upvotes: 4