Heroicons blade component using javascript
Tags: javascript, laravel
Published: 2022-01-02 20:48:56

`<x-hero::icon name="o:users" size=32></x-hero::icon>` This is how this Heroicons blade component used on a blade template, it uses namespace `hero` because I made [a laravel package on this alternate implementation](https://packagist.org/packages/tjd/heroicons-blade) - however, we can use this without having to install a laravel package.

## Anonymous Component

The component is defined in a single blade file without class association, it has a few props which control the size and the color of the icon.

``` php
@props([
    'name' => 'o:user',
    'size' => 24,
    'fill' => 'none' ,
    'stroke' => 'currentColor' ])

<svg
    id="heroIcon-{{$name}}"
    data-group="heroicons"
    data-name="{{$name}}"
    width="{{$size}}"
    height="{{$size}}"
    xmlns="http://www.w3.org/2000/svg"
    {{$attributes->merge(['class' => 'tj-heroicons'])}}
    fill="{{$fill}}" 
    viewBox="0 0 24 24" 
    stroke="{{$stroke}}"
    aria-hidden="true">
    <!-- no path -->
</svg>
```
The svg tag used as a template for applying `props`, without any path. Paths will be populated later by javascript through `window.fetch`. This is done n a `DOMContentLoaded` event, the fetched svg file content then parsed and apply paths element inside svg in the blade component.

## Fetching the svg file

The source of the svg icon should be placed inside the public directory so it is available for us to fetch. In this case, the icons is inside `heroicons/icons` url path.

``` bash
heroicons/
├── icons/
│   ├── outline/ 
|   ├── solid/

```
Displayed icon on the specific page gathered throuh querying into the **data-group** attribute of the svg element inside the blade component.

```javascript
document.addEventListener('DOMContentLoaded', () => {
    document
        .querySelectorAll('svg[data-group="heroicons"]')
            .forEach(heroIcon => {
                
                let iconPropName = heroIcon.dataset.name
                let iconPropNameSplitted = iconPropName.split(":")
                let iconType = iconPropNameSplitted[0] == 'o' ? 'outline' : 'solid'
                let iconName = iconPropNameSplitted[1]
                let iconPath = `/heroicons/icons/${iconType}/${iconName}.svg`
                
                window.fetch(request(iconPath))
                    .then(res => {
                        if (res.ok) {
                                return res.text()
                            }
                            console.error('Failed to fetch the svg icon.')
                    })
                    .then(data =>  insertPath(data, heroIcon))
                    .catch(err => console.error(err.message))
    })
})
```

The **request** argument supplied to `window.fetch` is a `Request` object, initiated through a function. While the insertPath function is the part where we get the paths out of the text file and inject it into `heroIcon` component from the array loop attached as a function argument.

``` javascript
 const request = (url) => {
    return new Request(url, {
        method: 'GET',
        redirect: 'follow',
        headers: {
            'Accept': 'image/svg+xml',
        },
    })
}
```
The headers section can be skipped the most important part is the `url` and the `method`. It shouldn't have problem with CORS - since it is from your own front end.

``` javascript
const insertPath = (svgText, el) => {
    
    let newEl = document.createElement('div')
    newEl.innerHTML = svgText
    
    // Standard style
    el.style.display = 'inline'
    el.style.marginBottom = '.2rem'

    newEl.querySelectorAll('path').forEach(p => {
        el.appendChild(p)
    })
}
```

I notice that some of the svg icons have more than one path, hence the forEach on the end of the function.


![fetch requests and response header](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r9brpibadct09pgv6kbk.png)