TUTORIAL - Create a Steem Browser Extension - JavaScript - Basic - Part 3
What Will I Learn?
- Use a custom extension Icon
- Use extension options
- A loading indicator screen
Requirements
- Read my previous parts (Click here)
- A TextEditor (e.g Atom.io)
- Basic JavaScript understanding
Difficulty
- Basic
Tutorial Contents
Custom Extension Icon
Every good product has its own Icon or Logo. I will show you as first step in this Tutorial how you can add a custom icon to your extension. For this purpose you need your logo ready as .png as 16x16px, 48x48px, 128x128px. Next open the manifest.json and add inside the icons key following values
"icons": {
"16": "./img/icon_16px.png",
"48": "./img/icon_48px.png",
"128": "./img/icon_128px.png"
}
Please make sure your icons exists in the img folder. (Btw. you can specify any path you want)
Test it and reload your extension

Using Extension Option Page
Every good extension does not define it's user depended variables inside of the code. To make it dynamic for every user we will use chrome extension options feature (here). These options can be opened and edited inside the lists of all your installed extensions.

(This is what the Options Button is used for)
But how can we as creator of the extension create one ?
Open your manifest.json and add following code between browser_action and content_security_policy:
"browser_action": {
...
},
"options_ui": {
"page": "options.html",
"chrome_style": true
},
"permissions": [
"storage"
],
"content_security_policy": "script-src 'self' https://cdn.steemjs.com; object-src 'self'"
options_ui defines the html which is used for the option popup, use options.html.
permissions is used because we need to safe the set options, therefore we need to access the local storage and need permissions for that.
Create now the options.html and options.js files !
options.html
<html>
<head>
<title>Options</title>
<script type="text/javascript" src="options.js"></script>
</head>
<body>
Steem User Account:
<input id="username-input" type="text" name="" value="">
<hr />
<button id="save">Save</button>
<div id="status"></div>
</body>
</html>
In the header we include the options.js script. Else we define some simple design with id's to modify the html from our script!
options.js
var input = null;
window.addEventListener('load', onLoad);
function onLoad() {
input = document.getElementById("username-input");
document.getElementById("save").addEventListener('click', saveOptions);
chrome.storage.sync.get(["username"], function(items) {
var username = '';
if (items.username !== undefined) {
username = items.username;
}
input.value = username;
});
}
function saveOptions() {
const username = input.value;
chrome.storage.sync.set({
username: username,
}, function() {
var status = document.getElementById('status');
status.textContent = 'Options saved.';
setTimeout(function() {
status.textContent = '';
}, 750);
});
}
We have to functions saveOptions and onLoad. onLoad is called when our html body loads, and sets the input variable accordingly as well as adding a click event for when the save button is pressed which fires the function saveOptions. Also does onLoad read our local storage and searches for the key "username". If a "username" is already set we will display it as the value for our input field.
saveOptions first gets the input field value and then tries to set it in our local storage for the key "username". If done it calls a callback where we shortly display a Options saved messaged below our save button (750ms).
Using a loading indicator
At this point of the series you maybe realized that loading our account informations takes some time. Plus loading data from the local storage consumes time too, our whole application get asynchronous and needs to wait for the tasks to finish BEFORE displaying the actual information. But what should we display in the meantime ? The empty design ? Nah, this is what loading indicators are meant for!
The idea is to show a loading indicator until our whole data is ready, then hide the indicator and show the actual design. For this purpose edit your index.html and index.js
index.html
<html>
<head>
<script src="https://cdn.steemjs.com/lib/latest/steem.min.js"></script>
<script src="./index.js"></script>
<style>
.profile-pic {
margin: auto;
width: 36px;
height: 36px;
border-radius: 18px;
overflow: hidden;
background-size: cover;
background-repeat: no-repeat;
background-position: 50% 50%;
}
.hidden {
display: none;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 15px;
height: 15px;
margin: auto;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body style="width: 200px">
<div id="indicator" class="loader">
</div>
<div id="container" class="hidden">
<div id="username" style="text-align: center;">
</div>
<div id="follow_stats">
</div>
<hr>
<div id="post_count">
</div>
<div id="steem_balance">
</div>
<div id="sbd_balance">
</div>
</div>
</body>
</html>
We added 2 new style classes: hidden (to hide elements) and loader (presenting a loading indicator).
Please read more about keyframes here
In the html we added a new div with the id "indicator" and as styling the class "loader".
The container div should be hidden from now on!
index.js
steem.api.setOptions({ url: 'wss://rpc.buildteam.io' });
window.addEventListener('load', onLoad);
function onLoad() {
chrome.storage.sync.get(["username"], function(items) {
var username = items.username;
if (username === undefined) {
return; // Should show error
}
getAccountData(username);
});
}
function getAccountData(username) {
var followCount = 0;
var followingCount = 0;
const indicator = document.getElementById("indicator");
const container = document.getElementById("container");
steem.api.getAccounts([username], function (err, result) {
if (err || !result) {
console.log('Error loading account: ' + err); // Should show error
return;
}
const userData = result[0];
console.log(userData);
const userHeader = document.getElementById("username");
userHeader.innerHTML = "<div class='profile-pic' style='background-image: url(https://steemitimages.com/u/"+ userData.name +"/avatar);'></div>" +
"<div>@"+ userData.name +"</div>";
const postCount = document.getElementById("post_count");
postCount.innerHTML = "<div style='display: inline-block;'>Post Count</div>" +
"<div style='display: inline-block; float: right'>"+ userData.post_count +"</div>";
const steemBalance = document.getElementById("steem_balance");
steemBalance.innerHTML = "<div style='display: inline-block;'>STEEM</div>" +
"<div style='display: inline-block; float: right'>"+ userData.balance +"</div>";
const sbdBalance = document.getElementById("sbd_balance");
sbdBalance.innerHTML = "<div style='display: inline-block;'>SBD</div>" +
"<div style='display: inline-block; float: right'>"+ userData.sbd_balance +"</div>";
steem.api.getFollowCount('moonrise', function(err, result) {
if (err || !result) {
console.log('Error loading follow count: ' + err); // Should show error
return;
}
followCount = result.follower_count;
followingCount = result.following_count;
const followStata = document.getElementById("follow_stats");
followStata.innerHTML = "<div style='display: inline-block;'>Follower: "+ followCount +"</div>" +
"<div style='display: inline-block; float: right'>Following: "+ followingCount +"</div>";
// Loaded everthing, so show content!
indicator.className += " hidden";
container.classList.remove('hidden');
});
});
}
(Click here for better formatted view)
Basically we changed the logic of our index.js as follows. First call the onLoad function and load the username from our settings. Then get the Account data and then the following / follower data. If something fails, the loading indicator won't hide. In this case we will show an error in the next episode.
If nothing fails we will hide the indicator & show our content div.
indicator.className += " hidden";
container.classList.remove('hidden');
The changes might look confusing, but basically we are now capsuling our api calls.
Try it and you will see the loading indicator before it shows the content !
Thanks for reading :)
What's up next ?
- Error Screen
Full Source Code
Curriculum
Posted on Utopian.io - Rewarding Open Source Contributors


very nice
Thank you for the contribution. It has been approved.
Would've been cool to see a GIF of the loading indicator!
You can contact us on Discord.
[utopian-moderator]
Hey @moonrise I am @utopian-io. I have just upvoted you!
Achievements
Suggestions
Get Noticed!
Community-Driven Witness!
I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!
Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x