The code below outputs all of the p tags with staff-member-title appended
let arr = [];
$("li.staff-directory-department").map((item, index) => {
arr.push({
title: $("p.staff-member-title", index).text()
});
});
console.log(arr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul class="staff-directory">
<li class="staff-directory-department">
<h2 class="staff-directory-department">
Academic Affairs
</h2>
<ul class="staff-directory-department-list">
<li class="staff-member ">
<a href="" class="staff-member-image">
<img src="" />
</a>
<h3 class="staff-member-name">
Meli
</h3>
<p class="staff-member-title">Director of Institutional Research & Effectiveness</p>
<p class="staff-member-email">
</p>
<p class="staff-member-phone">(90</p>
</li>
</ul>
</li>
</ul>
Current output for arrayInfo:
[
{ title: 'Director of Institutional Research & EffectivenessAdministrative AssistantDirector of Online Learning and Educational TechnologyAssociate Vice President of Academics and Strategic InitiativesVice President for Academics & Student Life' },
{ title: 'Director of Institutional Research & EffectivenessAdministrative AssistantDirector of Online Learning and Educational TechnologyAssociate Vice President of Academics and Strategic InitiativesVice President for Academics & Student Life' },
...
]
Expected output:
[
{ title: 'Director of Institutional Research & Effectiveness' },
{ title: 'Administrative Assistant' },
...
]
I believe your problem is that you're creating a dictionary with Javascript, but giving one key to multiple items, and so when that key is called you're pulling up each item associated with that key.
What you may want to try instead is to put all of the Titles into an array, and then associate that array with the key Title, so that when you call the key Title you can sift through the array of all the Titles.
let arr = [];
let titleArr = [];
$("li.staff-directory-department").map((item, index) => {
titleArr.push({
$("p.staff-member-title", index).text()
});
});
arr.push({title: titleArr});
then when you want to grab the titles just call the array using the key:
for(var x = 0; x < titleArr.length; x++){
console.log(arr["title"][x]);
}
You're mixing up the arguments, index comes first in Cheerio.map:
let arr = $('li.staff-directory-department').map((i, el) => {
return { title: $(el).find('p.staff-member-title').text() }
}).get()
It might be simpler to use Array.map
let arr = $('li.staff-directory-department').get().map((el) => {
return { title: $(el).find('p.staff-member-title').text() }
})
Note: get() turns the cheerio object into an array.
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>
[
{
title: "title of post"
author: "some guy"
},
{
title: "title of post",
author: "some guy"
}
]
When a user searches for an article, it might return something like the above. How could I make, using VanillaJS or jQuery, it return a page with a <ul> tag with <li> tags for the data inside for the title and author? I know this is a big question, but I don't need an exact answer, just an idea of what to do! I also tried foreach but couldn't figure it out.
Thanks,
Jude Wilson
As far as I understand your question, you're trying to create a <ul> tag that has many children <li> tags each containing the filtered data title, and author keys.
Well normally I recommend using something like vue.js to manage data bindings for your UI, but since you mentioned you want this accomplished using jQuery or vanilla JS, it's rather simple than you think.
Here is a complete HTML code example, using jQuery:
<html>
<body>
<!-- Posts List Container -->
<div>
<ul id="postsContainer"></ul>
</div>
<!-- Post Template (Just an example use-case) -->
<template id="postTemplate">
<li class="post-meta-data">
<h2 class="post-title"></h2>
<strong class="post-author"></strong>
<em class="post-tags"></em>
</li>
</template>
<!-- Include jQuery -->
<script
src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous"></script>
<!-- Our JS Logic -->
<script>
$(function () {
const
$container = $('#postsContainer'),
// Grabs the HTML content of the "tag" template
template = $('#postTemplate').html(),
// Here you can fetch/return your API's or filtered data
// NOTE: I modified the data to showcase the example.
getData = () => {
return [
{
title: "title of post #1",
author: "some guy",
tags: ['foo', 'bar', 'baz'],
},
{
title: "title of post #2",
author: "some other guy",
tags: ['watermelon', 'apple', 'banana'],
}
];
},
// Here you can call this function to update the UI
// based on the data returned by "getData()"
updateSearchTags = () => {
const postsArray = getData();
// Before adding any new elements to the container, we should empty it first.
$container.empty();
for (const post of postsArray) {
const
{ title, author, tags } = post,
$postEl = $(template);
// Hydrate the template with post data
$postEl.find('.post-title').text(title);
$postEl.find('.post-author').text(author);
$postEl.find('.post-tags').text(tags.join(', '));
// Add the post element to the list
$postEl.appendTo($container);
}
};
// Call the UI Update Function whenever necessary
updateSearchTags();
});
</script>
</body>
</html>
Not too sure what you mean by "return a page" but you could map over the data and set it to the ul with innerHTML
function printData(ele, data) {
ele.innerHTML = data.map(item => `<li>${item.title} - ${item.author}</li>`).join('')
}
const ulEle = document.querySelector('#target')
const data = [
{ title: "title of post", author: "some guy" },
{ title: "title of post", author: "some guy" }
]
printData(ulEle, data)
<ul id="target"></ul>
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.
I'm trying to automate naming conventions used for a reactive dictionary, but I'm stuck not knowing how to access the reactive dictionary key based on the data attribute I fetched on click.
Expected: Use same naming convention for both ReactiveDict keys and data-attribute names on HTML elements so I can fetch the name on the element clicked, take its data-attribute name and automatically know which ReactiveDict key it is because they use the same name.
Result: It's not recognizing the naming convention when trying to set or get from the ReactiveDict because... I don't know. I'm guessing it's because when you create a ReactiveDict, you use this convention template.search.set('generic_name', data) but it won't work if I replace 'generic_name' with the data-attribute name (that doesn't include the single quotes) on click.
Please see example code below and advise if you have any questions, request for more info, or funny jokes since we're all trapped at home avoiding the COVID-19 virus :slight_smile:
home-template.html
<template name="home_template">
<section class="home-template">
<div class="content">
<div class="filter-box">
<ul>
<legend class="sui-category__title">CATEGORY 1</legend>
{{#each option in category_one}}
<li data-filter-value="{{option.value}}" data-category-name="category_one">
<div class="circle">{{formatToNumberWithComma(option.count)}}</div>
<h4>{{option.value}}</h4><input id="filter-1-{{get_this(option)}}" type="checkbox" /><label for="filter-1-{{get_this(option)}}"><i class="fa fa-check"></i></label>
</li>
{{/each}}
</ul>
<span class="more-options">+ More</span>
</div>
<div class="filter-box">
<ul>
<legend class="sui-category__title">CATEGORY 2</legend>
{{#each option in category_two}}
<li data-filter-value="{{option.value}}" data-category-name="category_two">
<div class="circle">{{formatToNumberWithComma(option.count)}}</div>
<h4>{{option.value}}</h4><input id="filter-2-{{get_this(option)}}" type="checkbox" /><label for="filter-2-{{get_this(option)}}"><i class="fa fa-check"></i></label>
</li>
{{/each}}
</ul>
<span class="more-options">+ More</span>
</div>
<div class="filter-box">
<ul>
<legend class="sui-category__title">CATEGORY 3</legend>
{{#each option in category_three}}
<li data-filter-value="{{option.value}}" data-category-name="category_three">
<div class="circle">{{formatToNumberWithComma(option.count)}}</div>
<h4>{{option.value}}</h4><input id="filter-3-{{get_this(option)}}" type="checkbox" /><label for="filter-3-{{get_this(option)}}"><i class="fa fa-check"></i></label>
</li>
{{/each}}
</ul>
<span class="more-options">+ More</span>
</div>
</div>
</section>
</template>
home-template.js
Template.home_template.onCreated(() => {
const template = Template.instance();
template.search = new ReactiveDict();
template.search.set('category_one');
template.search.set('category_two');
template.search.set('category_three');
template.search.set('filter_parameters');
});
Template.home_template.helpers({
formatToNumberWithComma(number) {
return format_w_comma(number);
},
category_one() {
const template = Template.instance();
return template.search.get('category_one')[0].data;
},
category_two() {
const template = Template.instance();
return template.search.get('category_two')[0].data;
},
category_three() {
const template = Template.instance();
return template.search.get('category_three')[0].data;
},
get_this(option) {
const query_to_array = [];
const search_query_word_array = option.value.split(' ');
const search_query_word_array_count = search_query_word_array.length;
for (let i = 0; i < search_query_word_array_count; i++) {
query_to_array[i] = `${search_query_word_array[i]}`;
}
const search_query = query_to_array.join('-');
return search_query;
},
});
Template.home_template.events({
'click .home-template label': function (event, template) {
$(event.currentTarget).parent('li').toggleClass('active');
const category_clicked = $(event.currentTarget).parent('li')[0].dataset.categoryName;
const filter_selected = $(event.currentTarget).parent('li')[0].dataset.filterValue;
const array = template.search.get(category_clicked);
array.indexOf(filter_selected) === -1 ? array.push(filter_selected) : array.splice(array.indexOf(filter_selected), 1);
template.search.set(category_clicked, array);
const filter_parameters = {
filters: {
all: [{
category_one: template.search.get('category_one'),
},
{
category_two: template.search.get('category_two'),
},
{
category_three: template.search.get('category_three'),
},
],
},
};
template.search.set('filter_parameters', filter_parameters);
},
});
Thank you!
You data keys are working, you actually set the data here:
const category_clicked = $(event.currentTarget).parent('li')[0].dataset.categoryName
const filter_selected = $(event.currentTarget).parent('li')[0].dataset.filterValue
const array = template.search.get(category_clicked)
array.indexOf(filter_selected) === -1
? array.push(filter_selected)
: array.splice(array.indexOf(filter_selected), 1)
template.search.set(category_clicked, array)
Consider the following data:
template.search.set('category_one', [
{
data: [
{
value: 'some value here',
count: 100
}
]
}
])
and category_clicked to be category_one and filter_selected to be some value here then the code above will result in the following data:
[
{
data: [
{
value: 'some value here',
count: 100
}
]
},
'some value here'
]
I am not sure how you further process this data but this shows your reactive data is working. The data updated in the helper right after your clicked the label.
I know there are some posts on the subject, but I could get this specific example working,
because of the nesting:
I have a FAQ with 3 levels of nesting, rendered by ng-repeats,
I want to hide the Category and the Subcategory if the query returns no questions.
I made a Fiddle, can anyone help me get this to work?
http://jsfiddle.net/Lp02huqm/
Thanks!
HTML
<div ng-app="faqApp" ng-controller="FaqController">
<input ng-model="query" type="text">
<ul class="collapse-list">
<li ng-repeat="category in faq | filter:matchEveryWord() | orderBy:rankOrder">
<h3>{{category.category}}</h3>
<div>
<ul>
<li ng-repeat="subcategory in category.subcategories | filter:matchEveryWord()">
<h3>{{subcategory.subcategory}}</h3>
<div>
<ul>
<li ng-repeat="question in subcategory.questions | filter:matchEveryWord()">
<h3>{{question.question}}</h3>
<div>{{question.answer}}</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
</li>
</ul>
</div>
Javascript:
var faqApp = angular.module('faqApp', []);
faqApp.controller('FaqController', ['$scope' , function ($scope){
$scope.faq = [
{category: 'Category1',
rank:2,
subcategories: [
{subcategory: 'Subcategory1',
questions: [
{
question: 'this is the first question',
answer: 'this is the answer on the first question'
},
{
question: 'this is the second question',
answer: 'this is the answer on the second question'
}
]
},
{subcategory: 'Subcategory2',
questions: [
{
question: 'this is the first question of the second subcategory',
answer: 'this is the answer on the first question of the second subcategory'
},
{
question: 'this is the second question',
answer: 'this is the answer on the second question'
}
]
}
]
},
{category: 'Category2',
rank:1,
subcategories: [
{subcategory: 'Subcategory3',
questions: [
{
question: 'Apple',
answer: 'the answer to apple'
},
{
question: 'Banana',
answer: 'the answer to banana'
}
]
},
{subcategory: 'Subcategory4',
questions: [
{
question: 'this is the first question of subcategory 4',
answer: 'this is the answer on the first question of subcategory 4'
}
]
}
]
}
];
$scope.rankOrder = 'rank';
$scope.query = '';
$scope.matchEveryWord = function() {
return function( item ) {
var string = JSON.stringify(item).toLowerCase();
var words = $scope.query.toLowerCase();
if(words){
var filterBy = words.split(/\s+/);
if(!filterBy.length){
return true;
}
} else {
return true;
}
return filterBy.every(function (word){
var exists = string.indexOf(word);
if(exists !== -1){
return true;
}
});
};
};
}]);
In your li element, add ng-show to show subcategories only if questions are present. Something like:-
<li ng-repeat="subcategory in category.subcategories | filter:matchEveryWord()" ng-show="areQuestionsPresent(subcategory)">
Now, in your controller, define a method areQuestionsPresent, that will evaluate whether there are any questions or not:-
$scope.areQuestionsPresent = function(category) {
return category.questions.length > 0;
}
I am in a hurry otherwise would have given some more details. Hope this helps.
You can try nesting the two loops together, and only displaying the category title once.
That will change your current setup a bit (you can place your list back, I used a div because it was quicker for me), but it works:
<div ng-repeat="subcategory in category.subcategories | filter:matchEveryWord()">
<ul>
<li ng-repeat="question in subcategory.questions | filter:matchEveryWord()">
<h3 ng-if="$first">{{subcategory.subcategory}}</h3>
<h4 class="bullet">{{question.question}}</h4>
<div>{{question.answer}}</div>
</li>
</ul>
</div>
I updated your fiddle here: http://jsfiddle.net/Lp02huqm/5/