I'm building a custom search, as of now if I enter "The R" I get the result list with The Fellow ship of the Ring first, because the phrase "the ring" it's in its .text. I want The Return of the King to be first. Is there a way I can give more relevance to the .name field or sort the match array based on the name .field and the input text?
HTML
<section class="container-fluid px-0 justify-content-center">
<div class="row no-gutters">
<div class="col d-flex justify-content-center search">
<form class="form-inline position-relative">
<input id="search" class="form-control form-control-search" type="text" placeholder="Search..." aria-label="Search">
</form>
<div id="match-list" class="d-none"></div>
</div>
</div>
</section>
JAVASCRIPT
const searchIndex = async searchText => {
const res = await fetch('/data/index.json');
const index = await res.json();
matchList.classList.remove("d-none");
// Get matches to current text input
let matches = index.filter(index => {
const regex = new RegExp(`${searchText}`, 'gi');
return index.name.match(regex) || index.text.match(regex);
});
// Clear when input or matches are empty
if (searchText.length === 0) {
clearSearch();
}
outputHtml(matches);
};
function clearSearch(){
matches = [];
matchList.classList.add("d-none");
}
// Show results in HTML
const outputHtml = matches => {
if (matches.length > 0) {
const html = matches.map(function(match){
return `<a href="${match.url}">
<div class="media mb-2">
<div class="component-icon-slot my-auto" style="background-image: url('/img/${match.url}/icon.png"></div>
<div class="media-body pl-2">
<h3 class="mt-0 mb-0">${match.name}</h3>
<b>${match.type}</b><br/>
<i>Found in <b>${match.product}</b></i><br/>
${match.text}
</div>
</div></a>`
}
}).join('');
matchList.innerHTML = html;
}
};
index.JSON
[
{
"name": "The Fellowship of the Rings",
"type": "book",
"text": "Bilbo reveals that he intends to leave the Shire for one last adventure, and he leaves his inheritance, including the Ring, to his nephew Frodo. Gandalf investigates...",
"url": "books/the-fellowship-of-the-rings",
"product": "Books"
},
{
"name": "The Two Towers",
"type": "book",
"text": "Awakening from a dream of Gandalf fighting the Balrog in Moria, Frodo Baggins and Samwise Gamgee find themselves lost in the Emyn Muil near Mordor and discover they are being tracked by Gollum, a former bearer of the One Ring.",
"url": "books/the-two-towers",
"product": "Books"
},
{
"name": "The Return of the King",
"type": "book",
"text": "Gandalf flies in with eagles to rescue the Hobbits, who awaken in Minas Tirith and are reunited with the surviving Fellowship.",
"url": "books/the-return-of-the-king",
"product": "Books"
}
]
You could map your data to include relevance points:
const index = await res.json();
const searchTextLowercased = searchText.toLowerCase();
const rankedIndex = index.map(entry => {
let points = 0;
if (entry.name.toLowerCase().includes(searchTextLowercased)) {
points += 2;
}
if (entry.text.toLowerCase().includes(searchTextLowercased)) {
points += 1;
}
return {...entry, points};
}).sort((a, b) => b.points - a.points);
This way, you have ranked results in rankedIndex const.
Keep in mind that your code probably needs some refactoring, because you're fetching data on each search. I'm assuming your searchIndex() is called with every key press or something like that.
Related
I'm using fetch() to create a section which pulls and sorts listings from greenhouse api into its matching container.
I have a predefined list of departments which I have stored in the departments array. If a fetched item has a similar value as the data-dept value, then that html will be added under that container.
Here's an example, one of the items in the array is "Sales". Once fetch() is complete, two things can happen:
Either a job with the department of "sales" exists, in which case it will be appended to data-dept="sales". Note: In my code, I'm using data-dept^= to find similar names. If "sales us" exits in the api, then I want that too to be appended to [data-dept="sales"].
No jobs exist with the department of "sales". In this case, if "[data-dept="sales"]` has no child elements, hide it, as there's no point showing departments with no listings.
Current issues:
You can see by accessing the API URL that jobs with the "department" of "sales" do exist, but they do not get appended to my data-dept="sales" div (it has no child elements).
Any jobs that are not similar departments to those that are in the array need to appended to data-dept="other", but this section is also empty. For example, thee api has jobs for the "department" of "Architects". This option isn't in the array, so these jobs will need to be appended to data-dept="other".
Code:
$(function() {
fetch('https://boards-api.greenhouse.io/v1/boards/example/jobs?content=true', {})
.then(function (response) {
return response.json();
})
.then(function (data) {
appendDataToHTML(data);
})
.catch(function (err) {
console.log(err);
});
function appendDataToHTML(data) {
const mainContainer = document.getElementById("careers-listing");
// for each object, create card
for (var i = 0; i < Object.keys(data.jobs).length; i++) {
var department = data.jobs[i].departments[0].name;
department = department.replace(/\s+/g, '-').toLowerCase();
var job_title = data.jobs[i].title;
var job_location = data.jobs[i].location.name;
var html =
'<figure class="careercard" data-dept="'+ department +'">' +
'<div class="careercard__inner">' +
'<figcapton class="careercard__role">' +
'<span class="careercard__title">' + job_title + '</span>' +
'</figcapton>' +
'<div class="careercard__address">' +
'<span class="careercard__location">' + job_location + '</span>' +
'</div>' +
'</div>' +
'</figure>';
// filter card in correct parent category
if ("[data-dept^="+ department +"]") {
$(".careersIntegration__accordion-jobs[data-dept^='" + department + "']").append(html);
} else{
$(".careersIntegration__accordion-jobs[data-dept='other']").append(html);
}
}
}
/* fetch end */
$('.careersIntegration__accordion-jobs').each(function(index, obj){
console.log(this);
if ( $(this).length == 0 ) {
console.log("hide");
} else{
console.log("dont hide");
}
});
});
{% set departments = ["Sales" "Technology", "Creative", "Other"] %}
<section class="careersIntegration">
<div class="careersIntegration__listing" id="careers-listing">
{% for dept in departments %}
<div class="careersIntegration__accordion">
<div class="careersIntegration__accordion-header">
<span class="careersIntegration__accordion-dept">{{ dept }}</span>
</div>
<div class="careersIntegration__accordion-jobs" data-dept="{{ dept|lower|replace( ' ', '-' ) }}"></div>
</div>
{% endfor %}
</div>
</section>
Here is a visual guide of the layout I'm trying to achieve if it helps:
So this is not an exact answer but it does give you a good example. I just used mock data but here is the idea. Im sure there is a better way to do this but this is the quick and dirty.
Create a few variables to store the data for each department. This is a filter function that just stores anything inside the include() this will allow you to catch something like "US Sales" in the sales department.
Create a map function that takes in two paramiters the first is the variable you created earlier and the second is the name of the department which should match the name of whereever you are going to append this information.
The first part of the function creates the item using a template literal. this will create an array of all the items
The second part of the function wraps the array in a UL which is not super important but what is important is that you join the array using an empty sting.
Last part simply appends the html to the end of the department by using the name of the department as an ID and .insertAdjacentHTML("beforeend", list) which puts it before the end of the element and passes in the HTML which I have named as list
const data = [
{
title: "Lead Sales Person",
dept: "sales",
desc: "be a leader"
},
{
title: "Sales Person",
dept: "sales",
desc: "sell stuff to people"
},
{
title: "US Sales Person",
dept: "sales US",
desc: "sell stuff to people"
},
{
title: "Lead Developer",
dept: "dev",
desc: "be a leader"
},
{
title: "Developer",
dept: "dev",
desc: "Develop things and stuff"
},
{
title: "Random Guy",
dept: "other",
desc: "Do Random Stuff"
},
{
title: "Random Girl",
dept: "other",
desc: "Do Random Stuff"
}
];
let sales = data.filter(job => job.dept.includes("sales")),
dev = data.filter(job => job.dept.includes("dev")),
other = data.filter(job => job.dept.includes("other"));
mapDepartment(sales, "sales");
mapDepartment(dev, "dev");
mapDepartment(other, "other");
function mapDepartment(dept, name){
let items = dept.map(position => {
return `
<li>
<b>Title:</b> ${position.title}<br>
<b>Description:</b> ${position.desc}
</li>
`
})
let list = `<ul>${items.join("")}</ul>`;
document.getElementById(name).insertAdjacentHTML("beforeend", list)
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
li {
margin-bottom: 1rem;
}
<div id="sales">
<h1>Sales</h1>
</div>
<div id="dev">
<h1>Development</h1>
</div>
<div id="other">
<h1>Other</h1>
</div>
I am trying to learn EJS and make a blog but I cant seem to understand this error
What I am trying to do is try to write some db response as an Object to an array then push it to the file.
I am using replit DB
const fs = require("fs")
const Database = require("#replit/database")
const db = new Database()
exports.load = async function(){
db.set("hello", {
"author": "Some author 1",
"title": "Blog Post 1",
"content": "First post content",
"date_posted": "Dec 17, 2021"
})
var posts = new Array()
db.list().then(keys => {
keys.forEach(key => {
posts.push(` <article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="/p">Anonymous</a>
<small class="text-muted">${db.get(key).date_posted}</small>
</div>
<h2><a class="article-title" href="#">${ db.get(key).title }</a></h2>
<p class="article-content">${ db.get(key).content }</p>
</div>
</article`
)
})
});
posts = posts.join()
fs.writeFileSync("public/posts.ejs", posts)
}
Error that I am getting when I run the code:
UnhandledPromiseRejectionWarning: TypeError: posts.push is not a function
First, you declare var posts = new Array(). So posts is an array. Next line (in execution order) : posts = posts.join(). So now posts is an empty string. You are changing the type of the variable, which is a bad practice (Typescript wouldn't let you do that). Now next line in execution order : .then(keys =>. You start pushing stuff into posts, but posts is now a string, remember? Not an array anymore.
You use the async keyword for no reason, since there is no await in it. You might as well leverage it :
exports.load = async function(){
db.set("hello", {
"author": "Some author 1",
"title": "Blog Post 1",
"content": "First post content",
"date_posted": "Dec 17, 2021"
})
let postsArray = new Array();
const keys = await db.list();
keys.forEach(key => {
postsArray.push(`<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="/p">Anonymous</a>
<small class="text-muted">${db.get(key).date_posted}</small>
</div>
<h2><a class="article-title" href="#">${ db.get(key).title }</a></h2>
<p class="article-content">${ db.get(key).content }</p>
</div>
</article`
)
})
const posts = postsArray.join()
fs.writeFileSync("public/posts.ejs", posts)
}
OR with .map() in one line :
exports.load = async function(){
db.set("hello", {
"author": "Some author 1",
"title": "Blog Post 1",
"content": "First post content",
"date_posted": "Dec 17, 2021"
})
const keys = await db.list();
const posts = keys.map( key => `<article class="media content-section">....</article`).join();
fs.writeFileSync("public/posts.ejs", posts)
}
Browser Console Error
Uncaught ReferenceError: getNewCars is not defined at HTMLDivElement.onclick
Code Explained
I'm building a car selector form. How it works is there's categories (known as seasons) that can be clicked on to bring up a list of specific cars that pertain to that category (season).
Html
<div class="chooseCar">
<div class="chooseCarTabs">
</div>
<div class="chooseCarWrapper">
<div id="chooseCarSelection">
</div>
</div>
</div>
<script src="/public/theme/scripts/car-selection.js"></script>
car-selection.js
alert('new car-selection js')
let cars;
let seasons = [
{
"name": "Seasonal Cars",
"path": "seasonal.json"
},
{
"name": "Modern Cars",
"path": "modern.json"
},
{
"name": "Classic Cars",
"path": "classic.json"
},
{
"name": "Flag Cars",
"path": "flags.json"
}
];
let seasonTab = "Seasonal Cars";
const chooseCarBody = document.getElementById('chooseCarSelection');
const seasonsTabs = document.getElementsByClassName('chooseCarTabs')[0];
function loadCars(){
chooseCarBody.innerHTML = '';
cars.forEach(car => {
chooseCarBody.innerHTML += `
<div class="singleCar">
<div class="singleCarInner">
<img src="/public/images/avatars/${car.filename}" alt="${car.name}">
</div>
</div>
`
})
}
//Ajax Request
async function setSeasons() {
seasonsTabs.innerHTML = ''
await seasons.forEach(season => {
seasonsTabs.innerHTML += `
<div ${seasonTab == season.name ? 'class="activeSeasonTab"' : ''} onclick="getNewCars('${season.name}', '${season.path}' )">
${season.name}
</div>
`
});
}
//Will be replaced with AJAX Request
async function getNewCars(seasonName, season = seasons[0].path){
cars = null;
await fetch(`/public/data/cars/${season}`)
.then(response => response.json())
.then(data => {
console.log(data)
seasonTab = seasonName;
cars = data; console.log(cars)
})
.catch(error => console.log(error));
await loadCars()
}
async function initData(){
await setSeasons();
await getNewCars(seasons[0].name);
}
initData();
Extra code explanation
let cars; and let seasons work as a state of sorts. When a seasons tab is clicked on an ajax request is sent to get fill the cars state with cars for the category which is then looped through and populated on the page.
My Problem
When I reload the page the cars and category (season) tabs appear on the page just fine including the getNewCars(). But when I go to click on:
<div
${seasonTab == season.name ? 'class="activeSeasonTab"' : ''}
onclick="getNewCars('${season.name}', '${season.path}' )"
>
${season.name}
</div>
I get this error:
Uncaught ReferenceError: getNewCars is not defined at HTMLDivElement.onclick
Note, inline scripts I don't seem to get this error:
<div class="chooseCar">
<div class="chooseCarTabs">
</div>
<div class="chooseCarWrapper">
<div id="chooseCarSelection">
</div>
</div>
</div>
<script> /* All scripts in here*/</script>
How do I fix this and what's going wrong with my code that when import from another js file this happens?
I need to be able to append every even object on the left side of a vertical line, and every odd object on the right side. I am not sure how I can achieve this.
Sample of the JSON, (This is just dummy data for now) (url)
[{
"Year": 2010,
"Title": "Cadillac",
"Description": "Escalade ESV"
},
{
"Year": 1998,
"Title": "Volvo",
"Description": "V70"
},
{
"Year": 1992,
"Title": "Toyota",
"Description": "Camry"
},
{
"Year": 2012,
"Title": "Ford",
"Description": "Explorer"
}]
Heres my code:
fetch(url)
.then(result => {
return result.json();
})
.then (data =>{
console.log(data);
data.forEach( (point) => {
const appendToHtml = `
<div class="container left">
<div class="content">
<h2>${point.Title}</h2>
<p>${point.Description}</p>
<p>${point.Year}</p>
</div>
</div>
<div class="container right">
<div class="content">
<h2>${point.Title}</h2>
<p>${point.Description}</p>
<p>${point.Year}</p>
</div>
</div>
`;
$(".timeline").append(appendToHtml);
});
})
The issue is it append on the left AND the right.
Click here for an image of the issue.
I need to alternate
for example
Cadillac on the left, Volva on the right etc...
I cant seem to figure out what to do..
Thanks in advance!
You can calculate the odd or even of car index and based on that apply class left and right.
data.forEach( (point, index) => {
const isOdd = index % 2;
const appendToHtml = `
<div class="container ${isOdd ? 'right' : 'left' }">
<div class="content">
<h2>${point.Title}</h2>
<p>${point.Description}</p>
<p>${point.Year}</p>
</div>
</div>`;
$(".timeline").append(appendToHtml);
});
Here you go with a solution
fetch(url)
.then(result => {
return result.json();
})
.then (data =>{
console.log(data);
data.forEach( (point, i) => {
const appendToHtml = `
<div class=`container ${i%2 === 0 ? "right" : "left" }`>
<div class="content">
<h2>${point.Title}</h2>
<p>${point.Description}</p>
<p>${point.Year}</p>
</div>
</div>
`;
$(".timeline").append(appendToHtml);
});
})
Use template literal for finding odd even using mod function
I am having issues with using the .map function in JavaScript to extract a nested array from an api response.
here is the JSON:
[
{
"id": 3787,
"title": "Dummy title!",
"start_time": "2020-04-25T16:54:00.000Z",
"created_at": "2020-04-25T17:22:13.315Z",
"updated_at": "2020-04-25T17:32:15.364Z",
"incident_updates": [
{
"id": 9905,
"body": "Dummy Paragraph test!",
Here is the code that I put together my script.js file:
fetch(url)
.then((response) => {
if (!response.ok) {
throw Error("ERROR");
}
return response.json();
})
.then((data) => {
console.log(data);
const html = data
.map((item) => {
console.log(item.incident_updates[0]);
return `<div class="card card-space">
<div class="card-body">
<h5 class="card-title">${item.title}</h5>
<h6 class="card-subtitle mb-2 text-muted">${item.start_time}</h6>
<p class="card-text">${item.incident_updates.body}</p> // issues with this
</div>
</div>`;
})
.join("");
for whatever reason everything else is working fine like item.title & item.start_time however item.incident_updates.body doesn't work at all and in my HTML file it says "Undefined".
How am I able to get render the data from incident_updates.body?
Thanks guys!
incident_updates is an array, so you will need an index first to get the right element. Try something like this where i is the index of the element you're after:
var i = 0;
item.incident_updates[i].body
Since item.incident_updates is an array you can use map for that
const html = data
.map((item) => {
console.log(item.incident_updates[0]);
return `<div class="card card-space">
<div class="card-body">
<h5 class="card-title">${item.title}</h5>
<h6 class="card-subtitle mb-2 text-muted">${item.start_time}</h6>
{item.incident_updates.map((data)={return(<p class="card-text">${data.body}</p>)})}
</div>
</div>`;
})