DEV Community

Cover image for Heroicons blade component using javascript
Tajid Yakub
Tajid Yakub

Posted on

Heroicons blade component using javascript

<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 - 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.

@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>
Enter fullscreen mode Exit fullscreen mode

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.

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

Enter fullscreen mode Exit fullscreen mode

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

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))
    })
})
Enter fullscreen mode Exit fullscreen mode

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.

 const request = (url) => {
    return new Request(url, {
        method: 'GET',
        redirect: 'follow',
        headers: {
            'Accept': 'image/svg+xml',
        },
    })
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
    })
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)