Marvel api help

Hi, i have been struggling with this api but got this far and was wondering how i would implement and incorperate a search function in this code

let resultsPerPage = 5;
let offset = null
let currentPage = 1
const publicKey =  '';
const privateKey =  '';
let timestamp = new Date().getTime()
let hashVal =  md5(timestamp+privateKey+publicKey);

const button = document.getElementById('submit-search');
const searchInput = document.getElementById('search-input')
const pagination = document.getElementById('pagination')
const main = document.getElementById('main')

const getMarvelCharacters = async function(){
  const url = `http://gateway.marvel.com/v1/public/characters?ts=${timestamp}&apikey=${publicKey}&hash=${hashVal}&limit=${resultsPerPage}&offset=${offset}`;
  const response = await fetch(url)
  try {
    if(!response.ok){
      throw new Error('network response is not okay')
    }
    const data = await response.json()
	let characters  = data.data.results

	const numPages = Math.ceil(characters.length / resultsPerPage);
	displayCharacters(characters, searchInput)
	console.log(characters)
    return characters

  } catch (error) {
    console.error('There was an error:', error)
  }
}

const getResPage = function(characters,currentPage) {
  
  offset = (currentPage - 1) * resultsPerPage ;
  const start = offset
  const end = start + resultsPerPage;
  return  characters.slice(start, end);
}

const displayCharacters = function(characters){
  main.innerHTML = ''
  // const filteredResults = searchCharacters(characters, searchInput.value)
  let markup = ''
  characters.forEach(character => {

    markup +=
    `<div id="results-container">
		<div class="image-container">
			<img src="${character.thumbnail.path}.${character.thumbnail.extension}" alt="">  
		</div>
		<div class="character-info">
			<h2 class="Character-name">${character.name}</h2>
			<p class="character-info">${character.description}</p> 
		</div>
    </div>
    ` 
  });
  main.insertAdjacentHTML('beforeend', markup) 
}

const getPaginationButtonResults = function(currentPage){
  const next = document.querySelector('.right')
  const prev = document.querySelector('.left')
  
  next.addEventListener('click', async (event)=>{
    event.preventDefault()
    currentPage++
    offset+=resultsPerPage
    const characters = await getMarvelCharacters(currentPage)
    displayCharacters(characters)
  })
  
  prev.addEventListener('click', async (event)=>{
    event.preventDefault()
    currentPage--
    if(currentPage>0){
      offset-=resultsPerPage
      const characters = await getMarvelCharacters(currentPage)
      displayCharacters(characters)
    }
  })
}

getMarvelCharacters(currentPage)
getPaginationButtonResults(currentPage)

So… what part of it are you having trouble with?

sorry the code i wrote that didnt seem to work was;

const searchCharacters = function (characters, searchInput) {
  if (!searchInput) {
    return characters;
  }
  
  const searchTerm = searchInput.trim().toLowerCase();
  
  return characters.filter((character) => {
    return character.name.toLowerCase().includes(searchTerm);
  });
};

button.addEventListener('click', async () => {
  const characters = await getMarvelCharacters(currentPage, searchInput.value);
  const filteredResults = searchCharacters(characters, searchInput.value);
  
  displayCharacters(filteredResults, searchInput);
});

Well your getMarvelCharacters function is already calling displayCharacters inside it…
nothing’s immediately jumping out at me as wrong there… what’s it doing that it’s not supposed to do, or not doing that it should be doing? Have you verified your inputs to the functions?

its not doing anything, when i enter something in the search box for example if i searched ‘man’ i am expecting it to retrieve search results that contain ‘man’ in the character name.

what do you mean by – Have you verified your inputs to the functions? ??

it took me a whole day to get pagination working and i think i fluked it tbh, and i think this code is messy? is that correct? any tips on improving it?

I have just formatted your code for you. If you need to include code in your posts, select the code in the text editor and click on the </> menu item at the top. This will enclose your code in three backticks ```. Doing this will make your code easier to read for members trying to assist you.

One thing that did stand out to me is a bit further up and it’s your very long url line

const url = `http://gateway.marvel.com/v1/public/characters?ts=${timestamp}&apikey=${publicKey}&hash=${hashVal}&limit=${resultsPerPage}&offset=${offset}`;

Just doing a bit of reading up and URLSearchParams might be an option here.

I haven’t tested this, but I believe the above could be refactored to

const url = 'http://gateway.marvel.com/v1/public/characters'

const response = await fetch(url + new URLSearchParams({
  ts: timestamp,
  apikey: publicKey,
  hash: hashVal,
  limit: resultsPerPage,
  offset
}))

It’s more lines of code, but personally I think it is more readable.

1 Like

For starters…

It’s worthwhile using a linter e.g. eslint. It not only highlights errors, but picks up on things like unused variables etc. It can be very helpful.

For instance, I have just been looking at your code, and it kept on converting let currentPage = 1 to const currentPage = 1, the message being:
'currentPage' is never reassigned. Use 'const' instead

The reason for this is that you are passing it in as a value to getPaginationButtonResults, which then defines a local variable of it’s own called currentPage with that value assigned to it.

const getPaginationButtonResults = function (currentPage) {
  const next = document.querySelector('.right')
  const prev = document.querySelector('.left')

  next.addEventListener('click', async (event) => {
    event.preventDefault()
    currentPage ++ // this is updating a local currentPage

Albeit you are incrementing currentPage, it is not the currentPage you defined globally at the top of your code. It is a local currentPage inside of that function.

It is recommended that globals be avoided, but to keep things simple for now, you could fix this by removing that parameter entirely.

const getPaginationButtonResults = function () {
  const next = document.querySelector('.right')
  const prev = document.querySelector('.left')

  next.addEventListener('click', async (event) => {
    event.preventDefault()
    currentPage ++ // this is updating your global currentPage

I would recommend that you learn or re-learn about the difference between reference and primitive types and also about function scope.

It’s late here, so will leave it at that for now.

This is a bit of an attempt at a refactor and trying to deal with the dependency on global variables.

Fetching the Marvel Character data

To start with getMarvelCharacters, I think should really do one thing and this is fetch the Marvel Character data (not rendering etc). I have split it up into two functions. One a generic fetchJson function and a getMarvelCharacters function.

/* A generic fetch function that takes a url and params object and returns a JSON response */
const fetchJson = async function (url = '', params = {}) {
  const response = await fetch(url + new URLSearchParams(params))

  try {
    if (!response.ok) {
      throw new Error('network response is not okay')
    }
    return await response.json()

  } catch (error) {
    console.error('There was an error:', error)
  }
}

/*
  A function that takes an api keys object and page properties object
  and returns the Marvel characters data from the Marvel API
*/
const getMarvelCharacters = async function (apikeys = {}, pageProps = {}) {
  const timeStamp = new Date().getTime()
  const { publicKey, privateKey } = apikeys
  const { offset, currentPage, resultsPerPage } = pageProps

  const urlParams = {
    offset,
    limit: resultsPerPage,
    ts: timeStamp,
    apikey: publicKey,
    hash: md5(timeStamp + privateKey + publicKey),
  }

  const json = await fetchJson('http://gateway.marvel.com/v1/public/characters', urlParams)
  return json.data.results
}

HTML Creation and Displaying on the page

For the creation of the HTML from that data and displaying it, I have created 3 separate functions

/* HTML and Display Functions */

/* Takes a character object and returns a string of HTML */
const characterTemplate = function (character) {
  return `
    <div id="results-container">
      <div class="image-container">
        <img src="${character.thumbnail.path}.${character.thumbnail.extension}" alt="">
      </div>
      <div class="character-info">
        <h2 class="Character-name">${character.name}</h2>
        <p class="character-info">${character.description}</p>
      </div>
    </div>
  `
}

/* Takes a template function and an array of props and returns a string of HTML */
const createHTML = function (templateFunc, props = []) {
  return props.map(templateFunc).join('\n')
}

/* Takes an element and an array of characters and displays the characters in the DOM */
const displayCharacters = function (characters) {
  const main = document.getElementById('main')
  const markup = createHTML(characterTemplate, characters)
  main.innerHTML = markup
}

Instead of your forEach I have used Array.map and Array.join. It achieves the same thing.

Page Navigation

For page navigation I have used a module reveal pattern. This way the offset, currentPage and resultsPerPage can be removed from the global namespace and contained where they are needed. Putting this in a module might be a better alternative.

/* Creates a page navigation object with current, next and prev methods */

const CreatePageNavigation = function () {
  /* 
    Private Variables only accesible to
    the next, prev and current methods/functions
 */
  let offset = 0
  let currentPage = 1
  const resultsPerPage = 5

  const current = function () {
    return { currentPage, offset, resultsPerPage }
  }

  const next = function () {
    currentPage++
    offset += resultsPerPage
    return { currentPage, offset, resultsPerPage }
  }

  const prev = function () {
    if (currentPage > 1) {
      currentPage--
      offset -= resultsPerPage
    }
    return { currentPage, offset, resultsPerPage }
  }

  // return an object with these three methods/functions
  return { current, next, prev }
}

You can create a page nav function and use it like this

const getPage = CreatePageNavigation()
getPage.next()
//or
getPage.prev()
// or
getPage.current()

All of these methods will return an object containing the currentPage, offset and resultsPerPage properties.

The final setup

Simply setting up the eventlisteners and displaying the current page.

/* Setup Event Listeners and display initial page */
document.addEventListener('DOMContentLoaded', async () => {
  const apikeys = {
    publicKey: '',
    privateKey: ''
  }

  const nextBtn = document.querySelector('.right')
  const prevBtn = document.querySelector('.left')

  const getPage = CreatePageNavigation()

  nextBtn.addEventListener('click', async (event) => {
    displayCharacters(
      await getMarvelCharacters(apikeys, getPage.next())
    )
  })

  prevBtn.addEventListener('click', async (event) => {
    displayCharacters(
      await getMarvelCharacters(apikeys, getPage.prev())
    )
  })

  // Display initial page
  displayCharacters(
    await getMarvelCharacters(apikeys, getPage.current())
  )
})

This is obviously untested. The few things I have considered here are one to make functions with a single purpose, two make more generic functions where appropriate e.g. fetchJson, createHTML etc and three remove those global variables and put them more locally to where they are needed.

The above code hasn’t accounted for your search requirement.

I couldn’t figure out what getResPage was actually doing. It seems to be changing offset, but I am not sure how that figures in. Maybe it can be incorporated into the CreatePageNavigation.

Furthermore you seem to be passing currentPage around, but it has no final destination that I can see. It is passed to the getMarvelCharacters function, but the function does nothing with it.

Lastly my code may be flawed, but I think there are atleast a few ideas there.