<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cisco Vlahakis</title>
    <description>The latest articles on DEV Community by Cisco Vlahakis (@ciscovlahakis).</description>
    <link>https://dev.to/ciscovlahakis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1204521%2Fb93b07e8-6572-4565-9ca1-a9e810aa8528.jpeg</url>
      <title>DEV Community: Cisco Vlahakis</title>
      <link>https://dev.to/ciscovlahakis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ciscovlahakis"/>
    <language>en</language>
    <item>
      <title>Creating a CMS with Firestore, GCS, and Sinatra</title>
      <dc:creator>Cisco Vlahakis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 21:46:37 +0000</pubDate>
      <link>https://dev.to/ciscovlahakis/creating-a-cms-with-firestore-gcs-and-sinatra-3k8o</link>
      <guid>https://dev.to/ciscovlahakis/creating-a-cms-with-firestore-gcs-and-sinatra-3k8o</guid>
      <description>&lt;p&gt;A Content Management System (CMS) is a software application that allows users to create, manage, and modify content on a website without needing to know HTML, CSS, or other programming languages.&lt;/p&gt;

&lt;p&gt;It provides a user-friendly interface where you can create web pages, add content, upload images, and perform lots of other functions without directly manipulating code. Popular examples of CMS's include WordPress, Drupal, and Joomla. These platforms make it possible for individuals and businesses to run and maintain their websites with minimal technical knowledge.&lt;/p&gt;

&lt;p&gt;In my quest to build a super-app, I decided to implement a CMS that would make adding new services a breeze.&lt;/p&gt;

&lt;p&gt;Let's talk about how data would flow in a CMS.&lt;/p&gt;

&lt;p&gt;You could store HTML in Firestore directly, but Firestore has a maximum limit of 1 MiB (1,048,576 bytes) for a single document, including the document key, metadata, and any indexed fields. This is generally more than enough for most HTML content, but if your HTML content is very large, you may run into this limit.&lt;/p&gt;

&lt;p&gt;Storing HTML in a database can have some performance implications. The amount of data that needs to be transmitted and processed can slow down your app, especially if the HTML content is very large.&lt;/p&gt;

&lt;p&gt;Also, keep in mind that databases are not really designed to serve static content like HTML. As an alternative, we will store HTML content in a cloud storage service like Google Cloud Storage. It's designed to serve static content and can handle large amounts of data more efficiently.&lt;/p&gt;

&lt;p&gt;To create a minimally working CMS, we will render a form that lets us upload a local file. The file will then be given a url and stored in GCS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form action="/upload" method="post" enctype="multipart/form-data"&amp;gt;
     &amp;lt;input type="file" name="file"&amp;gt;
     &amp;lt;input type="submit" value="Upload"&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The action of this form post's to a method called "/upload" found in our &lt;code&gt;app.rb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;post("/upload") do
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we will fetch the "file" from params (we named the file input "file"). We will then give it a unique filename. Suppose we are storing HTML for a header in GCS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;post("/upload") do
  # Get the file from the request
  file = params.fetch("file")

  # Create a unique filename
  filename = "header.erb"
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's create GCS and bucket storage objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;post("/upload") do
  ...
  storage = Google::Cloud::Storage.new
  bucket = storage.bucket "cisco-vlahakis.appspot.com"
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production, caching is a great way to save you from being charged queries. Both the browser and GCS can cache data. While this is a great feature in production, it is annoying in development. You will probably be editing files you store in GCS quite often as you develop, which means we want to disable caching for now. If we didn't, we may or may not see changes to our code updated until the specified time has passed. Luckily, we can specify &lt;code&gt;Cache-Control&lt;/code&gt; metadata to prevent caching:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;post("/upload") do
  ...
  # Specify the Cache-Control metadata
  cache_control = "no-cache, no-store, must-revalidate"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are ready to send the HTML to GCS. We send a ref to the file, the filename, our cache rules, and finally, we will set permissions to "publicRead". For now, we would like to set objects created in GCS to Public so that we have permission to use them in development. You can edit these permissions later when ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;post("/upload") do
  ...
  # Upload the file to GCS
  gcs_file = bucket.create_file(file[:tempfile], filename, cache_control: cache_control, acl: "publicRead")
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we will store the url pointing to the GCS HTML file in Firestore. Let's store the header HTML url in a collection called &lt;code&gt;components&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;post("/upload") do
  # Get the public URL of the file
  url = gcs_file.public_url

  # Add a new document to Firestore with the URL
  doc_ref = $firestore.doc("components/#{filename}")
  doc_ref.set({ url: url })
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we will redirect to "/" when done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;post("/upload") do
   ...
   redirect "/"
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose we also have our pages stored in GCS and Firestore. We can now match all routes and have one function to parse and display the components associated with the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get("/*") do
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we will get the route from our browser and fetch the corresponding Page in Firestore with that route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
# Get the route from the URL
  route = request.path_info[1..] # Remove the leading slash

# Fetch the corresponding Page record from Firestore
  pages_col = $firestore.col("pages")
  matching_pages = pages_col.where("route", "=", route).get
  page = matching_pages.first
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the page is not found, we will redirect the user to a 404 page. Otherwise, we will get the components to display for the Page. This is an array of urls to GCS components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
# If the page is not found, redirect to a 404 page
  if page.nil?
    redirect "/404"
  else
    # Get the GCS URLs from the Page document
    gcs_urls = page.data.fetch(:components)
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will now create a helper function called &lt;code&gt;fetch_html_from_gcs&lt;/code&gt; that returns the complete, joined HTML of the fetched components. Afterwards, we will finally render the HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
else
    # Get the GCS URLs from the Page document
    gcs_urls = page.data.fetch(:components)

    # Fetch the HTML content from GCS
    html_content = fetch_html_from_gcs(gcs_urls)

    # Render the HTML content
    # The :page template should be set up to display the raw HTML content
    erb :page, :locals =&amp;gt; { :html_content =&amp;gt; html_content }
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let us build the &lt;code&gt;fetch_html_from_gcs&lt;/code&gt; function now. It will take an array of GCS component urls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def fetch_html_from_gcs(components)
    Array(components).map do |component|
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's fetch the url and continue to next iteration if there is no url. Let's then check that the url starts with http:// or https://:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
# Fetch the HTML template from GCS
url = component.fetch(:url)

# Continue to next iteration if url is not present
next unless url

# Check if the url starts with http:// or https://
unless url.start_with?('http://', 'https://')
  next
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use the &lt;code&gt;http&lt;/code&gt; module to get the HTML template response and parse it to a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
response = HTTP.get(url)
html_template = response.to_s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we will fetch the properties Hash from our component object taken from the Page object in Firestore. The properties Hash defines the custom properties that the Page passes to this component (such as "Home" for :title):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Get the properties for this component
properties = component.fetch(:properties, {})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to send over details about the signed in user and session, this would be the place to do so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;properties["current_user"] = current_user()

# Add session data to the properties
properties["session"] = session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose a component has nested components, like a Sidebar using a Link component. We can store a Hash as a property that stores a url to the nested component HTML in GCS. We can then recursively call fetch_html_from_gcs on the nested components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Look for placeholders in the HTML template and replace them
      # with the HTML content of the nested components
      properties.each do |key, value|
        if value.is_a?(Hash) &amp;amp;&amp;amp; value.has_key?(:url)
          # This is a nested component
          nested_html = fetch_html_from_gcs([value])
          html_template.gsub!("&amp;lt;%= #{key} %&amp;gt;", nested_html)
        end
      end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will render the HTML template with the properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;html_content = 
 ERB.new(html_template).result_with_hash(properties)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we join all the HTML strings with line breaks together together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;html_content
    end.compact.join("\n")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whew! We have successfully stored components (and Pages) in GCS, their urls in the associated Firestore records, and brought them to render in our web app!&lt;/p&gt;

&lt;p&gt;We used a file upload form to send files to GCS, but an extra step would be to create a code editor in the web app itself that submits the code via submit button (and could also let you see the output).&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Creating a Sidebar with Active Links</title>
      <dc:creator>Cisco Vlahakis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 20:25:09 +0000</pubDate>
      <link>https://dev.to/ciscovlahakis/creating-a-sidebar-with-active-links-1j3d</link>
      <guid>https://dev.to/ciscovlahakis/creating-a-sidebar-with-active-links-1j3d</guid>
      <description>&lt;p&gt;Suppose we want to create a sidebar menu that tracks active links.&lt;/p&gt;

&lt;p&gt;First, let's create the sidebar container div:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="sidebar-container"&amp;gt;

&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assume our data-sidebar-links are being passed from an ERB file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="sidebar-container" data-sidebar-links='&amp;lt;%= @sidebar_links %&amp;gt;'&amp;gt;

&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's set a title at the top of the sidebar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="sidebar-container" data-sidebar-links='&amp;lt;%= @sidebar_links %&amp;gt;'&amp;gt;
  &amp;lt;div class="sidebar-heading"&amp;gt;
    &amp;lt;%= @title %&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can loop through each link. We can place an href around a div containing an icon and title for the link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="sidebar-container" data-sidebar-links='&amp;lt;%= @sidebar_links %&amp;gt;'&amp;gt;
  &amp;lt;div class="sidebar-heading"&amp;gt;
    &amp;lt;%= @title %&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div id="menuContainer"&amp;gt;
    &amp;lt;% @sidebar_links.each do |link| %&amp;gt;
      &amp;lt;a href="/&amp;lt;%= link.fetch(:path,'') %&amp;gt;" class="list-group-item sidebar-link"&amp;gt;
        &amp;lt;i class="&amp;lt;%= link.fetch(:icon,'') %&amp;gt;"&amp;gt;&amp;lt;/i&amp;gt;
        &amp;lt;%= link.fetch(:title,'') %&amp;gt;
      &amp;lt;/a&amp;gt;
    &amp;lt;% end %&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's tackle our JS. First, we are going to add a listener for when content has loaded in the DOM to make sure we add the listeners on the sidebar at the right time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.addEventListener('DOMContentLoaded', (event) =&amp;gt; {

});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's grab the url in the browser to see which link should be active. We are also going to set up a reference to the links:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.addEventListener('DOMContentLoaded', (event) =&amp;gt; {
  const currentPath = window.location.pathname;
  const sidebarLinks = document.querySelectorAll('.sidebar-link');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's loop through each link and reset their active status. To do this, we will remove the active class style from all links then reset the active class style only to the link who's href is the browser's url:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.addEventListener('DOMContentLoaded', (event) =&amp;gt; {
  const currentPath = window.location.pathname;
  const sidebarLinks = document.querySelectorAll('.sidebar-link');
  sidebarLinks.forEach((link) =&amp;gt; {
    link.classList.remove('active');
    if (link.getAttribute('href') === currentPath) {
      link.classList.add('active');
    }
  });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can add styling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#sidebar-container {
  width: 20vw;
  height: 100vh;
  min-height: 100vh;
  background-color: black;
  color: #fff;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;#sidebar-container&lt;/code&gt;: This is a CSS ID selector. It selects the HTML element with &lt;code&gt;id="sidebar-container"&lt;/code&gt;. It sets the width to 20% of the viewport’s width (vw), the height to 100% of the viewport’s height (vh), and the minimum height to 100vh, ensuring that it always takes up at least the full height of the viewport. It also sets the background color to black and the text color to white.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#sidebar-container .sidebar-heading {
  font-size: calc(1.275rem + .3vw)!important;
  border-bottom: 1px solid #EEEEEE;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;#sidebar-container .sidebar-heading&lt;/code&gt;: This is a CSS descendant selector. It selects any element with the class &lt;code&gt;.sidebar-heading&lt;/code&gt; that is a descendant of &lt;code&gt;#sidebar-container&lt;/code&gt;. It sets the font size using a &lt;code&gt;calc()&lt;/code&gt; function, which lets you perform calculations to determine CSS property values. The &lt;code&gt;!important&lt;/code&gt; declaration increases the priority of this CSS rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#sidebar-container .list-group-item {
  display: flex;
  text-decoration: none;
  color: white;
  transition: background-color 0.3s;
  border: none;
  font-size: 1rem;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;#sidebar-container .list-group-item&lt;/code&gt;: This selects any element with the class &lt;code&gt;.list-group-item&lt;/code&gt; that is a descendant of &lt;code&gt;#sidebar-container&lt;/code&gt;. It sets the display property to flex, allowing for flexible layouts. It also removes text decoration (like underlines), sets the color to white, adds a transition effect to the background color, removes the border, and sets the font size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#sidebar-container .sidebar-heading,
#sidebar-container .list-group-item {
  padding: 1.25rem;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;#sidebar-container .sidebar-heading, #sidebar-container .list-group-item&lt;/code&gt;: This selects both &lt;code&gt;.sidebar-heading&lt;/code&gt; and &lt;code&gt;.list-group-item&lt;/code&gt; elements inside &lt;code&gt;#sidebar-container&lt;/code&gt; and sets their padding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#sidebar-container .list-group-item:hover,
#sidebar-container .list-group-item.active {
  background-color: #343a40;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;#sidebar-container .list-group-item:hover, #sidebar-container .list-group-item.active&lt;/code&gt;: This applies styles to &lt;code&gt;.list-group-item&lt;/code&gt; elements when they are being hovered over (mouse pointer is on it) or they have the &lt;code&gt;active&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#sidebar-container .list-group-item i {
  font-size: 1.2em;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;#sidebar-container .list-group-item i&lt;/code&gt;: This selects i elements inside &lt;code&gt;.list-group-item&lt;/code&gt; elements and sets their font size.&lt;/p&gt;

&lt;p&gt;Hope you enjoyed creating a sidebar!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Recurrence rules via cascading dropdowns</title>
      <dc:creator>Cisco Vlahakis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 16:58:06 +0000</pubDate>
      <link>https://dev.to/ciscovlahakis/recurrence-rules-via-cascading-dropdowns-1l2i</link>
      <guid>https://dev.to/ciscovlahakis/recurrence-rules-via-cascading-dropdowns-1l2i</guid>
      <description>&lt;p&gt;Suppose we have the following recurrence rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Daily&lt;/li&gt;
&lt;li&gt;By day of week (Every Monday, Every Tuesday, etc.)&lt;/li&gt;
&lt;li&gt;Monthly, By day of week&lt;/li&gt;
&lt;li&gt;Monthly, By day of month&lt;/li&gt;
&lt;li&gt;Every certain date (Every 1st, Every 2nd, etc.)&lt;/li&gt;
&lt;li&gt;By a range of days of week (From Monday to Wednesday)&lt;/li&gt;
&lt;li&gt;By a range of days of month (From 1st to 3rd)&lt;/li&gt;
&lt;li&gt;By a range of dates (From 10/1 to 12/7)&lt;/li&gt;
&lt;li&gt;Between a start time and end time (From 7 AM to 9 AM)&lt;/li&gt;
&lt;li&gt;Monthly (or Every 2 months, Every 3 months, etc.)&lt;/li&gt;
&lt;li&gt;Starting on a date (No end date)&lt;/li&gt;
&lt;li&gt;Ends on a certain date&lt;/li&gt;
&lt;li&gt;Until a number of recurrences&lt;/li&gt;
&lt;li&gt;Yearly recurrence: “every year”, “every January 1”, etc.&lt;/li&gt;
&lt;li&gt;Recurrence on specific dates: “on January 1”, “on July 4”, etc.&lt;/li&gt;
&lt;li&gt;Recurrence every X days/weeks/months/years: “every 2 days”, “every 3 weeks”, “every 4 months”, “every 5 years”, etc.&lt;/li&gt;
&lt;li&gt;Recurrence on the last day of the month: “every last day of the month”, “every last Friday of the month”, etc.&lt;/li&gt;
&lt;li&gt;Recurrence on a specific day of the week in a month: “every 1st Monday of the month”, “every last Friday of the month”, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are a few ways to implement recurrence rules. The most popular is a custom form with varying radio buttons for various use cases. A more elegant but complex solution is a single text input that parses the user's recurrence rule using NLP. And a more elegant but simpler solution is to allow the user to build their recurrence rule via dropdowns that suggest phrases depending on the last dropdown selections. For example:&lt;/p&gt;

&lt;p&gt;Dropdown 1 = “Every”, “From”, “Starting [Date]”, “For”, “On”&lt;/p&gt;

&lt;p&gt;Dropdown 2 = “Monday”, “Tuesday”, …, “Day”, “Week”, “Month”, “Year”, 1, 2, 3, 4 … 30, 12 AM, 1 AM, …, 1/1, 1/2, …, last&lt;/p&gt;

&lt;p&gt;Dropdown 3 = “-st”, “-rd”, “-th”, “at”, “until”, “from”, “occurrences”, “days”, “weeks”, “months”, “years”, “day”, “Monday”, “Tuesday”&lt;/p&gt;

&lt;p&gt;Dropdown 4 = “12 AM, 1 AM, …, “Monday”, “Tuesday, …, 1/1, 1/2, …”&lt;/p&gt;

&lt;p&gt;Dropdown 5 = “Until”&lt;/p&gt;

&lt;p&gt;Dropdown 6 = “12 AM, 1 AM, …”&lt;/p&gt;

&lt;p&gt;It is important to ensure all your recurrence rules can be implemented via each selection path.&lt;/p&gt;

&lt;p&gt;Implementing such a feature can be complex, but let's take it step by step!&lt;/p&gt;

&lt;p&gt;Let's make sure that the JavaScript code doesn’t run until the webpage has fully loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a helper function that creates the HTML structure for a dropdown menu with a specific index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {

  function createDropdownHTML(index) {
    return `
      &amp;lt;div class="dropdown" id="dropdown${index}" style="display: none;"&amp;gt;
        &amp;lt;button class="button"&amp;gt;Select:&amp;lt;/button&amp;gt;
        &amp;lt;div class="dropdown-content"&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;input type="hidden" id="recurrence${index}" name="recurrence${index}"&amp;gt;
      &amp;lt;/div&amp;gt;
    `;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we add four dropdown menus to the ‘dropdowns-container’ element. We can change 4 to be a larger number in the future the more use cases are added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
...
     var dropdownsContainer = document.getElementById('dropdowns-container');
     var dropdownsCount = 4;
     for (let i = 1; i &amp;lt;= dropdownsCount; i++) {
         dropdownsContainer.innerHTML += createDropdownHTML(i);
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we create an object &lt;code&gt;dropdownDependencies&lt;/code&gt; that maps each possible option to its corresponding options in the next dropdown menu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
...
    var dropdownDependencies = {
    'START': ['Every', 'For'],
    'Every': ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'Day', 'Week', 'Month', 'Year'],
    'For': ['1', '2', '3'],
    'Monday': ['For', 'Until'],
    'Tuesday': ['For', 'Until'],
    'Wednesday': ['For', 'Until'],
    'Thursday': ['For', 'Until'],
    'Friday': ['For', 'Until'],
    'Saturday': ['For', 'Until'],
    'Sunday': ['For', 'Until'],
    'Day': ['For', 'Until'],
    'Week': ['For', 'Until'],
    'Month': ['For', 'Until'],
    'Year': ['For', 'Until'],
    'For': ['1', '2', '3'],
    'Until': ['Date'],
    '1': [], // Add a corresponding array for '1'
    '2': [], // Add a corresponding array for '2'
    '3': [], // Add a corresponding array for '3'
    'Date': [] // Add a corresponding array for 'Date'
  };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we loop to create each dropdown menu and add them to an array. We then set the initial options for the first dropdown menu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
...
     // Initialize the dropdowns
     var dropdowns = [];
     for (let i = 1; i &amp;lt;= dropdownsCount; i++) {
         dropdowns.push(createDropdown(`dropdown${i}`, ".button", ".dropdown-content", `recurrence${i}`));
     }

     // Set the initial options for the first dropdown
     updateDropdownOptions(dropdowns[0], dropdownDependencies['START']);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose we want to show or hide the dropdown menus depending on if a user even wants to create a recurrence rule while they are viewing a form. We can reference a checkbox that will show or hide the dropdown menus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
...
     var isRecurrent = document.getElementById("is-recurrent");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let's create a helper function that updates the options of a given dropdown and sets up the behavior for when an option is selected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clear any existing options in the dropdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     dropdown.content.innerHTML = '';
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure that “Select:” is always an option in the dropdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     if (options[0] !== "Select:") {
      options.unshift("Select:");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go through each option in the options array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     for (let i = 0; i &amp;lt; options.length; i++) {

     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the text of the created paragraph element to the option. We will be using a custom hidden input instead of a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     for (let i = 0; i &amp;lt; options.length; i++) {
      let p = document.createElement('p');
      p.textContent = options[i];
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a data attribute on the paragraph element to the option. This will be used to get the selected option when it’s clicked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     for (let i = 0; i &amp;lt; options.length; i++) {
      ...
      p.setAttribute('data-value', options[i]);
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set up the behavior when an option is clicked. Prevent the click event from bubbling up to parent elements and stop the default action of the click event. Without these, clicking on an option could trigger other unrelated click events on parent elements or cause the page to refresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     for (let i = 0; i &amp;lt; options.length; i++) {
      ...
      p.onclick = function (e) {
        e.preventDefault();
        e.stopPropagation();
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store the previous value of the dropdown menu. This is used later to check if the selected option has changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     for (let i = 0; i &amp;lt; options.length; i++) {
      ...
      p.onclick = function (e) {
        ...
        var previousValue = dropdown.input.value;
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the displayed value of the dropdown to the selected option and set the hidden input’s value to the selected option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     for (let i = 0; i &amp;lt; options.length; i++) {
      ...
      p.onclick = function (e) {
        ...
        var previousValue = dropdown.input.value;
        dropdown.button.textContent = p.textContent;
        dropdown.input.value = p.getAttribute('data-value');
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hide the dropdown menu after an option is selected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     for (let i = 0; i &amp;lt; options.length; i++) {
      ...
      p.onclick = function (e) {
        ...
        dropdown.content.style.display = 'none';
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check if the selected option is different from the previously selected option. If so, we hide all the following dropdown menus and reset their selections. This is done because changing an option could change the options in the following dropdown menus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     for (let i = 0; i &amp;lt; options.length; i++) {
      ...
      p.onclick = function (e) {
        ...
        if (dropdown.input.value !== previousValue) {
          var currentDropdown = dropdown.dropdown.nextElementSibling;
          while (currentDropdown) {
            var currentButton = currentDropdown.querySelector(".button");
            var currentInput = currentDropdown.querySelector('input[type="hidden"]');
            currentDropdown.style.display = 'none';
            currentButton.textContent = 'Select:';
            currentInput.value = '';
            currentDropdown = currentDropdown.nextElementSibling;
          }
        }
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there is a dropdown menu following the current one, update the following dropdown’s options based on the selected option. This is where the cascading behavior of the dropdown menus comes from. The options in the following dropdown depend on the selected option in the current dropdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     for (let i = 0; i &amp;lt; options.length; i++) {
      ...
      p.onclick = function (e) {
        ...
        if (dropdown.dropdown.nextElementSibling) {
          var nextDropdown = dropdown.dropdown.nextElementSibling;
          var nextDropdownContent = nextDropdown.querySelector(".dropdown-content");
          var nextOptions = dropdownDependencies[dropdown.input.value];
          if (nextOptions) {
            nextDropdown.style.display = 'inline-block';
            updateDropdownOptions({
              dropdown: nextDropdown,
              button: nextDropdown.querySelector(".button"),
              content: nextDropdownContent,
              input: nextDropdown.querySelector('input[type="hidden"]')
            }, nextOptions);
          }
        }
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it for the &lt;code&gt;p.onclick&lt;/code&gt; function. Now, add the paragraph element and it's &lt;code&gt;onclick&lt;/code&gt; listener as an option to the dropdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function updateDropdownOptions(dropdown, options) {
     ...
     for (let i = 0; i &amp;lt; options.length; i++) {
      ...
      p.onclick = function (e) {
        ...
     }
     dropdown.content.appendChild(p);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it for &lt;code&gt;updateDropdownOptions&lt;/code&gt;. Next, let's create a helper function called &lt;code&gt;createDropdown&lt;/code&gt;. The function &lt;code&gt;createDropdown&lt;/code&gt; is defined with four parameters: &lt;code&gt;dropdownId&lt;/code&gt;, &lt;code&gt;buttonId&lt;/code&gt;, &lt;code&gt;contentId&lt;/code&gt;, &lt;code&gt;inputId&lt;/code&gt;. These are supposed to be the id of the dropdown, the button, the content of the dropdown, and the input field within the dropdown, respectively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
...
  function createDropdown(dropdownId, buttonId, contentId, inputId, nextDropdownId) {

  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select the corresponding HTML elements based on the provided id or class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
...
  function createDropdown(dropdownId, buttonId, contentId, inputId, nextDropdownId) {
    var dropdown = document.getElementById(dropdownId);
    var button = dropdown.querySelector(buttonId);
    var content = dropdown.querySelector(contentId);
    var input = document.getElementById(inputId);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define what should happen when the button in the dropdown is clicked. We toggle the display style of the dropdown content between ‘block’ (visible) and ‘none’ (hidden):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
...
  function createDropdown(dropdownId, buttonId, contentId, inputId, nextDropdownId) {
    ...
    button.onclick = function (e) {
         e.preventDefault();
         e.stopPropagation();
         content.style.display = content.style.display === 'block' ? 'none' : 'block';
    };
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get the initial options from the dropdown content by selecting all &lt;code&gt;p&lt;/code&gt; elements within the content (which represent the options), and mapping these elements to their data-value attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
...
  function createDropdown(dropdownId, buttonId, contentId, inputId, nextDropdownId) {
    ...
    var initialOptions = Array.from(content.querySelectorAll('p')).map(function (p) {
      return p.getAttribute('data-value');
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call the &lt;code&gt;updateDropdownOptions&lt;/code&gt; function to update the dropdown’s options to the &lt;code&gt;initialOptions&lt;/code&gt;. Pass an object that includes the dropdown, button, content, and input elements, as well as the &lt;code&gt;initialOptions&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
...
  function createDropdown(dropdownId, buttonId, contentId, inputId, nextDropdownId) {
    ...
    updateDropdownOptions({
      dropdown: dropdown,
      button: button,
      content: content,
      input: input
    }, initialOptions);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, return an object including the dropdown, button, content, and input elements. This is used later to manipulate the dropdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
...
  function createDropdown(dropdownId, buttonId, contentId, inputId, nextDropdownId) {
    ...
    updateDropdownOptions({
      dropdown: dropdown,
      button: button,
      content: content,
      input: input
    }, initialOptions);

    return {
      dropdown: dropdown,
      button: button,
      content: content,
      input: input
    };
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;createDropdown&lt;/code&gt; function is complete. Now, we need to set up the behavior when the checkbox is checked or unchecked. When it’s checked, the first dropdown menu is shown. When it’s unchecked, all the dropdown menus are hidden:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isRecurrent.onchange = function () {
    if (this.checked) {
      dropdowns[0].dropdown.style.display = "inline-block";
    } else {
      dropdowns.forEach(function (dropdown) {
        dropdown.dropdown.style.display = "none";
        dropdown.button.textContent = "Select:";
        dropdown.input.value = "";
      });
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And make sure to set the initial visibility of the dropdowns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isRecurrent.onchange();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JS is complete! Now let's add the checkbox and &lt;code&gt;dropdowns-container&lt;/code&gt; to our HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;span&amp;gt;Is this a recurrent event?&amp;lt;/span&amp;gt;
&amp;lt;input type="checkbox" id="is-recurrent" name="is-recurrent" class="large-checkbox"&amp;gt;
&amp;lt;div id="recurrence-wrapper"&amp;gt;
  &amp;lt;div id="dropdowns-container"&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now onto styling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// styles the wrapper for the dropdown menus.
#recurrence-wrapper {
  display: block;
}

// styles the dropdown select menus.
.recurrence-select {
  display: none;
}

// styles the checkbox that shows or hides the dropdown menus.
.large-checkbox {
  height: 20px;
  width: 20px;
  vertical-align: middle;
}

// styles the dropdown menus.
.dropdown {
  position: relative;
  display: inline-block;
}

// styles the content within the dropdown menus. The zIndex is set to 9999 because this component is within a modal for my use case.
.dropdown-content {
  display: none;
  position: absolute;
  padding: 0;
  margin: 0;
  z-index: 9999;
}

// styles the individual options within the dropdown menus.
.dropdown-content p {
  padding: 10px 20px;
  margin: 0;
  background-color: #fff;
  color: #000;
  font-size: 14px;
  cursor: pointer;
}

// styles the options when they’re hovered over or selected.
.dropdown-content p:hover,
.dropdown-content p.selected {
  background-color: #ccc;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the full JS from earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {

  function createDropdownHTML(index) {
    return `
      &amp;lt;div class="dropdown" id="dropdown${index}" style="display: none;"&amp;gt;
        &amp;lt;button class="button"&amp;gt;Select:&amp;lt;/button&amp;gt;
        &amp;lt;div class="dropdown-content"&amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;input type="hidden" id="recurrence${index}" name="recurrence${index}"&amp;gt;
      &amp;lt;/div&amp;gt;
    `;
  }

  var dropdownsContainer = document.getElementById('dropdowns-container');
  var dropdownsCount = 4;
  for (let i = 1; i &amp;lt;= dropdownsCount; i++) {
    dropdownsContainer.innerHTML += createDropdownHTML(i);
  }  

  var dropdownDependencies = {
    'START': ['Every', 'For'],
    'Every': ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', 'Day', 'Week', 'Month', 'Year'],
    'For': ['1', '2', '3'],
    'Monday': ['For', 'Until'],
    'Tuesday': ['For', 'Until'],
    'Wednesday': ['For', 'Until'],
    'Thursday': ['For', 'Until'],
    'Friday': ['For', 'Until'],
    'Saturday': ['For', 'Until'],
    'Sunday': ['For', 'Until'],
    'Day': ['For', 'Until'],
    'Week': ['For', 'Until'],
    'Month': ['For', 'Until'],
    'Year': ['For', 'Until'],
    'For': ['1', '2', '3'],
    'Until': ['Date'],
    '1': [], // Add a corresponding array for '1'
    '2': [], // Add a corresponding array for '2'
    '3': [], // Add a corresponding array for '3'
    'Date': [] // Add a corresponding array for 'Date'
  };

  // Initialize the dropdowns
  var dropdowns = [];
  for (let i = 1; i &amp;lt;= dropdownsCount; i++) {
    dropdowns.push(createDropdown(`dropdown${i}`, ".button", ".dropdown-content", `recurrence${i}`));
  }

  // Set the initial options for the first dropdown
  updateDropdownOptions(dropdowns[0], dropdownDependencies['START']);

  var isRecurrent = document.getElementById("is-recurrent");

  function updateDropdownOptions(dropdown, options) {

    dropdown.content.innerHTML = '';

    // Add "Select:" as an option
    if (options[0] !== "Select:") {
      options.unshift("Select:");
    }

    for (let i = 0; i &amp;lt; options.length; i++) {
      let p = document.createElement('p');
      p.textContent = options[i];
      p.setAttribute('data-value', options[i]);

      p.onclick = function (e) {
        e.preventDefault();
        e.stopPropagation();
        var previousValue = dropdown.input.value;
        dropdown.button.textContent = p.textContent;
        dropdown.input.value = p.getAttribute('data-value');
        dropdown.content.style.display = 'none';
        if (dropdown.input.value !== previousValue) {
          var currentDropdown = dropdown.dropdown.nextElementSibling;
          while (currentDropdown) {
            var currentButton = currentDropdown.querySelector(".button");
            var currentInput = currentDropdown.querySelector('input[type="hidden"]');
            currentDropdown.style.display = 'none';
            currentButton.textContent = 'Select:';
            currentInput.value = '';
            currentDropdown = currentDropdown.nextElementSibling;
          }
        }
        if (dropdown.dropdown.nextElementSibling) {
          var nextDropdown = dropdown.dropdown.nextElementSibling;
          var nextDropdownContent = nextDropdown.querySelector(".dropdown-content");
          var nextOptions = dropdownDependencies[dropdown.input.value];
          if (nextOptions) {
            nextDropdown.style.display = 'inline-block';
            updateDropdownOptions({
              dropdown: nextDropdown,
              button: nextDropdown.querySelector(".button"),
              content: nextDropdownContent,
              input: nextDropdown.querySelector('input[type="hidden"]')
            }, nextOptions);
          }
        }
      };

      dropdown.content.appendChild(p);
    }

    if (dropdown.dropdown.nextElementSibling) {
      var currentDropdown = dropdown.dropdown.nextElementSibling;
      while (currentDropdown) {
        var currentButton = currentDropdown.querySelector(".button");
        var currentInput = currentDropdown.querySelector('input[type="hidden"]');
        currentDropdown.style.display = 'none';
        currentButton.textContent = 'Select:';
        currentInput.value = '';
        currentDropdown = currentDropdown.nextElementSibling;
      }
    }
  }

  function createDropdown(dropdownId, buttonId, contentId, inputId, nextDropdownId) {
    var dropdown = document.getElementById(dropdownId);
    var button = dropdown.querySelector(buttonId);
    var content = dropdown.querySelector(contentId);
    var input = document.getElementById(inputId);

    button.onclick = function (e) {
      e.preventDefault();
      e.stopPropagation();
      content.style.display = content.style.display === 'block' ? 'none' : 'block';
    };

    var initialOptions = Array.from(content.querySelectorAll('p')).map(function (p) {
      return p.getAttribute('data-value');
    });

    updateDropdownOptions({
      dropdown: dropdown,
      button: button,
      content: content,
      input: input
    }, initialOptions);

    return {
      dropdown: dropdown,
      button: button,
      content: content,
      input: input
    };
  }

  // If checkbox is unchecked, hide all dropdowns and reset their selections
  isRecurrent.onchange = function () {
    if (this.checked) {
      dropdowns[0].dropdown.style.display = "inline-block";
    } else {
      dropdowns.forEach(function (dropdown) {
        dropdown.dropdown.style.display = "none";
        dropdown.button.textContent = "Select:";
        dropdown.input.value = "";
      });
    }
  }

  // Call the onchange function to set initial visibility of dropdowns
  isRecurrent.onchange();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whew! We have created a cascading dropdowns component for recurrence rules.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Recurrence rules with NLP</title>
      <dc:creator>Cisco Vlahakis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 15:42:20 +0000</pubDate>
      <link>https://dev.to/ciscovlahakis/recurrence-rules-with-nlp-6p0</link>
      <guid>https://dev.to/ciscovlahakis/recurrence-rules-with-nlp-6p0</guid>
      <description>&lt;p&gt;Recurrence rules allow users to set the frequency of events, such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Daily&lt;/li&gt;
&lt;li&gt;By day of week (Every Monday, Every Tuesday, etc.)&lt;/li&gt;
&lt;li&gt;Monthly, By day of week&lt;/li&gt;
&lt;li&gt;Monthly, By day of month&lt;/li&gt;
&lt;li&gt;Every certain date (Every 1st, Every 2nd, etc.)&lt;/li&gt;
&lt;li&gt;By a range of days of week (From Monday to Wednesday)&lt;/li&gt;
&lt;li&gt;By a range of days of month (From 1st to 3rd)&lt;/li&gt;
&lt;li&gt;By a range of dates (From 10/1 to 12/7)&lt;/li&gt;
&lt;li&gt;Between a start time and end time (From 7 AM to 9 AM)&lt;/li&gt;
&lt;li&gt;Monthly (or Every 2 months, Every 3 months, etc.)&lt;/li&gt;
&lt;li&gt;Starting on a date (No end date)&lt;/li&gt;
&lt;li&gt;Ends on a certain date&lt;/li&gt;
&lt;li&gt;Until a number of recurrences&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Almost all apps (think Google Calendar) implement recurrence rules via a form. Radio buttons are used for different cases. However, what if we wanted to condense the form into a single text input that lets the user type what they want?&lt;/p&gt;

&lt;p&gt;The feature we’re describing is known as natural language processing (NLP), which is a complex field of AI that involves interpreting, recognizing, and understanding human language in a valuable way.&lt;/p&gt;

&lt;p&gt;It can be challenging to implement this feature because of the complexities involved with understanding and interpreting human language. We need to consider a wide range of possible user inputs and how to parse and interpret them.&lt;/p&gt;

&lt;p&gt;However, there are libraries and APIs available that can make this task easier! For example, the &lt;code&gt;chrono&lt;/code&gt; JavaScript library can parse dates written in natural language.&lt;/p&gt;

&lt;p&gt;Here’s a simple example of how we could implement recurrence rules via NLP:&lt;/p&gt;

&lt;p&gt;Listen for changes to the recurrence-rule input field.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.getElementById('recurrence-rule').addEventListener('change', function() {

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the field is changed, we parse the user’s input using the &lt;code&gt;chrono&lt;/code&gt; library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.getElementById('recurrence-rule').addEventListener('change', function() {
     var parsedResult = chrono.parse(this.value);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;chrono&lt;/code&gt; is unable to parse the user’s input, we will display a message asking the user to try again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.getElementById('recurrence-rule').addEventListener('change', function() {
     var parsedResult = chrono.parse(this.value);
     // If chrono couldn't parse the input, show a default message.
    if (parsedResult.length === 0) {
        document.getElementById('suggested-rule').textContent = "Sorry, we couldn't understand your recurrence rule. Please try again.";
        return;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We then check whether or not the parsed result is recurrent (i.e., whether or not it’s certain of the weekday). If the event is recurrent, we suggest a rule of “Every [Day of Week]”. If the parsed date is certain of the weekday and the user’s input includes the word “other”, it suggests a rule of “Every other [Day of Week]”. If the user’s input includes the word “weekday”, it suggests a rule of “Every weekday”.&lt;/p&gt;

&lt;p&gt;If the event is not recurrent, we suggest a rule of “Once on [Date]”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.getElementById('recurrence-rule').addEventListener('change', function() {
     var parsedResult = chrono.parse(this.value);
     // If chrono couldn't parse the input, show a default message.
    if (parsedResult.length === 0) {
        document.getElementById('suggested-rule').textContent = "Sorry, we couldn't understand your recurrence rule. Please try again.";
        return;
    }
    // Extract the relevant information from the parsed result.
    var date = parsedResult[0].start.date();
    var isRecurrent = parsedResult[0].start.isCertain('weekday');

    // Create the suggested recurrence rule.
    var suggestedRule;
    if(isRecurrent) {
        var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
        var dayOfWeek = days[date.getDay()];
        var everyOther = this.value.toLowerCase().includes('other') ? 'other ' : '';
        suggestedRule = `Every ${everyOther}${dayOfWeek}`;
    } else if(this.value.toLowerCase().includes('weekday')) {
        suggestedRule = 'Every weekday';
    } else {
        suggestedRule = `Once on ${date.toLocaleDateString()}`;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we display the suggested recurrence rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;document.getElementById('recurrence-rule').addEventListener('change', function() {
...
// Display the suggested recurrence rule to the user for confirmation.
    document.getElementById('suggested-rule').textContent = `Suggested Rule: ${suggestedRule}`;  
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, this function becomes much more complex the more use cases we implement. We also need to add additional checks for the validity of the user's input. However, this is a great start to using NLP to condense a form of recurrence rule radio buttons to a sleek, minimal input. Remember, great implementation abstracts the complex details behind the scenes and provides a simple interface to the user.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Using Firestore with ERB view and table</title>
      <dc:creator>Cisco Vlahakis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 14:53:52 +0000</pubDate>
      <link>https://dev.to/ciscovlahakis/using-firestore-with-erb-view-and-table-5hp</link>
      <guid>https://dev.to/ciscovlahakis/using-firestore-with-erb-view-and-table-5hp</guid>
      <description>&lt;p&gt;Here is a simple example for querying data from Firestore to be displayed in a table. Ensure that you have Firestore properly installed and configured.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define a route that gets the documents (in this example, meal_events) from Firestore and renders a view:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'sinatra'
require 'firebase'

get '/meal_events' do
  base_uri = 'https://&amp;lt;your-firebase-app&amp;gt;.firebaseio.com/'
  firebase = Firebase::Client.new(base_uri)
  response = firebase.get('meal_events')
  @meal_events = response.body

  erb :meal_events
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this handler, we’re using the firebase gem to connect to Firestore, then we’re fetching the meal events and storing them in the instance variable @meal_events. This instance variable will be available in our view.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Next, in your views/meal_events.erb file, you can iterate over @meal_events to generate your table rows:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;table&amp;gt;
     &amp;lt;thead&amp;gt;...&amp;lt;/thead&amp;gt;
     &amp;lt;tbody&amp;gt;
          &amp;lt;% @meal_events.each do |meal_event| %&amp;gt;
               &amp;lt;tr&amp;gt;
                    &amp;lt;td&amp;gt;&amp;lt;%= meal_event.fetch('name') %&amp;gt;&amp;lt;/td&amp;gt;
                    &amp;lt;td&amp;gt;&amp;lt;%= meal_event.fetch('parent_meal_event') %&amp;gt;&amp;lt;/td&amp;gt;
                    &amp;lt;td&amp;gt;&amp;lt;%= meal_event.fetch('min_foods') %&amp;gt;&amp;lt;/td&amp;gt;
                    &amp;lt;td&amp;gt;&amp;lt;%= meal_event.fetch('max_foods') %&amp;gt;&amp;lt;/td&amp;gt;
               &amp;lt;/tr&amp;gt;
          &amp;lt;% end %&amp;gt;
     &amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a new table row for each meal event, with the table data filled in from the meal event.&lt;/p&gt;

&lt;p&gt;Remember to replace &lt;code&gt;&amp;lt;your-firebase-app&amp;gt;&lt;/code&gt; with your actual Firebase app’s name!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Opening and closing a modal with HTML, CSS, and JS</title>
      <dc:creator>Cisco Vlahakis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 14:42:00 +0000</pubDate>
      <link>https://dev.to/ciscovlahakis/opening-and-closing-a-modal-with-ruby-9bg</link>
      <guid>https://dev.to/ciscovlahakis/opening-and-closing-a-modal-with-ruby-9bg</guid>
      <description>&lt;p&gt;Modals are some of the most common ways to display a pertinent action to the user while keeping them tethered to the main content they were at.&lt;/p&gt;

&lt;p&gt;There are several steps to creating and using a modal. The first is to create a JS file and within that, begin by assigning references to your modal, a button to open it (in this example, &lt;code&gt;createButton&lt;/code&gt;, and a button to close it (in this example, &lt;code&gt;cancelButton&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let modal = document.getElementById('meal-event-modal');
let createButton = document.getElementById('create-button');
let cancelButton = document.getElementById('cancel-button');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You then want to create listeners for these elements that listen for mouse clicks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createButton.addEventListener('click', function() {
    modal.style.display = 'block';
});

cancelButton.addEventListener('click', function() {
    modal.style.display = 'none';
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have created references and listeners, but have not actually coded the modal in HTML nor provided styling. Let's do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.modal {
    display: none; /* Start off hidden */
    position: fixed; /* Stay in place */
    z-index: 1; /* Sit on top */
    left: 0;
    top: 0;
    width: 100%; /* Full width */
    height: 100%; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
    padding-top: 60px; /* Location of the box */
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above is a common pattern for styling modals. It begins by being hidden. A listener listens for click events on the button to open the modal and change the display to block. We then set a z-index so that it renders above everything. You may need a higher z-index value depending on the highest z-index value being used in the background. We set the modal left and top, give it a full width and height, enable scroll if needed, make the background color black with opacity, and add some padding for the form being displayed. Note that the above CSS is for the background of the modal. The contents can be styled as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.meal-event-form {
    background-color: #fefefe;
    margin: auto;
    padding: 20px;
    border: 1px solid #888;
    width: 80%;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To recap, the above CSS will give your modal a semi-transparent black background that covers the entire screen, and your form will have a white background with a bit of padding and a border. The form is centered on the screen and takes up 80% of the width of the screen.&lt;/p&gt;

&lt;p&gt;Now we need to actually create the modal in our HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="modal" id="meal-event-modal"&amp;gt;
    &amp;lt;div class="modal-content"&amp;gt;
        &amp;lt;h2&amp;gt;Create Meal Event&amp;lt;/h2&amp;gt;
        &amp;lt;form class="meal-event-form"&amp;gt;
            &amp;lt;label for="name"&amp;gt;Name:&amp;lt;/label&amp;gt;
            &amp;lt;input type="text" id="name" name="name"&amp;gt;
            &amp;lt;input type="submit" value="Submit"&amp;gt;
        &amp;lt;/form&amp;gt;
        &amp;lt;button id="cancel-button"&amp;gt;Cancel&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What if you want to close the modal when the user clicks outside the form? To do that, we need a listener on the window itself. Put this in your JS file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onclick = function(event) {
    if (event.target == modal) {
        modal.style.display = 'none';
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The display is set to 'none' when a user clicks outside the form.&lt;/p&gt;

&lt;p&gt;That's it!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Using Firestore in a Sinatra Web App</title>
      <dc:creator>Cisco Vlahakis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 14:27:02 +0000</pubDate>
      <link>https://dev.to/ciscovlahakis/using-firestore-in-a-sinatra-web-app-5fee</link>
      <guid>https://dev.to/ciscovlahakis/using-firestore-in-a-sinatra-web-app-5fee</guid>
      <description>&lt;p&gt;Firestore is a NoSql DB created by Google that allows for simple querying. Data is stored as documents within collections. For example, "restaurants" can be a collection and "McDonald's" can be a document with id "mcdonaldsid" and name "McDonald's". To install Firestore, run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'google-cloud-firestore'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and make sure to add&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'google-cloud-firestore'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to your Gemfile. Then, to use Firestore in an erb file, &lt;code&gt;require&lt;/code&gt; it and create a firestore object which takes your &lt;code&gt;project_id&lt;/code&gt; and &lt;code&gt;credentials&lt;/code&gt;. Remember that your &lt;code&gt;credentials&lt;/code&gt; file is the entire JSON object while &lt;code&gt;project_id&lt;/code&gt; is one field within that object. You can find this JSON object in your Firestore settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'google/cloud/firestore'

firestore = Google::Cloud::Firestore.new(
    project_id: 'your-project-id',
    credentials: '/path/to/your/credentials/file.json'
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the entire JSON object, which you can put in a file called &lt;code&gt;initialize_firestore.erb&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'google/cloud/firestore'

firestore = Google::Cloud::Firestore.new({
  :project_id =&amp;gt; ENV.fetch('GOOGLE_PROJECT_ID'),
  :credentials =&amp;gt; {
    :type =&amp;gt; ENV.fetch('GOOGLE_TYPE'),
    :project_id =&amp;gt; ENV.fetch('GOOGLE_PROJECT_ID'),
    :private_key_id =&amp;gt; ENV.fetch('GOOGLE_PRIVATE_KEY_ID'),
    :private_key =&amp;gt; ENV.fetch('GOOGLE_PRIVATE_KEY'),
    :client_email =&amp;gt; ENV.fetch('GOOGLE_CLIENT_EMAIL'),
    :client_id =&amp;gt; ENV.fetch('GOOGLE_CLIENT_ID'),
    :auth_uri =&amp;gt; ENV.fetch('GOOGLE_AUTH_URI'),
    :token_uri =&amp;gt; ENV.fetch('GOOGLE_TOKEN_URI'),
    :auth_provider_x509_cert_url =&amp;gt; ENV.fetch('GOOGLE_AUTH_PROVIDER_X509_CERT_URL'),
    :client_x509_cert_url =&amp;gt; ENV.fetch('GOOGLE_CLIENT_X509_CERT_URL')
  }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now onto the fun! In the example below, we are referencing a document holding info for "frank" in the "users" collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doc_ref = firestore.doc "users/frank"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we get the contents, which holds metadata like &lt;code&gt;id&lt;/code&gt; and the actual &lt;code&gt;data&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doc_snapshot = doc_ref.get
puts "Document data: #{doc_snapshot.data}."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also get all documents in a collection, create a snapshot on a collection that fires when documents are added, edited, or deleted, filter data, and much more!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Coding strategy for a nutrition-based linear equation system</title>
      <dc:creator>Cisco Vlahakis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 05:48:24 +0000</pubDate>
      <link>https://dev.to/ciscovlahakis/coding-strategy-for-a-nutrition-based-linear-equation-system-5bhc</link>
      <guid>https://dev.to/ciscovlahakis/coding-strategy-for-a-nutrition-based-linear-equation-system-5bhc</guid>
      <description>&lt;p&gt;Suppose you'd like to develop a nutrition app that allows users to precisely set the nutritional component amounts in their food to arrive at the "perfect" menu for the day or week. Traditionally, nutrition awareness programs have taught us to eat from a variety of food groups to maximize micronutrient consumption. While this is great advice, you can still go wrong. Suppose you eat too much of one color, such as red, but not enough black, green, or blue. I use colors as an example indicator of different micronutrient compositions. When you eat the perfect combination of macro- and micro- nutrients, your body flourishes and you just "feel" your absolute best! This is why I believe heavily in custom nutritional intervention.&lt;/p&gt;

&lt;p&gt;Suppose there are 100 nutritional components, such as Calories, Protein, Carbs, Sodium, Vitamin C, AGEs, etc.&lt;/p&gt;

&lt;p&gt;Suppose that a food can have 1 or many nutritional components.&lt;/p&gt;

&lt;p&gt;A user can select weights for each nutritional component, and my app should tell them the exact foods by gram they should consume to hit each of those goals.&lt;/p&gt;

&lt;p&gt;Yet I was stuck on just how to program such a task.&lt;/p&gt;

&lt;p&gt;Luckily, there are libraries like glpk or Simplex that make solving many, large linear systems of equations possible. This is exactly what I needed for this core feature of my app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'glpk'  # or another linear programming library

# Define your food items
foods = [
  {:name =&amp;gt; 'Turkey Breast', :calories =&amp;gt; 135, :sodium =&amp;gt; 70, :AGEs =&amp;gt; 1000, ...},
  {:name =&amp;gt; 'Strawberry', :calories =&amp;gt; 50, :sodium =&amp;gt; 2, :AGEs =&amp;gt; 500, ...},
  # more food items...
]

# Get user's nutritional goals
goals = {:calories =&amp;gt; 1700, :sodium =&amp;gt; 2300, :AGEs =&amp;gt; 9000, ...}

# Get user's food preferences (true = included in diet, false = not included)
preferences = {'Turkey Breast' =&amp;gt; true, 'Strawberry' =&amp;gt; false}

# Filter out unwanted foods
foods = foods.select { |food| preferences.fetch(food.fetch(:name)) }

# Create an empty matrix
matrix = []

# Transform data into a matrix that can be used by the linear programming library
# For each nutritional component
goals.keys.each do |component|
  # Create a new row
  row = []

  # For each food item
  foods.each do |food|
    # Add the amount of the component in the food to the row
    row &amp;lt;&amp;lt; food.fetch(component)
  end

  # Add the row to the matrix
  matrix &amp;lt;&amp;lt; row
end

# Solve the linear programming problem to find a combination of food items that satisfies the user's goals
solution = glpk.solve(matrix)

# Print the solution
solution.each do |item|
  puts "#{item[:name]}: #{item[:amount]}g"
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Performing linear algebra in a web app is no longer difficult with libraries like glpk or Simplex!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understanding increment syntax in Ruby</title>
      <dc:creator>Cisco Vlahakis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 05:18:08 +0000</pubDate>
      <link>https://dev.to/ciscovlahakis/understanding-increment-syntax-in-ruby-2go5</link>
      <guid>https://dev.to/ciscovlahakis/understanding-increment-syntax-in-ruby-2go5</guid>
      <description>&lt;p&gt;In Javascript, we can concisely increment a variable by 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fast, easy, and simple! I tried doing the same thing in Ruby! Alas, that did not work. In Ruby, there is no x++ operator like there is in JS. The reason for this is largely due to the design philosophy of the Ruby language.&lt;/p&gt;

&lt;p&gt;Ruby emphasizes readability and and prefers explicit operations over shorthand ones. In Ruby, we do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x+=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The x+=1 operation is clear: it increases the value of x by 1. In addition, Ruby has another method to increment a variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x = 1
x = x.next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, the good old fashioned&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x = x + 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;works just fine, too :)&lt;/p&gt;

&lt;p&gt;Although I understand Ruby prefers explicit operations, I do prefer the simplicity of JS and Python. Once you've been coding awhile, you come to appreciate the simpler syntax. However, an explicit language like Ruby is great for beginners understanding the basics. Every language has their pros and cons.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Fastest method for multi-line commenting in Ruby</title>
      <dc:creator>Cisco Vlahakis</dc:creator>
      <pubDate>Sun, 19 Nov 2023 05:11:28 +0000</pubDate>
      <link>https://dev.to/ciscovlahakis/fastest-method-for-multi-line-commenting-in-ruby-480h</link>
      <guid>https://dev.to/ciscovlahakis/fastest-method-for-multi-line-commenting-in-ruby-480h</guid>
      <description>&lt;p&gt;In Javascript, I was used to multi-line commenting like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* */
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It was fast, easy, and simple. I tried doing the same thing in Ruby! Alas, that did not work. Although single line commenting is fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did not know how to multi-line comment. Lo and behold, it was more complicated than in Javascript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=begin
This is a 
multi-line comment in Ruby
=end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fortunately, I learned that simply doing&lt;br&gt;
&lt;br&gt;
 &lt;code&gt;Cmd + /&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
 was a much faster way to multi-line comment, which incidentally, is the same shortcut used to multi-line comment in JS!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
