Skip navigation

Building your first plain JavaScript app

This tutorial will go through how to build a simple blog using only plain JavaScript with all of the content stored in Kentico Kontent. You won't need to install any libraries or packages, use any frameworks, or run any servers. You just include the Kentico Kontent Delivery SDK to get all your content when you need it.

See a live example of the blog you'll be making or check out the source code on GitHub.

New to Kentico Kontent?

If you're starting with Kentico Kontent, we suggest you begin with the Hello World tutorial, in which you'll learn the basics of creating content.

Table of contents

    Requirements

    All you'll need for this tutorial is a text editor (though you can use an IDE like Visual Studio Code if you prefer). The JavaScript used works on most modern browsers, so as long as you've moved on from Internet Explorer, you should be fine.

    The blog initially pulls content from a shared Kentico Kontent project. If you want to connect the blog to your own Kentico Kontent project to edit the content, you'll need a subscription along with a Sample Project that any subscription admin can generate.

    Create an article list

    1. Create your index

    The first thing to do for the blog is to create a list of articles. Start by creating the basic shell for your app in a new file called index.html:

    • HTML
    <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <!-- Include the Delivery SDK and its dependency --> <script type="text/javascript" src="https://unpkg.com/rxjs@6.4.0/bundles/rxjs.umd.min.js" ></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@kentico/kontent-delivery@7.0.0/_bundles/kontent-delivery.browser.umd.min.js" ></script> <title>Plain JavaScript Blog</title> <link href="style.css" rel="stylesheet" /> </head> <body> <div id="app-header"><h1>Our Blog</h1></div> <div id="app"></div> <!-- Include core functions --> <script type="text/javascript" src="core.js"></script> <!-- Include script for the list of articles --> <script src="articleList.js"></script> </body> </html>
    <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <!-- Include the Delivery SDK and its dependency --> <script type="text/javascript" src="https://unpkg.com/rxjs@6.4.0/bundles/rxjs.umd.min.js" ></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@kentico/kontent-delivery@7.0.0/_bundles/kontent-delivery.browser.umd.min.js" ></script> <title>Plain JavaScript Blog</title> <link href="style.css" rel="stylesheet" /> </head> <body> <div id="app-header"><h1>Our Blog</h1></div> <div id="app"></div> <!-- Include core functions --> <script type="text/javascript" src="core.js"></script> <!-- Include script for the list of articles --> <script src="articleList.js"></script> </body> </html>

    If you'd like your list to appear as pretty (or ugly, depending on your point of view) as the example, you can use the same CSS. Create a file called style.css:

    • CSS
    body { font-family: "Segoe UI", "Helvetica Neue", sans-serif; padding: 0 20%; } #app-header { background-color: #231f20; height: 6em; padding: 20px; color: #ffffff; } #app-header a { text-decoration: none; color: inherit; } #article-list { display: flex; flex-wrap: wrap; } .card { margin: 1rem; padding: 1rem; border: 1px solid #231f20; border-radius: 2.5%; } .card:hover { transform: translate3d(0, -0.5rem, 0); box-shadow: grey 0 0 1rem; } @media screen and (min-width: 700px) { .card { flex: 1 1 calc(50% - 8rem); } } @media screen and (min-width: 1200px) { .card { flex: 1 1 calc(33% - 4rem); } } .card a { text-decoration: none; color: inherit; } .article-teaser { width: 100%; }
    body { font-family: "Segoe UI", "Helvetica Neue", sans-serif; padding: 0 20%; } #app-header { background-color: #231f20; height: 6em; padding: 20px; color: #ffffff; } #app-header a { text-decoration: none; color: inherit; } #article-list { display: flex; flex-wrap: wrap; } .card { margin: 1rem; padding: 1rem; border: 1px solid #231f20; border-radius: 2.5%; } .card:hover { transform: translate3d(0, -0.5rem, 0); box-shadow: grey 0 0 1rem; } @media screen and (min-width: 700px) { .card { flex: 1 1 calc(50% - 8rem); } } @media screen and (min-width: 1200px) { .card { flex: 1 1 calc(33% - 4rem); } } .card a { text-decoration: none; color: inherit; } .article-teaser { width: 100%; }

    The page also includes two scripts in the <head> to make sure the Delivery SDK works. Then there's two more scripts in the <body> to make the list itself. The code.js script will contain code you'll use in multiple places in the app so you don't have to repeat yourself, while articleList.js is for code that is unique to this list. Now you have to create these scripts.

    2. Create a list container

    To hold your list, create a list container within the app. Start by creating a file in the same directory called core.js and add two constants that can be used throughout your app:

    • JavaScript
    // 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; };
    // 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; };

    The first constant should be pretty self-explanatory as it just finds the app container in the DOM. The second 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. 

    If that function seems too abstract, you can see how it works in practice. Create another file in the same directory called articleList.js and add one constant:

    • JavaScript
    // Add list container to app const articleList = addToElementbyId("div", "article-list", app);
    // Add list container to app const articleList = addToElementbyId("div", "article-list", app);

    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 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 and all, but you'd probably like some actual content.

    3. Call content from Kentico Kontent

    To get your content, first you need to define where you're calling from in core.js:

    • JavaScript
    // Set up Kontent delivery const Kk = window["kontentDelivery"]; const deliveryClient = new Kk.DeliveryClient({ projectId: "975bf280-fd91-488c-994c-2f04416e5ee3" });
    // Set up Kontent delivery const Kk = window["kontentDelivery"]; const deliveryClient = new Kk.DeliveryClient({ projectId: "975bf280-fd91-488c-994c-2f04416e5ee3" });

    That's just defining an instance of a delivery client for a specific project using the Kentico Kontent Delivery SDK that you can use anywhere in the app. Now you can call it inside articleList.js:

    • JavaScript
    // Call for a list of all articles deliveryClient .items() .type("article") .toPromise() .then(response => { console.log(response)});
    // Call for a list of all articles deliveryClient .items() .type("article") .toPromise() .then(response => { console.log(response)});

    As you can see, you've called 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.

    4. Add content to your list

    To put the content into the list, first create a useful helper function in core.js:

    • JavaScript
    // 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; };
    // 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; };

    Although this function may look complex, what it does is mostly straightforward. It 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. 

    Again, it's often better to see it in use so let's change your call for the delivery client in articleList.js:

    • JavaScript
    deliveryClient .items() .type("article") .toPromise() .then(response => { response.items.forEach(item => { // Create nodes const card = createElement("div", "card"); const link = createElement( "a", "link", "href", "./article.html#" + item.url_pattern.value ); // Add nodes to DOM articleList.appendChild(card); card.appendChild(link); }); });
    deliveryClient .items() .type("article") .toPromise() .then(response => { response.items.forEach(item => { // Create nodes const card = createElement("div", "card"); const link = createElement( "a", "link", "href", "./article.html#" + item.url_pattern.value ); // Add nodes to DOM articleList.appendChild(card); card.appendChild(link); }); });

    Now instead of just logging the response you get, 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 (assuming you added the same CSS) six rectangles with links inside. If you understand the code in the last example, you can move on to add in the rest of the content to your list:

    • JavaScript
    deliveryClient .items() .type("article") .toPromise() .then(response => { response.items.forEach(item => { // Create nodes const card = createElement("div", "card"); const link = createElement( "a", "link", "href", "./article.html#" + item.url_pattern.value ); const teaser = createElement( "img", "article-teaser", "src", item.teaser_image.value && item.teaser_image.value.length ? item.teaser_image.value[0].url + "?w=500&h=500" : undefined ); const title = createElement( "h2", "article-title", "innerText", item.title.value ); const description = createElement( "div", "article-description", "innerHTML", item.summary.value ); // Add nodes to DOM articleList.appendChild(card); card.appendChild(link); link.append(teaser, title, description); }); });
    deliveryClient .items() .type("article") .toPromise() .then(response => { response.items.forEach(item => { // Create nodes const card = createElement("div", "card"); const link = createElement( "a", "link", "href", "./article.html#" + item.url_pattern.value ); const teaser = createElement( "img", "article-teaser", "src", item.teaser_image.value && item.teaser_image.value.length ? item.teaser_image.value[0].url + "?w=500&h=500" : undefined ); const title = createElement( "h2", "article-title", "innerText", item.title.value ); const description = createElement( "div", "article-description", "innerHTML", item.summary.value ); // Add nodes to DOM articleList.appendChild(card); card.appendChild(link); link.append(teaser, title, description); }); });

    Now you've created a teaser image, title, and short description and added them all to your link. If you open index.html, you should see the same list as in the live example.

    Now you know how to create a simple list of items from Kentico Kontent and display them with JavaScript. That's a great start. To see the other options available, it's time to create the articles themselves.

    Create an article page

    1.Create a page for articles

    To display individual articles, create a file called article.html:

    • JavaScript
    <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <!-- Include the Delivery SDK and its dependency --> <script type="text/javascript" src="https://unpkg.com/rxjs@6.4.0/bundles/rxjs.umd.min.js" ></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@kentico/kontent-delivery@7.0.0/_bundles/kontent-delivery.browser.umd.min.js" ></script> <title>Plain JavaScript Blog</title> <link href="style.css" rel="stylesheet" /> </head> <body> <div id="app-header"> <h1><a href="index.html">Our Blog</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>
    <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <!-- Include the Delivery SDK and its dependency --> <script type="text/javascript" src="https://unpkg.com/rxjs@6.4.0/bundles/rxjs.umd.min.js" ></script> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@kentico/kontent-delivery@7.0.0/_bundles/kontent-delivery.browser.umd.min.js" ></script> <title>Plain JavaScript Blog</title> <link href="style.css" rel="stylesheet" /> </head> <body> <div id="app-header"> <h1><a href="index.html">Our Blog</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>

    This is basically the same as index.html (core.js is still included), but instead of articleList.js you've added article.js. Except that file doesn't exist yet.

    2. Create the basic article structure

    Create another file called article.js:

    • JavaScript
    // 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.items && response.items.length ? response.items[0] : undefined; // Create nodes const headerImage = createElement( "img", "article-header", "src", article.teaser_image.value[0].url + "?w=500&h=500" ); const title = createElement( "h2", "article-title", "innerText", article.title.value ); const body = createElement( "div", "article-description", "innerHTML", article.body_copy.resolveHtml() ); // Add nodes to DOM articleContainer.append(headerImage, title, body); return; })
    // 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.items && response.items.length ? response.items[0] : undefined; // Create nodes const headerImage = createElement( "img", "article-header", "src", article.teaser_image.value[0].url + "?w=500&h=500" ); const title = createElement( "h2", "article-title", "innerText", article.title.value ); const body = createElement( "div", "article-description", "innerHTML", article.body_copy.resolveHtml() ); // Add nodes to DOM articleContainer.append(headerImage, title, body); return; })

    Most of this code is very similar to what you had in articleList.js. The first constant defines the URL pattern of the article (the value you added to the link in the list). After that, you again add a container to the app and then call content from Kentico Kontent using the delivery client from core.js. The difference in the initial call is that you've added an equalsFilter that ensures you get only the article that matches the hash in the current URL. So you should get an object with only one item in it.

    In the response, you first check if the hash matches an article (so you don't get an exception from an empty array). Then you use the createElement function from core.js to create a header image, title, and body for your article and lastly attach them to the article container.

    If you load index.html, you should now be able to click on the articles in the list and see them display basically the same as in the live example. 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.

    Resolve links to content items

    One of the great features of Kentico Kontent is the ability to create links within content that don't rely on any front-end logic. So you don't have to hard code specific URLs into your content, but rather add a link to a content item.

    Because these links are independent of front-end logic, if you leave them alone, they'll resolve as <a> elements with empty href attributes, as you can see by opening article.html#coffee-beverages-explained and inspecting the link:

    • HTML
    <a data-item-id="3120ec15-a4a2-47ec-8ccd-c85ac8ac5ba5" href="">so rich as today</a>
    <a data-item-id="3120ec15-a4a2-47ec-8ccd-c85ac8ac5ba5" href="">so rich as today</a>

    1. Define a resolver function

    To get these links to resolve exactly as you'd like them, you can take advantage of the SDK's resolvers. In core.js, define a function to handle this:

    • JavaScript
    // Set link based on type const resolveUrl = link => { urlLocation = link.type === "article" ? `article.html#${link.urlSlug}` : link.type === "coffee" ? `coffee.html#${link.urlSlug}` : "unsupported-link"; return { url: urlLocation }; };
    // Set link based on type const resolveUrl = link => { urlLocation = link.type === "article" ? `article.html#${link.urlSlug}` : link.type === "coffee" ? `coffee.html#${link.urlSlug}` : "unsupported-link"; return { url: urlLocation }; };

    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. The Sample Project comes with links to a type for coffees, so this function includes a path for those links. You can see the basic message in the GitHub source code for coffee links. You can also create a page yourself based on the model of article.js.

    2. Add the function to your call

    In article.js, add in a queryConfig with a urlSlugResolver:

    • JavaScript
    deliveryClient .items() .type("article") .equalsFilter("elements.url_pattern", articleSlug) .queryConfig({ urlSlugResolver: (link, context) => { return resolveUrl(link); } }) .toPromise() // Continue as above
    deliveryClient .items() .type("article") .equalsFilter("elements.url_pattern", articleSlug) .queryConfig({ urlSlugResolver: (link, context) => { return resolveUrl(link); } }) .toPromise() // Continue as above

    This implements the resolver for this specific query, as you can see if you refresh article.html#coffee-beverages-explained and inspect the link again. (Note that it's also possible to define resolvers globally through classes, but you don't need to do that here.)

    3. Handle routing

    To make the link work when you click on it, add code to article.js to refresh the page (and so call the new article) whenever the hash changes:

    • JavaScript
    // Reload page on hash change const renderHash = () => { window.history.go(); }; window.addEventListener("hashchange", renderHash, false);
    // Reload page on hash change const renderHash = () => { window.history.go(); }; window.addEventListener("hashchange", renderHash, false);

    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.

    Resolve linked items and components

    The body of each article is a rich text element. One of the benefits of structuring your content with Kentico Kontent is you can have clearly predictable structure without having to know exactly what the content will be (read more in our article on dealing with structure in rich text). For these articles, that means being able to add things like embedded tweets and videos to the page. You can see an example of tweets in the mentioned article, so here just make sure videos are embedded correctly.

    Before you do anything, your embedded video will appear as an empty <p> element, as you can see by opening article.html#coffee-beverages-explained:

    • HTML
    <p class="kc-linked-item-wrapper"></p>
    <p class="kc-linked-item-wrapper"></p>

    1. Define a resolver function

    To fill in this element with an embedded video, in core.js define a function to handle resolving:

    • JavaScript
    // Resolved hosted videos const resolveLinkedItems = item => { if (item.system.type === "hosted_video") { const videoID = item.video_id.value; // Check if a video host exists const videoHost = item.video_host.value && item.video_host.value.length ? item.video_host.value[0].codename : undefined; if (videoHost) { // Return based on hosting provider return 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 ""; } return ""; };
    // Resolved hosted videos const resolveLinkedItems = item => { if (item.system.type === "hosted_video") { const videoID = item.video_id.value; // Check if a video host exists const videoHost = item.video_host.value && item.video_host.value.length ? item.video_host.value[0].codename : undefined; if (videoHost) { // Return based on hosting provider return 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 ""; } return ""; };

    This function starts by looking at whether the item you're trying to resolve is a video. If so, it checks if a specific host has been selected (to avoid an exception from an empty array). It then returns embed code based on the specified hosting provider. Lastly, it returns an empty string for inside the paragraph if no host has been selected or if the item is not a hosted video. (If you want to add code to resolve tweets, do it right above the last return.)

    2. Add the function to your call

    Now you need to include this function in your queryConfig in article.js:

    • JavaScript
    ... .queryConfig({ urlSlugResolver: (link, context) => { return resolveUrl(link); }, richTextResolver: (item, context) => { return resolveLinkedItems(item); } }) ...
    ... .queryConfig({ urlSlugResolver: (link, context) => { return resolveUrl(link); }, richTextResolver: (item, context) => { return resolveLinkedItems(item); } }) ...

    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.

    Now your app should have all the basic blog functionalities when everything is working well. You might want to add in some assistance for if (when) things don't go as planned.

    Handle errors

    Although you can hope everything will run smoothly, from time to time we all encounter errors. So it'd be good to add some basic error handling to your app.

    1. Define error messages

    The first thing to do is add your basic error messages to core.js so you can use them in both your list and individual articles:

    • JavaScript
    // Error messages const notFound = "<p>That article could not be found.</p>"; const unknownError = "<p>An error occured 😞. Check the console for more details.</p>"; const reportErrors = err => { console.error(err); app.innerHTML = unknownError; };
    // Error messages const notFound = "<p>That article could not be found.</p>"; const unknownError = "<p>An error occured 😞. Check the console for more details.</p>"; const reportErrors = err => { console.error(err); app.innerHTML = unknownError; };

    The first two constants are the error messages you'll display in the HTML. The last is a function to handle any errors that come when retrieving content (such as if you have the wrong project ID).

    2. Add error handling to call

    For your article list, you're not calling for a specific article so you just need to include handling of general errors, which you can do at the end of the code in articleList.js:

    • JavaScript
    ... // Add nodes to DOM articleList.appendChild(card); card.appendChild(link); link.append(teaser, title, description); }); }) .catch(err => { reportErrors(err); });
    ... // Add nodes to DOM articleList.appendChild(card); card.appendChild(link); link.append(teaser, title, description); }); }) .catch(err => { reportErrors(err); });

    Any errors will now show up in the console as error objects that you can investigate further.

    For article.js, you'll want to add the same general error handling, but also a response if a specific article isn't found (such as if someone enters a random string after the hash):

    • HTML
    ... // Check if article found before adding const article = response.items && response.items.length ? response.items[0] : undefined; // 404 message if not found if (!article) { app.innerHTML = notFound; return; }
    ... // Check if article found before adding const article = response.items && response.items.length ? response.items[0] : undefined; // 404 message if not found if (!article) { app.innerHTML = notFound; return; }

    Now users will see the notFound message you defined in article.js instead of a blank page.

    And that's it. Now you have your basic app up and running.

    What's next?

    You've seen how  to create a basic app with a list of articles and a view of each specific article. You made sure that linking among different articles worked correctly and added a way to handle added structure inside the articles themselves. You even added error handling so if you add in more functionalities later, you'll be able to deal with whatever comes your way.

    Other challenges you might want to tackle: