<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ google apps script - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ google apps script - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 16:31:52 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/google-apps-script/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Pop-up Image in Your Spreadsheet ]]>
                </title>
                <description>
                    <![CDATA[ In this article, I'll show you two ways to create a lightbox effect in a spreadsheet. The first will trigger the image to be displayed in a large area in the sheet. The second will be an actual HTML popup on top of the sheet. If you want to follow ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-an-image-lightbox/</link>
                <guid isPermaLink="false">66b8de0647c23b7ae1ad0bc0</guid>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Thu, 16 May 2024 10:17:53 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/05/Lightbox-2-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, I'll show you two ways to create a lightbox effect in a spreadsheet. The first will trigger the image to be displayed in a large area in the sheet. The second will be an actual HTML popup on top of the sheet.</p>
<p>If you want to follow along with the sheet I used, you can access it <a target="_blank" href="https://docs.google.com/spreadsheets/d/1Uz9sZJW1ts_YZc2-Ifd-UAQ8Pkgn23XLzmNdE_sDYrg/copy">here</a>. The link will prompt you to create a copy of the spreadsheet and accompanying Apps Script file.</p>
<h2 id="heading-what-is-an-image-lightbox">What is an Image Lightbox?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-35.png" alt="Image" width="600" height="400" loading="lazy">
<em>image thumbnails in a spreadsheet</em></p>
<p>An image lightbox is what we call it when we hover over or click on an image and it pops up into a bigger version on-screen.</p>
<p>It’s something we’re used to seeing on websites, and it gives things a nice, professional touch when done well.</p>
<p>What about in a spreadsheet, though? 🤔</p>
<p>Well, we’ve got two versions of a solution:</p>
<ol>
<li>Using built-in functions to display a larger version in a larger cell.</li>
<li>Using Apps Script to actually create a popup box on top of our spreadsheet.</li>
</ol>
<p>As a bonus to the first solution, we’ll also include optional Apps Script to make things a little smoother. More on that below 😉.</p>
<p>As usual, here's a walkthrough video where I go through the whole process:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/J39nMbuycEk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-image-popup-with-built-in-functions">Image Popup With Built-In Functions</h2>
<p>First, we need images in cells. From the top menu, <code>Insert - Image - Insert image in cell</code> will do the trick for this.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-37.png" alt="Image" width="600" height="400" loading="lazy">
<em>inserting image in spreadsheet cell</em></p>
<p>Next, we need to merge some cells together so that there’s a larger container that will hold our larger picture after the next step.</p>
<p>You can use one cell and change the width and height of it, but in my <a target="_blank" href="https://docs.google.com/spreadsheets/d/1Uz9sZJW1ts_YZc2-Ifd-UAQ8Pkgn23XLzmNdE_sDYrg/copy">example sheet</a>, the “lightbox” area is sharing rows with the rest of the data so I didn’t want to do that.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-38.png" alt="Image" width="600" height="400" loading="lazy">
<em>a large range of merged cells</em></p>
<p>In the column next to my image thumbnails, I’ve put checkboxes by selecting <code>Data - Data validation - Criteria: Checkboxes</code> from the top menu.</p>
<p>This will let us select which image to popup in our lightbox area.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-39.png" alt="Image" width="600" height="400" loading="lazy">
<em>data validation in google sheets</em></p>
<p>I’ve named the range <code>A2:A11</code> as <code>pics</code> and the range <code>B2:B11</code> as <code>checkboxes</code> to allow for easier readability in the function we’ll write next.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-41.png" alt="Image" width="600" height="400" loading="lazy">
<em>named ranges in google sheets</em></p>
<p>Now all that remains is one <code>XLOOKUP()</code> function to put inside our lightbox range.</p>
<p><code>=XLOOKUP(TRUE,checkboxes,pics,"")</code> is the function that searches for a check and then displays the corresponding image. By putting this in a big cell or range of merged cells, we can display whichever small image we select in the bigger area.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-40.png" alt="Image" width="600" height="400" loading="lazy">
<em>xlookup function in google sheets</em></p>
<p>Remember, all a checkbox is doing is storing either a <code>TRUE</code> (checked) or a <code>FALSE</code> (unchecked) value.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-42.png" alt="Image" width="600" height="400" loading="lazy">
<em>checkboxes and image thumbnails in google sheets</em></p>
<h2 id="heading-warning">⚠️WARNING⚠️</h2>
<p>This does have one issue, though. Do you know what it is?</p>
<p><code>XLOOKUP()</code> is going to return whichever checkboxes it comes to first with a TRUE value. So if you have multiple images checked, it’s only going to display the <em>first one it gets to</em>, not the most <em>recently clicked</em> one.</p>
<p>To get around this, let’s write some code.</p>
<h2 id="heading-apps-script-improvement">Apps Script Improvement</h2>
<p>Open up Apps Script by selecting <code>Extensions - Apps Script</code> from the top menu.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-46.png" alt="Image" width="600" height="400" loading="lazy">
<em>opening apps script in google sheets</em></p>
<p>Delete the built-in function in the code editor that opens. We'll start from scratch with an <code>onEdit</code> function:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onEdit</span>(<span class="hljs-params">e</span>) </span>{
</code></pre>
<p>We need to grab the range that we are currently editing.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> range = e.range
</code></pre>
<p>Then the checkboxes range.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> checkboxes = SpreadsheetApp.getActive().getRangeByName(<span class="hljs-string">"checkboxes"</span>)
</code></pre>
<p>Then we need to check whether what we just edited is in that checkbox range.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (range.getColumn() == <span class="hljs-number">2</span> &amp;&amp; range.getRow() &gt;= <span class="hljs-number">2</span> &amp;&amp; range.getRow() &lt;= <span class="hljs-number">10</span>)
</code></pre>
<p>If it was a checkbox, then we want to uncheck all the checkboxes and re-check the one we just checked.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Uncheck all other checkboxes in the range</span>
checkboxes.uncheck();
<span class="hljs-comment">// Check the edited cell</span>
range.check();
</code></pre>
<p>Now, there is a slight delay when you run the code. After clicking a checkbox, all of them are cleared right before the one you checked gets checked again.</p>
<p>Here’s what the full code looks like:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onEdit</span>(<span class="hljs-params">e</span>) </span>{
  <span class="hljs-keyword">var</span> range = e.range;
  <span class="hljs-keyword">var</span> checkboxes = SpreadsheetApp.getActive().getRangeByName(<span class="hljs-string">"checkboxes"</span>)

  <span class="hljs-comment">// Check if the edited cell is a checkbox in the desired range</span>
  <span class="hljs-keyword">if</span> (range.getColumn() == <span class="hljs-number">2</span> &amp;&amp; range.getRow() &gt;= <span class="hljs-number">2</span> &amp;&amp; range.getRow() &lt;= <span class="hljs-number">10</span>) {
    <span class="hljs-comment">// Uncheck all other checkboxes in the range</span>
    checkboxes.uncheck();
    <span class="hljs-comment">// Check the edited cell</span>
    range.check();
  }
}
</code></pre>
<h2 id="heading-a-real-popup-box-with-html">A Real Popup Box with HTML</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-43.png" alt="Image" width="600" height="400" loading="lazy">
<em>modal dialog box in google sheets</em></p>
<p>Okay, that’s all fine and dandy. What about the real thing, though?</p>
<p>This takes more Apps Script, but it’s doable thanks to the built-in <code>howModalDialog</code> method.</p>
<p>This is basically a popup window that can hold HTML. And since the internet is built with HTML, all we need to do is use a little bit to plug in an image.</p>
<p>This method requires an image live on the internet somewhere. So, we cannot reference the image that we've embedded in our sheet and use it in the HTML we're going to write.</p>
<p>Weird, I know.</p>
<p>Let’s find an image URL we can use. I’ve grabbed an eagle off of <a target="_blank" href="https://unsplash.com/">unsplash</a>.</p>
<p>We’ll hold this in a variable.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> imageURL = <span class="hljs-string">"https://images.unsplash.com/photo-1715002383611-63488b956401?q=80&amp;w=1887&amp;auto=format&amp;fit=crop&amp;ixlib=rb-4.0.3&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"</span>
</code></pre>
<p>Then we need to build our HTML. In our case, all we want is one element, so we won’t worry ourself with constructing a full, semantically correct page (although we certainly could 😉)</p>
<p>Another variable will hold this <code>img</code> element:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> html = <span class="hljs-string">'&lt;img src="'</span> + imageURL + <span class="hljs-string">'" style="max-width: 100%; max-height: 100%;"&gt;'</span>;
</code></pre>
<p>We have access to <a target="_blank" href="https://developers.google.com/apps-script/reference/base/ui">Class Ui</a> in Apps Script where we can “…add features like menus, dialogs, and sidebars.”</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">var</span> ui = SpreadsheetApp.getUi();
</code></pre>
<p>And finally, by calling the <code>showModalDialog()</code> method, we can generate HTML from our <code>html</code> variable using the <a target="_blank" href="https://developers.google.com/apps-script/reference/html/html-service">Class HtmlService</a>.</p>
<pre><code class="lang-javascript">ui.showModalDialog(HtmlService.createHtmlOutput(html).setWidth(<span class="hljs-number">700</span>).setHeight(<span class="hljs-number">1000</span>), <span class="hljs-string">'Eagle 🦅'</span>);
</code></pre>
<p>A final touch is to add a thumbnail version of our eagle image into our spreadsheet so that it is inserted on top of our cells (this next bit won’t work if it’s embedded in a cell itself).</p>
<p>Once it’s in our sheet, we can click the three black dots in the top right corner and assign a script directly to the image.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-44.png" alt="Image" width="600" height="400" loading="lazy">
<em>assigning a script to image in google sheets</em></p>
<p>We named our script <code>displayImagePopup</code>, so this is what we enter. Make sure to leave off the parentheses when typing it into the image's script form.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/image-45.png" alt="Image" width="600" height="400" loading="lazy">
<em>assigning script</em></p>
<p>Now, anytime we click the small image of the eagle, a popup box opens with the full image.</p>
<p>Here’s what the full code looks like:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">displayImagePopup</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// Get the active sheet</span>
  <span class="hljs-keyword">var</span> sheet = SpreadsheetApp.getActiveSheet();
  <span class="hljs-keyword">var</span> imageURL = <span class="hljs-string">"https://images.unsplash.com/photo-1715002383611-63488b956401?q=80&amp;w=1887&amp;auto=format&amp;fit=crop&amp;ixlib=rb-4.0.3&amp;ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"</span>
  <span class="hljs-comment">// Create an HTML string for the popup</span>
  <span class="hljs-keyword">var</span> html = <span class="hljs-string">'&lt;img src="'</span> + imageURL + <span class="hljs-string">'" style="max-width: 100%; max-height: 100%;"&gt;'</span>;

  <span class="hljs-comment">// Show the dialog</span>
  <span class="hljs-keyword">var</span> ui = SpreadsheetApp.getUi();
  ui.showModalDialog(HtmlService.createHtmlOutput(html).setWidth(<span class="hljs-number">700</span>).setHeight(<span class="hljs-number">1000</span>), <span class="hljs-string">'Eagle 🦅'</span>);
}
</code></pre>
<h2 id="heading-thank-you-so-much">Thank you so much!</h2>
<p>I hope this was helpful for you. </p>
<p>Check out my <a target="_blank" href="https://www.youtube.com/@eamonncottrell">YouTube channel</a> and <a target="_blank" href="https://www.gotsheet.xyz/subscribe">free newsletter</a> to get good at spreadsheets!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create a Search Form in Google Sheets – Google Apps Script Tutorial ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever struggled with navigating a massive spreadsheet? You can do away with the endless scrolling and unlock the power of targeted data retrieval with a custom search form using Google Apps Script.  In this hands-on guide, we'll craft a searc... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/create-search-form-in-google-sheets/</link>
                <guid isPermaLink="false">66ba5b06bab56b9458240037</guid>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nibesh Khadka ]]>
                </dc:creator>
                <pubDate>Fri, 09 Feb 2024 15:39:47 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/02/Search-Form-Thumbnail-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever struggled with navigating a massive spreadsheet? You can do away with the endless scrolling and unlock the power of targeted data retrieval with a custom search form using Google Apps Script. </p>
<p>In this hands-on guide, we'll craft a search tool that seamlessly integrates with your spreadsheet, allowing you to:</p>
<ul>
<li><strong>Search across multiple tabs:</strong> Effortlessly query data from different sections of your spreadsheet with dedicated input fields.</li>
<li><strong>Master AND &amp; OR searches:</strong> Find exactly what you need with both <strong>AND</strong> and <strong>OR</strong> search functionalities, ensuring precise or flexible matching based on your requirements.</li>
<li><strong>Boost your productivity:</strong> Save valuable time by eliminating manual searches and filtering through only the relevant data points.</li>
</ul>
<p>Ready to transform your spreadsheet into a dynamic search hub? Follow along as we explore the world of Google Apps Script and empower you to become a spreadsheet search master!</p>
<p>You can find all the code and related assets in <a target="_blank" href="https://github.com/nibukdk/Search-Form-In-Google-Sheets">this GitHub repo</a>.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/FkYW2oEbEQk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p></p><h2 id="toc">Table Of Contents</h2><p></p>
<ul>
  <li>
    <a href="#understanding-the-spreadsheet-structure">Understanding The Spreadsheet Structure</a>
  </li>
  <li>
    <a href="#how-to-build-the-search-form-dynamic-dropdowns-and-logic">How To Build The Search Form – Dynamic Dropdowns And Logic</a>
  </li>
  <li><a href="#and-vs-or-search">AND Vs OR Search</a></li>
  <li>
    <a href="#how-to-the-create-search-function-with-google-apps-script">How To The Create Search Function With Google Apps Script</a>
  </li>
  <li>
    <a href="#how-to-create-the-andsearch-function">How To Create The andsearch Function</a>
  </li>
  <li>
    <a href="#how-to-match-all-criteria-with-and-search">How To Match All Criteria with And Search</a>
  </li>
  <li>
    <a href="#bringing-search-results-to-life">Bringing Search Results To Life</a>
  </li>
  <li>
    <a href="#how-to-deduplicate-data-to-ensure-accuracy">How To Deduplicate Data To Ensure Accuracy</a>
  </li>
  <li>
    <a href="#putting-it-all-together-displaying-your-search-results">Putting It All Together – Displaying Your Search Results</a>
  </li>
  <li>
    <a href="#how-to-use-or-search-to-find-data-that-matches-any-term">How To Use Or Search To Find Data that Matches Any Term</a>
  </li>
  <li>
    <a href="#keeping-users-informed-toast-messages-for-seamless-search">Keeping Users Informed – Toast Messages for Seamless Search</a>
  </li>
  <li>
    <a href="#putting-it-all-together-testing-your-search-form">Putting It All Together – Testing Your Search Form</a>
  </li>
  <li>
    <a href="#congratulations-you-ve-built-a-powerful-search-engine-in-google-sheets-">Congratulations! You've Built a Powerful Search Engine in Google Sheets!
    </a>
  </li>
  <li>
    <a href="#exploring-customization-options">Exploring Customization Options</a>
  </li>
</ul>


<h2 id="heading-understanding-the-spreadsheet-structure">Understanding the Spreadsheet Structure</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/sheet_tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Structure Of The Spreadsheet</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/TABS.png" alt="Image" width="600" height="400" loading="lazy">
<em>Different Tabs In Spreadsheets</em></p>
<p>As portrayed in the image above, there are five tabs in the spreadsheet. The data is divided into three tabs by year: 2021, 2022 &amp; 2023.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/header_row.png" alt="Image" width="600" height="400" loading="lazy">
<em>Columns in 2021, 2022 &amp; 2023 in Spreadsheet</em></p>
<p>All the columns are the same in these three tabs.</p>
<p>Let's look at the structure of your dedicated search tab. It's divided into two key sections:</p>
<ol>
<li><strong>Search Form (Rows 1-7):</strong> This is where you interact with your data. Each input field corresponds to a specific column in your other spreadsheet tabs, allowing you to tailor your search queries. Think of them as filters, helping you hone in on the information you need.</li>
<li><strong>Search Results (Rows 8+):</strong> This is where you'll find the data you sought. Each result includes the relevant information you specified in your search, along with an additional column named "Sprd Name - Row Index." This acts as a convenient map, pinpointing the exact spreadsheet tab and row where each result originates from. No more hunting through endless rows – you'll be laser-focused on the data you need.</li>
</ol>
<p>By understanding this organized layout, you can navigate your search experience efficiently and retrieve the information you require swiftly.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/search_form_2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Search Form</em></p>
<p>### </p>
<h3 id="heading-how-to-build-the-search-form-dynamic-dropdowns-and-logic">How to Build the Search Form – Dynamic Dropdowns and Logic</h3>
<p>The search form has three input fields: Client, Quantity, and Description. Each utilizes a dropdown menu automatically populated with unique values from the Config spreadsheet tab. But how does this magic happen?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706803411374/7a8a5da4-b71a-4e35-b8d2-750cb611f23b.png" alt="Image" width="285" height="514" loading="lazy">
<em>Data Validation Of Dropdown Inputs</em></p>
<p>Here's what happens behind the scenes: </p>
<ol>
<li><strong>Data Source:</strong> Values for the dropdowns are meticulously collected from three separate tabs: 2021, 2022, and 2023.</li>
<li><strong>Combining Forces:</strong> A clever formula merges these values into a single, consolidated list.</li>
<li><strong>Splitting it Up:</strong> This combined list is then transformed into an array, allowing individual values to be accessed.</li>
<li><strong>Rearranging the Data:</strong> Transposition magic turns the row of values into a column, making them easier to work with.</li>
<li><strong>Duplicate Removal:</strong> The <code>UNIQUE</code> function eliminates any repeated values, ensuring a concise and organized list.</li>
<li><strong>Sorting it Out:</strong> Finally, the remaining values are sorted alphabetically for your browsing convenience.</li>
</ol>
<p>Here's the formula used: <code>SORT(UNIQUE(TRANSPOSE(split(TEXTJOIN(",",TRUE,'2021'!A2:A1001)&amp;","&amp;TEXTJOIN(",",TRUE,'2022'!A2:A1001)&amp;","&amp;TEXTJOIN(",",TRUE,'2023'!A2:A1001),","))))</code></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/02/config_tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Config Sheets</em></p>
<h3 id="heading-and-vs-or-search">AND vs OR Search</h3>
<p>A dedicated checkbox (located in G4:G5) serves as the control center for your search logic. When checked, it activates the <strong>AND</strong> search, requiring all specified criteria to be present in the results. </p>
<p>Leaving it unchecked switches to the <strong>OR</strong> search, providing more flexible results as long as any criterion matches.</p>
<p>Remember, the downloadable spreadsheet retains all the pre-configured formulas and data validation rules, making setup a breeze. We'll dive into crafting the magical search function in the next step!</p>
<h2 id="heading-how-to-the-create-search-function-with-google-apps-script">How to the Create Search Function With Google Apps Script</h2>
<p>Open script editor from <strong>Extensions&gt;Apps Script</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706803889387/4802b8d2-965f-4c14-8daa-efd0512a1c06.png" alt="Image" width="668" height="482" loading="lazy">
<em>Open Apps Script From Sheets</em></p>
<p>For this project, you'll create two files <strong>search.gs</strong> and <strong>utils.gs</strong> in the editor.</p>
<p>Inside the <strong>search.gs</strong> file, let's first fetch our spreadsheet and input terms.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> ss = SpreadsheetApp.getActiveSpreadsheet();
<span class="hljs-keyword">var</span> searchSheet = ss.getSheetByName(<span class="hljs-string">"search"</span>);
<span class="hljs-keyword">var</span> _2023Sheet = ss.getSheetByName(<span class="hljs-string">"2023"</span>);
<span class="hljs-keyword">var</span> _2022Sheet = ss.getSheetByName(<span class="hljs-string">"2022"</span>);
<span class="hljs-keyword">var</span> _2021Sheet = ss.getSheetByName(<span class="hljs-string">"2021"</span>);
<span class="hljs-comment">// ranges for name, description and quantity columns for each tab</span>
<span class="hljs-keyword">var</span> nameRangeNotation = <span class="hljs-string">'A2:A'</span>
<span class="hljs-keyword">var</span> descriptionRangeNotation = <span class="hljs-string">'F2:F'</span>
<span class="hljs-keyword">var</span> quantityRangeNotation = <span class="hljs-string">'E2:E'</span>
<span class="hljs-comment">// value for input boxes</span>
<span class="hljs-keyword">var</span> clientName = searchSheet.getRange(<span class="hljs-string">'B2:C2'</span>).getValue();
<span class="hljs-keyword">var</span> quantity = searchSheet.getRange(<span class="hljs-string">'E2'</span>).getValue();
<span class="hljs-keyword">var</span> description = searchSheet.getRange(<span class="hljs-string">'G2:H2'</span>).getValue();
<span class="hljs-keyword">var</span> hasIncludeAllSelected = searchSheet.getRange(<span class="hljs-string">'G4:G5'</span>).getValue();
</code></pre>
<p>Now below this code, we'll create the function <code>search</code>, which will orchestrate everything from the top.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * The main function assigned to search button in the spreadsheet. It orchestrates search opearaion.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">search</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">try</span> {

    <span class="hljs-keyword">let</span> status;

    <span class="hljs-keyword">if</span> (hasIncludeAllSelected) {
      <span class="hljs-comment">//perform AND search</span>
      <span class="hljs-keyword">const</span> newData = andSearch(clientName, description, quantity);

    }
    <span class="hljs-keyword">else</span> {
         }



  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-built_in">console</span>.log(e)

  }
}
</code></pre>
<p>In this project, we'll build our functions one step at a time. Let's start by determining the search type based on the checkbox in G4:G5.</p>
<p>If the box is checked, we'll activate the <strong>AND</strong> search functionality. This means all specified criteria in the input fields must be present in the results. To handle this, we'll call a dedicated function named <code>andSearch()</code>. </p>
<p>We'll create this function next, directly below the existing <code>search</code> function.</p>
<p>This approach ensures our script adapts to the user's chosen search type, providing accurate and relevant results based on their needs.</p>
<h3 id="heading-how-to-create-the-andsearch-function">How to Create the <code>andSearch()</code> Function</h3>
<pre><code class="lang-javascript">
<span class="hljs-comment">/**
 * Performs "AND" search for the given keywords in their respective columns Last Name, Descroption and Quantity for 
 * tabs 2021, 2022, 2023. Returns new nested arrays for search results to be filled in search spreadsheet.
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>name 
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>description 
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>quantity 
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Array&lt;Array&lt;String&gt;&gt;?}</span> </span>- [[],[],[]]
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">andSearch</span>(<span class="hljs-params">name = null, description = null, quantity = null</span>) </span>{

  <span class="hljs-comment">// get matching index for each sheet.</span>
  <span class="hljs-keyword">const</span> _2021SheetNameSearchIndexes = name === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2021Sheet, nameRangeNotation, name);
  <span class="hljs-keyword">const</span> _2021SheetQuantitySearchIndexes = quantity === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2021Sheet, quantityRangeNotation, quantity);
  <span class="hljs-keyword">const</span> _2021SheetDescriptionSearchIndexes = description === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2021Sheet, descriptionRangeNotation, description);


  <span class="hljs-keyword">const</span> _2022SheetNameSearchIndexes = name === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2022Sheet, nameRangeNotation, name);
  <span class="hljs-keyword">const</span> _2022SheetQuantitySearchIndexes = quantity === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2022Sheet, quantityRangeNotation, quantity);
  <span class="hljs-keyword">const</span> _2022SheetDescriptionSearchIndexes = description === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2022Sheet, descriptionRangeNotation, description);

  <span class="hljs-keyword">const</span> _2023SheetNameSearchIndexes = name === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2023Sheet, nameRangeNotation, name);
  <span class="hljs-keyword">const</span> _2023SheetQuantitySearchIndexes = quantity === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2023Sheet, quantityRangeNotation, quantity);
  <span class="hljs-keyword">const</span> _2023SheetDescriptionSearchIndexes = description === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2023Sheet, descriptionRangeNotation, description);


 <span class="hljs-comment">//.... continue</span>

}
</code></pre>
<p>This function takes three parameters, each corresponding to a user-defined search term: name, description and quantity.</p>
<p>If any of these search terms are blank, we simply assign an empty array as the result. This efficiently handles scenarios where users leave certain fields unfilled.</p>
<p>The core logic relies on the <code>searchSheetByColumn</code> function. Think of it as a data detective that checks specific columns within spreadsheet tabs. It takes three crucial arguments:</p>
<ul>
<li><strong>sheet</strong>: The specific tab to search within (for example: "2021").</li>
<li><strong>rangeNotation</strong>: The column range to target (for example: "A2:A").</li>
<li><strong>searchVal</strong>: The value to match within the chosen column (for example: "Khadka").</li>
</ul>
<p>Using this information, <code>searchSheetByColumn</code> scans the designated column and returns an array containing the row indexes where the <code>searchVal</code> is found in that sheet.</p>
<h4 id="heading-searching-input-value-in-a-column">Searching Input Value In a Column</h4>
<p>Let's create the function <code>searchSheetByColumn</code> in the <strong>utils.gs</strong> file now.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Searches the given keyword in the given column inside the given spreadsheet tab.
 * It returns all the matching indexes of data. Indexes are index from array not row.
 * <span class="hljs-doctag">@param <span class="hljs-type">{Spreadsheet}</span> <span class="hljs-variable">sheet</span></span> - sheet to search from
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> <span class="hljs-variable">rangeNotation</span></span> - range of the column in the given spreadsheet
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> <span class="hljs-variable">searchVal</span></span> - keyword to search
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Array&lt;number&gt;}</span> </span>- [1,23,12,45,12] 
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">searchSheetByColumn</span>(<span class="hljs-params">sheet, rangeNotation, searchVal</span>) </span>{
  <span class="hljs-keyword">const</span> data = sheet.getRange(rangeNotation).getValues().flat().filter(<span class="hljs-built_in">String</span>); <span class="hljs-comment">// get data</span>
  <span class="hljs-keyword">if</span> (data.length &lt; <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span> [];
  <span class="hljs-comment">// filter only matching rows indexes</span>
  <span class="hljs-comment">// got from https://stackoverflow.com/a/58980987/6163929</span>
  <span class="hljs-keyword">const</span> allIndexes = data.map(<span class="hljs-function">(<span class="hljs-params">val, index</span>) =&gt;</span> ({ val, index }))
    .filter(<span class="hljs-function">(<span class="hljs-params">{ val, index }</span>) =&gt;</span> rangeNotation === quantityRangeNotation ? <span class="hljs-built_in">Number</span>(val) === <span class="hljs-built_in">Number</span>(searchVal) : val.toLowerCase().includes(searchVal.toLowerCase())
    )
    .map(<span class="hljs-function">(<span class="hljs-params">{ val, index }</span>) =&gt;</span>
      index + <span class="hljs-number">1</span>
    ) <span class="hljs-comment">// +1 because we extract data from second row in notation later on have to match with whole data array</span>
  <span class="hljs-keyword">return</span> allIndexes;
}
</code></pre>
<p>The code above does the following:</p>
<ul>
<li>Retrieves data from the specified range and sheet using <code>sheet.getRange(rangeNotation).getValues().flat()</code>.</li>
<li>Removes empty values by filtering with <code>filter(String)</code>.</li>
<li>Iterates through data and indexes and applies <code>map</code> to create an array of objects with both values and their corresponding indexes.</li>
<li>Converts both search term and data values to numbers using <code>Number()</code>.</li>
<li>Filters for exact matches using <code>rangeNotation === quantityRangeNotation ? Number(val) === Number(searchVal)</code></li>
<li>Converts both search term and data values to lowercase.</li>
<li>Filters for matches using <code>val.toLowerCase().includes(searchVal.toLowerCase())</code></li>
<li>Extracts matching indexes using <code>map(({ val, index }) =&gt; index + 1)</code>.</li>
<li>Adds 1 to correct for starting extraction from the second row.</li>
</ul>
<h3 id="heading-how-to-match-all-criteria-with-and-search">How to Match All Criteria with AND Search</h3>
<p> Add the following piece of code in <code>andSearch</code> function.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">andSearch</span>(<span class="hljs-params">name = null, description = null, quantity = null</span>) </span>{

<span class="hljs-comment">// ..... continuing on from previous codes</span>
  <span class="hljs-comment">// matching indexes of rows in AND search</span>
  <span class="hljs-keyword">const</span> _2021SheetMatchingRowsIndexes = filterRowsIndexesWithAllSearchTerms(_2021SheetNameSearchIndexes, _2021SheetQuantitySearchIndexes, _2021SheetDescriptionSearchIndexes);
  <span class="hljs-keyword">const</span> _2022SheetMatchingRowsIndexes = filterRowsIndexesWithAllSearchTerms(_2022SheetNameSearchIndexes, _2022SheetQuantitySearchIndexes, _2022SheetDescriptionSearchIndexes);
  <span class="hljs-keyword">const</span> _2023SheetMatchingRowsIndexes = filterRowsIndexesWithAllSearchTerms(_2023SheetNameSearchIndexes, _2023SheetQuantitySearchIndexes, _2023SheetDescriptionSearchIndexes);
<span class="hljs-comment">//..........</span>
}
</code></pre>
<p>Remember the <strong>AND</strong> search? Its goal is to unearth data points that tick every box you've specified. To achieve this, we need to filter only rows that contain all your search terms – name, quantity, and description – across all three spreadsheets.</p>
<p>Enter the <code>filterRowsIndexesWithAllSearchTerms</code> function, to be created in the <strong>utils.gs</strong> file. This handy tool combs through each row and ensures it matches every criterion you've laid out. So, how does it work its magic? We'll explore the code next!</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Function filters only rows that consist all three keywords provided by user input
 * <span class="hljs-doctag">@param <span class="hljs-type">{Array&lt;String&gt;}</span> </span>arr1 
 * <span class="hljs-doctag">@param <span class="hljs-type">{Array&lt;String&gt;}</span> </span>arr2 
 * <span class="hljs-doctag">@param <span class="hljs-type">{Array&lt;String&gt;}</span> </span>arr3 
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Array&lt;String&gt;?}</span> </span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">filterRowsIndexesWithAllSearchTerms</span>(<span class="hljs-params">arr1, arr2, arr3</span>) </span>{
  <span class="hljs-comment">// create a nested array</span>
  <span class="hljs-keyword">const</span> arr = [arr1.length &gt; <span class="hljs-number">0</span> ? [...arr1] : <span class="hljs-string">""</span>, arr2.length &gt; <span class="hljs-number">0</span> ? [...arr2] : <span class="hljs-string">""</span>, arr3.length &gt; <span class="hljs-number">0</span> ? [...arr3] : <span class="hljs-string">""</span>].filter(<span class="hljs-built_in">String</span>);

  <span class="hljs-comment">// return empty if the master arrays length is lesser than number of search terms</span>
  <span class="hljs-keyword">if</span> (arr.length &lt; <span class="hljs-number">1</span> || arr.length &lt; numberOfInputFieldEntered) <span class="hljs-keyword">return</span> [];

  <span class="hljs-keyword">const</span> matchingIndexes = [];

  <span class="hljs-keyword">if</span> (arr.length === <span class="hljs-number">3</span>) {

    arr[<span class="hljs-number">0</span>].forEach(<span class="hljs-function">(<span class="hljs-params">val</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (arr[<span class="hljs-number">1</span>].includes(val) &amp;&amp; arr[<span class="hljs-number">2</span>].includes(val)) {
        matchingIndexes.push(val)
      }

    });

  }
  <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (arr.length === <span class="hljs-number">2</span>) {
    arr[<span class="hljs-number">0</span>].forEach(<span class="hljs-function">(<span class="hljs-params">val</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span> (arr[<span class="hljs-number">1</span>].includes(val)) {
        matchingIndexes.push(val)
      }

    });


  }
  <span class="hljs-keyword">else</span> {

    matchingIndexes.push(arr[<span class="hljs-number">0</span>]) <span class="hljs-comment">//just push the array thats not empty</span>
  }
  <span class="hljs-keyword">return</span> matchingIndexes.flat();

}
</code></pre>
<p>Here's what the code does:</p>
<p>The function takes three arrays as input, each representing matching indexes from one spreadsheet based on your search terms. However, we understand users might not fill in every search field.</p>
<p>To handle this, the function first creates a "master array" containing only non-empty arrays from the three inputs. Think of it as filtering out any blank search results. <code>const arr = [arr1.length &gt; 0 ? [...arr1] : "", arr2.length &gt; 0 ? [...arr2] : "", arr3.length &gt; 0 ? [...arr3] : ""].filter(String);</code></p>
<p>If the master array ends up being empty, it means no rows matched any of your search terms – the function simply returns an empty array, indicating no results found.</p>
<p>Similarly, if the master array has fewer elements than the total search terms you entered, it signifies an incomplete <strong>AND</strong> search. In this case, the function returns an empty array, letting you know that no results match all criteria. <code>arr.length &lt; numberOfInputFieldEntered</code></p>
<p>But when all three arrays have matches, the function begins its work, it iterates through the first array, meticulously checking if each index value exists in both the second and third arrays. If it does, that index is considered a match and added to a separate "matchingIndexes" array. This guarantees that only rows containing all your search terms are included: <code>arr[0].forEach((val) =&gt; { if (arr[1].includes(val) &amp;&amp; arr[2].includes(val)) { matchingIndexes.push(val)}</code></p>
<p>If only two arrays have matches, the function performs a simpler check, verifying if each value in the first array exists in the second. Any match is added to "matchingIndexes." <code>arr[0].forEach((val) =&gt; if (arr[1].includes(val)) { matchingIndexes.push(val)}</code>.</p>
<p>Else if only one array is present, the function simply uses that array directly.</p>
<p>In summary, the function ensures that only rows containing all your chosen search terms survive – a powerful tool for precise data retrieval!</p>
<p>Next, in your <strong>search.gs</strong> file right after you declared the variable <code>hasIncludeAllSelected</code> for the checkbox, create input value counter.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> numberOfInputFieldEntered = [clientName, description, quantity].filter(<span class="hljs-built_in">String</span>).length;
</code></pre>
<p>With this, we now have indexes for the rows from the <strong>AND</strong> search. Now, continue with your <code>andSearch</code> function and get data from those indexes.</p>
<h3 id="heading-bringing-search-results-to-life">Bringing Search Results to Life</h3>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">andSearch</span>(<span class="hljs-params">name = null, description = null, quantity = null</span>) </span>{
<span class="hljs-comment">//.... contnung from above</span>
  <span class="hljs-comment">// get data from row indexes</span>
  <span class="hljs-keyword">const</span> _2021SheetMatchingRows = fetchDataByRowIndexes(_2021Sheet, _2021SheetMatchingRowsIndexes)
  <span class="hljs-keyword">const</span> _2022SheetMatchingRows = fetchDataByRowIndexes(_2022Sheet, _2022SheetMatchingRowsIndexes)
  <span class="hljs-keyword">const</span> _2023SheetMatchingRows = fetchDataByRowIndexes(_2023Sheet, _2023SheetMatchingRowsIndexes)
}
</code></pre>
<p>Now that we have the matching row indexes, it's time to retrieve the actual data. Enter the <code>fetchDataByRowIndexes</code> function, residing in the <strong>utils</strong> file. This handy tool serves as your data retriever, fetching information based on the provided indexes.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Funciton extracts the rows of provided indexes+1, from the given spreadsheet tab.
 * <span class="hljs-doctag">@param <span class="hljs-type">{Spreadsheet}</span> <span class="hljs-variable">sheet</span></span> - sheet to search from
 * <span class="hljs-doctag">@param <span class="hljs-type">{Array&lt;number&gt;}</span> <span class="hljs-variable">indexes</span></span> - indexes of row to extract values.
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Array&lt;Array&lt;Srting&gt;&gt;}</span> </span>- Arrays of nested rows in the indexes from the given sheet.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchDataByRowIndexes</span>(<span class="hljs-params">sheet = _2021Sheet, indexes = []</span>) </span>{
  <span class="hljs-comment">// console.log("Inside fetchDataByRowIndexes() provided indexes are:" + indexes)</span>

  <span class="hljs-keyword">if</span> (indexes.length &lt; <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span> [];

  <span class="hljs-keyword">const</span> data = sheet.getDataRange().getValues();
  <span class="hljs-keyword">const</span> newData = [];

  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; indexes.length; i++) {
    newData.push([...data[indexes[i]], <span class="hljs-string">`<span class="hljs-subst">${sheet.getName()}</span> - <span class="hljs-subst">${indexes[i] + <span class="hljs-number">1</span>}</span>`</span>])
  }
  <span class="hljs-comment">// console.log("Inside fetchDataByRowIndexes() data from procvided indexes:" + newData)</span>
  <span class="hljs-keyword">return</span> newData;
}
</code></pre>
<p>The retrieved data isn't just dumped onto the search sheet – it gets a special touch. The function adds an extra value for the column named <code>Sprd Name - Row Indexes</code> with <code>${sheet.getName()} - ${indexes[i] + 1}</code> . </p>
<p>This column acts like a roadmap, displaying both the originating spreadsheet name and the corresponding row index for each result. So, at a glance, you know exactly where each piece of data came from.</p>
<p>Remember, this additional information is added as the last column in the search sheet. With this valuable context, search results become even more informative and easier to navigate.</p>
<h3 id="heading-how-to-deduplicate-data-to-ensure-accuracy">How to Deduplicate Data to Ensure Accuracy</h3>
<p>The next step is to ensure that our search results are free of duplicates, no matter from the sheet they originated. After all, who wants to see the same item twice? So, append this code in the <code>andSearch</code> function:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//.. continue inside andSearch Function</span>
 <span class="hljs-comment">// filter duplicate rows</span>
  <span class="hljs-keyword">const</span> _2021SheetMatchingUniqueRows = filterDuplicateRows(_2021SheetMatchingRows);
  <span class="hljs-keyword">const</span> _2022SheetMatchingUniqueRows = filterDuplicateRows(_2022SheetMatchingRows);
  <span class="hljs-keyword">const</span> _2023SheetMatchingUniqueRows = filterDuplicateRows(_2023SheetMatchingRows);
</code></pre>
<p>To create this function let's jump back to the file <strong>utils.gs</strong> again.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Takes Duplicate data that might have resulted from different individual column searches and only returns unique rows 
 * in each column from the serach results.
 * <span class="hljs-doctag">@param <span class="hljs-type">{Array&lt;String&gt;}</span> </span>arr 
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Array&lt;String&gt;}</span></span>- [[],[]]
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">filterDuplicateRows</span>(<span class="hljs-params">arr</span>) </span>{
  <span class="hljs-keyword">if</span> (arr.length &lt; <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span> [];
  <span class="hljs-keyword">const</span> delimiter = <span class="hljs-string">"*---*--*"</span>;
  <span class="hljs-comment">// console.log("Inside filterDuplicateRows() arr to check:" + arr)</span>

  <span class="hljs-keyword">const</span> strArr = arr.map(<span class="hljs-function"><span class="hljs-params">row</span> =&gt;</span> row.join(delimiter)).flat();
  <span class="hljs-comment">// console.log("Inside filterDuplicateRows() strArr:" + strArr)</span>

  <span class="hljs-keyword">const</span> uniqueArrays = [...new <span class="hljs-built_in">Set</span>(strArr)].map(<span class="hljs-function"><span class="hljs-params">str</span> =&gt;</span> str.split(delimiter))
  <span class="hljs-comment">// console.log("Inside filterDuplicateRows() uniqueArrays:" + uniqueArrays)</span>

  <span class="hljs-keyword">return</span> uniqueArrays;

}
</code></pre>
<p>Here's what we did:</p>
<ul>
<li><strong>Creating a Unique Fingerprint:</strong> We began by crafting a special "delimiter," a combination of characters highly unlikely to appear within your actual data. Think of it as a unique tag for each row.<code>const delimiter = "*---*--*";</code></li>
<li><strong>Joining Forces:</strong> Next, we mapped through each row, joining its elements with this delimiter to create a single string. This allows us to compare strings for uniqueness instead of individual data points.<code>const strArr = arr.map(row =&gt; row.join(delimiter)).flat();</code></li>
<li><strong>Duplicate Detective:</strong> We leveraged the power of JavaScript's <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set">Set</a> object, renowned for its ability to hold only unique values. By converting our string array into a Set, we effectively remove any identical entries: <code>[...new Set(strArr)]</code></li>
<li><strong>Back to Our Roots:</strong> Finally, we converted the unique strings back into an array of arrays, splitting them using the same delimiter we used earlier. This gives us a clean, deduplicated set of results. <code>map(str =&gt; str.split(delimiter))</code></li>
</ul>
<p><strong>Note:</strong> This uniqueness is based on the combined "<code>Sprd Name - Row Indexes</code>" value, ensuring true uniqueness across spreadsheets. Without this, duplicates can exist naturally.</p>
<p>With this final step, we've achieved accurate and streamlined search results, ready to be combined and presented from the <code>andSearch</code> function.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// inside andSearch function append at last</span>

  <span class="hljs-keyword">const</span> andSearchResult = [..._2023SheetMatchingUniqueRows, ..._2022SheetMatchingUniqueRows, ..._2021SheetMatchingUniqueRows]

  <span class="hljs-keyword">if</span> (andSearchResult.length &lt; <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span>;
  <span class="hljs-keyword">return</span> andSearchResult;
}
</code></pre>
<p>Find the completed <code>andSearch</code> function below.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Performs "AND" search for the given keywords in their respective columns Last Name, Descroption and Quantity for 
 * tabs 2021, 2022, 2023. Returns new nested arrays for search results to be filled in search spreadsheet.
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>name 
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>description 
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>quantity 
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Array&lt;Array&lt;String&gt;&gt;?}</span> </span>- [[],[],[]]
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">andSearch</span>(<span class="hljs-params">name = null, description = null, quantity = null</span>) </span>{

  <span class="hljs-comment">// get matching index for each sheet.</span>
  <span class="hljs-keyword">const</span> _2021SheetNameSearchIndexes = name === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2021Sheet, nameRangeNotation, name);
  <span class="hljs-keyword">const</span> _2021SheetQuantitySearchIndexes = quantity === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2021Sheet, quantityRangeNotation, quantity);
  <span class="hljs-keyword">const</span> _2021SheetDescriptionSearchIndexes = description === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2021Sheet, descriptionRangeNotation, description);


  <span class="hljs-keyword">const</span> _2022SheetNameSearchIndexes = name === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2022Sheet, nameRangeNotation, name);
  <span class="hljs-keyword">const</span> _2022SheetQuantitySearchIndexes = quantity === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2022Sheet, quantityRangeNotation, quantity);
  <span class="hljs-keyword">const</span> _2022SheetDescriptionSearchIndexes = description === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2022Sheet, descriptionRangeNotation, description);

  <span class="hljs-keyword">const</span> _2023SheetNameSearchIndexes = name === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2023Sheet, nameRangeNotation, name);
  <span class="hljs-keyword">const</span> _2023SheetQuantitySearchIndexes = quantity === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2023Sheet, quantityRangeNotation, quantity);
  <span class="hljs-keyword">const</span> _2023SheetDescriptionSearchIndexes = description === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2023Sheet, descriptionRangeNotation, description);


  <span class="hljs-comment">// matching indexes of rows in AND search</span>
  <span class="hljs-keyword">const</span> _2021SheetMatchingRowsIndexes = filterRowsIndexesWithAllSearchTerms(_2021SheetNameSearchIndexes, _2021SheetQuantitySearchIndexes, _2021SheetDescriptionSearchIndexes);
  <span class="hljs-keyword">const</span> _2022SheetMatchingRowsIndexes = filterRowsIndexesWithAllSearchTerms(_2022SheetNameSearchIndexes, _2022SheetQuantitySearchIndexes, _2022SheetDescriptionSearchIndexes);
  <span class="hljs-keyword">const</span> _2023SheetMatchingRowsIndexes = filterRowsIndexesWithAllSearchTerms(_2023SheetNameSearchIndexes, _2023SheetQuantitySearchIndexes, _2023SheetDescriptionSearchIndexes);

  <span class="hljs-comment">// get data from row indexes</span>
  <span class="hljs-keyword">const</span> _2021SheetMatchingRows = fetchDataByRowIndexes(_2021Sheet, _2021SheetMatchingRowsIndexes)
  <span class="hljs-keyword">const</span> _2022SheetMatchingRows = fetchDataByRowIndexes(_2022Sheet, _2022SheetMatchingRowsIndexes)
  <span class="hljs-keyword">const</span> _2023SheetMatchingRows = fetchDataByRowIndexes(_2023Sheet, _2023SheetMatchingRowsIndexes)

  <span class="hljs-comment">// filter duplicate rows</span>
  <span class="hljs-keyword">const</span> _2021SheetMatchingUniqueRows = filterDuplicateRows(_2021SheetMatchingRows);
  <span class="hljs-keyword">const</span> _2022SheetMatchingUniqueRows = filterDuplicateRows(_2022SheetMatchingRows);
  <span class="hljs-keyword">const</span> _2023SheetMatchingUniqueRows = filterDuplicateRows(_2023SheetMatchingRows);


  <span class="hljs-keyword">const</span> andSearchResult = [..._2023SheetMatchingUniqueRows, ..._2022SheetMatchingUniqueRows, ..._2021SheetMatchingUniqueRows]

  <span class="hljs-keyword">if</span> (andSearchResult.length &lt; <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span>;

  <span class="hljs-keyword">return</span> andSearchResult;

}
</code></pre>
<h3 id="heading-putting-it-all-together-displaying-your-search-results">Putting It All Together – Displaying Your Search Results</h3>
<p>Now that we can retrieve search results based on your "AND" criteria, it's time to integrate them into your <code>search</code> function. </p>
<p>We'll continue from where we left off. In the <code>if</code> block, add the following code.</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">if</span> (hasIncludeAllSelected) {
      <span class="hljs-comment">//perform AND search</span>
      <span class="hljs-keyword">const</span> newData = andSearch(clientName, description, quantity);
      <span class="hljs-comment">// ..........................</span>
      <span class="hljs-comment">// new peice of code </span>
       status = fillSearchWithResults(searchSheet.getDataRange().getValues(), newData)
       <span class="hljs-comment">// ................................................</span>
}
</code></pre>
<p>Let's create a new function, <code>fillSearchWithResults</code>, residing in the <strong>utils.gs</strong> file:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * To Fill search sheet with values
 * <span class="hljs-doctag">@param <span class="hljs-type">{Array&lt;Array&lt;Srting&gt;&gt;}</span>  <span class="hljs-variable">oldData</span></span> - previous search results data
 * <span class="hljs-doctag">@param <span class="hljs-type">{Array&lt;Array&lt;Srting&gt;&gt;}</span>  <span class="hljs-variable">newData</span></span> - new search result to fill
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fillSearchWithResults</span>(<span class="hljs-params">oldData, newData</span>) </span>{
  <span class="hljs-comment">// console.log("Inside fillSearchWithResults() old data:", oldData.length);</span>
  <span class="hljs-keyword">if</span> (oldData.length &gt;= <span class="hljs-number">8</span>) {
    searchSheet.getRange(<span class="hljs-number">8</span>, <span class="hljs-number">1</span>, oldData.length - <span class="hljs-number">7</span>, <span class="hljs-number">9</span>).clear(); <span class="hljs-comment">// clear until last filled data</span>
  }
  SpreadsheetApp.flush();
  Utilities.sleep(<span class="hljs-number">1000</span>);
  <span class="hljs-comment">// console.log("Inside fillSearchWithResults() new Data:", newData);</span>
  <span class="hljs-keyword">if</span> (newData.length &lt; <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">400</span>;
  searchSheet.getRange(<span class="hljs-number">8</span>, <span class="hljs-number">1</span>, newData.length, <span class="hljs-number">9</span>).setValues(newData);
  <span class="hljs-keyword">return</span> <span class="hljs-number">200</span>;
}
</code></pre>
<p>The function takes two key inputs:</p>
<ul>
<li><strong>Current Search Sheet Data:</strong> This represents the existing information displayed in your search sheet.</li>
<li><strong>New Search Results:</strong> This is the fresh data retrieved using the previously explained functions.</li>
</ul>
<p>Here's what happens step-by-step:</p>
<ol>
<li><strong>Clearing the Decks:</strong> If a previous search result exists (starting from row 8), the function clears it out to make space for the new findings.  <code>if (oldData.length &gt;= 8) { searchSheet.getRange(8, 1, oldData.length - 7, 9).clear(); }</code></li>
<li><strong>Empty Results? No Problem:</strong> If the newly retrieved search results are empty, the function returns a special code: 400. This code, which we'll use later, indicates to the user that no matching data was found. <code>if (newData.length &lt; 1) return 400</code></li>
<li><strong>Data Display Time!:</strong> If there are indeed results, the function saves them into the search sheet, starting from row 8. Additionally, it returns a different code: 200. This code signifies a successful operation, and we'll use it to show success messages to the user.</li>
</ol>
<p>With this final piece in place, your "AND" searches will effortlessly bring relevant data to your fingertips, presented neatly in your search sheet. </p>
<h3 id="heading-how-to-use-or-search-to-find-data-that-matches-any-term">How to Use OR Search to Find Data that Matches Any Term</h3>
<p>Our journey continues! After setting up the "AND" search, we can now conquer the "OR" search, allowing you to find data containing any of your specified terms.</p>
<p>In the <code>search</code> function's <code>else</code> block, we have the <code>orSearch</code> function. Its purpose is to sift through your data and identify rows containing at least one of your search terms. </p>
<p>Think of it as casting a wider net, capturing matches that meet any of your criteria.</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">//perform OR serach</span>
      <span class="hljs-keyword">let</span> newData = orSearch(clientName, description, quantity);

      status = fillSearchWithResults(searchSheet.getDataRange().getValues(), newData)
}
</code></pre>
<p>Create the function <code>orSearch</code> function below <code>andSearch</code> in the search file.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Performs "OR" search for the given keywords in their respective columns Last Name, Descroption and Quantity for 
 * tabs 2021, 2022, 2023. Returns new nested arrays for search results to be filled in search spreadsheet.
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>name 
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>description 
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>quantity 
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Array&lt;Array&lt;String&gt;&gt;?}</span> </span>- [[],[],[]]
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">orSearch</span>(<span class="hljs-params">name = null, description = null, quantity = null</span>) </span>{
  <span class="hljs-comment">// get matching index for each sheet.</span>
  <span class="hljs-keyword">const</span> _2021SheetNameSearchIndexes = name === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2021Sheet, nameRangeNotation, name);
  <span class="hljs-keyword">const</span> _2021SheetQuantitySearchIndexes = quantity === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2021Sheet, quantityRangeNotation, quantity);
  <span class="hljs-keyword">const</span> _2021SheetDescriptionSearchIndexes = description === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2021Sheet, descriptionRangeNotation, description);

  <span class="hljs-keyword">const</span> _2022SheetNameSearchIndexes = name === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2022Sheet, nameRangeNotation, name);
  <span class="hljs-keyword">const</span> _2022SheetQuantitySearchIndexes = quantity === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2022Sheet, quantityRangeNotation, quantity);
  <span class="hljs-keyword">const</span> _2022SheetDescriptionSearchIndexes = description === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2022Sheet, descriptionRangeNotation, description);

  <span class="hljs-keyword">const</span> _2023SheetNameSearchIndexes = name === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2023Sheet, nameRangeNotation, name);
  <span class="hljs-keyword">const</span> _2023SheetQuantitySearchIndexes = quantity === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2023Sheet, quantityRangeNotation, quantity);
  <span class="hljs-keyword">const</span> _2023SheetDescriptionSearchIndexes = description === <span class="hljs-string">""</span> ? [] : searchSheetByColumn(_2023Sheet, descriptionRangeNotation, description);

  <span class="hljs-comment">// get values from those indexes</span>
  <span class="hljs-keyword">const</span> _2021SheetNameSearch = fetchDataByRowIndexes(_2021Sheet, _2021SheetNameSearchIndexes);
  <span class="hljs-keyword">const</span> _2021SheetQuantitySearch = fetchDataByRowIndexes(_2021Sheet, _2021SheetQuantitySearchIndexes);
  <span class="hljs-keyword">const</span> _2021SheetDescriptionSearch = fetchDataByRowIndexes(_2021Sheet, _2021SheetDescriptionSearchIndexes);

  <span class="hljs-keyword">const</span> _2022SheetNameSearch = fetchDataByRowIndexes(_2022Sheet, _2022SheetNameSearchIndexes);
  <span class="hljs-keyword">const</span> _2022SheetQuantitySearch = fetchDataByRowIndexes(_2022Sheet, _2022SheetQuantitySearchIndexes);
  <span class="hljs-keyword">const</span> _2022SheetDescriptionSearch = fetchDataByRowIndexes(_2022Sheet, _2022SheetDescriptionSearchIndexes);

  <span class="hljs-keyword">const</span> _2023SheetNameSearch = fetchDataByRowIndexes(_2023Sheet, _2023SheetNameSearchIndexes);
  <span class="hljs-keyword">const</span> _2023SheetQuantitySearch = fetchDataByRowIndexes(_2023Sheet, _2023SheetQuantitySearchIndexes);
  <span class="hljs-keyword">const</span> _2023SheetDescriptionSearch = fetchDataByRowIndexes(_2023Sheet, _2023SheetDescriptionSearchIndexes);



  <span class="hljs-comment">// filter duplicate rows</span>
  <span class="hljs-keyword">const</span> _2021SheetMatchingUniqueRows = filterDuplicateRows([..._2021SheetNameSearch, ..._2021SheetQuantitySearch, ..._2021SheetDescriptionSearch]);
  <span class="hljs-keyword">const</span> _2022SheetMatchingUniqueRows = filterDuplicateRows([..._2022SheetNameSearch, ..._2022SheetQuantitySearch, ..._2022SheetDescriptionSearch]);
  <span class="hljs-keyword">const</span> _2023SheetMatchingUniqueRows = filterDuplicateRows([..._2023SheetNameSearch, ..._2023SheetQuantitySearch, ..._2023SheetDescriptionSearch]);

  <span class="hljs-keyword">const</span> orSearchResult = [..._2021SheetMatchingUniqueRows, ..._2022SheetMatchingUniqueRows, ..._2023SheetMatchingUniqueRows]

  <span class="hljs-keyword">if</span> (orSearchResult.length &lt; <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span>;

  <span class="hljs-keyword">return</span> orSearchResult;

}
</code></pre>
<p>Now, don't be surprised if some things look familiar! The overall structure of the <code>orSearch</code> function resembles its "AND" counterpart. However, a key difference sets them apart:</p>
<p>Since an "OR" search requires just one matching term, we can get rid of the <code>filterRowsIndexesWithAllSearchTerms</code> function. Recall that function ensured all terms were present, which isn't the case here.</p>
<p>In essence, the <code>orSearch</code> function works by iterating through each search term and its corresponding matching indexes. For each term, it retrieves the data from the spreadsheet using the familiar <code>fetchDataByRowIndexes</code> function. </p>
<p>Finally, it merges the retrieved data for all terms, creating a unified set of results, even if they come from different spreadsheets.</p>
<p>With this powerful tool in your arsenal, you can discover data points that might not have surfaced with an "AND" search, expanding your search capabilities and enriching your data exploration experience.</p>
<h3 id="heading-keeping-users-informed-toast-messages-for-seamless-search">Keeping Users Informed – Toast Messages for Seamless Search</h3>
<p>Now that our search functions are complete, let's add a crucial element: user feedback! After all, keeping users informed throughout the search process leads to a smoother experience.</p>
<p>To avoid confusion, replace the search function with this one:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * The main function assigned to search button in the spreadsheet. It orchestrates search opearaion.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">search</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">try</span> {
    SpreadsheetApp.getActiveSpreadsheet().toast(<span class="hljs-string">"Searching Through Your Database..."</span>, <span class="hljs-string">'Searching'</span>);

    <span class="hljs-keyword">let</span> status;

    <span class="hljs-keyword">if</span> (hasIncludeAllSelected) {
      <span class="hljs-comment">//perform AND search</span>
      <span class="hljs-keyword">const</span> newData = andSearch(clientName, description, quantity);



      status = fillSearchWithResults(searchSheet.getDataRange().getValues(), newData)
      <span class="hljs-comment">// console.log(status);</span>
      <span class="hljs-keyword">if</span> (status === <span class="hljs-number">400</span>) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(SEARCH_STATUS.SEARCH_FAILURE); }
    }
    <span class="hljs-keyword">else</span> {
      <span class="hljs-comment">//perform OR serach</span>
      <span class="hljs-keyword">let</span> newData = orSearch(clientName, description, quantity);

      status = fillSearchWithResults(searchSheet.getDataRange().getValues(), newData)
      <span class="hljs-comment">// console.log(status);</span>

      <span class="hljs-keyword">if</span> (status === <span class="hljs-number">400</span>) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(SEARCH_STATUS.SEARCH_FAILURE); }
    }

    <span class="hljs-keyword">if</span> (status === <span class="hljs-number">200</span>) {
      SpreadsheetApp.getActiveSpreadsheet().toast(SEARCH_STATUS.SEARCH_SUCCESFULL, <span class="hljs-string">'Success'</span>);
    }

  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-comment">// console.log(e)</span>
    <span class="hljs-keyword">if</span> (e.Error === SEARCH_STATUS.SEARCH_FAILURE) {
      SpreadsheetApp.getActiveSpreadsheet().toast(SEARCH_STATUS.SEARCH_FAILURE, <span class="hljs-string">'Not Found!'</span>);

    } <span class="hljs-keyword">else</span> {
      SpreadsheetApp.getActiveSpreadsheet().toast(e, <span class="hljs-string">'Error!'</span>);

    }

  }
}
</code></pre>
<p>We'll leverage the <a target="_blank" href="https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#toastmsg,-title">toast</a> method provided by SpreadsheetApp to display brief, non-intrusive messages directly within the spreadsheet interface. Here's what we'll achieve:</p>
<p><strong>Search Initiated:</strong> As soon as the user clicks the search button, a toast message appears: "Searching Through Your Database..." This lets them know the search is underway, preventing confusion or unnecessary waiting. <code>SpreadsheetApp.getActiveSpreadsheet().toast("Searching Through Your Database...", 'Searching');</code></p>
<p><strong>Success Stories:</strong> If the search returns any result (indicated by a status code of 200), a positive toast message pops up: "Search Was Successful!" This confirms the operation's completion and reassures the user that relevant data was found.  <code>if (status === 200) { SpreadsheetApp.getActiveSpreadsheet().toast(SEARCH_STATUS.SEARCH_SUCCESFULL, 'Success'); }</code></p>
<p><strong>Empty Findings:</strong> While not technically an error, an empty search result (status code of 400) triggers a slightly different message: "No items found with the given criteria." This informs the user about the outcome without causing alarm. <code>if (status === 400) { throw new Error(SEARCH_STATUS.SEARCH_FAILURE); }</code></p>
<p>Here's what happens behind the scenes:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> SEARCH_STATUS = {
  <span class="hljs-attr">SEARCH_SUCCESFULL</span>: <span class="hljs-string">"Search Was Successfull!"</span>,
  <span class="hljs-attr">SEARCH_FAILURE</span>: <span class="hljs-string">"No items found with the given criteria."</span>,
}
</code></pre>
<ul>
<li>An "enum" called <code>SEARCH_STATUS</code> in the <strong>utils.gs</strong> file stores these message strings for easy access and maintenance.</li>
<li>A "catch block" handles any unexpected errors, ensuring the user receives appropriate feedback even in unusual situations.</li>
</ul>
<p>With these toast messages in place, your search functionality becomes more user-friendly and transparent. Remember, clear communication leads to happy user experience!</p>
<h3 id="heading-putting-it-all-together-testing-your-search-form">Putting It All Together – Testing Your Search Form</h3>
<p>Now that you've built your powerful search functions, it's time to see them in action! Follow these steps to test your search form directly within your spreadsheet:</p>
<ol>
<li><strong>Save Your Scripts:</strong> Make sure you've saved all your code files (<strong>utils.gs</strong> and <strong>search.gs</strong>) before proceeding.</li>
<li><strong>Assign the Search Function:</strong> Right-click on the Search button in your form and select "Assign script." In the popup window, type the name of the <code>search</code> function and click "OK." This links the button to your code.</li>
<li><strong>Ready, Set, Search:</strong> In your spreadsheet, experiment with different search combinations. Try entering terms in various combinations to see how the AND and OR searches yield different results.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707047218515/d6e37765-ad85-4b08-ac8f-b62f80148a1e.png" alt="Image" width="1748" height="1240" loading="lazy">
<em>Assign a function to button in Google Sheets</em></p>
<h3 id="heading-congratulations-youve-built-a-powerful-search-engine-in-google-sheets">Congratulations! You've Built a Powerful Search Engine in Google Sheets!</h3>
<p>You've successfully accomplished an impressive feat: crafting a customized search engine within your Google Sheets. Let's recap your achievements:</p>
<ul>
<li><strong>Seamless Search Form:</strong> You created a user-friendly search form directly in your spreadsheet, simplifying data exploration.</li>
<li><strong>Scriptable Power:</strong> You harnessed the power of Apps Script to develop functions that handle various search operations behind the scenes.</li>
<li><strong>AND &amp; OR Mastery:</strong> You implemented both AND and OR search functionalities, giving users flexibility in finding relevant data.</li>
<li><strong>Precise Matching:</strong> You designed a function that selects rows containing all specified search terms, ensuring accurate results.</li>
<li><strong>Duplicate Removal:</strong> You implemented a mechanism to eliminate duplicate entries, keeping your search results clean and concise.</li>
<li><strong>Informative Feedback:</strong> You integrated user-friendly toast messages to notify users about search progress and outcomes.</li>
</ul>
<h3 id="heading-exploring-customization-options">Exploring Customization Options</h3>
<p>You've built a fantastic search engine, but remember, the journey doesn't end here! With a bit of tweaking, you can adapt this tool to perfectly suit your specific needs and workflows. Here are some exciting possibilities to consider:</p>
<p><strong>Diversifying Your Data:</strong> Break free from the confines of a single spreadsheet! Explore integrating with diverse data sources like inventory management systems, tax databases, or even restaurant reviews. With some adjustments to your code, you can unlock a wealth of information across different platforms.</p>
<p><strong>Dynamic Search Inputs:</strong> Need more flexibility in your search criteria? Consider adding or removing input fields based on your evolving needs. This allows for more tailored searches and streamlines your data exploration process.</p>
<p><strong>Detailed Search Logs:</strong> Keep track of your search history! Implement a log box to automatically record your latest search terms and the number of results found. This can be invaluable for revisiting past searches and analyzing trends.</p>
<p><strong>Visual Appeal Matters:</strong> Enhance the user experience by giving your search form a visual makeover. Play with colors, fonts, and layout to create a more engaging and intuitive interface.</p>
<p><strong>Speed Optimizations:</strong> Every second counts! Explore ways to optimize your search functions for faster response times. This might involve code refinement, data indexing, or leveraging caching strategies.</p>
<p><strong>Taming Large Datasets:</strong> Working with massive databases? Don't worry, you've got options! Implement logic to overcome the 6-minute runtime limit of Google Apps Script functions. </p>
<p>By exploring these avenues, you can transform your basic search function into a powerful and personalized data exploration tool. Remember, the possibilities are endless!</p>
<p>PS: How much more productive (or procrastinating) will you be with this new ability?</p>
<p>I am Nibesh Khadka. Share this blog &amp; like the video if helpful! Find more of my contents at <a target="_blank" href="https://medium.com/script-portal">Script-Portal</a> (Medium) &amp; on my YouTube channel: <a target="_blank" href="https://youtube.com/@codingWithNibesh">CodingWithNibesh</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Code Dark Mode for Google Sheets with Apps Script and JavaScript ]]>
                </title>
                <description>
                    <![CDATA[ Google Sheets is a great tool for working and collaborating on spreadsheets, but it doesn't have native support for dark mode.  In this article, we'll create our own dark mode. One way to do that would be by selecting all the cells and manually chang... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-code-dark-mode-for-google-sheets/</link>
                <guid isPermaLink="false">66b8de03753e1e313e83948f</guid>
                
                    <category>
                        <![CDATA[ dark mode ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Wed, 20 Sep 2023 22:23:15 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/09/code-your-own-dark-mode.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Google Sheets is a great tool for working and collaborating on spreadsheets, but it doesn't have native support for dark mode. </p>
<p>In this article, we'll create our own dark mode. One way to do that would be by selecting all the cells and manually changing their background color and font color. This will get the job done, but we can automate the process and add more style options to choose from.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/automate-2.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of cartoon character grabbing a computer</em></p>
<p>Here’s what we’ll build: a style selector that has four different styles triggered by either a new dropdown menu or by clicking a custom button icon.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/1-12.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of Google Sheets with different background modes</em></p>
<p>And here's the video walkthrough if you'd like to follow along with that instead: </p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/TxSGuXPav70" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-how-to-create-dark-mode-in-apps-script">How to Create Dark Mode in Apps Script</h2>
<p>We’ll create four functions, one for each style mode. Each of our functions will do the following:</p>
<ol>
<li>Set background color</li>
<li>Set font color</li>
<li>Set font family</li>
<li>Set border color and style</li>
</ol>
<p>Let’s walk through how to create a <code>darkMode</code> function in <a target="_blank" href="https://script.google.com/home/start">Apps Script</a>. We'll define each function with the <code>function</code> keyword, followed by whatever we’ll name it.</p>
<p>Because the functions take no arguments (they just run without needing more info from us). That is, we have open and closed parentheses with nothing inside them followed by an open curly brace.</p>
<p>All of our code for the function goes between the <code>darkMode</code> function's curly braces:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">darkMode</span>(<span class="hljs-params"></span>) </span>{
  SpreadsheetApp.getActive().getRange(<span class="hljs-string">'A1:Z'</span>)
    .setBackground(<span class="hljs-string">"#333333"</span>)
    .setFontColor(<span class="hljs-string">"white"</span>)
    .setFontFamily(<span class="hljs-string">"Google Sans"</span>)
    .setBorder(<span class="hljs-literal">false</span>,<span class="hljs-literal">false</span>,<span class="hljs-literal">false</span>,<span class="hljs-literal">false</span>,<span class="hljs-literal">true</span>,
     <span class="hljs-literal">true</span>,<span class="hljs-string">"#444444"</span>,SpreadsheetApp.BorderStyle.SOLID)
}
</code></pre>
<p>To select all the cells, we used the built-in methods from the <code>SpreadsheetApp</code> class: <code>getActive()</code> and <code>getRange()</code>. These select the active sheet as well as a given range.</p>
<p>In our case, we’ll plug in <code>A1:Z</code> as the range, but you can extend this further if you’d like. For instance,  <code>A1:AZ</code>, would add columns <code>AA:AZ</code> and then apply our styling to them.</p>
<p>The four lines that follow are simply dot notation extensions telling what styles to apply. You can write this on one line if you’d like, but it’s good practice to have line breaks to make the code easy to read.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/clever.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of man saying, that’s clever</em></p>
<h2 id="heading-how-to-set-colors-and-fonts">How to Set Colors and Fonts</h2>
<p>You'd notice that we used both <code>setBackground(#333333)</code> and <code>setFontColor("white")</code> in the code. This is because we can use CSS notation colors in either hex format or by using the color’s name.</p>
<p>Using <code>setFontFamily("Google Sans")</code>, we gave it the font family name within quotations. Being a Google product, you can use any of the <a target="_blank" href="https://fonts.google.com/">Google Fonts</a> as well as Google’s own Google Sans font as I found out making this project.</p>
<h2 id="heading-how-to-set-the-border">How to set the Border</h2>
<p>The <code>setBorder(false,false,false,false,true, true,"#444444",SpreadsheetApp.BorderStyle.SOLID)</code> function let’s you enter <code>true</code> or <code>false</code> values for the top, left, bottom, right, vertical, or horizontal borders, in that order, followed by the color and style.</p>
<p>To set the style, we had to invoke a built in <a target="_blank" href="https://developers.google.com/apps-script/reference/document/attribute">Enum Attribute</a> — <code>BorderStyle</code> — to change the style of the border.</p>
<h2 id="heading-how-to-create-the-style-menu">How to Create the Style Menu</h2>
<p>To be able to select any of the styles we’re making from the actual spreadsheet, we need a menu.</p>
<p>To add the menu, we'll create a new function called  <code>onOpen()</code> that runs as soon as the spreadsheet opens and then the built-in methods from the <code>getUi()</code> to build our custom menu.</p>
<p>We can create the menu with <code>.createMenu()</code> and then add each of our functions to the menu with the <code>addItem()</code> function.</p>
<p>Here's the code:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onOpen</span>(<span class="hljs-params"></span>)</span>{
  SpreadsheetApp.getUi()
    .createMenu(<span class="hljs-string">'Style'</span>)
    .addItem(<span class="hljs-string">"Dark"</span>,<span class="hljs-string">"darkMode"</span>)
    .addItem(<span class="hljs-string">"Papyrus"</span>,<span class="hljs-string">"papyrusMode"</span>)
    .addItem(<span class="hljs-string">"Light"</span>,<span class="hljs-string">"lightMode"</span>)
    .addItem(<span class="hljs-string">"Synth"</span>,<span class="hljs-string">"synthMode"</span>)
    .addToUi();
}
</code></pre>
<p>Google Apps Script automatically integrates with Google Workspace apps (like Google Sheets) so the functions we've added in the code will make the functionalities accessible in your Google Sheets.</p>
<h2 id="heading-how-to-add-icon-buttons">How to Add Icon Buttons</h2>
<p>As a bonus, I added four icon images to the sheet, and by selecting <code>Assign script</code> we can have these icons act as buttons to trigger any of the four styles we’ve built:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/2.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of icons in spreadsheet</em></p>
<p>When you type the function name into the Assign script dialog box, make sure you do not to use parentheses. You should only enter the name of the function itself:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/3.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of assigning script to image</em></p>
<p>And bingo! We’ve got ourselves a style picker. You can add or edit these to create any number of combinations that you can easily toggle on and off in your own Google Sheet!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/4.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of Synth Mode Style</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This article shows how you can create can code dark, and other background modes for your Google Sheets using Apps Script and JavaScript.</p>
<p>I hope this has been useful for you!</p>
<h2 id="heading-more-resources">More Resources😄</h2>
<p>▶ Find all my video tutorials and walkthroughs on YouTube: <a target="_blank" href="https://www.youtube.com/@eamonncottrell?sub_confirmation=1">https://www.youtube.com/@eamonncottrell?sub_confirmation=1</a></p>
<p>▶ Connect with me on LinkedIn where I share daily tips: <a target="_blank" href="https://www.linkedin.com/in/eamonncottrell/">https://www.linkedin.com/in/eamonncottrell/</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Remove Empty Rows and Columns in Google Sheets ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, you will learn how to remove empty rows and columns from Google Sheets using Google Apps Script. A while ago I wrote an article on how to remove empty rows and columns from Google Sheets.  I recently revisited that article and I now... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-remove-empty-rows-and-columns-in-google-sheets/</link>
                <guid isPermaLink="false">66ba5b0acccc49d721b6ea38</guid>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nibesh Khadka ]]>
                </dc:creator>
                <pubDate>Thu, 07 Sep 2023 07:10:42 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/08/delete-empty-rows-and-columns-in-google-sheets.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, you will learn how to remove empty rows and columns from Google Sheets using Google Apps Script.</p>
<p>A while ago I wrote an article on <a target="_blank" href="https://kcl.hashnode.dev/how-to-delete-empty-rows-and-columns-in-google-sheets">how to remove empty rows and columns from Google Sheets</a>. </p>
<p>I recently revisited that article and I now present you this revised version. </p>
<p>I also have a video version of this topic which you can check out below:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/Eiqa5ST9DYM" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-what-we-will-cover">What We Will Cover</h2>
<p>You'll create two functions: <code>deleteExternalEmptyRowsNColumns()</code> and <code>deleteInternalEmptyRowsNColumns()</code>. </p>
<p>The first function will delete empty rows and columns from the range that are outside of the range returned by the method <a target="_blank" href="https://developers.google.com/apps-script/reference/spreadsheet/sheet#getdatarange"><code>getDataRange()</code></a>.</p>
<p>The second function will delete the rows and columns that are empty which are inside of the range returned by <code>getDataRange()</code>. </p>
<p>We will also create a menu so that we can execute these functions from the spreadsheet itself.</p>
<h2 id="heading-how-to-prep-the-sheet">How to Prep the Sheet</h2>
<p>My spreadsheet currently looks like the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693110060718/c5e5f9e6-2ddf-4ee1-a08c-3a0a8ba4de87.png" alt="Sheets Sample" width="2367" height="1795" loading="lazy">
<em>Spreadsheet With Lots Of Empty Rows and Columns</em></p>
<p>It has a couple of columns and rows with data, with lots of blank rows and columns. </p>
<p>Let's make the spreadsheets look more presentable like the following image:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693110269477/fd55cbfd-88f8-47b2-b0d1-69d1ced421b0.png" alt="Image" width="1920" height="1080" loading="lazy">
<em>Final Clean Spreadsheet Version</em></p>
<h2 id="heading-how-to-open-apps-script-project">How to Open Apps Script Project</h2>
<p>Next, let's open our Apps Script project from the Extensions tab in the spreadsheet:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/open_apps_script-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Open Apps Script Project From Spreadsheet's Tab</em></p>
<h2 id="heading-how-to-create-a-function-to-delete-empty-rows-and-columns-outside-of-datarange">How to Create a Function to Delete Empty Rows and Columns Outside of DataRange</h2>
<p>We'll create a function named <code>deleteExternalEmptyRowsNColumns()</code>. </p>
<p>This function will be responsible for deleting any empty rows and columns that are outside of the range of <code>getDataRange()</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Delete the empty rows and columns outside of the DataRange()
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deleteExternalEmptyRowsNColumns</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// get sheets and data</span>
  <span class="hljs-keyword">const</span> ss = SpreadsheetApp.getActiveSheet();
  <span class="hljs-keyword">const</span> data = ss.getDataRange().getValues();

  <span class="hljs-comment">//console.log(data);</span>

  <span class="hljs-comment">// determine last row and column</span>
  <span class="hljs-keyword">const</span> lastRow = data.length;
  <span class="hljs-keyword">const</span> lastCol = data[<span class="hljs-number">0</span>].length;

  <span class="hljs-comment">// get maximum rows and columns sss</span>
  <span class="hljs-keyword">const</span> maxRows = ss.getMaxRows();
  <span class="hljs-keyword">const</span> maxCols = ss.getMaxColumns();

  <span class="hljs-comment">// only remove rows and columns if there are empty rows or columns beyond last row and columns</span>
  <span class="hljs-keyword">if</span> (maxRows &gt; lastRow) {
    ss.deleteRows(lastRow + <span class="hljs-number">1</span>, maxRows - lastRow);
  }
  <span class="hljs-keyword">if</span> (maxCols &gt; lastCol) {
    ss.deleteColumns(lastCol + <span class="hljs-number">1</span>, maxCols - lastCol);
  }

}
</code></pre>
<p>We're using maximum rows and maximum columns because these values will return the last row and the last column of the spreadsheet regardless of the content.</p>
<p> This means they also include empty rows and empty columns beyond the data range. </p>
<p>Next, we only remove the columns and rows if they are outside of the range.</p>
<p>This means that if the maximum row is greater than the last row, then we will remove the rows. The same goes for the columns.</p>
<p>We're using the <a target="_blank" href="https://developers.google.com/apps-script/reference/spreadsheet/sheet#deleterowsrowposition,-howmany"><code>deleteRows()</code></a> method to remove those rows, which takes two parameters.</p>
<p>The first one is the index of the row from where the rows should be deleted, <code>lastRow + 1</code> in our case.</p>
<p> The second parameter is the number of rows we should remove, which is <code>maxRows - lastRow</code> in our case. </p>
<p>For columns, we'll use the <a target="_blank" href="https://developers.google.com/apps-script/reference/spreadsheet/sheet#deletecolumnscolumnposition,-howmany"><code>deleteColumns()</code></a> method. The way this method works is the same as the way <code>deleteRows()</code> works but on columns.</p>
<p>If you run this function your spreadsheet will look similar to the following image: </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693112111932/eb0387b4-9b5a-49b3-9e9a-6b572d75be1e.png" alt="Image" width="1920" height="1080" loading="lazy">
<em>Spreadsheet with Out Of Bounds Columns and Rows Removed</em></p>
<p>You'll see the columns and rows that were outside of the range returned by <code>getDataRange()</code> have now been removed by the function.</p>
<h2 id="heading-how-to-create-a-function-to-delete-empty-rows-and-columns-inside-of-datarange">How to Create a Function to Delete Empty Rows and Columns Inside of DataRange</h2>
<p>Now we will create another function: <code>deleteInternalEmptyRowsNColumns()</code>.</p>
<p>This will be responsible for removing empty rows and columns which are included in the <code>getDataRange(</code>) method, with this function below:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Deletes the empty rows and columns inside of DataRange()
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deleteInternalEmptyRowsNColumns</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// get sheets and data</span>
  <span class="hljs-keyword">const</span> ss = SpreadsheetApp.getActiveSheet();
  <span class="hljs-keyword">const</span> data = ss.getDataRange().getValues();

  <span class="hljs-keyword">const</span> lastRow = data.length;
  <span class="hljs-keyword">const</span> lastCol = data[<span class="hljs-number">0</span>].length;

  <span class="hljs-comment">// lets check if there're any empty columns during the beginning which is included in data</span>
  <span class="hljs-keyword">const</span> emptyColumnIndexes = [];
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i &lt;= lastCol; i++) {
    <span class="hljs-keyword">if</span> (ss.getRange(<span class="hljs-number">1</span>, i, lastRow, <span class="hljs-number">1</span>).getValues().flat().join(<span class="hljs-string">""</span>) === <span class="hljs-string">""</span>) {
      <span class="hljs-comment">// subtract length before pushes value with less than 1 of original index</span>
      <span class="hljs-comment">// because later on when we delete colums one by one the indexes </span>
      <span class="hljs-comment">//will be out of bounds/wrong due to sprd being updated to new indexes</span>
      emptyColumnIndexes.push(i - emptyColumnIndexes.length);

    }

  }

  <span class="hljs-comment">// lets delete these columns</span>
  <span class="hljs-keyword">if</span> (emptyColumnIndexes.length &gt; <span class="hljs-number">0</span>) {
    <span class="hljs-comment">// delete column</span>
    emptyColumnIndexes.forEach(<span class="hljs-function"><span class="hljs-params">ind</span> =&gt;</span> ss.deleteColumn(ind));

  }

  <span class="hljs-comment">//***************Remove Internal empty rows */</span>
  <span class="hljs-comment">// convert nested arrays to string and remove empty strings with filter</span>
  <span class="hljs-keyword">const</span> newData = ss.getDataRange().getValues().filter(<span class="hljs-function">(<span class="hljs-params">arr</span>) =&gt;</span> arr.join(<span class="hljs-string">""</span>) !== <span class="hljs-string">""</span>)

  <span class="hljs-keyword">const</span> newLastRow = newData.length;
  <span class="hljs-keyword">const</span> newLastCol = newData[<span class="hljs-number">0</span>].length;

  <span class="hljs-comment">// clear previous values</span>
  ss.clearContents();

  <span class="hljs-comment">// set new values</span>
  ss.getRange(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, newLastRow, newLastCol).setValues(newData);

<span class="hljs-comment">// now delete empty rows and columns </span>
 deleteExternalEmptyRowsNColumns();
}
</code></pre>
<p>Let's explain what the function does in the following sections.</p>
<h3 id="heading-how-to-remove-empty-columns">How to Remove Empty Columns</h3>
<p>First, we'll work on empty columns. After that, we'll remove the empty rows.</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> emptyColumnIndexes = [];
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i &lt;= lastCol; i++) {
    <span class="hljs-keyword">if</span> (ss.getRange(<span class="hljs-number">1</span>, i, lastRow, <span class="hljs-number">1</span>).getValues().flat().join(<span class="hljs-string">""</span>) === <span class="hljs-string">""</span>) {
      <span class="hljs-comment">// subtract length before pushes value with less than 1 of original index</span>
      <span class="hljs-comment">// because later on when we delete colums one by one, the indexes </span>
      <span class="hljs-comment">//will be out of bounds/wrong due to sprd being updated to new indexes</span>
      emptyColumnIndexes.push(i - emptyColumnIndexes.length);

    }

  }

  <span class="hljs-comment">// lets delete these columns</span>
  <span class="hljs-keyword">if</span> (emptyColumnIndexes.length &gt; <span class="hljs-number">0</span>) {
    <span class="hljs-comment">// delete column</span>
    emptyColumnIndexes.forEach(<span class="hljs-function"><span class="hljs-params">ind</span> =&gt;</span> ss.deleteColumn(ind));

  }
</code></pre>
<p>Let's create an array named <code>emptyCoiumnIndexes</code>. It'll hold all the indexes of the columns that are empty. </p>
<p>To check if the columns are empty or not, we will loop through each column with a <code>for</code> loop starting from the first column.</p>
<p>Next, we will fetch the values of a column. In every loop, it will return a nested array and we will flatten the array.</p>
<p>After that, we will join the array with an empty string (""). </p>
<p>If the joined string is actually empty we know that this is an empty column so we will push that index to the empty column indexes array with the following code: <code>ss.getRange(1, i, lastRow, 1).getValues().flat().join("") === ""</code> .</p>
<p>But before pushing the index, we'll subtract the length of the <code>emptyColumnIndexes</code> array from the index itself each time. </p>
<p>That's because later on, when we delete this column, we have to delete each column one by one. </p>
<p>While doing so, we will find out that if we delete the first column, the structure of the spreadsheet changes, and the columns that come after the deleted column will have their index changed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693111825663/d8e82618-0404-4b89-89be-f22c33ed2d23.png" alt="Image" width="1920" height="1080" loading="lazy">
<em>Spreadsheet with Inbounds Empty Columns and Rows</em></p>
<p>For instance, from the preceding image, after we delete column "A" the index of column "F" will get changed to "E".</p>
<p>After this, if the <code>emptyColumnIndexes</code> is not empty then we will go through each value using <a target="_blank" href="https://www.w3schools.com/jsref/jsref_foreach.asp">forEach()</a>.</p>
<p> Then we will delete the column with <a target="_blank" href="https://developers.google.com/apps-script/reference/spreadsheet/sheet#deletecolumncolumnposition">deleteColumn()</a> method. </p>
<p>Now, run this function and you'll see a similar result as the following image, where all the empty columns have been removed:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693112373678/58a09e91-924f-4cef-a5ec-e437fcf1a597.png" alt="Image" width="1201" height="1551" loading="lazy">
<em>Spreadsheet with Inbounds Empty Columns Removed</em></p>
<h3 id="heading-how-to-remove-empty-rows">How to Remove Empty Rows</h3>
<p>Now we will work on removing empty rows from our spreadsheet. </p>
<p>To do so, we will filter all the non-empty rows using the same process we used earlier by joining them with empty string.</p>
<p> If they are not just an empty quote (""), we'll return them as the array items in the <code>newData</code> with <code>const newData = ss.getDataRange().getValues().filter((arr) =&gt; arr.join("") !== "")</code>.</p>
<p>Now, we'll save those values to our spreadsheet after clearing previous contents.</p>
<p>But if you run this function right now, you'll find out that this alone will not remove empty columns but accumulate rows into one place like the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693114104374/502bba63-dd4d-4d67-9635-0f2b2fc75358.png" alt="Image" width="1920" height="1080" loading="lazy">
<em>Spreadsheet with Outbound Empty Rows</em></p>
<p>This is not what we want.</p>
<p>So, to remove those extra rows, we will just call the function <code>deleteExternalEmptyRowsNColumns()</code> , we created earlier because now these extra spaces are outside of the range of <code>getDataRange()</code>. </p>
<p>Let's run the function again, and now we're be able to accomplish what we initially wanted:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693114340988/7f0bf2d5-8f24-4e83-9389-ad6e833c2c4b.png" alt="Image" width="1920" height="1080" loading="lazy">
<em>Clean Spreadsheet</em></p>
<h2 id="heading-how-to-create-a-custom-menu-for-the-spreadsheet">How to Create a Custom Menu for the Spreadsheet</h2>
<p>Finally, we will create a menu so that we can run these functions from the spreadsheet itself. </p>
<p>For this create a new script file in your project named menu:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Menu creates menu UI in spreadsheet.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createCustomMenu</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">let</span> menu = SpreadsheetApp.getUi().createMenu(<span class="hljs-string">"Delete Empty Rows N Columns"</span>);

  menu.addItem(<span class="hljs-string">"Delete External Empty Rows and Columns"</span>, <span class="hljs-string">"deleteExternalEmptyRowsNColumns"</span>);
  menu.addItem(<span class="hljs-string">"Delete Internal Empty Rows"</span>, <span class="hljs-string">"deleteInternalEmptyRowsNColumns"</span>);
  menu.addToUi();
}

<span class="hljs-comment">/**
 * OnOpen trigger that creates menu
 * <span class="hljs-doctag">@param <span class="hljs-type">{Dictionary}</span> <span class="hljs-variable">e</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onOpen</span>(<span class="hljs-params"></span>) </span>{
  createCustomMenu();
}
</code></pre>
<p>After saving the script, we will go to the spreadsheet and reload it. </p>
<p>After a moment, you'll be able to see a menu in the spreadsheet tab like the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693114629659/3fc2f151-12c9-47d7-abc8-549b2937d9e7.png" alt="Image" width="713" height="275" loading="lazy">
<em>Spreadsheet Menu</em></p>
<p>Congrats! </p>
<p>Now you just need to copy and paste the script from the tutorial or <a target="_blank" href="https://github.com/nibukdk/detete_empty_rows_n_columns_in_spreadsheet">this</a> GitHub repo and you'll be able to clean your sheets instantly.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, we created two functions: <code>deleteExternalEmptyRowsNColumns()</code> and <code>deleteInternalEmptyRowsNColumns()</code>. </p>
<p>We cleared empty rows and columns that were out of bounds and later deleted empty rows and columns that were in bounds of data. </p>
<p>Next, we created a menu that provides easy access to even non-coders to execute the functions mentioned above from the spreadsheet's tab.</p>
<p>Now, all that is left is for you to share this article. If you're also watching the video version I hope you'll subscribe to my <a target="_blank" href="https://youtube.com/@codingWithNibesh">channel</a> as well. </p>
<p>I am Nibesh Khadka, a freelancer specializing in automating Google products with Apps Script. Contact me if you need my services at me@nibeshkhadka.com.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Integrate ChatGPT with Google Sheets Using Google Apps Script ]]>
                </title>
                <description>
                    <![CDATA[ Welcome to this tutorial on how to integrate ChatGPT with Google Spreadsheets using the GPT API and Google Apps Script. We will create two custom formulas, GPT_SUMMARY and GPT_SIMPLIFY. You can use GPT_SUMMARY to summarize a large passage or text int... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/create-chat-gpt-formulas-in-google-sheets/</link>
                <guid isPermaLink="false">66ba5b03158e6c6a8cb8c7d0</guid>
                
                    <category>
                        <![CDATA[ chatgpt ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nibesh Khadka ]]>
                </dc:creator>
                <pubDate>Thu, 20 Jul 2023 16:06:35 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/GPT-IN-SHeets.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Welcome to this tutorial on how to integrate ChatGPT with Google Spreadsheets using the GPT API and Google Apps Script.</p>
<p>We will create two custom formulas, GPT_SUMMARY and GPT_SIMPLIFY. You can use GPT_SUMMARY to summarize a large passage or text into a few bullet points for easy reading. And you can use GPT_SIMPLIFY to simplify English into easy-to-read English.</p>
<p>We will also create menus with access to functions that perform the same tasks as formulas. We will then discuss the pros and cons of using formulas versus menus. </p>
<p>By the end of this tutorial, you will understand how to use ChatGPT in Google Sheets with Google Apps Script. You will also be able to modify the formulas and menus to meet your own needs, such as creating CVs, social media posts, or cover letters.</p>
<p>You can find the source code for this project in <a target="_blank" href="https://github.com/nibukdk/GPT_Google_Sheets_Integration">this</a> GitHub repo.</p>
<p>If you want to follow along with a video version of this article, here you go:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/DlcJv97TZhE" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h3 id="heading-pre-requisites">Pre-requisites</h3>
<p>This tutorial is not meant for beginners in Apps Script or JavaScript. I will not be explaining every method or classes used in the code. This is also not a tutorial on how to use and optimize ChatGPT – instead we'll focus on how to integrate GPT in Google Sheets.</p>
<h4 id="heading-who-is-this-tutorial-for">Who is this tutorial for?</h4>
<p>This tutorial is for intermediate to advanced users who have a basic understanding of Apps Script and JavaScript. If you are new to either of these, I recommend that you start with a beginner tutorial before attempting this one.</p>
<h2 id="heading-step-1-get-the-chatgpt-api-key">Step 1 – Get the ChatGPT API Key</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689645559540/943d9a3e-d326-4cd9-ab45-0866898110d2.png" alt="Get ChatGpt API Keys" width="1747" height="992" loading="lazy">
<em>Get ChatGpt API Key</em></p>
<p>First, if you don't already have an account with OpenAI, you'll need to <a target="_blank" href="https://auth0.openai.com/u/signup/identifier?state=hKFo2SBWU2Y5U0ZjYXlDWG5LU0xhdmxhd1pCVW1wQ2ppUUp3eKFur3VuaXZlcnNhbC1sb2dpbqN0aWTZIERpalA1aER5X3hGdEl0TzlRdnlud3FJQ2NlcDduNm4zo2NpZNkgRFJpdnNubTJNdTQyVDNLT3BxZHR3QjNOWXZpSFl6d0Q">create</a> one. Once you have an account, you can create a new API key by going to the API keys section under the User tab. </p>
<p>Click the Create new secret key button and copy the key after it has been created. <em>You will not be able to see this API key again,</em> so be sure to copy it somewhere safe.</p>
<h2 id="heading-step-2-fetch-the-data-from-the-chatgpt-api-with-apps-script">Step 2 – Fetch the Data From the ChatGpt API with Apps Script</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689558938459/8586ac7a-9b41-41ef-9dcd-c4297436912d.png" alt="Image" width="2227" height="1027" loading="lazy">
<em>Spreadsheet Sample</em></p>
<p>I have named my spreadsheet GPT_Integration with three columns: Passage, Simplified Passage, and Summarized Text.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689559154810/de6ba3ed-a5af-4a23-ab45-e64bd39a48e6.png" alt="How to Open Apps Script Code Editor From Spreadsheet" width="668" height="482" loading="lazy"></p>
<p>Let's open the app script for this spreadsheet, rename it to GPT_integration, and also rename the existing file to utils.gs. We'll create a function called <code>fetchData</code> here.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> CHAT_GPT_API_KEY = <span class="hljs-string">"paste your API key here"</span>;
<span class="hljs-keyword">const</span> BASE_URL = <span class="hljs-string">"https://api.openai.com/v1/chat/completions"</span>;


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchData</span>(<span class="hljs-params">systemContent, userContent</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> headers = {
      <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
      <span class="hljs-string">"Authorization"</span>: <span class="hljs-string">`Bearer <span class="hljs-subst">${CHAT_GPT_API_KEY}</span>`</span>
    };

    <span class="hljs-keyword">const</span> options = {
      headers,
      <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span>,
      <span class="hljs-attr">muteHttpExceptions</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">payload</span>: <span class="hljs-built_in">JSON</span>.stringify({
        <span class="hljs-string">"model"</span>: <span class="hljs-string">"gpt-3.5-turbo"</span>,
        <span class="hljs-string">"messages"</span>: [{
          <span class="hljs-string">"role"</span>: <span class="hljs-string">"system"</span>,
          <span class="hljs-string">"content"</span>: systemContent,
        },
        {
          <span class="hljs-string">"role"</span>: <span class="hljs-string">"user"</span>,
          <span class="hljs-string">"content"</span>: userContent
        },
        ],
        <span class="hljs-string">"temperature"</span>: <span class="hljs-number">0.7</span>
      })
    };

    <span class="hljs-keyword">const</span> response = <span class="hljs-built_in">JSON</span>.parse(UrlFetchApp.fetch(BASE_URL, options));
    <span class="hljs-comment">//console.log(response);</span>
    <span class="hljs-comment">//console.log(response.choices[0].message.content)</span>
    <span class="hljs-keyword">return</span> response.choices[<span class="hljs-number">0</span>].message.content;
  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-built_in">console</span>.log(e)
    SpreadsheetApp.getActiveSpreadsheet().toast(<span class="hljs-string">"Some Error Occured Please check your formula or try again later."</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Some Error Occured Please check your formula or try again later."</span>;
  }
}
</code></pre>
<p>Here are some key points to notice in the code above:</p>
<ol>
<li>Paste the API key that you created earlier inside the quotes.</li>
<li>We will be using the Chat Completions API. You can find more details about it <a target="_blank" href="https://platform.openai.com/docs/api-reference/chat/create">here</a>.</li>
<li>ChatGPT models have different roles, such as system, user, and assistant.</li>
<li>The systemContent parameter is where you provide roleplay for the GPT system. For example, you could say "You're an expert algebra teacher" or "You're an expert CV writer".</li>
<li>The userContent parameter is where you provide tasks to perform for the model. In our case, we will provide long passages from the spreadsheet to summarize and simplify.</li>
<li>We will be using the <a target="_blank" href="https://platform.openai.com/docs/models/gpt-3-5">GPT 3.5 turbo model</a>.</li>
<li>We are muting HTTPExceptions so that we can use our own error message in the catch block.</li>
<li>The error string is useful when we face errors such as <a target="_blank" href="https://platform.openai.com/docs/guides/rate-limits/what-are-the-rate-limits-for-our-api">Rate Limit Exceed</a>.</li>
</ol>
<p>We're returning the content from the response object of GPT that'll be later on handled by our formulas.</p>
<p>The response object from ChatGPT has following structure:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"chatcmpl-123"</span>,
  <span class="hljs-attr">"object"</span>: <span class="hljs-string">"chat.completion"</span>,
  <span class="hljs-attr">"created"</span>: <span class="hljs-number">1677652288</span>,
  <span class="hljs-attr">"choices"</span>: [{
    <span class="hljs-attr">"index"</span>: <span class="hljs-number">0</span>,
    <span class="hljs-attr">"message"</span>: {
      <span class="hljs-attr">"role"</span>: <span class="hljs-string">"assistant"</span>,
      <span class="hljs-attr">"content"</span>: <span class="hljs-string">"\n\nHello there, how may I assist you today?"</span>,
    },
    <span class="hljs-attr">"finish_reason"</span>: <span class="hljs-string">"stop"</span>
  }],
  <span class="hljs-attr">"usage"</span>: {
    <span class="hljs-attr">"prompt_tokens"</span>: <span class="hljs-number">9</span>,
    <span class="hljs-attr">"completion_tokens"</span>: <span class="hljs-number">12</span>,
    <span class="hljs-attr">"total_tokens"</span>: <span class="hljs-number">21</span>
  }
}
</code></pre>
<p>Read more on how to use URLFetchApp from <a target="_blank" href="https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app">here</a>.</p>
<h2 id="heading-step-3-integrate-chatgpt-as-a-sheets-formula">Step 3 – Integrate ChatGpt as a Sheets Formula</h2>
<h3 id="heading-gpt-simplify-formula">GPT SIMPLIFY Formula</h3>
<p>Again, for the custom formula, we'll create a new file named formula and then we will create a function named GPT_SIMPLIFY.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Simplifies the given paragraph in layman's term.
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>input The value to simplify.
 * <span class="hljs-doctag">@return </span>Simplified Text.
 * <span class="hljs-doctag">@customfunction</span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GPT_SIMPLIFY</span>(<span class="hljs-params">input</span>) </span>{
  <span class="hljs-built_in">console</span>.log(input)
  <span class="hljs-keyword">const</span> systemContent = <span class="hljs-string">"Simplify the given text in layman's term. Remember reader is not an expert in english."</span>;
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>.isArray(input) ?
    input.flat().map(<span class="hljs-function"><span class="hljs-params">text</span> =&gt;</span> fetchData(systemContent, text)) :
    fetchData(systemContent, input);

}
</code></pre>
<ol>
<li>The <code>GPT_SIMPLIFY</code> formula simplifies whatever text is provided as input. The input to this function is data that is coming from the spreadsheet. When you select a range, a cell, or multiple cells, the data in the range will be automatically provided by the spreadsheet to this formula.</li>
<li>The <code>systemContent</code> is defined to be passed as the first parameter to the <code>fetchData(systemContent,userContent)</code> function. </li>
<li>We are checking if the input is an Array because the data passed to this function can either be a nested array or just a string if we select multiple cells or single cell, respectively, in the spreadsheet.</li>
</ol>
<p>You can read more on custom functions on this <a target="_blank" href="https://developers.google.com/apps-script/guides/sheets/functions">page</a>.</p>
<p>Now go ahead and apply this formula in your spreadsheet. I copied some text from a book I'm reading in the first column and applied the formula in the second column named "Simplify Passage", like this <code>=GPT_SIMPLIFY(A2)</code> for the second cell.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/gpt_simplify_formula.png" alt="Image" width="600" height="400" loading="lazy">
_Appilcation of GPT<em>SIMPLIFY Formula</em></p>
<p>Note: Make sure to refresh the spreadsheet before you apply the formula to sync with the latest changes in the script.</p>
<h3 id="heading-gpt-summarize">GPT SUMMARIZE</h3>
<p>To summarize the formula we'll just copy the simplify function and some other things, as you can see in the code below.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Summarzies the given paragraph. It provides from 3-5 bullet points
 *
 * <span class="hljs-doctag">@param <span class="hljs-type">{String}</span> </span>input The value to summarize.
 * <span class="hljs-doctag">@return </span>summarize Text.
 * <span class="hljs-doctag">@customfunction</span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GPT_SUMMARY</span>(<span class="hljs-params">input</span>) </span>{
  <span class="hljs-built_in">console</span>.log(input)
  <span class="hljs-keyword">const</span> systemContent = <span class="hljs-string">"Summarize the given text. Provide atleast 3 and atmost 5 bullet points."</span>;
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>.isArray(input) ?
    input.flat().map(<span class="hljs-function"><span class="hljs-params">text</span> =&gt;</span> fetchData(systemContent, text)) :
    fetchData(systemContent, input);

}
</code></pre>
<p>The main thing to notice here is the different system content.</p>
<p>Note: Since this is not a tutorial on how to use ChatGpt optimally, I provided instructions as the system content instead of role-play, and then just provided data in the user content. You can improvise this by providing roles in system content, and tasks as well as data as two different user roles in our <code>FetchData()</code> function.</p>
<h3 id="heading-gpt-rate-limit-error">GPT Rate Limit Error</h3>
<p>For free users, the rate limit to use the API is <strong>3/minute</strong>. As such, when you apply these formulas in more than three cells you'll encounter the error. Luckily the execution won't stop because we're returning an error string from fetch data which will be saved into those cells.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689574131023/70e326ac-33bd-4d75-96db-208fc27b5859.png" alt="GPT Rate Limit Error" width="2178" height="321" loading="lazy">
<em>API rate limit error</em></p>
<h3 id="heading-auto-refresh-and-error">Auto Refresh and Error</h3>
<p>Moreover, the auto-refresh feature of the formula can force the re-application of the formula on cells that already have satisfying values whenever source cells are updated, in our case cells in column "A".</p>
<p>When we add a rate limit on top of auto-refresh it can cause a conundrum. You can technically make changes in custom functions to accommodate such circumstances but, I like to keep formulas light and efficient. So, I recommend we instead create custom menus and apply these functions manually.</p>
<h2 id="heading-step-4-integrate-gpt-chat-api-in-spreadsheet-menu-functions">Step 4 – Integrate GPT Chat API in Spreadsheet Menu Functions</h2>
<h3 id="heading-gpt-simplify-menu">GPT Simplify Menu</h3>
<p>First, let's create another file named <code>menu</code>. Then we'll create the <code>gptSimplifyMenu</code> function which will be an alternative to the GPT_SIMPLIFY formula.</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">/**
 * Simplifies the given paragraph in layman's term.
 * <span class="hljs-doctag">@customfunction</span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">gptSimplifyMenu</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// get sheets and data</span>
    <span class="hljs-keyword">const</span> ss = SpreadsheetApp.getActiveSheet();
    <span class="hljs-keyword">const</span> data = ss.getDataRange().getValues();
    <span class="hljs-keyword">const</span> lastRow = data.length;
    <span class="hljs-keyword">const</span> lastCol = data[<span class="hljs-number">0</span>].length;

    <span class="hljs-comment">// define gpt's role play</span>
    <span class="hljs-keyword">const</span> systemContent = <span class="hljs-string">"Simplify the given text in layman's term. Remember reader is not an expert in english."</span>;


    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i &lt; data.length; i++) {
      <span class="hljs-comment">// only simplify if not already simplified or error occured previously</span>
      <span class="hljs-keyword">if</span> (data[i][<span class="hljs-number">1</span>] === <span class="hljs-string">""</span> || data[i][<span class="hljs-number">1</span>] === <span class="hljs-string">"Some Error Occured Please check your formula or try again later."</span>) {
        data[i][<span class="hljs-number">1</span>] = fetchData(systemContent, data[i][<span class="hljs-number">0</span>]);
        <span class="hljs-built_in">console</span>.log(data[i][<span class="hljs-number">1</span>]);

      }
    }

    ss.getRange(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, lastRow, lastCol).setValues(data);
  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-built_in">console</span>.log(e)
    SpreadsheetApp.getActiveSpreadsheet().toast(<span class="hljs-string">"Some Error Occured Please check your formula or try again later."</span>);

  }
}
</code></pre>
<p>Key points that are different to understand in this code are:</p>
<ol>
<li>We're hardcoding the data sources, as such data[i][1], which refers to the second column (that is "Simplified Passage") as shown in the spreadsheet image above. This means that if you're using some other columns to save data from ChatGPT to, then you'll have to make changes according to it.</li>
<li>We only fetch data when the target cell is empty or contains an error message. This helps to avoid unnecessary API calls.</li>
</ol>
<h3 id="heading-add-a-custom-function-as-a-spreadsheet-menu">Add a Custom Function as a Spreadsheet Menu</h3>
<p>The function is ready to be tested, but it still will not appear in the spreadsheet. To do so, we'll need to provide the following instructions.</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">/**
 * Menu creates menu UI in spreadsheet.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createCustomMenu</span>(<span class="hljs-params"></span>) </span>{
   <span class="hljs-comment">// define menu ui </span>
  <span class="hljs-keyword">let</span> menu = SpreadsheetApp.getUi().createMenu(<span class="hljs-string">"GPT Functions"</span>);
   <span class="hljs-comment">// add function to the menu</span>
   menu.addItem(<span class="hljs-string">"GPT SIMPLIFY"</span>, <span class="hljs-string">"gptSimplifyMenu"</span>);
   <span class="hljs-comment">// add menu to the spreadsheet ui</span>
  menu.addToUi();
}

<span class="hljs-comment">/**
 * OnOpen trigger that creates menu
 * <span class="hljs-doctag">@param <span class="hljs-type">{Dictionary}</span> <span class="hljs-variable">e</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onOpen</span>(<span class="hljs-params">e</span>) </span>{
  createCustomMenu();
}
</code></pre>
<p>In <code>createCustomMenu()</code>:</p>
<ol>
<li>We define menu with <a target="_blank" href="https://developers.google.com/apps-script/reference/base/ui#createmenucaption"><code>SpreadsheetApp.getUi().createMenu("GPT Functions")</code></a> as GPT Functions the title appearing in the spreadsheet tab.</li>
<li>We add a function to the menu with the <code>menu.addItem("GPT SIMPLIFY", "gptSimplifyMenu")</code>, where the first parameter is the title for display and the second is the function to call when pressed.</li>
<li>Add the menu to the UI with <code>menu.addToUi()</code>.</li>
</ol>
<p>The <a target="_blank" href="https://developers.google.com/apps-script/guides/triggers#onopene">onOpen</a> trigger runs automatically whenever the document the script is attached to reloads and as such will add a menu to the spreadsheet as shown in the image below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1689573996102/660acf86-567e-4261-96db-2a8ed1c2182c.png" alt="GPT Function Menu" width="1009" height="236" loading="lazy"></p>
<p>Go ahead and try the formula – it'll only be applied if the cell is either empty or prefilled with an error message.</p>
<h3 id="heading-gpt-summarize-menu">GPT Summarize Menu</h3>
<p>We'll make some minor changes after copying the simplify function as shown below:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
 * Summarzies the given paragraph. It provides from 3-5 bullet points
 * <span class="hljs-doctag">@customfunction</span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">gptSummaryMenu</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// get sheets and data</span>
    <span class="hljs-keyword">const</span> ss = SpreadsheetApp.getActiveSheet();
    <span class="hljs-keyword">const</span> data = ss.getDataRange().getValues();
    <span class="hljs-keyword">const</span> lastRow = data.length;
    <span class="hljs-keyword">const</span> lastCol = data[<span class="hljs-number">0</span>].length;

    <span class="hljs-comment">// define gpt's role play</span>
    <span class="hljs-keyword">const</span> systemContent = <span class="hljs-string">"Summarize the given text. Provide atleast 3 and atmost 5 bullet points."</span>;


    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; data.length; i++) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Inside gptSummaryMenu() for loop`</span>)

      <span class="hljs-keyword">if</span> (i == <span class="hljs-number">0</span>) <span class="hljs-keyword">continue</span>;
      <span class="hljs-comment">// only summarize if not already summarized or error occured previously</span>
      <span class="hljs-keyword">if</span> (data[i][<span class="hljs-number">2</span>] === <span class="hljs-string">""</span> || data[i][<span class="hljs-number">2</span>] === <span class="hljs-string">"Some Error Occured Please check your formula or try again later."</span>) {
        data[i][<span class="hljs-number">2</span>] = fetchData(systemContent, data[i][<span class="hljs-number">0</span>]);
        <span class="hljs-built_in">console</span>.log(data[i][<span class="hljs-number">2</span>]);
      }
    }

    ss.getRange(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, lastRow, lastCol).setValues(data);
  } <span class="hljs-keyword">catch</span> (e) {
    <span class="hljs-built_in">console</span>.log(e)
    SpreadsheetApp.getActiveSpreadsheet().toast(<span class="hljs-string">"Some Error Occured Please check your formula or try again later."</span>);
  }
}
</code></pre>
<ol>
<li>The system role has been changed to address summary instruction.</li>
<li>The target column to save data is now the third column.</li>
<li>The doc string has been adjusted as well.</li>
</ol>
<p>As for adding this function to the menu I'll leave it to you.</p>
<h2 id="heading-tips-to-modify-the-code">Tips to Modify the Code</h2>
<p>All you need to make your own formula like =GPT_COVER_LETTER_CREATOR() are the following modifications:</p>
<h3 id="heading-to-fetchdata">To FetchData</h3>
<p>You can change System Content Description should to address your needs, like "You write an expert cover letter for software developers".</p>
<p>Add one more instruction in the messages array:</p>
<pre><code class="lang-json"><span class="hljs-comment">// from this </span>
[{
          <span class="hljs-attr">"role"</span>: <span class="hljs-string">"system"</span>,
          <span class="hljs-attr">"content"</span>: systemContent,
        },
        {
          <span class="hljs-attr">"role"</span>: <span class="hljs-string">"user"</span>,
          <span class="hljs-attr">"content"</span>: userContent
        },
        ], 

<span class="hljs-comment">// to </span>
[{
          <span class="hljs-attr">"role"</span>: <span class="hljs-string">"system"</span>,
          <span class="hljs-attr">"content"</span>: <span class="hljs-string">"You write an expert cover letter for software developers"</span>,
        },
        {
          <span class="hljs-attr">"role"</span>: <span class="hljs-string">"user"</span>,
          <span class="hljs-attr">"content"</span>: <span class="hljs-string">"Write me a cover letter for this given job advertisement"</span>
        },
        {
          <span class="hljs-attr">"role"</span>: <span class="hljs-string">"user"</span>,
          <span class="hljs-attr">"content"</span>: userContent <span class="hljs-comment">// this is job ad from spreadsheet</span>
        },
        ],
</code></pre>
<p>You can also add another list item to include your skills and experiences.</p>
<h3 id="heading-other-functions">Other Functions</h3>
<p>Just make sure that your source cell/columns and target cell/columns are accurately indexed (for instance, if you're not using the first column as the source cell and the second to save the data).</p>
<h2 id="heading-summary">Summary</h2>
<p>In this tutorial, you learned how to use Google Apps Script to fetch ChatGPT responses from the API and save them into spreadsheets using custom formulas and custom menus.</p>
<p>We started by creating a new Google Apps Script project and adding the ChatGPT API. Then, we wrote a script that would fetch a ChatGPT response for a given prompt. We saved the response using a custom formula into a spreadsheet cell.</p>
<p>We also created a custom menu item that would allow us to fetch a ChatGPT response from any cell in the spreadsheet. This menu item would open a button to fetch the response.</p>
<p>The final step was to share the spreadsheet with others so that they could use the custom formulas and menus to fetch ChatGPT responses.</p>
<p>I hope you enjoyed this article and found it helpful. If you have any questions, just let me know.</p>
<p>I am <strong>Nibesh Khadka,</strong> Freelancer specializing in automating Google products with Apps Script. Contact me if you need my services at me@nibeshkhadka.com.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Google Sheets – How to Automatically Post Events to Google Calendar with Apps Script ]]>
                </title>
                <description>
                    <![CDATA[ In this article we'll link two Google services -> Google Sheets and Google Calendar. By using a very short custom function in Google Apps Script, we can add a list of events from a Google Sheet to a Google Calendar. 🤯 And we'll even have it email ou... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/google-sheets-automatically-post-events-to-google-calendar-with-apps-script/</link>
                <guid isPermaLink="false">66b8ddec0a89d796f29a16df</guid>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Wed, 24 May 2023 21:44:39 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/Sheets-to-Calendar3.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article we'll link two Google services -&gt; Google Sheets and Google Calendar.</p>
<p>By using a very short custom function in Google Apps Script, we can add a list of events from a Google Sheet to a Google Calendar. 🤯</p>
<p>And we'll even have it email our guests as well. 🔥</p>
<p>Here's the video walkthrough to accompany the article:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/FxxPq2wXcK4" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-google-sheets-setup">Google Sheets Setup</h2>
<p>Our sheet is quite straightforward. We have event names, dates, start times, end times and guest emails. </p>
<p>The only curious thing is the formatting of our dates and times - I'll cover this as we go on, but you can see that columns B and C are repeating information from columns D, E and F...</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/1684886378577.png" alt="Screenshot of Google Sheet event information" width="600" height="400" loading="lazy"></p>
<p>Google Calendar needs to receive the start and end times in the form of a full date/time object. But in the Google Sheet, there's not an easy way to create a dropdown data validation for users to select a date/time object.</p>
<p>In column D, I've put data validation to select a valid date.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/1684886609169.png" alt="Screenshot of data validation for a valid date" width="600" height="400" loading="lazy"></p>
<p>And in columns E and F, I've created a dropdown list of valid times.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/1684886767503.png" alt="Screenshot of data validation for a valid time" width="600" height="400" loading="lazy"></p>
<p>Columns B and C combine these together into a format that's useable to send to Google Calendar by using the =TEXT() function to concatenate the date and times together.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/1684886775902.png" alt="Screenshot of google sheets text concatenation" width="600" height="400" loading="lazy"></p>
<p>I promise it'll make more sense in a second! 😃</p>
<h2 id="heading-calendar-setup">Calendar Setup</h2>
<p>Let's make a new calendar in Google Calendar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/1684887186669.png" alt="Screenshot of new calendar options" width="600" height="400" loading="lazy"></p>
<p>Underneath your calendars on the left sidebar of Google Calendar, click the plus icon to add a new one.</p>
<p>Give it a name and a description if you want, and then we're ready to roll.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/1684887287557.png" alt="Screenshot of creating a new calendar" width="600" height="400" loading="lazy"></p>
<p>Scroll down a bit in the calendar's settings to the Integrate Calendar section. Copy the calendar ID. This is how we'll get Apps Script talking to Calendar!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/1684887643027.png" alt="Screenshot of calendar ID" width="600" height="400" loading="lazy"></p>
<h2 id="heading-apps-script-calendarapp">Apps Script + CalendarApp</h2>
<p>Apps Script is awesome. 👏</p>
<p>The <a target="_blank" href="https://developers.google.com/apps-script/reference/calendar/calendar-app">Class CalendarApp</a> allows a script to access a user's Google Calendar and make changes to it.</p>
<p>Here is the full script, and we'll walk through what's going on below.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Creates an events variable which is an array of arrays</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createCalendarEvent</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> events = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(<span class="hljs-string">"events"</span>).getValues();

    <span class="hljs-comment">// Creates an event for each item in events array</span>

    events.forEach(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>)</span>{
        CalendarApp.getCalendarById(<span class="hljs-string">"f7574e7b4d1ad00c9ecd7f1eba5bed329e8600e317cd387a400748d67f301d06@group.calendar.google.com"</span>).createEvent(
          e[<span class="hljs-number">0</span>],
          <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(e[<span class="hljs-number">1</span>]),
          <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(e[<span class="hljs-number">2</span>]),
          {<span class="hljs-attr">guests</span>: e[<span class="hljs-number">6</span>],<span class="hljs-attr">sendInvites</span>: <span class="hljs-literal">true</span>}
    );
  })
}
</code></pre>
<p>I've named the range <code>A3:B8</code> as "events". Then in Apps Script, we create a variable named events that grabs all the values in that whole range. We used a small range, but you could make this as many rows long as need be.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">let</span> events = SpreadsheetApp.getActiveSpreadsheet().getRangeByName(<span class="hljs-string">"events"</span>).getValues();
</code></pre>
<p>Then, we loop through each item and add the events to our calendar.</p>
<p>The first part is where we use that calendar ID string we grabbed from Google Calendar for the <code>getCalendarById</code> method.</p>
<p>Then we use the <code>createEvent</code> method to pull data from each row in our Google Sheet and make new events.</p>
<p>Here's the <code>createEvent</code> description from the <a target="_blank" href="https://developers.google.com/apps-script/reference/calendar/calendar-app#createeventtitle,-starttime,-endtime,-options">developers page</a>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/1684888482755.png" alt="Screenshot of createEvent method" width="600" height="400" loading="lazy"></p>
<p>You can think of each row of data in the Google Sheet as an array of values. In the zero position is the event name, in the one position is the event date and start time, and so on.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/1684938707460.jpeg" alt="Screenshot of array of arrays illustrated" width="600" height="400" loading="lazy"></p>
<p>By using <code>e[0]</code> we can access the element that is in the zero position for every time we loop through the forEach loop...effectively looping through each row of data.</p>
<p>And this is where the funky stuff we did with the start and end times comes into play.</p>
<p>Because the values in columns B and C are strings since we concatenated them together, we need to turn them back into complete date objects now.</p>
<p>That's why we're passing <code>new Date(e[1])</code> and <code>new Date (e[2])</code> into our createEvent function.</p>
<p>It's a bit of a cumbersome way to allow ourselves to use those dropdown selections in Google Sheets rather than painfully typing in a full date/time object.</p>
<p>User experience &gt; code. 👍</p>
<p>And lastly, we add an optional parameter to send invites to guests.</p>
<h2 id="heading-send-with-button">Send with Button</h2>
<p>That's all there is to the Apps Script. 🎉</p>
<p>As an added feature, we've attached a script to the rounded rectangle drawing to make it function like a button. Any time this is pressed, the events in the Google Sheet will populate the Google Calendar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/1684938905901.png" alt="Screenshot of assigning a script to a drawing in Google Sheets" width="600" height="400" loading="lazy"></p>
<p>I hope this has been useful for you!</p>
<p>Please come check out and subscribe to my <a target="_blank" href="https://www.youtube.com/@eamonncottrell?sub_confirmation=1">YouTube channel</a> where I'm making weekly videos on coding and spreadsheets.</p>
<p>If you'd like my newsletter in your inbox, <a target="_blank" href="https://got-sheet.beehiiv.com/subscribe">check it out here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Google Apps Script Basics by Building a Tic Tac Toe Game ]]>
                </title>
                <description>
                    <![CDATA[ Google Sheets are powerful, and Apps Script makes them even more versatile and useful. Yes, you can use them for finance dashboards, personal budgets, and project management (and we'll cover these as future topics). But in this article, I'll go throu... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-google-apps-script-basics-by-building-tic-tac-toe/</link>
                <guid isPermaLink="false">66b8de150cedc1f2a4f7067c</guid>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Tue, 16 May 2023 16:58:37 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/Tic-Tac-Toe-Google-Sheet2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Google Sheets are powerful, and Apps Script makes them even more versatile and useful.</p>
<p>Yes, you can use them for finance dashboards, personal budgets, and project management (and we'll cover these as future topics). But in this article, I'll go through the basics of Apps Script by building a simple and playable Tic Tac Toe gameboard.</p>
<p>Here's the <a target="_blank" href="https://docs.google.com/spreadsheets/d/1I3mjQgfaZ9hFuUui6irpTdXg17TZXfK_jjtMvPrlGlM/edit?usp=sharing">link to the spreadsheet</a> we'll be making if you'd like to check it out while you follow along:</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQF91mJiUd4F5w/article-inline_image-shrink_1500_2232/0/1684162312276?e=1689811200&amp;v=beta&amp;t=ue0QHmYwX0I7oqirS6GZx66YXl5y_-zSkA6iPdDGKBM" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>Let's go gif</em></p>
<h3 id="heading-video-walkthrough-available">Video Walkthrough Available</h3>
<p>If you'd like to check out a video walkthrough of the Google Sheet, here you go:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/LYN3Cvlsflg" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Create a new Google Sheet by either going to your Google Drive and selecting NEW -&gt; Google Sheet or by simply typing in sheets.new in the URL bar of your browser.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQGtAhxBgZCiPQ/article-inline_image-shrink_1500_2232/0/1684161563448?e=1689811200&amp;v=beta&amp;t=5TuDNK0iSggXlKST3fzl-wv5JV04qwbYizc3ev8F51E" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of Google Drive</em></p>
<p>Since this is a gameboard, we are going to give it a little bit of formatting to look good, add data validation and conditional formatting to add functionality to the game, and create useable buttons for our scorecard.</p>
<p>Here's what we'll end up with:</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQFmLTdzhlehew/article-inline_image-shrink_1000_1488/0/1684161671894?e=1689811200&amp;v=beta&amp;t=2nJ0ZTnaCo2Dtq7u3zzSP7av2MHjtPtAyVp7dRQUwvY" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of Tic Tac Toe Google Sheet</em></p>
<p>Let's remove gridlines, add borders to the gameboard and the scorecard, and set an alternative font for the board.</p>
<p>To get rid of gridlines, select View -&gt; Show -&gt; Gridlines to uncheck this option.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQFsYtEPWEAxjA/article-inline_image-shrink_400_744/0/1684161946643?e=1689811200&amp;v=beta&amp;t=r6rUrzy6NSj-KQDb0o7Jx64N_u3CmTWQJxr_KaTAtII" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of Google Sheets view options</em></p>
<p>To get a nice square grid with large X's and O's, I set the row height and column width of rows 2 - 4 and columns B - D by highlighting them, right clicking, and selecting the resize options.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQGJGcSpYb2KyA/article-inline_image-shrink_1000_1488/0/1684162096408?e=1689811200&amp;v=beta&amp;t=uU37mbBSZBSMgi25ICD9r3hzAyCkepCmXAYx2ebCKsw" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of resizing columns in Google Sheets</em></p>
<p>I chose 150 pixels for the height and width. You'll need to do these separately – you cannot change both the row height and column width at the same time.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQHzgbT4rS6f6w/article-inline_image-shrink_1000_1488/0/1684162162590?e=1689811200&amp;v=beta&amp;t=J-yExtOPV3icc6-YhGUmWESA0FUMzaJQu6N4qemzCwU" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of resizing columns in Google Sheets</em></p>
<p>For the font size of the board, select 100, and for the Font, I am using Lexend. You can add additional Google Fonts from the toolbar dropdown:</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQF6Ss_zEGVSug/article-inline_image-shrink_1500_2232/0/1684162246754?e=1689811200&amp;v=beta&amp;t=R548yqr57k0ffuORMeqbZ3fmhQEWYL_QCiJv1EOGNo4" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of Fonts options in Google Sheets</em></p>
<p>Add a border to the board and the scorecard areas by highlighting the cells and then selecting the border options from the toolbar.</p>
<p>Click and drag over cells to select the whole range, and hold down the CTRL button to click and drag a second area. You can style these at the same time.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQHGcJin6dOdtw/article-inline_image-shrink_1000_1488/0/1684162477801?e=1689811200&amp;v=beta&amp;t=lfxyTUvqmv7KjhZ5B2X9eDeVw9bWVm5rNiCBmReF6OE" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of border options in Google Sheets</em></p>
<h2 id="heading-data-validation">Data Validation</h2>
<p>Highlight the gameboard (B2:D4) and select Data -&gt; Data validation from the menu.</p>
<p>This allows us to select Dropdown as the Criteria and add X and O as the two options to select.</p>
<p>Then click Advanced options and select Reject the input if the data is invalid, and plain text for the display style. This will keep the dropdown chips and handles from cluttering the gameboard.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQGj3dQDQ4QGuQ/article-inline_image-shrink_1500_2232/0/1684162681967?e=1689811200&amp;v=beta&amp;t=_HfNZ0C9L7tWucbWMMpzKf9gItYhkMP3G1cRlVGioxQ" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of Data validation menu in Google Sheets</em></p>
<h2 id="heading-conditional-formatting">Conditional Formatting</h2>
<p>We'll use conditional formatting for our gameboard also. We need to check for all the winning conditions, and if one of the players gets three in a row, we will highlight those cells.</p>
<p>Keeping the gameboard highlighted, select Format -&gt; Conditional formatting.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQFJwi61h00_3g/article-inline_image-shrink_1000_1488/0/1684162938075?e=1689811200&amp;v=beta&amp;t=rdKBw21dV6Q82CgxNfiOG8Zg9N3fKXA135SauhAAYhA" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of Format window in Google Sheets</em></p>
<p>There are eight conditions we'll check for three-in-a-row: going across x3, going down x3, and diagonal x2.</p>
<p>But we only need to write four formulas (two for the diagonals, one for across, and one for down) since we can use dollar ($) signs to drag the formula down and across for those.</p>
<p>For the down three-in-a-row:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//Apply to range B2:D2 </span>
=AND($B2=$C2,$B2=$D2,ISTEXT($B2))
</code></pre>
<p>For the across three-in-a-row:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//Apply to range B2:D2</span>
=AND(B$<span class="hljs-number">2</span>=B$<span class="hljs-number">3</span>,B$<span class="hljs-number">2</span>=B$<span class="hljs-number">4</span>,ISTEXT(B$<span class="hljs-number">3</span>))
</code></pre>
<p>For the diagonals we have to define them separately:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//Apply to range B2, C3, D4 </span>
=AND($B$<span class="hljs-number">2</span>=$C$<span class="hljs-number">3</span>,$B$<span class="hljs-number">2</span>=$D$<span class="hljs-number">4</span>,ISTEXT($B$<span class="hljs-number">2</span>))

<span class="hljs-comment">//Apply to range B4, C3, D2 </span>
=AND($B$<span class="hljs-number">4</span>=$C$<span class="hljs-number">3</span>,$B$<span class="hljs-number">4</span>=$D$<span class="hljs-number">2</span>,ISTEXT($B$<span class="hljs-number">4</span>))
</code></pre>
<p>We test for equality of each cell and whether there is anything in the cell with the <code>=ISTEXT()</code> function. By wrapping each item in an <code>=AND()</code> function, we'll only apply the formatting if all conditions are met.</p>
<p>I selected a green background for the conditional formatting.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQERGL76FWm8nA/article-inline_image-shrink_1500_2232/0/1684163627050?e=1689811200&amp;v=beta&amp;t=9C0IASvwa_KE1wm68iez-FxJ2YjmuwDRB42-ASak_5s" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of conditional formatting window</em></p>
<h2 id="heading-apps-script">Apps Script</h2>
<p>Now for the scorecard logic. Let's open Apps Script by selecting Extensions -&gt; Apps Script from the menu:</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQHtQjZ3mujfUQ/article-inline_image-shrink_1500_2232/0/1684178999142?e=1689811200&amp;v=beta&amp;t=N5Wvx0y2md14-2Ih_PGj1_lHbKpW2mcEIPRNnRsvUAI" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of Apps Script menu</em></p>
<p>We'll write four functions to handle our logic:</p>
<ol>
<li><code>xScore()</code> will increment X's score in the scorecard</li>
<li><code>oScore()</code> will increment O's score in the scorecard</li>
<li><code>clearBoard()</code> will clear the board but keep the scores</li>
<li><code>reset()</code> will clear the board and set the scores back to zero</li>
</ol>
<p>To make things more legible, let's set a few named ranges.</p>
<p>Highlight the gameboard again and select Data -&gt; Named ranges. Give this range a name of <strong>Board</strong>. Do the same for cells G4 and H4 for <strong>xScore</strong> and <strong>oScore</strong>, respectively.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQHS7_YVdCFbog/article-inline_image-shrink_1500_2232/0/1684163925497?e=1689811200&amp;v=beta&amp;t=8-jk-X5iea045w1ZBkCqiExObkF6c4A572zR4dqIrI0" alt="No alt text provided for this image" width="600" height="400" loading="lazy"></p>
<p>Now for the scores, we'll have the exact same function for each using only the two different ranges: xScore for X and oScore for O. Here's how those will look using the xScore as an example:</p>
<p><strong><code>xScore()</code> &amp; <code>oScore()</code>:</strong></p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">xScore</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">var</span> sheet = SpreadsheetApp.getActive(); 
    <span class="hljs-keyword">var</span> xScore = sheet.getRangeByName(<span class="hljs-string">'xScore'</span>).getValue();
    sheet.getRangeByName(<span class="hljs-string">'xScore'</span>).setValue(xScore+<span class="hljs-number">1</span>); clearBoard();
}
</code></pre>
<ul>
<li>Line 1: This sets a variable (we'll do this in each function) for the active spreadsheet.</li>
<li>Line 2: This sets a variable for xScore as the value in the named range xScore (cell G4)</li>
<li>Line 3: This sets a new value for the xScore cell as whatever it was plus 1.</li>
<li>Line 4: This runs the clearBoard() function which we'll write next...</li>
</ul>
<p><strong><code>clearBoard()</code>:</strong></p>
<p>This will simply clear the gameboard but leave the score board untouched.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">clearBoard</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> sheet = SpreadsheetApp.getActive();
    <span class="hljs-keyword">let</span> board = sheet.getRangeByName(<span class="hljs-string">'Board'</span>);
    board.clearContent(); 
}
</code></pre>
<ul>
<li>Line 1: Our sheet variable again.</li>
<li>Line 2: Our board variable. This is grabbing the range B2:D4 which we named 'Board'</li>
<li>Line3: This built-in <code>clearContent()</code> method simply clears everything in those cells. Pretty simple.</li>
</ul>
<p><strong><code>reset()</code>:</strong></p>
<p>Now we need a function to set the score board and gameboard back to their original states.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reset</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">let</span> sheet = SpreadsheetApp.getActive(); 
    sheet.getRangeByName(<span class="hljs-string">'xScore'</span>).setValue(<span class="hljs-number">0</span>);
    sheet.getRangeByName(<span class="hljs-string">'oScore'</span>).setValue(<span class="hljs-number">0</span>); clearBoard(); 
}
</code></pre>
<ul>
<li>Line 1: our active sheet</li>
<li>Line 2: we grab our xScore range and set its value to 0.</li>
<li>Line 3: we do the same for our oScore</li>
<li>Line 4: we run the reset function to handle the gameboard.</li>
</ul>
<p>And that's it! Now we can run any of these functions from within the Apps Script editor and see that they work.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQFUTfyjYKeECQ/article-inline_image-shrink_1000_1488/0/1684178952423?e=1689811200&amp;v=beta&amp;t=ek0cIfi0cCqZfWKM7q7ygA7n18pKwMD5wdqUVizCQjU" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>screenshot of running code within Apps Script editor</em></p>
<h2 id="heading-how-to-create-buttons">How to Create Buttons</h2>
<p>It would be a lot nicer to have buttons in our actual spreadsheet to be able to run the functions.</p>
<p>To do this, we'll draw a button and then assign a script to it.</p>
<p>Select Insert -&gt; Drawing from the menu.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQEmlXrm9lNOjw/article-inline_image-shrink_1500_2232/0/1684179236609?e=1689811200&amp;v=beta&amp;t=wSn2IgbM2H6_flsNE0CecTzLqKj9K2ILIgCDL9N5x9w" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>Screenshot of Insert menu in Google Sheets</em></p>
<p>You can draw anything you'd like, but I chose the basic rounded rectangle.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQFbc8Vp8xMAEw/article-inline_image-shrink_400_744/0/1684179297444?e=1689811200&amp;v=beta&amp;t=M7QO7ZdcylQ6W4grOmguGY3aI_S-G-cpAVWiJOiyMcc" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>Screenshot of shapes in Google Sheets drawings menu</em></p>
<p>Double click in the shape to add text, and resize, recolor, restyle as needed.</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQHSkIBOsRvNdg/article-inline_image-shrink_1000_1488/0/1684179429349?e=1689811200&amp;v=beta&amp;t=9G87KKPY_efHEOTU_K2POBbUzeSfFlbs5nG282Oo4Gc" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>Screenshot of button in Google Sheets</em></p>
<p>Once you've created your button, click Save and Close. Then resize and position it where you'd like it in the Google Sheet. I've put mine right under the scorecard, and I made one for each score as well as a reset button.</p>
<p>Finally, to make the button work, click the three little dots at the top right of the button and select <strong>assign script</strong>. Then type in the name of the script (without the parentheses).</p>
<p><img src="https://media.licdn.com/dms/image/D5612AQGCHPMJ8FW-hw/article-inline_image-shrink_1500_2232/0/1684179559428?e=1689811200&amp;v=beta&amp;t=41p0tXKvG3Moj8bTJAI45nUhcZAUjhzPNoSbD_IP57I" alt="No alt text provided for this image" width="600" height="400" loading="lazy">
<em>Screenshot of assigning a script to a button in Google Sheets</em></p>
<p>Now, all you've got to do is click one of the buttons and the assigned script will run 🔥.</p>
<p>Two notes:</p>
<ol>
<li>The first time you run a script, there will be a pop-up dialog box asking you to accept the security allowances. It's a safety net to make sure you know you're running the code that's written in Apps Script, and to examine it if you didn't write it. You'll need to click through those and accept the risk to allow it to run.</li>
<li>If you need to move a button after assigning the script, you may get frustrated when clicking it doesn't bring up the three dots for the menu and only runs the script. To get around this and allow for movement and the three dot menu, right click the button.</li>
</ol>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>I hope this has been helpful for you!</p>
<p>Please subscribe to <a target="_blank" href="https://www.youtube.com/@eamonncottrell?sub_confirmation=1">my YouTube channel here</a> for more content like this.</p>
<p>Have a great one!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Google Apps Script – Build a Paint By Numbers Spreadsheet ]]>
                </title>
                <description>
                    <![CDATA[ Spreadsheets are great for financial modeling, but they're also capable of displaying pixel art.  In this Apps Script tutorial, we'll build a paint by numbers spreadsheet using conditional formatting and a script that "paints" a blank spreadsheet. Yo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-google-apps-script-build-a-paint-by-numbers-spreadsheet/</link>
                <guid isPermaLink="false">66b8de180cedc1f2a4f7067f</guid>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Mon, 24 Apr 2023 21:12:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/04/Google-Apps-Script-Paint-by-Numbers-Spreadsheet-final.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Spreadsheets are great for financial modeling, but they're also capable of displaying pixel art. </p>
<p>In this Apps Script tutorial, we'll build a paint by numbers spreadsheet using conditional formatting and a script that "paints" a blank spreadsheet.</p>
<p>You'll learn how to:</p>
<ol>
<li>Import data</li>
<li>Apply proper data visualization formatting to it</li>
<li>Code a couple of Apps Script functions to make it interactive. </li>
</ol>
<p>Let's do it 🎨</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/giphy.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Tenacious D rocking out</em></p>
<h2 id="heading-video-walkthrough">Video Walkthrough</h2>
<p>Yes, I've got a full walkthrough for you. Pull this up as you read to reference and follow along 👇</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/zNqcLWGJlvQ" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>Demo sheet with Pikachu: <a target="_blank" href="https://docs.google.com/spreadsheets/d/1Zu0B0dE_N4UrgAAzlWKqbpmz2TL_qr9GYWS451O7UL0/edit#gid=0">https://docs.google.com/spreadsheets/d/1Zu0B0dE_N4UrgAAzlWKqbpmz2TL_qr9GYWS451O7UL0/edit#gid=0</a></p>
<p>Demo sheet with Volcano: <a target="_blank" href="https://docs.google.com/spreadsheets/d/11lOVseXtpB6xWxhrmZr1LfImI75TBDbof6mkFzz0ck4/edit#gid=0">https://docs.google.com/spreadsheets/d/11lOVseXtpB6xWxhrmZr1LfImI75TBDbof6mkFzz0ck4/edit#gid=0</a></p>
<p>You can make an editable copy of either of these by selecting <code>File -&gt; Make a copy</code>.</p>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Everything we're doing today is built on some simple formatting. We are going to have cells turn certain colors based on the number inside them.</p>
<p>See the pic below where all the blue cells have the number 15 in them. By setting the color of the font and the background to blue, we can create the effect of the cells being a solid color.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-207.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of Pikachu pixel artwork</em></p>
<p>We can make our own number grid, but there are a ton available. I print these for my kids to color, and we can import them to our spreadsheet with a couple clicks.</p>
<p><a target="_blank" href="https://www.coloringsquared.com/worksheet/volcano-numbers-coloring-page/">Here's the volcano grid</a> I used in the walkthrough video.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-226.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of volcano color by number grid</em></p>
<p>When I first recorded the walkthrough video, I was unable to copy and paste from the PDF. When I did, it pasted every number in one cell. </p>
<p>Instead, by opening in Microsoft Word first and then copying and pasting from there, I was able to bring the number grid into the Google Sheet.</p>
<p>Since then, I've also found that when copying and pasting from the PDF, sometimes it will bring the numbers in to the first cell in each row:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-209.png" alt="Image" width="600" height="400" loading="lazy">
<em>picture of Google Sheets number grid</em></p>
<p>This doesn't work, either, because we need each number in its own cell. But, by applying the <code>=SPLIT()</code> function, we can achieve this easily. </p>
<p><code>=SPLIT(A1," ")</code> will split each value in the cell by the empty spaces. So, all the numbers are pulled out into their own cells in the row.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-210.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of Split function in Google Sheets</em></p>
<p>Once all the numbers are in individual cells, apply some formatting to the spreadsheet so that every cell is a square. Resize as big or as small as you'd like. I chose a row and column height of 30px.</p>
<p>To do this, select the column headers by clicking and dragging from A all the way to the end of the columns. Right click anywhere in the range, and select <code>Resize columns</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-211.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of resizing columns in Google Sheets</em></p>
<p>Do the same for the rows, specifying 30px for each.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-212.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of resizing rows in Google Sheets</em></p>
<p>Turn off the gridlines by selecting <code>View -&gt; Show -&gt; Gridlines</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-213.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of View options in Google Sheets</em></p>
<h2 id="heading-conditional-formatting">Conditional Formatting</h2>
<p>Select the entire range where all the numbers are and then click <code>Format -&gt; Conditional formatting</code>. </p>
<p>Click <code>Add new rule</code> and under Format rules, select <code>Is equal to</code> from the dropdown menu.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/equal.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of conditional formatting in Google Sheets</em></p>
<p>Under Formatting style, follow the color key from the coloring page you selected and adjust the font and background colors according to each number. </p>
<p>In our example, all the number 10s need to be blue, so we enter 10 and then have the same blue for both background and font colors:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-215.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of color options in Google Sheets</em></p>
<h3 id="heading-important-note">⭐Important Note</h3>
<p>Because of the script we're writing and how we're triggering it, you need to alter the HEX code for one of these two numbers. If they are the exact same, it will cause an error later. </p>
<p>So, first enter the same color for both, then open one and select the plus icon in the custom color swatch.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/custom.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of custom colors in Google Sheets</em></p>
<p>Manually change one value in the HEX code by one digit. In the example, I changed it from <code>#0b5294</code> to <code>#0b5394</code>. Visually, it will still look the same. If this is confusing, be sure to check out the <a target="_blank" href="https://youtu.be/zNqcLWGJlvQ?t=159">walkthrough video at 02:39</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-216.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of custom colors in Google Sheets</em></p>
<p>Do this for each color in your piece of art, and you'll have a gorgeous piece of pixel artwork in your spreadsheet. This alone is rewarding! 😀</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-217.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of volcano pixel art in Google Sheets</em></p>
<h2 id="heading-apps-script-setup">Apps Script Setup</h2>
<p>Name the sheet that we're on by double clicking <code>Sheet1</code> at the bottom. We'll call it "art". Then make a new sheet by clicking the plus icon on the bottom bar. Name it "canvas".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-219.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of sheet names in Google Sheets</em></p>
<p>Setup the canvas in the same way we did at the beginning, only without the conditional formatting. Make everything the same size, remove the gridlines, and add a border around the <code>B2:T21</code> range that will serve as a frame.</p>
<p>Now, we need to make buttons to toggle in each cell. In Google Sheets, the way to do this is by adding checkboxes to all the cells. Checkboxes will hold either a <code>true</code> or <code>false</code> value, and when we click them, they'll change back and forth.</p>
<p>Select our full range again, and select <code>Data -&gt; Data validation</code>. Change the criteria to <code>Checkbox</code> and under Advanced options select <code>Reject the input</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-227.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of data validation rules in Google Sheets</em></p>
<p>This will give our script something to be triggered by.</p>
<p>Format these checkboxes in the same way we did our conditional formatting: make the background white: <code>#ffffff</code>, and the font color just slightly different: <code>#fffeff</code>. Then, give them a huge font size, like 200. This will allow for us to click anywhere in the cell and not run the risk of clicking just outside the border of the box itself.</p>
<p>Now, let's open our code editor by selecting <code>Extensions -&gt; Apps Script</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-221.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of Extensions menu in Google Sheets</em></p>
<h2 id="heading-script-logic">Script Logic</h2>
<p>We need to copy and paste the formatting of individual cells every time we click the blank cells in our canvas.</p>
<p>To do this, we'll use an <code>onEdit(e)</code> trigger method built into Apps Script.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onEdit</span>(<span class="hljs-params">e</span>) </span>{
  <span class="hljs-comment">//get current sheet</span>
  <span class="hljs-keyword">var</span> sheet = SpreadsheetApp.getActiveSheet();

  <span class="hljs-comment">// if we're not on the art sheet...</span>
  <span class="hljs-keyword">if</span>(sheet.getName() != <span class="hljs-string">"art"</span>){
</code></pre>
<p>First, we'll grab the active sheet as a variable. Then, making sure we're not on the "art" sheet, we'll go through the steps to grab and paste the formatting we need...</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// get the active cell and it's row, column reference</span>
<span class="hljs-keyword">var</span> activeRange = sheet.getActiveCell();
<span class="hljs-keyword">var</span> row = activeRange.getRow();
<span class="hljs-keyword">var</span> column = activeRange.getColumn();
</code></pre>
<p>Within our conditional if statement, we'll make three more variables so that we can grab the position of the cell we're in.</p>
<p>Then we need to go to our "art" sheet and grab the formatting from the corresponding cell.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> artRange = SpreadsheetApp.getActive().getSheetByName(<span class="hljs-string">"art"</span>).getRange(row,column);
<span class="hljs-comment">// get the background color from the same reference in art sheet</span>
<span class="hljs-keyword">var</span> backgroundColor = artRange.getBackground();
<span class="hljs-keyword">var</span> fontColor = artRange.getFontColor();
</code></pre>
<p>We'll make another three variables: one for the artRange which grabs the range from the "art" sheet using the <code>row</code> and <code>column</code> that we're on in the "canvas" sheet. And then two variables for the colors: one for background and one for font.</p>
<p>Now all we need to do is set the "canvas" sheet's cell to the colors we just grabbed. I've also chosen to make it toggle back to a blank white cell if it's already been colored. So we'll use another if statement to handle that:</p>
<pre><code class="lang-javascript">trueFalse = activeRange.getValue();
<span class="hljs-keyword">if</span>(trueFalse){
      <span class="hljs-comment">// set activeRange with that backgroundColor</span>
      activeRange.setBackground(backgroundColor);
      activeRange.setFontColor(fontColor);
    }
    <span class="hljs-keyword">else</span>{
      activeRange.setBackground(<span class="hljs-string">'#ffffff'</span>);
      activeRange.setFontColor(<span class="hljs-string">'#fffeff'</span>);
    }
</code></pre>
<p>First, we set a trueFalse variable equal to the activeRange's value. This is either <code>true</code> or <code>false</code> depending on the state of the checkbox.</p>
<p>If it's false (the checkbox isn't checked), then we set the background and font colors using the variables we grabbed from our "art" sheet.</p>
<p>Here's the full <code>onEdit(e)</code> code:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onEdit</span>(<span class="hljs-params">e</span>) </span>{
  <span class="hljs-comment">//get current sheet</span>
  <span class="hljs-keyword">var</span> sheet = SpreadsheetApp.getActiveSheet();

  <span class="hljs-comment">// if we're not on the art sheet...</span>
  <span class="hljs-keyword">if</span>(sheet.getName() != <span class="hljs-string">"art"</span>){

    <span class="hljs-comment">// get the active cell and it's row, column reference</span>
    <span class="hljs-keyword">var</span> activeRange = sheet.getActiveCell();
    <span class="hljs-keyword">var</span> row = activeRange.getRow();
    <span class="hljs-keyword">var</span> column = activeRange.getColumn();

    <span class="hljs-keyword">var</span> artRange = SpreadsheetApp.getActive().getSheetByName(<span class="hljs-string">"art"</span>).getRange(row,column);

    <span class="hljs-comment">// get the background color from the same reference in art sheet</span>
    <span class="hljs-keyword">var</span> backgroundColor = artRange.getBackground();
    <span class="hljs-keyword">var</span> fontColor = artRange.getFontColor();

    Logger.log(backgroundColor)
    Logger.log(fontColor)

    trueFalse = activeRange.getValue();

    <span class="hljs-keyword">if</span>(trueFalse){
      <span class="hljs-comment">// set activeRange with that backgroundColor</span>
      activeRange.setBackground(backgroundColor);
      activeRange.setFontColor(fontColor);
    }
    <span class="hljs-keyword">else</span>{
      activeRange.setBackground(<span class="hljs-string">'#ffffff'</span>);
      activeRange.setFontColor(<span class="hljs-string">'#fffeff'</span>);
    }
  }
}
</code></pre>
<h2 id="heading-reset-function">Reset Function</h2>
<p>As an added feature, we'll add an actual button to reset the canvas. To do this, we'll make a new function in our Apps Script code editor. </p>
<p>We'll grab the sheet and all the checkboxes as variables. To get the checkboxes, we'll use the <code>getRangebyName()</code> method on our 'canvasArt' range.</p>
<p>Then, Apps Script makes it pretty easy with built in methods. We set the value of all the checkboxes to <code>false</code>, the background color to <code>#ffffff</code>, and the font color to <code>#fffeff</code>.</p>
<p>Here's the full <code>reset()</code> code:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reset</span>(<span class="hljs-params"></span>)</span>{
  <span class="hljs-keyword">var</span> sheet = SpreadsheetApp.getActive();
  <span class="hljs-keyword">var</span> checkboxes = sheet.getRangeByName(<span class="hljs-string">'canvasArt'</span>);
  checkboxes.setValue(<span class="hljs-literal">false</span>);
  checkboxes.setBackground(<span class="hljs-string">"#ffffff"</span>);
  checkboxes.setFontColor(<span class="hljs-string">"#fffeff"</span>);
}
</code></pre>
<h2 id="heading-trigger-with-button">Trigger with Button</h2>
<p>To make a button in the spreadsheet, select <code>Insert -&gt; Drawing</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-222.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of Insert menu in Google Sheets</em></p>
<p>Select the rounded rectangle shape and drag it onto the grid.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-223.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of Shapes menu in Google Sheets</em></p>
<p>Double click into the shape to write "CLEAR". Adjust the font and colors as you see fit.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-224.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of button drawing in Google Sheets</em></p>
<p>Click Save and Close and then drag it to re-size and reposition onto your sheet at the bottom of the canvas.</p>
<p>Once you've positioned it, click the three circles in the top right, select <code>Assign script</code>, and type in the name of the script you'd like it to trigger (in our case, <code>reset</code>). </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/image-225.png" alt="Image" width="600" height="400" loading="lazy">
<em>Picture of assigning script to button in Google Sheets</em></p>
<p>Now, when you click this button, that script will run and clear the whole art canvas.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I hope this has been helpful for you! I had a great time making this, and I have more game-type spreadsheet content coming soon.</p>
<p>Come follow me on <a target="_blank" href="https://www.youtube.com/@eamonncottrell">YouTube</a>, and say hey over on <a target="_blank" href="https://www.linkedin.com/in/eamonncottrell/">LinkedIn</a>.</p>
<p>Have a great one! 👋</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Google Apps Script Code to Clear Data Ranges in Google Sheets ]]>
                </title>
                <description>
                    <![CDATA[ Google Apps Script allows you to write code in your spreadsheet. It functions like Visual Basic for Applications (VBA) does in Excel. They're both incredibly useful for executing more complicated actions or for automating repetitive tasks. In this ar... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/use-apps-script-to-clear-data-in-google-sheets/</link>
                <guid isPermaLink="false">66b8de27f805ffd579552e9c</guid>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Wed, 29 Mar 2023 19:55:52 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/03/fcc-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Google Apps Script allows you to write code in your spreadsheet. It functions like Visual Basic for Applications (VBA) does in Excel. They're both incredibly useful for executing more complicated actions or for automating repetitive tasks.</p>
<p>In this article, I'll give you a brief overview of Google Apps Script by showing you how to do something very simple: clearing a bunch of data in your spreadsheet.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/homer-simple.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of Homer Simpson saying, of course, it's so simple.</em></p>
<h2 id="heading-what-is-apps-script">What is Apps Script?</h2>
<p>Good question. Though you may be familiar with the square grid that makes up the interface of most spreadsheets, you may not know that behind the scenes, there is a powerful cloud-based JavaScript platform. </p>
<p>Apps Script lets you write custom functions, automations, add-ons and more. The screenshot below is from <a target="_blank" href="https://developers.google.com/apps-script#:~:text=Apps%20Script%20is%20a%20cloud,automate%20tasks%20across%20Google%20products.">Google Workspace Docs</a>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/image-213.png" alt="Image" width="600" height="400" loading="lazy">
<em>Google Workspace docs</em></p>
<h2 id="heading-open-apps-script">Open Apps Script</h2>
<p><a target="_blank" href="https://docs.google.com/spreadsheets/d/1wmiSt2KnwTOX7wQZZaLnHXYIN9NBk_B487JJ4EHbnlc/edit?usp=sharing">Here is the link to the demo sheet</a> if you'd like to follow along and/or make a copy.</p>
<p>You can also build one with me by opening a new sheet: <a target="_blank" href="https://sheets.new">https://sheets.new</a>.</p>
<p>And here's a video walkthrough detailing everything we're about to do:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/PVoa7dp6pr0" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>For our example, we just need some data to clear. In my business we use this apps script to clear out templates once a month that we use weekly for inventory purposes.</p>
<p>We'll build a miniature version of this to practice with, but the principles we go over can be used on much larger scales where apps script can save a ton of time and effort.</p>
<p>Here's what our sheet will look like: four weeks worth of inventory and order amounts.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/image-209.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of order form in Google Sheets</em></p>
<p>We want an automated way to clear the data in the inventory and order columns.</p>
<p>Yes, you could simply select that range and hit delete or backspace. But if your spreadsheet is larger and more nuanced, there may be tens or hundreds of ranges to select.</p>
<p>And this is where Apps Script comes in super handy.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/assist.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of Bill Murray</em></p>
<p>To open Apps Script, go to <code>Extensions -&gt; Apps Script</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/open.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of Extensions Toolbar</em></p>
<p>🤔Now, what's better than working in a dapper spreadsheet? </p>
<p>Being able to write custom code for it! </p>
<p>From here, we're greeted with our old friend, the code editor. </p>
<p>We'll give this project a name of <code>Clear Range</code> at the top, and then making sure the code editor is selected on the left side-bar, we'll begin writing our first function, also named <code>clearRange</code>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/editor.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We can save our progress as we write by either pressing <code>CTRL + S</code> or clicking the save disk in the toolbar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/save.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of apps script code editor</em></p>
<p>It wouldn't be coding with a console to debug ourselves, and sure enough, there is a <code>Logger.log()</code> method built in to Apps Script. Let's write our first Apps Script...script 😀 to log the message, <code>Hello console! :)</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">clearRange</span>(<span class="hljs-params"></span>) </span>{
  Logger.log(<span class="hljs-string">'Hello console! :)'</span>)
}
</code></pre>
<p>And here's what you should see when you hit the Run button in the toolbar: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/console.log.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of Logger.log() in Apps Script</em></p>
<p>Okay, so things are indeed working. Let's get to business with our actual script...</p>
<h2 id="heading-how-to-add-a-custom-menu">How to Add a Custom Menu</h2>
<p>We can execute our code within the code editor by clicking <code>Run</code>, but we don't want to open this up every time we use the code. So, let's add a custom menu to the toolbar inside our spreadsheet.</p>
<p>We've got two options here: creating a custom menu or creating an addon menu. The custom menu will function as a dropdown menu directly on the Toolbar to the right of the <code>Help</code> menu. The addon menu will display within the regular <code>Extensions</code> dropdown menu.</p>
<p>Let's add the custom menu. It can be helpful to copy methods from the <a target="_blank" href="https://developers.google.com/apps-script/reference/base/ui#createMenu(String)">Apps Script Docs</a> and then fit them to our purposes. That's what I've done below from the createMenu() method here:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">onOpen</span>(<span class="hljs-params">e</span>) </span>{
  SpreadsheetApp.getUi()
      .createMenu(<span class="hljs-string">'Clear Entries'</span>)
      .addItem(<span class="hljs-string">'Clear \'em all!'</span>, <span class="hljs-string">'clearRange'</span>)
      .addSeparator()
      .addToUi();
}
</code></pre>
<p>This creates a menu when the spreadsheet is opened named Clear Entries. Within the menu, there's a clickable item named Clear 'em all! that calls the <code>clearRange</code> function.</p>
<h2 id="heading-how-to-add-the-function">How to Add the Function</h2>
<p>Now, we need the function to do more than log a message to the console.</p>
<p>We can clear the contents of a range or ranges in a few ways. We'll do it here by naming the ranges that we will want to clear. This will prevent us from having to hard code cell references in case those change down the road as we modify the spreadsheet.</p>
<p>I've named each week's inventory and order columns as named ranges:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/image-255.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of named ranges</em></p>
<p>Then, in our function we make a variable to hold an array of all the named ranges, iterate over each one in a <code>forEach()</code> loop, and clear each range's contents using the <code>clearContent()</code> method.</p>
<p>Very simple, and only it takes a few lines of code:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">clearRange</span>(<span class="hljs-params"></span>)</span>{
  <span class="hljs-keyword">var</span> ss = SpreadsheetApp.getActive();
  <span class="hljs-keyword">var</span> ranges = ss.getNamedRanges();
  ranges.forEach(<span class="hljs-function"><span class="hljs-params">range</span> =&gt;</span> range.getRange().clearContent());
}
</code></pre>
<p>In the <a target="_blank" href="https://docs.google.com/spreadsheets/d/1wmiSt2KnwTOX7wQZZaLnHXYIN9NBk_B487JJ4EHbnlc/edit?usp=sharing">demo Google Sheet</a>, I included the alternative way to write this with A1 notation if you needed to not use the named ranges.</p>
<h2 id="heading-how-to-execute-the-function">How to Execute the Function</h2>
<p>When you first execute a function that requires access to your data, you'll be greeted by this Authorization screen.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/image-251.png" alt="Image" width="600" height="400" loading="lazy">
<em>authorization screenshot</em></p>
<p>Choose your Google account, click Advanced when you see the "Google hasn't verified this app" screen:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/verify.png" alt="Image" width="600" height="400" loading="lazy">
<em>Google verification screen</em></p>
<p>It will describe what the function trying to execute is going to have permission to access. Click Allow:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/sign.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot allowing permission to use our new function</em></p>
<p>Once you've allowed the permissions, you'll need to execute the function again for it to actually run this time. You'll see this running script dialog pop up at the top:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/image-256.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of running script dialog box</em></p>
<p>And, voilà!</p>
<p>We've cleared our range :)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/03/clear-script_1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of the spreadsheet range clearing</em></p>
<h2 id="heading-summary">Summary</h2>
<p>I hope this has been a useful walkthrough for you, and that you learned something new. I use a version of this very script in the real world on a regular basis. It's an example of a pretty small thing that saves a ton of time and errors.</p>
<p>Come say hey and subscribe to my <a target="_blank" href="https://www.youtube.com/@eamonncottrell">YouTube channel over here</a>. I create walkthroughs and tech content weekly.</p>
<p>Have a great one! 👋</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Give your workday super-powers with Google Apps Script ]]>
                </title>
                <description>
                    <![CDATA[ By Peter Gleeson The best learn-to-code projects are often those which solve a real world problem. These projects can provide that extra dose of motivation so essential to finishing any project. They encourage you to actively explore and discover new... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/automate-your-workday-with-google-app-script/</link>
                <guid isPermaLink="false">66d4608d37bd2215d1e245a2</guid>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ work life balance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 08 Jun 2019 17:59:09 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9ca228740569d1a4ca52e9.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Peter Gleeson</p>
<p>The best learn-to-code projects are often those which solve a real world problem.</p>
<p>These projects can provide that extra dose of motivation so essential to finishing any project. They encourage you to actively explore and discover new concepts, rather than imitate examples you've seen before.</p>
<p>There's also something that bit extra satisfying about solving a problem you face day-to-day.</p>
<p>An easy way to start is with <a target="_blank" href="https://developers.google.com/apps-script/">Google Apps Script</a>.</p>
<p>It is a scripting language for a range of Google applications. The language itself is in fact JavaScript.</p>
<p>What Google Apps Script provides are libraries and classes that allow you to work with objects such as spreadsheets, emails, calendars, slides, and more.</p>
<p>If you want to dive right in, the documentation is available <a target="_blank" href="https://developers.google.com/apps-script/reference/">here</a>.</p>
<p>Here are three examples that will show how to get started with Google Apps Script. Hopefully it will give you some ideas for your own projects!</p>
<h3 id="heading-launching-google-apps-script">Launching Google Apps Script</h3>
<p>You will need a Google account to start developing Apps Script projects. To start a new project, simply navigate to <a target="_blank" href="https://script.google.com/home">script.google.com/home</a> and click 'New Script'.</p>
<p>You will be taken to an in-browser IDE that looks something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/Screenshot-2019-06-08-at-18.40.56.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Give your project a name by changing the title in the top left corner.</p>
<p>Note that every time you require Apps Script to access different Google applications, you will need to give the necessary permissions.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/Screenshot-2019-06-08-at-17.34.16.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This might look a bit daunting, but if you are running your own project carefully, there will be no problem. Click "Advanced" and allow your project permission to run.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/Screenshot-2019-06-08-at-17.34.42.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Let's take a look at some examples.</p>
<h3 id="heading-calculate-your-income-tax">Calculate your income tax</h3>
<p>This simple example will show you how to extend <a target="_blank" href="https://www.google.com/sheets/about/">Google Sheets</a> by adding your own custom formulae. In this example, the formula will be used to calculate UK income tax.</p>
<p>In the UK, <a target="_blank" href="https://www.gov.uk/income-tax-rates">different income tax rates</a> are applied to different earnings categories. Therefore, the amount of income tax owed varies depending on the income.</p>
<p>First, create a new <a target="_blank" href="https://docs.google.com/spreadsheets/u/0/">Google Sheet</a>. Then, from the menu ribbon, select Tools &gt; Script editor. You will be taken to the Apps Script IDE.</p>
<p>The code block below uses a <a target="_blank" href="https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/basic-javascript/selecting-from-many-options-with-switch-statements/">switch statement</a> to calculate the right amount of tax for a numerical argument <code>income</code>. If you are familiar with JavaScript, you will recognise the syntax.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TAX</span>(<span class="hljs-params">income</span>) </span>{

  <span class="hljs-keyword">switch</span> (<span class="hljs-literal">true</span>) {

    <span class="hljs-keyword">case</span> income &lt;= <span class="hljs-number">12500</span>:
      <span class="hljs-keyword">var</span> tax = <span class="hljs-number">0</span>;
      <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">case</span> income &lt;= <span class="hljs-number">50000</span>:
      <span class="hljs-keyword">var</span> tax = <span class="hljs-number">0.2</span> * (income - <span class="hljs-number">12500</span>);
      <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">case</span> income &lt;= <span class="hljs-number">150000</span>:
      <span class="hljs-keyword">var</span> tax = <span class="hljs-number">7500</span> + (<span class="hljs-number">0.4</span> * (income - <span class="hljs-number">50000</span>));
      <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">case</span> income &gt; <span class="hljs-number">150000</span>:
      <span class="hljs-keyword">var</span> tax = <span class="hljs-number">47500</span> + (<span class="hljs-number">0.45</span> * (income - <span class="hljs-number">150000</span>));
      <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">var</span> tax = <span class="hljs-string">"ERROR"</span>;
  }

  <span class="hljs-keyword">return</span> tax;

}
</code></pre>
<p>Save your project if you haven't already.</p>
<p>Now, back in the sheet, enter your chosen salary in e.g., cell A1. You can now call the new formula with  <code>=TAX(A1)</code>.</p>
<p>You could write a similar function to calculate <a target="_blank" href="https://www.which.co.uk/money/tax/national-insurance/national-insurance-rates-ajg9u9p48f2f#headline_3">UK National Insurance contributions</a>.</p>
<p>What other Sheets functions could you write?</p>
<h3 id="heading-remember-to-check-your-emails">Remember to check your emails</h3>
<p>It can be difficult to make time to respond to important emails. This example will bring together <a target="_blank" href="https://www.google.com/gmail/">Gmail</a> and <a target="_blank" href="https://calendar.google.com/calendar/r">Google Calendar</a> in one short application.</p>
<p>The idea is simple. You provide a list of important email contacts and/or keywords. The application checks your inbox every six hours. If it finds any new emails from these contacts (with any of the keywords in the subject line), it creates a calendar event reminding you to reply later in the day.</p>
<p>You can create a new project from <a target="_blank" href="https://script.google.com/home">script.google.com/home</a>.</p>
<p>Check out the code below:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reminder</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">/* create list of senders and subject keywords */</span>
  senders = [<span class="hljs-string">"freecodecamp"</span>, <span class="hljs-string">"codecademy"</span>, <span class="hljs-string">"meetup"</span>];
  subjects = [<span class="hljs-string">"javascript"</span>, <span class="hljs-string">"python"</span>, <span class="hljs-string">"data science"</span>];

  <span class="hljs-comment">/* build the search query */</span>
  <span class="hljs-keyword">var</span> searchString = <span class="hljs-string">"is:unread newer_than:1d from: { "</span> +
    senders.join(<span class="hljs-string">" "</span>) + <span class="hljs-string">"} subject: { "</span> + 
    subjects.join(<span class="hljs-string">" "</span>) + <span class="hljs-string">" }"</span>

  <span class="hljs-comment">/* retrieve any matching messages */</span>
  threads = GmailApp.search(searchString);

  <span class="hljs-comment">/* if there are any results, create a calendar event */</span>
  <span class="hljs-keyword">if</span> (threads.length &gt; <span class="hljs-number">0</span>) {
    <span class="hljs-keyword">var</span> event = CalendarApp.getDefaultCalendar();
    event.createEventFromDescription(<span class="hljs-string">'Review emails 6pm today'</span>);
  }

}
</code></pre>
<p>To run this function at regular intervals, you can set up a trigger. From the menu ribbon, choose Edit &gt; Current project's triggers.</p>
<p>This will take you to a new tab where you can add a new trigger for the current project. Click 'Add new trigger' and choose the settings you wish to use.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/Screenshot-2019-06-08-at-16.30.47.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, your script will run every 6 hours, and create a calendar event if you have any emails you need to review.</p>
<p>A useful extension might be to create a spreadsheet or Google Form that lets you add contacts and keywords easily.</p>
<p>How else could you integrate your inbox and your calendar?</p>
<h3 id="heading-slides-update">Slides update</h3>
<p>Keeping presentations and slide decks up-to-date can be a tedious task. Luckily, you can use Google Apps Script to automate the process.</p>
<p>For this example, we'll use a fictional mobile app. The aim is to produce a slide deck with up-to-date metrics such as app downloads, active users, and revenue.</p>
<p>The trick will be to replace a number of <code>&lt;tags&gt;</code> in the deck with data contained in a Google Sheet.</p>
<p>In Slides, create a new presentation. Give it a name such as "App update template".</p>
<p>Create a new slide. Give it a title such as "Key metrics".</p>
<p>In a text box, add some content such as below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/Screenshot-2019-06-08-at-18.55.18.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Notice the tags included in each line. These will be replaced by up-to-date figures each time the script is run.</p>
<p>Next, create a new Sheet and add some data to use in the slide deck. In one column, refer to the tags in the slide deck. In the other, add the latest data.</p>
<p>In a real-life example, this would be calculated from raw data elsewhere in the spreadsheet. The raw data could come from Google Analytics, or be exported from a data warehouse, or from some other source.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/Screenshot-2019-06-08-at-18.53.00.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Back in Slides, select Tools &gt; Script Editor from the menu ribbon. This will open a new Apps Script project. </p>
<p>Now you can start writing some code. The function takes two file ids as arguments - one for the Slides template, one for the Sheet. The file id is the string of letters and numbers you can find in the file's URL.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateSlides</span>(<span class="hljs-params">templateId, sheetId</span>) </span>{

  <span class="hljs-comment">/* Make a latest copy of the slide deck template */</span>
  <span class="hljs-keyword">var</span> template = DriveApp.getFileById(templateId);
  <span class="hljs-keyword">var</span> today = <span class="hljs-built_in">Date</span>();
  <span class="hljs-keyword">var</span> copyName = <span class="hljs-string">"App update "</span> + today;
  <span class="hljs-keyword">var</span> templateCopy = template.makeCopy(copyName);

  <span class="hljs-comment">/* Open spreadsheet and slides by their id*/</span>
  <span class="hljs-keyword">var</span> sheet = SpreadsheetApp.openById(sheetId);
  <span class="hljs-keyword">var</span> slides = SlidesApp.openById(templateCopy.getId());

  <span class="hljs-comment">/* Get the data from the sheet */</span>
  <span class="hljs-keyword">var</span> data = sheet.getRange(<span class="hljs-string">"A1:B5"</span>).getValues();

  <span class="hljs-comment">/* replace all the tags in the deck with their latest values */</span>
  <span class="hljs-keyword">for</span>(<span class="hljs-keyword">var</span> i=<span class="hljs-number">0</span>; i &lt;data.length; i++){
    <span class="hljs-keyword">var</span> tag = <span class="hljs-string">"&lt;"</span>+data[i][<span class="hljs-number">0</span>]+<span class="hljs-string">"&gt;"</span>;
    <span class="hljs-keyword">var</span> value = data[i][<span class="hljs-number">1</span>].toString();

    slides.replaceAllText(tag, value);

  }
}
</code></pre>
<p>If you run this script, a new presentation will be created with the latest data in place of each of the tags.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/06/Screenshot-2019-06-08-at-18.56.23.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You could schedule this script to run at regular intervals, such as at the end of each month. If you wanted to develop the idea even further, you could use Apps Script to automatically email the new deck to a list of contacts.</p>
<h3 id="heading-over-to-you">Over to you</h3>
<p>Google Apps Script is a great way to start writing real JavaScript in a way which is immediately practical. Hopefully you found these three examples helpful. </p>
<p>Perhaps this introduction has given you ideas for projects you could develop?</p>
<p>Remember, coding is a powerful tool - don't do anything with Apps Script you wouldn't do manually. Best not to erase your entire inbox or overwrite an important file with memes.</p>
<p>Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Hack G Suite using Apps Scripts — in under an hour. ]]>
                </title>
                <description>
                    <![CDATA[ By Supriya Shashivasan Have you heard of Google Apps Script? I was introduced to it for the first time at a Google Developer Group meetup held in Bangalore. Apps Script helps you use Google’s G Suite products, by running a script similar to JavaScrip... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-i-built-a-hack-using-apps-scripts-in-under-an-hour-8442a1495dce/</link>
                <guid isPermaLink="false">66c34d4d4f7405e6476b01e2</guid>
                
                    <category>
                        <![CDATA[ Google ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google apps script ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 21 May 2018 05:42:15 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/0*nVQ-TOygSLHF9ucy" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Supriya Shashivasan</p>
<p>Have you heard of <a target="_blank" href="https://developers.google.com/apps-script/">Google Apps Script</a>? I was introduced to it for the first time at a <a target="_blank" href="https://www.meetup.com/GDGBangalore/">Google Developer Group</a> meetup held in Bangalore.</p>
<p>Apps Script helps you use Google’s G Suite products, by running a script similar to JavaScript. With just a few lines of code, users can get things done at a click of a button, which would otherwise take much more time.</p>
<p>Google Apps Scripts is very easy to pickup, and helps you to build complex systems by making use of G Suite. Users can publish web apps, and build custom functions for Google Slides, Sheets and Forms.</p>
<p>In this article, I will walk you through building a small app that uses Google Sheets, Google Slides, and Google Translate.</p>
<p>I built this app for travellers. When we visit foreign countries, communication becomes a problem due to language barriers. People often take flash cards with them to help communicate with locals.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/NNMmCE6qcHtQ1kn3fi6AGLQI-UK81XER9XUe" alt="Image" width="800" height="1077" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@sonereker?utm_source=medium&amp;utm_medium=referral" rel="noopener" target="_blank" title=""&gt;Soner Eker on &lt;a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral" rel="noopener" target="<em>blank" title=")</em></p>
<p>We are going to build exactly this. Questions and phrases are put up in Google Sheets. Then, a script translates the lines and writes them on to Google Slides. There!! Translated flash cards are ready to use.</p>
<p>Now, <em>READY.SET.CODE</em></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/wnoNxBiBIyjAcxeQAoR85BqnqU3TAdZH4egu" alt="Image" width="500" height="281" loading="lazy"></p>
<h3 id="heading-storing-data">Storing data</h3>
<p>Data here are sentences/words you want to translate. They are stored in the rows of the Google sheet.</p>
<p>So the structure of the spreadsheet will be:</p>
<ul>
<li>Each row will be filled with sentences that you want translated.</li>
<li>The second column will hold the value to tell us the language the sentences must be translated to. The value here is the <a target="_blank" href="https://ctrlq.org/code/19899-google-translate-languages">Google translate language code</a>. If I want the sentences translated in Spanish, the code will be “es”.</li>
</ul>
<p><img src="https://cdn-media-1.freecodecamp.org/images/-CaL-t-tLtaweljpuILtIsX4xcV04CkZijiO" alt="Image" width="800" height="206" loading="lazy">
<em>Filled Google Sheet</em></p>
<h3 id="heading-accessing-script-editor">Accessing script editor</h3>
<p>The script to complete the desired task is written in the Script Editor. To access this, go to <strong>Tools &gt; Script Edito</strong>r. Another way to access the Script Editor is to visit t<a target="_blank" href="https://script.google.com/home">he App Scripts dashbo</a>ard and create a new Apps script. All the scripts users write can be managed by this dashboard.</p>
<p>The script editor contains an empty file called <strong>Code.gs</strong>. We will write the code here in just one script.</p>
<h3 id="heading-main-function">Main function</h3>
<p>We write a main function <code>sheetToSlide()</code> in which the active sheet and slide are initialized. Another function <code>translate()</code> is called in the main function. It is here that the actual logic takes place.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sheetToSlide</span>(<span class="hljs-params"></span>) </span>{  <span class="hljs-keyword">var</span> sheet =   SpreadsheetApp.getActiveSheet();  <span class="hljs-keyword">var</span> slide = SlidesApp.create(<span class="hljs-string">'TranslateApp'</span>);  <span class="hljs-keyword">var</span> data = sheet.getDataRange().getValues();  <span class="hljs-keyword">var</span> lan= data[<span class="hljs-number">0</span>][<span class="hljs-number">1</span>];  Logger.log(lan);  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i=<span class="hljs-number">0</span>; i&lt;data.length; i++){     translate(i,data[i][<span class="hljs-number">0</span>],lan,slide);  }}
</code></pre><p>In the variable <code>data</code>, the contents of the spreadsheet are stored as a multi-dimensional array. These values can be accessed by <code>data[Row][Column]</code>.</p>
<p>They are passed on to the <code>translate</code> function for further processing, along with the <code>slide</code> variable and <code>language</code> in which translation is required.</p>
<p><code>Logger</code> is a class used to write text to the logging console. This helps a lot in the process of developing a code. The output of the code can be printed on to the debugging logs. To have a look at the logs go to <strong>View &gt; L</strong>ogs in the script editor window.</p>
<h3 id="heading-translate-function">Translate function</h3>
<p>In this function, new slides are added to the presentation that contain both the original and the translated sentences. Each sentence is inserted in a new slide in a text box.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">translate</span>(<span class="hljs-params">num,data,language,slide</span>)</span>{  <span class="hljs-keyword">var</span> translate_lang = LanguageApp.translate(data, <span class="hljs-string">'en'</span>, language);  <span class="hljs-keyword">var</span> card= slide.insertSlide(num);  <span class="hljs-keyword">var</span> shapeEnglish = card.insertShape(SlidesApp.ShapeType.TEXT_BOX, <span class="hljs-number">150</span>,<span class="hljs-number">100</span>,<span class="hljs-number">300</span>,<span class="hljs-number">60</span>);  <span class="hljs-keyword">var</span> textEnglish = shapeEnglish.getText();    textEnglish.setText(data);  textEnglish.getTextStyle().setBold(<span class="hljs-literal">true</span>);  card.insertLine(SlidesApp.LineCategory.STRAIGHT, <span class="hljs-number">200</span>,<span class="hljs-number">175</span>,<span class="hljs-number">300</span>,<span class="hljs-number">175</span>)      <span class="hljs-keyword">var</span> shapeTranslated = card.insertShape(SlidesApp.ShapeType.TEXT_BOX, <span class="hljs-number">150</span>,<span class="hljs-number">200</span>,<span class="hljs-number">300</span>,<span class="hljs-number">60</span>);  <span class="hljs-keyword">var</span> textTranslated = shapeTranslated.getText();  textTranslated.setText(translate_lang);  textTranslated.getTextStyle().setBold(<span class="hljs-literal">true</span>);  }
</code></pre><p>The sentence obtained is first translated using Google Translate which is a part of G Suite.</p>
<p>A new slide is inserted to hold the sentences. In the slide a text box is placed at a particular position. You can alter it by going through the docs <a target="_blank" href="https://developers.google.com/apps-script/reference/slides/">here</a>.</p>
<p>The text that must be displayed in the text box is done using the <code>getText()</code> and <code>setText()</code> methods. These are all properties of the Google Slides that you can manipulate and customize according to your wishes.</p>
<p>The design here is made very simple. A horizontal line is placed in the middle using <code>insertLine()</code> method to split the original and translated text. The properties and variables of all these methods used are given in detail in the docs provided by Google.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/bw5GvkzBf8xM-B66nc1XsQBoSYKyoT5AcSMM" alt="Image" width="800" height="385" loading="lazy"></p>
<p>To run a the script, click on the run button beside the time icon. The script will prompt up a window which will ask for permission to access the Sheets and Slides, simply allow it. Next, go to your drive and a new presentation will be ready which will have translated sentences in the cards.</p>
<p>That’s how helpful and easy Apps Script is. You can also fill up a Firebase Realtime database by just using Google Sheets. By just writing simple scripts in few lines, you can automate a lot of things and also build web apps that can be hosted.</p>
<p>App Scripts is really powerful and aims highly at letting users make their services automated. Next time you want to send an email to a bunch of people, try using App Scripts. Once you get the hang of it, you can build wondrous things, like Sheets to website, your own blog, Sheets to Slides and many more.</p>
<p>Hope this helped. Cheers!!</p>
<p>You can feel free to reach out to me!</p>
<p><strong>Twitter</strong>: <a target="_blank" href="https://twitter.com/@s_omeal">https://twitter.com/@s_omeal</a></p>
<p><strong>Paybackhub</strong> : paybackhub.com and <strong>Certhive</strong>: certhive.com</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
