Reputation: 75
I've searched SO for a while trying to find an answer to this question as I imagine someone's run into this scenario. Please point me in the right direction if an answer is already out there. I have an array of objects containing books. These books are stored in a database and can have one or many writers. For each writer in the writers table, there is a column for book_id(FK), writer_name, and review_id(FK). Therefore if there are multiple writers on a book, there is a row for each writer containing the same information except the name. Here is an example of a JSON response from the DB:
var data = [
{"id": 1, "writer" : "John Joseph Doe", "review_id" : 4},
{"id": 1, "writer" : "Daniel Smith", "review_id" : 4},
{"id": 1, "writer" : "Thomas Edward Jones", "review_id" : 4},
{"id": 2, "writer" : "Erin Davis", "review_id" : 5},
{"id": 2, "writer" : "Jill Steinberg", "review_id" : 5},
{"id": 2, "writer" : "Laurie Beth Jennings", "review_id" : 5},
{"id": 3, "writer" : "Emma Jean Williams", "review_id" : 3},
{"id": 3, "writer" : "Mary Joe Williams", "review_id" : 3},
{"id": 3, "writer" : "Helen Andrews", "review_id" : 3},
{"id": 3, "writer" : "Samantha Jones", "review_id" : 3}
];
I would like to filter this array so that I end up with 3 objects, one for each book, that has no repeat values and each writer concatenated as a comma delimited string. Like so:
var data = [
{"id": 1, "writer" : "John Joseph Doe, Daniel Smith, Thomas Edward Jones", "review_id" : 4},
{"id": 2, "writer" : "Erin Davis, Jill Steinberg, Laurie Beth Jennings", "review_id" : 5},
{"id": 3, "writer" : "Emma Jean Williams, Mary Joe Williams, Helen Andrews, Samantha Jones", "review_id" : 3}
];
So I made an attempt to create a function that uses a for loop and a 'first' variable to keep track of which object I'm at based on it's id. My logic is to add each writer to the first instance of each object with a unique id, then remove all the proceeding objects with the same id after the writer has been added. Therefore I'm left with a single object for each book id and a concatenated string of writers separated by commas. The results are close to what I'm looking for, but I can't get it right. I'm not sure if removing the objects from the array is my issue or if I'm taking the entirely wrong approach in general. Here's a working fiddle: http://jsfiddle.net/mlabita37/Lvc0u55v/8987/ . There is a textarea above the table that I'm using as a 'console' to show the final data array, just expand to see the full result.
Here's some snippets of that code:
HTML:
<div ng-controller="BookCtrl">
<table class="table table-striped">
<thead>
<tr>
<th>Book ID</th>
<th>Writer</th>
<th>Review ID</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="book in books">
<td>{{ book.id }}</td>
<td>{{ book.writer }}</td>
<td>{{ book.review_id }}</td>
<textarea>{{ books }}</textarea>
</tr>
</tbody>
Controller:
var data = [
{"id": 1, "writer" : "John Joseph Doe", "review_id" : 4},
{"id": 1, "writer" : "Daniel Smith", "review_id" : 4},
{"id": 1, "writer" : "Thomas Edward Jones", "review_id" : 4},
{"id": 2, "writer" : "Erin Davis", "review_id" : 5},
{"id": 2, "writer" : "Jill Steinberg", "review_id" : 5},
{"id": 2, "writer" : "Laurie Beth Jennings", "review_id" : 5},
{"id": 3, "writer" : "Emma Jean Williams", "review_id" : 3},
{"id": 3, "writer" : "Mary Joe Williams", "review_id" : 3},
{"id": 3, "writer" : "Helen Andrews", "review_id" : 3},
{"id": 3, "writer" : "Samantha Jones", "review_id" : 3}
];
combine = function(data){
var first = data[0];
for(var i = 1; i < data.length; i++){
if(data[i].id === first.id){
first.writer = first.writer + ", " + data[i].writer;
data.splice(i, 1);
}
if(data[i].id !== first.id){
first = data[i];
var j = i+1;
if(data[j].id === first.id){
first.writer = first.writer + ", " + data[j].writer;
data.splice(j, 1);
}
}
}
};
combine(data);
$scope.books = data;
Another idea I had was to use a filter, however I don't have much experience with them and don't know if that would be a better approach. I also wasn't sure if this was something that could/should be handled in my MySQL query instead, or in the backend PHP. I'm under the impression that this is best handled on the front end.
Upvotes: 1
Views: 1113
Reputation: 1903
var data = [
{"id": 1, "writer" : "John Joseph Doe", "review_id" : 4},
{"id": 1, "writer" : "Daniel Smith", "review_id" : 4},
{"id": 1, "writer" : "Thomas Edward Jones", "review_id" : 4},
{"id": 2, "writer" : "Erin Davis", "review_id" : 5},
{"id": 2, "writer" : "Jill Steinberg", "review_id" : 5},
{"id": 2, "writer" : "Laurie Beth Jennings", "review_id" : 5},
{"id": 3, "writer" : "Emma Jean Williams", "review_id" : 3},
{"id": 3, "writer" : "Mary Joe Williams", "review_id" : 3},
{"id": 3, "writer" : "Helen Andrews", "review_id" : 3},
{"id": 3, "writer" : "Samantha Jones", "review_id" : 3}
];
function existsAt(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] == value) {
return i;
}
}
return false;
}
var _data = [];
_data.push(data[0]);
for (var i = 1; i < data.length; i++) {
var alreadyExistsAt = existsAt(_data, 'id', data[i].id);
if (alreadyExistsAt !== false) {
_data[alreadyExistsAt].writer += ', ' + data[i].writer;
} else {
_data.push(data[i]);
}
}
document.body.innerHTML = JSON.stringify(_data);
Upvotes: 1
Reputation: 12458
I hope the variable names in the code make it fairly self-explanatary.
forEach
loop selects each review (i.e. currReview
) in turn from the complete list of reviews.forEach
loop loops over each pre-existing concatenated book review object (i.e. prevReviewsForThisBook
...empty at first). This inner loop determines whether the current review is for a book that already has at least one review for it:
writer
property of that pre-existing review object is updated to include the new writer.Note that your id
which is presumably your book_id
is always identical to your review_id
. Is that really what you want? It seems like one of them is either redundant/unnecessary, or one of them is calculated/determined incorrectly. The latter seems possible. For example, are the review_id
of the review done by John Joseph Doe and the review_id
of the review done by Daniel Smith both supposed to have the same value, 4? I would think that those are separate reviews and thus should have different review_id
values, even if they both have the same id
/book_id
value (because those two people both reviewed the same book). This issue is not central to answering this question, but you might want to re-think that.
var data = allReviews = [
{"id": 1, "writer" : "John Joseph Doe", "review_id" : 4},
{"id": 1, "writer" : "Daniel Smith", "review_id" : 4},
{"id": 1, "writer" : "Thomas Edward Jones", "review_id" : 4},
{"id": 2, "writer" : "Erin Davis", "review_id" : 5},
{"id": 2, "writer" : "Jill Steinberg", "review_id" : 5},
{"id": 2, "writer" : "Laurie Beth Jennings", "review_id" : 5},
{"id": 3, "writer" : "Emma Jean Williams", "review_id" : 3},
{"id": 3, "writer" : "Mary Joe Williams", "review_id" : 3},
{"id": 3, "writer" : "Helen Andrews", "review_id" : 3},
{"id": 3, "writer" : "Samantha Jones", "review_id" : 3}
];
var reviewsByBook = [];
allReviews.forEach(function(currReview) {
var currReviewIsForNewBook = true;
reviewsByBook.forEach(function(prevReviewsForThisBook) {
if (currReview.id === prevReviewsForThisBook.id) {
prevReviewsForThisBook.writer += ", " + currReview.writer;
currReviewIsForNewBook = false;
}
});
if (currReviewIsForNewBook) {
reviewsByBook.push(currReview);
}
});
console.log(reviewsByBook);
Upvotes: 1