Задача №1
Використовуючи один з АРІ
https://github.com/public-apis/public-apis#animals
та функцію fetch організувати завантаження та відображення даних. Намагайтесь зробити це з використанням класів. Окремо клас для побудови розмітки. Окремо клас, який буде робити запити і повертати результати.
Рішення:
import CatCardManager from './cat_card_manager.js'
const cardManager = new CatCardManager({
amount: 6,
imgSrc: './images/placeholder_image.webp',
})
cardManager.render('.js-app')
import Field from './field.js'
import CatCard from './cat_card.js'
export default class CatCardManager {
constructor(options) {
this.options = {
amount: 1,
imgSrc: '',
btnLabel: 'Get cats!',
placeholder: 'Cats say:',
alt: 'A cat',
spinnerLabel: 'Loading...',
styles: {
main: 'u-flow-400',
controls: 'u-flex u-flex-wrap u-gap-200',
btn: 'u-flex-none',
imgContainer: 'u-flex u-justify-center u-flex-wrap u-gap-200',
imgFrame:
'u-relative u-grid u-place-items-center u-aspect-3/4 u-is-5600 u-rounded-lg u-overflow-hidden',
img: 'u-ibg u-object-cover u-text-none',
spinner: 'u-text-yellow-400 u-absolute',
},
...options,
}
}
/** @param {string} label */
#renderBtn(label) {
const btnEl = document.createElement('button')
btnEl.textContent = label
// btnEl.type = 'button'
btnEl.className = this.options.styles.btn
return btnEl
}
#renderControls() {
const getBtn = this.#renderBtn(this.options.btnLabel)
const {$el: fieldEl, inputEl} = new Field({
placeholder: this.options.placeholder,
}).render()
inputEl.classList.add('u-flex-auto', 'u-is-4000')
const controlsEl = document.createElement('div')
controlsEl.className = this.options.styles.controls
controlsEl.append(fieldEl, getBtn)
return {controlsEl, fieldEl, getBtn, inputEl}
}
/** @param {SubmitEvent} e */
async handleSubmit(e) {
e.preventDefault()
// prevent multiple requests
if (this.getBtn.disabled) return
this.getBtn.disabled = true
const phrase = this.inputEl.value.trim()
await Promise.allSettled(
this.catCards.map((card) => card.reloadImg(phrase)),
)
this.getBtn.disabled = false
}
/** @param {string} [cssSelector] */
render(cssSelector) {
this.$el = document.createElement('form')
this.$el.className = this.options.styles.main
this.$el.addEventListener('submit', this.handleSubmit.bind(this))
const {controlsEl, inputEl, getBtn} = this.#renderControls()
this.inputEl = inputEl
this.getBtn = getBtn
this.$el.append(controlsEl)
this.catCards = Array.from(
{length: this.options.amount},
() =>
new CatCard({imgSrc: this.options.imgSrc, styles: this.options.styles}),
)
const imgContainer = document.createElement('div')
imgContainer.className = this.options.styles.imgContainer
imgContainer.append(...this.catCards.map((card) => card.render()))
this.$el.append(imgContainer)
if (cssSelector) document.querySelector(cssSelector).append(this.$el)
return this.$el
}
}
export default class Field {
options
id = crypto.randomUUID()
constructor(options) {
this.options = {
type: 'text',
initValue: '',
placeholder: '',
autocomplete: true,
...options,
}
}
#renderInput() {
const inputEl = document.createElement('input')
inputEl.type = this.options.type
inputEl.value = this.options.initValue
if (!this.options.autocomplete) inputEl.autocomplete = 'off'
if (this.options.placeholder) inputEl.placeholder = this.options.placeholder
return inputEl
}
#renderLabel() {
const labelEl = document.createElement('label')
labelEl.textContent = this.options.label
return labelEl
}
/**
* @param {HTMLInputElement} inputEl
* @param {HTMLLabelElement} labelEl
*/
#bindInputWithLabel(inputEl, labelEl) {
inputEl.id = this.id
labelEl.setAttribute('for', this.id)
}
/** @param {string} [cssSelector] */
render(cssSelector) {
this.$el = new DocumentFragment()
const inputEl = this.#renderInput()
this.$el.append(inputEl)
if (this.options.label) {
const labelEl = this.#renderLabel()
this.#bindInputWithLabel(inputEl, labelEl)
this.$el.prepend(labelEl)
}
if (cssSelector) document.querySelector(cssSelector).append(this.$el)
return {$el: this.$el, inputEl}
}
}
import CataasAPI from './cataas_api.js'
import Spinner from './spinner.js'
export default class CatCard {
$el
spinnerEl
imgEl
constructor(options) {
this.options = {
imgSrc: '',
alt: 'A cat',
spinnerLabel: 'Loading...',
styles: {
imgFrame:
'u-relative u-grid u-place-items-center u-aspect-3/4 u-is-6000 u-rounded-lg u-overflow-hidden',
img: 'u-ibg u-object-cover u-text-none',
spinner: 'u-text-yellow-400 u-absolute',
},
...options,
}
}
#renderSpinner() {
const spinnerEl = new Spinner({
className: this.options.styles.spinner,
label: this.options.spinnerLabel,
}).render()
return spinnerEl
}
#renderPicture() {
const imgEl = document.createElement('img')
imgEl.src = this.options.imgSrc
imgEl.alt = this.options.alt
imgEl.className = this.options.styles.img
const imgFrameEl = document.createElement('div')
imgFrameEl.className = this.options.styles.imgFrame
imgFrameEl.append(imgEl)
return {imgFrameEl, imgEl}
}
async reloadImg(phrase = '') {
this.$el.append(this.spinnerEl)
try {
const imgURL =
phrase !== ''
? await CataasAPI.getCatSays(phrase)
: await CataasAPI.getCat()
this.imgEl.src = imgURL
} catch (error) {
console.error(error)
} finally {
this.spinnerEl.remove()
}
}
/** @param {string} [cssSelector] */
render(cssSelector) {
const {imgFrameEl, imgEl} = this.#renderPicture()
this.$el = imgFrameEl
this.imgEl = imgEl
this.spinnerEl = this.#renderSpinner()
if (cssSelector) document.querySelector(cssSelector).append(this.$el)
return this.$el
}
}
export default class CataasAPI {
static API_KEY = 'https://cataas.com/'
/** @param {string} endPoint */
static async #getURL(endPoint) {
const resp = await fetch(CataasAPI.API_KEY + endPoint)
if (!resp.ok) throw new Error(`${resp.status} – bad response.`)
const blob = await resp.blob()
return URL.createObjectURL(blob)
}
static getCat() {
return this.#getURL('cat')
}
/** @param {string} text */
static getCatSays(text) {
return this.#getURL(`cat/says/${text}`)
}
}
export default class Spinner {
options
$el
constructor(options = {}) {
this.options = {
className: '',
hint: 'Loading...',
...options,
}
}
/** @param {string} [cssSelector] */
render(cssSelector) {
this.$el = document.createElement('div')
this.$el.className = `c-spinner ${this.options.className}`
this.$el.setAttribute('aria-live', 'polite')
const hint = document.createElement('span')
hint.textContent = this.options.hint
hint.className = 'c-spinner__hint'
this.$el.append(hint)
if (cssSelector) document.querySelector(cssSelector).append(this.$el)
return this.$el
}
}
