Let's build: Art Recommending App in Vanilla JS using the Harvard Art API - part 4: Art & Error handling
Tags: javascript
Published: 2020-03-08 12:39:19

<a href="https://sanderdev.nl/projects/smartart/">View demo</a>
<a href="https://github.com/sanderdebr/smartart">View code</a>

## 7. Art detail information and error handling

In this section we will create the bottom part where information about the artwork is displayed, which is currently hard coded information.

### 7.1 Setting up event listener

Let’s add three new query selector in our elements.js file

``` javascript
year: document.querySelector('.year'),
artist: document.querySelector('.content__h2'),
title: document.querySelector('.content__h5')
```

Then create a new view file called detailView.js. Here we will add the renderDetails method which we will call in index.js after the user has clicked on an artwork.

In the renderPaintings method we will now set the year, title and description as data attributes on the parent div of the image by the following code:

``` javascript
// Replace paintings
paintings.forEach((painting, i) => {
    const imgPath = paintings[i].primaryimageurl;
    const artist = paintings[i].title;
    const year = paintings[i].accessionyear;
    const desc = paintings[i].medium;
    if(imgPath) {
        elements.paintingImg[i].src = imgPath;
        elements.paintingImg[i].parentNode.setAttribute('data-year', year);
        elements.paintingImg[i].parentNode.setAttribute('data-desc', desc);
        elements.paintingImg[i].parentNode.setAttribute('data-artist', artist);
    }
})
```

In index.js we will then send the clicked painting information to the renderDetails function:

``` javascript
// GET ART DETAILS
let newPaintings = document.querySelectorAll('.painting');
newPaintings.forEach(painting => {
   painting.addEventListener('click', () => {
        renderDetails(painting)
   });
});
```

In detailView.js we can then easily update our detail information:

``` javascript
import { elements } from './elements';

export const renderDetails = painting => {
    elements.year.innerHTML = painting.dataset.year;
    elements.artist.innerHTML = painting.dataset.artist;
    elements.title.innerHTML = painting.dataset.desc;
}
```

Great! Our detail information is working.

### 7.2 Default artwork information with async await

Let’s adSetting up the default information is a bit tricky, because we only can get the data attributes of the artworks when they are loaded. So that is why we’ll be using an async await functionality. Let’s first return true in the controlSettings function when it has been completed. Make sure to include the removeLoader function before calling the return, otherwise the loader will stay on screen. Then in the init function we’ll add:

``` javascript
// Render default information
async function renderDefault() {
    try {
        const response = await controlSettings();
        if (response) {
            let defaultPainting = document.querySelector('.painting:first-child');
            renderDetails(newPaintings[0]);
        }
    }
    catch (err) {
        console.log('renderdefault failed', err);
    }
}
    
renderDefault();  
```

The function above waits for the controlSettings function to finish, and then retrieves the artwork data from the rendered artworks.

### 7.3 Error handling 

First let’s remove the current detail information when a new search has been called. We’ll add removeDetails in the detailView.js page

``` javascript
export const clear = () => {
    elements.year.innerHTML = '';
    elements.artist.innerHTML = 'Please select an artwork';
    elements.title.innerHTML = '';
}
```

And we’ll add it in the controlSettings function

``` javascript
// Remove current paintings and detail information
paintingView.clear();
detailView.clear();
```

Also if no results are returned:

``` javascript
export const noResults = () => {
    elements.year.innerHTML = '';
    elements.artist.innerHTML = 'No results found';
    elements.title.innerHTML = '';
}
```

Our renderPaintings then looks like this:

``` javascript
export const renderPaintings = paintings => {

    if (paintings.length > 1) {

            // Show paintings again
            elements.paintings.forEach(painting => {
                painting.style.opacity = 1;
            })

            // Replace paintings
            paintings.forEach((painting, i) => {
                const imgPath = paintings[i].primaryimageurl;
                const artist = paintings[i].title;
                const year = paintings[i].accessionyear;
                const desc = paintings[i].medium;
                if(imgPath) {
                    elements.paintingImg[i].src = imgPath;
                    elements.paintingImg[i].parentNode.setAttribute('data-year', year);
                    elements.paintingImg[i].parentNode.setAttribute('data-desc', desc);
                    elements.paintingImg[i].parentNode.setAttribute('data-artist', artist);
                } 
            })
    } else {
        detailView.noResults();
        removeLoader();
        throw "No images found";
    }
}
```

Sometimes null or defined gets displayed on the default artwork. If we do not have that information we’ll ask the user to select an artwork first. This is an easy fix by changing our renderDetails function and checking if all the artwork data is actually available:

``` javascript
export const renderDetails = painting => {
    if (painting.dataset.year && painting.dataset.artist && painting.dataset.desc) {
        elements.year.innerHTML = painting.dataset.year;
        elements.artist.innerHTML = painting.dataset.artist;
        elements.title.innerHTML = painting.dataset.desc;
    } else {
        clear();
    }
}
```

### 7.4 Fixing text breaking

Some artworks have a long title and description which breaks our layout. 

![My helpful screenshot](https://sanderdev.nl/blog/assets/7.png)

Let’s set the font of .info .content .content__h2 to 2.3em so it still looks good on mobile.

![My helpful screenshot](https://sanderdev.nl/blog/assets/8.png)

### 7.5 Adding styles to selected artwork

It’s currently unclear which artwork the user has selected, let’s change that. In our renderDetails method inside detailView.js we’ll call the showCurrent function on the current painting that just has been clicked:

``` javascript
export const renderDetails = painting => {
    if (painting.dataset.year && painting.dataset.artist && painting.dataset.desc) {
        elements.year.innerHTML = painting.dataset.year;
        elements.artist.innerHTML = painting.dataset.artist;
        elements.title.innerHTML = painting.dataset.desc;
        paintingView.showCurrent(painting);
    } else {
        clear();
    }
}
```

Then we’ll add the showCurrent painting in paintingView by toggling a painting--active class and adding it on the current painting:

``` javascript
// Show current painting
export const showCurrent = currentPainting => {
    Array.from(elements.paintings).forEach(painting => {
        painting.classList.remove('painting--active');
    });
    currentPainting.classList.add('painting--active');
}
```
 
I’ve added the following classes in the css file for the active painting:

``` javascript
.painting {
    margin: 0 5em;
    transition: all 250ms ease;
    border:solid 2px;
    border-bottom-color:#ffe;
    border-left-color:#eed;
    border-right-color:#eed;
    border-top-color:#ccb;
    transform: scale(.8);
    img {
        margin: 10px;
    }
}
.painting:hover {
    cursor: pointer;
    transform: scale(1.2);
}
.painting--active {
    transform: scale(1.2);
}
```

Then for the frame around the artworks, I found a nice little pure CSS code:

``` javascript
// CSS art frame
.frame {
    background-color:#ddc;
    border:solid 5vmin #eee;
    border-bottom-color:#fff;
    border-left-color:#eee;
    border-radius:2px;
    border-right-color:#eee;
    border-top-color:#ddd;
    box-shadow:0 0 5px 0 rgba(0,0,0,.15) inset, 0 5px 10px 5px rgba(0,0,0,.15);
    box-sizing:border-box;
    display:inline-block;
    max-height: 40vh;
    max-width: 30vw;
    position:relative;
    text-align:center;
    &:before {
      border-radius:2px;
      bottom:-2vmin;
      box-shadow:0 2px 5px 0 rgba(0,0,0,.15) inset;
      content:"";
      left:-2vmin;
      position:absolute;
      right:-2vmin;
      top:-2vmin;
    }
    &:after {
      border-radius:2px;
      bottom:-2.5vmin;
      box-shadow: 0 2px 5px 0 rgba(0,0,0,.15);
      content:"";
      left:-2.5vmin;
      position:absolute;
      right:-2.5vmin;
      top:-2.5vmin;
    }
  }
```