<?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" version="2.0"><channel><title><![CDATA[enzircle.com]]></title><description><![CDATA[Personal website of Joash Xu. Write about Python, machine learning, AI, database and computer science in general.]]></description><link>https://enzircle.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1636203899096/hjcOlrh4z.png</url><title>enzircle.com</title><link>https://enzircle.com</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 11 May 2026 18:24:02 GMT</lastBuildDate><atom:link href="https://enzircle.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Enhancing Your Django Apps: Inline SVG Icons with Iconify and Tailwind]]></title><description><![CDATA[When building a web application, we often face the challenge of managing a diverse set of icons. Icons are integral to user interface design. However, managing these icons can quickly become cumbersome, especially when aiming for consistency and effi...]]></description><link>https://enzircle.com/enhancing-your-django-apps-inline-svg-icons-with-iconify-and-tailwind</link><guid isPermaLink="true">https://enzircle.com/enhancing-your-django-apps-inline-svg-icons-with-iconify-and-tailwind</guid><category><![CDATA[Django]]></category><category><![CDATA[Tailwind CSS]]></category><category><![CDATA[UX]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Thu, 01 Feb 2024 06:31:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/_zKxPsGOGKg/upload/8dcdeb9f76428ff2dacf1d494855c615.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When building a web application, we often face the challenge of managing a diverse set of icons. Icons are integral to user interface design. However, managing these icons can quickly become cumbersome, especially when aiming for consistency and efficiency across different parts of the application.</p>
<p>Enter Iconify, a solution that streamlines the use of icons in web projects. Iconify offers a vast library of icons from various collections, simplifying their integration into projects. It enables dynamic generation of SVG icons, eliminating the need to download and store them manually. Through the use of APIs or client-side libraries, Iconify fetches and renders SVG icons on-demand based on the icons' identifiers.</p>
<p>While icons can be added to HTML through CSS or directly as SVG, using inline SVG provides greater flexibility. This method allows for complex interactions and styling, making icons more accessible and adaptable to the application's state, thereby improving user experience.</p>
<p>This article will show you how to set up Iconify along with TailwindCSS so that you can easily manage your icons. It assumed that you have set the <code>tailwindcss</code> node package and that you use this to generate your CSS file.</p>
<h2 id="heading-the-goal">The Goal</h2>
<p>If you are using the CSS method, you can easily use Iconify as follows.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon-[mdi-light--home]"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon-[flowbite--archive-solid"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
</code></pre>
<p>To achieve this, install the <code>@iconify/tailwind</code> package, then open, import from <code>iconify/tailwind</code>, and add it to the list of plugins. Example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { addDynamicIconSelectors } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@iconify/tailwind'</span>);

<span class="hljs-comment">/** @type {import('tailwindcss').Config} */</span>
<span class="hljs-built_in">module</span>.exports = {
   <span class="hljs-attr">content</span>: [<span class="hljs-string">'./src/*.html'</span>],
   <span class="hljs-attr">plugins</span>: [
       <span class="hljs-built_in">require</span>(<span class="hljs-string">'@iconify-json/flowbite'</span>),
       <span class="hljs-built_in">require</span>(<span class="hljs-string">'@iconify-json/mdi'</span>),
       addDynamicIconSelectors(),
   ],
};
</code></pre>
<p>However, in this article, we will use the inline method. Essentially, what we want to achieve is the following:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>
     <span class="hljs-attr">class</span>=<span class="hljs-string">"w-4 w-4 me-2"</span>
     <span class="hljs-attr">fill</span>=<span class="hljs-string">"currentColor"</span>
     <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 20 20"</span>&gt;</span>
  {% include "icons/icon-[mdi-light--home]" %}
<span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
</code></pre>
<p>Or, if you use Django Slippers like me, you can define a component.</p>
<pre><code class="lang-xml">{# icons.html #}
{% var size=size|match:"extra-small: h-3 w-3 , small: h-3 w-3, base: h-3.5 w-3.5 , large: h-4 w-4, extra-large: h-5 w-5"|default:"h-3.5 w-3.5" %}
{% var color=color|default:"" %}
{% var fill_color=fill_color|default:"currentColor" %}
{% var viewbox=viewbox|default:"0 0 24 24" %}
{% var extra_class=extra_class|default:"" %}

{% var icon_path="icons/"|add:name %}

<span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span>
     <span class="hljs-attr">class</span>=<span class="hljs-string">"{{ size }} {{ color }} {{ extra_class }}"</span>
     <span class="hljs-attr">fill</span>=<span class="hljs-string">"{{ fill_color }}"</span>
     <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"{{ viewbox }}"</span>&gt;</span>
  {% include icon_path %}
<span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
</code></pre>
<p>And use it in your templates as follows.</p>
<pre><code class="lang-xml">{% icon name="icon-[flowbite--adjustments-horizontal-outline]"  %}
{% icon name="icon-[flowbite--face-grin-stars-outline]"  %}
{% icon name="icon-[flowbite--archive-solid]" size="small" %}
{% icon name="icon-[flowbite--annotation-solid]" %}
</code></pre>
<h2 id="heading-install-necessary-packages">Install Necessary Packages</h2>
<p>Locate your <code>tailwind.config.js</code> file and write the following functions.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { readFileSync, mkdir, writeFileSync } <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>;
<span class="hljs-keyword">import</span> { getIconData, matchIconName } <span class="hljs-keyword">from</span> <span class="hljs-string">'@iconify/utils'</span>;

<span class="hljs-keyword">const</span> TARGET = <span class="hljs-string">'reux/templates/icons'</span>;
<span class="hljs-keyword">const</span> CACHE = <span class="hljs-built_in">Object</span>.create(<span class="hljs-literal">null</span>);


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadIconSet</span>(<span class="hljs-params">prefix</span>) </span>{
  <span class="hljs-keyword">let</span> main = <span class="hljs-built_in">require</span>.resolve(<span class="hljs-string">`@iconify-json/<span class="hljs-subst">${prefix}</span>/icons.json`</span>);
  <span class="hljs-keyword">if</span> (!main) {
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-keyword">if</span> (CACHE[main]) {
    <span class="hljs-keyword">return</span> CACHE[main];
  }

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> result = <span class="hljs-built_in">JSON</span>.parse(readFileSync(main, <span class="hljs-string">'utf8'</span>));
    CACHE[main] = result;
    <span class="hljs-keyword">return</span> result;
  }
  <span class="hljs-keyword">catch</span> { }
}


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addDynamicIconSelectors</span>(<span class="hljs-params">options</span>) </span>{
  <span class="hljs-keyword">const</span> prefix = <span class="hljs-string">'icon'</span>;
  <span class="hljs-keyword">const</span> outDir = <span class="hljs-string">`<span class="hljs-subst">${TARGET}</span>`</span>;

  mkdir(outDir, { <span class="hljs-attr">recursive</span>: <span class="hljs-literal">true</span> }, <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> { <span class="hljs-keyword">if</span> (err) <span class="hljs-keyword">throw</span> err; });

  <span class="hljs-keyword">return</span> (<span class="hljs-function">(<span class="hljs-params">{ matchComponents }</span>) =&gt;</span> {
    matchComponents({
      [prefix]: <span class="hljs-function">(<span class="hljs-params">icon</span>) =&gt;</span> {
        <span class="hljs-comment">// Get icon identifier</span>
        <span class="hljs-keyword">const</span> nameParts = icon.split(<span class="hljs-regexp">/--|\:/</span>);

        <span class="hljs-keyword">if</span> (nameParts.length !== <span class="hljs-number">2</span>) { 
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Invalid icon name: "<span class="hljs-subst">${icon}</span>"`</span>); 
        }

        <span class="hljs-comment">// Get icon set prefix and icon name</span>
        <span class="hljs-keyword">const</span> [iconSetPrefix, name] = nameParts;
        <span class="hljs-keyword">if</span> (!(iconSetPrefix.match(matchIconName) &amp;&amp; name.match(matchIconName))) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Invalid icon name: "<span class="hljs-subst">${icon}</span>"`</span>);
        }

        <span class="hljs-comment">// Load icon set</span>
        <span class="hljs-keyword">const</span> iconSet = loadIconSet(iconSetPrefix);
        <span class="hljs-keyword">if</span> (!iconSet) {
          <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(
              <span class="hljs-string">`Cannot load icon set for "<span class="hljs-subst">${prefix}</span>".
               Install "@iconify-json/<span class="hljs-subst">${prefix}</span>" as dev dependency?`</span>
          );
        }

        <span class="hljs-comment">// Get icon data</span>
        <span class="hljs-keyword">const</span> data =  getIconData(iconSet, name);

        <span class="hljs-comment">// Save data to file</span>
        writeFileSync(<span class="hljs-string">`<span class="hljs-subst">${__dirname}</span>/<span class="hljs-subst">${outDir}</span>/<span class="hljs-subst">${prefix}</span>-[<span class="hljs-subst">${icon}</span>]`</span>, 
                      data[<span class="hljs-string">'body'</span>]);
        <span class="hljs-keyword">return</span>;
     }
    });
  })
}


<span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('tailwindcss').Config}</span> </span>*/</span>
<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">content</span>: [
      <span class="hljs-string">'./reux/templates/**/*.html'</span>,
  ],
  <span class="hljs-attr">theme</span>: {
    <span class="hljs-attr">extend</span>: {},
  },
  <span class="hljs-attr">plugins</span>: [
      <span class="hljs-built_in">require</span>(<span class="hljs-string">'flowbite/plugin'</span>),
      <span class="hljs-built_in">require</span>(<span class="hljs-string">'@iconify-json/flowbite'</span>),
      <span class="hljs-built_in">require</span>(<span class="hljs-string">'@iconify-json/mdi'</span>),
      addDynamicIconSelectors(),
  ],
}
</code></pre>
<p>The <code>addDynamicIconSelectors</code> will scan the template files, and when it finds any mention of the text that matches our icon identifier (e.g., <code>icon-[flowbite--annotation-solid]</code>), it will read the icon set, get the SVG path data, and save it to the file, which we can then include using Django Template Language.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Incorporating inline SVG icons into our web application with Iconify and Tailwind CSS offers a powerful way to enhance the project's visual appeal and user interaction. By following the steps outlined above, you can easily manage and use a vast library of icons, providing a more flexible and accessible experience.</p>
]]></content:encoded></item><item><title><![CDATA[A Beginner's Guide to Facebook Marketing API with Python]]></title><description><![CDATA[Facebook Marketing API allows developers and marketers to create and manage their Facebook ad campaigns more efficiently through a programmatic interface. This beginner's guide will introduce you to the basics of using the Facebook Marketing API with...]]></description><link>https://enzircle.com/a-beginners-guide-to-facebook-marketing-api-with-python</link><guid isPermaLink="true">https://enzircle.com/a-beginners-guide-to-facebook-marketing-api-with-python</guid><category><![CDATA[Python]]></category><category><![CDATA[facebookads]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Tue, 05 Dec 2023 12:19:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Ah4F6g-OmgI/upload/64bc19e646d5091481c96b7e19ddc275.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Facebook Marketing API allows developers and marketers to create and manage their Facebook ad campaigns more efficiently through a programmatic interface. This beginner's guide will introduce you to the basics of using the Facebook Marketing API with Python, covering essential topics such as authentication, working with accounts, retrieving campaign data, and fetching campaign insights. By the end of this guide, you should have a solid understanding of how to integrate the API into your marketing strategies and harness its full potential.</p>
<h2 id="heading-requirements">Requirements</h2>
<ul>
<li><p><code>facebook_business</code>. This is the official Facebook Python SDK. See its <a target="_blank" href="https://github.com/facebook/facebook-python-business-sdk">GitHub page</a>.</p>
</li>
<li><p><code>requests-oauthlib</code>. Python OAuth library that we can use to authenticate to Facebook. For more info, see <a target="_blank" href="https://github.com/requests/requests-oauthlib">Requests-OAuthlib</a>.</p>
</li>
</ul>
<h2 id="heading-before-we-begin">Before We Begin</h2>
<p>To use Facebook API, we need to have <code>App ID</code>, <code>App Secret</code> and <code>Access Token</code>. The <code>Access Token</code> is something that you need to generate. But the App ID and App Secret are something you get from the Facebook app dashboard.</p>
<p>That means we need to create an app before we can start using the API. You can create an app from the <a target="_blank" href="https://developers.facebook.com/">Facebook developer website</a>. Here is a short guide on how to create a new app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701773958384/89c6d2f9-f7da-4205-809d-7f53c32f4a15.png" alt="How to create a Facebook App" class="image--center mx-auto" /></p>
<p>Once you have created an application, you will have access to the <code>App ID</code> and the <code>App Secret</code> from the <code>App Settings &gt; Basic</code> menu.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701774701067/103f077b-09fd-44de-a105-21015469af63.png" alt="Where to get App ID and App Secret" class="image--center mx-auto" /></p>
<h3 id="heading-access-token">Access Token</h3>
<p>To get an access token, we can use the requests_oauthlib</p>
<pre><code class="lang-python">
<span class="hljs-keyword">from</span> requests_oauthlib.compliance_fixes <span class="hljs-keyword">import</span> facebook_compliance_fix

my_app_id = <span class="hljs-string">"&lt;app id&gt;"</span>
my_app_secret = <span class="hljs-string">"&lt;app secret&gt;"</span>

authorization_base_url = <span class="hljs-string">"https://www.facebook.com/v18.0/dialog/oauth"</span>
token_url = <span class="hljs-string">"https://graph.facebook.com/v18.0/oauth/access_token"</span> 
redirect_uri = <span class="hljs-string">"https://localhost/"</span> 

facebook = OAuth2Session(
    my_app_id,
    redirect_uri=redirect_uri,
    scope=<span class="hljs-string">"ads_management,ads_read"</span>
)
facebook = facebook_compliance_fix(facebook)

<span class="hljs-comment"># Print the authorization URL. Access the URL using your browser.</span>
authorization_url, state = facebook.authorization_url(authorization_base_url)
print(<span class="hljs-string">f"Please go here and authorize: <span class="hljs-subst">{authorization_url}</span>"</span>)

<span class="hljs-comment"># Wait for the user to input the redirect URL</span>
redirect_response = input(<span class="hljs-string">"Paste the full redirect URL here:"</span>)

<span class="hljs-comment"># Get the token</span>
token = facebook.fetch_token(token_url, client_secret=my_app_secret,
                              authorization_response=redirect_response)
</code></pre>
<p>When you run the code above in a terminal, it will print out an <code>authorization_url</code>. And wait for you to paste the redirect URL. You then need to use your browser to open the link. Facebook will ask for your consent, and if everything goes well, you will be redirected to a blank page. Don't be alarmed. All you need to do now is copy the whole URL from your browser location bar and paste it into the terminal that is waiting. Once you are done, we can finally get the access token.</p>
<p>The <code>token</code> variable will have something like:</p>
<pre><code class="lang-python">{<span class="hljs-string">'access_token'</span>: <span class="hljs-string">'&lt;access_token&gt;'</span>,
 <span class="hljs-string">'token_type'</span>: <span class="hljs-string">'bearer'</span>,
 <span class="hljs-string">'expires_in'</span>: <span class="hljs-number">5132946</span>,
 <span class="hljs-string">'expires_at'</span>: <span class="hljs-number">1703429997.4087522</span>}
</code></pre>
<p>Now that we have all the needed items, we can continue playing around with the API.</p>
<h2 id="heading-authorization-and-authentication">Authorization and Authentication</h2>
<p>Before making any API calls, we need to invoke the <code>FacebookAdsApi.init</code>. This method sets up a default <code>FacebooksAdsApi</code> object to be used.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> facebook_business.api <span class="hljs-keyword">import</span> FacebookAdsApi

my_app_id = <span class="hljs-string">"&lt;app_id&gt;"</span>
my_app_secret = <span class="hljs-string">"&lt;app_secret&gt;"</span>
my_access_token = <span class="hljs-string">"&lt;app_token&gt;"</span>

FacebookAdsApi.init(my_app_id, my_app_secret, my_access_token)
</code></pre>
<h2 id="heading-getting-accounts">Getting Accounts</h2>
<p>The first API we will try is getting the account. The simplest way is to use the <code>User</code> class with the <code>fbid="me"</code> parameters.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> facebook_business.adobjects.user <span class="hljs-keyword">import</span> User

me = User(fbid=<span class="hljs-string">"me"</span>)
my_accounts = list(me.get_ad_accounts(
    fields=[<span class="hljs-string">"account_id"</span>, <span class="hljs-string">"id"</span>, <span class="hljs-string">"name"</span>])
)
</code></pre>
<p>The <code>my_accounts</code> will have something like the following.</p>
<pre><code class="lang-python">[&lt;AdAccount&gt; {
     <span class="hljs-string">"account_id"</span>: <span class="hljs-string">"&lt;account id&gt;"</span>,
     <span class="hljs-string">"id"</span>: <span class="hljs-string">"act_&lt;account id&gt;"</span>,
     <span class="hljs-string">"name"</span>: <span class="hljs-string">"Account 1"</span>
 },
 &lt;AdAccount&gt; {
     <span class="hljs-string">"account_id"</span>: <span class="hljs-string">"&lt;account id&gt;"</span>,
     <span class="hljs-string">"id"</span>: <span class="hljs-string">"act_&lt;account id&gt;"</span>,
     <span class="hljs-string">"name"</span>: <span class="hljs-string">"Account 2"</span>
 }]
</code></pre>
<p>Great. We just made our first API call.</p>
<p>A brief note: Although the method above is functional, it has a minor issue. It retrieves all accounts linked to your Facebook account. If you possess a lot of accounts, managing them could become challenging.</p>
<p>The alternative to this approach is to use a Business account. On Facebook, you can create a Business and establish an app associated with that business. This method makes it easier to manage, even if you have a lot of ad accounts. You can do the following to obtain accounts associated with a specific business.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> facebook_business.adobjects.application <span class="hljs-keyword">import</span> Application
<span class="hljs-keyword">from</span> facebook_business.api <span class="hljs-keyword">import</span> FacebookAdsApi

FacebookAdsApi.init(business_id, my_app_secret, my_access_token)

app = Application(fbid=business_id)
my_accounts = list(app.get_authorized_ad_accounts(
    fields=[<span class="hljs-string">"id"</span>, <span class="hljs-string">"account_id"</span>, <span class="hljs-string">"name"</span>])
)
</code></pre>
<h2 id="heading-getting-campaigns">Getting Campaigns</h2>
<p>Now that you get the account list. How do we get campaigns for all those accounts? Use the <code>AdAccount</code> class and invoke the <code>get_campaigns</code> method.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> facebook_business.adobjects.adaccount <span class="hljs-keyword">import</span> AdAccount

campaign_list = []
<span class="hljs-keyword">for</span> account <span class="hljs-keyword">in</span> my_accounts:
    my_account = AdAccount(account[<span class="hljs-string">"id"</span>])
    campaigns = my_account.get_campaigns(
        fields=[<span class="hljs-string">"id"</span>, <span class="hljs-string">"account_id"</span>, <span class="hljs-string">"name"</span>]
    )
    campaign_list.extend(campaigns)
</code></pre>
<p>The <code>campaign_list</code> will look something like:</p>
<pre><code class="lang-python">[&lt;Campaign&gt; {
    <span class="hljs-string">"account_id"</span>: <span class="hljs-string">"&lt;account_id&gt;"</span>,
    <span class="hljs-string">"id"</span>: <span class="hljs-string">"&lt;campaign_id&gt;"</span>,
    <span class="hljs-string">"name"</span>: <span class="hljs-string">"&lt;campaign_name&gt;"</span>
}, ...]
</code></pre>
<h2 id="heading-getting-campaigns-insight">Getting Campaigns Insight</h2>
<p>To get campaign insight, we can run <code>get_insights</code> with the level set to the <code>campaign</code>. This will get the campaign cost in bulk. The alternative is to get the campaign list for the account, loop through each campaign, and fetch the insight data. This will deplete your API rate limit. But of course, we must be aware of the amount of data. We will be using <code>yesterday</code> as the <code>date_preset</code> in the example below, so this might not be a problem. But if the data gets big and causes time out, you need to switch to the <code>async</code> version (the SDK provides this but requires a bit more handling).</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> facebook_business.adobjects.adaccount <span class="hljs-keyword">import</span> AdAccount

insight_list = []
<span class="hljs-keyword">for</span> account <span class="hljs-keyword">in</span> my_accounts:
    my_account = AdAccount(account[<span class="hljs-string">"id"</span>])
    insight = my_account.get_insights(
        fields=[<span class="hljs-string">"account_id"</span>, <span class="hljs-string">"account_name"</span>, 
                <span class="hljs-string">"campaign_id"</span>, <span class="hljs-string">"campaign_name"</span>,
                <span class="hljs-string">"date_start"</span>, <span class="hljs-string">"date_stop"</span>, <span class="hljs-string">"spend"</span>],
        params={<span class="hljs-string">"date_preset"</span>:<span class="hljs-string">"yesterday "</span>,   
                <span class="hljs-string">"level"</span>:<span class="hljs-string">"campaign"</span>})
    insight_list.extend(insight)
</code></pre>
<p>For the above example, I only get the <code>spend</code> data. There are other data you can get, like <code>impression</code>, <code>cpc</code>, <code>cpm</code>, etc.</p>
<p>When we run the code above, the <code>insight_list</code> will look something like the following.</p>
<pre><code class="lang-python">[[&lt;AdsInsights&gt; {
     <span class="hljs-string">"account_id"</span>: <span class="hljs-string">"&lt;account id&gt;"</span>,
     <span class="hljs-string">"account_name"</span>: <span class="hljs-string">"&lt;account name&gt;"</span>,
     <span class="hljs-string">"campaign_id"</span>: <span class="hljs-string">"&lt;campaign id&gt;"</span>,
     <span class="hljs-string">"campaign_name"</span>: <span class="hljs-string">"&lt;campaign name&gt;"</span>,
     <span class="hljs-string">"date_start"</span>: <span class="hljs-string">"&lt;yesterday date&gt;"</span>,
     <span class="hljs-string">"date_stop"</span>: <span class="hljs-string">"&lt;today date&gt;"</span>,
     <span class="hljs-string">"spend"</span>: <span class="hljs-string">"&lt;the cost&gt;"</span>
 }, ...]
</code></pre>
<h2 id="heading-summary">Summary</h2>
<p>The Facebook Marketing API offers a powerful and efficient way to manage your ad campaigns programmatically. By using Python and the official SDK, you can easily authenticate, retrieve accounts, fetch campaign data, and gain insights into your campaign costs. With this foundational knowledge, you can now start integrating the API into your marketing strategies and harness its full potential to optimize your advertising efforts on Facebook.</p>
]]></content:encoded></item><item><title><![CDATA[Building Your First Clickstream Analytics Dashboard: An End-to-End Guide]]></title><description><![CDATA[The article you are about to read was originally published on GlassFlow Blog. GlassFlow provides serverless and production-ready streaming data pipelines setup for the data team.

Venturing into the world of data pipelines may feel daunting, especial...]]></description><link>https://enzircle.com/building-your-first-clickstream-analytics-dashboard-an-end-to-end-guide</link><guid isPermaLink="true">https://enzircle.com/building-your-first-clickstream-analytics-dashboard-an-end-to-end-guide</guid><category><![CDATA[data-engineering]]></category><category><![CDATA[kafka]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Mon, 27 Nov 2023 12:11:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/LRzkT1gIU8A/upload/378ddd0d3a26cff77ac36437f82f9173.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>The article you are about to read was originally published on <a target="_blank" href="https://learn.glassflow.dev/blog">GlassFlow Blog</a>. <a target="_blank" href="https://www.glassflow.dev/">GlassFlow</a> provides serverless and production-ready streaming data pipelines setup for the data team.</p>
</blockquote>
<p>Venturing into the world of data pipelines may feel daunting, especially when juggling several advanced technologies. Work with us as we create an analytics dashboard tailored for clickstream data, leveraging Python's Streamline, Apache Kafka, and Apache Pinot. Even if you're new to data pipelines, this comprehensive guide is here to help.</p>
<h3 id="heading-overview-build-your-first-clickstream-dashboard">Overview - Build your first clickstream dashboard</h3>
<p>By the end of this project, we will have built a simple dashboard that shows a funnel and Sankey chart. There are four components that we need to work on. First, we need to set up Apache Kafka and Pinot. Then, we will create some data to stream to our Kafka. Finally, we need to create a dashboard that visualizes the data we get from Pinot.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701086877877/d6a0643c-e8d6-4a6d-920f-65ee1888ef06.png" alt="Clickstream pipeline overview" class="image--center mx-auto" /></p>
<p>Tools I Used</p>
<ul>
<li><p><strong>Apache Kafka</strong>. Apache Kafka is a distributed streaming platform that is used for building real-time data pipelines and streaming apps.</p>
</li>
<li><p><strong>Apache Pinot</strong>. Apache Pinot is an open-source, distributed data system designed for real-time analytics and suited for time series databases, OLAP queries, and real-time monitoring systems.</p>
</li>
<li><p><strong>Streamlit</strong>. Streamlit is an open-source Python library that allows developers to create dashboards featuring visualizations, widgets, and more.</p>
</li>
<li><p><strong>Plotly</strong>. Plotly is an open-source graphing library. We will use this to create the funnel and Sankey chart.</p>
</li>
</ul>
<h3 id="heading-clickstream-data">Clickstream Data</h3>
<p>Clickstream data is the information collected when a user uses a web browser. Clickstream analytics is the process of tracking, analyzing, and reporting data on the pages a user visits and user behavior while on a webpage. This data can reveal much about user behavior, including how they found the product or service and their actions while using it.</p>
<p>The data for the project looks like this</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>User</strong></td><td><strong>Page They Visited</strong></td><td><strong>When They Visited</strong></td><td><strong>Location</strong></td><td><strong>Returning Visit</strong></td></tr>
</thead>
<tbody>
<tr>
<td>User 1</td><td>Home Page</td><td>A timestamp</td><td>User location</td><td>1</td></tr>
</tbody>
</table>
</div><p>To make it easy to process, we turn the timestamp (<code>When They Visited</code> column) into order. Essentially, we sort the timestamp and assign an integer from smallest to largest.</p>
<p>In the real world, the data is sent as the user navigates through the website. But in this project, we will read the data from a CSV file and send its content row by row. The data looks like the following.</p>
<pre><code class="lang-plaintext">user_id,web_page,order,location,visit
1,home,1,Colorado,1
1,login,2,Colorado,1
1,shop,3,Colorado,1
1,cart,4,Colorado,1
1,checkout,5,Colorado,1
2,home,1,California,1
2,login,2,California,1
2,shop,3,California,1
2,cart,4,California,1
2,checkout,5,California,1
</code></pre>
<p>You can also use Python random library or any mock tools (for example, <a target="_blank" href="https://www.mockaroo.com">Mockaroo</a>) to generate this kind of data.</p>
<h3 id="heading-setting-up-kafka-and-pinot">Setting Up Kafka and Pinot</h3>
<p>Next, we need to set up Kafka and Pinot. However, manually setting up such complex systems can be daunting, even for experienced developers. You have to deal with complex dependencies, for example, using Zookeeper to manage distributed systems. Installing and configuring these dependencies can be time-consuming. You have to pay attention to many configurations. You need to make sure all components and their dependencies are correct. And so on.</p>
<p>Since the focus of this article is to give an end-to-end review of the whole system, let’s not get bogged down with a manual setup. Let’s use Docker-Compose. A side note: Docker-Compose is not meant for a production environment. It is primarily used in development because it is easy to manage multi-container applications like this project.</p>
<p>Docker-Compose offers a simplified approach. It will help streamline the setup process. I will provide two docker-compose files—one for Apple Silicon and the other for Intel-based machines.</p>
<h4 id="heading-docker-compose-file-for-apple-silicon">Docker-Compose File for Apple Silicon</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.7'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">zookeeper:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">zookeeper:latest</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">"zookeeper-clickstream"</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"2181:2181"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">ZOOKEEPER_CLIENT_PORT:</span> <span class="hljs-number">2181</span>
      <span class="hljs-attr">ZOOKEEPER_TICK_TIME:</span> <span class="hljs-number">2000</span>
  <span class="hljs-attr">kafka:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">wurstmeister/kafka:latest</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">"kafka-clickstream"</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9092:9092"</span>
    <span class="hljs-attr">expose:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9093"</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">zookeeper</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">KAFKA_ZOOKEEPER_CONNECT:</span> <span class="hljs-string">zookeeper-clickstream:2181/kafka</span>
      <span class="hljs-attr">KAFKA_BROKER_ID:</span> <span class="hljs-number">0</span>
      <span class="hljs-attr">KAFKA_ADVERTISED_HOST_NAME:</span> <span class="hljs-string">kafka-clickstream</span>
      <span class="hljs-attr">KAFKA_ADVERTISED_LISTENERS:</span> <span class="hljs-string">PLAINTEXT://kafka-clickstream:9093,OUTSIDE://localhost:9092</span>
      <span class="hljs-attr">KAFKA_LISTENERS:</span> <span class="hljs-string">PLAINTEXT://0.0.0.0:9093,OUTSIDE://0.0.0.0:9092</span>
      <span class="hljs-attr">KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:</span> <span class="hljs-string">PLAINTEXT:PLAINTEXT,OUTSIDE:PLAINTEXT</span>
  <span class="hljs-attr">pinot-controller:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">apachepinot/pinot:0.12.0-arm64</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">"StartController -zkAddress zookeeper-clickstream:2181"</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">"pinot-controller-clickstream"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./config:/config</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./data:/data</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9000:9000"</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">zookeeper</span>
  <span class="hljs-attr">pinot-broker:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">apachepinot/pinot:0.12.0-arm64</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">"StartBroker -zkAddress zookeeper-clickstream:2181"</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">"pinot-broker-clickstream"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./config:/config</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8099:8099"</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">pinot-controller</span>
  <span class="hljs-attr">pinot-server:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">apachepinot/pinot:0.12.0-arm64</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">"StartServer -zkAddress zookeeper-clickstream:2181"</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">"pinot-server-clickstream"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./config:/config</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8098:8098"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8097:8097"</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">pinot-broker</span>
</code></pre>
<h4 id="heading-docker-compose-file-for-x86">Docker-Compose File for x86</h4>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.7'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">zookeeper:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">zookeeper:3.5.6</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">"zookeeper-clickstream"</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"2181:2181"</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">ZOOKEEPER_CLIENT_PORT:</span> <span class="hljs-number">2181</span>
      <span class="hljs-attr">ZOOKEEPER_TICK_TIME:</span> <span class="hljs-number">2000</span>
  <span class="hljs-attr">kafka:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">wurstmeister/kafka:latest</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">"kafka-clickstream"</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9092:9092"</span>
    <span class="hljs-attr">expose:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9093"</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">zookeeper</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">KAFKA_ZOOKEEPER_CONNECT:</span> <span class="hljs-string">zookeeper-clickstream:2181/kafka</span>
      <span class="hljs-attr">KAFKA_BROKER_ID:</span> <span class="hljs-number">0</span>
      <span class="hljs-attr">KAFKA_ADVERTISED_HOST_NAME:</span> <span class="hljs-string">kafka-clickstream</span>
      <span class="hljs-attr">KAFKA_ADVERTISED_LISTENERS:</span> <span class="hljs-string">PLAINTEXT://kafka-clickstream:9093,OUTSIDE://localhost:9092</span>
      <span class="hljs-attr">KAFKA_LISTENERS:</span> <span class="hljs-string">PLAINTEXT://0.0.0.0:9093,OUTSIDE://0.0.0.0:9092</span>
      <span class="hljs-attr">KAFKA_LISTENER_SECURITY_PROTOCOL_MAP:</span> <span class="hljs-string">PLAINTEXT:PLAINTEXT,OUTSIDE:PLAINTEXT</span>
  <span class="hljs-attr">pinot-controller:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">apachepinot/pinot:0.12.0</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">"StartController -zkAddress zookeeper-clickstream:2181 -dataDir /data"</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">"pinot-controller-clickstream"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./config:/config</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./data:/data</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9000:9000"</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">zookeeper</span>
  <span class="hljs-attr">pinot-broker:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">apachepinot/pinot:0.12.0</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">"StartBroker -zkAddress zookeeper-clickstream:2181"</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">"pinot-broker-clickstream"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./config:/config</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8099:8099"</span>      
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">pinot-controller</span>
  <span class="hljs-attr">pinot-server:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">apachepinot/pinot:0.12.0</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">"StartServer -zkAddress zookeeper-clickstream:2181"</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">"pinot-server-clickstream"</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./config:/config</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"8098:8098"</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">pinot-broker</span>
</code></pre>
<h4 id="heading-spin-up-kafka-and-pinot">Spin Up Kafka and Pinot</h4>
<p>Using the above docker-compose file, we can now spin both Kafka and Pinot up using the following command:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># For x86 machine</span>
$ docker-compose up

<span class="hljs-comment"># For Apple silicon   </span>
$ docker-compose -f docker-compose-m1.yml up
</code></pre>
<h4 id="heading-creating-topic">Creating Topic</h4>
<p>Before sending a bunch of data to Kafka, we must create a “topic.” A topic in Kafka is a category or name to which records are published. Think of it like a channel on a TV. If you want to watch a particular type of show or get specific news updates, you tune into a specific channel. Similarly, in Kafka, producers send records to topics, and consumers read records from topics.</p>
<p>The following command will create a topic named “clickstream_events” in Kafka.</p>
<pre><code class="lang-bash">$ docker <span class="hljs-built_in">exec</span> -it kafka-clickstream kafka-topics.sh \\
  --bootstrap-server localhost:9092 \\
  --partitions 5 \\
  --topic clickstream_events \\
  --create
</code></pre>
<h4 id="heading-add-pinot-table">Add Pinot Table</h4>
<p>Before Pinot can work, it must know how to save the data and connect to Kafka. To allow that, we must first define a schema.json and a table.json. Then, we need to tell Pinot to create the table following our schema and definition.</p>
<p>The schema will follow the data definition from the previous section. There will be <code>user_id</code>, <code>web_page</code>, <code>order</code>, <code>location</code>, and <code>visit</code>. We also add a timestamp column named <code>ts</code>. This timestamp column is vital in Pinot because it determines the time boundary and manages the life cycle of data stored in Pinot segments.</p>
<p><strong>schema.json</strong></p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"schemaName"</span>: <span class="hljs-string">"clickstream"</span>,
    <span class="hljs-attr">"dimensionFieldSpecs"</span>: [
      {
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"user_id"</span>,
        <span class="hljs-attr">"dataType"</span>: <span class="hljs-string">"INT"</span>
      },
      {
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"web_page"</span>,
        <span class="hljs-attr">"dataType"</span>: <span class="hljs-string">"STRING"</span>
      },
      {
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"order"</span>,
        <span class="hljs-attr">"dataType"</span>: <span class="hljs-string">"INT"</span>
      },
      {
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"location"</span>,
        <span class="hljs-attr">"dataType"</span>: <span class="hljs-string">"STRING"</span>
      },
      {
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"visit"</span>,
        <span class="hljs-attr">"dataType"</span>: <span class="hljs-string">"INT"</span>
      }
    ],
    <span class="hljs-attr">"dateTimeFieldSpecs"</span>: [
      {
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"ts"</span>,
        <span class="hljs-attr">"dataType"</span>: <span class="hljs-string">"TIMESTAMP"</span>,
        <span class="hljs-attr">"format"</span>: <span class="hljs-string">"1:MILLISECONDS:EPOCH"</span>,
        <span class="hljs-attr">"granularity"</span>: <span class="hljs-string">"1:MILLISECONDS"</span>
      }
    ]
  }
</code></pre>
<p>To connect Pinot with Kafka, we need to create a table and define the connection configuration. This is so Pinot knows where to connect, what topic to read from, and other settings.</p>
<p>The complete <code>table.json</code> content is as follows. Notice the <code>streamConfigs</code> section where we define the stream type (Kafka), the broker (<code>kafka-clickstream:9093</code>), and the topic name (<code>clickstream-events</code>). This is how Pinot gets the data From Kafka.</p>
<p><strong>table.json</strong></p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"tableName"</span>: <span class="hljs-string">"clickstream"</span>,
    <span class="hljs-attr">"tableType"</span>: <span class="hljs-string">"REALTIME"</span>,
    <span class="hljs-attr">"segmentsConfig"</span>: {
      <span class="hljs-attr">"timeColumnName"</span>: <span class="hljs-string">"ts"</span>,
      <span class="hljs-attr">"schemaName"</span>: <span class="hljs-string">"clickstream"</span>,
      <span class="hljs-attr">"replicasPerPartition"</span>: <span class="hljs-string">"1"</span>
    },
    <span class="hljs-attr">"tenants"</span>: {},
    <span class="hljs-attr">"tableIndexConfig"</span>: {
      <span class="hljs-attr">"streamConfigs"</span>: {
        <span class="hljs-attr">"streamType"</span>: <span class="hljs-string">"kafka"</span>,
        <span class="hljs-attr">"stream.kafka.topic.name"</span>: <span class="hljs-string">"clickstream-events"</span>,
        <span class="hljs-attr">"stream.kafka.broker.list"</span>: <span class="hljs-string">"kafka-clickstream:9093"</span>,
        <span class="hljs-attr">"stream.kafka.consumer.type"</span>: <span class="hljs-string">"lowlevel"</span>,
        <span class="hljs-attr">"stream.kafka.consumer.prop.auto.offset.reset"</span>: <span class="hljs-string">"smallest"</span>,
        <span class="hljs-attr">"stream.kafka.consumer.factory.class.name"</span>: 
          <span class="hljs-string">"org.apache.pinot.plugin.stream.kafka20.KafkaConsumerFactory"</span>,
        <span class="hljs-attr">"stream.kafka.decoder.class.name"</span>: 
          <span class="hljs-string">"org.apache.pinot.plugin.stream.kafka.KafkaJSONMessageDecoder"</span>,
          <span class="hljs-attr">"realtime.segment.flush.threshold.rows"</span>: <span class="hljs-string">"1000"</span>,
          <span class="hljs-attr">"realtime.segment.flush.threshold.time"</span>: <span class="hljs-string">"24h"</span>,
          <span class="hljs-attr">"realtime.segment.flush.segment.size"</span>: <span class="hljs-string">"100M"</span>
      }
    },
    <span class="hljs-attr">"metadata"</span>: {},
    <span class="hljs-attr">"ingestionConfig"</span>: {
      <span class="hljs-attr">"transformConfigs"</span>: [
        {
          <span class="hljs-attr">"columnName"</span>: <span class="hljs-string">"user_id"</span>,
          <span class="hljs-attr">"transformFunction"</span>: <span class="hljs-string">"JSONPATH(meta, '$.user_id')"</span>
        },
        {
          <span class="hljs-attr">"columnName"</span>: <span class="hljs-string">"web_page"</span>,
          <span class="hljs-attr">"transformFunction"</span>: <span class="hljs-string">"JSONPATH(meta, '$.web_page')"</span>
        },
        {
          <span class="hljs-attr">"columnName"</span>: <span class="hljs-string">"order"</span>,
          <span class="hljs-attr">"transformFunction"</span>: <span class="hljs-string">"JSONPATH(meta, '$.order')"</span>
        },
        {
          <span class="hljs-attr">"columnName"</span>: <span class="hljs-string">"location"</span>,
          <span class="hljs-attr">"transformFunction"</span>: <span class="hljs-string">"JSONPATH(meta, '$.location')"</span>
        },
        {
          <span class="hljs-attr">"columnName"</span>: <span class="hljs-string">"visit"</span>,
          <span class="hljs-attr">"transformFunction"</span>: <span class="hljs-string">"JSONPATH(meta, '$.visit')"</span>
        },
        {
            <span class="hljs-attr">"columnName"</span>: <span class="hljs-string">"ts"</span>,
            <span class="hljs-attr">"transformFunction"</span>: <span class="hljs-string">"\\"</span>timestamp\\<span class="hljs-string">" * 1000"</span>
        }
      ]
    }
  }
</code></pre>
<p>Now that we have defined our schema and table configuration, we only need to run the following command.</p>
<pre><code class="lang-bash">$ docker <span class="hljs-built_in">exec</span> -it pinot-controller-wiki bin/pinot-admin.sh AddTable \\
  -tableConfigFile /config/table.json \\
  -schemaFile /config/schema.json \\
  -<span class="hljs-built_in">exec</span>
</code></pre>
<h3 id="heading-sending-data">Sending Data</h3>
<p>Once we have set up Kafka and Pinot, it is time to send some data. As mentioned before, we will read the funnel data from a CSV and send every row to Kafka. Giving some random sleep between each transmission.</p>
<p>Our code that sends this data to Kafka is as follows.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">import</span> random
<span class="hljs-keyword">import</span> csv

<span class="hljs-keyword">from</span> confluent_kafka <span class="hljs-keyword">import</span> Producer

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">acked</span>(<span class="hljs-params">err, msg</span>):</span>
    <span class="hljs-keyword">if</span> err <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
        print(<span class="hljs-string">f"Failed to deliver message: <span class="hljs-subst">{msg.value()}</span>: <span class="hljs-subst">{err.str()}</span>"</span>)

producer = Producer({<span class="hljs-string">'bootstrap.servers'</span>: <span class="hljs-string">'localhost:9092'</span>})

<span class="hljs-keyword">with</span> open(<span class="hljs-string">'funnel_steps.csv'</span>, newline=<span class="hljs-string">''</span>) <span class="hljs-keyword">as</span> csvfile:
    reader = csv.DictReader(csvfile)

    <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> reader:
        print(<span class="hljs-string">f'Sending payload: <span class="hljs-subst">{row}</span>'</span>)
        <span class="hljs-comment"># Send to Kafka</span>
        payload = json.dumps(row)
        producer.produce(topic=<span class="hljs-string">'clickstream-events'</span>, key=str(row[<span class="hljs-string">'user_id'</span>]),
                         value=payload, callback=acked)

        <span class="hljs-comment"># Random sleep</span>
        sleep_time = random.randint(<span class="hljs-number">1</span>, <span class="hljs-number">4</span>)
        time.sleep(sleep_time)
</code></pre>
<h3 id="heading-building-the-dashboard">Building the Dashboard</h3>
<p>Now that the data is sent to Kafka and Pinot, we can display our dashboard. I use this Streamlit code to show a funnel and user flow data.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> streamlit <span class="hljs-keyword">as</span> st
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> plotly.graph_objects <span class="hljs-keyword">as</span> go
<span class="hljs-keyword">from</span> streamlit_autorefresh <span class="hljs-keyword">import</span> st_autorefresh
<span class="hljs-keyword">import</span> pinotdb

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_funnel_figure</span>(<span class="hljs-params">df</span>):</span>
    trace = go.Funnel(
        x=df.agg(<span class="hljs-string">'sum'</span>, numeric_only=<span class="hljs-number">1</span>).values,
        y=[<span class="hljs-string">'home'</span>, <span class="hljs-string">'login'</span>, <span class="hljs-string">'cart'</span>, <span class="hljs-string">'shop'</span>, <span class="hljs-string">'help'</span>, <span class="hljs-string">'error'</span>,
           <span class="hljs-string">'checkout'</span>, <span class="hljs-string">'OLD_CHECKOUT'</span>]
    )

    layout = go.Layout(margin={<span class="hljs-string">"l"</span>: <span class="hljs-number">180</span>, <span class="hljs-string">"r"</span>: <span class="hljs-number">0</span>, <span class="hljs-string">"t"</span>: <span class="hljs-number">30</span>, <span class="hljs-string">"b"</span>: <span class="hljs-number">0</span>, <span class="hljs-string">"pad"</span>: <span class="hljs-number">0</span>},
                       funnelmode=<span class="hljs-string">"stack"</span>,
                       showlegend=<span class="hljs-literal">False</span>,
                       hovermode=<span class="hljs-string">'closest'</span>,
                       title=<span class="hljs-string">''</span>,
                       legend=dict(orientation=<span class="hljs-string">"v"</span>,
                                   bgcolor=<span class="hljs-string">'#E2E2E2'</span>,
                                   xanchor=<span class="hljs-string">'left'</span>,
                                   font=dict(size=<span class="hljs-number">12</span>)))
    fig = go.Figure(trace, layout)
    fig.update_layout(title_text=<span class="hljs-string">"Funnel"</span>, font_size=<span class="hljs-number">10</span>)
    <span class="hljs-keyword">return</span> fig

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_sankey_figure</span>(<span class="hljs-params">df</span>):</span>
    <span class="hljs-comment"># Process the data to capture transitions</span>
    all_transitions = []

    <span class="hljs-keyword">for</span> path <span class="hljs-keyword">in</span> df[<span class="hljs-string">'web_page'</span>]:
        steps = path.split(<span class="hljs-string">','</span>)
        transitions = list(zip(steps[:<span class="hljs-number">-1</span>], steps[<span class="hljs-number">1</span>:]))
        all_transitions.extend(transitions)

    transition_df = pd.DataFrame(all_transitions, columns=[<span class="hljs-string">'source'</span>, <span class="hljs-string">'target'</span>])
    trans_count = (transition_df.groupby([<span class="hljs-string">'source'</span>, <span class="hljs-string">'target'</span>])
                   .size()
                   .reset_index(name=<span class="hljs-string">'value'</span>)
                   .sort_values(<span class="hljs-string">'value'</span>, ascending=<span class="hljs-literal">False</span>))

    <span class="hljs-comment"># Create unique labels for the nodes</span>
    unique_labels = pd.concat([trans_count[<span class="hljs-string">'source'</span>],
                               trans_count[<span class="hljs-string">'target'</span>]]).unique()

    <span class="hljs-comment"># Map the source and target strings to numeric values</span>
    trans_count[<span class="hljs-string">'source'</span>] = trans_count[<span class="hljs-string">'source'</span>].map(
        {label: idx <span class="hljs-keyword">for</span> idx, label <span class="hljs-keyword">in</span> enumerate(unique_labels)})
    trans_count[<span class="hljs-string">'target'</span>] = trans_count[<span class="hljs-string">'target'</span>].map(
        {label: idx <span class="hljs-keyword">for</span> idx, label <span class="hljs-keyword">in</span> enumerate(unique_labels)})

    <span class="hljs-comment"># Create the Sankey diagram</span>
    fig = go.Figure(go.Sankey(
        node=dict(pad=<span class="hljs-number">15</span>, thickness=<span class="hljs-number">15</span>,
                  line=dict(color=<span class="hljs-string">"black"</span>, width=<span class="hljs-number">0.5</span>),
                  label=unique_labels),
        link=dict(arrowlen=<span class="hljs-number">15</span>,
                  source=trans_count[<span class="hljs-string">'source'</span>],
                  target=trans_count[<span class="hljs-string">'target'</span>],
                  value=trans_count[<span class="hljs-string">'value'</span>])
    ))

    fig.update_layout(title_text=<span class="hljs-string">"User Flow"</span>, font_size=<span class="hljs-number">10</span>)
    <span class="hljs-keyword">return</span> fig

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_connection</span>():</span>
    conn = pinotdb.connect(host=<span class="hljs-string">'localhost'</span>, port=<span class="hljs-number">9000</span>,
                           path=<span class="hljs-string">'/sql'</span>, scheme=<span class="hljs-string">'http'</span>)
    <span class="hljs-keyword">return</span> conn

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_funnel_data</span>(<span class="hljs-params">conn</span>):</span>
    query = <span class="hljs-string">"""SELECT
                SUM(case when web_page='home' then 1 else 0 end) as home,
                SUM(case when web_page='login' then 1 else 0 end) as login,
                SUM(case when web_page='cart' then 1 else 0 end) as cart,
                SUM(case when web_page='shop' then 1 else 0 end) as shop,
                SUM(case when web_page='help' then 1 else 0 end) as help,
                SUM(case when web_page='error' then 1 else 0 end) as error,
                SUM(case when web_page='checkout' then 1 else 0 end) as checkout,
                SUM(case when web_page='OLD_CHECKOUT' then 1 else 0 end) as OLD_CHECKOUT,
                location,
                user_id
                FROM clickstream
                GROUP BY location, user_id
                LIMIT 200
            """</span>
    df = pd.read_sql_query(query, conn)
    <span class="hljs-keyword">return</span> df

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_sankey_data</span>(<span class="hljs-params">conn</span>):</span>
    df = pd.read_sql_query(<span class="hljs-string">'SELECT * FROM clickstream LIMIT 200'</span>, conn)
    df = (df.groupby([<span class="hljs-string">'location'</span>, <span class="hljs-string">'user_id'</span>])[<span class="hljs-string">'web_page'</span>]
                    .apply(<span class="hljs-keyword">lambda</span> x: <span class="hljs-string">','</span>.join(x))
                  .reset_index())
    <span class="hljs-keyword">return</span> df

conn = get_connection()

<span class="hljs-comment"># update every 30 seconds</span>
st_autorefresh(interval=<span class="hljs-number">30</span> * <span class="hljs-number">1000</span>, key=<span class="hljs-string">"dataframerefresh"</span>)

<span class="hljs-comment"># Funnel Chart</span>
funnel_data = get_funnel_data(conn)
funnel_fig = get_funnel_figure(funnel_data)
st.plotly_chart(funnel_fig, use_container_width=<span class="hljs-literal">True</span>)

<span class="hljs-comment"># Sankey Chart</span>
sankey_data = get_sankey_data(conn)
sankey_fig = get_sankey_figure(sankey_data)
st.plotly_chart(sankey_fig, use_container_width=<span class="hljs-literal">True</span>)
</code></pre>
<h3 id="heading-result">Result</h3>
<p>Once everything is set up correctly, we can see the result.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701086752390/5bafaec6-3198-4d49-893c-060a10618c36.png" alt="Real-time funnel dashboard" class="image--center mx-auto" /></p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>After building your first clickstream dashboard you might think embarking on the data pipeline journey can be intimidating. With some tools that are available right now, such as Docker, Kafka, Pinot, and Streamlit, we can make this complex task manageable. Whether you're analyzing user behaviors, optimizing sales funnels, or just experimenting, these tools offer a scalable and efficient way to handle and visualize data.</p>
<p>Now, you might be wondering why we should go through all this trouble. Why not just batch process the data every day or every half a day? Why use streaming when you can use batching?</p>
<p>Modern applications need to act fast. Often up to the millisecond fast. With the sheer volume of data being produced at every moment, it is critical to act fast on the data before it becomes stale or obsolete. This continuous data processing offers advantages that can transform the way businesses run.</p>
<p>As businesses and technologies grow, the paradigms of data handling and processing are shifting. Being equipped with the right tools and understanding the significance of real-time processing could be the determining factor in staying ahead in the competitive landscape.</p>
]]></content:encoded></item><item><title><![CDATA[Handling Binary Data in Socket Programming]]></title><description><![CDATA[Socket programming involves the transmission of data over a network using sockets. One type of data that can be transmitted over a network is binary data. Handling binary data in socket programming requires special attention, as different systems use...]]></description><link>https://enzircle.com/handling-binary-data-in-socket-programming</link><guid isPermaLink="true">https://enzircle.com/handling-binary-data-in-socket-programming</guid><category><![CDATA[Python]]></category><category><![CDATA[socket]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Sat, 04 Mar 2023 02:00:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/HtDQ9Z64Vpo/upload/2a1dd2e10a1b55bad7b386c23cb47325.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Socket programming involves the transmission of data over a network using sockets. One type of data that can be transmitted over a network is binary data. Handling binary data in socket programming requires special attention, as different systems use different byte orders (endianness). Endianness is the order of bytes of digital data in computer memory.</p>
<p>There are two types of byte order: big-endian and little-endian. For example, the network byte order is big-endian, with the most significant byte first, so a 16-bit integer with the value <code>1</code> would be the two hex bytes 00 01. However, the most common processors (x86/AMD64, ARM, RISC-V) are little-endian, with the least significant byte first - that same <code>1</code> would be 01 00.</p>
<p>When transmitting binary data over a network using sockets, it is essential to ensure that both the sender and receiver use the same byte order. The transmitted data may be corrupted or unreadable if the byte order is inconsistent. To ensure that both the sender and receiver are using the same byte order, a common byte order convention must be established.</p>
<p>We can use the <code>struct</code> module in Python to simplify handling binary data. The <code>struct</code> module converts between Python values and C structs represented as Python bytes objects.</p>
<p>The module provides functions for packing and unpacking binary data in a specific format. The first character of the format string can be used to indicate the byte order according to the following table:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Character</td><td>Byte Order</td></tr>
</thead>
<tbody>
<tr>
<td>@</td><td>Native</td></tr>
<tr>
<td>\=</td><td>Native</td></tr>
<tr>
<td>&lt;</td><td>Little-Endian</td></tr>
<tr>
<td>\&gt;</td><td>Big-Endian</td></tr>
<tr>
<td>!</td><td>Network (= big-endian)</td></tr>
</tbody>
</table>
</div><p>If the first character is not of these, <code>@</code> is assumed.</p>
<p>After the byte order indicator, we need to put in format characters. Some of the popular format characters are:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Format</td><td>C Type</td><td>Python Type</td></tr>
</thead>
<tbody>
<tr>
<td>?</td><td><em>Bool</em></td><td>bool</td></tr>
<tr>
<td>h</td><td>short</td><td>integer</td></tr>
<tr>
<td>l</td><td>long</td><td>integer</td></tr>
<tr>
<td>i</td><td>int</td><td>integer</td></tr>
<tr>
<td>f</td><td>float</td><td>float</td></tr>
<tr>
<td>q</td><td>long long</td><td>integer</td></tr>
</tbody>
</table>
</div><p>Here is an implementation example in Python that demonstrates how to pack and unpack binary data using the <code>struct</code> module:</p>
<pre><code class="lang-python">```python
<span class="hljs-keyword">import</span> socket
<span class="hljs-keyword">import</span> struct

HOST = <span class="hljs-string">"localhost"</span>
PORT = <span class="hljs-number">5000</span>


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">serve</span>():</span>
    <span class="hljs-comment"># Create a TCP/IP socket</span>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((HOST, PORT))
    sock.listen(<span class="hljs-number">1</span>)
    print(<span class="hljs-string">"Server listening on {}:{}"</span>.format(HOST, PORT))

    <span class="hljs-comment"># Wait for a connection</span>
    conn, addr = sock.accept()
    print(<span class="hljs-string">"Connected by "</span>, addr)

    <span class="hljs-comment"># Receive the data</span>
    data = conn.recv(<span class="hljs-number">1024</span>)
    print(<span class="hljs-string">"Received: {!r}"</span>.format(data))

    <span class="hljs-comment"># Unpack the binary data</span>
    unpacked_data = struct.unpack(<span class="hljs-string">"!Hf"</span>, data)
    print(<span class="hljs-string">"Unpacked data: "</span>, unpacked_data)

    conn.close()
    sock.close()


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">client</span>():</span>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((HOST, PORT))

    <span class="hljs-comment"># Pack the binary data</span>
    data = struct.pack(<span class="hljs-string">"!Hf"</span>, <span class="hljs-number">123</span>, <span class="hljs-number">3.14</span>)

    <span class="hljs-comment"># Send the data</span>
    sock.sendall(data)

    sock.close()
```
</code></pre>
<p>In this example, the server listens for incoming connections on <a target="_blank" href="http://localhost">localhost</a> on port 5000. When a client connects, the server receives binary data from the client using the <code>recv</code> method of the socket. The server then unpacks the binary data using the <code>struct.unpack</code> function.</p>
<p>The client packs binary data in network byte order using the <code>struct.pack</code> function and sends it to the server using the <code>sendall</code> method of the socket.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In conclusion, handling binary data in socket programming requires attention to byte order, as different systems use different byte orders. To ensure compatibility, it is recommended to use network byte order, which is defined as big-endian byte order. The struct module in Python provides functions for packing and unpacking binary data in a specific format, making it easy to handle binary data in socket programming.</p>
]]></content:encoded></item><item><title><![CDATA[Handling Message Boundaries in Socket Programming]]></title><description><![CDATA[In TCP Socket, handling message boundaries can be a huge problem. This is because TCP sockets are stream-oriented, meaning that data is transmitted as a continuous stream of bytes without any inherent message boundaries. Sending and receiving message...]]></description><link>https://enzircle.com/handling-message-boundaries-in-socket-programming</link><guid isPermaLink="true">https://enzircle.com/handling-message-boundaries-in-socket-programming</guid><category><![CDATA[Python]]></category><category><![CDATA[socket]]></category><category><![CDATA[network]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Sun, 26 Feb 2023 08:40:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/21mJd5NUGZU/upload/6716fd5573ad2995567c1ff8cc5f325a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In TCP Socket, handling message boundaries can be a huge problem. This is because TCP sockets are stream-oriented, meaning that data is transmitted as a continuous stream of bytes without any inherent message boundaries. Sending and receiving messages operate on network buffers. So you have to save the data you receive in a buffer until you have read enough bytes to have a complete message that makes sense to your application. It is up to you to define and keep track of where the message ends. As far as the TCP socket is concerned, it is just sending and receiving bytes to and from the network.</p>
<p>Fortunately, there are several methods you can use to handle message boundaries in socket programming. In this article, I will explore some of the most common methods and provide examples for each one.</p>
<h2 id="heading-method-1-fixed-length-messages">Method 1: Fixed-length messages</h2>
<p>One of the simplest ways to handle message boundaries is to send messages of a fixed length. This means that the sender and receiver agree on the length of each message, and the receiver knows exactly how many bytes to read in order to receive a complete message.</p>
<p>Here is an example of how you can send and receive fixed-length messages:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> socket

HOST = <span class="hljs-string">"localhost"</span>
PORT = <span class="hljs-number">12345</span>
MSG_LEN = <span class="hljs-number">10</span>


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">serve</span>():</span>
    <span class="hljs-comment"># Initialize Socket</span>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((HOST, PORT))
    sock.listen()
    print(<span class="hljs-string">"Server listening on {}:{}"</span>.format(HOST, PORT))

    <span class="hljs-comment"># Accept connection from outside</span>
    conn, addr = sock.accept()
    print(<span class="hljs-string">"Connected by"</span>, addr)

    <span class="hljs-comment"># Receive the message data</span>
    chunks = []
    bytes_recd = <span class="hljs-number">0</span>

    <span class="hljs-comment"># recv only tell you how many bytes they handled.</span>
    <span class="hljs-comment"># So you need to keep recv until the message length is</span>
    <span class="hljs-comment"># correct.</span>
    <span class="hljs-keyword">while</span> bytes_recd &lt; MSG_LEN:
        chunk = conn.recv(min(MSG_LEN - bytes_recd, <span class="hljs-number">2048</span>))
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> chunk:
            <span class="hljs-keyword">raise</span> RuntimeError(<span class="hljs-string">"ERROR"</span>)
        chunks.append(chunk)
        bytes_recd += len(chunk)

    data = <span class="hljs-string">b""</span>.join(chunks)

    <span class="hljs-comment"># Print the message </span>
    message = data.decode(<span class="hljs-string">"utf-8"</span>).strip()
    print(<span class="hljs-string">"Received message: "</span>, message)

    <span class="hljs-comment"># Close connection and socket</span>
    conn.close()
    sock.close()    


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">client</span>():</span>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((HOST, PORT))

    message = input(<span class="hljs-string">"Enter a message: "</span>)

    <span class="hljs-comment"># Truncate or pad the message to fit the fixed length</span>
    message = message[:MSG_LEN].ljust(MSG_LEN)

    <span class="hljs-comment"># Send the message data</span>
    sock.sendall(message.encode(<span class="hljs-string">"utf-8"</span>))

    sock.close()
</code></pre>
<p>In this example, the server is set up to listen for incoming connections and receive messages of exactly 10 bytes. If the client sends a message with less than 10 bytes, we will pad the message with space. For this simplified version, if the message is larger than <code>MSG_LEN</code>, we simply truncate the message.</p>
<p>This method is pretty easy to understand and implement. However, the downside of this method is how inefficient it is when dealing with lots of small messages. Plus you still have to deal with data not fitting into one message.</p>
<h2 id="heading-method-2-delimiter-separated-messages">Method 2: Delimiter-Separated Messages</h2>
<p>Another common method for handling message boundaries is to use a delimiter to separate messages. This means that the sender and receiver agree on a special character or sequence of characters that will mark the end of each message. The only thing you need to pay attention to is when you allow multiple messages to be sent back to back. You may end up reading the start of the following message. You will need to hold onto it until it is needed.</p>
<p>Here is an example of how you can send and receive delimiter-separated messages using Python:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> socket

HOST = <span class="hljs-string">"localhost"</span>
PORT = <span class="hljs-number">12346</span>
DELIMITER = <span class="hljs-string">b"\r\n"</span>
BUFFER_SIZE = <span class="hljs-number">4096</span>


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Buffer</span>(<span class="hljs-params">object</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, sock</span>):</span>
        self.sock = sock
        self.buffer = <span class="hljs-string">b""</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_line</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">while</span> <span class="hljs-string">b"\r\n"</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.buffer:
            data = self.sock.recv(BUFFER_SIZE)
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> data: <span class="hljs-comment"># socket is closed</span>
                <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>
            self.buffer += data
        line, sep, self.buffer = self.buffer.partition(<span class="hljs-string">b"\r\n"</span>)
        <span class="hljs-keyword">return</span> line.decode()


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">serve</span>():</span>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((HOST, PORT))
    sock.listen()
    print(<span class="hljs-string">f"Server listening on <span class="hljs-subst">{HOST}</span>:<span class="hljs-subst">{PORT}</span>"</span>)

    conn, addr = sock.accept()
    print(<span class="hljs-string">"Connected by"</span>, addr)

    buff = Buffer(conn)
    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>: 
        line = buff.get_line()
        <span class="hljs-keyword">if</span> line <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
            <span class="hljs-keyword">break</span>
        print(<span class="hljs-string">"Received message: "</span>, line)

    conn.close()
    sock.close()


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">client</span>():</span>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((HOST, PORT))

    messages = [<span class="hljs-string">b"Line One\r\n"</span>,
                <span class="hljs-string">b"Line "</span>,
                <span class="hljs-string">b"Two\r\nLine"</span>,
                <span class="hljs-string">b" Three\r\n"</span>]

    <span class="hljs-comment"># Send the message data</span>
    <span class="hljs-keyword">for</span> message <span class="hljs-keyword">in</span> messages:
       sock.sendall(message)

    sock.close()
</code></pre>
<p>In this example, the server is set up to listen for incoming connections and receive messages that are delimited by <code>\r\n</code> characters. The client sends four messages, and the server splits the incoming data into separate messages based on the delimiter.</p>
<h2 id="heading-method-3-message-length-header">Method 3: Message Length Header</h2>
<p>A more advanced method for handling message boundaries is to include a message length header in each message. This means that the sender and receiver agree on a fixed number of bytes that will indicate the length of the message that follows.</p>
<p>In this method, we will use a message length header, which is a fixed-length field that indicates the length of the message to follow. The sender first sends the length of the message and then the actual message. The receiver reads the message length, reads the corresponding number of bytes from the socket, and then processes the message.</p>
<p>Here is an example of how you can send and receive messages with a length header using Python:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> socket

HOST = <span class="hljs-string">"localhost"</span>
PORT = <span class="hljs-number">12345</span>
BUFFER_SIZE = <span class="hljs-number">1024</span>


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">serve</span>():</span>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((HOST, PORT))
    sock.listen()
    print(<span class="hljs-string">"Server listening on {}:{}"</span>.format(HOST, PORT))

    conn, addr = sock.accept()
    print(<span class="hljs-string">"Connected by"</span>, addr)

    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
        <span class="hljs-comment"># Receive the length</span>
        header = conn.recv(<span class="hljs-number">4</span>)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> header:
            <span class="hljs-keyword">break</span>

        <span class="hljs-comment"># Parse the header</span>
        msg_len = int.from_bytes(header[<span class="hljs-number">0</span>:<span class="hljs-number">4</span>], byteorder=<span class="hljs-string">"big"</span>)

        <span class="hljs-comment"># Receive the message data</span>
        chunks = []
        bytes_recd = <span class="hljs-number">0</span>
        <span class="hljs-keyword">while</span> bytes_recd &lt; msg_len:
            chunk = conn.recv(min(msg_len - bytes_recd,
                                  BUFFER_SIZE))
            <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> chunk:
                <span class="hljs-keyword">raise</span> RuntimeError(<span class="hljs-string">"ERROR"</span>)
            chunks.append(chunk)
            bytes_recd += len(chunk)

        data = <span class="hljs-string">b""</span>.join(chunks)

        <span class="hljs-comment"># Print the message</span>
        message = data.decode(<span class="hljs-string">"utf-8"</span>).strip()
        print(<span class="hljs-string">"Received message:"</span>, message)

    conn.close()
    sock.close()

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">client</span>():</span>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((HOST, PORT))

    message = input(<span class="hljs-string">'Enter a message: '</span>)

    <span class="hljs-comment"># Create the header</span>
    msg_len = len(message.encode(<span class="hljs-string">'utf-8'</span>))
    header = msg_len.to_bytes(<span class="hljs-number">4</span>, byteorder=<span class="hljs-string">'big'</span>)

    <span class="hljs-comment"># Send the header and message data</span>
    sock.sendall(header + message.encode(<span class="hljs-string">'utf-8'</span>))

    sock.close()
</code></pre>
<p>In this example, the server is set up to listen for incoming connections and receive messages with a length header. The client sends two messages, each preceded by a 4-byte length header. The server first reads the message length header, unpacks it using the struct module, and then reads the corresponding number of bytes from the socket to get the message.</p>
<h3 id="heading-warning">Warning</h3>
<p>The above example has two <code>recv</code>, the first is to get the length, and the second one is in a loop to get the rest. But, since we are working with a network buffer, the first <code>recv</code> might not get all 4 characters in one <code>recv</code>. If you choose to ignore this, in high network loads your code will break. To be bulletproof, you need two <code>recv</code> loops, to first determine the length, and the second part to get the data.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Handling message boundaries in socket programming using Python can be challenging, but there are several methods you can use to solve this problem. Fixed-length messages, delimiter-separated messages, and message-length headers are some of the most common methods, each with its own advantages and disadvantages. By understanding these methods and how to implement them in Python, you can write robust and reliable network applications.</p>
]]></content:encoded></item><item><title><![CDATA[Bulk Actions in Table with Django, htmx, and AlpineJS]]></title><description><![CDATA[When it comes to displaying large amounts of data in a tabular format, tables are often the go-to choice for developers. However, traditional tables can be limiting in their functionality, especially when it comes to performing actions on multiple ro...]]></description><link>https://enzircle.com/bulk-actions-in-table-with-django-htmx-and-alpinejs</link><guid isPermaLink="true">https://enzircle.com/bulk-actions-in-table-with-django-htmx-and-alpinejs</guid><category><![CDATA[Django]]></category><category><![CDATA[htmx]]></category><category><![CDATA[alpinejs]]></category><category><![CDATA[Python]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Thu, 19 Jan 2023 02:50:34 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/tjX_sniNzgQ/upload/a1905f9c7bad1624af0da0b827395f36.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When it comes to displaying large amounts of data in a tabular format, tables are often the go-to choice for developers. However, traditional tables can be limiting in their functionality, especially when it comes to performing actions on multiple rows. This can lead to a poor user experience, as users are forced to perform actions on each row individually, leading to slow and tedious interactions.</p>
<p>The need for a table that supports multiple actions by the user is becoming increasingly important, especially in today's fast-paced digital environment. Users expect to be able to perform actions on multiple rows in a single click, providing a more efficient and user-friendly experience. By implementing a table that supports multiple actions by the user, developers can improve their applications' overall usability and satisfaction, making it a necessary feature for modern web development.</p>
<p>In this article, we will discuss how to implement a table that supports bulk actions by the user using the combination of the <code>django-tables2</code>, <code>htmx</code>, and <code>AlpineJS</code> libraries. The <code>django-tables2</code> package is a powerful library that allows developers to create and manage tables in Django projects with minimal effort. It provides a simple and flexible way to define the columns, data, and behavior of the table. The <code>htmx</code> and <code>Alpine.js</code> packages are lightweight JavaScript libraries that allow developers to perform rich interactions and dynamic updates on web pages without the need for extensive JavasScript code.</p>
<p>Together, these libraries provide a powerful solution for building tables without compromising performance, scalability, and maintainability.</p>
<h2 id="heading-the-requirements">The Requirements</h2>
<p>Before we start, let’s write down what we want to build. The following are the rough specification</p>
<ol>
<li><p>The table shall support pagination and sorting when the user clicks on the column header.</p>
</li>
<li><p>The table shall have a search query form that allows the user to filter the table. The pagination and sort shall be ignored when the user performs a search.</p>
</li>
<li><p>The table shall allow the user to select the row(s) and perform a specific action on those selected row(s).</p>
<ul>
<li><p>Provide a checkbox column. The user shall be able to select individual rows by clicking on the checkbox of each row.</p>
</li>
<li><p>When the user clicks on the checkbox column on the table header, all the checkboxes in the rows on this page shall be selected.</p>
</li>
<li><p>The user shall be able to select multiple rows using the combination of the shift key and a mouse click on each row.</p>
</li>
</ul>
</li>
<li><p>The table shall be rendered in a way that provides a seamless interaction.</p>
<ul>
<li><p>No page refresh on any table actions</p>
</li>
<li><p>A progress bar to indicate the process of fetching data on all activities that requires access to the server (pagination, sorting, filtering, etc.)</p>
</li>
<li><p>Provide a highlight for rows that are recently updated.</p>
</li>
</ul>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674096467951/0b3170ed-f30f-442f-bbec-f53846e017fa.png" alt /></p>
<h2 id="heading-the-packages-used-for-this-project">The Packages Used For This Project</h2>
<p>To build this table, we will need to use the following packages:</p>
<ol>
<li><p><a target="_blank" href="https://htmx.org/">htmx</a>. <code>htmx</code> gives access to using AJAX directly in HTML, using attributes. It is simple and, for a package this small, quite powerful.</p>
</li>
<li><p><a target="_blank" href="https://github.com/jieter/django-tables2">django-tables2</a>. This Django app lets you define tables like you define Django models. It can automatically generate a table based on a Django model. It supports pagination, column-based table sorting, custom column functionality via subclassing, and many other features.</p>
</li>
<li><p><a target="_blank" href="https://github.com/carltongibson/django-filter">django-filter</a>. I use this package for the filtering functionality. It has APIs similar to Django's <code>ModelForm</code> and works well with <code>django-tables2</code>.</p>
</li>
<li><p><a target="_blank" href="https://github.com/adamchainz/django-htmx">django-htmx</a>. For <code>htmx</code> to work, Django view needs to tell which request is made using <code>htmx</code> and which is not. It has a middleware that adds <code>htmx</code> attribute to a request object.</p>
</li>
<li><p><a target="_blank" href="https://alpinejs.dev/">Alpine-js</a>. This project requires a more complex behavior, <code>htmx</code> alone will not be enough. Specifically, the table needs to make sorting, pagination, and filtering work together nicely. This is where <code>Alpine-js</code> comes in. This small javascript package allows me to store data and trigger action based on changes happening to a variable.</p>
</li>
</ol>
<h2 id="heading-the-model">The Model</h2>
<p>First, let's define a model to work with.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models  


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Product</span>(<span class="hljs-params">models.Model</span>):</span>  
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Status</span>(<span class="hljs-params">models.IntegerChoices</span>):</span>  
        ACTIVE = <span class="hljs-number">1</span>, <span class="hljs-string">"Active"</span>  
        INACTIVE = <span class="hljs-number">2</span>, <span class="hljs-string">"Inactive"</span>  
        ARCHIVED = <span class="hljs-number">3</span>, <span class="hljs-string">"Archived"</span>  

    name = models.CharField(max_length=<span class="hljs-number">255</span>)  
    category = models.CharField(max_length=<span class="hljs-number">255</span>)  
    price = models.DecimalField(max_digits=<span class="hljs-number">10</span>, decimal_places=<span class="hljs-number">2</span>)  
    cost = models.DecimalField(max_digits=<span class="hljs-number">10</span>, decimal_places=<span class="hljs-number">2</span>)  
    status = models.PositiveSmallIntegerField(choices=Status.choices)  

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        ordering = (<span class="hljs-string">"pk"</span>,)  

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>  
        <span class="hljs-keyword">return</span> self.name
</code></pre>
<h2 id="heading-the-table">The Table</h2>
<p>Next, based on our model above, we are going to define the table. For this project, we need to:</p>
<ul>
<li><p>Add a checkbox column. We do this by using <code>tables.CheckBoxColumn(accessor="pk", orderable=False)</code>.</p>
</li>
<li><p>To make the checkbox column appear as the first column, we will utilize the <code>sequence</code> parameter on the table meta's class.</p>
</li>
<li><p>We also need to add a class to our rows to indicate when rows are updated. We use this class to give some animation when a user updates rows. To achieve this, we will use the <code>row_attrs</code> parameter on the table meta's class. To get the information of the updated rows, we will add a new parameter for the table <code>__init__</code> method named <code>selection</code>.</p>
</li>
<li><p>Also, we will allow a user to select multiple rows using Shift Key and a mouse click. To achieve this, we are going to define an attribute <code>attrs={td__input:{ "@click": "checkRange"}}</code>. The <code>AlpineJS</code> package will handle the rest.</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> django_tables2 <span class="hljs-keyword">as</span> tables  

<span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> Product

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">rows_higlighter</span>(<span class="hljs-params">**kwargs</span>):</span>  
    <span class="hljs-comment"># Add highlight class to rows </span>
    <span class="hljs-comment"># when the product is recently updated.</span>
    <span class="hljs-comment"># Recently updated rows are in the table</span>
    <span class="hljs-comment"># selection parameter.  </span>
    selected_rows = kwargs[<span class="hljs-string">"table"</span>].selected_rows  
    <span class="hljs-keyword">if</span> selected_rows <span class="hljs-keyword">and</span> kwargs[<span class="hljs-string">"record"</span>].pk <span class="hljs-keyword">in</span> selected_rows:  
        <span class="hljs-keyword">return</span> <span class="hljs-string">"highlight-me"</span>  
    <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductHTMxBulkActionTable</span>(<span class="hljs-params">tables.Table</span>):</span>  
    <span class="hljs-comment"># Add a checkbox column to the table.  </span>
    selection = tables.CheckBoxColumn(accessor=<span class="hljs-string">"pk"</span>, orderable=<span class="hljs-literal">False</span>,   
                                      attrs={  
                                        <span class="hljs-string">"td__input"</span>: {  
                                            <span class="hljs-string">"@click"</span>: <span class="hljs-string">"checkRange"</span>  
                                        }  
                                      })  
    <span class="hljs-comment"># Status is not orderable</span>
    status = tables.Column(accessor=<span class="hljs-string">"status"</span>, orderable=<span class="hljs-literal">False</span>)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>  
        model = Product  
        template_name = <span class="hljs-string">"tables/bootstrap_htmx_bulkaction.html"</span>
        show_header = <span class="hljs-literal">False</span>

        <span class="hljs-comment"># This will put the checkbox column first.  </span>
        sequence = (<span class="hljs-string">"selection"</span>, <span class="hljs-string">"..."</span>)  

        <span class="hljs-comment"># This will add the highlight class to the rows  </span>
        <span class="hljs-comment"># when the product is recently updated.</span>
        row_attrs = {  
            <span class="hljs-string">"class"</span>: rows_higlighter  
        }  

        <span class="hljs-comment"># Additional class for easier styling.  </span>
        attrs = {<span class="hljs-string">"class"</span>: <span class="hljs-string">"table checkcolumn-table"</span>}  

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, selected_rows=None, *args, **kwargs</span>):</span>  
        super().__init__(*args, **kwargs)  

        <span class="hljs-comment"># The selection parameter is a list of product ids  </span>
        <span class="hljs-comment"># that are recently updated.</span>
        self.selected_rows = selected_rows 
        <span class="hljs-keyword">return</span>
</code></pre>
<h2 id="heading-the-filter">The Filter</h2>
<p>The table supports a single query form so that the user can filter the table. We define the filter below.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> decimal <span class="hljs-keyword">import</span> Decimal  

<span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Q  
<span class="hljs-keyword">from</span> django.forms <span class="hljs-keyword">import</span> TextInput  
<span class="hljs-keyword">import</span> django_filters  

<span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> Product

<span class="hljs-comment"># Custom widget that uses search input type</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SearchInput</span>(<span class="hljs-params">TextInput</span>):</span>  
    input_type = <span class="hljs-string">"search"</span>  


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductUniversalFilter</span>(<span class="hljs-params">django_filters.FilterSet</span>):</span>  
    query = django_filters.CharFilter(  
        method=<span class="hljs-string">"universal_search"</span>,  
        label=<span class="hljs-string">""</span>,  
        widget=SearchInput(attrs={<span class="hljs-string">"placeholder"</span>: <span class="hljs-string">"Search..."</span>}),  
    )  

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>  
        model = Product  
        fields = [<span class="hljs-string">"query"</span>]  

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">universal_search</span>(<span class="hljs-params">self, queryset, name, value</span>):</span>  
        <span class="hljs-keyword">if</span> value.replace(<span class="hljs-string">"."</span>, <span class="hljs-string">""</span>, <span class="hljs-number">1</span>).isdigit():  
            value = Decimal(value)  
            <span class="hljs-keyword">return</span> Product.objects.filter(Q(price=value) | Q(cost=value))  

        <span class="hljs-keyword">return</span> Product.objects.filter(  
            Q(name__icontains=value) | Q(category__icontains=value)  
        )
</code></pre>
<p>I made a simple custom widget to change the <code>input_type</code> to <code>search</code>. The main basic differences come in the way browsers handle them. The first thing to note is that some browsers show a cross icon that can be clicked on to remove the search term instantly if desired.</p>
<h2 id="heading-the-views">The Views</h2>
<p>Now that we have our model, table, and filter, we can start implementing our view. First, we need to implement a view that renders the table.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django_tables2 <span class="hljs-keyword">import</span> SingleTableMixin  
<span class="hljs-keyword">from</span> django_filters.views <span class="hljs-keyword">import</span> FilterView  

<span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> Product  
<span class="hljs-keyword">from</span> .tables <span class="hljs-keyword">import</span> ProductHTMxBulkActionTable
<span class="hljs-keyword">from</span> .filters <span class="hljs-keyword">import</span> ProductUniversalFilter


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductHTMxBulkActionView</span>(<span class="hljs-params">SingleTableMixin, FilterView</span>):</span>  
    table_class = ProductHTMxBulkActionTable  
    queryset = Product.objects.all()  
    filterset_class = ProductUniversalFilter  
    paginate_by = <span class="hljs-number">10</span>  

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_template_names</span>(<span class="hljs-params">self</span>):</span>  
        <span class="hljs-keyword">if</span> self.request.htmx:  
            template_name = <span class="hljs-string">"products/product_table_partial.html"</span>  
        <span class="hljs-keyword">else</span>:  
            template_name = <span class="hljs-string">"products/product_table_bulkaction.html"</span>  

        <span class="hljs-keyword">return</span> template_name  

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_table_kwargs</span>(<span class="hljs-params">self</span>):</span>  
        <span class="hljs-comment"># Get the list of recently updated products.  </span>
        <span class="hljs-comment"># Pass the list to the table kwargs.</span>
        kwargs = super().get_table_kwargs()  
        selected_rows = self.request.GET.get(<span class="hljs-string">"selection"</span>, <span class="hljs-literal">None</span>)  
        <span class="hljs-keyword">if</span> selected_rows:  
            selected_rows = [int(_) <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> selected_rows.split(<span class="hljs-string">","</span>)]  
            kwargs[<span class="hljs-string">"selected_rows"</span>] = selected_rows  

        <span class="hljs-keyword">return</span> kwargs
</code></pre>
<p>In the <code>get_template_names</code>, we render different templates based on whether or not the request is made via <code>htmx</code>. Also, since we need to pass the selected rows to our table constructor, we need to implement the <code>get_table_kwargs</code>.</p>
<p>Next, we need a view to handle the bulk update request.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.http <span class="hljs-keyword">import</span> HttpResponseRedirect 
<span class="hljs-keyword">from</span> django.utils.http <span class="hljs-keyword">import</span> urlencode  
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> reverse_lazy  


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">reverse_querystring</span>(<span class="hljs-params">view, urlconf=None, args=None, kwargs=None,
                        current_app=None, query_kwargs=None</span>):</span>  
    <span class="hljs-string">'''Custom reverse to handle query strings.  
    Usage:
        reverse('app.views.my_view', kwargs={'pk': 123}, 
                 query_kwargs={'search': 'Bob'})
    '''</span>
    base_url = reverse_lazy(view, urlconf=urlconf, args=args, 
                            kwargs=kwargs, current_app=current_app)  
    <span class="hljs-keyword">if</span> query_kwargs:  
        <span class="hljs-keyword">return</span> <span class="hljs-string">'{}?{}'</span>.format(base_url, urlencode(query_kwargs))  
    <span class="hljs-keyword">return</span> base_url


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">response_updateview</span>(<span class="hljs-params">request</span>):</span>  
    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">"POST"</span> <span class="hljs-keyword">and</span> request.htmx:  
        <span class="hljs-comment"># Get the selected products  </span>
        selected_products = request.POST.getlist(<span class="hljs-string">"selection"</span>)  

        <span class="hljs-comment"># Check if the activate/deactivate button is pressed  </span>
        <span class="hljs-keyword">if</span> request.htmx.trigger_name == <span class="hljs-string">"activate"</span>:  
            <span class="hljs-comment"># Activate the selected products              </span>
            Product.objects
                   .filter(pk__in=selected_products)
                   .update(status=Product.Status.ACTIVE)  
        <span class="hljs-keyword">elif</span> request.htmx.trigger_name == <span class="hljs-string">"deactivate"</span>:  
            <span class="hljs-comment"># Deactivate the selected products  </span>
            Product.objects
                   .filter(pk__in=selected_products)
                   .update(status=Product.Status.INACTIVE)  

        <span class="hljs-comment"># Get the page number  </span>
        page = request.POST.get(<span class="hljs-string">"page"</span>, <span class="hljs-number">1</span>)  
        page = int(page)  

        <span class="hljs-comment"># Get the sort by column  </span>
        sort_by = request.POST.get(<span class="hljs-string">"sort"</span>, <span class="hljs-literal">None</span>)

        <span class="hljs-comment"># Get the query  </span>
        query = request.POST.get(<span class="hljs-string">"query"</span>, <span class="hljs-string">""</span>)  

        <span class="hljs-comment"># Get selection  </span>
        selection = <span class="hljs-string">","</span>.join(selected_products)  

    <span class="hljs-comment"># Redirect to table view </span>
    <span class="hljs-keyword">return</span> HttpResponseRedirect(  
        reverse_querystring(<span class="hljs-string">"products:products_htmx_bulkaction"</span>,   
                            query_kwargs={<span class="hljs-string">"page"</span>: page, <span class="hljs-string">"sort"</span>: sort_by,  
                                          <span class="hljs-string">"query"</span>: query,
                                          <span class="hljs-string">"selection"</span>: selection}))
</code></pre>
<p>In this view, we first check what action is triggered (activate/deactivate) and update our data. Finally, we redirect to our table view, including all the needed parameters like pagination, sort, query/filter, and the selected row.</p>
<h2 id="heading-the-templates">The Templates</h2>
<p>There are three templates in play here:</p>
<ol>
<li><p>A template to render the whole page. Where I generate the page with an HTML header and body and the table.</p>
</li>
<li><p>A template to render only the table. When we perform some action like sorting or filtering, we only need to render the partial page.</p>
</li>
<li><p>Custom table template to override the default. I need to remove the table definition. And add some <code>Alpine-js</code> attributes for the pagination to work.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674096488268/7b44f1a6-c296-4da6-af5e-6c48175ca1b5.png" alt /></p>
<h3 id="heading-template-to-render-the-entire-page">Template to Render the Entire Page</h3>
<p>For this project, I put the table header and query form on this page. This template has a lot of functionality.</p>
<ol>
<li><p>Bulk activate or de-active products. To achieve this, we need to include all information like pagination, sorting, and filtering. We accomplish this using <code>hx-include="#bulk-actions, #id_query"</code>.</p>
</li>
<li><p>For searching, we included a simple search form. There are two ways to trigger the search functionality. The first is when the user types something in the search box. We delay the trigger for 500ms, and the trigger only happens when the content is changed. The second trigger is when the user clicks on the cross icon on the search box. We achieve this by using <code>hx-trigger="keyup changed delay:500ms, search"</code>. Also, when we search, we will remove the pagination and sorting information. To achieve this, we first need to define <code>x-data</code> and trigger the custom <code>clear-pagination-and-sort</code> event before every request. To accomplish this functionality, we use <code>x-on:htmx:before-request="$dispatch('clear-pagination-and-sort')</code>. The custom event is defined later via the <code>@clear-pagination-and-sort.window</code> functionality.</p>
</li>
<li><p>Pagination and sorting. I define two extra hidden <code>input</code> fields. The <code>sort</code> and <code>page</code> input. When a user performs an action, be it sorting, jumping between pages, or filtering, I will submit all three pieces of information back to the server.</p>
</li>
<li><p>We also added functionality in the check box column, where the whole rows in that page got selected upon clicking. We achieved this using <code>@click="toggleSelection()"</code>. Inside the <code>toggleSelection</code> we simply checked all rows on that page.</p>
</li>
<li><p>To support multiple rows selection when the user uses Shift Key and mouse click, I added the <code>checkRange</code> function at the bottom.</p>
</li>
</ol>
<pre><code class="lang-html">{% extends "products/base.html" %}

{% load render_table from django_tables2 %}
{% load i18n %}
{% load django_tables2 %}

{% block bulkaction_table %}active{% endblock %}

{% block table_main %}
<span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Product Table<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">class</span>=<span class="hljs-string">"table-top-container"</span>&gt;</span>

  {# Bulk actions and search bar #}
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"row justify-content-between"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col-4"</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">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"activate"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"activate"</span>
                  <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-secondary"</span>
                  <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"{% url 'tables:products_htmx_bulkaction_update' %}"</span>
                  <span class="hljs-attr">hx-target</span>=<span class="hljs-string">".table-container"</span>
                  <span class="hljs-attr">hx-indicator</span>=<span class="hljs-string">".progress"</span>
                  <span class="hljs-attr">hx-include</span>=<span class="hljs-string">"#bulk-actions, #id_query"</span>&gt;</span>
          Activate
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"deactivate"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"deactivate"</span>
                <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-secondary"</span>
                <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"{% url 'tables:products_htmx_bulkaction_update' %}"</span>
                <span class="hljs-attr">hx-target</span>=<span class="hljs-string">".table-container"</span>
                <span class="hljs-attr">hx-indicator</span>=<span class="hljs-string">".progress"</span>
                <span class="hljs-attr">hx-include</span>=<span class="hljs-string">"#bulk-actions, #id_query"</span>&gt;</span>
        Deactivate
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</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 class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col-4"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-inline"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"d-flex justify-content-end"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"search"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"query"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Search..."</span>
                 <span class="hljs-attr">class</span>=<span class="hljs-string">"searchinput form-control"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"id_query"</span>
                 <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"keyup changed delay:500ms, search"</span>
                 <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"{% url 'tables:products_htmx_bulkaction' %}"</span>
                 <span class="hljs-attr">hx-indicator</span>=<span class="hljs-string">".progress"</span>
                 <span class="hljs-attr">hx-target</span>=<span class="hljs-string">".table-container"</span>
                 <span class="hljs-attr">x-data</span>
                 <span class="hljs-attr">x-on:htmx:before-request</span>=<span class="hljs-string">"$dispatch('clear-pagination-and-sort')"</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 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>

  {# Table header #}
  <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"bulk-actions"</span>
        <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"{% url 'tables:products_htmx_bulkaction' %}"</span>
        <span class="hljs-attr">hx-target</span>=<span class="hljs-string">".table-container"</span>
        <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"sort-initiated, pagination-initiated"</span>
        <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span>
        <span class="hljs-attr">hx-include</span>=<span class="hljs-string">"#id_query"</span>
        <span class="hljs-attr">hx-indicator</span>=<span class="hljs-string">".progress"</span>
        <span class="hljs-attr">x-data</span>=<span class="hljs-string">"{ sort_by: '',
                  page_by: 1,
                  select_all: false,
                  last_checked: false }"</span>
        @<span class="hljs-attr">clear-pagination-and-sort.window</span>=<span class="hljs-string">"page_by = 1; sort_by = ''"</span>
        <span class="hljs-attr">x-on:htmx:after-swap</span>=<span class="hljs-string">"select_all = false"</span>&gt;</span>

    {% csrf_token %}

    {# Hidden input to store pagination page and column to sort by #}
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"sort"</span> <span class="hljs-attr">x-ref</span>=<span class="hljs-string">"sort_input"</span> <span class="hljs-attr">x-model</span>=<span class="hljs-string">"sort_by"</span>
           <span class="hljs-attr">x-init</span>=<span class="hljs-string">"$watch('sort_by',
                           () =&gt; $refs.sort_input.dispatchEvent(
                                    new Event('sort-initiated',
                                              { bubbles: true })))"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"page"</span> <span class="hljs-attr">x-ref</span>=<span class="hljs-string">"paginate_input"</span> <span class="hljs-attr">x-model</span>=<span class="hljs-string">"page_by"</span>
           <span class="hljs-attr">x-init</span>=<span class="hljs-string">"$watch('page_by',
                           () =&gt; $refs.paginate_input.dispatchEvent(
                                    new Event('pagination-initiated',
                                              { bubbles: true })))"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">table</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table checkcolumn-table header"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">thead</span> {{ <span class="hljs-attr">table.attrs.thead.as_html</span> }}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
          {% for column in table.columns %}
            {% if column.name == 'selection' %}
            <span class="hljs-tag">&lt;<span class="hljs-name">th</span> {{ <span class="hljs-attr">column.attrs.th.as_html</span> }}
                <span class="hljs-attr">x-data</span>=<span class="hljs-string">"{ toggleSelection(event) {
                  select_all = !select_all;
                  let checkboxes = document.getElementsByName('selection');
                  [...checkboxes].map((el) =&gt; {
                    el.checked = select_all;
                  })
                }
              }"</span>
              @<span class="hljs-attr">click</span>=<span class="hljs-string">"toggleSelection()"</span>
              <span class="hljs-attr">style</span>=<span class="hljs-string">"cursor: pointer;"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"checkbox"</span> <span class="hljs-attr">x-model</span>=<span class="hljs-string">"select_all"</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
            {% else %}
              {% if column.orderable %}
                <span class="hljs-tag">&lt;<span class="hljs-name">th</span> {{ <span class="hljs-attr">column.attrs.th.as_html</span> }}
                    <span class="hljs-attr">x-data</span>=<span class="hljs-string">"{ col_name: '{{ column.order_by_alias }}',
                    toggleSort(event) {
                      this.col_name = this.col_name.startsWith('-') ? this.col_name.substring(1) : ('-' + this.col_name);
                      sort_by = this.col_name;
                    }
                  }"</span>
                  @<span class="hljs-attr">click</span>=<span class="hljs-string">"toggleSort()"</span>
                  <span class="hljs-attr">:class</span>=<span class="hljs-string">"sort_by !== '' ? (sort_by === col_name ? (sort_by.startsWith('-') ? 'desc' : 'asc') : '') : ''"</span>
                  <span class="hljs-attr">style</span>=<span class="hljs-string">"cursor: pointer;"</span>&gt;</span>
                  {{ column.header }}
                <span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
              {% else %}
                <span class="hljs-tag">&lt;<span class="hljs-name">th</span> {{ <span class="hljs-attr">column.attrs.th.as_html</span> }}&gt;</span>{{ column.header }}<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
              {% endif %}
            {% endif %}
          {% endfor %}
        <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">table</span>&gt;</span>

    {# Progress indicator #}
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"progress"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"indeterminate"</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>

    {# Content table #}
    {% render_table table %}
  <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{% endblock %}

{% block footer %}
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    <span class="hljs-comment">// Set the checkbox to be checked from the start </span>
    <span class="hljs-comment">// to end when the user presses the shift key.</span>
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkRange</span>(<span class="hljs-params">event</span>) </span>{
    <span class="hljs-keyword">let</span> checkboxes = <span class="hljs-built_in">document</span>.getElementsByName(<span class="hljs-string">'selection'</span>);
    <span class="hljs-keyword">let</span> inBetween =  <span class="hljs-literal">false</span>;
    <span class="hljs-keyword">if</span>( event.shiftKey &amp;&amp; event.target.checked ) {
      checkboxes.forEach( <span class="hljs-function"><span class="hljs-params">checkbox</span> =&gt;</span> {
        <span class="hljs-keyword">if</span>( checkbox === event.target || checkbox === last_checked ) {
          inBetween = !inBetween;
        }
        <span class="hljs-keyword">if</span>( inBetween ) {
          checkbox.checked = <span class="hljs-literal">true</span>;
        }
      });
    }
    last_checked = event.target;
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
{% endblock %}
</code></pre>
<h3 id="heading-template-to-render-table">Template to Render Table</h3>
<p>Here we only render the table content, no HTML header, and no HTML body. Just the content.</p>
<pre><code class="lang-html">{# templates/products/product_table_partial.html #}
{% load render_table from django_tables2 %}

{% render_table table %}
</code></pre>
<h3 id="heading-template-to-override-the-default-table">Template to Override the Default Table</h3>
<p>This template extends the default table from <code>django-tables2</code>. It seamlessly updates the <code>page_by</code> variable in <code>x-data</code> when a user triggers any pagination action. This, in turn, updates the value of the <code>page</code> input, which is linked via the <code>x-model</code>. And it starts a custom event called pagination-initiated, which tells htmx to send the complete form back to the server, including all sorting, pagination, and filtering information. It's like magic!</p>
<pre><code class="lang-html">{% extends "django_tables2/bootstrap4.html" %}  

{% load django_tables2 %}  
{% load i18n %}  

{% block pagination.previous %}  
<span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"previous page-item"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"button"</span>&gt;</span>  
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"page_by = {{table.page.previous_page_number}}"</span>  
         <span class="hljs-attr">class</span>=<span class="hljs-string">"page-link"</span>&gt;</span>  
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-symbol">&amp;laquo;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>  
        {% trans 'previous' %}  
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>  
<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>  
{% endblock pagination.previous %}  

{% block pagination.range %}  
{% for p in table.page|table_page_range:table.paginator %}  
<span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-item{% if table.page.number == p %} active{% endif %}"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"button"</span>&gt;</span>  
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-link"</span> {% <span class="hljs-attr">if</span> <span class="hljs-attr">p</span> != <span class="hljs-string">'...'</span> %}@<span class="hljs-attr">click</span>=<span class="hljs-string">"page_by={{p}}"</span>{% <span class="hljs-attr">endif</span> %}&gt;</span>  
        {{ p }}  
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>  
<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>  
{% endfor %}  
{% endblock pagination.range %}  

{% block pagination.next %}  
<span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"next page-item user-select"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"button"</span>&gt;</span>  
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"page_by = {{table.page.next_page_number}}"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-link"</span>&gt;</span>  
        {% trans 'next' %}  
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-symbol">&amp;raquo;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</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">li</span>&gt;</span>  
{% endblock pagination.next %}
</code></pre>
<h2 id="heading-style">Style</h2>
<h3 id="heading-column-width">Column Width</h3>
<pre><code class="lang-css"><span class="hljs-comment">/* Column Width */</span>
<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">td</span><span class="hljs-selector-pseudo">:nth-child(1)</span>,  
<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">th</span><span class="hljs-selector-pseudo">:nth-child(1)</span> {  
    <span class="hljs-attribute">width</span>: <span class="hljs-number">1%</span>;  
}  

<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">td</span><span class="hljs-selector-pseudo">:nth-child(2)</span>,  
<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">th</span><span class="hljs-selector-pseudo">:nth-child(2)</span> {  
    <span class="hljs-attribute">width</span>: <span class="hljs-number">8%</span>;  
}  

<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">td</span><span class="hljs-selector-pseudo">:nth-child(3)</span>,  
<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">th</span><span class="hljs-selector-pseudo">:nth-child(3)</span> {  
    <span class="hljs-attribute">width</span>: <span class="hljs-number">34%</span>;  
}  

<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">td</span><span class="hljs-selector-pseudo">:nth-child(4)</span>,  
<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">th</span><span class="hljs-selector-pseudo">:nth-child(4)</span> {  
    <span class="hljs-attribute">width</span>: <span class="hljs-number">32%</span>;  
}  

<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">td</span><span class="hljs-selector-pseudo">:nth-child(5)</span>,  
<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">td</span><span class="hljs-selector-pseudo">:nth-child(6)</span>,
<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">td</span><span class="hljs-selector-pseudo">:nth-child(7)</span>,  
<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">th</span><span class="hljs-selector-pseudo">:nth-child(5)</span>,  
<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">th</span><span class="hljs-selector-pseudo">:nth-child(6)</span>,  
<span class="hljs-selector-class">.checkcolumn-table</span> <span class="hljs-selector-tag">th</span><span class="hljs-selector-pseudo">:nth-child(7)</span> {  
    <span class="hljs-attribute">width</span>: <span class="hljs-number">8%</span>;  
}
</code></pre>
<h3 id="heading-pagination">Pagination</h3>
<pre><code class="lang-css"><span class="hljs-comment">/* Pagination */</span>
<span class="hljs-selector-tag">ul</span><span class="hljs-selector-class">.pagination</span> {  
  <span class="hljs-attribute">justify-content</span>: end <span class="hljs-meta">!important</span>;  
}
</code></pre>
<h3 id="heading-sorting">Sorting</h3>
<pre><code class="lang-css"><span class="hljs-comment">/* Sorting */</span>
<span class="hljs-selector-tag">th</span><span class="hljs-selector-class">.asc</span><span class="hljs-selector-pseudo">:after</span> {  
    <span class="hljs-attribute">content</span>: <span class="hljs-string">'\0000a0\0025b2'</span>;  
    <span class="hljs-attribute">float</span>: right;  
    <span class="hljs-attribute">width</span>: <span class="hljs-number">10%</span>;  
}  

<span class="hljs-selector-tag">th</span><span class="hljs-selector-class">.desc</span><span class="hljs-selector-pseudo">:after</span> {  
    <span class="hljs-attribute">content</span>: <span class="hljs-string">'\0000a0\0025bc'</span>;  
    <span class="hljs-attribute">float</span>: right;  
    <span class="hljs-attribute">width</span>: <span class="hljs-number">10%</span>;  
}
</code></pre>
<h3 id="heading-highlight-rows-on-update">Highlight Rows on Update</h3>
<pre><code class="lang-css"><span class="hljs-comment">/* Rows highlight */</span>
<span class="hljs-selector-class">.highlight-me</span> {  
  <span class="hljs-attribute">background-color</span>: white;  
  <span class="hljs-attribute">animation-name</span>: blink;  
  <span class="hljs-attribute">animation-duration</span>: <span class="hljs-number">2s</span>;  
  <span class="hljs-attribute">transition-timing-function</span>: ease-in;  
  <span class="hljs-attribute">transition</span>: <span class="hljs-number">0.2s</span>;  
} 

<span class="hljs-keyword">@keyframes</span> blink {  
  0% { <span class="hljs-attribute">background-color</span>: orange; <span class="hljs-attribute">color</span>: white;}  
  50% { <span class="hljs-attribute">background-color</span>: orange; <span class="hljs-attribute">color</span>: white; }  
  51% { <span class="hljs-attribute">background-color</span>: white; }  
  100% { <span class="hljs-attribute">background-color</span>: white; }  
}
</code></pre>
<h3 id="heading-progress-bar">Progress Bar</h3>
<pre><code class="lang-css"><span class="hljs-comment">/* Progress bar */</span>  
<span class="hljs-selector-class">.checkcolumn-table</span><span class="hljs-selector-class">.header</span> {  
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0</span>;  
}

<span class="hljs-selector-class">.progress</span> {  
    <span class="hljs-attribute">height</span>: <span class="hljs-number">4px</span>;  
    <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;  
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">2px</span>;  
    <span class="hljs-attribute">background-clip</span>: padding-box;  
    <span class="hljs-attribute">overflow</span>: hidden;  
    <span class="hljs-attribute">position</span>: relative;  
    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;  
}  

<span class="hljs-selector-class">.htmx-request</span> <span class="hljs-selector-class">.progress</span>,
<span class="hljs-selector-class">.htmx-request</span><span class="hljs-selector-class">.progress</span> {  
    <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;  
}  

<span class="hljs-selector-class">.progress</span> <span class="hljs-selector-class">.indeterminate</span> {  
    <span class="hljs-attribute">background-color</span>: blue;  
}  

<span class="hljs-selector-class">.progress</span> <span class="hljs-selector-class">.indeterminate</span><span class="hljs-selector-pseudo">:after</span>,  
<span class="hljs-selector-class">.progress</span> <span class="hljs-selector-class">.indeterminate</span><span class="hljs-selector-pseudo">:before</span> {  
    <span class="hljs-attribute">content</span>: <span class="hljs-string">''</span>;  
    <span class="hljs-attribute">position</span>: absolute;  
    <span class="hljs-attribute">background-color</span>: inherit;  
    <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;  
    <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;  
    <span class="hljs-attribute">bottom</span>: <span class="hljs-number">0</span>;  
    <span class="hljs-attribute">will-change</span>: left, right;  
}  

<span class="hljs-selector-class">.progress</span> <span class="hljs-selector-class">.indeterminate</span><span class="hljs-selector-pseudo">:before</span> {  
    <span class="hljs-attribute">animation</span>: indeterminate <span class="hljs-number">2.1s</span> <span class="hljs-built_in">cubic-bezier</span>(<span class="hljs-number">0.65</span>, <span class="hljs-number">0.815</span>, <span class="hljs-number">0.735</span>, <span class="hljs-number">0.395</span>) infinite;  
}  

<span class="hljs-selector-class">.progress</span> <span class="hljs-selector-class">.indeterminate</span><span class="hljs-selector-pseudo">:after</span> {  
    <span class="hljs-attribute">animation</span>: indeterminate-short <span class="hljs-number">2.1s</span> <span class="hljs-built_in">cubic-bezier</span>(<span class="hljs-number">0.165</span>, <span class="hljs-number">0.84</span>, <span class="hljs-number">0.44</span>, <span class="hljs-number">1</span>) infinite;  
}

<span class="hljs-keyword">@keyframes</span> indeterminate {  
    0% { <span class="hljs-attribute">left</span>: -<span class="hljs-number">35%</span>; <span class="hljs-attribute">right</span>: <span class="hljs-number">100%</span>; }  
    60% { <span class="hljs-attribute">left</span>: <span class="hljs-number">100%</span>; <span class="hljs-attribute">right</span>: -<span class="hljs-number">90%</span>; }  
    100% {  <span class="hljs-attribute">left</span>: <span class="hljs-number">100%</span>;  <span class="hljs-attribute">right</span>: -<span class="hljs-number">90%</span>; }  
}  

<span class="hljs-keyword">@keyframes</span> indeterminate-short {  
    0% { <span class="hljs-attribute">left</span>: -<span class="hljs-number">200%</span>; <span class="hljs-attribute">right</span>: <span class="hljs-number">100%</span>; }  
    60% { <span class="hljs-attribute">left</span>: <span class="hljs-number">107%</span>; <span class="hljs-attribute">right</span>: -<span class="hljs-number">8%</span>; }  
    100% { <span class="hljs-attribute">left</span>: <span class="hljs-number">107%</span>; <span class="hljs-attribute">right</span>: -<span class="hljs-number">8%</span>; }  
}
</code></pre>
<h2 id="heading-summary">Summary</h2>
<p>In this article, we have covered the process of building a table that supports bulk actions by the user using the combination of <code>django-tables2</code>, <code>htmx</code>, and <code>AlpineJS</code>. We have gone through the implementation of pagination and sorting, the search query form, and the checkbox column. We also covered how to enhance the user experience by improving the table's design and layout, adding responsive design, and providing feedback to the user.</p>
<p>We have seen how <code>django-tables2</code> is a powerful library that allows developers to create and manage tables in Django projects with minimal effort. The <code>htmx</code> allows developers to perform rich interactions and dynamic updates on web pages without the need for extensive JavaScript code, and <code>Alpine.js</code> will enable developers to add interactive functionality to their web pages with minimal code. Together, these libraries provide a powerful solution for building tables that support bulk actions by the user.</p>
]]></content:encoded></item><item><title><![CDATA[Week #46 - 2022]]></title><description><![CDATA[I am thinking about using the burner list. The idea came from the book "Make Time" by Jake Knapp And John Zeratsky. A burner list is a list of things that you want to accomplish in a given period of time, typically within a week or two. The idea is t...]]></description><link>https://enzircle.com/week-46-2022</link><guid isPermaLink="true">https://enzircle.com/week-46-2022</guid><category><![CDATA[weekly]]></category><category><![CDATA[general]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Mon, 21 Nov 2022 09:30:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1669368532950/XJHKmoKGo.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I am thinking about using <strong>the burner list</strong>. The idea came from the book "Make Time" by Jake Knapp And John Zeratsky. A burner list is a list of things that you want to accomplish in a given period of time, typically within a week or two. The idea is to have a finite number of items on your list so that you can focus on completing them and not be overwhelmed. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1669368566624/RIlSIFBun.png" alt="burner-list.png" /></p>
<p><strong>I tried out <code>miniconda</code> as an alternative to <code>anaconda</code>.</strong> To do that, I need to uninstall my <code>anaconda</code>. </p>
<pre><code class="lang-bash">$ conda install anaconda-clean
$ anaconda-clean --yes
$ rm -rf ~/opt/anaconda3
</code></pre>
<p>Then I just installed <code>miniconda</code> for my Apple Silicon. I use the graphical installer.</p>
]]></content:encoded></item><item><title><![CDATA[Animate Point Along an Arc in MapBox]]></title><description><![CDATA[Have you ever wanted to animate a point along an arc in MapBox? In this blog post, I'll show you how to do just that! I'll also provide code examples to get you started. So, whether you're a developer or a student, read on!
This project is inspired b...]]></description><link>https://enzircle.com/animate-point-along-an-arc-in-mapbox</link><guid isPermaLink="true">https://enzircle.com/animate-point-along-an-arc-in-mapbox</guid><category><![CDATA[turf]]></category><category><![CDATA[mapbox]]></category><category><![CDATA[Geospatial]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Tue, 15 Nov 2022 03:27:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1668246624953/7ZzHOAygj.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever wanted to animate a point along an arc in MapBox? In this blog post, I'll show you how to do just that! I'll also provide code examples to get you started. So, whether you're a developer or a student, read on!</p>
<p>This project is inspired by a case study made by <a target="_blank" href="https://fantasy.co/">Fantasy.co</a>. You can still find the project description at the <a target="_blank" href="https://fantasy.co/legacy/fi-airlines/">Fantasy.co website</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668482497024/uwkjZxOul.png" alt="flight-interesting-ux.png" /></p>
<h2 id="heading-the-packages-used-for-this-project">The Packages Used For This Project</h2>
<ul>
<li><a target="_blank" href="https://www.mapbox.com/mapbox-gljs">Mapbox GL JS</a>. Mapbox GL JS is a JavaScript library for vector maps on the Web. </li>
<li><a target="_blank" href="https://turfjs.org/">turf</a>. Turf is an advanced geospatial javascript tool that is simple to use and runs very fast.</li>
</ul>
<h2 id="heading-file-setup">File setup</h2>
<p>For this simple project, we only need to have three files. An <code>index.html</code>, a <code>style.css</code> and a <code>map.js</code>. The content of  <code>index.html</code> and <code>style.css</code> are detailed here. I will talk about the content of <code>map.js</code> in more detail later. </p>
<h3 id="heading-indexhtml">index.html</h3>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Animated <span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://api.mapbox.com/mapbox-gl-js/v2.10.0/mapbox-gl.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://api.mapbox.com/mapbox-gl-js/v2.10.0/mapbox-gl.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://unpkg.com/@turf/turf@6/turf.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"style.css"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">'map'</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">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"map.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h3 id="heading-stylecss">style.css</h3>
<pre><code class="lang-css"><span class="hljs-selector-id">#map</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">600px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">600px</span>;
}
</code></pre>
<p>Now that we have the <code>index.html</code> and <code>style.css</code> let's move on to the juicy stuff. </p>
<h2 id="heading-making-semi-circle-path">Making Semi-Circle Path</h2>
<p>First, let's make the semi-circle path. There are many ways to make a path between two points. But, in this article, I created a number of new coordinates between the start and end points. Creating more points is also helpful since we will animate a marker using these new coordinates later. More points will generate a smoother animation.</p>
<p>The idea of the process is as follows:</p>
<ol>
<li>Get the midpoint between origin and destination. Also, get the bearing and radius of the semi-circle.</li>
<li>Translate that midpoint based on the bearing (modified by some angle) and the radius.</li>
<li>Repeat this process multiple times. In this article, I make 1000 additional coordinates between the origin and destination. More coordinates mean smoother lines and smoother animation.</li>
</ol>
<p>By the end of this process, we will get a list of new coordinates.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668482601400/ml7h17q0F.png" alt="How to make the arc" /></p>
<p>And the function to generate this is as follows.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Generate arc from start point to the end point.</span>
<span class="hljs-comment">// The segments parameter tell us how many</span>
<span class="hljs-comment">// new point should we generate.</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateArc</span>(<span class="hljs-params">start, end, segments</span>) </span>{
    <span class="hljs-comment">// Get the mid point between start and end</span>
    <span class="hljs-keyword">let</span> midPoint = turf.midpoint(start, end);

    <span class="hljs-comment">// Get the bearing </span>
    <span class="hljs-keyword">let</span> bearing = turf.bearing(end, start);

    <span class="hljs-comment">// Get half of the distance, because we </span>
    <span class="hljs-comment">// start from the midpoint.</span>
    <span class="hljs-keyword">let</span> distance = turf.distance(start, end) / <span class="hljs-number">2</span>;

    <span class="hljs-comment">// Add the start coordinate</span>
    <span class="hljs-keyword">let</span> arc = [start.geometry.coordinates];

    <span class="hljs-comment">// We devide 180 angle by segments, and for each angle</span>
    <span class="hljs-comment">// we transform the mid point by distance and an angle.</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> angle = <span class="hljs-number">0</span>; angle &lt; <span class="hljs-number">180</span>; angle += (<span class="hljs-number">180</span>/ (segments))) {
        <span class="hljs-keyword">let</span> rotatedPoint = turf.transformTranslate(midPoint,
                                                   distance,
                                                   bearing - angle);
        arc.push(rotatedPoint.geometry.coordinates);
    }

    <span class="hljs-comment">// Add the last coordinate</span>
    arc.push(end.geometry.coordinates);

    <span class="hljs-keyword">return</span> arc
}
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668482651372/3Sin6LSVQ.png" alt="Semi Circle" /></p>
<h2 id="heading-animate-a-marker">Animate a Marker</h2>
<p>Once we created a bunch of new coordinates. We can start moving a point or a marker between these coordinates.</p>
<p>So for every coordinates pair we created from the previous step, we will:</p>
<ol>
<li>rotate our marker using <code>turf.bearing</code> . </li>
<li>move the marker using <code>mapbox.getSource().setData()</code></li>
</ol>
<p>In the reference, the animation slows down halfway. So I tried using some ease-out functions from https://easings.net/, but the result was not quite what I wanted. So I decided to simply slow down the animation when the marker reached halfway.</p>
<pre><code class="lang-Javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animate</span>(<span class="hljs-params">map, counter, route, point, source_id, segments</span>) </span>{

    <span class="hljs-keyword">const</span> start = 
        route.features[<span class="hljs-number">0</span>].geometry.coordinates[
            counter &gt;= segments ? counter - <span class="hljs-number">1</span> : counter
        ];
    <span class="hljs-keyword">const</span> end =
        route.features[<span class="hljs-number">0</span>].geometry.coordinates[
            counter &gt;= segments ? counter : counter + <span class="hljs-number">1</span>
        ];

    <span class="hljs-keyword">if</span> (!start || !end) <span class="hljs-keyword">return</span>;

    <span class="hljs-comment">// Update point geometry to a new position </span>
    <span class="hljs-comment">// based on counter denoting the index to access the arc.</span>
    point.features[<span class="hljs-number">0</span>].geometry.coordinates =
        route.features[<span class="hljs-number">0</span>].geometry.coordinates[counter];

    <span class="hljs-comment">// Calculate the bearing to ensure the icon is </span>
    <span class="hljs-comment">// rotated to match the route arc.</span>
    point.features[<span class="hljs-number">0</span>].properties.bearing = turf.bearing(
        turf.point(start),
        turf.point(end)
    );

    <span class="hljs-comment">// Update the source with this new data</span>
    map.getSource(<span class="hljs-string">'point-'</span> + source_id).setData(point);

    <span class="hljs-comment">// Request the next frame of animation </span>
    <span class="hljs-keyword">if</span> (counter &lt; (segments/<span class="hljs-number">2</span>)) {
        <span class="hljs-comment">// Before halfway, just run the animation </span>
        <span class="hljs-comment">// as normal.</span>
        requestAnimationFrame(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
            animate(map, counter, route, point, source_id, segments);
        });
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// Slow down the animation after half way.</span>
        <span class="hljs-keyword">let</span> fps = <span class="hljs-number">20</span>
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
            requestAnimationFrame(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
                animate(map, counter, route, point, source_id, segments);
            });
          }, <span class="hljs-number">1000</span> / fps);
    }
    counter += <span class="hljs-number">1</span>;
}
</code></pre>
<h2 id="heading-zoom-level">Zoom Level</h2>
<p>Another thing I like to do is to set a proper zoom level. For this, I utilize the <code>fitBounds</code> functionality. To correctly calculate the bounding box, I should probably add the rotated midpoint. But here, I only use the original city coordinates to get the bounding box. And added some padding when I invoked the <code>fitBounds()</code>.</p>
<pre><code class="lang-js"><span class="hljs-keyword">let</span> bbox = turf.bbox(turf.points(cityCoordinates));
map.fitBounds(bbox, {
    <span class="hljs-attr">linear</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">easing</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">x</span>)</span>{
        <span class="hljs-keyword">return</span> x * x * x;
    },
    <span class="hljs-attr">padding</span>: {<span class="hljs-attr">top</span>: <span class="hljs-number">100</span>, <span class="hljs-attr">bottom</span>:<span class="hljs-number">100</span>, <span class="hljs-attr">left</span>: <span class="hljs-number">100</span>, <span class="hljs-attr">right</span>: <span class="hljs-number">300</span>}
});
</code></pre>
<h2 id="heading-all-together-now">All together now</h2>
<p>With all of the above, we can write our <code>map.js</code> as follow.</p>
<h3 id="heading-mapjs">map.js</h3>
<pre><code class="lang-js"><span class="hljs-comment">// Set origin, destination, and route</span>
<span class="hljs-comment">//</span>

<span class="hljs-comment">// Cities</span>
<span class="hljs-keyword">const</span> cities = {
    <span class="hljs-string">"JKT"</span>: [<span class="hljs-number">106.8227</span>, <span class="hljs-number">-6.208763</span>],
    <span class="hljs-string">"SG"</span>: [<span class="hljs-number">103.8198</span>, <span class="hljs-number">1.3521</span>],
    <span class="hljs-string">"KL"</span>: [<span class="hljs-number">101.693207</span>, <span class="hljs-number">3.140853</span>]
}

<span class="hljs-keyword">var</span> cityCoordinates = <span class="hljs-built_in">Object</span>.keys(cities).map(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">key</span>)</span>{
    <span class="hljs-keyword">return</span> cities[key];
});

<span class="hljs-comment">// Route</span>
<span class="hljs-keyword">const</span> routes = [
    [cities[<span class="hljs-string">"JKT"</span>], cities[<span class="hljs-string">"SG"</span>]],
    [cities[<span class="hljs-string">"SG"</span>], cities[<span class="hljs-string">"KL"</span>]],
];

<span class="hljs-comment">// Map setup</span>
<span class="hljs-comment">//</span>
mapboxgl.accessToken = <span class="hljs-string">'&lt;USE YOUR TOKEN&gt;'</span>;

<span class="hljs-comment">// Get the center coordinate based on the city coordinates</span>
<span class="hljs-keyword">let</span> center = turf.center(
                turf.points(cityCoordinates)
             ).geometry.coordinates;

<span class="hljs-comment">// Setup map</span>
<span class="hljs-keyword">var</span> map = <span class="hljs-keyword">new</span> mapboxgl.Map({
    <span class="hljs-attr">container</span>: <span class="hljs-string">'map'</span>,
    <span class="hljs-attr">style</span>: <span class="hljs-string">'mapbox://styles/mapbox/dark-v10'</span>,
    <span class="hljs-attr">center</span>: center,
    <span class="hljs-attr">zoom</span>: <span class="hljs-number">5</span>
});

<span class="hljs-comment">// Add navigation control</span>
map.addControl(<span class="hljs-keyword">new</span> mapboxgl.NavigationControl());

<span class="hljs-comment">// Load the map</span>
<span class="hljs-comment">//</span>
map.on(<span class="hljs-string">'load'</span>, <span class="hljs-function">() =&gt;</span> {

    <span class="hljs-comment">// Set zoom level.</span>
    <span class="hljs-keyword">let</span> bbox = turf.bbox(turf.points(cityCoordinates));
    map.fitBounds(bbox, {
        <span class="hljs-attr">linear</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">easing</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">x</span>)</span>{
            <span class="hljs-keyword">return</span> x * x * x;
        },
        <span class="hljs-attr">padding</span>: {<span class="hljs-attr">top</span>: <span class="hljs-number">100</span>, <span class="hljs-attr">bottom</span>:<span class="hljs-number">100</span>, <span class="hljs-attr">left</span>: <span class="hljs-number">100</span>, <span class="hljs-attr">right</span>: <span class="hljs-number">300</span>}
    });

    <span class="hljs-comment">// Number of segment to use in the arc.</span>
    <span class="hljs-comment">// More segment means a smoothe arc.</span>
    <span class="hljs-keyword">const</span> segments = <span class="hljs-number">1000</span>;

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; routes.length; i++) {
        <span class="hljs-comment">// Route</span>
        <span class="hljs-comment">//</span>
        <span class="hljs-keyword">let</span> route = routes[i];

        <span class="hljs-comment">// Create the line segments based on the route</span>
        <span class="hljs-keyword">let</span> arc = generateArc(turf.point(route[<span class="hljs-number">0</span>]), 
                              turf.point(route[<span class="hljs-number">1</span>]),
                              segments);

        <span class="hljs-comment">// Turn the object into feature collection</span>
        <span class="hljs-keyword">let</span> mapRoute = turf.featureCollection([turf.lineString(arc)]);

        <span class="hljs-comment">// Put the information on to the map</span>
        map.addSource(<span class="hljs-string">"route-"</span> + i, {
            <span class="hljs-string">"type"</span>: <span class="hljs-string">"geojson"</span>,
            <span class="hljs-string">"data"</span>: mapRoute
        });

        map.addLayer({
            <span class="hljs-string">"id"</span>: <span class="hljs-string">"route-layer-"</span> + i,
            <span class="hljs-string">"type"</span>: <span class="hljs-string">"line"</span>,
            <span class="hljs-string">"source"</span>: <span class="hljs-string">"route-"</span> + i,
            <span class="hljs-string">"layout"</span>: {
                <span class="hljs-string">"line-join"</span>: <span class="hljs-string">"round"</span>,
                <span class="hljs-string">"line-cap"</span>: <span class="hljs-string">"round"</span>
            },
            <span class="hljs-string">"paint"</span>: {
            <span class="hljs-string">"line-color"</span>: <span class="hljs-string">"#fff"</span>,
            <span class="hljs-string">"line-width"</span>: <span class="hljs-number">1</span>
            }
        });

        <span class="hljs-comment">// Point</span>
        <span class="hljs-comment">//</span>

        <span class="hljs-keyword">let</span> mapPoint = turf.featureCollection([turf.point(routes[i][<span class="hljs-number">0</span>])]);
        map.addSource(<span class="hljs-string">'point-'</span> + i, {
            <span class="hljs-string">'type'</span>: <span class="hljs-string">'geojson'</span>,
            <span class="hljs-string">'data'</span>: mapPoint
        });

        map.addLayer({
            <span class="hljs-string">'id'</span>: <span class="hljs-string">'point-layer-'</span> + i,
            <span class="hljs-string">'source'</span>: <span class="hljs-string">'point-'</span> + i,
            <span class="hljs-string">'type'</span>: <span class="hljs-string">'symbol'</span>,
            <span class="hljs-string">'layout'</span>: {
                <span class="hljs-string">'icon-image'</span>: <span class="hljs-string">'airport-15'</span>,
                <span class="hljs-string">'icon-size'</span>: <span class="hljs-number">1</span>,
                <span class="hljs-string">'icon-rotate'</span>: [<span class="hljs-string">'get'</span>, <span class="hljs-string">'bearing'</span>],
                <span class="hljs-string">'icon-rotation-alignment'</span>: <span class="hljs-string">'map'</span>,
                <span class="hljs-string">'icon-allow-overlap'</span>: <span class="hljs-literal">true</span>,
                <span class="hljs-string">'icon-ignore-placement'</span>: <span class="hljs-literal">true</span>
            }
        });

        <span class="hljs-comment">// Animate</span>
        animate(map, <span class="hljs-number">0</span>, mapRoute, mapPoint, i, segments);
    }
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateArc</span>(<span class="hljs-params">start, end, segments</span>) </span>{
    <span class="hljs-comment">// See the above</span>
    <span class="hljs-comment">// ...</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">animate</span>(<span class="hljs-params">map, counter, route, point, source_id, segments</span>) </span>{
    <span class="hljs-comment">// See the above</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>And when you open the <code>index.html</code> in your browser, you will get the following.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1668482774511/BbM5WBIYQ.gif" alt="demo.gif" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, I show how to create a semi-circle arc between two points using MapBox. I also demonstrated how you could move a point or a marker along these routes. I hope you can learn something from this. Thank you for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Socket Programming in Python]]></title><description><![CDATA[Python is a versatile programming language that can be used for various projects. In this blog post, we will discuss socket programming in python. We will cover the basics of sockets, and how to use them to create network connections between devices....]]></description><link>https://enzircle.com/socket-programming-in-python</link><guid isPermaLink="true">https://enzircle.com/socket-programming-in-python</guid><category><![CDATA[Python]]></category><category><![CDATA[socket]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Sun, 06 Nov 2022 10:27:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/ImcUkZ72oUs/upload/v1667730349450/t2qoJzU-E.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Python is a versatile programming language that can be used for various projects. In this blog post, we will discuss socket programming in python. We will cover the basics of sockets, and how to use them to create network connections between devices. We will also provide some example code for those who want to try out socket programming in python. So, if you are interested in learning more about sockets, or want to start using them in your Python projects, keep reading!</p>
<p>The Python <code>socket</code> module provides a straightforward interface to the Berkeley sockets API. But it uses Python’s object-oriented style. The <code>socket()</code> function returns a <em>socket object</em> whose methods implement the various socket system calls.</p>
<p>The primary socket API functions are:</p>
<ul>
<li><p><code>socket()</code></p>
</li>
<li><p><code>.bind()</code></p>
</li>
<li><p><code>.listen()</code></p>
</li>
<li><p><code>.accept()</code></p>
</li>
<li><p><code>.connect()</code></p>
</li>
<li><p><code>.send()</code></p>
</li>
<li><p><code>.recv()</code></p>
</li>
<li><p><code>.close()</code></p>
</li>
</ul>
<h2 id="heading-server-socket-vs-client-socket">Server socket vs client socket</h2>
<p>Sockets can mean subtly different things depending on context. There is a difference between a client socket and a server socket.</p>
<p>A client socket is an endpoint of a conversation. It usually is only used for one exchange or a small set of sequential exchanges, and that is it.</p>
<p>A server socket on the other hand is more like a switchboard operator. It doesn't send or receive any data. It only produces client sockets. Each client socket is created in response to some other client socket doing <code>connect()</code> to the server. Once a client socket is created, the two clients are free to communicate with each other.</p>
<p>An example of this distinction can be found on your browser. The web browser you operate uses client sockets exclusively. The web server that handles requests from the client uses both server sockets and client sockets.</p>
<h2 id="heading-echo-client-and-server">Echo client and server</h2>
<p>Below is the minimum example code. A server that echoes all data it receives back (servicing only one client) and a client that sends data.</p>
<h3 id="heading-echo-server">Echo Server</h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> socket

QUEUE_SIZE = <span class="hljs-number">5</span>
BUFFER_SIZE = <span class="hljs-number">4096</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">serve</span>(<span class="hljs-params">host, port</span>):</span>
    <span class="hljs-comment"># Create a server socket</span>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    <span class="hljs-comment"># Bind socket to host and port</span>
    sock.bind((host, port))

    <span class="hljs-comment"># Officially become a server socket</span>
    sock.listen(QUEUE_SIZE)

    <span class="hljs-comment"># Accept connection from outside</span>
    client_socket, addr = sock.accept()

    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
        data = client_socket.recv(BUFFER_SIZE)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> data:
            <span class="hljs-keyword">break</span>
        client_socket.sendall(data)

    <span class="hljs-comment"># Close the client socket</span>
    client_socket.close()    

    <span class="hljs-comment"># Close the server socket</span>
    sock.close()
</code></pre>
<p>A server must perform the sequence <code>socket()</code>, <code>bind()</code>, <code>listen()</code>, <code>accept()</code>. The server does not <code>send()</code>, <code>sendall()</code> or <code>recv()</code> on the socket it is listening on but on the new socket returned by <code>accept()</code>.</p>
<p>The <code>socket()</code> function takes two parameters: address family and socket types. The address family is usually IPv4 (<code>socket.AF_INET</code>) or IPv6 (<code>socket.AF_INET6</code>). Socket types are usually streaming sockets (<code>socket.SOCK_STREAM</code>) or datagram sockets (<code>socket.SOCK_DGRAM</code>).</p>
<p>The value passed to <code>bind()</code> depends on the socket's address family. For <code>socket.AF_INET</code> (IPv4), it expects a <code>(host, port)</code> tuple. <code>host</code> can be a hostname, IP address, or an empty string. The IP address <code>127.0.0.1</code> is the standard IPv4 address for the loopback interface, so only processes on the host will be able to connect to the server. If you pass an empty string, the server will accept connections on all available IPv4 interfaces.</p>
<p>Socket <code>listen()</code> can take a parameter called <em>backlog</em>. When a backlog is specified, it tells the system to allow a number of unaccepted connections before refusing new connections. A good number for a backlog is 5 (the typical max). If you write your code correctly, five should be plenty.</p>
<h3 id="heading-echo-client">Echo Client</h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> socket

BUFFER_SIZE = <span class="hljs-number">4096</span>


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">connect</span>(<span class="hljs-params">host, port, message</span>):</span>
    <span class="hljs-comment"># Create a client socket</span>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    <span class="hljs-comment"># Connect to a server</span>
    sock.connect((host, port))

    <span class="hljs-comment"># Send message to the server</span>
    sock.sendall(bytes(message, <span class="hljs-string">"utf-8"</span>))

    <span class="hljs-comment"># Capture any information back </span>
    <span class="hljs-comment"># from the server</span>
    data = sock.recv(BUFFER_SIZE)

    <span class="hljs-comment"># Close the connection</span>
    sock.close()
</code></pre>
<h3 id="heading-notes-on-recv-and-send">Notes on <code>recv()</code> and <code>send()</code></h3>
<p><code>send</code> and <code>recv</code> operate on network buffers. They do NOT necessarily handle all the bytes you hand them (or expect from them). They return when the network buffers have been filled (<code>send</code>) or emptied (<code>recv</code>). They then tell you how many bytes they handled. It is <strong>your</strong> responsibility to call them again until your message has been completely dealt with.</p>
<p>The <code>recv()</code> method receive data from the socket. It returns the number of bytes received, which may be less than the size of the data. It requires the <code>bufsize</code> parameters to be specified. It is the maximum amount of data to be received at once. As for the value of the <code>bufsize</code>, the documentation recommended the following:</p>
<blockquote>
<p>For best match with hardware and network realities, the value of <em>bufsize</em> should be a relatively small power of 2, for example, 4096.</p>
</blockquote>
<p>Typically we set this to 4096 or 8192.</p>
<p>The other side has closed the connection when a <code>recv</code> returns 0 bytes. You will NOT receive any more data from this connection.</p>
<p>The <code>send()</code> method also sends the number of bytes sent, which may be less than the size of the data passed in. To ensure you send all the data, use the <code>sendall()</code> method instead.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>As we have seen, sockets are the fundamental building blocks of network communications. By learning how to use them in Python, we can easily add this powerful tool to our arsenal. With a little practice, you'll be able to build robust client-server applications that can communicate across networks. Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Introduction to Socket Programming]]></title><description><![CDATA[You have probably heard about socket programming. Socket programming is the de facto way to write programs that communicate over a network. You can send and receive data from other applications or systems using sockets. In this blog post, we'll take ...]]></description><link>https://enzircle.com/introduction-to-socket-programming</link><guid isPermaLink="true">https://enzircle.com/introduction-to-socket-programming</guid><category><![CDATA[General Programming]]></category><category><![CDATA[socket]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Wed, 02 Nov 2022 04:23:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/qTEj-KMMq_Q/upload/v1667362898786/2BHRvYgP3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You have probably heard about socket programming. Socket programming is the de facto way to write programs that communicate over a network. You can send and receive data from other applications or systems using sockets. In this blog post, we'll take a look at socket programming. We'll discuss what sockets are and how they work. By the end of this post, you'll have a better understanding of sockets. So let's get started!</p>
<p>Sockets are simply an endpoint for communication, similar to how we use ports on our phones for sending and receiving data across the network. They are used nearly everywhere, but as a technology, they often need to be understood.</p>
<h2 id="heading-brief-history">Brief History</h2>
<p>Before we start, let's take a brief look at the short history of the socket.</p>
<ul>
<li><strong>1971</strong>. The term <em>socket</em> appeared in the publication of RPC 147 when it was used in the ARPANET.</li>
<li><strong>1983</strong>. Most modern implementations of sockets are based on Berkeley sockets.</li>
<li><strong>1989</strong>. UC Berkeley released versions of the network library free from the licensing constraints of AT&amp;T copyright-protected Unix.</li>
<li><strong>1991</strong>. Windows define its network API called <em>WinSock</em>.</li>
</ul>
<h2 id="heading-socket-types">Socket Types</h2>
<p>There are several types of sockets, but only two appear to be useful. They are <strong>stream sockets</strong> and <strong>datagram sockets</strong>. </p>
<p>Stream sockets are reliable two-way connected communication streams. For instance, your web browsers use HTTP, which uses stream sockets to get pages. They are reliable because they use the Transmission Control Protocol (TCP) protocol. TCP makes sure data arrives sequentially and error-free. </p>
<p>Datagram sockets use the User Datagram Protocol (UDP). If you use datagram sockets to send data, it may or may not arrive. It may come out of order. If it arrives, though, the data within the packet will be error-free. Datagram sockets are typically used in a system where you can ignore the dropped packets—applications like games, audio, or video, for instance. </p>
<h2 id="heading-socket-abstraction">Socket Abstraction</h2>
<p>The socket was invented in Berkeley as part of the BSD flavor of Unix. And it has become the standard way to communicate over the internet. Today all machines, be it Windows, Linux, or Mac, support the so-called Berkeley Socket API. </p>
<p>The primary Berkeley socket API are:</p>
<ul>
<li><code>socket()</code> creates a new socket. An endpoint for communication and returns a file descriptor.</li>
<li><code>bind()</code> associates a socket with an address, i.e., an IP address and a port number.  Bind is typically used on the server side.</li>
<li><code>listen()</code> is used on the server side and causes a bound TCP socket to be ready for incoming connections.  </li>
<li><code>accept()</code> is used on the server side. It creates a new socket for each connection and removes the connection from the listening queue. </li>
<li><code>connect()</code> establishes a direct communication link to a specific remote host. It is used on the client side and assigns a free local port number to a socket. </li>
<li><code>send()</code>, <code>recv()</code>, <code>sendto()</code>, and <code>recvfrom()</code> are used for sending and receiving data. </li>
<li><code>close()</code> causes the system to release resources allocated to a socket. In the case of TCP, the connection is terminated.</li>
</ul>
<h2 id="heading-tcp-socket-flow">TCP Socket Flow</h2>
<p>You will need more than simply knowing the socket abstraction above to help you with how to actually use it in coding. You need to know in what order to call the socket API. Let's take a look at the sequence of API calls and data flow for TCP:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1667618670722/LyhvTaL11.png" alt="Image by Author" /></p>
<p>For the server to be able to accept a connection, it needs to call <code>socket()</code>, <code>bind()</code>, <code>listen()</code> and <code>accept()</code> consecutively. Once we've set up the server, the client calls <code>connect()</code> to establish a connection to the server and initiate the three-way handshake. Socket uses <code>send</code> and <code>recv</code> to exchange data between the client and server. When done, the client and server close their respective sockets.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this post, we've taken a high-level look at what sockets are and how they work. We briefly covered the history of sockets and their place in computer networking. They start at Berkeley but become the standard of communication between systems over a network. In a future article, I will talk about socket programming in Python. Thanks for reading!</p>
]]></content:encoded></item><item><title><![CDATA[Week #40 - 2022]]></title><description><![CDATA[I started this blog to learn how to be a better communicator and write technical stuff. But I have been neglecting this blog for a while. So to push myself to write, I decided to start writing weekly posts. The idea is to take a brief note of what I ...]]></description><link>https://enzircle.com/week-40-2022</link><guid isPermaLink="true">https://enzircle.com/week-40-2022</guid><category><![CDATA[weekly]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Fri, 07 Oct 2022 13:23:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1665148791918/_7hli_sfY.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I started this blog to learn how to be a better communicator and write technical stuff. But I have been neglecting this blog for a while. So to push myself to write, I decided to start writing <strong>weekly posts</strong>. The idea is to take a brief note of what I find interesting during the week.</p>
<hr />
<p>I am looking into making a data visualization dashboard for an analytics app. The library I am currently looking into is <code>Bokeh</code>. For aesthetic reasons, I would like a <strong>smooth area chart</strong> instead of a janky-looking graph. In C3 (a D3-based chart library), this function is available through the <code>area-spline</code> graph type, but in <code>Bokeh</code> you have to do it manually. The key to having a smooth graph is through the interpolation operation. For this, I use the <code>pchip_interpolate</code> from the wonderful <code>scipy</code> library. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1665148862215/U3QlcEiM8.png" alt="Plot Comparison" /></p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_ts_area_plot</span>(<span class="hljs-params">x, y, plot</span>):</span>
    date_range = x.astype(<span class="hljs-string">'int64'</span>)
    date_space = np.linspace(date_range.min(),
                             date_range.max(),
                             len(x) * <span class="hljs-number">10</span>)
    value_smooth = pchip_interpolate(date_range, 
                                     y,
                                    date_space)

    fill_color = <span class="hljs-string">"#1f77b4"</span>
    area_opacity = <span class="hljs-number">0.2</span>

    <span class="hljs-comment"># Line chart</span>
    plot.line(x=pd.to_datetime(date_space),
              y=value_smooth,
              color=fill_color)

    <span class="hljs-comment"># Area chart</span>
    plot.varea(x=pd.to_datetime(date_space),
               y1=<span class="hljs-number">0</span>, y2=value_smooth,
               alpha=area_opacity,
               fill_color=fill_color)

    <span class="hljs-comment"># Dot </span>
    plot.circle(x=x, y=y, size=<span class="hljs-number">5</span>, color=fill_color)

    <span class="hljs-keyword">return</span> plot
</code></pre>
<hr />
<p>I stumbled upon a book titled <strong>Designerly Ways of Knowing</strong>. The book starts by saying that there are these “two cultures” in general education: science and humanities. But there is a third neglected culture which is design. While sciences deal with the natural world and humanities deal with human experience. Design deals with the artificial world. While we value objectivity, rationality, and neutrality in the sciences, we value subjectivity and imagination in the humanities. In design, however, we value practicality, ingenuity, and empathy. I have not finished reading the whole document, but it is pretty interesting, and I intend to finish this book.</p>
<hr />
<p>I started incorporating some Okinawan diet components into my daily meal to stay healthy. The Okinawan diet has produced one of the world’s longest-lived populations. The most common ingredients in their diet are <strong>purple sweet potato, bitter gourd, tofu, turmeric, and green tea </strong>. According to one source, about 60% of all calories came from the purple sweet potato. The purple potato is high in B vitamins and potassium. It also has a higher concentration of the antioxidant anthocyanin (from purple pigment) than blueberries. Bitter gourd has potent compounds that control blood sugar. Out of all the so-called “blue zones,” Okinawa is the closest to where I live, and I can easily find the ingredients.  </p>
]]></content:encoded></item><item><title><![CDATA[Four Thousand Weeks]]></title><description><![CDATA[I am always on the lookout for any tips to get more productive. I mean, everyone wants to get more productive,  right? To get more done in less time. There is a lot of productivity method floating around. I am sure you have heard of some of it. Getti...]]></description><link>https://enzircle.com/four-thousand-weeks</link><guid isPermaLink="true">https://enzircle.com/four-thousand-weeks</guid><category><![CDATA[Productivity]]></category><category><![CDATA[books]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Thu, 08 Sep 2022 10:46:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662634633434/F4aT8Wmtp.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I am always on the lookout for any tips to get more productive. I mean, everyone wants to get more productive,  right? To get more done in less time. There is a lot of productivity method floating around. I am sure you have heard of some of it. Getting Things Done, Eat the Frog, Time Blocking, OKRs, Sprints, The Action Method, and so on. All of these methods try to help you to have perfect control over your time.</p>
<p>However, in Four Thousand Weeks, Oliver Burkeman presents a different approach to productivity and time management. Burkeman argues that you can never have perfect control over your time. That it is delusional to think that you can get everything done. And the reason that managing time feels like such a struggle is that we are constantly trying to master it.</p>
<p>In this post, I summarize some of Burkeman's approaches to time management that help me look at time management differently. I also outline a set of practices I hope to use in my daily life when dealing with productivity and time management issues.</p>
<h2 id="heading-how-to-approach-time-management">How to Approach Time Management</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662633880751/rv-5I20Sz.png" alt="Image by Author" /></p>
<h3 id="heading-too-much-to-do">Too Much to Do</h3>
<p>Do you ever feel like you have too much to do and not enough time to do it? You are not alone. There are just so many things. Works to submit,  movies to watch, places to visit, etc. For most people, this can be frustrating and overwhelming. But you need to accept this: There will always be things to do. More than you can ever finish them.</p>
<h3 id="heading-tough-choices">Tough Choices</h3>
<p>Now that you know that there are just too many things to do. You need to understand that you can not avoid tough choices. You are limited. Your time is limited. You simply can not get everything done. You have to choose. Think about what matters most to you. Then you make your decisions.  </p>
<h3 id="heading-world-does-not-run-at-your-preferred-speed">World does not run at your preferred speed</h3>
<p>Things progress just the way they are. No matter how hard you might wish they were not. You need to work with this fact. Only then can you really make an impact on your situation. Problems often arise when you try to rush things or postpone things. You can not push the pace or slow it down. </p>
<h3 id="heading-you-can-not-predict-the-future">You can not predict the future</h3>
<p>You can't know in advance if an experience will turn out to be painless and well. Often people delay things because they worry about what will happen in the future. Or rush things in the hope of "getting there" quicker. But you can not tell what will happen in the future. So instead of imagining things too much, make a decision. Do the work or don't do the work. Just stop fidgeting over things you can't control.</p>
<h3 id="heading-it-does-not-matter">It does not matter</h3>
<p>Stop overvaluing your existence. This will only lead to an unrealistic definition of what it would mean to be successful. You don't have to have impressive accomplishments. From a cosmic viewpoint, when it is all over, it will not have counted for very much anyway. Don't undervalue things because you think they are not "significant" enough. Find meaning in your daily mundane life.</p>
<h2 id="heading-daily-practice">Daily Practice</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1662634812317/myM0T2r9F.png" alt="Image by Author" /></p>
<h3 id="heading-embrace-finitude">Embrace Finitude</h3>
<p>Embrace the truth. You have limited time and limited control over time. Only by accepting this reality can you get to accomplish more of what matters and feel more fulfilled about it.</p>
<h3 id="heading-be-present">Be Present</h3>
<p>Learn to stop focusing exclusively on where you are headed at the expense of focusing on where you are. Do not treat everything you do as the groundwork for something else. You are already living in the moment. You do not need to modify your relationship to the present moment in time. Just know that you are here now. </p>
<h3 id="heading-rest">Rest</h3>
<p>Modern people are so used to working so much that rest is difficult to do. Even in their spare time, people often do stuff related to their work. But spare time is not time to "invest" in your personal growth. Accept that your days are not progressing toward a perfect future of invulnerable happiness, where things are calmer, better, and more fulfilling. Where you can finally rest. That future may never actually exist. So learn to rest for the sake of rest now. </p>
<h3 id="heading-be-patience">Be Patience</h3>
<p>Things are the way there are, no matter how hard you wish they were not. Your only hope of having any real influence over the World is to work with that fact. You want to move things faster, but know that moving faster can be counterproductive. Working too hastily will lead to more errors, which you will have to go back to correct. </p>
<p>You will feel less anxious when you finally face the truth that you can not dictate how fast things go. So, be willing to stop, to stay where you are. Engage in the journey instead of badgering reality to hurry up. </p>
<h2 id="heading-end-note">End note</h2>
<p>We can not change how time works, but we can change our attitude towards it. Instead of trying to control every minute of a day, accept that you are limited. There are simply too many things you need to do. Learn to make tough decisions. Be patience. Be here now. And when it is time to rest, well, rest.</p>
]]></content:encoded></item><item><title><![CDATA[Essential Skills for Developers]]></title><description><![CDATA[As a developer, you need to have many skills to succeed. Not only do you need to be able to write code, but you also need to be able to operate and maintain the computer, write and organize your code, and solve problems as they arise. 
To become bett...]]></description><link>https://enzircle.com/essential-skills-for-developers</link><guid isPermaLink="true">https://enzircle.com/essential-skills-for-developers</guid><category><![CDATA[Programming Tips]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Thu, 01 Sep 2022 03:56:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/isw7s8PWi0U/upload/v1662004242223/84xaDbqxt.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As a developer, you need to have many skills to succeed. Not only do you need to be able to write code, but you also need to be able to operate and maintain the computer, write and organize your code, and solve problems as they arise. </p>
<p>To become better at your craft, developing a strong set of supporting skills is crucial. In this article, I point out several skills to help you become a better developer.</p>
<p>While this list is not exhaustive, it will give you a good starting point as you continue your journey to become a better developer. I encourage you to take the time to learn and hone these skills; they will serve you well in your career. </p>
<h2 id="heading-the-shell">The Shell</h2>
<p>A shell is a powerful tool that can make your life easier. The shell is an environment that lets you interact with your computer using text commands. You can do amazing things with a few simple commands with the shell. </p>
<p>Many people treat the shell as a black box. They use it but don't understand it. And that's a shame. If you master the shell, you can take control and make your computer work for you. </p>
<p>There are two broad categories of shell commands you need to master. The first one is using the shell for basic operation. Operations like listing, copying, and deleting files and directories. The other category is the knowledge of the shell environment. You need to know about disks and filesystems and how to handle processes and schedule jobs. You also need to understand how to manage users and some network operations.</p>
<h2 id="heading-ides-and-editors">IDEs and Editors</h2>
<p>As a developer, you spend hours in front of a text editor daily. So it is wise to invest in the skills that can help you work faster and more efficiently. </p>
<p>There are many IDEs and Editors out there. Each IDE and editor has its own strengths and weaknesses, so it's important to know which one will work best for the task at hand.</p>
<p>Personally,  I use two IDEs and two editors. My primary IDE is PyCharm. When I need to do a  quick code-build-debug cycle, I use Visual Studio Code (yes, I consider Visual Studio Code as IDE). For editing text, I use Sublime Text and Vim. So these are tools that I try to master.</p>
<p>In the end, finding an editor or IDE that makes you productive is essential. You need to find something you are comfortable using and stick with it. Once you have found your go-to editor or IDE, spend time learning all its features. With a bit of practice, you will be able to work faster and more efficiently than ever before. </p>
<h2 id="heading-version-control-system">Version Control System</h2>
<p>Developers who want to be proficient in their craft should learn how to use a version control system. Version control is the practice of tracking and managing changes to software code. Version control systems are software tools that help software teams manage changes to source code over time.</p>
<p>In the programming world, <code>git</code> has become the go-to source code management system. It's used by developers for everything from tracking individual file changes to managing entire project histories. While <code>git</code> can seem intimidating at first, it's not that difficult to understand. With a bit of practice, you can get relatively productive. With all the powerful features, it's easy to see why it's so popular. </p>
<h2 id="heading-debugging-and-profiling">Debugging and Profiling</h2>
<p>As a developer, you will bump into problems. This is why learning about debuggers and profilers is essential for developers. These tools help you troubleshoot issues with your code and identify performance bottlenecks.</p>
<p>There are several tools that help you to do this. One of the most well-known debugger is of course, <code>gdb</code>. It is widely available on many Unix-like systems. And it works for many programming languages, including <code>C</code>, <code>C++</code>, and <code>Go</code>. In Python world, you can use <code>pdb</code> for debugging and <code>cProfile</code> for profiling.  </p>
<p>Of course, you can also use IDE for debugging. PyCharm and Visual Studio Code, for instance,  are excellent for debugging. Both also allow you to optimize your code using profilers.</p>
<p>Debuggers and profilers are essential tools for any developer, regardless of experience level. By learning how to use them effectively, you can speed up your development process, identify and fix problems quickly, and improve the overall quality of your code. </p>
<h2 id="heading-regular-expression">Regular Expression</h2>
<p>If you're a developer, there's a good chance you've heard of regular expressions before. They can make your life a lot easier, and it's worth taking the time to learn how to use them.</p>
<p>A regular expression or regex is a sequence of characters that specifies a search pattern in a text. With regex, you can quickly and easily find and fix errors in large chunks of text, or extract specific information from a document. So if you're looking to become a more efficient developer, learning regex is essential.</p>
<h2 id="heading-shell-scripting">Shell Scripting</h2>
<p>Many developers and programmers avoid learning shell because they think it is too difficult or complex. Me included. However, shell scripting is a handy skill to have, and it can be learned relatively easily.</p>
<p>Shell scripting is a programming language that lets you control your computer's operations by running scripts. Scripts are short programs that can be written in a text editor and executed from the command line.  </p>
<p>Shell scripting is a powerful tool that can make your job as a developer easier. It can help you automate tedious tasks and make your work faster and easier. </p>
<h2 id="heading-endnote">Endnote</h2>
<p>These skills are not specific to coding but rather general computer operation and maintenance. As you can see, there is a lot that goes into being a developer than just writing code. If you want to be the best developer you can be, it's important that you take the time to learn these essential skills. Have you worked on developing any of these skills? Are there any other essential digital skills you think every developer should know? Let me know in the comments below!</p>
]]></content:encoded></item><item><title><![CDATA[The Importance of Encoding]]></title><description><![CDATA[You probably can't remember anything today if you read a book or studied the previous week. Unless, of course, you put in real effort. And I mean serious effort. You might remember some of the big ideas. But often forgot the specific. However, someti...]]></description><link>https://enzircle.com/the-importance-of-encoding</link><guid isPermaLink="true">https://enzircle.com/the-importance-of-encoding</guid><category><![CDATA[learning]]></category><category><![CDATA[memory]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Wed, 24 Aug 2022 04:34:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/t0SlmanfFcg/upload/v1661315626810/OPeeCwiPm.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You probably can't remember anything today if you read a book or studied the previous week. Unless, of course, you put in real effort. And I mean serious effort. You might remember some of the big ideas. But often forgot the specific. However, sometimes you actually need to remember the specific. Sure, you can reread the book. Repetition is one of the keys to effective learning. But what if you could learn something faster and with less boring repetition? That's where encoding comes in.</p>
<p>To learn anything, we need to encode information into our brains. But what does that actually mean though? And how can encoding help us succeed in learning? In this blog post, I'll explore the different ways encoding can help us learn and retain information. But first, let's talk about how our memory is stored.</p>
<h2 id="heading-how-memory-is-stored">How Memory is Stored</h2>
<p>The human mind is complex. The most influential model on how we actually store information treats our mind as an information-processing system similar to computers. We get input from the environment, process it, and output decisions.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661315533782/UkD6mNLKY.png" alt="Image by Author" /></p>
<p>The stages of information processing according to this model consists of three parts:</p>
<ol>
<li>Sensory memory. Any new input occurs in this memory. This is where we interact with our environment. It is a temporary register of all the information your senses are taking in.</li>
<li>Working memory. It is just whatever you are thinking about right at this moment. It is also called short-term memory. Working memory can hold about seven plus or minus two pieces of information at a time, so about five to nine.</li>
<li>Long-term memory is the final stage in the information processing model. We can store a lot of information in long-term memory. </li>
</ol>
<p>So what does encoding have to do with all of this?</p>
<h2 id="heading-what-is-encoding">What is Encoding</h2>
<p>Encoding is about moving information. Moving information from a temporary store into your working memory. And moving that into the permanent store in your long-term memory. It is the process in which data is processed and categorized for storage and retrieval. It is how information from sensory input is changed into a form so it can be stored in the brain. It transforms internal thoughts and external events into working memory and long-term memory. </p>
<p>Encoding is vital because if you encode information well, it is much easier to retrieve it later. Okay, great. But how do I use this knowledge to remember information better? </p>
<h2 id="heading-encoding-strategies">Encoding Strategies</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661315561743/dGrGCtkD6.png" alt="Image by Author" /></p>
<p>There is a lot of encoding strategy. However, all successful encoding requires you to tie any new information with old information you already have in your head. This article outlines the most essential strategies. Remember, to be successful, use what you already know to help you encode the new information.</p>
<h3 id="heading-chunking">Chunking</h3>
<p>Chunking is grouping information into groups or categories. For example, if I ask you to remember a shopping list that contains: mangos, oranges, grapes, bread, rice, beef, almonds, flour, eggs, and butter. It will be difficult for you to remember them all as is. But if you group them into categories, it will be much easier to remember. For instance, you can group mangos, oranges, and grapes into the fruit category. Bread and rice into carbs. Beef and almonds into proteins. Flour, eggs, and butter into the baking item's category. This is much easier to remember.</p>
<p>You can change the category in whatever way that makes sense to you. It is definitely okay to chunk those items differently. As long as they make sense to you.</p>
<h3 id="heading-mnemonic-devices">Mnemonic Devices</h3>
<p>Mnemonic devices are a set of tools to help you remember stuff. There are many mnemonic devices. The first thing you learn in school is probably the use of acronyms. Acronyms make remembering stuff super easy. For example,  I am not from the US, but I remember the US Great Lakes because the acronym is so easy. HOMES: Huron, Ontario, Michigan, Erie, Superior. </p>
<p>Other effective devices involve memory palace, peg system, visual alphabet, etc. You can read the Memory Craft by Lynne Kelly to learn more about memory techniques. Or you can visit the artofmemory.com <a target="_blank" href="https://artofmemory.com/wiki/Main_Page/">wiki</a> website. They have an extensive list of mnemonic devices.</p>
<h3 id="heading-self-referencing">Self-referencing</h3>
<p>Self-referencing is to look at a piece of information and think about how you relate to that information personally. You can use it to imagine the information. For instance, if you are learning about the history of a Chinese emperor, you can imagine being the emperor himself. Live the experience, and you will remember more.</p>
<p>Another typical way to use this technique is to prepare the material as if you are going to teach them. Writing a blog post elaborating on the topic also works.</p>
<h3 id="heading-spacing">Spacing</h3>
<p>Spacing is not precisely an encoding technique. It is a meta-strategy. While other methods point to what you should do during the learning process. Spacing is a strategy for how you should structure your learning session. </p>
<p>The point of this technique is to space your study session apart. Learning for one hour every day is much better than learning five hours in a single day. Spacing your study apart has been proven to help you learn more information and retain it longer.</p>
<h2 id="heading-endnote">Endnote</h2>
<p>Converting information into a code that your brain can easily remember makes learning faster and more efficient. So why not give it a try? The next time you're studying for an exam or trying to learn a new skill, use some encoding techniques to help get the job done. You may be surprised at just how effective they can be!</p>
]]></content:encoded></item><item><title><![CDATA[Column Filter Table with htmx, Alpine-js, and Django]]></title><description><![CDATA[Have you ever needed to create a table where you need to filter between multiple columns? Maybe you've tried using a standard table with a single search/filter input field, but it didn't quite work the way you wanted. 
This article will show how I bu...]]></description><link>https://enzircle.com/column-filter-table-with-htmx-alpine-js-and-django</link><guid isPermaLink="true">https://enzircle.com/column-filter-table-with-htmx-alpine-js-and-django</guid><category><![CDATA[Django]]></category><category><![CDATA[Python]]></category><category><![CDATA[htmx]]></category><category><![CDATA[alpinejs]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Thu, 18 Aug 2022 02:52:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/Wpnoqo2plFA/upload/v1660790780680/YXLliKwc8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Have you ever needed to create a table where you need to filter between multiple columns? Maybe you've tried using a standard table with a single search/filter input field, but it didn't quite work the way you wanted. </p>
<p>This article will show how I build a table with a multi-column filter. The table I am going to implement looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660790298524/6S0k7HedG.png" alt="Image by Author" /></p>
<p>As a side note, this article builds on the previous article, where I create a simple table using <code>Django</code> and <code>htmx</code>; I won't revisit how <code>htmx</code> work. Now, let's get started.</p>
<h2 id="heading-the-packages-used-for-this-project">The Packages Used For This Project</h2>
<ol>
<li><a target="_blank" href="https://htmx.org/">htmx</a>. <code>htmx</code> gives access to using AJAX  directly in HTML, using attributes. It is simple and, for a package this small, quite powerful.</li>
<li><a target="_blank" href="https://github.com/jieter/django-tables2">django-tables2</a>. This Django app lets you define tables like you define Django models. It can automatically generate a table based on a Django model. It supports pagination, column-based table sorting, custom column functionality via subclassing, and many other features.</li>
<li><a target="_blank" href="https://github.com/carltongibson/django-filter">django-filter</a>. I use this package for the filtering functionality. It has APIs similar to Django's <code>ModelForm</code> and works well with <code>django-tables2</code>.</li>
<li><a target="_blank" href="https://github.com/adamchainz/django-htmx">django-htmx</a>. For <code>htmx</code> to work, Django view needs to tell which request is made using <code>htmx</code> and which is not. It has a middleware that adds <code>htmx</code> attribute to a request object.</li>
<li><a target="_blank" href="https://alpinejs.dev/">Alpine-js</a>. This project requires a more complex behavior, <code>htmx</code> alone will not be enough. Specifically, the table needs to make sorting, pagination, and filtering work together nicely. This is where <code>Alpine-js</code> comes in. This small javascript package allows me to store data and trigger action based on changes happening to a variable. </li>
</ol>
<h2 id="heading-the-model">The Model</h2>
<p>The model I use for this project is as follows:</p>
<pre><code class="lang-python"><span class="hljs-comment"># products/models.py</span>
<span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Product</span>(<span class="hljs-params">models.Model</span>):</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Status</span>(<span class="hljs-params">models.IntegerChoices</span>):</span>
        ACTIVE = <span class="hljs-number">1</span>, <span class="hljs-string">"Active"</span>
        INACTIVE = <span class="hljs-number">2</span>, <span class="hljs-string">"Inactive"</span>
        ARCHIVED = <span class="hljs-number">3</span>, <span class="hljs-string">"Archived"</span>

    name = models.CharField(max_length=<span class="hljs-number">255</span>)
    category = models.CharField(max_length=<span class="hljs-number">255</span>)
    price = models.DecimalField(max_digits=<span class="hljs-number">10</span>, decimal_places=<span class="hljs-number">2</span>)
    cost = models.DecimalField(max_digits=<span class="hljs-number">10</span>, decimal_places=<span class="hljs-number">2</span>)
    status = models.PositiveSmallIntegerField(choices=Status.choices)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.name
</code></pre>
<h2 id="heading-the-table">The Table</h2>
<p>The table is straightforward to set up. The <code>show_header</code> is set to <code>False</code> because I put the table header, which includes columns name, in a separate template. I will discuss this further in the template section below.</p>
<pre><code class="lang-python"><span class="hljs-comment"># products/tables.py</span>
<span class="hljs-keyword">import</span> django_tables2 <span class="hljs-keyword">as</span> tables
<span class="hljs-keyword">from</span> products.models <span class="hljs-keyword">import</span> Product

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductHTMxMultiColumnTable</span>(<span class="hljs-params">tables.Table</span>):</span>
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = Product
        show_header = <span class="hljs-literal">False</span>
        template_name = <span class="hljs-string">"tables/bootstrap_col_filter.html"</span>
</code></pre>
<h2 id="heading-the-filter">The Filter</h2>
<p>The filter I use is as follows:</p>
<pre><code class="lang-python"><span class="hljs-comment"># products/filters.py</span>
<span class="hljs-keyword">from</span> decimal <span class="hljs-keyword">import</span> Decimal

<span class="hljs-keyword">from</span> django.db.models <span class="hljs-keyword">import</span> Q
<span class="hljs-keyword">from</span> django.forms <span class="hljs-keyword">import</span> TextInput
<span class="hljs-keyword">import</span> django_filters

<span class="hljs-keyword">from</span> products.models <span class="hljs-keyword">import</span> Product


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductFilter</span>(<span class="hljs-params">django_filters.FilterSet</span>):</span>
    id = django_filters.NumberFilter(label=<span class="hljs-string">""</span>)
    name = django_filters.CharFilter(label=<span class="hljs-string">""</span>, lookup_expr=<span class="hljs-string">"istartswith"</span>)
    category = django_filters.CharFilter(label=<span class="hljs-string">""</span>, lookup_expr=<span class="hljs-string">"istartswith"</span>)
    price = django_filters.NumberFilter(label=<span class="hljs-string">""</span>, method=<span class="hljs-string">"filter_decimal"</span>)
    cost = django_filters.NumberFilter(label=<span class="hljs-string">""</span>, method=<span class="hljs-string">"filter_decimal"</span>)
    status = django_filters.ChoiceFilter(label=<span class="hljs-string">""</span>, choices=Product.Status.choices)

    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Meta</span>:</span>
        model = Product
        fields = [<span class="hljs-string">"id"</span>, <span class="hljs-string">"name"</span>, <span class="hljs-string">"category"</span>, <span class="hljs-string">"price"</span>, <span class="hljs-string">"cost"</span>, <span class="hljs-string">"status"</span>]

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">filter_decimal</span>(<span class="hljs-params">self, queryset, name, value</span>):</span>
        <span class="hljs-comment"># For price and cost, filter based on</span>
        <span class="hljs-comment"># the following property:</span>
        <span class="hljs-comment"># value &lt;= result &lt; floor(value) + 1</span>

        lower_bound = <span class="hljs-string">"__"</span>.join([name, <span class="hljs-string">"gte"</span>])
        upper_bound = <span class="hljs-string">"__"</span>.join([name, <span class="hljs-string">"lt"</span>])

        upper_value = math.floor(value) + Decimal(<span class="hljs-number">1</span>)

        <span class="hljs-keyword">return</span> queryset.filter(**{lower_bound: value,
                                  upper_bound: upper_value})
</code></pre>
<h2 id="heading-the-view">The View</h2>
<p>The view will send out a full page when the request is not made by <code>htmx</code> and send partial results when the request is made by <code>htmx</code>.  </p>
<pre><code class="lang-python"><span class="hljs-comment"># products/views.py</span>
<span class="hljs-keyword">from</span> django.core.paginator <span class="hljs-keyword">import</span> Paginator, EmptyPage

<span class="hljs-keyword">from</span> django_tables2 <span class="hljs-keyword">import</span> SingleTableMixin
<span class="hljs-keyword">from</span> django_filters.views <span class="hljs-keyword">import</span> FilterView

<span class="hljs-keyword">from</span> products.models <span class="hljs-keyword">import</span> Product
<span class="hljs-keyword">from</span> products.tables <span class="hljs-keyword">import</span> ProductHTMxMultiColumnTable
<span class="hljs-keyword">from</span> products.filters <span class="hljs-keyword">import</span> ProductFilter


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomPaginator</span>(<span class="hljs-params">Paginator</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">validate_number</span>(<span class="hljs-params">self, number</span>):</span>
        <span class="hljs-keyword">try</span>:
            <span class="hljs-keyword">return</span> super().validate_number(number)
        <span class="hljs-keyword">except</span> EmptyPage:
            <span class="hljs-keyword">if</span> int(number) &gt; <span class="hljs-number">1</span>:
                <span class="hljs-comment"># return the last page</span>
                <span class="hljs-keyword">return</span> self.num_pages
            <span class="hljs-keyword">elif</span> int(number) &lt; <span class="hljs-number">1</span>:
                <span class="hljs-comment"># return the first page</span>
                <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>
            <span class="hljs-keyword">else</span>:
                <span class="hljs-keyword">raise</span>


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductHTMxMultiColumTableView</span>(<span class="hljs-params">SingleTableMixin, FilterView</span>):</span>
    table_class = ProductHTMxMultiColumnTable
    queryset = Product.objects.all()
    filterset_class = ProductFilter
    paginate_by = <span class="hljs-number">10</span>
    paginator_class = CustomPaginator

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_template_names</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">if</span> self.request.htmx:
            template_name = <span class="hljs-string">"products/product_table_partial.html"</span>
        <span class="hljs-keyword">else</span>:
            template_name = <span class="hljs-string">"products/product_table_col_filter.html"</span>

        <span class="hljs-keyword">return</span> template_name
</code></pre>
<p>I also add a <code>CustomPaginator</code> to handle an edge case in pagination. Specifically, this is to handle a case where for instance:</p>
<ol>
<li>A user selects a valid page 8</li>
<li>The user then uses the filter</li>
<li>Because the result of the filter is less than 8 pages long, <code>Django</code> then raises 404.</li>
</ol>
<p>Here I can choose to use a different template when Django raises 404. But I am not sure if it is desirable. The template will not have any pagination information. I think this will confuse users since they would not know that the empty page results from an invalid page and filtering, rather than just filtering. </p>
<p>So, when a user filter and the page is invalid, I simply return either the last page or the first page of the filtering result. </p>
<h2 id="heading-the-template">The Template</h2>
<p>There are three templates in play here:</p>
<ol>
<li>A template to render the whole page. Where I generate the page with HTML header and body and the table. </li>
<li>A template to render only the table. When we perform some action like sorting or filtering, we only need to render the partial page. </li>
<li>Custom table template to override the default. I need to remove the table definition. And add some <code>Alpine-js</code> attributes for the pagination to work.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660790417812/76bPTT3NN.png" alt="Image by Author" /></p>
<h3 id="heading-template-to-render-the-entire-page">Template to Render the Entire Page</h3>
<p>For this project, I put the table header and filter form on this page. Here I define two extra hidden <code>input</code> fields. The <code>sort</code> and <code>page</code> input. When a user performs an action, be it sorting, jumping between pages, or filtering, I will submit all three pieces of information back to the server. </p>
<p>To make this happens, I need to use <code>Alpine-js</code>. Here is the summary of how it works:</p>
<ol>
<li>When a user sorts a table by clicking on a column header,  the value of the <code>sort_by</code> that I define in the <code>x-data</code> gets updated via <code>toggle</code> function. This also changes the <code>sort</code> input value because this is linked through the use of <code>x-model</code>. To toggle between the ascending or descending arrow, I use <code>:class</code> in the column header.</li>
<li>I use <code>x-init</code> to watch if the input value for <code>sort</code> has changed or not. When the value changes,  I send out a custom event called <code>sort-initiated</code>.</li>
<li>Since I have specified that custom event in the <code>hx-trigger</code> attribute, when this custom event is made, <code>htmx</code> will submit the whole form, which contains information on the filter, the sorting, and the pagination.</li>
</ol>
<pre><code class="lang-html">{# templates/products/product_table_col_filter #}
{% extends "base.html" %}

{% load render_table from django_tables2 %}
{% load i18n %}
{% load django_tables2 %}
{% load crispy_forms_tags %}

{% block multi_htmx_table %}active{% endblock %}

{% block main %}
<span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Product table<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">id</span>=<span class="hljs-string">"table-container"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table-container"</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"form-inline"</span>
          <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"{% url 'tables:products_htmx_multicol' %}"</span>
          <span class="hljs-attr">hx-target</span>=<span class="hljs-string">".table-body-container"</span>
          <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"input, select, sort-initiated, pagination-initiated"</span>
          <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span>
          <span class="hljs-attr">hx-indicator</span>=<span class="hljs-string">".progress"</span>
          <span class="hljs-attr">x-data</span>=<span class="hljs-string">"{ sort_by: '', page_by: 1 }"</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"sort"</span> <span class="hljs-attr">x-ref</span>=<span class="hljs-string">"sort_input"</span> <span class="hljs-attr">x-model</span>=<span class="hljs-string">"sort_by"</span>
                <span class="hljs-attr">x-init</span>=<span class="hljs-string">"$watch('sort_by',
                        () =&gt; $refs.sort_input.dispatchEvent(
                                new Event('sort-initiated', { bubbles: true })))"</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"page"</span> <span class="hljs-attr">x-ref</span>=<span class="hljs-string">"paginate_input"</span> <span class="hljs-attr">x-model</span>=<span class="hljs-string">"page_by"</span>
               <span class="hljs-attr">x-init</span>=<span class="hljs-string">"$watch('page_by',
                        () =&gt; $refs.paginate_input.dispatchEvent(
                                new Event('pagination-initiated', { bubbles: true })))"</span> &gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">table</span> {% <span class="hljs-attr">render_attrs</span> <span class="hljs-attr">table.attrs</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table multi-col-header"</span> %}&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">thead</span> {{ <span class="hljs-attr">table.attrs.thead.as_html</span> }}&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
                    {% for column in table.columns %}
                    <span class="hljs-tag">&lt;<span class="hljs-name">th</span> {{ <span class="hljs-attr">column.attrs.th.as_html</span> }}
                        <span class="hljs-attr">x-data</span>=<span class="hljs-string">"{ col_name: '{{ column.order_by_alias }}',
                                  toggle(event) {
                                    this.col_name = this.col_name.startsWith('-') ? this.col_name.substring(1) : ('-' + this.col_name);
                                    sort_by = this.col_name;}}"</span>
                        @<span class="hljs-attr">click</span>=<span class="hljs-string">"toggle()"</span>
                        <span class="hljs-attr">:class</span>=<span class="hljs-string">"sort_by !== '' ? (sort_by === col_name ? (sort_by.startsWith('-') ? 'desc' : 'asc') : '') : ''"</span>
                        <span class="hljs-attr">style</span>=<span class="hljs-string">"cursor: pointer;"</span>&gt;</span>
                        {{ column.header }}
                    <span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
                    {% endfor %}
                <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>
                    {% for field in filter.form %}
                    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>{{ field|as_crispy_field }}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
                    {% endfor %}
                <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">table</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"progress"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"indeterminate"</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>

        {% render_table table %}
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{% endblock %}
</code></pre>
<h3 id="heading-template-to-render-table">Template to Render Table</h3>
<p>Here we only render the table content, no HTML header, no HTML body. Just the content.</p>
<pre><code class="lang-html">{# templates/products/product_table_partial.html #}
{% load render_table from django_tables2 %}

{% render_table table %}
</code></pre>
<h3 id="heading-template-to-override-the-default-table">Template to Override the Default Table</h3>
<p>This template overrides the default table from <code>django-tables2</code>. On this template, I render the table body and pagination information. The pagination works like sorting. When a user triggers any pagination action, the <code>page_by</code> variable in <code>x-data</code> gets updated, which changes the value of <code>page</code> input because it is linked via <code>x-model</code>. Which creates a custom event <code>pagination-initiated</code>. Which then tells <code>htmx</code> to send the complete form with all sorting, pagination, and filtering information back to the server.</p>
<pre><code class="lang-html">{# templates/tables/bootstrap_col_filter.html #}
{% load django_tables2 %}
{% load i18n %}

{% block table-wrapper %}
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table-body-container"</span>&gt;</span>
        {% block table %}
            <span class="hljs-tag">&lt;<span class="hljs-name">table</span> {% <span class="hljs-attr">render_attrs</span> <span class="hljs-attr">table.attrs</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table"</span> %}&gt;</span>
                {% block table.thead %}{% endblock table.thead %}

                {% block table.tbody %}
                    <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"body-target"</span> {{ <span class="hljs-attr">table.attrs.tbody.as_html</span> }}&gt;</span>
                    {% for row in table.paginated_rows %}
                        {% block table.tbody.row %}
                            <span class="hljs-tag">&lt;<span class="hljs-name">tr</span> {{ <span class="hljs-attr">row.attrs.as_html</span> }}&gt;</span>
                                {% for column, cell in row.items %}
                                    <span class="hljs-tag">&lt;<span class="hljs-name">td</span> {{ <span class="hljs-attr">column.attrs.td.as_html</span> }}&gt;</span>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
                                {% endfor %}
                            <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
                        {% endblock table.tbody.row %}
                    {% empty %}
                        {% if table.empty_text %}
                            {% block table.tbody.empty_text %}
                                <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">td</span> <span class="hljs-attr">colspan</span>=<span class="hljs-string">"{{ table.columns|length }}"</span>&gt;</span>{{ table.empty_text }}<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>
                            {% endblock table.tbody.empty_text %}
                        {% endif %}
                    {% endfor %}
                    <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
                {% endblock table.tbody %}
                {% block table.tfoot %}
                    {% if table.has_footer %}
                        <span class="hljs-tag">&lt;<span class="hljs-name">tfoot</span> {{ <span class="hljs-attr">table.attrs.tfoot.as_html</span> }}&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
                            {% for column in table.columns %}
                                <span class="hljs-tag">&lt;<span class="hljs-name">td</span> {{ <span class="hljs-attr">column.attrs.tf.as_html</span> }}&gt;</span>{{ column.footer }}<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
                            {% endfor %}
                        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">tfoot</span>&gt;</span>
                    {% endif %}
                {% endblock table.tfoot %}
            <span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
        {% endblock table %}

        {% block pagination %}
            {% if table.page and table.paginator.num_pages &gt; 1 %}
                <span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Table navigation"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"pagination justify-content-end"</span>&gt;</span>
                {% if table.page.has_previous %}
                {% block pagination.previous %}
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"previous page-item"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"button"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"page_by = {{table.page.previous_page_number}}"</span>
                         <span class="hljs-attr">class</span>=<span class="hljs-string">"page-link"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-symbol">&amp;laquo;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                        {% trans 'previous' %}
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                {% endblock pagination.previous %}
                {% endif %}
                {% if table.page.has_previous or table.page.has_next %}
                {% block pagination.range %}
                {% for p in table.page|table_page_range:table.paginator %}
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-item{% if table.page.number == p %} active{% endif %}"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"button"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-link"</span> {% <span class="hljs-attr">if</span> <span class="hljs-attr">p</span> != <span class="hljs-string">'...'</span> %}@<span class="hljs-attr">click</span>=<span class="hljs-string">"page_by={{p}}"</span>{% <span class="hljs-attr">endif</span> %}&gt;</span>
                        {{ p }}
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
                {% endfor %}
                {% endblock pagination.range %}
                {% endif %}
                {% if table.page.has_next %}
                {% block pagination.next %}
                <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"next page-item user-select"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"button"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> @<span class="hljs-attr">click</span>=<span class="hljs-string">"page_by = {{table.page.next_page_number}}"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-link"</span>&gt;</span>
                        {% trans 'next' %}
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-symbol">&amp;raquo;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</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">li</span>&gt;</span>
                {% endblock pagination.next %}
                {% endif %}
            <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
            {% endif %}
        {% endblock pagination %}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{% endblock table-wrapper %}
</code></pre>
<h2 id="heading-the-result">The Result</h2>
<p>This is what it looks like in the end.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td></td></tr>
</thead>
<tbody>
<tr>
<td>Sorting <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660790446017/vYU_yu52i.gif" alt="Sorting" /></td><td>Pagination <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660790497288/CbdJSystz.gif" alt="Pagination" /></td></tr>
<tr>
<td>Filtering <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660790539553/ZqK-Zq8Qk.gif" alt="Filter" /></td><td>Progress Bar <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660790573899/62wqb7LQ0.gif" alt="Progress" /></td></tr>
<tr>
<td>All actions <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1660790601540/GUwFnQn4h.gif" alt="All actions" /></td></tr>
</tbody>
</table>
</div><h2 id="heading-summary">Summary</h2>
<p>In this article, I explore how to use <code>Django</code>, <code>htmx</code> to build a slightly more complex table. For a more complex behavior, <code>htmx</code> alone is not enough. This is where I use <code>Alpine-js</code>. With <code>Alpine-js</code> I built a table with a multi-column filtering feature. It is pretty straightforward to make. I hope you find this article helpful. </p>
<p>In the future, I will explore how to build an even more complex table with features like complex filters, column selection, and saved filters.</p>
]]></content:encoded></item><item><title><![CDATA[The Psychology of Money]]></title><description><![CDATA[Money is one of those things that we all think about. It is often a source of anxiety and stress. We are constantly bombarded with messages about how we should save it, spend it, and earn it. But how should you think about money so that you can make ...]]></description><link>https://enzircle.com/the-psychology-of-money</link><guid isPermaLink="true">https://enzircle.com/the-psychology-of-money</guid><category><![CDATA[personal finance]]></category><category><![CDATA[books]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Wed, 22 Jun 2022 04:20:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1655871563754/IPGqa42A7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Money is one of those things that we all think about. It is often a source of anxiety and stress. We are constantly bombarded with messages about how we should save it, spend it, and earn it. But how should you think about money so that you can make the most out of it?</p>
<p>In his book, "The Psychology of Money," Morgan Housel writes about a set of behavior you can use to achieve financial success. He argues that financial success is not hard science. It is a soft skill. Doing well with money has little to do with how smart you are and a lot to do with your behavior. This article summarizes the most helpful tips on how you should think about money and some helpful tips on managing your money effectively.</p>
<h2 id="heading-how-to-think-about-money">How to think about money</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655870900198/x3amElkog.png" alt="Image by Author." /></p>
<h3 id="heading-humility-and-compassion">Humility and Compassion</h3>
<p>Handling money is hard. We all have wildly different views on how to handle money. The world is complex. Luck and risk are hard to identify. Know that when making decisions regarding money, sometime you will be correct, sometime you will be wrong. Find humility when things are going right. And do not beat yourself up when things are going wrong. Be compassionate when judging yourself and others. Things are never as good or as bad as it looks.</p>
<h3 id="heading-control-over-time">Control Over Time</h3>
<p>Ultimately, we all want to be happy. One of the most potent ways that affect your happiness is having control over your time. To be able to do what you want, when you want, with who you want, and for as long as you wish will significantly boost your happiness. Instead of chasing money, you should see money as a means to gain control over your time.</p>
<h3 id="heading-reasonable-andgt-rational">Reasonable &gt; Rational</h3>
<p>You should aim to be mostly reasonable rather than trying to be coldly rational. Reasonable is more realistic. It may be rational to take more risk on a certain decision. And some people are fine taking a higher risk if it means they are earning the highest returns. But others will only get a good rest if they invest more conservatively. To each their own. Learn to manage your money in a way that helps you sleep at night.</p>
<h3 id="heading-compounding-works">Compounding Works</h3>
<p>Time is your friend. Time is the most powerful force in investing. It makes little things grow big, and big mistakes fade away. It can not neutralize luck and risk, but it pushes results closer to what you deserve. Consider this: $81.5 billion of Warren Buffett's $84.5 billion net worth came after his 65th birthday.</p>
<h3 id="heading-play-your-own-game">Play Your Own Game</h3>
<p>Define the game you are playing and ensure your actions are not influenced by people playing a different game. A short-term trader has a different mindset than a long-term investor. Once you know which game you play, define the cost of success and pay it.</p>
<h3 id="heading-respect-the-mess">Respect the Mess</h3>
<p>A lot of things can and will go wrong. You will make mistakes. And it is okay to make mistakes. Believe that you can be wrong half the time and still make a fortune because a small minority of things account for most outcomes. There is no single right answer when it comes to handling money. There is just the answer that works for you. Smart, informed, and reasonable people can disagree in finance because we all have different goals and desires.  </p>
<h2 id="heading-daily-practice">Daily Practice</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655870922380/DY1HSvIrl.png" alt="Image by Author" /></p>
<h3 id="heading-save">Save</h3>
<p>Saving money is the path to wealth. Wealth is created by suppressing what you could buy today to have more stuff or options in the future. No matter how much you earn, you will never build wealth unless you can limit how much fun you can have with your money today. </p>
<h3 id="heading-be-nicer-and-less-flashy">Be Nicer and Less Flashy</h3>
<p>No one is impressed with your possessions as much as you are. Nobody cares about your sports car or your watch. What you want is to be respected and admired by others. But using money to buy fancy things does not help. You gain respect and admiration through kindness and humility.</p>
<h3 id="heading-survival-mindset">Survival Mindset</h3>
<p>Getting money is one thing. Keeping it is another. There are a million ways to get wealthy, but there is only one way to stay wealthy: some combination of frugality and paranoia. The world is full of surprises, and the future is impossible to predict or define. Having a survival mentality is essential with money. Having a survival mentality means more than big returns; you aim to be financially unbreakable. It means you have a plan, but also prepare when things are not going according to plan. It means you have hope for the future but are paranoid about what will prevent you from getting to that future. </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>These are lessons we can carry with us as we go about our lives, making minor tweaks to how we think and behave around money. Remember, money is just one piece of the puzzle – do not let it become your entire life.</p>
]]></content:encoded></item><item><title><![CDATA[How to Decide]]></title><description><![CDATA[You make a lot of decisions every day. Some big and some small. Some have significant consequences, and some have little impact. No matter what type of decision you are facing, it is imperative to develop a decision process that improves your decisio...]]></description><link>https://enzircle.com/how-to-decide-1</link><guid isPermaLink="true">https://enzircle.com/how-to-decide-1</guid><category><![CDATA[Productivity]]></category><category><![CDATA[books]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Mon, 23 May 2022 09:20:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1653291422010/qyV79jjij.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You make a lot of decisions every day. Some big and some small. Some have significant consequences, and some have little impact. No matter what type of decision you are facing, it is imperative to develop a decision process that improves your decision quality and helps sort your choices to identify which ones are more important and which ones are less important.</p>
<p>In her book "How To Decide," Annie Duke presents a framework that helps you make decisions. This article is a summary of the decision-making framework detailed in her book.</p>
<p>For a piece of background information, Annie Duke is a former professional poker player and author in cognitive-behavioral decision science and decision education. Duke's total lifetime live tournament winnings of $4,270,548 place her at fourth overall in the women's all-time live tournament winnings. </p>
<h2 id="heading-things-to-understand">Things to Understand</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653285838836/cuE1o9SiO.png" alt="Image by Author" /></p>
<h3 id="heading-decision-multiverse">Decision Multiverse</h3>
<p>There are many possible futures. When you make a decision, you see the future possibilities like the branches of a tree, each branch representing how things could unfold. To be a better decision-maker, you need to learn to put possible outcomes (good or bad) in perspective.</p>
<h3 id="heading-willingness-to-guess">Willingness to guess</h3>
<p>Most people are reluctant to estimate the likelihood of something happening in the future. ("That's speculative." "I don't know enough." "I'd just be guessing.”). Even though your information is usually imperfect, you know something about most things, enough to make an educated guess.</p>
<p>The willingness to guess is essential to improving decisions. If you don't make yourself guess, you'll be less likely to ask, "What do I know?" and "What don't I know?"</p>
<h2 id="heading-things-to-avoid">Things to Avoid</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653285856047/MLTW-GnkI.png" alt="Image by Author" /></p>
<h3 id="heading-outcome-bias">Outcome Bias</h3>
<p>Outcome bias is a mental shortcut in which we use the quality of an outcome to figure out the quality of a decision. It is the tendency to look at whether a result was good or bad to figure out whether a decision was good or bad.</p>
<p>You should not judge if a decision is good or bad based on the result. There is only a loose relationship between the quality of the decision and the quality of the outcome. When we decide in real life, there is an element of luck. Luck intervenes between your decision and the actual outcome. You can make a good decision and have a bad result due to luck. You can also make a super dumb decision that has a good result. </p>
<h3 id="heading-hindsight-bias">Hindsight Bias</h3>
<p>Hindsight bias is the tendency to believe that an outcome is predictable or inevitable after it occurs. To get better at making decisions, you need to learn from your choices and their outcomes, but hindsight bias distorts the way you process outcomes. </p>
<p>Once you know how a decision turns out, you can experience memory creep, where the stuff that reveals itself after the fact creeps into your memory of what you knew or was knowable before the decision.</p>
<h2 id="heading-decision-making-framework">Decision-Making Framework</h2>
<h3 id="heading-the-three-ps">The Three Ps</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653285874840/KqnZVR6Q6.png" alt="Image by Author" /></p>
<h4 id="heading-preference">Preference</h4>
<p>There is a greater liking for one alternative over another for every outcome you identified. This liking or preference is individual to you and depends on your goals and values. How much you prefer a particular result relative to other possibilities will naturally be different from another person's preference for the same outcome relative to other possibilities. That doesn't make either of you wrong. It just means that you are different people with particular likes and dislikes.</p>
<h4 id="heading-payoffs">Payoffs</h4>
<p>For any set of outcomes, there will be stuff you could gain (upside potential) and stuff you could lose (downside potential). These gains or losses are called payoffs. Payoffs can be measured in anything you value, like money, time, happiness, health, etc. They will drive your preference because you will prefer gains over losses. When you are figuring out whether a decision is good or bad, you are comparing the upside vs. downside. Does the upside potential compensate for the risk of the downside potential?</p>
<h4 id="heading-probabilities">Probabilities</h4>
<p>Probabilities express how likely something is to occur. To figure out if a decision is good or bad, you need to know the likelihood of each possible outcome.                     </p>
<p>Without information about the probability of any possibility unfolding, you can beat yourself up over a bad result because you can not see that the result was improbable to happen in the first place.</p>
<h3 id="heading-six-steps-to-better-decision-making">Six steps to better decision-making</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653285941550/h7p4kZILg.png" alt="Image by Author" /></p>
<ol>
<li>Identify the reasonable set of possible outcomes</li>
<li>Identify your preference using the payoff for each outcome-- to what degree do you like or dislike each outcome, given your values</li>
<li>Estimate the likelihood of each outcome unfolding</li>
<li>Assess the relative likelihood of outcomes you like and dislike for the option under consideration</li>
<li>Repeat steps 1-4 for other options under consideration.</li>
<li>Compare the options to one another</li>
</ol>
<h2 id="heading-how-to-spend-decision-making-time-wisely">How to Spend Decision-Making Time Wisely</h2>
<p>The average person spends 250–275 hours per year deciding what to eat, watch, and wear. We take too much time when we make decisions. And many of those decisions we take are routine inconsequential decisions. </p>
<p>When making any decisions, we are faced with a trade-off. Increasing accuracy costs time. Saving time costs accuracy.</p>
<p>The key to balancing this trade-off is to figure out the penalty for not getting the decision exactly right. When you are making decisions consider these four scenarios.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653297544108/bseWbzVIv.png" alt="Image by Author" /></p>
<h3 id="heading-happiness-test">Happiness Test</h3>
<p>If the outcome of your decision, good or bad, has no effect on your happiness in a year, you have passed the happiness test, and you can speed up.</p>
<h3 id="heading-freerolling">Freerolling</h3>
<p>When you face a situation where the upside is positive, and the downside is limited, go fast. The faster you decide to seize the opportunity, the quicker you realize the one-sided upside potential of the decision.</p>
<h3 id="heading-sheep-in-wolfs-clothing">Sheep in wolf's clothing</h3>
<p>When you are considering two options that are close, then the decision is easy. Whichever one you choose, you can't be wrong since the difference between the two is so tiny.</p>
<h3 id="heading-quit-early-quit-often">Quit early, quit often</h3>
<p>The lower the cost to quit, the faster you can go because it is easier to unwind the decision and choose a different option, including options you may have rejected in the past. </p>
<p>Decisions with a low cost to quit, known as two-way-door decisions, also provide you with low-cost opportunities to make experimental decisions to gather information and learn about your values and preferences for future decisions. </p>
<p>When facing a decision with a high or prohibitive cost of changing your mind, try decision stacking, making two-way-door decisions ahead of the one-way-door decision. You can also defray opportunity costs by exercising multiple options in parallel.</p>
<p>Here is a simple flow chart on how to manage the time-accuracy trade-off.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653285971865/K66HOMk3a.png" alt="Image by Author" /></p>
<h3 id="heading-knowing-when-your-decision-process-is-finished">Knowing When Your Decision Process Is Finished</h3>
<p>Because you can not be sure of the outcome of your decision, you will make most decisions while still uncertain. To know if it is worthwhile to have additional time to decide, ask yourself, "Is there additional information (available at a reasonable cost) that would change your mind?" If yes, find it. If no, decide and move on.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653286081102/QjkQGnAOi.png" alt="Image by Author" /></p>
]]></content:encoded></item><item><title><![CDATA[Coding Techniques for Solving Algorithmic Problems]]></title><description><![CDATA[How many times have you seen an algorithm problem and had no idea how to start solving it? Do not worry. You are not alone. I struggle with the process of solving algorithm problems all the time. Often, I just stare at the problem or start typing awa...]]></description><link>https://enzircle.com/coding-techniques-for-solving-algorithmic-problems</link><guid isPermaLink="true">https://enzircle.com/coding-techniques-for-solving-algorithmic-problems</guid><category><![CDATA[algorithms]]></category><category><![CDATA[coding]]></category><category><![CDATA[coding challenge]]></category><category><![CDATA[learn coding]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Sun, 15 May 2022 08:32:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1652603416390/CzGSdSjjs.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>How many times have you seen an algorithm problem and had no idea how to start solving it? Do not worry. You are not alone. I struggle with the process of solving algorithm problems all the time. Often, I just stare at the problem or start typing away at the keyboard until I get the right solution, spending hours in the process. </p>
<p>It turns out that there are underlying patterns for most of these problems. Finding the right technique to solve an algorithm problem can be the difference between a correct solution and spending hours staring at a problem. </p>
<p>I have summarized some patterns that help me solve algorithmic problems in a short study note. There are 14 techniques overall. Each method is explained with examples that illustrate how it can be used to solve specific types of problems. You can learn how to apply these techniques to solving problems in order to produce correct solutions more efficiently.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652708849960/UsOtWL7Mu.png" alt="Sliding windows technique study notes" /></p>
<p>I also provide just a summary of the coding techniques without the code examples for quick reference.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652603366433/oEn2QeEdn.png" alt="Coding techniques quick guide" /></p>
<p>With a bit of practice, you will be able to apply these patterns to any algorithm problem and start solving problems. Pick up a copy of coding techniques today!</p>
<div class="hn-embed-widget" id="gr-coding-tech"></div>]]></content:encoded></item><item><title><![CDATA[Effective Memory Techniques for Learning]]></title><description><![CDATA[Do you struggle to remember things? Do you feel like you are constantly forgetting important information? If so, don't worry - you're not alone. Many people find it difficult to remember things. There are a number of memory techniques that can help m...]]></description><link>https://enzircle.com/effective-memory-techniques-for-learning</link><guid isPermaLink="true">https://enzircle.com/effective-memory-techniques-for-learning</guid><category><![CDATA[learning]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Thu, 12 May 2022 07:48:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/Nwkh-n6l25w/upload/v1652342028178/qZQnCq5MU.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Do you struggle to remember things? Do you feel like you are constantly forgetting important information? If so, don't worry - you're not alone. Many people find it difficult to remember things. There are a number of memory techniques that can help make remembering facts easier. In this blog post, I will discuss some of the most effective memory techniques and how to use them. So, if you're looking for ways to improve your memory, keep reading! </p>
<p>But before we talk about memory techniques, let's talk about the need for memorization.</p>
<h2 id="heading-why-memorize">Why memorize?</h2>
<p>There is a lot of debate on whether or not you should memorize things. Some people swear by it, while others say that there are better ways to learn and remember information. Here are some reasons why you should memorize:</p>
<ol>
<li>To reason, you need facts. When you commit data to memory, you can play with information in your head. The more information you have in your memory, the more you can use it to reason. Memorizing facts gives you a bank of material you can use to form or test a hypothesis. </li>
<li>Foundation for higher-order thinking. Only with a factual base that you believe can you effectively use your higher levels of thinking. Memorizing makes information readily available for learning deeper and making connections to new material. You can not be creative or innovative with information you do not have.</li>
<li>Organise your knowledge. To memorize any information, you need to first organize it into little chunks that flow in a logical order. In turn, it gives you a structure and context in which to put any new information. Memorizing a set of facts means that new information can be inserted in an orderly way, sandwiched, or used to enhance other facts in an organized framework.</li>
<li>It stays with you. You can now look up anything thanks to technology, but that consumes time and working memory. By committing facts to memory, you are much more efficient and can work around the working memory limitations. Even if you don't understand it the first time, memorizing information allows you to return to it. And research has shown that the retrieval practice will significantly increase learning. Having to pull the information out and make meaning is how learning occurs.</li>
</ol>
<p>Keep in mind that memorization is different from rote learning. Rote learning is learning by repetition without any regard for understanding. It is not the primary goal of learning, but it is a critical component.</p>
<h2 id="heading-memory-techniques-and-learning">Memory techniques and learning</h2>
<p>When you first learn any new information, it is put in your working memory. Working memory is very good at receiving data from our long-term memory and manipulating it. Unfortunately, your working memory can only hold between five to seven items. </p>
<p>To make learning effective, you need to move the information you have learned to long-term memory. Encoding is the process of transferring information from working memory to long-term memory. If you encode correctly, you will have an easier time remembering the information in the future.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652342078919/FZ5wWOrAn.png" alt="Image by Author." /></p>
<p>Memory techniques or mnemonic devices are among the best techniques to help you encode information. Other encoding techniques are chunking, self-referencing, and spacing.</p>
<h2 id="heading-effective-memory-techniques">Effective memory techniques</h2>
<p>There are many different types of memory techniques, and people have used them for centuries to help remember everything from essential facts to the order of cards in a deck. In this article, I will focus on the three most effective memory techniques you can use when you learn.</p>
<p>First of all, the basis of all memory techniques is story, imagination, and characters. To remember better, you need to be able to make up stories, imagination, and characters in your mind. The more vivid and wild, and unusual you make your images association, the more likely you can remember information. Mundane information will not excite your brain, but wild stories and wild imagination will.</p>
<p>Other memory techniques rely on your ability to create stories, images, and characters. Luckily everyone can make these things up. And the more often you do this, the better you get at creating this.</p>
<p>Now that you understand the basics of memory techniques let us move on to the more advanced technique: memory palace, visual alphabet, and Dominic system.</p>
<h3 id="heading-memory-palace">Memory palace</h3>
<p>Nearly everyone has heard of the phrase "memory palace." Still, not many people know what it is or how to use it. A memory palace is a technique used to improve your memory by associating objects with specific locations. You attach knowledge to the locations by imagining events there. It can be used for short-term and long-term memories and is the most effective technique available. </p>
<p>To "build" a memory palace, you need to choose a location. Potential memory palaces can be your homes, schools, workplaces, parks, neighborhoods, libraries, malls,  gyms, etc. You can turn any location you are familiar with into a memory palace. But to be effective, you should choose locations carefully. According to an ancient Roman textbook, "Rhetorica ad Helenium," you should choose locations that are far away from distractions. The locations should be well lit. They should be pretty distinct from each other. Locations should be moderate in size and have a moderate distance between them. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652342110745/aXD1ET_0A.png" alt="Image by Author with illustration from &lt;a href=&quot;http://www.freepik.com&quot;&gt;macrovector&lt;/a&gt; and &lt;a href=&quot;https://undraw.co/&quot;&gt;Undraw&lt;/a&gt;." /></p>
<p>Once you have a location you want to turn into a memory palace, you can plan out the whole route. For example, gate, front porch, front door, shoe rack, bathroom, living room, etc. When you have a list that you want to memorize, take one or two items and place a mental image of them in each locus of your memory palace. Exaggerate the visualization of the information and have them interact with the location. Even better if you can turn the images alive with your senses. </p>
<h3 id="heading-visual-alphabet">Visual alphabet</h3>
<p>When it comes to memorizing a list, there is no better technique than the visual alphabet. This method uses images to represent each letter of the alphabet. By creating a mental picture associated with each letter, it becomes much easier to remember items on a list. For example, the letter A might be represented by an image of an apple, B could be a banana, and so on. Here is the visual alphabet that I have created and used.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652342151656/sgUhanioZ.png" alt="Image by Author." /></p>
<p>Most of the time, I use this technique in conjunction with other memory techniques. For instance, when I want to memorize a piece of information that contain sub-components. I use the memory palace to store the main points. If there are sub-components, I will just use this technique to create a list and place it on a locus.</p>
<h3 id="heading-dominic-system">Dominic system</h3>
<p>The Dominic system is a memory technique invented by Dominic O'Brien. It is a system for memorizing long sequences of numbers by first converting them into pairs of letters. And then associating those letters with easier-to-remember people and actions.</p>
<p>The digit-letter association used by the Dominic system are :</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Number</td><td>Mnemonic</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>A</td></tr>
<tr>
<td>2</td><td>B</td></tr>
<tr>
<td>3</td><td>C</td></tr>
<tr>
<td>4</td><td>D</td></tr>
<tr>
<td>5</td><td>E</td></tr>
<tr>
<td>6</td><td>S</td></tr>
<tr>
<td>7</td><td>G</td></tr>
<tr>
<td>8</td><td>H</td></tr>
<tr>
<td>9</td><td>N</td></tr>
<tr>
<td>0</td><td>O</td></tr>
</tbody>
</table>
</div><p>For instance, 04 is "OD," which becomes Oscar De La Hoya punching a sandbag. The number 62 is "SB" and could be Sandra Bullock driving a bus.</p>
<p>To use this system, I have made visualization for numbers from 00 - 99. To help me remember this better, I dump them into my Anki and review the numbers every day. Here is an example of my first 20 visualizations.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>#</td><td>Visualization</td><td>#</td><td>Visualization</td></tr>
</thead>
<tbody>
<tr>
<td>00</td><td>Obelisk eating boar</td><td>10</td><td>Maradona scoring a goal using his hand</td></tr>
<tr>
<td>01</td><td>Asterix putting on his feathered hat</td><td>11</td><td>Andre Agassi swinging a tenis racket</td></tr>
<tr>
<td>02</td><td>Obi-wan Kenobi wielding a lightsaber</td><td>12</td><td>Astro boy flying</td></tr>
<tr>
<td>03</td><td>Trinity kicking a cop</td><td>13</td><td>Al Capone spraying bullet using a tommy gun</td></tr>
<tr>
<td>04</td><td>Oscar De La Hoya punching a sandbag</td><td>14</td><td>Anthony Davies trimming his unibrow</td></tr>
<tr>
<td>05</td><td>Oedipus blinding his eyes</td><td>15</td><td>Amelia Earhart flying an aeroplane</td></tr>
<tr>
<td>06</td><td>OJ Simpson trying on gloves</td><td>16</td><td>Arnold Schwarzenegger performing a squat</td></tr>
<tr>
<td>07</td><td>Oscar the Grouch sitting on a trash can</td><td>17</td><td>Ariana Grande singing into a mic</td></tr>
<tr>
<td>08</td><td>Oliver Hardy swinging a plank</td><td>18</td><td>Adolf Hitler raising his hand</td></tr>
<tr>
<td>09</td><td>Owen Wilson wearing a cowboy suit</td><td>19</td><td>Alfred Nobel blowing up a mine using dynamite</td></tr>
</tbody>
</table>
</div><p>Long numbers are chunked in 4s. The first 2 digits are converted into a person, and the second 2 digits become an action. To remember the year 1619, I imagine Arnold Schwarzenegger riding a motorcycle. </p>
<p>To remember a long number like 17191902, for example, you need to chunk them into 2763 and 6339. Then convert them into AGAN ANOB. Using my own list, I can create a story of Ariana Grande blowing up a mine with dynamite and getting cut in half by Alfred Nobel, who wields a lightsaber.</p>
<h2 id="heading-tips-for-using-memory-techniques-effectively">Tips for using memory techniques effectively</h2>
<p>Memory techniques can be a great way to boost your ability to remember information. However, using them effectively can be tricky. Here are some tips for getting the most out of your memory techniques, according to Alex Mullen, a memory athlete and a medical student from mullenmemory.com.</p>
<ol>
<li>Do not memorize everything using memory techniques. Memory techniques are just tools. If you can intuitively remember a concept, you don't need to utilize memory techniques. Use your critical thinking about the material. Think about what is worth memorizing and what is not.</li>
<li>Do not encode everything using images. Maintain an optimal mix of understanding/intuition coupled with memory techniques as a supplement</li>
<li>Use memory palace to structure material. The memory palace is incredibly good at helping you organize and sequence information. </li>
<li>Use spaced repetition software to maintain your mnemonics. Images can get faded away in your memory. Spaced repetition software like Anki can help you manage your memory. Spaced repetition is a critical component of building lasting retention.</li>
</ol>
<h2 id="heading-summary">Summary</h2>
<p>Memory techniques are an effective way to learn and remember facts. The three techniques I have shared should help you get started with improving your memory. Have you tried any of these methods before? What has been your experience? </p>
]]></content:encoded></item><item><title><![CDATA[Introduction to Personal Content Curation]]></title><description><![CDATA[If you are like most people, you have a lot of information coming at you from all directions. It can be hard to keep track of it all and even harder to figure out what is important. That is where personal content curation comes in. By assembling a ta...]]></description><link>https://enzircle.com/introduction-to-personal-content-curation</link><guid isPermaLink="true">https://enzircle.com/introduction-to-personal-content-curation</guid><category><![CDATA[Productivity]]></category><dc:creator><![CDATA[Joash Xu]]></dc:creator><pubDate>Fri, 08 Apr 2022 04:41:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1649406825680/Y5EBRNDbp.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are like most people, you have a lot of information coming at you from all directions. It can be hard to keep track of it all and even harder to figure out what is important. That is where personal content curation comes in. By assembling a tailored selection of books, articles, videos, images, and other online content, you can create a personalized digest that helps you stay informed about the topics that matter most to you. In this blog post, I will introduce you to the basics of personal content curation and show you how to get started.</p>
<h2 id="heading-what-is-personal-content-curation">What is personal content curation</h2>
<p>Content curation is the process of sifting through the vast amount of information available and organizing them in a meaningful way for an audience. Content curation is not about mindlessly collecting links; it is about putting them into a context with organization, annotation, and presentation. A content curator continually seeks, makes sense of, and shares the best and most relevant content on a particular topic online to share with their community. </p>
<p>Personal content curation is no different from content curation. The work involves sifting, sorting, and arranging. The main difference is that you are the primary audience, which means that the value of the content lies in its usefulness and application to you, first and foremost.</p>
<h2 id="heading-why-you-should-curate">Why you should curate</h2>
<p>We live in an age where there is simply too much information. Even the most minor niches like growing bonsai or home hydroponics gardens have excessive information. This is why curating content plays an essential role. Here are three reasons why you should curate content. </p>
<ol>
<li><strong>Tackle information overload</strong>. There is so much information available to us today. Social networks, websites, emails, and other digital sources make data that is now measured in exabytes, equal to a quintillion bytes. The problem is not the amount of information per se but our consumption. We are indulging too much in the easy access we get to the information. Content curation can help in this regard because mindful consumption of information is at the heart of content curation practice.</li>
<li><strong>Build knowledge</strong>. Information is not equal to knowledge. Just because you have access to information does not mean you are more knowledgeable and skillful. To create understanding, you must know how to sift through the vast amount of information and judge the value, organize, and connect that information. The amount of available information leaves many unable to organize their thoughts and ideas. As a result, their work is merely information without knowledge. Content curation can be an essential tool for you to start building knowledge. The process of organizing resources forces you to construct connections between concepts and identify areas of synthesis, encouraging critical thinking and rhetorical engagement with the information.</li>
<li><strong>Develop your tastes</strong>. Content curation teaches you to distinguish what is excellent from merely good. It teaches you to see what is missing or not working. It helps you uncover what you like or dislike, what you think is worth your time, and what is not. Why is this important? Content curation helps us form our tastes, and our tastes influence our work.</li>
</ol>
<h2 id="heading-what-to-curate">What to curate</h2>
<p>If you are unsure what to curate, you can start by asking yourself some questions. In "Show your work!" Austin Kleon, the author, suggests that you ask the following questions to kick off your curating journey:</p>
<blockquote>
<p>Where do you get inspirations? What sorts of things do you fill your head with? What do you read? Do you subscribe to anything? What sites do you visit on the Internet? What music do you listen to? What movies do you see? Do you look at art? What do you collect? What's inside your scrapbook? What do you pin to the corkboard above your desk? What do you stick on your refrigerator? Who's done work that you admire? Who do you steal ideas from? Do you have any heroes? Who do you follow online? Who are the practitioners you look up to in your field?</p>
</blockquote>
<h2 id="heading-how-to-start-curating">How to start curating</h2>
<p>It can be tough to know where to start when it comes to curating content. But do not worry, I am here to help! Here are five ways to get started: </p>
<ol>
<li><strong>Create a repository</strong>. Create a place where you can put your collections. This can be a Pinterest board or a Raindrop collection. You can start by collecting a small set of valuable sources and personal insights for yourself. </li>
<li><strong>Filter your sources</strong>. Pull from a consistent set of high-quality sources to save you time. Be intentional. Good content is everywhere, and you do not want to be overwhelmed. Pick a handful of great blogs or newsletters and start from there. Do not capture unless the content is of high quality.</li>
<li><strong>Add your personality</strong>. Think about why the content is relevant or interesting to you and take some notes. Always add your interpretation or commentary.</li>
<li><strong>Organize your judgment</strong>. Take important critical takeaways from the content and turn them into an outline. Compare it with other content. Whenever you choose one piece of content over another, you refine your judgment about what is essential and what is not. Curating requires and improves your judgment skill.</li>
<li><strong>Learn and fail in public</strong>. Consider sharing your curation with the public. When you share with others, you might get comments and complaints that test your understanding and surface doubts and weak points. This will lead you to an even deeper understanding of your collection.</li>
</ol>
<h2 id="heading-personal-content-curation-tools">Personal content curation tools</h2>
<p>There are numerous tools to help you curate content. From the supply side, there are a lot of websites (example: Medium, Hackernews, etc.), news aggregators (example: Google news), and countless newsletters (example: Hackernews, Python Weekly, PyCoder, etc.) for you to curate content. Remember to start from a small set of high-quality sources and build up from that. </p>
<p>Once you have read them, you want to store some of them. For this, at the moment, I am using the following platform:</p>
<ol>
<li><a target="_blank" href="https://raindrop.io">Raindrop</a>. I am currently using Raindrop most of the time. It is a bookmark app with a lot of features. You can save a bookmark, move it into a collection, give it proper tags, etc. It supports multiple viewing modes. You can display your bookmark in list format, card formats, headlights format, and mood board. It is not perfect by any means; for instance, you can only save a bookmark to a single collection. You can work around this issue by playing around with the URL a bit. But it is a bit annoying having to do that. </li>
<li><a target="_blank" href="https://www.notion.so/">Notion</a>. I use Notion to create lists, tables, and databases. At the moment, Notion is mostly for storing programming problems or security hacking writeups. But I am also experimenting with using GitHub for this. So I might stop using Notion in the future.  </li>
<li><a target="_blank" href="https://pinterest.com">Pinterest</a>. Pinterest probably is the most well-known curating platform. It is effortless to use, and there is a lot of great content on the platform. The downside, it can turn into a rabbit hole. For this reason, I only use it for several types of content. But it is by far the easiest to start and use. </li>
</ol>
<p>I should also mention <a target="_blank" href="https://feedly.com/">Feedly</a>. It is a news aggregator application. It compiles news feeds from many online sources, and you can customize this, of course. You can even subscribe to a newsletter via Feedly, so any email from the newsletter gets into Feedly, keeping your mail inbox a bit more tidy. But it also has a curation feature called Board, similar to Pinterest board or Raindrop collections. This idea of having a news feeder and a curation platform in a single place is excellent. So I am looking into this app at the moment. </p>
<h2 id="heading-tips-for-content-curation">Tips for content curation</h2>
<p>Here are some tips for staying organized and efficient when curating your own content.</p>
<ol>
<li><strong>Select a theme</strong>. A theme is a unifying idea. You should decide on a theme when you make a repository or a collection. For example, suppose you are learning about Algorithms. In that case, you can have a narrow collection such as "Top 5 resources for learning Algorithms" or a broad theme like "Algorithms." It is easier to find content that will fit a broad theme. But a narrower theme makes your collection more cohesive. </li>
<li><strong>Know your space</strong>. Know the parameters. Is it a Pinterest board or a RainDrop collection? Both can have an unlimited number of pins or bookmarks. Think about what it means if you put a lot of items, say a hundred items vs. ten items, in your collection. A hundred items may sound significant, but it might make sense for some collections. A "Top 100 resources for learning algorithm" might be too significant to be useful. But a "A 100 of most influential persons in history" collection, a hundred makes a lot of sense. There is no right or wrong answer to the number of items you can have in your collection. You decide the number based on the theme you chose. </li>
<li><strong>Provide information</strong>. Curating is like writing an essay -- you want to present your work in a certain way. On Pinterest, you can add a description to any board. Unfortunately, you can not do it on Raindrop. On both platforms, you can manually arrange the position of the curated content. In Raindrop, there are more options; you can use lists, cards, headlines, and mood boards. So you can set up the collections a bit better. For instance, you can set up a collection in chronological order.</li>
<li><strong>Selecting work</strong>. You should start by choosing a lot of content. But, select only a handful of content that best fits your theme and is of high quality. This is where you do the work of comparing, connecting, and making sense of the information. For instance, there is a lot of book on Algorithm out there. Which one should you choose? Some books are rigorous with math, and some are not. Some start with Graph, and some are not. There is also a matter of personal preference. Maybe you like the way the author writes. It makes more sense to you. And so on.</li>
<li><strong>Write a note</strong>. Add standard labels like name, title, and other basic information. This should be automatic if you are using Raindrop and Pinterest. Both Pinterest and Raindrop also allow you to add a description to a pin or a bookmark. Always write down a description. Even a short commentary is better than no note at all. You should also write additional available information or comments if available. For example, why it fits in the collections.</li>
<li><strong>Think about anchor pieces</strong>. Every collection has certain elements that stand out. Think about which content stands out. And what makes that content an anchor piece. When you share your collections with the public, think about how these anchor pieces can direct your audience's attention. For instance, my anchor piece in my top learning algorithm collection is the "Algorithm Design Manual" book.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1649406867988/LOQ5A32v_.png" alt="Example of a collection. Image by author" /></p>
<h2 id="heading-how-to-use-curated-content-in-different-areas-of-your-life">How to use curated content in different areas of your life</h2>
<p>Curating content does not have to stop in the digital world. The concept of curating, which is selecting and organizing items in a meaningful way, can also be applied to other areas of your life. Here are some examples of how to use curated content in different areas of your life:</p>
<ol>
<li><strong>Content creation</strong>. Curating content is one of the best ways to start creating content. The act of curating the works of others can help you in creating your own work. Examples of things you can make as a curator include curated news feeds, guides, comparison tables, diagrams, learning curriculums, etc. Maybe you can be a part of the new curator economy.</li>
<li><strong>Fashion</strong>. In the fashion world, there is the term capsule wardrobe. A capsule wardrobe is a small collection of garments designed to be worn together, harmonizing in color and line. If you think about it, a capsule wardrobe is essentially a curation. Instead of having a lot of pieces of clothing, you can start curating a handful of pieces of clothing items. A collection that suits you better saves you money and that you can wear for any occasion.</li>
<li><strong>Books</strong>. You probably have heard about the "100 books to read before you die" collection. If you are an avid reader, maybe you should start your own collections. And you do not have to stop at books. You can make collections of movies, wine, art, etc.</li>
</ol>
<h2 id="heading-summary">Summary</h2>
<p>Content curation can be a great way to tackle information overload, build your knowledge, and develop your taste. Although it takes time to develop the habit, personal content curation can boost your productivity and knowledge development. It allows you to focus on learning what is important to you while building a valuable reference library along the way. By using the tips I shared in this post, I hope you feel inspired and motivated to start curating content for your own use. Remember, the key is to find what works best for you and to stick with it. The more personal your approach, the better. What are some of your favorite methods for finding and organizing information?</p>
]]></content:encoded></item></channel></rss>