Javascript

Building a Simple Icon Picker With Javascript

Building a Simple Icon Picker With Javascript

In this snippet we will build a simple Icon Picker input UI to be used in web applications and websites using Javascript.

 

 

Icon Pickers is a special web UI’s mostly exist in content management systems, template builders and form builders. You can find javascript packages or libraries already exist that provide this functionality.

However you may not find the required library that provides you the icons you need, so for this reason you may think about building the picker by yourself.

In this tutorial we will make use of the material icons and i will store them into javascript file as array.

 

Preparing Project

In your webroot directory create a new folder named “simple-icon-picker” with these files:

building simple icon picker- project structure

 

 

 

 

Now let’s think about the picker we will build.

simple icon picker mockup

As you see in this figure the picker supposed to be an input that when gets focused it will display dropdown container with all icons loaded and the user can filter in those icons.

Open index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Simple icon picker</title>
    
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/MaterialDesign-Webfont/7.2.96/css/materialdesignicons.min.css" integrity="sha512-LX0YV/MWBEn2dwXCYgQHrpa9HJkwB+S+bnBpifSOTO1No27TqNMKYoAn6ff2FBh03THAzAiiCwQ+aPX+/Qt/Ow==" crossorigin="anonymous" referrerpolicy="no-referrer" />

    <link type="text/css" rel="stylesheet" href="./css/style.css" />
</head>
<body>
    <!-- Begin icon picker -->
    <div class="tm-icon-picker">
        
    </div>
    <!-- End icon picker -->

    <script type="text/javascript" src="./js/scripts.js"></script>
</body>
</html>

In this html page i loaded material design icon cdn that will render our icons and the custom style.css and scripts.js files. The style.css will contain the necessary stylesheet for the picker and also the scripts.js will contain the javascript code to handle picker.

The div with class “tm-icon-picker” encapsulate the icon picker element, we will update this shortly with more elements inside. It’s more important that you prefix your custom elements like “tm-” so that i didn’t collide with other elements in the page.

 

Icon Picker Element

In the html page i added a skeleton element with class “tm-icon-picker” that wraps the icon picker. Let’s add the remaining elements inside:

<!-- Begin icon picker -->
    <div class="tm-icon-picker">
        <label>Select icon</label>
        <div class="tm-icon-picker-input-wrapper">
            <div class="tm-icon-picker-input">
                <input type="text" name="icon" class="tm-icon-picker-input-text" placeholder="pick icon" />
                <div class="icons-grid"></div>
            </div>
            <div class="tm-icon-picker-append"></div>
        </div>
    </div>
    <!-- End icon picker -->

Here in this code inside the parent div i added a label, then i added another div with class “tm-icon-picker-input-wrapper” that wraps the picker input.

The picker input is a div with class “tm-icon-picker-input” that have two children, the input of type=text and div with class “icons-grid” that will show some kind of dropdown when user focus into the input.

The div with class “tm-icon-picker-append” display the selected icon html.

Now update the index.html page to include this whole div and it’s children.

 

Icon Picker Style

According to the previous html element, i already added the styling that will properly render the div.

Open css/style.css and update like so:

body {
    font-family: Arial, sans-serif;
}

.tm-icon-picker {
    display:  flex;
    align-items: center;
}

.tm-icon-picker label {
    margin-right:  5px;
}

.tm-icon-picker .tm-icon-picker-input-wrapper {
    display:  flex;
    position: relative;
}

.tm-icon-picker .tm-icon-picker-input input {
    font-size: 20px;
    color:  #07616a;
    padding: 5px 5px;
    border:  1px solid #07616a;
    border-radius: 5px;
    border-top-right-radius: 0px;
    border-bottom-right-radius: 0px;
}

.tm-icon-picker .tm-icon-picker-append {
    min-width: 50px;
    background: #ccc;
    border:  1px solid #a2b1b3;
    border-top-right-radius: 5px;
    border-bottom-right-radius: 5px;
    text-align: center;
    padding: 8px 5px;
}

.tm-icon-picker .icons-grid {
    width:  79%;
    height: 162px;
    position: absolute;
    border: 1px solid #ccc;
    display: none;
    overflow-y: scroll;
    padding: 5px;
}

.tm-icon-picker .icons-grid .icon {
    display: inline-block;
    font-size: 32px;
    margin: 4px 3px;
    cursor: pointer;
    transition: all 0.3s;
    padding: 2px;
}

.tm-icon-picker .icons-grid .icon:hover {
    border:  1px solid #ccc;
    border-radius: 3px;
}

 

Icon Picker Javascript Code

For the javascript part and this is the most important part i will add the javascript code inside js/scripts.js file.

The steps for rendering the icon picker as follows:

  • First we listen for the focus event on the input. Inside the focus listener we show the icons dropdown and then load and display the icons using ajax.
  • If the user type into the input then we listen on the “keyup” event and we take the filter string and filter and display the filtered icons.
  • If the user click outside the icons dropdown or the input it should close the icons dropdown.
  • The icons is loaded from json file using ajax request. i.e using javascript fetch.
  • To render any material design icon typically we use <span> or <i> tag and give it a class of “material-icons mdi mdi-{icon name}” like <span class=”material-icons mdi mdi-account”></span>.
  • If the user click on any icon on the dropdown it will be selected and displayed on the input and the append div.

For the material design icons create new folder inside the project named “json” and then download the material icons from this url and add the json file inside the created folder.

 

According to the steps above, i will create a constructor function that represent the picker and will wrap the whole picker processing code.

js/scripts.js

function simpleIconPicker(selector)
{
     /*
        The icon picker code
     */
}

const picker = new simpleIconPicker('.tm-icon-picker');    // call the picker

Let’s go through the steps one by one and update the code accordingly:

  • Showing and hiding the Picker on focus
function simpleIconPicker(selector)
{
    this.pickerEle = document.querySelector(selector);
    this.inputEle = this.pickerEle.querySelector(".tm-icon-picker-input-text");
        this.iconsGridEle = this.pickerEle.querySelector(".icons-grid");
        this.selectedIconEle = this.pickerEle.querySelector(".tm-icon-picker-append");

        this.iconsloaded = false;
        this.allIcons = [];
        this.selectedIcon = '';


        this.showIconGrid = () => {
          this.iconsGridEle.style.display = "block";
        }

        this.hideIconGrid = () => {
          this.iconsGridEle.style.display = "none";
        }

        this.handleInputFocus = e => {
            this.showIconGrid();
        }

        this.inputEle.addEventListener('focus', this.handleInputFocus);    // handling input focus
        
        window.addEventListener('click', (e) => {                          // handling click outside input
          if (!this.inputEle.contains(e.target) && !this.iconsGridEle.contains(e.target)){
            this.hideIconGrid();
          }
  	});
}

Our constructor function accepts the selector as the only argument. Next i declared some variables like this.pickerEle, this.inputEle, this.iconsGridEle, etc. The iconsLoaded and allIcons variables used when fetching the icons to indicate the icons is already loaded.

The selectedIcon variable stores the selected Icon when clicking on any icon on the dropdown.

Then i created some utility functions like this.showIconGrid() and this.hideIconGrid() for toggling the “this.iconsGridEle.style.display” property.

I listened for the focus event on the input and display the icons grid like so:

this.handleInputFocus = e => {
            this.showIconGrid();
        }

this.inputEle.addEventListener('focus', this.handleInputFocus);

To close the icons grid we listened for the click event on the window and check that the input element and icons grid not included in the target:

window.addEventListener('click', (e) => {                          // handling click outside input
    if (!this.inputEle.contains(e.target) && !this.iconsGridEle.contains(e.target)){
       this.hideIconGrid();
    }
});
  • Fetching and displaying the icons

Add those function in js/scripts.js as follows:

this.fetchIcons = (callback = null) => {
                fetch("./json/material-design-icons.json").then((response) => {
                    response.json().then(data => {

                this.iconsloaded = true;
                    this.allIcons = data;

                        if(callback) {
                        	callback();
                        }
                    });
                    
                })
                .catch(error => {
                    console.error(error);
                });
        }

        this.renderIcons = (icons) => {
            if(icons) {
                let iconHtml = '';

                icons.forEach(icon => {
                    iconHtml += `<div class='icon' data-icon='${icon}' onclick='selectIcon(this)'><i class="material-icons mdi mdi-${icon}"></i></div>`;
                });

                this.iconsGridEle.innerHTML = iconHtml;
            }
        }

        this.handleInputFocus = e => {
            this.showIconGrid();

            if(!this.iconsloaded) {
                this.fetchIcons(() => {
                    this.renderIcons(this.allIcons);
                });
            }
        }

In this code the fetchIcons() function fetches the icons from the json file and then updates “this.allIcons” and “this.iconsLoaded” variables.

The renderIcons() function loops over the icons parameter and sets the icons html into iconsGridEle.innerHTML property to display the icons

The next thing is we update “this.handleInputFocus” handler to invoke the “fetchIcons” and then “renderIcons” functions.

After running this code into browser you will see the icons loaded when you focus into the input.

  • Selecting Icon and handling selected Icon

Add the selectIcon() handler before this.renderIcons() function:

window.selectIcon = e => {
    const icon = e.dataset.icon;

    this.selectedIcon = icon;

    this.selectedIconEle.innerHTML = `<i class="material-icons mdi mdi-${this.selectedIcon}"></i>`;
    this.inputEle.value = this.selectedIcon;

    this.hideIconGrid();
}

The “window.selectIcon()” handler fires when user click on any icon to select it. First we read the icon prop using “e.dataset.icon” to read data attribute “data-icon“.

Then i set the “this.selectedIcon=icon” and set “this.selectedIconEle.innerHTML” property to icon html like so:

this.selectedIconEle.innerHTML = `<i class="material-icons mdi mdi-${this.selectedIcon}"></i>`;
this.inputEle.value = this.selectedIcon;

And finally i hide the icons grid:

this.hideIconGrid();

 

  • Typing into input and filtering icons

Below “this.fetchIcons()” function add this new function:

this.filterIcons = (query) => {
    let filtered = [];

    if(query) {
        filtered = this.allIcons.filter(icon => {
           return icon.includes(query);
            });
    } else {
        filtered = this.allIcons;
    }
        
       return filtered;
}

And then add the “keyup” handler below the “focus” handler like so:

this.inputEle.addEventListener('keyup', (e) => {
    const enteredValue = e.target.value;
            
    let filteredIcons = this.filterIcons(enteredValue);

    this.renderIcons(filteredIcons);
});

The “keyup” listener registered on the input filters the icons. First i retrieved the input value using “e.target.value” and then calling “this.filterIcons()” passing the value, in the end i call the this.renderIcons(filteredIcons) again.

The “this.filterIcons” function filters the icons array using javascript filter method like so:

filtered = this.allIcons.filter(icon => {
    return icon.includes(query);
});

 

  • Focus into input and filtering

The remaining piece is to complete the focus handler when the user focus into input and there is already a search string, it should load the filtered icons.

Update the focus handler as follows:

this.handleInputFocus = e => {
            const enteredValue = e.target.value;

            this.showIconGrid();

            if(!this.iconsloaded) {
                this.fetchIcons(() => {
                    let filteredIcons = this.filterIcons(enteredValue);
                    
                    this.renderIcons(filteredIcons);
                });
            } else {
                let filteredIcons = this.filterIcons(enteredValue);
            this.renderIcons(filteredIcons);
            }
}

What’s new here is i got the search string using “e.target.value“, then i call “this.filterIcons(enteredValue)” and passed the filtered icons into “this.renderIcons“.

 

The full javascript code

js/scripts.js

function simpleIconPicker(selector)
{
    this.pickerEle = document.querySelector(selector);
    this.inputEle = this.pickerEle.querySelector(".tm-icon-picker-input-text");
        this.iconsGridEle = this.pickerEle.querySelector(".icons-grid");
        this.selectedIconEle = this.pickerEle.querySelector(".tm-icon-picker-append");

        this.iconsloaded = false;
        this.allIcons = [];
        this.selectedIcon = '';


        this.showIconGrid = () => {
          this.iconsGridEle.style.display = "block";
        }

        this.hideIconGrid = () => {
          this.iconsGridEle.style.display = "none";
        }

        this.fetchIcons = (callback = null) => {
                fetch("./json/material-design-icons.json").then((response) => {
                    response.json().then(data => {

                this.iconsloaded = true;
                    this.allIcons = data;

                        if(callback) {
                        	callback();
                        }
                    });
                    
                })
                .catch(error => {
                    console.error(error);
                });
        }

        this.filterIcons = (query) => {
            let filtered = [];

            if(query) {
                filtered = this.allIcons.filter(icon => {
                    return icon.includes(query);
                });
            } else {
                filtered = this.allIcons;
            }

            return filtered;
        }

        window.selectIcon = e => {
                const icon = e.dataset.icon;

                this.selectedIcon = icon;

                this.selectedIconEle.innerHTML = `<i class="material-icons mdi mdi-${this.selectedIcon}"></i>`;
                this.inputEle.value = this.selectedIcon;

                this.hideIconGrid();
        }

        this.renderIcons = (icons) => {
            if(icons) {
                let iconHtml = '';

                icons.forEach(icon => {
                    iconHtml += `<div class='icon' data-icon='${icon}' onclick='selectIcon(this)'><i class="material-icons mdi mdi-${icon}"></i></div>`;
                });

                this.iconsGridEle.innerHTML = iconHtml;
            }
        }

        this.handleInputFocus = e => {
            const enteredValue = e.target.value;

            this.showIconGrid();

            if(!this.iconsloaded) {
                this.fetchIcons(() => {
                    let filteredIcons = this.filterIcons(enteredValue);

                    this.renderIcons(filteredIcons);
                });
            } else {
                let filteredIcons = this.filterIcons(enteredValue);
            this.renderIcons(filteredIcons);
            }
        }

        this.inputEle.addEventListener('focus', this.handleInputFocus);    // handling input focus
        
        this.inputEle.addEventListener('keyup', (e) => {
            const enteredValue = e.target.value;
            
            let filteredIcons = this.filterIcons(enteredValue);

            this.renderIcons(filteredIcons);
        });

        window.addEventListener('click', (e) => {                          // handling click outside input
          if (!this.inputEle.contains(e.target) && !this.iconsGridEle.contains(e.target)){
            this.hideIconGrid();
          }
  	});


}

const picker = new simpleIconPicker('.tm-icon-picker');

Now run the script you will see a fully functional icon picker.

 

Github Repository

 

0 0 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
0
Confused
0

You may also like

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments