index.html
.
The page includes a script in the <head>
to make sure the Delivery JS SDK works. Then there's two more scripts in the <body>
to make the list itself. You'll create these scripts later.
core.js
script will contain code you'll use in multiple places in the app.articleList.js
script will be for code that is unique to this list.style.css
.
core.js
and add two constants that can be used throughout your app.
app
constant finds the app container in the DOM.addToElementById
constant is a function that can be used to create a new element with a specific ID in the DOM and then attach it as the child of another element.articleList.js
and add one constant named articleList
.
This takes the function you added in core.js and uses it to create a <div>
element within the app container with an ID of article-list
. If you now load index.html in a browser and inspect the DOM, you'll be able to see your new element inside the app.
That's a great start! Now let's get some actual content.
core.js
you need to define your Kontent.ai project.
That's just defining an instance of a delivery client for a specific project using the Delivery SDK that you can use anywhere in the app.
Now you can use deliveryClient
inside articleList.js
.
You've retrieved all the articles from the project and logged them in the console. To get an idea of what kind of content you're getting, you can explore the object in the browser console (the list of content is in the items
collection).
So now you've got a page to hold your list and the content that should be listed. It's time to put them together.
core.js
.
This function creates an element in the DOM with a defined class. Then if you include an attribute and a value to look for, it adds those to the element based on the type.
To see the function in use, change your call for the delivery client in articleList.js
as follows.
You're processing each of the items. Inside the forEach
, you can see two examples calling the function you added to core.js
. The first creates a <div>
element with a class of card
to hold each article. The second creates an <a>
element with a class of link
and an href
attribute set to a page with a hash you're getting from the response (read more about the URL pattern value if you're interested – you'll use the value again for building an article page). The card is then added to the article-list
container and the link to the card.
If you open index.html
now, you'll see six rectangles with links inside.
Let's add the rest of the content to your list.
You've created a teaser image, title, and short description and added them all to your link.
Now you know how to create a simple list of items from Kontent.ai and display them with JavaScript. To see the other options available, it's time to create the articles themselves.
article.html
.
This is basically the same as index.html
(core.js
is still included), but instead of articleList.js
you add article.js
. Except that file doesn't exist yet.
article.js
. The code is very similar to what you had in articleList.js
.
articleSlug
constant defines the URL pattern of the article, that is the value you added to the link in the list.articleContainer
, you again add a container to the app.deliveryClient
call, you add an equalsFilter
that ensures you get only the article that matches the hash in the current URL. You should get an object with only one item in it.createElement
function from core.js
to create a header image, title, and body for your article and lastly attach them to the article container.index.html
, you should now be able to click on the articles in the list. You can use the blog header to go back to the list and see all of the articles.
Now you've got a basic blog with a listing and article view that really works. You can stop there if you like or continue on and add in a couple more useful features.
<a>
elements with empty href
attributes, as you can see by opening article.html#coffee-beverages-explained
and inspecting the link.
rteResolver
in article.js
, define a function to handle hypertext links.
Here, you're defining different paths based on the type of link that's being resolved. If there's a link to an article, it will use a path like the one you defined for your article list.
article.js
, add in a queryConfig
with a urlSlugResolver
.
The code implements the URL slug resolver for this specific query, as you can see if you refresh article.html#coffee-beverages-explained
and inspect the link again.
article.js
to refresh the page (and get the new article) whenever the hash changes.
Now your app should be able to handle links between different content items and always display the article you want. To get the full advantage of your structured content, you'll want to make sure your app can handle added structure.
<p>
element, as you can see by opening article.html#coffee-beverages-explained
.
rteResolver
in article.js
define a function to handle resolving.
This function starts by checking if the item you're trying to resolve is a video. Then it checks if a specific host has been selected. It then returns embed code based on the specified hosting provider. If no host has been selected or if the item is not a hosted video, the function returns an empty string for inside the paragraph.
queryConfig
in article.js
.
Now if you open article.html#coffee-beverages-explained
, you'll see an embedded YouTube video about halfway down the page. There's still an empty <p>
element for a tweet, if you'd like to take on an additional challenge.
Your app should have all the basic blog functionalities when everything is working well. You might want to add in some assistance for when things don't go as planned.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<!-- Include the Delivery JS SDK -->
<script type="text/javascript"
src="https://cdn.jsdelivr.net/npm/@kontent-ai/delivery-sdk@14.2.0/dist/bundles/kontent-delivery.umd.min.js"></script>
<title>Kontent.ai JavaScript sample app</title>
<link href="style.css" rel="stylesheet" />
</head>
<body>
<div id="app-header">
<h1><a href="index.html">Articles</a></h1>
</div>
<div id="app"></div>
<!-- Include core functions -- code you'll use in multiple places in the app -->
<script type="text/javascript" src="core.js"></script>
<!-- Include script for the list of articles -- code that is unique to this list -->
<script src="articleList.js"></script>
</body>
</html>
@import url('http://fonts.cdnfonts.com/css/gt-walsheim');
body {
font-family: 'GT Walsheim', sans-serif;
/* padding: 0 20%; */
margin: 0;
padding: 0 0 48px 0;
background-color: #f1f5f9;
}
body a {
color: rgb(219, 60, 0);
}
#app {
margin-left: auto;
margin-right: auto;
}
@media screen and (min-width: 700px) {
#app {
width: 90%;
}
}
@media screen and (min-width: 1200px) {
#app {
width: 60%;
}
}
#app-header {
margin-bottom: 24px;
background-color: rgb(219, 60, 0);
padding: 12px 24px;
color: #ffffff;
line-height: 50px;
}
#app-header .github-link img {
width: 50px;
height: 50px;
float: right;
}
#app-header a {
text-decoration: none;
color: inherit;
border-radius: 25px;
}
#article-list {
display: flex;
flex-wrap: wrap;
gap: 24px;
}
.card {
padding: 24px 32px;
border: solid 1px #e6e6e6;
border-radius: 25px;
background: white;
}
.card-no-link-style a {
color: inherit;
text-decoration: none;
}
.card:hover {
box-shadow: #cfcfcf 0 0 1rem;
}
.card img {
width: 100%;
}
@media screen and (min-width: 700px) {
.card {
flex: 1 1 calc((100% / 2));
}
}
@media screen and (min-width: 1200px) {
.card {
flex: 1 1 calc((100% / 3));
}
}
@media screen and (min-width: 1900px) {
.card {
flex: 1 1 calc((100% / 4));
}
}
.article-teaser {
width: 100%;
}
// Define main container
const app = document.getElementById('app');
// Function for creating and appending elements
const addToElementbyId = (elementType, id, parent) => {
const element = document.createElement(elementType);
element.setAttribute('id', id);
parent.appendChild(element);
return element;
};
// Add list container to app
const articleList = addToElementbyId('div', 'article-list', app);
// Set up Delivery client
const Kk = window['kontentDelivery'];
const deliveryClient = new Kk.createDeliveryClient({
environmentId: '975bf280-fd91-488c-994c-2f04416e5ee3'
});
// Call for a list of all articles
deliveryClient
.items()
.type('article')
.toPromise()
.then(response => {
console.log(response)
});
// Function for adding elements to DOM with specific attributes
const createElement = (elementType, classToAdd, attribute, attributeValue) => {
const element = document.createElement(elementType);
element.setAttribute('class', classToAdd);
// Set attribute value based on the attribute required
attribute === 'href'
? (element.href = attributeValue)
: attribute === 'innerHTML'
? (element.innerHTML = attributeValue)
: attribute === 'innerText'
? (element.innerText = attributeValue)
: attribute === 'src'
? (element.src = attributeValue)
: undefined;
return element;
};
deliveryClient
.items()
.type('article')
.toPromise()
.then(response => {
response.data.items.forEach(item => {
// Create nodes
const card = createElement('div', 'card');
card.classList.add('card-no-link-style');
const link = createElement(
'a',
'link',
'href',
'./article.html#' + item.elements.url_pattern.value
);
// Add nodes to DOM
articleList.appendChild(card);
card.appendChild(link);
});
});
deliveryClient
.items()
.type('article')
.toPromise()
.then(response => {
response.data.items.forEach(item => {
// Create nodes
const card = createElement('div', 'card');
card.classList.add('card-no-link-style');
const link = createElement(
'a',
'link',
'href',
'./article.html#' + item.elements.url_pattern.value
);
const teaser = createElement(
'img',
'article-teaser',
'src',
item.elements.teaser_image.value && item.elements.teaser_image.value.length
? item.elements.teaser_image.value[0].url + '?w=500&h=500'
: undefined
);
const title = createElement(
'h2',
'article-title',
'innerText',
item.elements.title.value
);
const description = createElement(
'div',
'article-description',
'innerHTML',
item.elements.summary.value
);
// Add nodes to DOM
articleList.appendChild(card);
card.appendChild(link);
link.append(teaser, title, description);
});
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<!-- Include the Delivery JS SDK -->
<script
type="text/javascript"
src="https://cdn.jsdelivr.net/npm/@kontent-ai/delivery-sdk@14.2.0/dist/bundles/kontent-delivery.umd.min.js"
></script>
<title>Article</title>
<link href="style.css" rel="stylesheet" />
</head>
<body>
<div id="app-header">
<h1><a href="index.html">Articles</a></h1>
</div>
<div id="app"></div>
<!-- Include core functions -->
<script type="text/javascript" src="core.js"></script>
<!-- Include script for the article -->
<script src="article.js"></script>
</body>
</html>
// Define which article is being retrieved
const articleSlug = location.hash.slice(1);
// Create article container
const articleContainer = addToElementbyId('div', 'article', app);
// Call for article info
deliveryClient
.items()
.type('article')
.equalsFilter('elements.url_pattern', articleSlug)
.toPromise()
.then(response => {
// Check if article found before adding
const article =
response.data.items && response.data.items.length ? response.data.items[0] : undefined;
// Update title
document.title = `Article | ${article.system.name}`;
// Create nodes
const headerImage = createElement(
'img',
'article-header',
'src',
article.elements.teaser_image.value[0].url
);
const title = createElement(
'h2',
'article-title',
'innerText',
article.elements.title.value
);
const richTextElement = article.elements.body_copy;
const rteResolver = Kk.createRichTextHtmlResolver().resolveRichText({
element: richTextElement,
// Here you'll define your resolvers
});
const body = createElement(
'div',
'article-description',
'innerHTML',
rteResolver.html
);
// Add nodes to DOM
articleContainer.append(headerImage, title, body);
return;
});
<a data-item-id="3120ec15-a4a2-47ec-8ccd-c85ac8ac5ba5" href="">so rich as today</a>
const rteResolver = Kk.createRichTextHtmlResolver().resolveRichText({
element: richTextElement,
linkedItems: Kk.linkedItemsHelper.convertLinkedItemsToArray(response.data.linkedItems),
urlResolver: (linkId, linkText, link) => {
// Set link based on type
const urlLocation =
link.type === 'article'
? `article.html#${link.urlSlug}`
: link.type === 'coffee'
? `coffee.html#${link.urlSlug}`
: 'unsupported-link';
return { linkUrl: urlLocation };
},
});
deliveryClient
.items()
.type('article')
.equalsFilter('elements.url_pattern', articleSlug)
.queryConfig({
urlSlugResolver: (link, context) => {
return resolveUrl(link);
},
})
.toPromise()
// Continue as above
// Reload page on hash change
const renderHash = () => {
window.history.go();
};
window.addEventListener('hashchange', renderHash, false);
<p class="kc-linked-item-wrapper"></p>
const rteResolver = Kk.createRichTextHtmlResolver().resolveRichText({
element: richTextElement,
linkedItems: Kk.linkedItemsHelper.convertLinkedItemsToArray(response.data.linkedItems),
urlResolver: (linkId, linkText, link) => {
// Your link resolution logic
},
contentItemResolver: (itemId, item) => {
if (item.system.type === 'hosted_video') {
const videoID = item.elements.video_id.value;
// Check if a video host exists
const videoHost =
item.elements.video_host.value && item.elements.video_host.value.length
? item.elements.video_host.value[0].codename
: undefined;
if (videoHost) {
// Return based on hosting provider
const htmlCode = videoHost === 'youtube'
? `<iframe src='https://www.youtube.com/embed/${videoID}' width='560' height='315' frameborder='0'></iframe>`
: `<iframe src='https://player.vimeo.com/video/${videoID}' width='560' height='315' allowfullscreen frameborder='0'></iframe>`;
return {
contentItemHtml: htmlCode
};
}
}
return {
contentItemHtml: ''
};
}
});
deliveryClient
.items()
.type('article')
.equalsFilter('elements.url_pattern', articleSlug)
.queryConfig({
urlSlugResolver: (link, context) => {
return resolveUrl(link);
},
richTextResolver: (item, context) => {
return resolveLinkedItems(item);
}
})
.toPromise()
// Continue as above