[
{
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>
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 set up an example in which a series of news items will be passed in using ajax in a json format. At the moment I am just using a function to simulate returned data.
Here is the jsfiddle: http://jsfiddle.net/c8b4naL5/
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
<span data-bind="foreach: { data: newsItems, as: 'item' }" >
<!-- <span data-bind="foreach: { data: items, as: 'item' }"> -->
<div class="news-item">
<span data-bind='text:item.title'></span>
</div>
</span>
<script type="text/javascript">
function NewsItemsCall(){
return {
newsItemsFromCall: [
{title:'First Title From call'},
{title:'Second Title From call'}
]
}
}
function NewsItem(newsItemsCall){
var map = ko.mapping.fromJS(newsItemsCall);
return map;
}
var viewModel = {
newsItems:ko.observableArray([new NewsItem(new NewsItemsCall())])
}
ko.applyBindings(viewModel);
</script>
The ko.toJSON displays the following:
{
"newsItems": [
{
"newsItemsFromCall": [
{
"title": "First Title From call"
},
{
"title": "Second Title From call"
}
],
"__ko_mapping__": {
"ignore": [],
"include": [
"_destroy"
],
"copy": [],
"observe": [],
"mappedProperties": {
"newsItemsFromCall[0].title": true,
"newsItemsFromCall[1].title": true,
"newsItemsFromCall": true
},
"copiedProperties": {}
}
}
]
}
At this point I am just trying to get it to work to display the data in the template. Any insights would be appreciated. Thanks in advance.
Well the modification required could be approached from either the data side or the client side. At face value, your view isn't matched up to the data due to newsItems containing an array of newItemsFromCall. If the data is in the correct format, then just add another foreach binding.
Modifying the data
NewsItemsCall could return an array instead of an object
be aware of the return of the mapping call depending on how you will be using that value elsewhere
Modifying the UI
<span data-bind="foreach: { data: newsItems, as: 'item' }" >
<div data-bind='foreach: item.newsItemsFromCall'>
<span data-bind='text: title'></span>
</div>
</span>
Modified fiddle with changes to the data structure. I also included an alternate approach that maps the fromJS call directly as a viewmodel.
Example of mocking json calls in a fiddle.
I have a list of products where the name is a link to the product's details view. The list of products is the "Results" view
Samsumg
iPhone
When the user clicks on a product, the "Details" template is shown, and the "Results" template is not shown; at least that is the behavior that I want.
I am using the following code to accomplish this behavior, and have the jsFiddle here http://jsfiddle.net/justinnafe/mLf5G/:
<div data-bind="template: displayMode"></div>
<script type="text/html" id="Result">
<ul data-bind="foreach: products">
<li></li>
</ul>
</script>
<script type="text/html" id="Details">
<p data-bind="text: name"></p>
<p data-bind="text: description"></p>
</script>
and the javascript:
var view = {
name: "Result"
};
var initialProducts = [{
name: "Samsumg",
description: "The best phone"
},{
name: "iPhone",
description: "The other best phone"
}];
var viewModel = (function (){
var products = ko.observableArray(initialProducts),
displayMode = ko.observable(view),
switchDisplayMode = function(item){
if (displayMode() == 'Result') {
displayMode({ name: "Details", data: item });
}
else {
displayMode({ name: "Result", data: item });
}
};
return {
products: products,
displayMode: displayMode,
switchDisplayMode: switchDisplayMode
};
})();
ko.applyBindings(viewModel);
I am trying to pass that product to the Details template, but have been unsuccessful. Any clues or tips would be helpful.
I am currently getting a "ReferenceError: products is not defined" error when I click on a link, but not sure how to fix it. Maybe if I fix that error, the switching views will behave as expected.
In your function to switch the template, you are forgetting that your displayMode observable is holding an object - not a string value.
So inside switchDisplayMode, displayMode() = { name: 'Result' }. Switching that to displayMode().name fixes the problem. See updated fiddle
I'm learning ember these days and I encountered a problem with link-to helper. If I use it to create a link for nested route it works fine (if click on the link, "active" class will be added to the element - as described in docs) until I reload the page. When I reload the page the content for nested rouse will be loaded to the {{outlet}} properly but link will lose its "active" class. What am I doing wrong?
JavaScript:
window.App = Ember.Application.create({ rootElement: '#app' });
App.Router.map(function () {
this.resource('notes', { path: '/' }, function () {
this.route('show', { path: '/:note_id' });
});
});
App.NotesRoute = Em.Route.extend({
model: function () {
return App.Note.find();
}
});
App.NotesShowRoute = Em.Route.extend({
model: function (params) {
return App.Note.find(params.note_id);
}
});
App.Note = Em.Object.extend();
App.Note.reopenClass({
find: function(id) {
var notes = [
{
id: 1,
title: 'abc',
text: 'lorem ipsum text 1111111'
},
{
id: 2,
title: 'def',
text: 'lorem ipsum text 2222222'
}
];
return id ? notes[parseInt(id) - 1] : notes;
}
});
HTML:
<div id="app" class="row">
<script type="text/x-handlebars">
<div class="col-md-2">
<h2>Tags</h2>
</div>
{{outlet}}
</script>
</div>
<script type="text/x-handlebars" data-template-name="notes">
<div class="col-md-3">
<h2>Notes</h2>
{{#each}}
{{#link-to 'notes.show' this}}{{title}}{{/link-to}}
{{/each}}
</div>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="notes/show">
<div class="col-md-7">
<h2>{{title}}</h2>
<p>{{text}}</p>
</div>
</script>
When you click a link-to, it passes the object to the new route. So the model lookup isn't called. So both the context of the show route and the linked object refer to the same object. So it will get marked as active.
However, when you refresh the page, you're doing the lookup twice, once in the NotesRoute model (which you loop over with each), and once in the NotesShowRoute model.
Javascript objects are reference types. Two plain javascript objects aren't considered equal, even if their content is the same. e.g. try typing this into your javascript console.
{ one: 1, two: 2} == {one: 1, two: 2}
So the object referred to in the link-to isn't the same as the model of the current route. So the equality check for the link being active won't work.
Quick solution is to stop the find from creating the object every time. e.g.
App.Note.reopenClass({
all: [
{
id: 1,
title: 'abc',
text: 'lorem ipsum text 1111111'
},
{
id: 2,
title: 'def',
text: 'lorem ipsum text 2222222'
}
],
find: function(id) {
return id ? this.all[parseInt(id) - 1] : this.all;
}
});
Another options is to roll some sort of identity map for your objects. Here is a blog post doing a much better example than I can of explaining it.
Note I haven't actually tested that code because I'm too lazy to create a jsbin. But let me know if it doesn't work.
I'm currently using underscore templates to render a HTML list that displays a list of contacts.
The template looks something like this:
<li>
<span class="name">Name: <=%data.name%></span>
<span class="email">Name: <=%data.email%></span>
<img class="avatar" src="<=%data.avatar%>"></img>
</li>
The issue is, when I set the template data, the source of the image won't be known. Why? Because my data looks something like this:
contact = {
name: string, // i.e. 'John Doe'
email: string, // i.e 'john#doe.com'
avatar: string // i.e. '11a93150-14d4-11e3'
}
The avatar is actually not a URL, rather a link to a remote database that needs fetching. Something like:
function getAvatar(uuid, cb) { // uuid is something like 11a93150-14d4-11e3
window.db.getImageUrl(function(url) {
cb(url); // url is something like http://foo.com/avatar.png
});
}
Question is, is there a way to write my template so that instead of reading the avatar value of the contact object directly, I can embed a reference to a function like getAvatar that when the template is rendered, fetches the url to the image and sets the avatar image URL?
Thanks in advance
Here's an example to demonstrate how you can call JavaScript functions and asynchornously update src attribute of thumbnails. I've tried to simulate your DB call using setTimeout and the DB using associative array.
HTML:
<script type='text/html' id='contactTemplate'>
<li id="contact-<%= avatar %>">
<span class = "name"> Name: <%= name %> </span>
<span class="email">Name: <%= email %></span>
<img class = "avatar" data-populate-path="<% getPath( avatar ) %>" />
</li>
</script>
<ul id='contactList'></ul>
JavaScript:
var contacts = [
{name: 'John Doe', email: 'john#doe.com', avatar: '11a93150-14d4-11e3'},
{name: 'Hannah Smith', email: 'hannah#smith.com', avatar: '11a93150-14d4-1231' }
],
simulatedDB = [];
simulatedDB['11a93150-14d4-11e3'] = "path to avatar 1";
simulatedDB['11a93150-14d4-1231'] = "path to avatar 2";
$(document).ready(function () {
var compiled = _.template($("#contactTemplate ").html());
_.each(contacts, function (d, i) {
$("#contactList").append(compiled(d));
});
});
function getPath(target) {
setTimeout(updateAvatar, 1000, target);
}
function updateAvatar(target) {
$("#contact-"+target+" img").attr("src", simulatedDB[target]);
}