I have been learning JS over the last 2 weeks and am trying to build a news app using the Guardian's Api. I have managed to get the images, links and headlines pulling in, but I am struggling with getting the search functionality working. I want it to work so the page instantly updates as soon as you start typing. I can see the below is 'working' in the console but I can't figure out how to display it effectively on the page.
I have tried creating a new array for 'searched for articles' but this was too long winded and did not return a real time updated feed whilst typing.
I have tried 'toggling' the h1 (headline) elements of the articles, but kept getting 'not a function' errors. Any insights would be greatly appreciated as I am not sure of the best approach here.
VIEW CLASS
class ArticlesView {
constructor(model, api) {
this.model = model;
this.api = api;
this.articlesFromModel = this.model.getArticles();
this.newsFeed = document.querySelector("#news-feed");
this.clearFeedBtn = document.querySelector("#clear-feed-button");
this.refreshBtn = document.querySelector("#refresh-button");
this.searchInput = document.querySelector("#search-input");
this.allHeadlines = [document.querySelectorAll("h1")];
this.clearFeedBtn.addEventListener("click", () => {
this.clearFeed();
});
this.searchInput.addEventListener("input", (e) => {
this.articlesFromModel.forEach((article) => {
const searchInput = e.target.value.toLowerCase();
const isVisible = article.webTitle.toLowerCase().includes(searchInput);
if (!isVisible) {
** // not sure what to do here
** }
console.log(searchInput);
console.log(article.webTitle);
console.log(isVisible);
});
});
}
displayArticlesFromApi() {
this.api.loadArticles(
(repoData) => {
this.model.addArticle(repoData.response.results);
this.displayArticles();
},
() => {
this.displayError();
}
);
}
displayArticles() {
this.articlesFromModel.forEach((article) => {
this.addImage(article);
this.addHeadline(article);
});
}
addHeadline(article) {
const h1 = document.createElement("h1");
h1.className = "news-title";
h1.innerText = article.webTitle;
h1.onclick = () => {
window.location.href = article.webUrl;
};
this.newsFeed.append(h1);
}
addImage(article) {
const img = document.createElement("img");
img.className = "news-image";
img.setAttribute("id", article.id);
img.src = article.fields.thumbnail;
img.onclick = () => {
window.location.href = article.webUrl;
};
this.newsFeed.append(img);
}
displayError() {
let errorMessage = document.createElement("div");
errorMessage.className = "error";
errorMessage.textContent = "Oops, something went wrong!";
this.newFeed.append(errorMessage);
}
clearFeed() {
const images = document.querySelectorAll("img.news-image");
images.forEach((element) => {
element.remove();
});
const headlines = document.querySelectorAll("h1.news-title");
headlines.forEach((element) => {
element.remove();
});
}
}
module.exports = ArticlesView;
MODEL CLASS
class ArticlesModel {
constructor() {
this.articles = [];
}
getArticles() {
return this.articles;
}
addArticle(article) {
article.forEach((a) => {
this.articles.push(a);
});
}
reset() {
this.articles = [];
}
}
module.exports = ArticlesModel;
API CLASS
class GuardianApi {
constructor() {
this.apiURL = `https://content.guardianapis.com/search?api-key=${apikey}&show-fields=thumbnail`;
}
loadArticles(callback) {
fetch(this.apiURL)
.then((response) => response.json())
.then((data) => {
callback(data);
});
}
}
module.exports = GuardianApi;
INDEX.js
console.log("the app is running");
const ArticlesModel = require("./src/ArticlesModel.js");
const ArticlesView = require("./src/ArticlesView.js");
const GuardianApi = require("./src/GuardianApi.js");
const client = new GuardianApi();
const model = new ArticlesModel();
const view = new ArticlesView(model, client);
view.displayArticlesFromApi();
HTML FILE
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<title> Your Daily News Feed</title>
</head>
<body>
<h1>News Feed</h1>
<input type="search" id="search-input" placeholder="e.g. Football...">
<br><br>
<button onClick="window.location.reload();">Refresh Page</button> <button id="clear-feed-button">Clear Feed</button>
<br> <br>
<div id="news-feed">
</div>
</body>
<script src="bundle.js"></script>
</html>
CodePudding user response:
I've created a code snippet for your case. You'll need to adjust the line for the api key. For now I've used a key generated for a dummy email. I've also highlighted where the code was changed
The new search function is:
this.searchInput.addEventListener("input", (e) => {
const searchInput = e.target.value.toLowerCase();
// Create a filtered copy of articlesFromModel
const filteredArticles = this.articlesFromModel.filter((article) => {
return article.webTitle.toLowerCase().includes(searchInput);
});
this.clearFeed()
// Display filtered list.
this.displayArticles(filteredArticles);
});
displayArticles()
has been modified to accept a parameter
displayArticles(articles) {
articles.forEach((article) => {
this.addImage(article);
this.addHeadline(article);
});
}
Filtering existing articles
class ArticlesView {
constructor(model, api) {
this.model = model;
this.api = api;
this.articlesFromModel = this.model.getArticles();
this.newsFeed = document.querySelector("#news-feed");
this.clearFeedBtn = document.querySelector("#clear-feed-button");
this.refreshBtn = document.querySelector("#refresh-button");
this.searchInput = document.querySelector("#search-input");
this.allHeadlines = [document.querySelectorAll("h1")];
this.clearFeedBtn.addEventListener("click", () => {
this.clearFeed();
});
// New search function
this.searchInput.addEventListener("input", (e) => {
const searchInput = e.target.value.toLowerCase();
// Create a filtered copy of articlesFromModel
const filteredArticles = this.articlesFromModel.filter((article) => {
return article.webTitle.toLowerCase().includes(searchInput);
});
this.clearFeed()
// Display filtered list.
this.displayArticles(filteredArticles);
});
}
displayArticlesFromApi() {
this.api.loadArticles(
(repoData) => {
this.model.addArticle(repoData.response.results);
// displayArticles() is now being passed an argument
this.displayArticles(this.articlesFromModel);
},
() => {
this.displayError();
}
);
}
// displayArticles() now accepts an argument
displayArticles(articles) {
articles.forEach((article) => {
this.addImage(article);
this.addHeadline(article);
});
}
addHeadline(article) {
const h1 = document.createElement("h1");
h1.className = "news-title";
h1.innerText = article.webTitle;
h1.onclick = () => {
window.location.href = article.webUrl;
};
this.newsFeed.append(h1);
}
addImage(article) {
const img = document.createElement("img");
img.className = "news-image";
img.setAttribute("id", article.id);
img.src = article.fields.thumbnail;
img.onclick = () => {
window.location.href = article.webUrl;
};
this.newsFeed.append(img);
}
displayError() {
let errorMessage = document.createElement("div");
errorMessage.className = "error";
errorMessage.textContent = "Oops, something went wrong!";
this.newFeed.append(errorMessage);
}
clearFeed() {
const images = document.querySelectorAll("img.news-image");
images.forEach((element) => {
element.remove();
});
const headlines = document.querySelectorAll("h1.news-title");
headlines.forEach((element) => {
element.remove();
});
}
}
class ArticlesModel {
constructor() {
this.articles = [];
}
getArticles() {
return this.articles;
}
addArticle(article) {
article.forEach((a) => {
this.articles.push(a);
});
}
reset() {
this.articles = [];
}
}
class GuardianApi {
constructor() {
this.apiURL = `https://content.guardianapis.com/search?api-key=bb887d29-f877-4222-96b2-c1e7051bcf71&show-fields=thumbnail`;
}
loadArticles(callback) {
fetch(this.apiURL)
.then((response) => response.json())
.then((data) => {
callback(data);
});
}
}
const client = new GuardianApi();
const model = new ArticlesModel();
const view = new ArticlesView(model, client);
view.displayArticlesFromApi();
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<title> Your Daily News Feed</title>
</head>
<body>
<h1>News Feed</h1>
<input type="search" id="search-input" placeholder="e.g. Football...">
<br><br>
<button onClick="window.location.reload();">Refresh Page</button> <button id="clear-feed-button">Clear Feed</button>
<br> <br>
<div id="news-feed">
</div>
</body>
<script src="bundle.js"></script>
</html>