How to get a random card with user input - YGOPRODECK API
Tags: beginners, javascript, api, tutorial
Published: 2023-07-06 05:47:02

## Introduction
Hello, I am Antani, a student of the HBO-ICT program in HZ UAS and today I will show you how to use the [YGOPRODECK API](https://ygoprodeck.com/api-guide/) to generate a random card with user input.

## Goal
Our goal is to be able to choose the type of card we want to generate (Monster, Spell, Trap) and then get a random card based on the user input.

We want to be able to replicate this [project](https://tonevs.github.io/get-ygo-card/)

### Start
Make a folder where you will put the project.
Go into it and then make 3 files.

* **index.html** - for the DOM structure
* **style.css** - for styling, you can use whatever CSS you want (Custom CSS, Tailwind CSS, Bootstrap, etc.)
* **app.js** - for our API requests and dynamic DOM generation

#### HTML

##### Basic Structure
In the html file we are going to add the basic structure of our website. It consists of a header and a main. In the header we have several buttons which on click generate select elements for user input. In the main element we have 4 div elements with different ids.
* div id=**inputs** - here the select elements will be created as well as a button which will make the API call
* div id=**results** - a div which holds the result of the API call
* div id=**card** - here the image of the card will be displayed
* div id=**description** - here the description of the card will be displayed

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Get YGO Card</title>
    <link rel="stylesheet" href="/dist/output.css">
</head>
<body class="h-screen">
    <header class="flex-center w-1/4 float-left h-full bg-ygo-black">
        <nav class="flex flex-col justify-evenly h-full">
            <button onclick="generateMonsterCardDOM()">Monster</button>
            <button onclick="generateSpellCardDOM()">Spell</button>
            <button onclick="generateTrapCardDOM()">Trap</button>
        </nav>
    </header>
    <main class="flex flex-col flex-grow h-full">
        <div id="inputs" class="basis-1/4 p-4 gap-4 flex justify-evenly 
items-center flex-wrap bg-ygo-black/80">
        </div>
        <div id="results" class="flex-center flex-grow 
        gap-5 bg-ygo-black/60">
            <div id="card" class="flex-center basis-2/5">

            </div>
            <div id="description" class="flex-center basis-2/5">

            </div>
        </div>
        
    </main>
    <script src="app.js"></script>
</body>
</html>
```
#### Javascript

##### Functions that we will reuse a lot

###### **cleanElementById** - It will delete all of the parents children

It is mainly used when before generating the new DOM content so that there aren't any duplicates on the page.
```
function cleanElementById(id) {
    const div = document.getElementById(id);
    while (div.firstChild) {
        div.removeChild(div.firstChild);
    }
}
```


###### **fillSelectWithLength** - It will fill the select with option elements with numbers as values 
```
function fillSelectWithLength(select, length, i = 0) {
    for (;i < length; i++) {
        const option = document.createElement('option');
        option.innerHTML = i;
        option.value = i;
        select.append(option);
    }
}
```


###### **fillSelectWithArray** - It will fill the select with option elements with the array content as values 
```
function fillSelectWithArray(select, array) {
    for (let i = 0; i < array.length; i++) {
        const option = document.createElement('option');
        option.innerHTML = array[i];
        option.value = array[i];
        select.append(option);
    }
}
```


###### **wrapWithLabel** - It will wrap the element in a div and also assign it a label with the appropriate text
```
function wrapWithLabel(element, text) {
    const div = document.createElement('div');
    div.className = 'group';
    const label = document.createElement('label');
    label.htmlFor = element.id;
    label.textContent = text;
    div.append(label, element);
    return div;
}
```


##### API Call for a random card based on a query string

When the function is called, based on the user input a fetch request will be sent to the the API and a random card will be returned

The query string contains based on the type of the card
* Monster - level, type, race, attribute (link if type is Link Monster, scale if type contains Pendulum)
* Spell - type (always Spell), race
* Trap - type (always Trap), race

```
async function getYGOCardFromAPI(string) {
    try {
        const response = await fetch(`https://db.ygoprodeck.com/
        api/v7/cardinfo.php?${string}`);
        const jsonData = await response.json();
        const card = jsonData.data[
            Math.floor(Math.random() * jsonData.data.length)
        ];
        return card;
    } catch (error) {
        console.log(error);
    }
}
```


##### Displaying card information after the API is called

This function is called after clicking on a generated button from one of the generate DOM functions. It will check the input and then make a query string which will be used when calling the **getYGOCardFromAPI** function.


```
async function displayYGOCard(inputs) {
    console.log(inputs.type, inputs.data);
    let string;
    
    if (inputs.type.includes('monster')) {
        switch (inputs.type) {
            case 'monster':
                string = `level=${inputs.data[0]}&
                type=${inputs.data[1]}&
                race=${inputs.data[2]}&
                attribute=${inputs.data[3]}`;
                break;
            case 'monsterPendulum':
                string = `level=${inputs.data[0]}&
                type=${inputs.data[1]}&
                race=${inputs.data[2]}&
                attribute=${inputs.data[3]}&
                scale=${inputs.data[4]}`;
                break;
            case 'monsterLink':
                string = `link=${inputs.data[0]}&
                type=${inputs.data[1]}&
                race=${inputs.data[2]}&
                attribute=${inputs.data[3]}`;
                break;
            default:
                break;
        }
    } else if (inputs.type === 'spell') {
        string = `type=spell%20card&race=${inputs.data[0]}`;
    } else if (inputs.type === 'trap') {
        string = `type=trap%20card&race=${inputs.data[0]}`;
    }
    console.log(string);
    const card = await getYGOCardFromAPI(string);

    console.log(card);
    const cardImageDiv = document.getElementById('card');
    const cardImage = document.createElement('img');
    cardImage.src = card.card_images[0].image_url;
    
    const cardDescriptionDiv = document.getElementById('description');
    const cardDescription = document.createElement('p');
    cardDescription.textContent = card.desc;

    cardImageDiv.append(cardImage);
    cardDescriptionDiv.append(cardDescription);
}
```


##### Generate Monster Card DOM

This function is called when you click a button. After clicking it, a select will be made for every endpoint that can be used for monster cards and then filled with the hard-coded data through the use of the two functions - **fillSelectWithLength** and **fillSelectWithArray**. After that the select will be wrapped in a div and assigned with a label using the **wrapWithLabel** function.

At the end a button is generated, when clicked it will get the values of all selects and then call the **displayYGOCard** function. 

```
function generateMonsterCardDOM() {

    const monsterLevelSelect = document.createElement('select');
    monsterLevelSelect.id = 'monsterLevel';
    fillSelectWithLength(monsterLevelSelect, 13, 1);
    const monsterLevelDiv = wrapWithLabel(monsterLevelSelect, 'Level/Rank');

    const monsterTypeSelect = document.createElement('select');
    monsterTypeSelect.id = 'monsterType';
    const types = ['Effect Monster', 'Flip Effect Monster',
     'Flip Tuner Effect Monster', 'Gemini Monster', 'Normal Monster',
      'Normal Tuner Monster', 'Pendulum Effect Monster',
       'Pendulum Flip Effect Monster', 'Pendulum Normal Monster',
        'Pendulum Tuner Effect Monster', 'Ritual Effect Monster',
         'Ritual Monster', 'Spirit Monster', 'Toon Monster',
          'Tuner Monster', 'Union Effect Monster', "Fusion Monster",
           'Link Monster', 'Pendulum Effect Fusion Monster',
            'Synchro Monster', 'Synchro Pendulum Effect Monster', 
            'Synchro Tuner Monster', 'XYZ Monster', 
            'XYZ Pendulum Effect Monster'];
    fillSelectWithArray(monsterTypeSelect, types);


    monsterTypeSelect.addEventListener('change', () => {
        if (monsterTypeSelect.value === 'Link Monster') {
            monsterLevelSelect.disabled = true;
            monsterPenScaleSelect.disabled = true;
            monsterLinkRatingSelect.disabled = false;
        } else {
            if (monsterTypeSelect.value.includes('Pendulum')) {
                monsterPenScaleSelect.disabled = false;
            } else {
                monsterPenScaleSelect.disabled = true;
            }
            monsterLevelSelect.disabled = false;
            monsterLinkRatingSelect.disabled = true;
        }
    });
    const monsterTypeDiv = wrapWithLabel(monsterTypeSelect, 'Type');

    const monsterRaceSelect = document.createElement('select');
    monsterRaceSelect.id = 'monsterRace';
    const races = ['Aqua', 'Beast', 'Beast-Warrior',
    'Creator-God', 'Cyberse', 'Dinosaur', 'Divine-Beast',
    'Dragon', 'Fairy', 'Fiend', 'Fish', 'Insect',
    'Machine','Plant', 'Psychic', 'Pyro', 'Reptile', 'Rock', 'Sea Serpent',
    'Spellcaster', 'Thunder', 'Warrior', 'Winged Beast', 'Wyrm', 'Zombie'];
    fillSelectWithArray(monsterRaceSelect, races);
    const monsterRaceDiv = wrapWithLabel(monsterRaceSelect, 'Race');

    const monsterAttributeSelect = document.createElement('select');
    monsterAttributeSelect.id = 'monsterAttribute';
    const attributes = ['DARK', 'DIVINE', 'EARTH',
     'FIRE', 'LIGHT', 'WATER', 'WIND'];
    fillSelectWithArray(monsterAttributeSelect, attributes);
    const monsterAttributeDiv = wrapWithLabel(
        monsterAttributeSelect,
        'Attribute'
    );


    const monsterLinkRatingSelect = document.createElement('select');
    monsterLinkRatingSelect.id = 'monsterLinkRating';
    fillSelectWithLength(monsterLinkRatingSelect, 7, 1);
    monsterLinkRatingSelect.disabled = true;
    const monsterLinkRatingDiv = wrapWithLabel(
        monsterLinkRatingSelect,
        'Link Rating'
    );

    const monsterPenScaleSelect = document.createElement('select');
    monsterPenScaleSelect.id = 'monsterPenScale';
    fillSelectWithLength(monsterPenScaleSelect, 14);
    monsterPenScaleSelect.disabled = true;
    const monsterPenScaleDiv = wrapWithLabel(
        monsterPenScaleSelect,
        'Pendulum Scale'
    );

    const getCardButton = document.createElement('button');
    getCardButton.textContent = 'Get Card';

    getCardButton.addEventListener('click', () => {
        cleanElementById('card');
        cleanElementById('description');
        if (monsterTypeSelect.value === 'Link Monster') {
            displayYGOCard({type: 'monsterLink', data: [
                monsterLinkRatingSelect.value, 
                monsterTypeSelect.value, 
                monsterRaceSelect.value, 
                monsterAttributeSelect.value
            ]});
        } else if (monsterTypeSelect.value.includes('Pendulum')) {
            displayYGOCard({type: 'monsterPendulum', data: [
                monsterLevelSelect.value, 
                monsterTypeSelect.value, 
                monsterRaceSelect.value, 
                monsterAttributeSelect.value, 
                monsterPenScaleSelect.value
            ]});
        } else {
            displayYGOCard({type: 'monster', data: [
                monsterLevelSelect.value, 
                monsterTypeSelect.value, 
                monsterRaceSelect.value,
                monsterAttributeSelect.value
            ]})
        }
    });

    cleanElementById('inputs');
    cleanElementById('card');
    cleanElementById('description');

    const inputsDiv = document.getElementById('inputs');
    inputsDiv.append(monsterTypeDiv, monsterRaceDiv,
        monsterAttributeDiv, monsterLevelDiv,
        monsterPenScaleDiv, monsterLinkRatingDiv, getCardButton);
}
```


##### Generate Spell Card DOM

This function is called when you click a button. After clicking it, a select will be made for the race endpoint that can be used for spell cards and then filled with the hard-coded data through the use of the function - **fillSelectWithArray**. After that the select will be wrapped in a div and assigned with a label using the **wrapWithLabel** function.

At the end a button is generated, when clicked it will get the values of the select and then call the **displayYGOCard** function. 

```
function generateSpellCardDOM() {
    const spellRaceSelect = document.createElement('select');
    const races = ['Normal', 'Continuous', 'Equip',
     'Field', 'Quick-Play', 'Ritual'];
    fillSelectWithArray(spellRaceSelect, races);
    const spellRaceDiv = wrapWithLabel(spellRaceSelect, 'Type')

    const getCardButton = document.createElement('button');
    getCardButton.textContent = 'Get Card';

    getCardButton.addEventListener('click', () => {
        cleanElementById('card');
        cleanElementById('description');
        displayYGOCard({type: 'spell', data: [spellRaceSelect.value]})
    });
    
    cleanElementById('inputs');
    cleanElementById('card');
    cleanElementById('description');

    const inputsDiv = document.getElementById('inputs');
    inputsDiv.append(spellRaceDiv, getCardButton);
}
```


##### Generate Trap Card DOM

This function is called when you click a button. After clicking it, a select will be made for the race endpoint that can be used for trap cards and then filled with the hard-coded data through the use of the function - **fillSelectWithArray**. After that the select will be wrapped in a div and assigned with a label using the **wrapWithLabel** function.

At the end a button is generated, when clicked it will get the values of the select and then call the **displayYGOCard** function.

```
function generateTrapCardDOM() {
    const trapRaceSelect = document.createElement('select');
    const races = ['Normal', 'Continuous', 'Counter'];
    fillSelectWithArray(trapRaceSelect, races);
    const trapRaceDiv = wrapWithLabel(trapRaceSelect, 'Type')
    
    const getCardButton = document.createElement('button');
    getCardButton.textContent = 'Get Card';

    getCardButton.addEventListener('click', () => {
        cleanElementById('card');
        cleanElementById('description');
        displayYGOCard({type: 'trap', data: [trapRaceSelect.value]})
    });

    cleanElementById('inputs');
    cleanElementById('card');
    cleanElementById('description');

    const inputsDiv = document.getElementById('inputs');
    inputsDiv.append(trapRaceDiv, getCardButton);
}
```

#### How it works at the end

The page loads and you see a blank page with 3 buttons which say Monster, Spell, Trap. 

When you click on a button, a function is executed which makes several or only one select element, appends them with options with hard-coded values and then generates another button, when clicked, it will get the current values of the select elements and based on the chosen options a query string is made. 

Then a function is executed which send a fetch request to the API with the query string. The returned data is either nothing (in case if the user provided values for which a card doesn't exist) or a random card from a collection that matches the information in the query string. 

After that random card is saved in a variable, another function is called which displays the cards image and the effect of that card.

I hope this tutorial is of help and to whoever reads this, good luck with you project!