ERLEND
erlend-homebrerwing-beer-demonstrated.png

How to write your own brewers calculator extension in Firefox or Chrome

Erlend EelmetsMonday 07 December 2020

Intro

\\"value\\": \\"<p>As I have previously demonstrated how to create simple extensions like browser <a id=\\\\\\"9\\\\\\" linktype=\\\\\\"page\\\\\\">History Clearer</a> and a basic <a id=\\\\\\"10\\\\\\" linktype=\\\\\\"page\\\\\\">Ad-blocker</a>. I will move onto showing how to write a slightly more complex application that uses the popup bar as it&#x27;s control panel. This will add some interactivity to our future extensions. Additionally we will get into including outside libraries into our extensions by including <a href=\\\\\\"https://getbootstrap.com/\\\\\\">Bootstrap</a> here for some quick and dirty design.</p><p>We will be building a homebrewer calculator. Why? Well because the core logic will be just mathemathical formulas. So we can focus on the extension writing problems. You can substitute the formulas for whatever you want for your app. Make it a kitchen measure converter,

metric-to-imperial calculator. I just wanted create something a bit less generic and potentially useful. Thus I will be using formulas homebrewers use for their brewing proccesses.</p><p>We will first go through planning the application. Then figure out the core logic. The formulas we will need. That will be followed by creating a HTML skeleton for our extension. We will quickly style it using some CSS and a library called <a href=\\\\\\"https://getbootstrap.com/\\\\\\">Bootstrap</a>. Finally we will connect the frontend and the backend trough even listeners. We will finish up by doing some testing and uploading our extension to Firefox and Chrome developer hubs.</p>\\",

The plan

\\"value\\": \\"<ol><li>Project layout</li><li>Calculations</li><li>HTML</li><li>CSS</li><li>Javascript</li><li>Testing and publishing</li><li>Closing thoughts</li></ol>\\",

\\"id\\": \\"32e8003b-133b-43d1-aa83-9255f14ee95d\\"},

Project layout

\\"value\\": \\"<p>We start as we are used to by now. Pick a directory for your project. Create a new file called &quot;manifest.json&quot;. We are going to fill in our basic boilerplate here.</p>\\",

\\"code\\": \\"{\\\\r\\\\n    \\\\\\"manifest_version\\\\\\": 2,
\\\\r\\\\n    \\\\\\"name\\\\\\": \\\\\\"Brewy\\\\\\",
\\\\r\\\\n    \\\\\\"author\\\\\\": \\\\\\"Erlend Eelmets\\\\\\",
\\\\r\\\\n    \\\\\\"version\\\\\\": \\\\\\"0.1\\\\\\",
\\\\r\\\\n    \\\\\\"description\\\\\\": \\\\\\"Various formulas and tools that are useful for homebrewer.\\\\\\",
\\\\r\\\\n    \\\\\\"icons\\\\\\": {\\\\r\\\\n        \\\\\\"36\\\\\\": \\\\\\"icons/android-icon-36x36.png\\\\\\",
\\\\r\\\\n        \\\\\\"48\\\\\\": \\\\\\"icons/android-icon-48x48.png\\\\\\",
\\\\r\\\\n        \\\\\\"72\\\\\\": \\\\\\"icons/android-icon-72x72.png\\\\\\",
\\\\r\\\\n        \\\\\\"96\\\\\\": \\\\\\"icons/android-icon-96x96.png\\\\\\",
\\\\r\\\\n        \\\\\\"144\\\\\\": \\\\\\"icons/android-icon-144x144.png\\\\\\",
\\\\r\\\\n        \\\\\\"192\\\\\\": \\\\\\"icons/android-icon-192x192.png\\\\\\"\\\\r\\\\n      },
\\\\r\\\\n    \\\\\\"browser_action\\\\\\": {\\\\r\\\\n      \\\\\\"default_icon\\\\\\": {\\\\r\\\\n        \\\\\\"36\\\\\\": \\\\\\"icons/android-icon-36x36.png\\\\\\",
\\\\r\\\\n        \\\\\\"48\\\\\\": \\\\\\"icons/android-icon-48x48.png\\\\\\",
\\\\r\\\\n        \\\\\\"72\\\\\\": \\\\\\"icons/android-icon-72x72.png\\\\\\",
\\\\r\\\\n        \\\\\\"96\\\\\\": \\\\\\"icons/android-icon-96x96.png\\\\\\",
\\\\r\\\\n        \\\\\\"144\\\\\\": \\\\\\"icons/android-icon-144x144.png\\\\\\",
\\\\r\\\\n        \\\\\\"192\\\\\\": \\\\\\"icons/android-icon-192x192.png\\\\\\"\\\\r\\\\n      },
\\\\r\\\\n      \\\\\\"default_title\\\\\\": \\\\\\"Brewy: Homebrewer toolkit\\\\\\",
\\\\r\\\\n      \\\\\\"default_popup\\\\\\": \\\\\\"popup.html\\\\\\"\\\\r\\\\n    },
\\\\r\\\\n    \\\\r\\\\n    \\\\\\"background\\\\\\": {\\\\r\\\\n        \\\\\\"persistent\\\\\\": true,
\\\\r\\\\n        \\\\\\"scripts\\\\\\": [\\\\r\\\\n            \\\\\\"js/background.js\\\\\\"\\\\r\\\\n        ]\\\\r\\\\n    },
\\\\r\\\\n\\\\r\\\\n\\\\r\\\\n    \\\\\\"permissions\\\\\\": [\\\\r\\\\n      ]\\\\r\\\\n  }\\"},
 \\"id\\": \\"e71321e1-d5b2-46f5-8c3e-9e65a086c049\\"},

<p>Save and create new subdirectories called &quot;css&quot;,

&quot;js&quot;,

&quot;icons&quot;. These will allow us to organise our project a bit better.</p><p>Create a new HTML file called &quot;popup.html&quot;. This is where all of our markup will live.</p><p>Next navigate to the js directory and create file called &quot;background.js&quot;. This is where all of your Javascript logic will live.</p><p>Navigate back to the &quot;css&quot; folder we created. Create a file called &quot;styles.css&quot;</p><p>We are going to use a library called <a href=\\\\\\"https://getbootstrap.com/docs/4.5/getting-started/download/\\\\\\">Bootstrap</a>. So use the link I provided to download the file. We will be needing only the &quot;bootsrap.css&quot; file since all we are really interested in is the looks of it. Extract it to the &quot;css&quot; folder we previously created. With that we have our project layout done.</p><p>Don&#x27;t use the CDN version. It works fine on websites but on extensions Chrome Extension Store requires extra permissions and you to go trough extra review steps due to needing to guard against code injection from foreign sources. Just extra headache we don&#x27;t need for now. Besides in the future when we introduce more complex libraries like Vue or React we will be using a module bundler anyway.</p>\\",

Calculations

\\"value\\": \\"<p>The calculations I use this section are gathered throughout the internet. Mostly from wikipedia. Since it includes too many links I will be just demonstrating the functions I arrived at after reducing the formulas into functions. You can copy them into your &quot;background.js&quot; file.</p>\\",

\\"id\\": \\"0673796b-0ee1-4960-8adf-3cd4cd6c6b8a\\"},

\\"function beerAlcoholContent(originalGravity,
 finalGravity){\\\\r\\\\n    return rounddecimal((76.08 * (originalGravity - finalGravity) / (1.775 - originalGravity)) * (finalGravity / 0.794),
 1)\\\\r\\\\n}\\\\r\\\\n\\\\r\\\\nfunction beerPrimingCalculator(temp,
 batchsize,
 volumes) {\\\\r\\\\n    return rounddecimal(((volumes * 2) - (3.0378 - (0.050062 * temp) + (0.00026555 * temp * temp) * 2)) * 2 * batchsize,
 1)\\\\r\\\\n}\\\\r\\\\n\\\\r\\\\nfunction convertSugars(sucrose){\\\\r\\\\n    return {\\\\\\"Sucrose\\\\\\": rounddecimal(sucrose,
 1),
 \\\\\\"Dextrose\\\\\\": rounddecimal(sucrose / 0.91,
 1),
 \\\\\\"Brown sugar\\\\\\": rounddecimal(sucrose / 0.89,
1) ,
 \\\\\\"Honey\\\\\\": rounddecimal(sucrose / 0.74,
 1)};\\\\r\\\\n}\\\\r\\\\n\\\\r\\\\nfunction beerApparentAttenuation(originalGravity,
 finalGravity){\\\\r\\\\n    return (originalGravity - finalGravity) / originalGravity - 1\\\\r\\\\n}\\"},

\"<p>So here we have five seperate functions.</p><ol><li>First one seems pretty self explanatory. It is to calculate the alcohol content. You measure specific gravity of the beer before you add yeast to it and you just measure the specific gravity or the wort after yeast has eaten all the sugars. You pop it into the formula and you have a close approximation of the alcohol content of the beer.</li><li>Second one is concerned with bottling the beer. Once we have our beer we want to bottle it but we also want to add carbonation to it so it wouldn&#x27;t taste flat. To do that we need to add some more sugar after bottling the beer so the yeast can create the CO2 we need. It is important to get this about right otherwise you might crack your bottle or end up with a flat beer.</li><li>Third calculation is a supplement to the second one which allows us to use various different variations of sugar but still end up with the same result.</li><li>Fourth one is for measuring how much of the sugars have been converted into alcohol by fermentation process. This calculations is useful to figure out whether the fermentation process is done yet. It is a bit old and flawed due to assuming that ethanol and water have the same specific gravity but as a rough estimate still works.</li></ol><p>With that we have the core logic of the extension written. Now we will need to make it usable.</p>\\",

\\"id\\": \\"f9d248df-11fa-4928-8b71-f507ad45e050\\"},

HTML

<p>The HTML as in the skeleton of the popup. Open up your &quot;popup.html&quot;. We are going to start out with some basic boilerplate HTML to get us going.</p><p></p>\\",

\\"id\\": \\"edbba071-b6a5-4ee4-9d8a-9e53c44466fa\\"},

\\"code\\": \\"<!doctype html>\\\\r\\\\n\\\\r\\\\n<html lang=\\\\\\"en\\\\\\">\\\\r\\\\n<head>\\\\r\\\\n  <meta charset=\\\\\\"utf-8\\\\\\">\\\\r\\\\n  <link rel=\\\\\\"stylesheet\\\\\\" href=\\\\\\"css/bootstrap.css\\\\\\">\\\\r\\\\n  <link rel=\\\\\\"stylesheet\\\\\\" href=\\\\\\"css/styles.css\\\\\\">\\\\r\\\\n</head>\\\\r\\\\n<body>\\\\r\\\\n\\\\r\\\\n</body>\\\\r\\\\n</html>\\"},
 \\"id\\": \\"7fa89130-a23c-48cf-936d-35713db65756\\"},

<p>With that we have our boilerplate setup to include our CSS files. Next we are going to create title bar.</p>\\",

\\"id\\": \\"6dd313e7-4a4e-4e34-85d8-1da33bf14ad1\\"},

\\"code\\": \\"<!doctype html>\\\\r\\\\n\\\\r\\\\n<html lang=\\\\\\"en\\\\\\">\\\\r\\\\n<head>\\\\r\\\\n  <meta charset=\\\\\\"utf-8\\\\\\">\\\\r\\\\n  <link rel=\\\\\\"stylesheet\\\\\\" href=\\\\\\"css/bootstrap.css\\\\\\">\\\\r\\\\n  <link rel=\\\\\\"stylesheet\\\\\\" href=\\\\\\"css/styles.css\\\\\\">\\\\r\\\\n</head>\\\\r\\\\n<body>\\\\r\\\\n    <div>\\\\r\\\\n         <div>\\\\r\\\\n            <div><img src=\\\\\\"icons/android-icon-48x48.png\\\\\\"></div>\\\\r\\\\n            <span>\\\\r\\\\n                Brewy\\\\r\\\\n            </span>\\\\r\\\\n         </div>\\\\r\\\\n    </div>\\\\r\\\\n</body>\\\\r\\\\n</html>\\"},
 \\"id\\": \\"680598a4-b39f-4a43-a33e-e80ebd05f9f1\\"},

\\"value\\": \\"<p>Next let&#x27;s add a navbar for seperate tools. So we don&#x27;t have to scroll and can just click trough different sections.</p>\\",

\\"id\\": \\"ad29ec72-422a-47f3-9349-fdb6e1af7ce4\\"},

\\"code\\": \\"<div>\\\\r\\\\n            <button id=\\\\\\"btn-tab1\\\\\\">Alcohol Content</button>\\\\r\\\\n            <button id=\\\\\\"btn-tab2\\\\\\">Apparent Attenuation</button>\\\\r\\\\n            <button id=\\\\\\"btn-tab3\\\\\\">Priming</button>\\\\r\\\\n</div>\\"},
 \\"id\\": \\"a9d40505-cdda-426f-8b07-56b4d13d197c\\"},

\\"value\\": \\"<p>Next we come to our tabs. We are going to create HTTP POST form in it so we can use browsers native client side field validation and later on we are going to override it&#x27;s default functionality with Javascript. We need to specify ID and Class fields for nodes we already know we are going to need work with within Javascript. Additionally we will make other tabs invisible on screen by default.</p>\\",

\\"id\\": \\"2d6b5c4a-f103-42c2-9e95-211589af899f\\"},

\\"code\\": \\"<div id=\\\\\\"tab1\\\\\\">\\\\r\\\\n    <h2>Alcohol By Volume</h2>\\\\r\\\\n    <p>\\\\r\\\\n        Finds ABV based on gravity change. If you took an original gravity reading (or had estimated OG),
 and also took a final gravity (FG) reading prior to adding priming sugar at bottling you can find out your batch's alcohol by volume\\\\r\\\\n        ABV.\\\\r\\\\n    </p>\\\\r\\\\n    <form id=\\\\\\"abv-form\\\\\\"  class=\\\\\\"tab\\\\\\">\\\\r\\\\n        <div>\\\\r\\\\n            <div>\\\\r\\\\n                <div>\\\\r\\\\n                    <span>Original gravity</span>\\\\r\\\\n                </div>\\\\r\\\\n                <input type=\\\\\\"number\\\\\\" step=\\\\\\"0.001\\\\\\" placeholder=\\\\\\"1.058\\\\\\" name=\\\\\\"og\\\\\\" id=\\\\\\"abv-og\\\\\\" required />\\\\r\\\\n            </div>\\\\r\\\\n            <div>\\\\r\\\\n                <div>\\\\r\\\\n                    <span>Final gravity</span>\\\\r\\\\n                </div>\\\\r\\\\n                <input type=\\\\\\"number\\\\\\" step=\\\\\\"0.001\\\\\\" placeholder=\\\\\\"1.005\\\\\\" name=\\\\\\"fg\\\\\\" id=\\\\\\"abv-fg\\\\\\" required />\\\\r\\\\n            </div>\\\\r\\\\n            <h4>Value:</h4>\\\\r\\\\n            <h3 id=\\\\\\"abv-val\\\\\\">0<span>%</span></h3>\\\\r\\\\n            <button type=\\\\\\"submit\\\\\\">Calculate</button>\\\\r\\\\n        </div>\\\\r\\\\n    </form>\\\\r\\\\n</div>\\\\r\\\\n\\\\r\\\\n<div id=\\\\\\"tab2\\\\\\" style=\\\\\\"display: none;\\\\\\"  class=\\\\\\"tab\\\\\\">\\\\r\\\\n    <h2>Apparent Attenuation</h2>\\\\r\\\\n    <p>\\\\r\\\\n        In brewing,
 attenuation refers to the conversion of sugars into alcohol and carbon dioxide by the fermentation process; the greater the attenuation,
 the more sugar has been converted into alcohol. A more attenuated beer is drier and\\\\r\\\\n        more alcoholic than a less attenuated beer made from the same wort. Attenuation can be quantified by comparing the specific gravity \\\\u2014 the density of a solution,
 relative to pure water \\\\u2014 of the extract before and after fermentation,
\\\\r\\\\n        quantities termed the original and final gravities. Specific gravity can be measured by buoyancy,
 with a hydrometer. The higher the specific gravity of a solution,
 the higher the hydrometer floats.\\\\r\\\\n    </p>\\\\r\\\\n    <form id=\\\\\\"aa-form\\\\\\">\\\\r\\\\n        <div>\\\\r\\\\n            <div>\\\\r\\\\n                <div>\\\\r\\\\n                    <span>Original gravity</span>\\\\r\\\\n                </div>\\\\r\\\\n                <input type=\\\\\\"number\\\\\\" step=\\\\\\"0.001\\\\\\" placeholder=\\\\\\"1.058\\\\\\" name=\\\\\\"og\\\\\\" id=\\\\\\"aa-og\\\\\\" required />\\\\r\\\\n            </div>\\\\r\\\\n            <div>\\\\r\\\\n                <div>\\\\r\\\\n                    <span>Final gravity</span>\\\\r\\\\n                </div>\\\\r\\\\n                <input type=\\\\\\"number\\\\\\" step=\\\\\\"0.001\\\\\\" placeholder=\\\\\\"1.005\\\\\\" name=\\\\\\"fg\\\\\\" id=\\\\\\"aa-fg\\\\\\" required />\\\\r\\\\n            </div>\\\\r\\\\n            <h4>Value:</h4>\\\\r\\\\n            <h3 id=\\\\\\"aa-val\\\\\\">0<span></span></h3>\\\\r\\\\n            <button type=\\\\\\"submit\\\\\\">Calculate</button>\\\\r\\\\n        </div>\\\\r\\\\n    </form>\\\\r\\\\n</div>\\\\r\\\\n<div id=\\\\\\"tab3\\\\\\" style=\\\\\\"display: none;\\\\\\"  class=\\\\\\"tab\\\\\\">\\\\r\\\\n    <h2>Beer Priming</h2>\\\\r\\\\n    <p>\\\\r\\\\n        Calculates how much priming sugar to add at bottling time for home brewed beer. Includes the residual amount of CO2 present in the beer due to fermentation. Works for Corn Sugar (Dextrose),
 Table Sugar (Sucrose),
 Dry Malt Extract\\\\r\\\\n        (DME),
 and variety of priming sugars. Also known as bottle priming. Sugar is added at bottling time. The remaining yeast ferment the sugar and this produces CO2.\\\\r\\\\n    </p>\\\\r\\\\n    <form id=\\\\\\"primer-form\\\\\\">\\\\r\\\\n        <div>\\\\r\\\\n            <div>\\\\r\\\\n                <div>\\\\r\\\\n                    <span>Amount(L)</span>\\\\r\\\\n                </div>\\\\r\\\\n                <input type=\\\\\\"number\\\\\\" placeholder=\\\\\\"20\\\\\\" name=\\\\\\"og\\\\\\" id=\\\\\\"primer-batch\\\\\\" required />\\\\r\\\\n            </div>\\\\r\\\\n            <div>\\\\r\\\\n                <div>\\\\r\\\\n                    <span>Volumes of CO2</span>\\\\r\\\\n                </div>\\\\r\\\\n                <input type=\\\\\\"number\\\\\\" placeholder=\\\\\\"2.0\\\\\\" name=\\\\\\"fg\\\\\\" id=\\\\\\"primer-volumes\\\\\\" required />\\\\r\\\\n            </div>\\\\r\\\\n            <div>\\\\r\\\\n                <div>\\\\r\\\\n                    <span>Temperature</span>\\\\r\\\\n                </div>\\\\r\\\\n                <input type=\\\\\\"number\\\\\\" placeholder=\\\\\\"20\\\\\\" name=\\\\\\"temp\\\\\\" id=\\\\\\"primer-temp\\\\\\" required />\\\\r\\\\n            </div>\\\\r\\\\n\\\\r\\\\n            <table>\\\\r\\\\n                <thead>\\\\r\\\\n                    <tr>\\\\r\\\\n                        <th scope=\\\\\\"col\\\\\\">Sugar</th>\\\\r\\\\n                        <th scope=\\\\\\"col\\\\\\">Weight</th>\\\\r\\\\n                    </tr>\\\\r\\\\n                </thead>\\\\r\\\\n                <tbody id=\\\\\\"primer-body\\\\\\"></tbody>\\\\r\\\\n            </table>\\\\r\\\\n            <button type=\\\\\\"submit\\\\\\">Calculate</button>\\\\r\\\\n        </div>\\\\r\\\\n    </form>\\\\r\\\\n    <br />\\\\r\\\\n</div>\\"},
 \\"id\\": \\"bebcd92d-a630-4a18-8260-fad26f2cd6c5\\"},

\\"value\\": \\"<p>With that we have the layout of the popup done. Next we are going to quickly use some CSS classes from Bootstrap to give the items some quick styling features to improve the look.</p>\\",

\\"id\\": \\"ea9d47b2-3dcd-4bf1-a687-ea744efe08e5\\"},

CSS

\\"value\\": \\"<p>Now the styling. The easyest bit that makes the popup look good. It is easiest to do this with temporarily loading up the extension in your browser. In firefox open in URL bar &quot;about:debugging&quot;. Then click &quot;This Firefox&quot;. After that &quot;Load Temporary addon&quot; and navigate to your &quot;manifest.json&quot; file.</p><p>Now you should have your extremly ugly but functional extension loaded. Now we just start adding classes to our HTML nodes. The easiest way to get comfortable with it is to go trough Bootstrap documentation. Most of the classnames you use similar naming conventions as CSS and HTML does but with with it&#x27;s own logic. Overall it is not complex to do with Bootsrap documentation on one side of the screen and your own on the other. So I am going to run through it fairly quickly.</p><p>Add overall container class to our content container which creates a nice margin. Swap header to flexbox and give it a margin. Add margin to image container and change the image class to fluid sized image. Finally give the name Header 1 styling.</p>\\",

\\"id\\": \\"41f2077e-8f17-47df-a5bd-76f9325d6bc3\\"},

\\"code\\": \\"<div class=\\\\\\"container-fluid\\\\\\">\\\\r\\\\n         <div class=\\\\\\"m-3 d-flex\\\\\\">\\\\r\\\\n            <div class=\\\\\\"mr-3\\\\\\"><img src=\\\\\\"icons/android-icon-48x48.png\\\\\\" class=\\\\\\"img-fluid\\\\\\"></div>\\\\r\\\\n            <span class=\\\\\\"h1\\\\\\">\\\\r\\\\n                Brewy\\\\r\\\\n            </span>\\\\r\\\\n         </div>\\"},
 \\"id\\": \\"5b271517-7021-4f4a-a8a2-815662ea637b\\"},

\\"value\\": \\"<p>Next we need to style the navbar. We will group it as a button group. Give it a bottom margin. Then give some nice Bootstrap button styles to the buttons themselves.</p>\\",

\\"id\\": \\"002165ef-619f-4b85-af28-d5097cc6ee79\\"},

\\"code\\": \\"<div class=\\\\\\"btn-group btn-manager mb-3\\\\\\" role=\\\\\\"group\\\\\\">\\\\r\\\\n            <button type=\\\\\\"button\\\\\\" class=\\\\\\"btn btn-outline-dark btn-tab\\\\\\" id=\\\\\\"btn-tab1\\\\\\">Alcohol Content</button>\\\\r\\\\n            <button type=\\\\\\"button\\\\\\" class=\\\\\\"btn btn-outline-dark btn-tab\\\\\\" id=\\\\\\"btn-tab2\\\\\\">Apparent Attenuation</button>\\\\r\\\\n            <button type=\\\\\\"button\\\\\\" class=\\\\\\"btn btn-outline-dark btn-tab\\\\\\" id=\\\\\\"btn-tab3\\\\\\">Priming</button>\\\\r\\\\n</div>\\"},
 \\"id\\": \\"0a117529-a963-4fed-94e5-7f323a9d961e\\"},

\\"value\\": \\"<p>Now some tab styling. Form group styling from Bootstrap. Inputs and some margins,

do a fancy prepended from,

and finally the button again. Rinse and repeat for all of the tabs.</p>\\",

\\"code\\": \\"<div id=\\\\\\"tab1\\\\\\" class=\\\\\\"tab\\\\\\">\\\\r\\\\n            <h2>Alcohol By Volume</h2>\\\\r\\\\n            <p>Finds ABV based on gravity change. If you took an original gravity reading (or had estimated OG),
 and also took a final gravity (FG) reading prior to adding priming sugar at bottling you can find out your batch's alcohol by volume ABV. </p>\\\\r\\\\n            <form id=\\\\\\"abv-form\\\\\\">\\\\r\\\\n               <div class=\\\\\\"form-group\\\\\\">\\\\r\\\\n                  <div class=\\\\\\"input-group mb-3\\\\\\">\\\\r\\\\n                     <div class=\\\\\\"input-group-prepend\\\\\\">\\\\r\\\\n                        <span class=\\\\\\"input-group-text\\\\\\">Original gravity</span>\\\\r\\\\n                     </div>\\\\r\\\\n                     <input type=\\\\\\"number\\\\\\" class=\\\\\\"form-control\\\\\\" step=\\\\\\"0.001\\\\\\" placeholder=\\\\\\"1.058\\\\\\" name=\\\\\\"og\\\\\\" id=\\\\\\"abv-og\\\\\\" required>\\\\r\\\\n                  </div>\\\\r\\\\n                  <div class=\\\\\\"input-group mb-3\\\\\\">\\\\r\\\\n                     <div class=\\\\\\"input-group-prepend\\\\\\">\\\\r\\\\n                        <span class=\\\\\\"input-group-text\\\\\\">Final gravity</span>\\\\r\\\\n                     </div>\\\\r\\\\n                     <input type=\\\\\\"number\\\\\\" class=\\\\\\"form-control\\\\\\" step=\\\\\\"0.001\\\\\\" placeholder=\\\\\\"1.005\\\\\\" name=\\\\\\"fg\\\\\\" id=\\\\\\"abv-fg\\\\\\"  required>\\\\r\\\\n                  </div>\\\\r\\\\n                  <h4 class=\\\\\\"mb-3\\\\\\">Value: </h4>\\\\r\\\\n                  <h3 class=\\\\\\"mb-3\\\\\\" id=\\\\\\"abv-val\\\\\\">0<span>%</span></h3>\\\\r\\\\n                  <button  type=\\\\\\"submit\\\\\\" class=\\\\\\"btn btn-primary\\\\\\">Calculate</button>\\\\r\\\\n               </div>\\\\r\\\\n            </form>\\\\r\\\\n         </div>\\"},

\\"value\\": \\"<p>With that we have now styled our popup. It should look much better now from before. Do not forget to reload the extension if you see no apparent changes. Onto the function.</p>\\",

Javascript

\\"value\\": \\"<p>Due to the nature of our pop up we are going to need to listen for 2 different types of event. One for our tabbing system and another for our submission, so we can do the calculations. First let&#x27;s make our tabbing function as it should. Open up your &quot;background.js&quot; file. Add an event listener that listens for &quot;click&quot; events.</p>\\",

\\"code\\": \\"document.addEventListener(\\\\\\"click\\\\\\",
 function (event) {\\\\r\\\\n},
 false)\\"},

\\"value\\": \\"<p>This function tells the browser to execute the code within this function whenever it sees a &quot;click&quot; event. But we don&#x27;t want it to execute our functions when somebody clicks for example on the refresh or back button. So we need make our listener more specific. Let&#x27;s listen for click event on only elements with very specific ID.</p>\\",

\\"code\\": \\"document.addEventListener(\\\\\\"click\\\\\\",
 function (event) {\\\\r\\\\n    var eventId = event.target.id;\\\\r\\\\n\\\\r\\\\n    if (eventId === \\\\\\"btn-tab1\\\\\\"){\\\\r\\\\n\\\\r\\\\n    } else if(eventId === \\\\\\"btn-tab2\\\\\\"){\\\\r\\\\n\\\\r\\\\n    } else if(eventId === \\\\\\"btn-tab3\\\\\\"){\\\\r\\\\n\\\\r\\\\n    }\\\\r\\\\n},
 false)\\"},

\\"value\\": \\"<p>Now we a variable that get&#x27;s the ID of the item that was clicked on and our if function compares it to an ID we have previously set. If it matches it will execute whatever code it holds within. We want this code to do primarily make one of the tabs visible and others invisible. Thus to achieve our tabbing function. Additionally to add &quot;active&quot; CSS class to our active tab and remove it from all the others thus demonstrating to the end user which of the tabs is open. We can achieve it thusly.</p>\\",

\\"code\\": \\"document.addEventListener(\\\\\\"click\\\\\\",
 function (event) {\\\\r\\\\n    var eventId = event.target.id;\\\\r\\\\n\\\\r\\\\n    if (eventId === \\\\\\"btn-tab1\\\\\\"){\\\\r\\\\n        var tabs = document.querySelectorAll(\\\\\\".tab\\\\\\");\\\\r\\\\n        var btnTabs = document.querySelectorAll(\\\\\\".btn-tab\\\\\\");\\\\r\\\\n        tabs.forEach(function(item){item.style.display = \\\\\\"none\\\\\\"})\\\\r\\\\n        btnTabs.forEach(function(item){item.classList.remove(\\\\\\"active\\\\\\")})\\\\r\\\\n        document.querySelector(\\\\\\"#tab1\\\\\\").style.display = 'block';\\\\r\\\\n        document.querySelector(\\\\\\"#btn-tab1\\\\\\").classList.add(\\\\\\"active\\\\\\") \\\\r\\\\n\\\\r\\\\n    } else if(eventId === \\\\\\"btn-tab2\\\\\\"){\\\\r\\\\n\\\\r\\\\n    } else if(eventId === \\\\\\"btn-tab3\\\\\\"){\\\\r\\\\n\\\\r\\\\n    }\\\\r\\\\n},
 false)\\"},
 \\"id\\": \\"33745560-e3a2-4a8e-ae73-85443792d638\\"},
 {\\"type\\": \\"paragraph\\",
 \\"value\\": \\"<p>This however would require us to repeat ourselves for each different if case. This would ruin one of the good principles of good design DRY - Do not Repeat Yourself. Thus we should refactor the code so we do not need to repeat ourselves. We should create a function out of it.</p>\\",
 \\"id\\": \\"39b9b403-e204-417b-af5d-8760d0745fe6\\"},
 {\\"type\\": \\"code\\",
 \\"value\\": {\\"language\\": \\"javascript\\",
 \\"caption\\": \\"background.js\\",
 \\"code\\": \\"function makeActiveTab(block,
 button){\\\\r\\\\n    var tabs = document.querySelectorAll(\\\\\\".tab\\\\\\");\\\\r\\\\n    var btnTabs = document.querySelectorAll(\\\\\\".btn-tab\\\\\\");\\\\r\\\\n    tabs.forEach(function(item){item.style.display = \\\\\\"none\\\\\\"})\\\\r\\\\n    btnTabs.forEach(function(item){item.classList.remove(\\\\\\"active\\\\\\")})\\\\r\\\\n    document.querySelector(block).style.display = 'block';\\\\r\\\\n    document.querySelector(button).classList.add(\\\\\\"active\\\\\\") \\\\r\\\\n}\\"},
 \\"id\\": \\"d0f37c23-2a0c-4b22-9ef2-e0ed8c13b23f\\"},
 {\\"type\\": \\"paragraph\\",
 \\"value\\": \\"<p>Now we can easily rewrite our event listener like this.</p>\\",
 \\"id\\": \\"9b810f9d-016c-4a20-819e-a395a667edd7\\"},
 {\\"type\\": \\"code\\",
 \\"value\\": {\\"language\\": \\"javascript\\",
 \\"caption\\": \\"background.js\\",
 \\"code\\": \\"function makeActiveTab(block,
 button){\\\\r\\\\n    var tabs = document.querySelectorAll(\\\\\\".tab\\\\\\");\\\\r\\\\n    var btnTabs = document.querySelectorAll(\\\\\\".btn-tab\\\\\\");\\\\r\\\\n    tabs.forEach(function(item){item.style.display = \\\\\\"none\\\\\\"})\\\\r\\\\n    btnTabs.forEach(function(item){item.classList.remove(\\\\\\"active\\\\\\")})\\\\r\\\\n    document.querySelector(block).style.display = 'block';\\\\r\\\\n    document.querySelector(button).classList.add(\\\\\\"active\\\\\\") \\\\r\\\\n}\\\\r\\\\n\\\\r\\\\ndocument.addEventListener(\\\\\\"click\\\\\\",
 function (event) {\\\\r\\\\n    var eventId = event.target.id;\\\\r\\\\n    if (eventId === \\\\\\"btn-tab1\\\\\\"){\\\\r\\\\nmakeActiveTab(\\\\\\"#tab1\\\\\\",
 \\\\\\"btn-tab1\\\\\\")\\\\r\\\\n    } else if(eventId === \\\\\\"btn-tab2\\\\\\"){\\\\r\\\\nmakeActiveTab(\\\\\\"#tab2\\\\\\",
 \\\\\\"btn-tab2\\\\\\")\\\\r\\\\n    } else if(eventId === \\\\\\"btn-tab3\\\\\\"){\\\\r\\\\nmakeActiveTab(\\\\\\"#tab3\\\\\\",
 \\\\\\"btn-tab3\\\\\\")\\\\r\\\\n    }\\\\r\\\\n},
 false)\\"},

With that our tabs should be nice and functional. Onto connecting the forms.

\\"code\\": \\"document.addEventListener(\\\\\\"submit\\\\\\",
 function(event){\\\\r\\\\n    var inpObj = event.target\\\\r\\\\n\\\\r\\\\n    if(inpObj.checkValidity() && inpObj.id === \\\\\\"abv-form\\\\\\"){\\\\r\\\\n        var og = document.querySelector(\\\\\\"#abv-og\\\\\\").value\\\\r\\\\n        var fg = document.querySelector(\\\\\\"#abv-fg\\\\\\").value\\\\r\\\\n        var abv = beerAlcoholContent(og,
 fg)\\\\r\\\\n        document.querySelector(\\\\\\"#abv-val\\\\\\").textContent = abv.toString() + \\\\\\"%\\\\\\"\\\\r\\\\n        event.preventDefault();\\\\r\\\\n    } else if (inpObj.checkValidity() && inpObj.id === \\\\\\"primer-form\\\\\\"){\\\\r\\\\n        var batch = document.querySelector(\\\\\\"#primer-batch\\\\\\").value\\\\r\\\\n        var volumes = document.querySelector(\\\\\\"#primer-volumes\\\\\\").value\\\\r\\\\n        var temp = document.querySelector(\\\\\\"#primer-temp\\\\\\").value\\\\r\\\\n        var priming = beerPrimingCalculator(batch,
 volumes,
 temp)\\\\r\\\\n        var sugars = convertSugars(priming)\\\\r\\\\n        document.querySelector(\\\\\\"#primer-body\\\\\\").textContent = ''\\\\r\\\\n        Object.keys(sugars).map(function(objKey,
 index){\\\\r\\\\n            var value = sugars[objKey]\\\\r\\\\n            var containerElem = document.createElement(\\\\\\"tr\\\\\\")\\\\r\\\\n            var keyElem = document.createElement(\\\\\\"td\\\\\\")\\\\r\\\\n            keyElem.textContent = objKey.toString()\\\\r\\\\n            var valueElem = document.createElement(\\\\\\"td\\\\\\")\\\\r\\\\n            valueElem.textContent = value.toString() + \\\\\\"g\\\\\\"\\\\r\\\\n            containerElem.appendChild(keyElem)\\\\r\\\\n            containerElem.appendChild(valueElem)\\\\r\\\\n            document.querySelector(\\\\\\"#primer-body\\\\\\").appendChild(containerElem)\\\\r\\\\n        })\\\\r\\\\n        event.preventDefault();\\\\r\\\\n    } else if (inpObj.checkValidity() && inpObj.id === \\\\\\"pr-form\\\\\\"){\\\\r\\\\n        var originalGravity = document.querySelector(\\\\\\"#pr-og\\\\\\").value\\\\r\\\\n        var wortVolume = document.querySelector(\\\\\\"#pr-wv\\\\\\").value\\\\r\\\\n        var k = document.querySelector(\\\\\\"#pr-k\\\\\\").value\\\\r\\\\n        var bpr = beerPitchingRate(k,
 wortVolume,
 originalGravity)\\\\r\\\\n        document.querySelector(\\\\\\"#pr-val\\\\\\").textContent = bpr.toString() \\\\r\\\\n        event.preventDefault();\\\\r\\\\n    } else {\\\\r\\\\n        event.preventDefault();\\\\r\\\\n    }\\\\r\\\\n})\\"},

After all that we have a functional extension. All that is left is to package it and upload it.

Testing and publishing

\\"value\\": \\"<p>You can publish this extension now on the Firefox developer hub and on the Chrome developer hub. A slightly more advanced version of this extension can be seen on the <a href=\\\\\\"https://addons.mozilla.org/en-GB/firefox/addon/brewy/\\\\\\">Firefox add-on marketplace</a>.</p>\\",

Some more reading
Erinevate Eesti riigi API-de kogumik
Eesti maakondade GEOJSON lihtsateks kaartide valmistamiseks
How to write your own Clear History extension for Firefox or Chrome