<?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 sheets - 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 sheets - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 15 Jun 2026 23:30:26 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/google-sheets/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Learn Google Sheets – Course for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ Spreadsheets are important for modern data management, analysis, and organization. Whether you're tracking personal finances, managing a business, or conducting advanced data analysis, spreadsheets offer a versatile platform for countless tasks. Amon... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-google-sheets-course-for-beginners/</link>
                <guid isPermaLink="false">674f49768f5b31d71eee8f9a</guid>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Tue, 03 Dec 2024 18:09:58 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733249376070/af3b622d-ff58-446a-ae47-b4dcac71cffe.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Spreadsheets are important for modern data management, analysis, and organization. Whether you're tracking personal finances, managing a business, or conducting advanced data analysis, spreadsheets offer a versatile platform for countless tasks. Among the many tools available, <strong>Google Sheets</strong> stands out for its accessibility, powerful features, and seamless collaboration capabilities.</p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that will teach you all about Google Sheets from the ground up. This <strong>project-based course by Eamonn Cottrell</strong> is designed to take beginners through the fundamentals of Google Sheets and guide them toward mastering its core functionalities. By the end of the course, you’ll have hands-on experience with navigation, collaboration, data organization, advanced formulas, visualization tools, and even custom functions using Google Apps Script.</p>
<h3 id="heading-what-youll-learn-in-this-course">What You’ll Learn in This Course</h3>
<p>This course offers a comprehensive and structured approach to learning Google Sheets. Here’s a breakdown of the topics covered:</p>
<ol>
<li><p><strong>Navigation and Interface Basics</strong><br> Learn how to navigate Google Sheets' interface, use its toolbar effectively, and explore the features that make it user-friendly.</p>
</li>
<li><p><strong>Sharing and Collaboration</strong><br> Discover how to share spreadsheets, collaborate with others in real-time, and control permissions to ensure data integrity.</p>
</li>
<li><p><strong>Functions and Custom Formulas</strong><br> Master essential functions and create custom formulas to automate calculations and streamline your workflows.</p>
</li>
<li><p><strong>Data Validation and Formatting</strong><br> Use tools like conditional formatting, dropdown lists, and data validation to organize and highlight key information effectively.</p>
</li>
<li><p><strong>Advanced Features</strong><br> Combine functions to solve complex problems, filter data dynamically, and use slicers and search bars for better data management.</p>
</li>
<li><p><strong>Data Visualization</strong><br> Create and customize charts, including pie, column, and line charts, to present your data in visually compelling ways.</p>
</li>
<li><p><strong>Apps Script for Customization</strong><br> Take your skills to the next level by learning how to build custom functions and menus using Google Apps Script.</p>
</li>
</ol>
<h3 id="heading-why-learn-google-sheets">Why Learn Google Sheets?</h3>
<p>Google Sheets is not just for basic data entry; it’s a powerful tool for individuals and teams working with data. Its cloud-based nature enables seamless collaboration, while its integration with other Google Workspace tools and its extensive feature set make it a go-to solution for professionals across industries.</p>
<p>This course emphasizes practical, project-based learning to ensure you not only understand the concepts but also know how to apply them. By working on real-world tasks, you’ll build confidence and skills that are directly transferable to your personal or professional projects.</p>
<p>Whether you're entirely new to spreadsheets or looking to sharpen your skills, this course is an excellent opportunity to unlock the full potential of Google Sheets.</p>
<p>Watch the full course on the freeCodeCamp.org YouTube channel (2-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/cWGQncQxaHI" 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>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Work with Tables in Excel vs Google Sheets ]]>
                </title>
                <description>
                    <![CDATA[ Google Sheets recently released an all new feature: tables. Well, new is a bit of an overstatement. Excel has had proper tables for many, many years, and it's been a point of contention in the spreadsheet community. In this article, I'll break down w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/excel-vs-google-sheets-tables/</link>
                <guid isPermaLink="false">66b8dde70a89d796f29a16dd</guid>
                
                    <category>
                        <![CDATA[ excel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Tue, 02 Jul 2024 17:27:11 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/4.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Google Sheets recently released an all new feature: tables.</p>
<p>Well, <em>new</em> is a bit of an overstatement. Excel has had proper tables for many, many years, and it's been a point of contention in the spreadsheet community.</p>
<p>In this article, I'll break down what exactly tables are, why they're important, and then see how Google Sheet's new tables stack up against Microsoft Excel's.</p>
<p>Here is a video walkthrough of everything we'll cover:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/vBp5mveYZZ4" 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-whats-a-table">What's a Table?</h2>
<p>A table is a way of structuring and formatting data in a spreadsheet. It groups together rows and columns of data so that they can be more easily filtered, grouped, and analyzed.</p>
<p>Many people would look at the following bit of data and wrongly assume that it's already in a table.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-103.png" alt="Image" width="600" height="400" loading="lazy">
<em>Data in Excel</em></p>
<p>This is merely well organized rows and columns of data in Excel. Each column is a separate category of information, that is ids, names, emails, job titles, and salaries.</p>
<p>Each row represents one entry of that data. So, you'd put your id, name, email, job title and salary going left to right in a row.</p>
<p>Simple, yes?</p>
<p>A table contains all the same data, but by formatting it as a table we can unlock a whole lot of additional functionality.</p>
<p>The first of which is the appearance itself. When we create a table, Excel immediately colors our data with a dark header row and bands of alternating colors.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-104.png" alt="Image" width="600" height="400" loading="lazy">
<em>Table in Excel</em></p>
<p>Sheets does the same thing, as we can see below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-105.png" alt="Image" width="600" height="400" loading="lazy">
<em>Table in Google Sheets</em></p>
<p>So, a table is simply a way of managing and grouping data more easily. But it goes much further than just formatting, as we'll see.</p>
<h2 id="heading-why-are-tables-important">Why are Tables Important?</h2>
<p>Tables help reduce errors. When dealing with data, we are always making sure the data is clean and that we don't have errors in our formulas. </p>
<p>Tables help keep things orderly simply by being structured and formatted well. But as we'll see in the formula section in a moment, they also allow us to reduce errors in formulas by dynamically calculating things for us</p>
<h2 id="heading-how-to-create-a-table-in-excel-and-sheets">How to Create a Table in Excel and Sheets</h2>
<p>In Microsoft Excel, creating a table is as easy as clicking anywhere in the data range and pressing <code>CTRL + T</code>. Immediately, Excel will predict the data range for the table and ask you to confirm this.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-106.png" alt="Image" width="600" height="400" loading="lazy">
<em>Excel table data range</em></p>
<p>Alternatively, you can find the same create table option from the Insert Menu in the Ribbon at the top.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/table.png" alt="Image" width="600" height="400" loading="lazy">
<em>Excel insert menu</em></p>
<p>Over in Sheets, you'll need to either right click in a cell in the data range, or select the option from the Format menu to Convert to Table.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/sheets.png" alt="Image" width="600" height="400" loading="lazy">
<em>Convert to table options in Google Sheets</em></p>
<p>One caveat in Sheets: if you right click in a cell, you have to select the whole data range for it to convert to a table. Whereas, if you select Format - Convert to table from the menu, it is (like Excel) smart enough to predict the whole data range.</p>
<p>A small thing. But Excel takes the prize for ease of creation.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/right-click.png" alt="Image" width="600" height="400" loading="lazy">
<em>Convert to table in Google Sheets</em></p>
<p><strong>⭐Winner: EXCEL</strong></p>
<h2 id="heading-how-to-format-tables-in-excel-and-sheets">How to Format Tables in Excel and Sheets</h2>
<p>As we saw initially, some formatting is done as soon as we create a table.</p>
<p>From here, though, both programs allow for further customization.</p>
<p>In Sheets, we can select the dropdown in the top left next to the table name to access a few options immediately. For the most part, we can simply change the alternating colors of the table.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/sheets-format.png" alt="Image" width="600" height="400" loading="lazy">
<em>Formatting options in Google Sheets</em></p>
<p>If we select Custom, it opens up the full alternating colors menu that is also accessible through the Format menu. This gives us more control over the colors, but it's all aesthetic.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-110.png" alt="Image" width="600" height="400" loading="lazy">
<em>Alternating colors menu in Google Sheets</em></p>
<p>Meanwhile in Excel, we have the same options with a few more toggle selections for styling. For instance, we can check the first column to bold the text in the id column or we toggle between banded columns and/or rows.</p>
<p>And on the far right of the Table Design tab in the Ribbon, there are a ton of prebuilt styles that we can toggle on and off.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/format-excel.png" alt="Image" width="600" height="400" loading="lazy">
<em>Table design in Excel</em></p>
<p>Both programs give plenty of options here, and this is mostly to make the tables look good. But Excel comes out on top with more options.</p>
<p><strong>⭐Winner: EXCEL</strong></p>
<h2 id="heading-how-to-sort-tables-in-excel-and-google-sheets">How to Sort Tables in Excel and Google Sheets</h2>
<p>In both programs, there is a dropdown toggle button in each of the header row's cells. Selecting this in Excel allows us to sort ascending or descending...or even by color.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/sort.png" alt="Image" width="600" height="400" loading="lazy">
<em>sort in excel</em></p>
<p>For instance, if we have some of our data using a blue font color, we can actually sort it by that color:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/sort-color.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sort by color in Excel</em></p>
<p>What about Google Sheets? Yep, same deal there. It will also detect when different colors are used and allow you to do the same type of sorting.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/sheets-sorting-options-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sort by color in Sheets</em></p>
<p>Excel does have a Custom Sorting dialog box that can drill down into more detail. For instance, you can add levels of sorting.</p>
<p>Using our color example, we can first sort by the blue font colors in the email color and then by the red font colors in the job title column.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/double-sort.png" alt="Image" width="600" height="400" loading="lazy">
<em>Multiple column sorting in Excel</em></p>
<p>Google Sheets can do the same thing, but not from the header drop downs. The header drop down sorting is restricted to one row at a time in Sheets.</p>
<p>But, if you select the entire table's data range and then <code>Data - Sort Range - Advanced range sorting options</code>, you are able to sort by multiple columns in Google Sheets.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/advanced-sort-google-sheets.png" alt="Image" width="600" height="400" loading="lazy">
<em>Advanced sorting in Google Sheets</em></p>
<p>Sheets' advanced sorting is not as powerful as Excel's, though. You are only able to sort ascending or descending by value. Excel takes the cake on this one by a hair.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/google-sheets-muiltiple-row-sorting.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><strong>⭐Winner: EXCEL</strong></p>
<h2 id="heading-how-to-filter-tables-in-excel-and-google-sheets">How to Filter Tables in Excel and Google Sheets</h2>
<p>Filtering works exactly the same as sorting. In both programs, click the dropdown selector in the header row to see the options for filtering.</p>
<p>In both programs, we have the same options. We can filter by color just like in our sorting. We can filter by values by either selecting all, none, or individual values. And we can filter by condition.</p>
<p>Here's Google Sheet's menu:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-113.png" alt="Image" width="600" height="400" loading="lazy">
<em>Filtering in Google Sheets</em></p>
<p>And here's Excel's menu. All the same options are available. Both programs allow for custom filter formulas to be entered as well.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-112.png" alt="Image" width="600" height="400" loading="lazy">
<em>Filtering in Excel</em></p>
<p><strong>⭐Winner: TIE</strong></p>
<h2 id="heading-how-to-use-tables-in-formulas-in-excel-and-google-sheets">How to Use Tables in Formulas in Excel and Google Sheets</h2>
<p>One of the big reasons to use tables lies in formulas. Whether you use Excel or Sheets, you are likely taking advantage of built-in functions and the ability to create custom formulas for analyzing your data.</p>
<p>By holding your data in a table, your formulas can reference that data dynamically.</p>
<p>Meaning, if you add rows of data to your table, any formulas referencing those table values will update automatically.</p>
<p>The risk of breaking things by adding data decreases dramatically with the use of tables.</p>
<p>Here's a simple example. If we wanted to combine the first and last names into one cell, we could concatenate them with this formula <code>=Salary[first_name]&amp;" "&amp;Salary[last_name]</code>.</p>
<p>In Excel, we reference a table by its name, in this case, <code>Salary</code>. Then within brackets, we reference a column name, <code>[last_name]</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-115.png" alt="Image" width="600" height="400" loading="lazy">
<em>Spill formula in Excel</em></p>
<p>We can do the exact same in Sheets.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-116.png" alt="Image" width="600" height="400" loading="lazy">
<em>Formula referencing in Sheets</em></p>
<p>There's one powerful difference, though. In Excel, the formula will spill down. Meaning, we only have to write it once at the very top, but because it sees that we're referencing values in a table, it will spill down to every row in the table.</p>
<p>In Google Sheets, we still have to drag the formula down.</p>
<p>Now, sometimes, we may not want things to spill down. In this case we can use different syntax in Excel. Instead of the column name within brackets, we can add an @ sign and another set of brackets. This tells Excel to only make the calculation on one row: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-117.png" alt="Image" width="600" height="400" loading="lazy">
<em>Formula referencing in Excel</em></p>
<p>Excel flexes on this one. It's much more powerful to use tables in formulas in Excel than in Sheets.</p>
<p><strong>⭐Winner: EXCEL</strong></p>
<h2 id="heading-how-to-change-table-range-in-excel-and-google-sheets">How to Change Table Range in Excel and Google Sheets</h2>
<p>What if we want to extend our table or remove data from it? Both Google Sheets and Excel allow us to do this easily.</p>
<p>Say we want to add a column for the full name of a person. In both programs, if we simply type in <code>full_name</code> in G1 to the right of our last column, that column will become a part of the table's data range.</p>
<p>Anytime we type in an adjacent column or row to our table data, the programs will assume the table needs to extend to include it.</p>
<p>Then, we can use a version of the formula from our previous example to insert the full names. Now that we are inside of the table, though, it's not necessary to include the title of the table in our formula.</p>
<p>Now, all that's needed in Excel is <code>=[@[first_name]]&amp;" "&amp;[@[last_name]]</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-119.png" alt="Image" width="600" height="400" loading="lazy">
<em>Reference table columns in Excel</em></p>
<p>For Google Sheets, it's the same inside the table as outside it: <code>=Table2[first]&amp;" "&amp;Table2[last]</code>. Sheets also requires us to drag the formula down. It does not handle spilling like Excel (yet).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-118.png" alt="Image" width="600" height="400" loading="lazy">
<em>Reference table columns in Sheets</em></p>
<p>To add columns within a table, we can right click the column name and select insert.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-120.png" alt="Image" width="600" height="400" loading="lazy">
<em>Insert column in Excel</em></p>
<p>Google has a slight edge here in that you can select whether to insert to the left or the right, whereby Excel only inserts to the left.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-121.png" alt="Image" width="600" height="400" loading="lazy">
<em>Insert column Google Sheets</em></p>
<p>Inserting rows is exactly the same. Excel allows for inserting rows above, while Sheets allows you to select above or below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-122.png" alt="Image" width="600" height="400" loading="lazy">
<em>Inserting rows Google Sheets</em></p>
<p>In both programs, deleting rows and columns is as simple as selecting the row(s) or column(s), right clicking, and selecting delete.</p>
<p>In Excel you have the added benefit of a keyboard shortcut. <code>CTRL + -</code> will delete the selected rows or columns.</p>
<p>Both programs will also allow you to convert a table back to a regular data range. In Excel, it's the <code>Convert to Range</code> button in the <code>Table Design</code> tab of the menu</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/convert.png" alt="Image" width="600" height="400" loading="lazy">
<em>Convert to Range option in Excel</em></p>
<p>And in Google Sheets, it's the <code>Revert to unformatted data</code> option from the table dropdown menu.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/revert.png" alt="Image" width="600" height="400" loading="lazy">
<em>Revert to unformatted data option in Sheets</em></p>
<p><strong>⭐Winner: TIE</strong></p>
<h2 id="heading-how-to-add-a-total-row-in-a-table">How to Add a Total Row in a Table</h2>
<p>There's a good chance you'll want to total up the amounts in a column. How easy is this to add in a table?</p>
<p>You can do it in both programs, but...</p>
<p>Excel makes it incredibly easy. There's a toggle option for this in the Table Design menu in the Ribbon. Toggle this on, and a Total row is automatically included and calculated at the bottom.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/total-row.png" alt="Image" width="600" height="400" loading="lazy">
<em>Total row in Excel</em></p>
<p>Can you do the same in Sheets? </p>
<p>Yes, you've just got to do it yourself.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/image-124.png" alt="Image" width="600" height="400" loading="lazy">
<em>Total row in Sheets</em></p>
<p><strong>⭐Winner: EXCEL</strong></p>
<h2 id="heading-who-wins">Who Wins?</h2>
<p>Well, it's no surprise that Excel comes out on top. Sheets users (myself included) have a lot to be excited about with the ability to finally create proper tables. By and large, the functionality is just as powerful as Excel. </p>
<p>And much like many features compared between the two programs, Sheets can probably get the job done for most use cases.</p>
<p>Excel, as per normal, simply does more and does it a little bit better.</p>
<p>I'm Eamonn, and I'll help you <strong>get good at spreadsheets</strong>. Join my free newsletter, <a target="_blank" href="https://www.gotsheet.xyz/subscribe">Got Sheet, here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Basic CMS with Google Sheets and React ]]>
                </title>
                <description>
                    <![CDATA[ In today's digital landscape, creating a content management system (CMS) that is both cost-effective and easy to maintain can be difficult, especially if you're operating on a tight budget.  This tutorial will show you a solution that leverages Googl... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-basic-cms-with-google-sheets-and-reactjs/</link>
                <guid isPermaLink="false">66bdff5a0b4523e3b8b99090</guid>
                
                    <category>
                        <![CDATA[ cms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Marco Venturi ]]>
                </dc:creator>
                <pubDate>Wed, 06 Mar 2024 17:55:48 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/--1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In today's digital landscape, creating a content management system (CMS) that is both cost-effective and easy to maintain can be difficult, especially if you're operating on a tight budget. </p>
<p>This tutorial will show you a solution that leverages Google Sheets as a makeshift database and React to build the frontend. This will let you effectively bypass the need for a dedicated server or traditional database system. </p>
<p>This approach not only reduces the overhead costs associated with web development, but also simplifies content updates and management. It's an ideal solution if you're looking to launch your own simple CMS without substantial investment.</p>
<p>This solution is suitable for freelancers at the beginning of their career and for clients who cannot invest much in their website.</p>
<h2 id="heading-why-google-sheets">Why Google Sheets?</h2>
<p>Opting for Google Sheets as the backbone of your CMS comes down to its simplicity, flexibility, and cost-effectiveness.</p>
<p>Traditional web development requires a backend server to process data, a database to store information, and a frontend to display content. But each layer adds complexity and cost. </p>
<p>Google Sheets, on the other hand, acts as a highly accessible and intuitive interface that eliminates the need for a server and a database. It lets your users update content in real-time, much like any CMS, but without the usual setup and maintenance costs. This makes it an excellent choice for individuals, small businesses, or anyone looking to deploy a web application quickly and with minimal expense. </p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>Before diving into the code, ensure you have Node.js and npm installed on your system. These tools will allow us to create a React application and manage its dependencies. </p>
<p>Let's start with Google Sheets now.</p>
<h3 id="heading-step-1-set-up-your-google-sheets">Step 1: Set Up Your Google Sheets</h3>
<ol>
<li>Go to your Google Sheets</li>
<li>Open the sheet you want to use or create a new one</li>
<li>Click on <code>Extensions</code> in the menu</li>
<li>Then click on <code>Apps Script</code></li>
</ol>
<p>In the Apps Script editor, you can write a script to serve as your endpoint. Here's a script that returns the contents of a Google Sheet in JSON format:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">convertRangeToJson</span>(<span class="hljs-params">data</span>) </span>{
  <span class="hljs-keyword">var</span> jsonArray = [];

  <span class="hljs-comment">// Check if data is empty or doesn't contain enough rows for headers and at least one data row</span>
  <span class="hljs-keyword">if</span> (!data || data.length &lt; <span class="hljs-number">2</span>) {
    <span class="hljs-comment">// Return an empty array or a meaningful message as needed</span>
    <span class="hljs-keyword">return</span> jsonArray; <span class="hljs-comment">// or return 'No data available';</span>
  }

  <span class="hljs-keyword">var</span> headers = data[<span class="hljs-number">0</span>];
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">1</span>, length = data.length; i &lt; length; i++) {
    <span class="hljs-keyword">var</span> row = data[i];
    <span class="hljs-keyword">var</span> record = {};

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> j = <span class="hljs-number">0</span>; j &lt; row.length; j++) {
      record[headers[j]] = row[j];
    }

    jsonArray.push(record);
  }

  <span class="hljs-keyword">return</span> jsonArray;
}
</code></pre>
<p>Then:</p>
<ol>
<li>Click <code>File</code> &gt; <code>Save</code>, and give your project a name</li>
<li>Click on <code>Deploy</code> &gt; <code>New deployment</code>.</li>
<li>Click on <code>Select type</code> and choose <code>Web app</code>.</li>
<li>Fill in the details for your deployment. Under <code>Execute as</code>, choose whether the script should run as your account or as the user accessing the web app. Under <code>Who has access</code>, choose who can access your web app.</li>
<li>Click <code>Deploy</code>.</li>
</ol>
<p>You may be asked to authorize the script to access your Google Sheets. Follow the prompts to do so.</p>
<p>After deploying, you'll be given a URL for your web app. This is your API endpoint.</p>
<p>To give you an idea of what you have done so far, this is your sheet structure:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Schermata-2024-03-04-alle-16.49.37.png" alt="Image" width="600" height="400" loading="lazy">
<em>How your sheet should currently look</em></p>
<p>And this is the JSON you get when you call the endpoint:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/postman_I.png" alt="Image" width="600" height="400" loading="lazy">
<em>JSON</em></p>
<h3 id="heading-step-2-create-your-react-app">Step 2: Create Your React App</h3>
<p>With your Google Sheets API ready, it's time to create the React app that will fetch and display this data.</p>
<p>First, go ahead and create a React app. Run the following command in your terminal to create a new React application:</p>
<pre><code class="lang-bash">npx create-react-app google-sheets-cards
<span class="hljs-built_in">cd</span> google-sheets-cards
npm start
</code></pre>
<p>You can also <a target="_blank" href="https://www.freecodecamp.org/news/get-started-with-vite/">use modern build tools like Vite</a> for this purpose, as CRA is no longer the recommended way of building a React app.</p>
<p>Next, create the card component. Inside the <code>src</code> directory, create a file named <code>Card.js</code>. This component will be responsible for displaying each data record:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// src/Card.js</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Card</span>(<span class="hljs-params">{ title, content }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"card"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Card;
</code></pre>
<p>Now it's time to fetch and display your data in App.js. Modify the <code>App.js</code> file to include logic for fetching the data from your Google Sheets API and using the Card component to display it:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// src/App.js</span>
<span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> Card <span class="hljs-keyword">from</span> <span class="hljs-string">'./Card'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'./App.css'</span>; <span class="hljs-comment">// Make sure to create some basic styles for the cards in App.css</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [data, setData] = useState([]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    fetch(<span class="hljs-string">'YOUR_ENDPOINT_URL'</span>) <span class="hljs-comment">// Replace with your actual endpoint URL</span>
      .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
      .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> setData(data))
      .catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error fetching data:'</span>, error));
  }, []);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Data from Google Sheets<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"cards-container"</span>&gt;</span>
        {data.map((item, index) =&gt; (
          <span class="hljs-tag">&lt;<span class="hljs-name">Card</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span> <span class="hljs-attr">title</span>=<span class="hljs-string">{item.Title}</span> <span class="hljs-attr">content</span>=<span class="hljs-string">{item.Content}</span> /&gt;</span>
        ))}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Next, you can style your cards. Go ahead and add the below CSS in <code>App.css</code> for basic card styling:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.card</span> {
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">4px</span> <span class="hljs-number">8px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.2</span>);
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">10px</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span>;
  <span class="hljs-attribute">display</span>: inline-block;
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#f9f9f9</span>;
}

<span class="hljs-selector-class">.cards-container</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-wrap</span>: wrap;
  <span class="hljs-attribute">justify-content</span>: center;
}
</code></pre>
<h3 id="heading-step-3-run-your-react-app">Step 3: Run Your React App</h3>
<p>With everything set up, you can now run your React application and see the data from Google Sheets displayed in your browser. To do this, follow these steps:</p>
<p>First, start the React app. In your terminal, navigate to the root directory of your React app if you're not already there. Run the following command to start the development server:</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>This command compiles your React application and opens it in your default web browser. You should see a webpage with a title "Data from Google Sheets", and below that, a series of cards, each displaying a title and content fetched from your Google Sheets data. </p>
<p>Here's, in fact, what we get:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Schermata-2024-03-04-alle-16.52.22.png" alt="Image" width="600" height="400" loading="lazy">
<em>Data from Google Sheets and Card 1, Card 2, and Card 3 displayed on the screen</em></p>
<p>Now you can view your data. Each card on the page corresponds to a row in your Google Sheets, with the title and content fields displayed as specified in your Card component. If you make any updates to your Google Sheets data, you can refresh the web page to see the changes reflected immediately.</p>
<p>You can deploy your React app to one of the many services you can find online such as Github Actions or Netlify. This is a simple and effective way to host your frontend application for free with significant performance. </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Congratulations! You've created a dynamic web application that fetches data from a Google Sheet and displays it using React. </p>
<p>This approach offers a flexible and straightforward way to manage your application's content without needing a backend server or database.</p>
<p>Google Sheets serves as an accessible and collaborative platform for managing data, while React allows you to build a responsive and interactive user interface. Together, they provide a powerful combination for creating web applications that can be quickly updated and easily maintained.</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[ Advanced Google Sheets Concepts – How to Build a Vigenère Cipher ]]>
                </title>
                <description>
                    <![CDATA[ A Vigenère Cipher encrypts and decrypts text using a different alphabet for each letter. It consists of text to encrypt and a key with a different letter corresponding to each letter in the text.  Creating a grid of alphabets helps illustrate this pr... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/advanced-google-sheets-concepts-build-a-vigenere-cipher/</link>
                <guid isPermaLink="false">66b8ddc90a89d796f29a16d9</guid>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Wed, 13 Dec 2023 18:13:22 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/12/2cipher.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A Vigenère Cipher encrypts and decrypts text using a different alphabet for each letter. It consists of text to encrypt and a key with a different letter corresponding to each letter in the text. </p>
<p>Creating a grid of alphabets helps illustrate this practice. If the first letter in your text is <code>A</code> and the first letter in your key is <code>L</code>, you would find the encrypted letter, <code>L</code>, where the plaintext and key intersect:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/cipher2.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of a grid of alphabets</em></p>
<p>So your text to encrypt might look like this: <code>attackatdawn</code>, your key might be this: <code>LEMONLEMONLE</code>, and the ciphertext would be this: <code>LXFOPVEFRNHR</code>.</p>
<p>This is the same example used on the <a target="_blank" href="https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher">Wikipedia page</a> which I'll walk you through in this article.</p>
<p>You could create a cipher encoder/decoder with any number of programming languages. But spreadsheets are a nice tool to use for this kind of project because they come with some pretty robust built-in functions that will help us dissect the cipher. </p>
<p>I've chosen Google Sheets because it's a hair simpler to jump into, especially if you already have a Gmail account. But you can do all the same things in Microsoft Excel.</p>
<p>Here's the <a target="_blank" href="https://docs.google.com/spreadsheets/d/1i_auliGPhTOIOk3WrsIRuHveXyEdQxi16BcvSBo6MTA/edit#gid=1257498594">Google Sheet I used</a>.</p>
<p>If you'd like to check out a video walkthrough, here you go 👇</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/dM_Ims4KnVc" 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-spreadsheet-preparation">Spreadsheet Preparation</h2>
<p>This isn't too complicated. We need a grid of alphabets. No, you don't have to type them in over and over, either.</p>
<p>In fact, you don't even have to type the whole first alphabet. Let's use Unicode characters. For capital letters, the Unicode for the alphabet starts with 65. </p>
<p>The <code>CHAR()</code> function in spreadsheets converts a number into a character according to the current Unicode table.</p>
<p>The <code>COLUMN()</code> function returns a number corresponding to the current column where column A returns 1, column B returns 2, and so on. </p>
<p>So if we start our alphabet in column B, we can access each letter by adding 63 to the <code>COLUMN()</code> function and wrapping it in the <code>CHAR()</code> function:</p>
<pre><code>=CHAR(COLUMN()+<span class="hljs-number">63</span>)
</code></pre><p>Then we can drag this over to the right until we've got the full alphabet.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/image-41.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of CHAR and COLUMN functions</em></p>
<p>The same thing applies going down. We'll just use the <code>ROW()</code> function instead:</p>
<pre><code>=CHAR(ROW()+<span class="hljs-number">63</span>)
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/12/image-42.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of CHAR and ROW functions</em></p>
<p>Then we can reference the cell above and to the right for every cell below to fill out the full alphabet grid:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/image-43.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of spreadsheet cell</em></p>
<p>The only one that will be different will be in the last column where we'll need to reference the first column to have the alphabet wrap:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/image-44.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of spreadsheet cell</em></p>
<p>We only have to do this on the second line. Then, select the full second row and drag the formulas down to fill out the grid:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/drag.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of spreadsheet grid</em></p>
<p>At this point, you can copy <code>CTRL + C</code> and paste values only <code>CTRL + SHIFT + V</code> for all the alphabet. If you add or shift rows, the alphabets may get skewed since their formulas reference fixed columns.</p>
<h2 id="heading-the-game-plan">The Game Plan</h2>
<p>Now the fun stuff. We want our text to be encrypted automatically. Let's highlight three cells: one for the text to encrypt, one for a key, and then a blank cell for our encrypted text:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/image-46.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of text to encrypt</em></p>
<p>Note: the key needs to be the same length as the text to encrypt/decrypt. In this example, the key is the word LEMON, but we extend it by repeating it for every character we need to encrypt.</p>
<p>Now we need to do three things: </p>
<ol>
<li>Split out each character of our text into its own cell</li>
<li>Loop through each character and encrypt it based on the corresponding key character.</li>
<li>Join all those characters together and place the result in our encrypted text cell.</li>
</ol>
<h2 id="heading-split-text-to-setup">Split Text to Setup</h2>
<p>Interestingly, we cannot use the <code>SPLIT()</code> - or if you're in Excel, the <code>TEXTSPLIT()</code> - function without a delimiter. That is, we can't just tell it to split each character without there being something in between the characters.</p>
<p>So, we have to get fancy. In the video walkthrough, I explore a Google Sheets-specific approach using regular expressions...which are hard fun.</p>
<p>Here's what it looks like, and it involves creating character groups, inserting something in between each one, and then splitting using that same something as the delimiter. I used a blank space as the thing which I inserted and then split by:</p>
<pre><code>=SPLIT(REGEXREPLACE(AG6,<span class="hljs-string">"(.)"</span>,<span class="hljs-string">"$1 "</span>),<span class="hljs-string">" "</span>)
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/12/hard.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of woman saying, that's just hard</em></p>
<p>Excel users and mortals, fear not – there's a more elegant way that I'll show you here that can be done in either program by using the <code>MID()</code> and <code>COLUMNS()</code> functions.</p>
<p>The <code>MID()</code> function extracts a segment of a string, and <code>COLUMNS()</code> returns the number of columns in an array. By nesting <code>COLUMNS()</code> inside <code>MID()</code> we can one by one extract each character from the string:</p>
<pre><code>=MID($AG$<span class="hljs-number">6</span>,COLUMNS($AG6:AG6),<span class="hljs-number">1</span>)
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/12/image-47.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of MID and COLUMNS functions</em></p>
<p>By locking <code>$AG6</code> as the first part of the array referenced in the <code>COLUMNS()</code>  function, this number adds one for every column we drag our formula over.</p>
<p>Drag this over until you reach the end of the text to encrypt. Every character should be in its own cell now. Do the same for the key.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/image-48.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of spreadsheet cells</em></p>
<h2 id="heading-how-to-use-xlookup-to-encrypt">How to Use <code>XLOOKUP()</code> to Encrypt</h2>
<p>Below each pairing of plaintext and key text, we will now do a double <code>XLOOKUP()</code>. </p>
<p>Yes, it's as awesome as it sounds.</p>
<p>Here's what it looks like, and let's walkthrough what's happening:</p>
<pre><code>=XLOOKUP(AD9,<span class="hljs-attr">$B$2</span>:$B$<span class="hljs-number">27</span>,XLOOKUP(AD10,<span class="hljs-attr">$B$2</span>:$AA$<span class="hljs-number">2</span>,<span class="hljs-attr">$B$2</span>:$AA$<span class="hljs-number">27</span>))
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/12/image-49.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of double XLOOKUP functions</em></p>
<p>For the beginning <code>XLOOKUP()</code>, we are looking up the plaintext and we're referencing the first plaintext alphabet for our lookup range. But then for our result range, we're opening up another <code>XLOOKUP()</code>...</p>
<p>This is because we still have to use the correct key alphabet to return an encrypted value.</p>
<p>So, we need the second <code>XLOOKUP()</code> to return a full alphabet based on the position of the key character.</p>
<p>We're using the key character as our lookup value, we're using the first alphabet along the top key row for our lookup range, and then we're returning the whole grid as our result range. This will let us return a full alphabet which is in turn referenced as the result range for our first <code>XLOOKUP()</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/12/cipher3.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of alphabet grid and XLOOKUP example</em></p>
<p>You can see the second <code>XLOOKUP()</code> by itself in the picture above. The formula is in the top left. We're using the first letter of key text as the lookup value. The lookup range is the top alphabet in the purple box. The result range is the whole grid in the blue box. And the returned alphabet is the one that starts with L in the red box.</p>
<p>You can see it being returned underneath the formula where there is an arrow in the picture.</p>
<p>Drag the double <code>XLOOKUP()</code> function over to encrypt every character of the plaintext with the key text.</p>
<h2 id="heading-join-text-for-final-solution">Join Text for Final Solution</h2>
<p>Now that we've encrypted each letter, we want them to be joined into one string and returned in our encrypted text cell.</p>
<p>To do this, we can use the <code>JOIN()</code> function - or <code>TEXTJOIN()</code> for Excel - with an empty quote as the delimiter. </p>
<pre><code>=JOIN(<span class="hljs-string">""</span>,<span class="hljs-attr">AD11</span>:AO11)
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/12/image-50.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of JOIN function</em></p>
<p>And voilà! We've just encrypted a string using a double <code>XLOOKUP()</code>.</p>
<h2 id="heading-how-to-decrypt-text">How to Decrypt Text</h2>
<p>Decryption works exactly the same. The only difference is the placement of the second <code>XLOOKUP()</code>. Instead of using it as the result range, we're using it as the lookup range:</p>
<pre><code>=XLOOKUP(AD20,XLOOKUP(AD21,<span class="hljs-attr">$B$2</span>:$AA$<span class="hljs-number">2</span>,<span class="hljs-attr">$B$2</span>:$AA$<span class="hljs-number">27</span>),<span class="hljs-attr">$B$2</span>:$B$<span class="hljs-number">27</span>)
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2023/12/image-51.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of decryption double XLOOKUP example</em></p>
<h2 id="heading-thanks-for-reading">Thanks for reading</h2>
<p>I hope this was helpful for you and that you learned something new!</p>
<p>You can find more of my tutorials on <a target="_blank" href="https://www.youtube.com/@eamonncottrell">YouTube</a>, and please sign up for my newsletter about <a target="_blank" href="https://got-sheet.beehiiv.com/">coding and spreadsheets here</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[ Web Scraping with Google Sheets – How to Scrape Web Pages with Built-in Functions ]]>
                </title>
                <description>
                    <![CDATA[ You read that right – you can practice web scraping without leaving your happy place: Google Sheets. Google Sheets has five built-in functions that help you import data from other sheets and other web pages. We'll walk through all of them in order fr... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/web-scraping-google-sheets/</link>
                <guid isPermaLink="false">66b8de2af805ffd579552e9e</guid>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web scraping ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Thu, 07 Sep 2023 21:14:07 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/09/5-functions-for-web-scraping-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>You read that right – you can practice web scraping without leaving your happy place: Google Sheets.</p>
<p>Google Sheets has five built-in functions that help you import data from other sheets and other web pages. We'll walk through all of them in order from easiest (most limited) to hardest (most powerful).</p>
<p>Here they are, and you can click each function to skip down to its dedicated section. I've made a video as well that walks through everything:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/Hx1Uepq3lLI" 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-section-shortcuts">Section Shortcuts</h3>
<ul>
<li>How to use the <a class="post-section-overview" href="#">IMPORTRANGE</a> function</li>
<li>How to use the  <a class="post-section-overview" href="#-1">IMPORTDATA</a> function</li>
<li>How to use the <a class="post-section-overview" href="#-2">IMPORTFEED</a> function</li>
<li>How to use the <a class="post-section-overview" href="#-3">IMPORTHTML</a> function</li>
<li>How to use the <a class="post-section-overview" href="#-4">IMPORTXML</a> function</li>
</ul>
<p><a target="_blank" href="https://docs.google.com/spreadsheets/d/1n8CYEHYktePXJzt5quCBn2gwHvnvTH49vvJziXLnQSE/edit#gid=511198009">Here's the Google Sheet</a> we'll be using to demo each function.</p>
<p>If you'd like to edit it, make a copy by selecting File - Make a copy when you open it.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of Google Sheet</em></p>
<p><a id="importrange"></a></p>
<h2 id="heading-how-to-use-the-importrange-function">How to use the IMPORTRANGE function</h2>
<p>This is the only function that imports a range from another sheet rather than data from another web page. So, if you've got another Google Sheet, you can link the two sheets together and import the data you need from one sheet into the other sheet.</p>
<p>For instance, <a target="_blank" href="https://docs.google.com/spreadsheets/d/1S0H1FDHBC_7oxe2NCpnfuJcklaLpYCFuo_eRhADnyWg/edit#gid=1363138812">here's a sheet</a> with a bunch of random Samsung Galaxy data in it.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can see that we have a few hundred rows of data about phones. If we want to import this data into another spreadsheet, we can use <code>IMPORTRANGE()</code>. This is the simplest to use of the five functions we'll look at. All it needs is a URL for a Google Sheet and the range we want to import.</p>
<p>Check out the tab for IMPORTRANGE in the Google Sheet <a target="_blank" href="https://docs.google.com/spreadsheets/d/1n8CYEHYktePXJzt5quCBn2gwHvnvTH49vvJziXLnQSE/edit#gid=0">here</a>, and you'll see that in cell <code>A5</code>, we've got the function <code>=IMPORTRANGE(B4,"data!a1:K")</code>. This is pulling in the range <code>A1:K</code> from the <code>data</code> tab of our second spreadsheet whose URL is in cell <code>B4</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-3.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of IMPORTRANGE function</em></p>
<p>Once your data is pulled into your spreadsheet, you can do one of two things. </p>
<ol>
<li>Leave it linked through the <code>IMPORTRANGE</code> function. This way, if your data source is going to be updated, you'll pull in the updated data.</li>
<li>Copy and CTRL+SHIFT+V to paste values only. This way, you have the raw data in your new spreadsheet and you won't have to be dependent on something changing with the URL down the road.</li>
</ol>
<p><a id="importdata"></a></p>
<h2 id="heading-how-to-use-the-importdata-function">How to use the IMPORTDATA function</h2>
<p>This is pretty straightforward. It'll import .csv or .tsv data from anywhere on the internet. These stand for Comma Separated Values and Tab Separated Values. </p>
<p>.csv is the most commonly used file type for financial data that needs to be imported into spreadsheets and other programs. </p>
<p>Like <code>IMPORTRANGE</code>, we only need a couple pieces of information for <code>IMPORTDATA</code>: the URL where the file lives, and the delimiter. There's also an optional variable for locale, but I found that it was unnecessary.</p>
<p>In fact, Google Sheets is pretty smart – you can leave off the delimiter too, and it will usually decipher what type of data (.csv or .tsv) lives at the URL.</p>
<p>You can see that I've found a New York government data website where there lives some winning lottery number data. I've put the URL for a .csv file in <code>A5</code>, and then used the function <code>=IMPORTDATA(A5,",")</code> to pull in the data from the .csv file.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-4.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of IMPORTDATA function</em></p>
<p>You could alternatively download the .csv file and then select File - Import to bring in this data. But in the event that you do not have download permissions or simply want to get it straight from a site, <code>IMPORTDATA</code> works great.</p>
<p><a id="importfeed"></a></p>
<h2 id="heading-how-to-use-the-importfeed-function">How to use the IMPORTFEED function</h2>
<p>This imports RSS feed data. If you're familiar with podcasting, you may recognize the term. Every podcast has an RSS feed which is a structured file full of XML data. </p>
<p>Using the URL for the RSS feed, IMPORTFEED will pull in data about a podcast, news article, or blog from its RSS information.</p>
<p>This is the first function that begins to have a few more options at its disposal, too.</p>
<p>All that's required is the URL of a feed, and it'll bring in data from that feed. However, we can specify a few other parameters if we like. The options include:</p>
<ol>
<li>[query]: this specifies which pieces of data to pull from the feed. We can select from options like "feed " where type can be title, description, author or URL. Same deal with "items " where type can be title, summary, URL or created.</li>
<li>[headers]: this will either bring in headers (TRUE) or not (FALSE)</li>
<li>[num_items]: this will specify how many items to return when using Query. (The docs state that if this isn't specified, all items currently published are returned, but I did not find this to be the case. I had to specify a larger number to get back more than a dozen or so).</li>
</ol>
<p>You can see from the screenshots below that I am querying one of my feeds to pull in the episode titles and URLs. </p>
<p>First, to get all the titles, I used <code>IMPORTFEED(A3, "items title", TRUE, 50</code>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-6.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of IMPORTFEED</em></p>
<p>Then, similarly for the URLs, I used <code>IMPORTFEED(A3, "items url", TRUE, 50)</code>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-7.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of IMPORTFEED #2</em></p>
<p><a id="importhtml"></a></p>
<h2 id="heading-how-to-use-the-importhtml-function">How to use the IMPORTHTML function</h2>
<p>Now we're getting into scraping data straight off of a web site. This will take a URL and then a query parameter where we specify to look for either a "table" or a "list".</p>
<p>This is followed by an index value representing which table or list to look for if there are multiple on the page. It is zero indexed, so input zero if you're looking for the first one.</p>
<p>IMPORTHTML looks through the HTML code on a website for <code>&lt;table&gt;</code> and <code>&lt;list&gt;</code> HTML elements.</p>
<pre><code class="lang-html"><span class="hljs-comment">&lt;!--Here's what a simple table looks like:--&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">table</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">thead</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>table header 1<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>table header 2<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">thead</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>table data row 1 cell1<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>table data row 1 cell2<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>table data row 2 cell1<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>table data row 2 cell2<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>

<span class="hljs-comment">&lt;!--Here's what an ordered list looks like:--&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ol</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>ordered item 1<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>ordered item 2<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>ordered item 2<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ol</span>&gt;</span>
<span class="hljs-comment">&lt;!--Here's what an unordered list looks like:--&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>unordered item 1<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>unordered item 2<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>unordered item 3<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
</code></pre>
<p>In the sample sheet, I've got the URL for some stats about the Barkley Marathons in cell <code>B3</code> and am then referencing that in <code>A4</code>'s function: <code>=IMPORTHTML(B3,"table",0)</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-8.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of IMPORTHTML</em></p>
<p>FYI, freeCodeCamp created <a target="_blank" href="https://scrapepark.org/">ScrapePark</a> as a place to practice web scraping, so you can use it for <code>IMPORTHTML</code> and <code>IMPORTXML</code> coming up next👇.</p>
<p><a id="importxml"></a></p>
<h2 id="heading-how-to-use-the-importxml-function">How to use the IMPORTXML function</h2>
<p>We saved the best for last. This will look through websites and scrape darn near anything we want it too. It's complicated, though, because instead of importing all the table or list data like with <code>IMPORTHTML</code>, we write our queries using what's called XPath. </p>
<p>XPath is an expression language itself used to query XML documents. We can write XPath expressions to have <code>IMPORTXML</code> scrape all kinds of things from an HTML page.</p>
<p>There are many resources to find the proper XPath expressions. <a target="_blank" href="https://devhints.io/xpath">Here's one</a> that I used for this project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-182.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of XPath cheat sheet</em></p>
<p>In the <a target="_blank" href="https://docs.google.com/spreadsheets/d/1n8CYEHYktePXJzt5quCBn2gwHvnvTH49vvJziXLnQSE/edit#gid=438611895">sheet</a> for <code>IMPORTHTML</code>, I have several examples that I encourage you to click through and check out.</p>
<p>For example, using the function <code>=IMPORTXML(A11,"//*[@class='post-card-title']")</code> allows us to bring in all the titles of my articles because from inspecting the HTML on my author page here, I found that they all have the class <code>post-card-title</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/class.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of inspecting a web page with dev tools</em></p>
<p>In the same way, we can use the function <code>=IMPORTXML(A11,"//*[@class='post-card-title']//a/@href")</code> to grab the URL slug of each of those articles.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-10.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of IMPORTXML</em></p>
<p>You'll notice that it does bring in the full URL, so as a bonus, we can simply prepend the domain to each of these. Here's the function for the first row which we can drag down to get all those proper URLs: <code>="https://www.freecodecamp.org"&amp;B13</code></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/09/image-11.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of prepending domain to slug</em></p>
<h2 id="heading-follow-me">Follow Me</h2>
<p>I hope this was helpful for you! I learned a lot myself, and enjoyed putting the video together. </p>
<p>You can find me on YouTube: <a target="_blank" href="https://www.youtube.com/@eamonncottrell">https://www.youtube.com/@eamonncottrell</a></p>
<p>And, I've got a newsletter here: <a target="_blank" href="https://got-sheet.beehiiv.com/">https://got-sheet.beehiiv.com/</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 Use Google Sheets – A Beginner's Guide ]]>
                </title>
                <description>
                    <![CDATA[ Google Sheets is an online spreadsheet app with real-time collaboration features. It's like Microsoft Excel for regular people. 🙌 gif of guy saying aren't ordinary people adorable And, these days, it's actually giving Excel a run for its money. It'... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/google-sheets-for-beginners/</link>
                <guid isPermaLink="false">66b8ddef0a89d796f29a16e1</guid>
                
                    <category>
                        <![CDATA[ beginners guide ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Tue, 25 Jul 2023 17:53:57 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/maxresdefault.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Google Sheets is an online spreadsheet app with real-time collaboration features. It's like Microsoft Excel for regular people. 🙌</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/ordinary.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of guy saying aren't ordinary people adorable</em></p>
<p>And, these days, it's actually giving Excel a run for its money. It's picked up a lot of features and processing power over the years that used to be exclusive to Excel.</p>
<p>But we're here to talk about the basics today.</p>
<p>🧾I'm going to cover:</p>
<ol>
<li>How to create new Google Sheets</li>
<li>Intro to Templates</li>
<li>Menu and toolbar overview</li>
<li>Basic data entry and calculations</li>
<li>Basic formatting</li>
<li>How to create a table</li>
<li>How to sort and filter data</li>
<li>Intro to formulas and functions</li>
</ol>
<h2 id="heading-video-walkthrough">Video Walkthrough</h2>
<p>I've made a video walkthrough of the things we'll be covering in this article. You can check it out below:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/_bvRa7T-59U" 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-a-new-sheet">How to Create a New Sheet</h2>
<p>Spreadsheets can be intimidating even in their most basic form. </p>
<p>Don't be scared, though.👇 </p>
<p>To use Google Sheets, you need a free Google account. (If you're using Gmail, you already have this.) Go <a target="_blank" href="https://www.google.com/sheets/about/">here</a> to sign up if you don't have one yet.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-141.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of the Google Sheets homepage</em></p>
<p>Go ahead and go to <a target="_blank" href="https://sheets.new">sheets.new</a> to create a brand new Google Sheet. It will also prompt you to sign up if you don't have an account yet. </p>
<p> 💥This is what you'll see:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-138.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of a blank spreadsheet</em></p>
<p>😓Some of you may already have begun perspiring because a spreadsheet looks like an unapproachable blank slate reserved for data analysts, financial gurus, and overly ambitious content creators. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/sweat.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of man stressing out</em></p>
<p>💪Fear not. </p>
<p>A spreadsheet, and particularly a Google Sheet, is ripe with possibilities for the average person.</p>
<p>You can use them to enhance productivity after learning only a few basic things. </p>
<p>📊A spreadsheet is a <strong>big grid</strong> made up of <strong>columns labeled with letters</strong> and <strong>rows labeled with numbers</strong>. Each of the rectangles of the grid are called a <strong>cell</strong>, and the active cell is the one with the blue outline. </p>
<p>If you start typing, whatever you type will appear in the active cell.</p>
<p>The cells can contain numbers, words, formulas, dates, pretty much anything...</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-140.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of spreadsheet cell</em></p>
<h2 id="heading-google-sheets-templates">Google Sheets Templates</h2>
<p>Google has supplied us with a respectable amount of templates to get started with. </p>
<p>I will not be covering these in any detail because it's important to get the basics down first. I just want you to be aware of them if you need a starting point <strong>after</strong> you've gotten comfortable with Sheets. </p>
<p>You can look through the template gallery <a target="_blank" href="https://docs.google.com/spreadsheets/u/0/?ftv=1">here</a>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-142.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of Google Sheets templates</em></p>
<h2 id="heading-menu-and-toolbar-overview">Menu and Toolbar Overview</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/menus.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of Google Sheets menu and toolbar</em></p>
<p>You will likely be familiar with the menu and toolbar setup. As with most modern applications, they are at the top of the page. The menu has many familiar options like File, Edit, View, Insert, and so on. And the toolbar below it consists of mostly icons related to formatting and text options.</p>
<p>The <strong>File</strong> menu has options to share, download, copy, import, rename and other such things related to the whole spreadsheet.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-144.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of file menu</em></p>
<p><strong>Edit</strong> and <strong>View</strong> have familiar options regarding copy/paste and choosing different levels of visibility for your spreadsheet.</p>
<p><strong>Insert</strong> gives us a host of options of things to import into our sheet like charts, pivot tables, checkboxes, emojis, dropdown lists, and many more.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-145.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of insert menu</em></p>
<p><strong>🎨Format</strong> contains many options for formatting our sheet. We can add color, borders, tables styles, and more from here.</p>
<p>The <strong>Data</strong> menu has a lot of spreadsheet specific functions. It contains shortcuts to sort and filter data, to protect different ranges of cells, to group ranges together by naming them, data validation, and advanced items like connecting data sources.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-146.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of the Data menu in Google Sheets</em></p>
<p>Tools and Extensions will be lesser used menus, at least in the beginning. But some really exciting stuff is possible by leveraging the power of <strong>Google Apps Script</strong> through the Extensions menu. This lets us write programs in a language similar to JavaScript all while in a spreadsheet.</p>
<p>But, that's for later – or check out some of my other <a target="_blank" href="https://www.youtube.com/@eamonncottrell">videos</a> and <a target="_blank" href="https://www.freecodecamp.org/news/author/eamonn">articles</a> to get a taste of Apps Script.</p>
<h2 id="heading-basic-data-entry-and-calculations">Basic Data Entry and Calculations</h2>
<p>What is data? According to my Google search, data are "facts and statistics collected together for reference or analysis."</p>
<p>Spreadsheets thrive on data. Yes, think numbers, dates, percentages...things that are easily calculable.</p>
<p>In Google Sheets, we can enter in some data. Say, a list of names, dollar amounts, and dates. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-150.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of some data</em></p>
<p>Now, we have a simple list of data. Because it's small, we can scan it and analyze it ourselves pretty easily. </p>
<ol>
<li>We can see that it's ordered by date</li>
<li>We can see that Paul either has or owes the most</li>
<li>We can see that Sara either has or owes the least</li>
</ol>
<p>But imagine this is a list of thousands of records (each row of an amount, a name, and a date can be referred to as one record).</p>
<p>Suddenly it gets a lot harder to analyze or make sense of the data. </p>
<p>Then add a dozen or a hundred more columns of data for each record. It becomes virtually impossible for a human to draw anything meaningful from the data without the help of a spreadsheet or computer program.</p>
<p>Next, we'll see how some organization and simple spreadsheet operations can help us draw insights from our data.👇</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/easy.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of woman saying this is so easy</em></p>
<h2 id="heading-basic-formatting">Basic Formatting</h2>
<p>Highlight the table. You can do this by clicking and dragging across the whole range of cells. Now, you should have an active <strong>range</strong> instead of an active <strong>cell</strong>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-151.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of Google Sheets table</em></p>
<p>Take a moment and click through some of the formatting options on the toolbar. I've changed the text, the font size, and then made bold the first header row. </p>
<p>I've also highlighted only the dollar amounts, and changed their format to <strong>currency</strong> instead of just a number.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/menus-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of Google Sheets formatting</em></p>
<p>Already, the information is a little more readable. In the next steps we will go further.👇</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/rabbit-hole.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of Alice falling down rabbit hole</em></p>
<h2 id="heading-how-to-create-a-table-in-sheets">How to Create a Table in Sheets</h2>
<p>Alas, we don't (yet) have a swift shortcut like Excel does to make a table. But if we highlight our data again, and select <strong>Format -&gt; Alternating Colors</strong> from the toolbar, we can create table formatting for enhanced readability.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-153.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of Alternating Colors table style in Google Sheets</em></p>
<p>Now, we have banded rows of alternating colors. This becomes very useful in large data sets, but is equally pleasing in our small example. Once satisfied with your color selections, click done.</p>
<p>Now, if we add rows of data to the bottom of our table, Google Sheets is smart enough to know that we probably want to extend the table downward. It will extend the alternating color formatting as we add two more lines:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-154.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of extended Google Sheets table</em></p>
<h2 id="heading-how-to-sort-data-with-a-filter-in-sheets">How to Sort Data with a Filter in Sheets</h2>
<p>Tables are useful because, with some small steps, we can sort and filter data in ways that make it easier to draw conclusions or extract meaning from our data.</p>
<p>Click anywhere in the range of data, and select <strong>Data -&gt; Create a filter</strong> from the Menu bar.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-155.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of creating a filter in Google Sheets</em></p>
<p>Again, Google Sheets knows to create a filter for the entire data set, and you can see two things visually represented: </p>
<ol>
<li>The rows and columns of the data set are now given a highlight color to show that a filter has been applied.</li>
<li>To the right of each header label, there are three horizontal lines that look like an upside-down pyramid. These are the filters.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-156.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of filter on a Google Sheets table</em></p>
<p>From here, we can both sort and/or filter our data. If we sort, for instance, Z -&gt; A, we can display each row from highest to lowest dollar amount.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-157.png" alt="Image" width="600" height="400" loading="lazy">
<em>sorted table</em></p>
<p>If we click the data column's pyramid, we can see that for filtering options, we can either filter by condition or by value.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-158.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of filtering options in Google Sheets</em></p>
<p>Filtering by <strong>values</strong> lets us toggle on or off certain dates. Filtering by <strong>condition</strong> allows for all sorts of options. We can choose dates that are before or after certain dates. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-159.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of filter options in Google Sheets</em></p>
<p>Or we can select "Is between" and then write in a period of dates we want to filter for.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-161.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of filter options in Google Sheets</em></p>
<p>This becomes immensely helpful to narrow down a field of results for analysis. Now we can focus on only the subset of filtered results.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-162.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-intro-to-formulas-and-functions">Intro to Formulas and Functions</h2>
<p>The final piece we'll touch on is the ability to <strong>write formulas</strong> and <strong>use functions</strong>.</p>
<h3 id="heading-formulas-in-google-sheets">Formulas in Google Sheets</h3>
<p>Formulas are instructions we manually type into a cell to manipulate data. If we wanted to add the values in cells A16 and A17, we could write a formula that did this like so: <code>=A16+A17</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-163.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of a formula in Google Sheets</em></p>
<p>We can do basic math very easily like this, but there's an even better way to do this by using functions.</p>
<h3 id="heading-functions-in-google-sheets">Functions in Google Sheets</h3>
<p>Functions are built-in formulas that we use by typing their name. Again, we start out by typing the equals sign, but then we type the name of the function, in this case SUM: <code>=SUM(A16:A17)</code>.</p>
<p>When we begin typing a function, a tooltip will pop up giving us options for the different functions available. You can hit the TAB key to select a function, click one from the list, or complete the spelling of the function and type an open parentheses to select a function.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-165.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of available functions in Google Sheets</em></p>
<p>Once selected, the tooltip will display helper text about that function in case we need an explanation of what it does and/or the variables it needs:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/image-164.png" alt="Image" width="600" height="400" loading="lazy">
<em>pic of the SUM function in Google Sheets</em></p>
<p>These are straightforward examples of adding numbers, but there are over 400 built-in functions that range greatly in complexity. </p>
<h2 id="heading-whats-next">What's Next?</h2>
<p>As you've probably figured out, this barely scratches the surface of Google Sheets. Once you're comfortable with the basics, it's time to dive deeper and use Sheets to solve some problems of your own.</p>
<p>Check out the templates I've linked above for inspiration, spin up a personal finance tracker, make a workout calendar, track stock prices, build a cost of goods sheet for a small business, build an amortization sheet to see if you can afford a house, track your time with a project management sheet...the possibilities are vast.</p>
<p>🔗Check out my <a target="_blank" href="https://www.youtube.com/@eamonncottrell">YouTube channel</a> and <a target="_blank" href="https://got-sheet.beehiiv.com/subscribe">newsletter</a> for more Sheets content and projects.</p>
<p>🔗Connect with me on <a target="_blank" href="https://www.linkedin.com/in/eamonncottrell/">LinkedIn</a>.</p>
<p>Hope you have a great one!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/bye-gosling.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of Gosling waving bye</em></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[ AI in Google Sheets – How to Use GPT Copilot ]]>
                </title>
                <description>
                    <![CDATA[ It seems to be the year of AI. And my favorite tool, Google Sheets, is not to be left out of the fun. 🎉 Coefficient produced a Google sheets extension with the ability to use OpenAI's GPT models from within a spreadsheet. It’s called GPT Copilot an... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/ai-in-google-sheets/</link>
                <guid isPermaLink="false">66b8ddcc0cedc1f2a4f70674</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Artificial Intelligence ]]>
                    </category>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Mon, 12 Jun 2023 22:10:01 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/06/ai-in-google-sheets-thumb.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>It seems to be the year of AI. And my favorite tool, Google Sheets, is not to be left out of the fun. 🎉</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/giphy.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p><a target="_blank" href="https://coefficient.io/gpt-google-sheets">Coefficient</a> produced a Google sheets extension with the ability to use OpenAI's GPT models from within a spreadsheet. It’s called GPT Copilot and it’s available to try for free. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-09-at-8.00.44-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of Coefficient's GPT in Sheets page</em></p>
<p>I have been putting off looking into this tool for quite some time, though I've been following Coefficient's Google Sheets work for a while now.</p>
<p>To be honest, I'd gotten a little tired of all the AI talk and was wary of yet another effort in that realm. Do I really need to use AI in spreadsheets? </p>
<p>🤷Yes and no.</p>
<h2 id="heading-video-example">Video Example</h2>
<p>I made a quick video walking through a handful of the GPTx functions, and I'll detail all of them in the article below. If you have a sec, check out the 2.5 min video and give it a 👍.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/HaKYyPLaOFA" 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>The extension is pretty cool.</p>
<p>AI is not some thing that is going to do all of my work for me. But it is a pretty powerful tool with which I can do work faster, more efficiently, and in some cases get some unique inspiration from.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/potential.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of ted lasso saying, smells like potential</em></p>
<h2 id="heading-gpt-copilot-functions">GPT Copilot Functions</h2>
<p>In the GPT Copilot extension on Google Sheets, we are given a list of several built-in <code>GPTX</code> functions we can now use directly in the sheet itself. </p>
<p><a target="_blank" href="https://docs.google.com/spreadsheets/d/1CaLdC22IS_9K42ycwkyYlnsCSGsEpyJtedPWXxn5poI/edit#rangeid=903106705">Here</a> is a document with all the functions listed out, and we'll go through each below.</p>
<p>And <a target="_blank" href="https://docs.google.com/spreadsheets/d/10suhdcRdi5NI_PCGO0VK1TXiLIGdy44T8R3zL9938So/edit?usp=sharing">here's the spreadsheet</a> I used for all my samples and the video. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-09-at-8.12.07-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The first function, <code>=GPTX(prompt)</code> simply lets us generate text using Open AI's GPT model.</p>
<p>I used this to generate a subject line for an email below. This generic <code>GPTX</code> function can be used to query ChatAI's model for anything you can think of: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-113.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of GPTX() function</em></p>
<p>Then we get <code>**=GPTX_LIST(prompt)**</code> which lets us generate a list of values in the same way. </p>
<p>This is similar to the first example, but the list of items will appear in separate cells so it's handy if you want...say, the top 10 video games by sales in Korea:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-114.png" alt="Image" width="600" height="400" loading="lazy">
_Screenshot of GPTX<em>LIST() function</em></p>
<p><code>**=GPTX_EDIT(text,[task])**</code> was useful as I went through and created email body messages based on a prompt that I fed into the function. So it lets us perform a task or transformation on an input text.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-09-at-8.14.09-PM.png" alt="Image" width="600" height="400" loading="lazy">
_screenshot of GPTX<em>EDIT function</em></p>
<p><code>**GPTX_MAP(search_keys, inputs)**</code> performs a fuzzy search, given a list of search keys in the list of input values, and then outputs the most similar search for each input value.</p>
<p>If we have a list of games and platform companies, it can map the company values to the correct game:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-124.png" alt="Image" width="600" height="400" loading="lazy">
_Screenshot of GPTX<em>MAP()</em></p>
<p><code>**GPTX_FILL(text, [task])**</code> fills in missing information in the table based on example rows.</p>
<p>This will let you feed it some example data and have GPTX fill in the rest:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-115.png" alt="Image" width="600" height="400" loading="lazy">
_Screenshot of GPTX<em>FILL</em></p>
<p><code>**GPTX_TABLE(prompt, [header_row])**</code> generates a table of values. This one was particularly cool because you can give it a real query to get data from, or have it fill out a table with dummy data.</p>
<p>Here we can pull in some population info on the top 5 cities in Tennessee: 😀</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-116.png" alt="Image" width="600" height="400" loading="lazy">
_Screenshot of GPTX<em>TABLE()</em></p>
<p><code>**GPTX_FORMAT(text, language)**</code> converts input values into the specified format. </p>
<p><code>**GPTX_TAG(text, tags)**</code> does what it sounds like: it applies one or more tags matching a piece text.</p>
<p>If you have a list of movies, and a list of genres, you can tag them here. Interestingly:</p>
<ol>
<li>GPT doesn't seem to have a sense of humor as it didn't classify Waterworld as a flop.</li>
<li>It also won't blindly fill in genres that are missing from the list (Goldeneye and Seven)</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/flop.png" alt="Image" width="600" height="400" loading="lazy">
_Screenshot of GPTX<em>TAG()</em></p>
<p>Another cautionary tale, though...when I added crime to see if it would fill in Seven's blank, it actually came back with different answers on some of the others...and left off Casper and Jumanji this time. So, while nothing is really tagged incorrectly, it does have varying results. </p>
<p>In this case, it does behave like a human, in a way. You'll get different people answering this type of tagging question in different ways too. 🤷‍♂️</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><code>**GPTX_CLASSIFY(text, labels)**</code>, in the same way, classifies text given a set of labels or categories.</p>
<p><code>**GPTX_EXTRACT(text, info_to_extract)**</code> extracts the desired information from the input text. So if you have an address in a cell, it can extract the city name from the address.</p>
<blockquote>
<p>Caution though; I encountered several instances while writing these GPTX functions where I got #ERROR!s and had to refresh the page, close it and reopen, or simply wait a while...</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-118.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><code>**GPTX_SUMMARIZE(text, language)**</code> summarizes the input text according to the given format.</p>
<p>I tried to be clever here. I pasted the first chapter of The Great Gatsby into a cell. Pretty quickly got a "too large" error 😂)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-119.png" alt="Image" width="600" height="400" loading="lazy">
_screenshot of GPTX<em>Summarize error</em></p>
<p>But! Reducing down to the first 726 words was sufficient to have it actually summarize the beginning of this awesome novel:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-121.png" alt="Image" width="600" height="400" loading="lazy">
_Screenshot of GPTX<em>SUMMARIZE() summarizing the start of The Great Gatsby</em></p>
<p><code>**GPTX_TRANSLATE(text, language)**</code> translates the input text into the specified language.</p>
<p>This one's straightfoward and not incredibly useful for my spreadsheets...but nonetheless, it's pretty neat:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-122.png" alt="Image" width="600" height="400" loading="lazy">
_Screenshot of GPTX<em>TRANSLATE()</em></p>
<p><code>**GPTX_CONVERT(text, format)**</code> converts the input text into the specified structured format.</p>
<p><code>**GPTX_CODE(task, language)**</code>generates code in a specified language, which performs the specified task. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-09-at-8.21.54-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><code>GPTX_CODE()</code> was really neat. I also see the most chances for problems here because novices will be tempted to blindly use code not knowing how or if it works.</p>
<p>This seems most beneficial as a helper for when I know how the code should work, but can prompt for the basic skeleton instead of needing to search for methods on my own.</p>
<h2 id="heading-limits-to-gpt-copilot">Limits to GPT Copilot</h2>
<p>Are there limits to the number of prompts you can feed these functions? Yes. Of course there are. 😆</p>
<p>To stay free, you get 10,000 executions. Monthly plans start at 100,000 executions per month.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-09-at-8.33.46-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot from the Coefficient GPT CopilotFAQ</em></p>
<h2 id="heading-what-i-think">What I Think</h2>
<p>I walk through some real basic stuff that I could have generated on my own in the <a target="_blank" href="https://youtu.be/HaKYyPLaOFA">example video</a> that I encourage you to watch. But I do see the potential for this to be a very helpful tool as the ecosystem matures.</p>
<p>Insofar as dummy data is concerned, it is really helpful to just generate stuff in a spreadsheet quickly.</p>
<p>And it works great to fill in the blanks where you previously might do a little bit more manual labor, extracting values, formatting things, classifying things, and so on.</p>
<p>In addition to these built-in functions, the Coefficient add-on also contains a pivot builder, a formula builder, and a chart builder tool.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-09-at-8.25.26-PM.png" alt="Image" width="600" height="400" loading="lazy">
<em>screenshot of Coefficient's extension</em></p>
<p>These allow you to feed a prompt into the extension based on the data in your spreadsheet, and it will generate either a pivot table, a formula, or a chart.</p>
<p>These are kind of crutches for doing some pretty basic operations in Google Sheets. But they do have an interesting utility and are pretty reliable when you give well-written prompts to them.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>🤔 AI tools can be a big time sink. It's pretty easy to spend more time getting mediocre results from them than it would have taken to do things manually.</p>
<p>But, that's part of the learning curve for any toolkit. Upfront investments can certainly pay off in the long run. I'll keep using these tools to hopefully make better spreadsheets more efficiently.</p>
<p>❓What do you think? Is this another example of over-engineering simple things? Or is it the next big thing in Google Sheets?</p>
<p>Come join the conversation on my YouTube channel: <a target="_blank" href="https://www.youtube.com/@eamonncottrell?sub_confirmation=1">https://www.youtube.com/@eamonncottrell?sub_confirmation=1</a></p>
<p>Or my LinkedIn: <a target="_blank" href="https://www.linkedin.com/in/eamonncottrell/">https://www.linkedin.com/in/eamonncottrell/</a></p>
<p>Have a great one! 👋</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Google Sheets – How to Make a Dynamic Search Bar ]]>
                </title>
                <description>
                    <![CDATA[ This tutorial is for when CTRL + F is not enough. 🔥 I bet I've used the CTRL + F shortcut more than any other keyboard shortcut in my life. CTRL + Z probably comes close, but I use CTRL + F to find things... 💥ALL 💥 💥💥THE 💥💥 💥💥💥TIME ]]>
                </description>
                <link>https://www.freecodecamp.org/news/dynamic-search-bar-google-sheets/</link>
                <guid isPermaLink="false">66b8dddbfedc3fd92fddb767</guid>
                
                    <category>
                        <![CDATA[ google sheets ]]>
                    </category>
                
                    <category>
                        <![CDATA[ search ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eamonn Cottrell ]]>
                </dc:creator>
                <pubDate>Fri, 02 Jun 2023 13:31:11 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/06/Google-Sheets-Dynamic-Search-Bar-with-profile.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>This tutorial is for when CTRL + F is not enough. 🔥</p>
<p>I bet I've used the CTRL + F shortcut more than any other keyboard shortcut in my life. CTRL + Z probably comes close, but I use CTRL + F to find things...</p>
<p>💥<strong>ALL 💥</strong></p>
<p>💥💥<strong>THE</strong> 💥💥</p>
<p>💥💥💥<strong>TIME</strong> 💥💥💥</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/more2.gif" alt="Image" width="600" height="400" loading="lazy">
<em>gif of man saying we need more</em></p>
<p>And yes, it'll work just fine in a Google Sheet to find information.</p>
<p>But sometimes I want to display a range of results based on a word I'm searching for. To do that, we'll create a dynamic search bar in our Google Sheet.</p>
<p>You can also follow along in this walkthrough video:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/5xgwvokDhT0" 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-the-search-bar">The Search Bar</h2>
<p>Our search bar is nothing more than a cell or range of cells. In the example below, it starts in J2.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/google-sheets-search-bar.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of a search bar on Google Sheets</em></p>
<p>By adding a <code>=QUERY()</code> function in J5 we can look at whatever is typed into <code>J2</code> (the red circle) and display the search results below it (the blue rectangle).</p>
<p>In my example, I'm searching through a bunch of personal finance transactions (with randomized amounts😀) that are in columns <code>A:F</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/google-sheets-finance-data.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of finance data on Google Sheets</em></p>
<h2 id="heading-the-query-function">The Query Function</h2>
<p>The <code>=QUERY()</code> function looks in the Transactions range (which is that <code>A3:F</code> range where all the transactions live).</p>
<p>And it grabs all the info in either column B or column D that <code>CONTAINS</code> what we type in <code>J2</code>.</p>
<p>So it searches through all our transaction descriptions in column B and categories in column D for whatever we type in <code>J2</code>. The <code>LOWER</code> command turns the info in B and D into lowercase. This makes it easier to search because the <code>CONTAINS</code> command is case-sensitive.</p>
<pre><code class="lang-javascript">=QUERY(Transactions, 
       <span class="hljs-string">"SELECT A,B,C,D,E WHERE LOWER(B) CONTAINS '"</span>&amp;J2&amp;<span class="hljs-string">"' OR LOWER(D) CONTAINS '"</span>&amp;J2&amp;<span class="hljs-string">"'"</span>)
</code></pre>
<ul>
<li>the only caveat is that if you type in uppercase in the search bar, it won't work properly.</li>
</ul>
<h2 id="heading-the-filter-function">The Filter Function</h2>
<p>By using the <code>=FILTER()</code> function in combination with the <code>=SEARCH()</code> function, we can do the same thing in a little bit shorter formula and without having to worry about case sensitivity.</p>
<pre><code class="lang-xls">=<span class="hljs-built_in">IF</span>(<span class="hljs-built_in">ISBLANK</span>(<span class="hljs-symbol">J2</span>),<span class="hljs-string">""</span>,FILTER(Transactions,<span class="hljs-built_in">SEARCH</span>(<span class="hljs-symbol">J2</span>,<span class="hljs-symbol">B3</span>:<span class="hljs-symbol">B225</span>)))
</code></pre>
<p>The tradeoff here is that when we want to add multiple conditions like we did in the <code>=QUERY()</code> statement, it breaks down. Both <code>=FIND()</code> and <code>=SEARCH()</code> did not work properly when trying to use them more than once inside the <code>=FILTER()</code>.</p>
<p>I was able to find a work around by using the plus operator and constructing the formula in this way:</p>
<pre><code class="lang-xls">=<span class="hljs-built_in">IF</span>(<span class="hljs-built_in">ISBLANK</span>(<span class="hljs-symbol">J2</span>),<span class="hljs-string">""</span>,FILTER(Transactions,(<span class="hljs-symbol">B3</span>:<span class="hljs-symbol">B225</span>=<span class="hljs-symbol">J2</span>)+(<span class="hljs-symbol">D3</span>:<span class="hljs-symbol">D225</span>=<span class="hljs-symbol">J2</span>)))
</code></pre>
<p>Unfortunately when you filter in this way, partial matches are not included in the search results. </p>
<p>In the case of Query, partial answers are always returned. </p>
<p>So, when we enter "hom" all the lines with "home" in it would be returned. When using multiple conditions with filter, nothing would return unless you entered the whole word "home".</p>
<h2 id="heading-what-about-xlookup">What About XLOOKUP?</h2>
<p>The issue with XLOOKUP is twofold. One, it doesn't handle partial matches well unless you add wildcard characters:</p>
<pre><code class="lang-xls">=XLOOKUP(<span class="hljs-string">"*"</span>&amp;<span class="hljs-symbol">J2</span>&amp;<span class="hljs-string">"*"</span>,<span class="hljs-symbol">B3</span>:<span class="hljs-symbol">B225</span>,<span class="hljs-symbol">A3</span>:<span class="hljs-symbol">F225</span>,,<span class="hljs-number">2</span>)
</code></pre>
<p>This increases complexity but still works. </p>
<p>The more important difference is that it will only return one result so it's not going to work at all for us for this use case.</p>
<h2 id="heading-the-winner-is-query">The Winner is Query</h2>
<p>Query takes the prize simply because it doesn't need further manipulation to add multiple conditions, and it will return all the values that meet our search criteria.</p>
<p>It may take you a minute to wrap your head around the syntax, but it's just as powerful and more versatile than Filter in the long haul.</p>
<p>The only thing to make sure to remember is case sensitivity. If you're using the <code>LOWER</code> command in your query, don't use any uppercase search letters. </p>
<h2 id="heading-make-it-neat">Make it Neat</h2>
<p>In the full formula, I've added an <code>=IF()</code> function at the start to handle the blank search bar. We want to return nothing in that case:</p>
<pre><code class="lang-xls">=<span class="hljs-built_in">IF</span>(<span class="hljs-built_in">ISBLANK</span>(<span class="hljs-symbol">J2</span>),<span class="hljs-string">""</span>, QUERY(Transactions <span class="hljs-string">"SELECT A,B,C,D,E WHERE LOWER(B) CONTAINS '"</span>&amp;<span class="hljs-symbol">J2</span>&amp;<span class="hljs-string">"' OR LOWER(D) CONTAINS '"</span>&amp;<span class="hljs-symbol">J2</span>&amp;<span class="hljs-string">"'"</span>)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-30.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of a blank search bar in Google Sheets</em></p>
<h2 id="heading-follow-along">Follow Along</h2>
<p>Come <a target="_blank" href="https://www.youtube.com/@eamonncottrell?sub_confirmation=1">follow me over on YouTube</a> as I make tutorials each week.</p>
<p><a target="_blank" href="https://got-sheet.beehiiv.com/subscribe">Sign up here</a> to get my newsletter in your email each week.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Beehiivp.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Eamonn's Sheets | Coding | Education logo</em></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>
        
    </channel>
</rss>
