<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Raymond Camden</title>
    <link href="https://www.raymondcamden.com/feed.xml" rel="self" type="application/atom+xml"></link>
    <link href="https://www.raymondcamden.com/" rel="alternate" type="text/html"></link>
    <subtitle>DevRel at Adobe, Star Wars nerd, Web/Serverless hacker, lover of good beer and good books. Oh, and cats.</subtitle>

    <updated>2023-03-21T20:13:05+00:00</updated>
    <author>
        <name>Raymond Camden</name>
        <email>raymondcamden@gmail.com</email>
    </author>
    <id>https://www.raymondcamden.com/feed.xml</id>

    <generator>Eleventy</generator>

        
            <entry>
                <id>https://www.raymondcamden.com/2023/03/21/adobe-firefly-in-beta</id>
                <title>Adobe Firefly in Beta</title>
                <updated>2023-03-21T18:00:00+00:00</updated>
                <link href="https://www.raymondcamden.com/2023/03/21/adobe-firefly-in-beta" rel="alternate" type="text/html" title="Adobe Firefly in Beta"/>
                <content type="html">
				
                        &lt;p&gt;I&apos;m currently at &lt;a href=&quot;https://business.adobe.com/summit/adobe-summit.html&quot;&gt;Adobe Summit&lt;/a&gt; and this morning at the keynote we announced the beta of &lt;a href=&quot;https://firefly.adobe.com/&quot;&gt;Adobe Firefly&lt;/a&gt;. Firefly is a generative AI service, which by itself isn&apos;t new, but Firefly has a strong commitment to being a more &lt;em&gt;responsible&lt;/em&gt; AI service. You can read more about that and Adobe Sensei &lt;a href=&quot;https://www.adobe.com/sensei/generative-ai.html&quot;&gt;here&lt;/a&gt; if you would like. As an Adobe employee, I&apos;m obviously biased, but the focus on having a responsible service that respects creators feels like a pretty important differentiator. While there are a lot of good uses for this, I decided to have some fun with it and see how well it would do something business-critical... with cats.&lt;/p&gt;&lt;p&gt;For my test, I looked up a list of Dungeons and Dragons classes and found this &lt;a href=&quot;https://www.dndbeyond.com/classes&quot;&gt;excellent list&lt;/a&gt; here. I then went to Firefly and used prompts like so:&lt;/p&gt;&lt;blockquote&gt;dungeons and dragons &amp;lt;class&amp;gt; as a cat&lt;/blockquote&gt;&lt;p&gt;In general, this worked well, but sometimes I added a bit to get things a bit closer to what I had expected. The &amp;quot;physical&amp;quot; classes looked pretty similar and I probably could have given a bit more context to help Firefly out, but I still found the results delightful.&lt;/p&gt;&lt;p&gt;Here&apos;s a Fighter:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/cat_fighter.jpg&quot; alt=&quot;Cat fighter&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;Here&apos;s a Paladin:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/cat_paladin.jpg&quot; alt=&quot;Cat paladin&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;The Barbarian, which is very similar, and could have been improved if I asked for a common weapon like an axe:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/cat_barbarian.jpg&quot; alt=&quot;Cat barbarian&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;Now for something really fun - the Bard. The double-sided lute was crazy and I absolutely loved it:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/cat_bard.jpg&quot; alt=&quot;Cat bard&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;Next up is the Cleric:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/cat_cleric.jpg&quot; alt=&quot;Cat cleric&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;For Druid, I specifically asked to add a &amp;quot;leafy staff&amp;quot;, and the result wasn&apos;t what I expected, but I loved it:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/cat_druid.jpg&quot; alt=&quot;Cat druid&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;Here&apos;s the Ranger - I added &amp;quot;bow and arrow&amp;quot; and the result was much more stylish than I expected:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/cat_ranger.jpg&quot; alt=&quot;Cat ranger&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;Next is the Monk:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/cat_monk.jpg&quot; alt=&quot;Cat monk&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;The Rogue ended up being my favorite, it looks like they&apos;re carrying the severed head of someone they just assasinated:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/cat_rogue.jpg&quot; alt=&quot;Cat rogue&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;Finally, while there are multiple magic-using classes in D&amp;amp;D, I went with simple and just used &amp;quot;wizard&amp;quot; as a prompt. The eyes on this one are crazy good:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/cat_wizard.jpg&quot; alt=&quot;Cat wizard&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;This was fun, and if you want to try it yourself, head over to the &lt;a href=&quot;https://firefly.adobe.com/&quot;&gt;site&lt;/a&gt; and request Beta access. I&apos;m not sure how long it will take to get in, but if you are interested, sign up as soon as possible.&lt;/p&gt;&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@amavcinema?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Amauri Mejía&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/s/photos/art-samples?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
                        
                
				</content>

                
                <category term="adobe" />
                
                
                <category term="design" />
                
                <author>
                    <name>Raymond Camden</name>
                    <email>raymondcamden@gmail.com</email>
                </author>
            </entry>
        
            <entry>
                <id>https://www.raymondcamden.com/2023/03/19/links-for-you</id>
                <title>Links For You</title>
                <updated>2023-03-19T18:00:00+00:00</updated>
                <link href="https://www.raymondcamden.com/2023/03/19/links-for-you" rel="alternate" type="text/html" title="Links For You"/>
                <content type="html">
				
                        &lt;p&gt;Hello friends, tomorrow I&apos;m heading out to Vegas for &lt;a href=&quot;https://summit.adobe.com/na/&quot;&gt;Adobe Summit&lt;/a&gt;, so I expect the posting to be a bit light this week.&lt;/p&gt;&lt;h2&gt;Automating your Mastodon profile with Pipedream.com&lt;/h2&gt;&lt;p&gt;Here&apos;s a &lt;a href=&quot;https://stefanbohacek.com/blog/automating-your-mastodon-profile-with-pipedream-com/&quot;&gt;great article&lt;/a&gt; that talks about using one of my favorite services, &lt;a href=&quot;https://pipedream.com&quot;&gt;Pipedream&lt;/a&gt; to automate the updating of a Mastodon profile. I really like Mastodon and the flexibility of its API is pretty great. I&apos;ve been focused on writing bots, but I love how Stefan uses it to update his profile instead. Check out his &lt;a href=&quot;https://stefanbohacek.com/blog/automating-your-mastodon-profile-with-pipedream-com/&quot;&gt;article&lt;/a&gt; and see for yourself.&lt;/p&gt;&lt;h2&gt;WebComponent * 2&lt;/h2&gt;&lt;p&gt;I use an Evernote note to keep track of the links I want to share, and for some reason, these two links have been in my queue for a few months now. They kept getting pushed down by new awesomeness. Today I look to fix that.&lt;/p&gt;&lt;p&gt;First up is &lt;a href=&quot;https://github.com/web-padawan/awesome-web-components&quot;&gt;Awesome Web Components&lt;/a&gt;, a huge list of web component articles hosted as a GitHub repository. Sometime this week I need to find time to contribute a few of my articles to it.&lt;/p&gt;&lt;p&gt;Next up is a set of toots tagged, &lt;a href=&quot;https://fosstodon.org/tags/WebComponentsAdvent&quot;&gt;WebComponentsAdvent&lt;/a&gt;. I&apos;m a big fan of the &amp;quot;Advent of X&amp;quot; type format as a way of sharing daily tips about a technical topic. (I also like my beer and wine Advent collections too, neither of which we&apos;ve finished.) When clicking the link, be sure to scroll down to the bottom to read in the right order.&lt;/p&gt;&lt;h2&gt;And now for something completely unnecessary...&lt;/h2&gt;&lt;p&gt;&lt;a href=&quot;https://www.amazon.com/EDIER-Fantasy-Jellyfish-Lava-Lamp/dp/B08B3SXHPG?crid=24I2I0UJB2XB0&amp;keywords=EDIER+LED+Fantasy+Jellyfish+Lava+Lamp&amp;qid=1679258561&amp;sprefix=edier+led+fantasy+jellyfish+lava+lamp%2Caps%2C130&amp;sr=8-1-spons&amp;psc=1&amp;spLa=ZW5jcnlwdGVkUXVhbGlmaWVyPUFBRFcwMTA2OVJHWk4mZW5jcnlwdGVkSWQ9QTAxMzgxODEyQkY0VDJDUUhXMVpYJmVuY3J5cHRlZEFkSWQ9QTEwMDQ4ODQxSVZSVlJCU1lUQ0FFJndpZGdldE5hbWU9c3BfYXRmJmFjdGlvbj1jbGlja1JlZGlyZWN0JmRvTm90TG9nQ2xpY2s9dHJ1ZQ%3D%3D&amp;linkCode=li2&amp;tag=raymondcamd06-20&amp;linkId=bc2fadadc089270dc0c11cc9b3039716&amp;language=en_US&amp;ref_=as_li_ss_il&quot; target=&quot;_blank&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://ws-na.amazon-adsystem.com/widgets/q?_encoding=UTF8&amp;ASIN=B08B3SXHPG&amp;Format=_SL160_&amp;ID=AsinImage&amp;MarketPlace=US&amp;ServiceVersion=20070822&amp;WS=1&amp;tag=raymondcamd06-20&amp;language=en_US&quot; &gt;&lt;/a&gt;&lt;img src=&quot;https://ir-na.amazon-adsystem.com/e/ir?t=raymondcamd06-20&amp;language=en_US&amp;l=li2&amp;o=1&amp;a=B08B3SXHPG&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;&lt;/p&gt;&lt;p&gt;I know what you&apos;re thinking, &amp;quot;My office is missing something, Raymond, do you have any suggestions?&amp;quot; Why yes, I do. What about some lit-up jellyfish that dance in a tube of water? Just head over to Amazon to pick up the &lt;a href=&quot;https://amzn.to/3JQVMbb&quot;&gt;jellyfish lava lamp&lt;/a&gt; that&apos;s been in my office this past week and I absolutely love it. My wife and I saw it recently in the background of a YouTube video, searched on Amazon, and pick it up literally mid-video. It&apos;s cheap, and a lot of these kinda things don&apos;t work as well as the product page says, but this one&apos;s been a true delight.&lt;/p&gt;&lt;p&gt;Here is a picture of it in my office, but honestly it&apos;s not a great picture and it looks a heck of a lot cooler in motion:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/lamp.jpg&quot; alt=&quot;Jellyfish lava lamp&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;
                        
                
				</content>

                
                <category term="links4you" />
                
                
                <category term="misc" />
                
                <author>
                    <name>Raymond Camden</name>
                    <email>raymondcamden@gmail.com</email>
                </author>
            </entry>
        
            <entry>
                <id>https://www.raymondcamden.com/2023/03/17/another-week-another-mastodon-bot-random-album-cover</id>
                <title>Another Week, Another Mastodon Bot - Random Album Cover</title>
                <updated>2023-03-17T18:00:00+00:00</updated>
                <link href="https://www.raymondcamden.com/2023/03/17/another-week-another-mastodon-bot-random-album-cover" rel="alternate" type="text/html" title="Another Week, Another Mastodon Bot - Random Album Cover"/>
                <content type="html">
				
                        &lt;p&gt;Last September, I blogged about how I used the Spotify API and &lt;a href=&quot;https://pipedream.com&quot;&gt;Pipedream&lt;/a&gt; to discover new music: &lt;a href=&quot;https://www.raymondcamden.com/2022/09/13/discover-new-music-with-the-spotify-api-and-pipedream&quot;&gt;Discover New Music with the Spotify API and Pipedream&lt;/a&gt;. I used a Pipedream workflow to select a random track from Spotify and email me a track every morning. I&apos;ve still got this process running and I enjoy seeing it every morning. More recently, I noticed a cool bit of album art in my Spotify client and it occurred to me that it would be kind of cool to see more. With that in mind, I present to you my latest Mastodon bot, &lt;a href=&quot;https://botsin.space/@randomalbumcover&quot;&gt;Random Album Cover&lt;/a&gt;. You can see an example toot here:&lt;/p&gt;&lt;blockquote class=&quot;toot-blockquote&quot; cite=&quot;https://botsin.space@randomalbumcover/status/110062402052148416&quot; data-pagefind-ignore&gt;            &lt;div class=&quot;toot-header&quot;&gt;                &lt;a class=&quot;toot-profile&quot; href=&quot;https://botsin.space/@randomalbumcover&quot; rel=&quot;noopener&quot;&gt;&lt;img src=&quot;https://files.botsin.space/accounts/avatars/110/038/769/234/613/546/original/d3e8161d8dcbc321.jpg&quot; alt=&quot;Mastodon avatar for @randomalbumcover@botsin.space&quot; loading=&quot;lazy&quot; /&gt;&lt;/a&gt;                &lt;div class=&quot;toot-author&quot;&gt;                    &lt;a class=&quot;toot-author-name&quot; href=&quot;https://botsin.space/@randomalbumcover&quot; rel=&quot;noopener&quot;&gt;Random Album Cover&lt;/a&gt;                    &lt;a class=&quot;toot-author-handle&quot; href=&quot;https://botsin.space/@randomalbumcover&quot; rel=&quot;noopener&quot;&gt;@randomalbumcover@botsin.space&lt;/a&gt;                &lt;/div&gt;            &lt;/div&gt;            &lt;p class=&quot;toot-body&quot;&gt;&lt;p&gt;Album:     Miss You (&lt;a href=&quot;https://open.spotify.com/album/32G4vFNwLJQjpzkOoGEUUo&quot; target=&quot;_blank&quot; rel=&quot;nofollow noopener noreferrer&quot;&gt;&lt;span class=&quot;invisible&quot;&gt;https://&lt;/span&gt;&lt;span class=&quot;ellipsis&quot;&gt;open.spotify.com/album/32G4vFN&lt;/span&gt;&lt;span class=&quot;invisible&quot;&gt;wLJQjpzkOoGEUUo&lt;/span&gt;&lt;/a&gt;)&lt;br /&gt;Artist(s): Oliver Tree, Robin Schulz&lt;br /&gt;Released:  2022-08-05&lt;/p&gt;&lt;/p&gt;&lt;div&gt;&lt;div class=&quot;toot-img-grid-NaN&quot;&gt;&lt;style&gt;.img-71fbb299c6f4c383d249890952e2731c {aspect-ratio: 640 / 640}&lt;/style&gt;&lt;img src=&quot;https://files.botsin.space/media_attachments/files/110/062/402/034/119/899/original/3ea6501c727e5648.jpg&quot; alt=&quot;Image 110062402034119899 from toot 110062402052148416 on botsin.space&quot; class=&quot;toot-media-img img-71fbb299c6f4c383d249890952e2731c&quot; loading=&quot;lazy&quot;/&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;toot-footer&quot;&gt;                &lt;a href=&quot;https://botsin.space/@randomalbumcover/110062402052148416&quot; class=&quot;toot-date&quot; rel=&quot;noopener&quot;&gt;5:16 PM • March 21, 2023&lt;/a&gt;&amp;nbsp;&lt;span class=&quot;legal&quot;&gt;(UTC)&lt;/span&gt;            &lt;/div&gt;        &lt;/blockquote&gt;&lt;p&gt;I have no idea what you&apos;ll see when viewing this post as it will be generated during a build, but I&apos;m looking at a striking album cover from an artist I&apos;ve never heard of, NLE Choppa. So, how was it built?&lt;/p&gt;&lt;p&gt;For the most part, it follows the logic of my &lt;a href=&quot;https://www.raymondcamden.com/2022/09/13/discover-new-music-with-the-spotify-api-and-pipedream&quot;&gt;previous post&lt;/a&gt;, doing the following:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Select a random letter&lt;/li&gt;&lt;li&gt;Randomly decide to make it the beginning of a search string (&amp;quot;A something&amp;quot;) or in the middle (&amp;quot;something A something&amp;quot;)&lt;/li&gt;&lt;li&gt;Select a random number between 0 and 1000&lt;/li&gt;&lt;li&gt;Hit the Spotify API. Their API doesn&apos;t have a &amp;quot;real&amp;quot; random search, but we use the random letter and offset to search.&lt;/li&gt;&lt;li&gt;Given our set of results, select a random record from that.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;All of the above hasn&apos;t changed from the previous post, except I switched the search from &lt;code&gt;track&lt;/code&gt; to &lt;code&gt;album&lt;/code&gt;. Next, I download the image to a temporary directory. This is straight from the Pipedream samples:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import stream from &amp;quot;stream&amp;quot;;import { promisify } from &amp;quot;util&amp;quot;;import fs from &amp;quot;fs&amp;quot;;import got from &amp;quot;got&amp;quot;;export default defineComponent({  async run({ steps, $ }) {    const pipeline = promisify(stream.pipeline);    return await pipeline(      got.stream(steps.select_random_album.$return_value.images[0].url),      fs.createWriteStream(&apos;/tmp/cover.jpg&apos;)    );  },})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And then I post the toot. This code is pretty short as it makes use of the excellent &lt;a href=&quot;https://www.npmjs.com/package/mastodon-api&quot;&gt;mastodon-api&lt;/a&gt; package. My only real work is crafting the text to go along with the image.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Mastodon from &apos;mastodon-api&apos;import fs from &apos;fs&apos;export default defineComponent({  async run({ steps, $ }) {    const M = new Mastodon({      access_token: process.env.RANDOMALBUMCOVER_MASTODON,      api_url: &apos;https://botsin.space/api/v1/&apos;,     });  let artists = steps.select_random_album.$return_value.artists.reduce((cur, art) =&amp;gt; {    if(cur == &apos;&apos;) return art.name;    return cur + &apos;, &apos; + art.name  },&apos;&apos;);  let toot = `Album:     ${steps.select_random_album.$return_value.name} (${steps.select_random_album.$return_value.external_urls.spotify})Artist(s): ${artists}Released:  ${steps.select_random_album.$return_value.release_date}  `.trim()  let resp = await M.post(&apos;media&apos;, { file: fs.createReadStream(&apos;/tmp/cover.jpg&apos;) });  await M.post(&apos;statuses&apos;, {         status: toot,        media_ids: [resp.data.id]     });  },})&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I just want to go on record as saying that this is like the third or fourth time I&apos;ve used &lt;code&gt;reduce&lt;/code&gt; without checking the docs and I&apos;m definitely a JavaScript expert now. Definitely.&lt;/p&gt;&lt;p&gt;I&apos;ll point out that I spent maybe thirty minutes total on this. The longest wait for was the Mastodon instance to approve my bot (maybe 1.5 hours). I also spent more than a few minutes wondering why my Python code wasn&apos;t running in a Node step, so maybe I&apos;m not an expert. Maybe.&lt;/p&gt;&lt;p&gt;If you want to check out the complete workflow, you can do so here: &lt;a href=&quot;https://pipedream.com/new?h=tch_m5ofq7&quot;&gt;https://pipedream.com/new?h=tch_m5ofq7&lt;/a&gt;&lt;/p&gt;
                        
                
				</content>

                
                <category term="pipedream" />
                
                <category term="mastodon" />
                
                
                <category term="serverless" />
                
                <author>
                    <name>Raymond Camden</name>
                    <email>raymondcamden@gmail.com</email>
                </author>
            </entry>
        
            <entry>
                <id>https://www.raymondcamden.com/2023/03/14/progressively-enhancing-a-table-with-a-web-component</id>
                <title>Progressively Enhancing a Table with a Web Component</title>
                <updated>2023-03-14T18:00:00+00:00</updated>
                <link href="https://www.raymondcamden.com/2023/03/14/progressively-enhancing-a-table-with-a-web-component" rel="alternate" type="text/html" title="Progressively Enhancing a Table with a Web Component"/>
                <content type="html">
				
                        &lt;p&gt;Back nearly a year ago (holy smokes time goes fast), one of my first articles about web components involved building a component to create a paginated/sorted table: &lt;a href=&quot;https://www.raymondcamden.com/2022/05/23/building-table-sorting-and-pagination-in-a-web-component&quot;&gt;Building Table Sorting and Pagination in a Web Component&lt;/a&gt;. In that example, the component looked like so in your HTML:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;data-table src=&amp;quot;https://www.raymondcamden.com/.netlify/functions/get-cats&amp;quot; cols=&amp;quot;name,age&amp;quot;&amp;gt;&amp;lt;/data-table&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I thought this was cool, but one big issue with it is that if JavaScript is disabled, or if something else goes wrong with the code, then absolutely nothing is rendered to the page. This got me thinking - what if I could build a web component that enhanced a regular HTML table? Here&apos;s what I came up with.&lt;/p&gt;&lt;p&gt;First, I set up a table of simple data:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table&amp;gt;	&amp;lt;thead&amp;gt;		&amp;lt;tr&amp;gt;			&amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;			&amp;lt;th&amp;gt;Breed&amp;lt;/th&amp;gt;			&amp;lt;th&amp;gt;Gender&amp;lt;/th&amp;gt;			&amp;lt;th&amp;gt;Age&amp;lt;/th&amp;gt;	&amp;lt;/thead&amp;gt;	&amp;lt;tbody&amp;gt;		&amp;lt;tr&amp;gt;			&amp;lt;td&amp;gt;Luna&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Domestic Shorthair&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Female&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;11&amp;lt;/td&amp;gt;		&amp;lt;/tr&amp;gt;		&amp;lt;tr&amp;gt;			&amp;lt;td&amp;gt;Elise&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Domestic Longhair&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Female&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;12&amp;lt;/td&amp;gt;		&amp;lt;/tr&amp;gt;		&amp;lt;tr&amp;gt;			&amp;lt;td&amp;gt;Pig&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Domestic Shorthair&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Female&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;8&amp;lt;/td&amp;gt;		&amp;lt;/tr&amp;gt;		&amp;lt;tr&amp;gt;			&amp;lt;td&amp;gt;Crackers&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Maine Coon&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Male&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;5&amp;lt;/td&amp;gt;		&amp;lt;/tr&amp;gt;		&amp;lt;tr&amp;gt;			&amp;lt;td&amp;gt;Zuma&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Ragdoll&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Male&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;8&amp;lt;/td&amp;gt;		&amp;lt;/tr&amp;gt;		&amp;lt;tr&amp;gt;			&amp;lt;td&amp;gt;Lord Fluffybottom, the Third&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Domestic Longhair&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Male&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;8&amp;lt;/td&amp;gt;		&amp;lt;/tr&amp;gt;		&amp;lt;tr&amp;gt;			&amp;lt;td&amp;gt;Zelda&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Domestic Shorthair&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Female&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;7&amp;lt;/td&amp;gt;		&amp;lt;/tr&amp;gt;			&amp;lt;tr&amp;gt;			&amp;lt;td&amp;gt;Apollo&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Persian&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Male&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;3&amp;lt;/td&amp;gt;		&amp;lt;/tr&amp;gt;		&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note that I make use of both &lt;code&gt;thead&lt;/code&gt; and &lt;code&gt;tbody&lt;/code&gt;. I&apos;m going to require this for my component to work, but outside of that, there&apos;s nothing special here, just a vanilla table. Now let&apos;s look at my component. First, I&apos;ll name it &lt;code&gt;table-sort&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;class TableSort extends HTMLElement {		// stuff here..}if(!customElements.get(&apos;table-sort&apos;)) customElements.define(&apos;table-sort&apos;, TableSort);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In my constructor, I&apos;m just going to set up a few values. One will hold a copy of the table data, one will remember the last column sorted, and one will be a boolean that indicates if we&apos;re sorting ascending or descending:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;constructor() {	super();	this.data = [];	this.lastSort = null;	this.sortAsc = true;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alright, now for some real work. In my &lt;code&gt;connectedCallback&lt;/code&gt;, I&apos;m going to do a few things. First, I&apos;ll do a sanity check for a &lt;code&gt;table&lt;/code&gt;, &lt;code&gt;thead&lt;/code&gt; and &lt;code&gt;tbody&lt;/code&gt; inside myself:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;connectedCallback() {	let table = this.querySelector(&apos;table&apos;);	// no table? end!	if(!table) {		console.warn(&apos;table-sort: No table found. Exiting.&apos;);		return;	}		// require tbody and thead	let tbody = table.querySelector(&apos;tbody&apos;);	let thead = table.querySelector(&apos;thead&apos;);	if(!tbody || !thead) {		console.warn(&apos;table-sort: No tbody or thead found. Exiting.&apos;);		return;				}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, I look at the body of the table and get a copy of the data:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;	let rows = tbody.querySelectorAll(&apos;tr&apos;);	rows.forEach(r =&amp;gt; {		let datum = [];		let row = r.querySelectorAll(&apos;td&apos;);		row.forEach((r,i) =&amp;gt; {			datum[i] = r.innerText;		});		this.data.push(datum);	});&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For the next portion, I look at the head. For each column, I want to do two things. First, set a CSS style to make it more obvious you can click on the header. Then I add an event handler for sorting:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;	// Get our headers	let headers = thead.querySelectorAll(&apos;th&apos;);	headers.forEach((h,i) =&amp;gt; {		h.style.cursor = &apos;pointer&apos;;		h.addEventListener(&apos;click&apos;, e =&amp;gt; {				this.sortCol(e,i);		});	});&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, I copy over a reference to the body. This will be helpful later when I render the table on sort:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;	// copy body to this scope so we can use it again later	this.tbody = tbody;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alright. At this point, the component is set up. Now let&apos;s look at the sorting event handler:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;sortCol(e,i) {	let sortToggle = 1;	if(this.lastSort === i) {		this.sortAsc = !this.sortAsc;		if(!this.sortDir) sortToggle = -1;	}	this.lastSort = i;		this.data.sort((a,b) =&amp;gt; {		if(a[i] &amp;lt; b[i]) return -1 * sortToggle;		if(a[i] &amp;gt; b[i]) return 1 * sortToggle;		return 0;	});		this.renderTable();}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The event is passed a numeric index for a column which makes sorting our data simpler. The only really fancy part here is how I remember what I sorted last time, which lets me reverse the sort if you click two or more times on the same column. If you are noticing a potential issue here, good, you are absolutely right and I&apos;ll show the issue in a sec.&lt;/p&gt;&lt;p&gt;Alright, the final part of the code is rendering the table:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;renderTable() {	let newHTML = &apos;&apos;;	for(let i=0;i&amp;lt;this.data.length;i++) {		let row = &apos;&amp;lt;tr&amp;gt;&apos;;		for(let c in this.data[i]) {			row += `&amp;lt;td&amp;gt;${this.data[i][c]}&amp;lt;/td&amp;gt;`;		}		row += &apos;&amp;lt;/tr&amp;gt;&apos;;		newHTML += row;	}	this.tbody.innerHTML = newHTML;}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is pretty boilerplate. It does have one issue - if the original table cells had other stuff, for example, inline styles, or data attributes, then that is lost. I could have made a copy of the DOM node itself and sorted them around, but for this simple component, I thought it was ok.&lt;/p&gt;&lt;p&gt;Whew! The final thing to do is to wrap my table:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table-sort&amp;gt;&amp;lt;table&amp;gt;	&amp;lt;thead&amp;gt;		&amp;lt;tr&amp;gt;			&amp;lt;th&amp;gt;Name&amp;lt;/th&amp;gt;			&amp;lt;th&amp;gt;Breed&amp;lt;/th&amp;gt;			&amp;lt;th&amp;gt;Gender&amp;lt;/th&amp;gt;			&amp;lt;th&amp;gt;Age&amp;lt;/th&amp;gt;	&amp;lt;/thead&amp;gt;	&amp;lt;tbody&amp;gt;		&amp;lt;tr&amp;gt;			&amp;lt;td&amp;gt;Luna&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Domestic Shorthair&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;Female&amp;lt;/td&amp;gt;			&amp;lt;td&amp;gt;11&amp;lt;/td&amp;gt;		&amp;lt;/tr&amp;gt;		&amp;lt;!-- more rows ---&amp;gt;	&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&amp;lt;/table-sort&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now let&apos;s test it out in the CodePen below:&lt;/p&gt;&lt;p class=&quot;codepen&quot; data-height=&quot;500&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;gOdvJxW&quot; data-editable=&quot;true&quot; data-user=&quot;cfjedimaster&quot; style=&quot;height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cfjedimaster/pen/gOdvJxW&quot;&gt;  PE Table for Sorting&lt;/a&gt; by Raymond Camden (&lt;a href=&quot;https://codepen.io/cfjedimaster&quot;&gt;@cfjedimaster&lt;/a&gt;)  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;&lt;p&gt;Hopefully, it worked fine for you. Of course, if it failed for some reason, you still saw a table right? But maybe you tried sorting on age and saw this:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/pet1.jpg&quot; alt=&quot;Table sorted incorrectly for age&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;Oops. The age column, which is a number, is sorted as a string. So how do we fix that? Remember that my goal was to have you not touch your original table at all. I initially thought I&apos;d maybe have you add a &lt;code&gt;data-&lt;/code&gt; attribute to the table, but that didn&apos;t feel right. Instead, I came up with another solution - an attribute to the web component:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;table-sort numeric=&amp;quot;4&amp;quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case, I&apos;m specifying that the fourth column is numeric. Here&apos;s how I supported this in code. In &lt;code&gt;connectedCallback&lt;/code&gt;, I look for the attribute:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;let numericColumns = [];if(this.hasAttribute(&apos;numeric&apos;)) {	numericColumns = this.getAttribute(&apos;numeric&apos;).split(&apos;,&apos;).map(x =&amp;gt; parseInt(x-1,10));}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since the value in the HTML is 1-based, I take your input (which can be comma-delimited), split it, convert each value to a real number and subtract one. The end result with my sample input is an array with one value, 3.&lt;/p&gt;&lt;p&gt;The final bit is to check for this when I create a copy of the data:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;let rows = tbody.querySelectorAll(&apos;tr&apos;);rows.forEach(r =&amp;gt; {	let datum = [];	let row = r.querySelectorAll(&apos;td&apos;);	row.forEach((r,i) =&amp;gt; {		if(numericColumns.indexOf(i) &amp;gt;= 0) datum[i] = parseInt(r.innerText,10);		else datum[i] = r.innerText;	});	this.data.push(datum);});&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And that&apos;s it. You can test that version below:&lt;/p&gt;&lt;p class=&quot;codepen&quot; data-height=&quot;500&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;OJovJee&quot; data-editable=&quot;true&quot; data-user=&quot;cfjedimaster&quot; style=&quot;height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cfjedimaster/pen/OJovJee&quot;&gt;  PE Table for Sorting (2)&lt;/a&gt; by Raymond Camden (&lt;a href=&quot;https://codepen.io/cfjedimaster&quot;&gt;@cfjedimaster&lt;/a&gt;)  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;
                        
                
				</content>

                
                <category term="javascript" />
                
                <category term="web components" />
                
                
                <category term="development" />
                
                <author>
                    <name>Raymond Camden</name>
                    <email>raymondcamden@gmail.com</email>
                </author>
            </entry>
        
            <entry>
                <id>https://www.raymondcamden.com/2023/03/09/reminder-about-web-components-and-attributes</id>
                <title>Reminder about Web Components and Attributes</title>
                <updated>2023-03-09T18:00:00+00:00</updated>
                <link href="https://www.raymondcamden.com/2023/03/09/reminder-about-web-components-and-attributes" rel="alternate" type="text/html" title="Reminder about Web Components and Attributes"/>
                <content type="html">
				
                        &lt;p&gt;After my post yesterday about &lt;a href=&quot;https://www.raymondcamden.com/2023/03/08/interesting-caveat-with-web-components-and-the-event-lifecycle&quot;&gt;web component lifecycle events&lt;/a&gt;, I had an interesting conversation with &lt;a href=&quot;https://blog.ltgt.net/&quot;&gt;Thomas Broyer&lt;/a&gt; on Mastodon. He brought up an issue with web components that I covered before on this blog, but as it was a very appropriate thing to discuss immediately after yesterday&apos;s post, I thought a bit of repetition would be ok. And heck, I&apos;ll take any chance to write more web component code as it gives me more practice.&lt;/p&gt;&lt;p&gt;So as a reminder, yesterday&apos;s &lt;a href=&quot;https://www.raymondcamden.com/2023/03/08/interesting-caveat-with-web-components-and-the-event-lifecycle&quot;&gt;post&lt;/a&gt; specifically dealt with what code is best used in a web component&apos;s constructor versus the &lt;code&gt;connectedCallback&lt;/code&gt; event. Specifically, it dealt with the use case of checking attributes and handling web component elements created via JavaScript. To be clear, I don&apos;t mean the &lt;em&gt;definition&lt;/em&gt; of the web component, but creating an instance of one, like so:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;let mc = document.createElement(&apos;my-component&apos;);document.body.appendChild(mc); &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;While I didn&apos;t bother setting a title in that example, I could have done so like this:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;let mc = document.createElement(&apos;my-component&apos;);mc.setAttribute(&apos;title&apos;,&apos;My title&apos;);document.body.appendChild(mc); &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And it works as expected. But here&apos;s an interesting question. What if later on I change the title? Imagine this code:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;setTimeout(() =&amp;gt; {	console.log(&apos;timer done, lets do this&apos;);	mc.setAttribute(&apos;title&apos;,&apos;New title&apos;);	console.log(`title for the mc is ${mc.getAttribute(&apos;title&apos;)}`);}, 3 * 1000);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When run, what will it do? Check out the CodePen below to see:&lt;/p&gt;&lt;p class=&quot;codepen&quot; data-height=&quot;500&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;yLxPzjy&quot; data-editable=&quot;true&quot; data-user=&quot;cfjedimaster&quot; style=&quot;height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cfjedimaster/pen/yLxPzjy&quot;&gt;  WC Tests (5)&lt;/a&gt; by Raymond Camden (&lt;a href=&quot;https://codepen.io/cfjedimaster&quot;&gt;@cfjedimaster&lt;/a&gt;)  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;&lt;p&gt;As you can see, it does &lt;em&gt;not&lt;/em&gt; work. Remember you can open your browser&apos;s console here if you want to see the messages. It will clearly say that the title attribute matches the update, but that&apos;s what you&apos;ll see reflected in the DOM.&lt;/p&gt;&lt;p&gt;The good (?) news is that this is completely expected and easily (for the most part) addressed. When defining a web component, you need to define which attributes you care about it (in terms of them changing) and write code to listen for those changes.&lt;/p&gt;&lt;p&gt;The first part is simple:&lt;/p&gt;&lt;p&gt;``jsstatic get observedAttributes() { return [&apos;title&apos;] };&lt;/p&gt;&lt;pre&gt;&lt;code&gt;The next part involves adding an event handler named `attributeChangedCallback`:```jsattributeChangedCallback(name, oldValue, newValue) {	console.log(`changing the value of ${name} from ${oldValue} to ${newValue}`);}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you try this, you&apos;ll see that it&apos;s fired multiple times. I had a &amp;quot;hard-coded&amp;quot; instance of the component in the DOM and it will message that the title is changing from null to the hard-coded value, reflecting the immediate change of the web component being added to the DOM. You will also see this run with the instance of the component created in JavaScript.&lt;/p&gt;&lt;p&gt;Now for the fun part. The event handler needs to actually update the display to reflect the new value. In the first iteration of my example component, I skipped the Shadow DOM and just wrote it out directly to the main DOM. Since I now need to (possibly) update the DOM multiple times, I made two more changes. I switched to the Shadow DOM and built a new method, &lt;code&gt;updateDisplay&lt;/code&gt;, that handles updating the display. Here&apos;s the entire class:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;class MyComponent extends HTMLElement {	constructor() {		super();		console.log(&apos;constructor called&apos;);		const shadow = this.attachShadow({ mode: &amp;quot;open&amp;quot; });		const div = document.createElement(&apos;div&apos;);		const h2 = document.createElement(&apos;h2&apos;);		div.appendChild(h2);		shadow.appendChild(div);	}		connectedCallback() {		console.log(&apos;connected callback called&apos;);		if(!this.getAttribute(&apos;title&apos;)) this.setAttribute(&apos;title&apos;, &apos;No title&apos;);		this.updateDisplay();	}		updateDisplay() {		this.shadowRoot.querySelector(&apos;h2&apos;).innerText = `My Component: ${this.getAttribute(&apos;title&apos;)}`;	}		static get observedAttributes() { return [&apos;title&apos;] };	attributeChangedCallback(name, oldValue, newValue) {		console.log(`changing the value of ${name} from ${oldValue} to ${newValue}`);		this.updateDisplay();	}}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice that &lt;code&gt;updateDisplay&lt;/code&gt; just uses &lt;code&gt;querySelector&lt;/code&gt; to find its &lt;code&gt;h2&lt;/code&gt; node and update the text. Now our code that updates the title after a few seconds will work correctly:&lt;/p&gt;&lt;p class=&quot;codepen&quot; data-height=&quot;500&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;qBMVPyX&quot; data-editable=&quot;true&quot; data-user=&quot;cfjedimaster&quot; style=&quot;height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cfjedimaster/pen/qBMVPyX&quot;&gt;  WC Tests (5)&lt;/a&gt; by Raymond Camden (&lt;a href=&quot;https://codepen.io/cfjedimaster&quot;&gt;@cfjedimaster&lt;/a&gt;)  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;&lt;p&gt;If you don&apos;t see the switch, just click the &amp;quot;Rerun&amp;quot; button on the bottom right. Anyway, as I said, I&apos;ve discussed this before, but it definitely tripped me up the first time I ran into it so hopefully this helps others!&lt;/p&gt;&lt;p&gt;Photo by &lt;a href=&quot;https://unsplash.com/@chrislawton?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Chris Lawton&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/photos/5IHz5WhosQE?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText&quot;&gt;Unsplash&lt;/a&gt;&lt;/p&gt;
                        
                
				</content>

                
                <category term="web components" />
                
                
                <category term="javascript" />
                
                <author>
                    <name>Raymond Camden</name>
                    <email>raymondcamden@gmail.com</email>
                </author>
            </entry>
        
            <entry>
                <id>https://www.raymondcamden.com/2023/03/08/interesting-caveat-with-web-components-and-the-event-lifecycle</id>
                <title>Interesting Caveat with Web Components and the Event Lifecycle</title>
                <updated>2023-03-08T18:00:00+00:00</updated>
                <link href="https://www.raymondcamden.com/2023/03/08/interesting-caveat-with-web-components-and-the-event-lifecycle" rel="alternate" type="text/html" title="Interesting Caveat with Web Components and the Event Lifecycle"/>
                <content type="html">
				
                        &lt;p&gt;I&apos;ve been exploring &lt;a href=&quot;https://www.raymondcamden.com/tags/web+components&quot;&gt;web components&lt;/a&gt; the last few months and as part of that exploration, I&apos;ve been reading &lt;a href=&quot;https://www.manning.com/books/web-components-in-action&quot;&gt;&amp;quot;Web Components in Action&amp;quot;&lt;/a&gt; by fellow Adobian Ben Farrell. I&apos;m still at the beginning of the book but so far it&apos;s been great. It &lt;em&gt;is&lt;/em&gt; a few years old now, but for the most part, the only thing I&apos;ve seen out of date is that at the time of publication, Microsoft Edge didn&apos;t have complete support for web components yet. That&apos;s been corrected (good thing, I switched to Edge a while back) so it&apos;s not really a concern.&lt;/p&gt;&lt;p&gt;However yesterday I read something that didn&apos;t quite jive with my understanding. The fourth chapter, &amp;quot;The component lifecycle&amp;quot;, deals with the various hooks you get into web components when they are used on a page. In this chapter, he spends a good amount of time comparing the constructor of a web component to the &lt;code&gt;connectedCallback&lt;/code&gt; event. The constructor is called when the component is created, but &lt;code&gt;connectedCallback&lt;/code&gt; is not fired until the component is added to the browser&apos;s DOM. That last bit is important. If you add an instance of a web component to a DOM element, let&apos;s say a &lt;code&gt;div&lt;/code&gt; you created in JavaScript, but that div itself is not in the browser&apos;s DOM, the event won&apos;t fire.&lt;/p&gt;&lt;p&gt;Before going further, let&apos;s look at a quick example. Assume this JavaScript for a trivial component:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;class MyComponent extends HTMLElement {	constructor() {		super();		console.log(&apos;constructor called&apos;);		this.innerHTML = &apos;&amp;lt;h2&amp;gt;My Component&amp;lt;/h2&amp;gt;&apos;;	}		connectedCallback() {		console.log(&apos;connected callback called&apos;);	}}if(!customElements.get(&apos;my-component&apos;)) customElements.define(&apos;my-component&apos;, MyComponent);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we use &lt;code&gt;&amp;lt;my-component&amp;gt;&lt;/code&gt; in the DOM, we will see both console messages for each instance of the tag. Here&apos;s a CodePen that demonstrates this. Note that you will need to &amp;quot;Edit on CodePen&amp;quot; to actually see console messages, or open your console right here on my site.&lt;/p&gt;&lt;p class=&quot;codepen&quot; data-height=&quot;400&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;WNgpQPB&quot; data-user=&quot;cfjedimaster&quot; style=&quot;height: 400px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cfjedimaster/pen/WNgpQPB&quot;&gt;  WC Tests&lt;/a&gt; by Raymond Camden (&lt;a href=&quot;https://codepen.io/cfjedimaster&quot;&gt;@cfjedimaster&lt;/a&gt;)  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;&lt;p&gt;All of this made sense, and really touched on something I&apos;ve been noodling over - what should I put in the constructor versus &lt;code&gt;connectedCallback&lt;/code&gt;. He made one point that didn&apos;t seem right to me - that if you check the value of an attribute in the constructor, it will be null. I&apos;ve been doing this in my previous examples, and heck, even MDN shows it in one of their &lt;a href=&quot;https://github.com/mdn/web-components-examples/blob/main/popup-info-box-web-component/main.js&quot;&gt;examples&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;class PopUpInfo extends HTMLElement {  constructor() {    // Always call super first in constructor    super();    // Create a shadow root    const shadow = this.attachShadow({mode: &apos;open&apos;});    // Create spans    const wrapper = document.createElement(&apos;span&apos;);    wrapper.setAttribute(&apos;class&apos;, &apos;wrapper&apos;);	// stuff deleted...    // Take attribute content and put it inside the info span    const text = this.getAttribute(&apos;data-text&apos;);    info.textContent = text;	//lot more stuff...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here&apos;s an example where it clearly works just fine:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;class MyComponent extends HTMLElement {	constructor() {		super();		console.log(&apos;constructor called&apos;);				if(!this.getAttribute(&apos;title&apos;)) this.setAttribute(&apos;title&apos;, &apos;No title&apos;);	}		connectedCallback() {		console.log(&apos;connected callback called&apos;);		this.innerHTML = `&amp;lt;h2&amp;gt;My Component: ${this.getAttribute(&apos;title&apos;)}&amp;lt;/h2&amp;gt;`;	}}if(!customElements.get(&apos;my-component&apos;)) customElements.define(&apos;my-component&apos;, MyComponent);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And if called like so:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;my-component title=&amp;quot;ray&amp;quot;&amp;gt;&amp;lt;/my-component&amp;gt;&amp;lt;my-component&amp;gt;&amp;lt;/my-component&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I get:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;My Component: rayMy Component: No title&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As I said, this matched my expectations. Here&apos;s a complete CodePen for this:&lt;/p&gt;&lt;p class=&quot;codepen&quot; data-height=&quot;400&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;MWqEPOa&quot; data-user=&quot;cfjedimaster&quot; style=&quot;height: 400px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cfjedimaster/pen/MWqEPOa&quot;&gt;  WC Tests (2)&lt;/a&gt; by Raymond Camden (&lt;a href=&quot;https://codepen.io/cfjedimaster&quot;&gt;@cfjedimaster&lt;/a&gt;)  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;&lt;p&gt;So, Ben and I talked about this over Slack, and initially, we just figured it was a change since he released his book, but then he made a really important point. What happens if you create an instance of your component via JavaScript? Consider:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;class MyComponent extends HTMLElement {	constructor() {		super();		console.log(&apos;constructor called&apos;);				if(!this.getAttribute(&apos;title&apos;)) this.setAttribute(&apos;title&apos;, &apos;No title&apos;);	}		connectedCallback() {		console.log(&apos;connected callback called2&apos;);		this.innerHTML = `&amp;lt;h2&amp;gt;My Component: ${this.getAttribute(&apos;title&apos;)}&amp;lt;/h2&amp;gt;`;	}}if(!customElements.get(&apos;my-component&apos;)) customElements.define(&apos;my-component&apos;, MyComponent);let mc = document.createElement(&apos;my-component&apos;);document.body.appendChild(mc); &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case, I&apos;ve made a new &lt;code&gt;my-component&lt;/code&gt; and added it to my DOM. I would have assumed this just worked, but instead, you get an error:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;Uncaught DOMException: Failed to construct &apos;CustomElement&apos;: The result must not have attributes&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you want to see this yourself, open up &lt;a href=&quot;https://codepen.io/cfjedimaster/pen/KKxXGQw&quot;&gt;this CodePen&lt;/a&gt;, and open your browser&apos;s console, not the CodePen one. The error doesn&apos;t get floated up right to the &apos;virtual&apos; console CodePen uses.&lt;/p&gt;&lt;p&gt;Now it makes sense, and it&apos;s an easy enough correction to move that logic to &lt;code&gt;connectedCallback&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;class MyComponent extends HTMLElement {	constructor() {		super();		console.log(&apos;constructor called&apos;);			}		connectedCallback() {		console.log(&apos;connected callback called&apos;);		if(!this.getAttribute(&apos;title&apos;)) this.setAttribute(&apos;title&apos;, &apos;No title&apos;);		this.innerHTML = `&amp;lt;h2&amp;gt;My Component: ${this.getAttribute(&apos;title&apos;)}&amp;lt;/h2&amp;gt;`;	}}if(!customElements.get(&apos;my-component&apos;)) customElements.define(&apos;my-component&apos;, MyComponent);&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And in doing so, I can then create instances in JavaScript, and even set my title:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;let mc = document.createElement(&apos;my-component&apos;);document.body.appendChild(mc); let mc2 = document.createElement(&apos;my-component&apos;);mc2.setAttribute(&apos;title&apos;,&apos;Custom title&apos;);document.body.appendChild(mc2); &lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the CodePen below, you can see I used both a &amp;quot;regular&amp;quot; instance of the component in HTML as well as the two defined here and all three act correctly:&lt;/p&gt;&lt;p class=&quot;codepen&quot; data-height=&quot;400&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;OJoxBoG&quot; data-user=&quot;cfjedimaster&quot; style=&quot;height: 400px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cfjedimaster/pen/OJoxBoG&quot;&gt;  WC Tests (3)&lt;/a&gt; by Raymond Camden (&lt;a href=&quot;https://codepen.io/cfjedimaster&quot;&gt;@cfjedimaster&lt;/a&gt;)  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;&lt;p&gt;I hope this makes sense, and as always, reach out if it doesn&apos;t. Going forward, I&apos;ll be doing more of my attribute validation and setting in &lt;code&gt;connectedCallback&lt;/code&gt;.&lt;/p&gt;
                        
                
				</content>

                
                <category term="web components" />
                
                
                <category term="javascript" />
                
                <author>
                    <name>Raymond Camden</name>
                    <email>raymondcamden@gmail.com</email>
                </author>
            </entry>
        
            <entry>
                <id>https://www.raymondcamden.com/2023/03/06/adding-a-chart-to-an-aplinejs-application</id>
                <title>Adding a Chart to an Apline.js Application</title>
                <updated>2023-03-06T18:00:00+00:00</updated>
                <link href="https://www.raymondcamden.com/2023/03/06/adding-a-chart-to-an-aplinejs-application" rel="alternate" type="text/html" title="Adding a Chart to an Apline.js Application"/>
                <content type="html">
				
                        &lt;p&gt;For a while now my blog queue has had an item in there suggesting I take a look at adding a basic chart to an &lt;a href=&quot;https://alpinejs.dev/&quot;&gt;Alpine.js&lt;/a&gt; application. I finally got a chance to play around with this over the weekend and I thought I&apos;d share the result. For this post, I&apos;ve used &lt;a href=&quot;https://www.chartjs.org/&quot;&gt;Chart.js&lt;/a&gt;, which is a free, open-source charting library that&apos;s relatively easy enough to use. Certainly, others could be used as well and as always, if you&apos;ve got an example, I&apos;d love to see it. With that out of the way, let&apos;s take a look at the application.&lt;/p&gt;&lt;h2&gt;Before the Chart&lt;/h2&gt;&lt;p&gt;I&apos;ll start by sharing what I built &lt;em&gt;before&lt;/em&gt; I added a chart to the display. This application consists of a list of cities. For each city, we use the &lt;a href=&quot;https://pirateweather.net/&quot;&gt;Pirate Weather API&lt;/a&gt; to get an hourly forecast and from that, I display the temperature over the next twelve hours. Here&apos;s how that looks:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/alp1.jpg&quot; alt=&quot;Table of four cities and four weather forecasts.&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;I probably should have included a timestamp but for now, this gets the point across. Let&apos;s take a look at the code. I begin by defining my cities. This probably would be dynamic, loaded from a database or API, etc.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;cities: [	{ label: &amp;quot;Lafayette, LA&amp;quot;, latitude: 30.22, longitude: -92.02 },	{ label: &amp;quot;Bellingham, WA&amp;quot;, latitude: 48.768, longitude: -122.485 },	{ label: &amp;quot;Chicago, IL&amp;quot;, latitude: 41.881, longitude: -87.623 },	{ label: &amp;quot;Washington, DC&amp;quot;, latitude: 38.895, longitude: -77.036 }			]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When the application starts, I want to fire off requests to get forecasts. I did this in two methods. The first top-level method fires off the requests:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;async getForecasts(locs) {	console.log(&apos;get forecasts for my locations&apos;);	let requests = [];	locs.forEach(l =&amp;gt; {		requests.push(this.getHourlyForecast(l.latitude, l.longitude));	});	let data = await Promise.all(requests);	data.forEach((d, i) =&amp;gt; {		this.cities[i].forecast = d;	});	this.numForecasts = this.cities[0].forecast.length;},&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here I make use of promises to fire all four requests at once and then wait for them to finish. Yes, I should have error handling here. The result of &lt;code&gt;Promise.all&lt;/code&gt; will be one array item per promise and will be in the same order I created them, so I can assign the results to my cities by just looping over them.&lt;/p&gt;&lt;p&gt;The actual API call is done here:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;async getHourlyForecast(lat, lng) {	let req = await fetch(`https://api.pirateweather.net/forecast/${APIKEY}/${lat},${lng}?exclude=alerts,daily,currently,minutely&amp;amp;units=us`);	let data = await req.json();	return data.hourly.data.slice(0,12);}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I pass in my API key and the location to the API. The result contains a lot of information, but all I want is the hourly records and only the first twelve. I could probably simplify the result even more but this is good enough.&lt;/p&gt;&lt;p&gt;With the forecast information ready, the table can now be displayed. Here&apos;s the UI:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div x-data=&amp;quot;app&amp;quot;&amp;gt;	&amp;lt;table&amp;gt;		&amp;lt;thead&amp;gt;			&amp;lt;tr&amp;gt;				&amp;lt;template x-for=&amp;quot;city in cities&amp;quot;&amp;gt;					&amp;lt;th x-text=&amp;quot;city.label&amp;quot;&amp;gt;&amp;lt;/th&amp;gt;				&amp;lt;/template&amp;gt;			&amp;lt;/tr&amp;gt;		&amp;lt;/thead&amp;gt;		&amp;lt;tbody&amp;gt;			&amp;lt;template x-for=&amp;quot;i in numForecasts&amp;quot;&amp;gt;				&amp;lt;tr&amp;gt;					&amp;lt;template x-for=&amp;quot;city in cities&amp;quot;&amp;gt;						&amp;lt;td x-text=&amp;quot;city.forecast[i-1].temperature&amp;quot;&amp;gt;&amp;lt;/td&amp;gt;					&amp;lt;/template&amp;gt;								&amp;lt;/tr&amp;gt;			&amp;lt;/template&amp;gt;		&amp;lt;/tbody&amp;gt;	&amp;lt;/table&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Basically one loop over the cities to build the table header, and then a loop over the number of forecasts with an inner loop over each city to build each row.&lt;/p&gt;&lt;p&gt;Here&apos;s a CodePen demonstrating the complete application.&lt;/p&gt;&lt;p class=&quot;codepen&quot; data-height=&quot;500&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;xxadMNB&quot; data-editable=&quot;true&quot; data-user=&quot;cfjedimaster&quot; style=&quot;height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cfjedimaster/pen/xxadMNB&quot;&gt;  Alpine + ChartJS (Initial)&lt;/a&gt; by Raymond Camden (&lt;a href=&quot;https://codepen.io/cfjedimaster&quot;&gt;@cfjedimaster&lt;/a&gt;)  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;&lt;h2&gt;Adding the Chart&lt;/h2&gt;&lt;p&gt;For my chart, I thought it would be nice to visualize both the highest and lowest temperatures for each of the cities. That would give us an idea of the range over our time period as well as the relative difference in warmth between the cities. (Spoiler - Louisiana is hot. Always hot.) Here&apos;s the chart I came up with:&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://static.raymondcamden.com/images/2023/03/alp2.jpg&quot; alt=&quot;Chart showing temperature ranges&quot; class=&quot;lazyload imgborder imgcenter&quot;&gt;&lt;/p&gt;&lt;p&gt;Note that this was me doing the bare minimum in terms of &amp;quot;design&amp;quot;. Chart.js seems really powerful and I could absolutely do more to make this prettier, but honestly, it works, and I was pleased with how quickly I got this working. Here&apos;s what I had to do.&lt;/p&gt;&lt;p&gt;First, I added the library, https://cdn.jsdelivr.net/npm/chart.js. And hey, thank you Chart.js for not forcing me to &lt;code&gt;npm&lt;/code&gt; anything. I appreciate it.&lt;/p&gt;&lt;p&gt;Next, I added a &lt;code&gt;canvas&lt;/code&gt; to my HTML. Because I&apos;m lazy, I used the same ID as their docs, but this can be changed of course.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;canvas id=&amp;quot;myChart&amp;quot;&amp;gt;&amp;lt;/canvas&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, I added a new method to my code, &lt;code&gt;renderChart&lt;/code&gt;, to handle the process. Here&apos;s that code.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;renderChart() {	const ctx = document.getElementById(&apos;myChart&apos;);	let names = this.cities.map(c =&amp;gt; c.label);		let highestTemps = this.cities.map(c =&amp;gt; {		return c.forecast.reduce((highest,f) =&amp;gt; {			if(f.temperature &amp;gt; highest) return f.temperature;			return highest;		},0);	});	let lowestTemps = this.cities.map(c =&amp;gt; {		return c.forecast.reduce((lowest,f) =&amp;gt; {			if(f.temperature &amp;lt; lowest) return f.temperature;			return lowest;		},999);	});		new Chart(ctx, {		type: &apos;line&apos;,		data: {			labels: names,			datasets: [				{					label: &apos;Highest Temp&apos;,					data: highestTemps,					borderWidth: 1				},				{					label: &apos;Lowest Temp&apos;,					data: lowestTemps,					borderWidth: 1				}									]		},		options: {			scales: {				y: {					min: -20,					max: 120				}			}		}	});	}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&apos;s examine this. The very first line simply gets a reference to the &lt;code&gt;canvas&lt;/code&gt; tag where Chart.js will do its work. The next few lines of code are all me &amp;quot;prepping&amp;quot; my data for the chart. First I get a list of cities. Then I get both the highest and lowest temps for each city with the crafty use of both &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;reduce&lt;/code&gt;. I am a JavaScript master and I will absolutely pass the next arbitrary coding challenge I get for a job interview. Honest.&lt;/p&gt;&lt;p&gt;The net result of the above three blocks of code is three arrays. Each of these can then be passed to my chart declaration. You&apos;ll see &lt;code&gt;names&lt;/code&gt; passed in for the labels and then my two datasets. This is all pretty much boilerplate demo code from Chart.js, the only thing I did custom was to specify a scale for my Y-axis. My range there isn&apos;t perfect, I know some places were below negative twenty recently, but it works for now.&lt;/p&gt;&lt;p&gt;You can demo this version here:&lt;/p&gt;&lt;p class=&quot;codepen&quot; data-height=&quot;500&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;eYLEvEO&quot; data-editable=&quot;true&quot; data-user=&quot;cfjedimaster&quot; style=&quot;height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;  &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cfjedimaster/pen/eYLEvEO&quot;&gt;  Alpine + ChartJS (Chart)&lt;/a&gt; by Raymond Camden (&lt;a href=&quot;https://codepen.io/cfjedimaster&quot;&gt;@cfjedimaster&lt;/a&gt;)  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;&lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&gt;&lt;/script&gt;&lt;h2&gt;Some Quick Notes&lt;/h2&gt;&lt;p&gt;Ok, all of the following does not actually apply to the main point of this post, but I had some thoughts about what I built and wanted to share them.&lt;/p&gt;&lt;p&gt;First, I&apos;m still relatively new to Alpine and still trying to figure out the &amp;quot;best&amp;quot; (for me) way to work with it. I like that Alpine is flexible in its definition and lets you specify methods and data all at once. That being said - I&apos;m not sure I&apos;m happy with how I organized my code. I think my feeling is that I should use the following rules:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Put the init() method on top.&lt;/li&gt;&lt;li&gt;Put any and all simple variable declarations next.&lt;/li&gt;&lt;li&gt;Put methods after.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Second, you may or may not notice I added a simple cache to the forecast function in the second CodePen. I did this to ensure I didn&apos;t kill my access to the API as CodePen tends to rerun stuff quite a bit. (I need to disable that I think. I just did. Will remember for next time. Honest.)&lt;/p&gt;
                        
                
				</content>

                
                <category term="alpinejs" />
                
                <category term="javascript" />
                
                
                <category term="development" />
                
                <author>
                    <name>Raymond Camden</name>
                    <email>raymondcamden@gmail.com</email>
                </author>
            </entry>
        
            <entry>
                <id>https://www.raymondcamden.com/2023/03/04/links-for-you</id>
                <title>Links For You</title>
                <updated>2023-03-04T18:00:00+00:00</updated>
                <link href="https://www.raymondcamden.com/2023/03/04/links-for-you" rel="alternate" type="text/html" title="Links For You"/>
                <content type="html">
				
                        &lt;p&gt;Good morning readers! I&apos;m writing this in a hotel room in Tuscaloosa where my wife and I are visiting our son. He was presented with a significant award a few nights ago (the &lt;a href=&quot;https://sullivanfdn.org/&quot;&gt;Algernon Sydney Sullivan&lt;/a&gt; award) and we stayed up a few extra days. We&apos;re about to head back to Louisiana so I thought I&apos;d share a few quick links with folks. Have a great Sunday.&lt;/p&gt;&lt;h2&gt;Taking Eleventy into the Spiderverse with eleventy-fetch&lt;/h2&gt;&lt;p&gt;Here&apos;s a great &lt;a href=&quot;https://box464.com/posts/eleventy-fetch-marvel/&quot;&gt;post&lt;/a&gt; by Jeff Sikes where he describes how he made use of the &lt;a href=&quot;https://developer.marvel.com/&quot;&gt;Marvel API&lt;/a&gt; in an Eleventy site. That&apos;s mixing two of my favorite things, Marvel and Eleventy! I really wish Marvel would continue working on their API. The last update was nearly a decade ago, but on the other hand, I&apos;m happy they just didn&apos;t shut it down.&lt;/p&gt;&lt;h2&gt;Eleventy Collection Schemas&lt;/h2&gt;&lt;p&gt;Yet another awesome Eleventy tip from &lt;a href=&quot;https://thinkdobecreate.com/&quot;&gt;Stephanie Eckles&lt;/a&gt;, this &lt;a href=&quot;https://11ty.rocks/posts/eleventy-collection-schemas/&quot;&gt;post&lt;/a&gt; documents an Eleventy plugin for enforcing your frontmatter setup in Eleventy collections. This is &lt;em&gt;really&lt;/em&gt; a good idea since Eleventy gives you complete freedom over your frontmatter, being able to enforce certain rules in your site will help prevent issues in your site. Heck, I made a mistake with my frontmatter a few weeks ago and this would have really helped!&lt;/p&gt;&lt;h2&gt;Frontend Development Projects with Vue 3 - Second Edition&lt;/h2&gt;&lt;p&gt;About a year or so ago I was involved in the writing of a Vue 2 book for Packt, and now that book has been updated for Vue 3 by myself and Maya Shavin. I have to say - I&apos;ve been kinda... not again... but not terribly excited about Vue lately. That being said, having used Vue 3 (obviously) for working on this book, I&apos;m feeling somewhat better about it. I still don&apos;t think Vue is going to be my framework of choice going forward, I really prefer Alpine, but for any &amp;quot;application&amp;quot;, I&apos;d definitely build it in Vue 3.&lt;/p&gt;&lt;p&gt;You can get the book from Packt here, &lt;a href=&quot;https://www.packtpub.com/product/frontend-development-projects-with-vuejs-3-second-edition/9781803234991&quot;&gt;https://www.packtpub.com/product/frontend-development-projects-with-vuejs-3-second-edition/9781803234991&lt;/a&gt;, or if you buy from Amazon &lt;a href=&quot;https://amzn.to/3Jch1Uz&quot;&gt;here&lt;/a&gt; I&apos;ll get an Amazon Associates kickback. Either way, check it out and let me know what you think.&lt;/p&gt;
                        
                
				</content>

                
                <category term="links4you" />
                
                
                <category term="misc" />
                
                <author>
                    <name>Raymond Camden</name>
                    <email>raymondcamden@gmail.com</email>
                </author>
            </entry>
        
            <entry>
                <id>https://www.raymondcamden.com/2023/03/01/supporting-pdf-embeds-in-an-eleventy-webc-component</id>
                <title>Supporting PDF Embeds in an Eleventy WebC Component</title>
                <updated>2023-03-01T18:00:00+00:00</updated>
                <link href="https://www.raymondcamden.com/2023/03/01/supporting-pdf-embeds-in-an-eleventy-webc-component" rel="alternate" type="text/html" title="Supporting PDF Embeds in an Eleventy WebC Component"/>
                <content type="html">
				
                        &lt;p&gt;Way back in the old days, in August of 2021, I wrote up an example of adding support for Adobe&apos;s &lt;a href=&quot;https://developer.adobe.com/document-services/apis/pdf-embed/&quot;&gt;PDF Embed API&lt;/a&gt; as an Eleventy plugin: &lt;a href=&quot;https://www.raymondcamden.com/2021/08/02/an-adobe-pdf-embed-plugin-for-eleventy&quot;&gt;&amp;quot;An Adobe PDF Embed Plugin for Eleventy&amp;quot;&lt;/a&gt;. When I find time, I need to update that to the newest URL for the library, but more recently I was curious if I could recreate support using the &lt;a href=&quot;https://www.11ty.dev/docs/languages/webc/&quot;&gt;WebC&lt;/a&gt; template language. While it was a bit difficult at times (and a big thank you goes to &lt;a href=&quot;https://www.zachleat.com/&quot;&gt;Zach&lt;/a&gt; for patiently helping me), I think it&apos;s at a point now where it can be shared. I will warn folks that I&apos;m still struggling a bit with the best way to work with WebC, and at least one feature I&apos;m showing isn&apos;t documented yet (but I&apos;ve confirmed it will 100% ship), but hopefully this example will be useful for folks.&lt;/p&gt;&lt;p&gt;Before we start, know that if you want to try this yourself, you will need a &lt;a href=&quot;https://developer.adobe.com/document-services/apis/interstitial/?api=pdf-embed-api&quot;&gt;free credential&lt;/a&gt;. Credentials are host-based, which means a credential for raymondcamden.com will &lt;em&gt;not&lt;/em&gt; work for localhost. You can definitely create a few, and if you &lt;em&gt;really&lt;/em&gt; want to use one key for development and production, consider setting the host for your domain, minus the www, and use dev dot your domain locally. If you&apos;ve never used your local &lt;code&gt;hosts&lt;/code&gt; file for something like this, reach out and I&apos;ll help explain it. (Or heck, that may be my next post.)&lt;/p&gt;&lt;p&gt;Alright, so I began actually by writing a template that called my component. I kinda figured I&apos;d write the example code in what felt like the most logical manner, and then I&apos;d actually build the WebC component to match it. Here&apos;s how I did it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;pdfembed width=&amp;quot;700&amp;quot; height=&amp;quot;600&amp;quot;  clientid=&amp;quot;912a3ba447664592bfbffb224b74a371&amp;quot;   pdf=&amp;quot;https://static.raymondcamden.com/images/2023/03/cat.pdf&amp;quot;&amp;gt;&amp;lt;/pdfembed&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&apos;ve got two attributes to define the size of my PDF, my client credential (it&apos;s host-based, so safe to share here, go ahead, steal it, I won&apos;t tell), and then the URL for my PDF. Note that when you point to a PDF, it needs to be in a CORS-accessible location. You can also use file promises in the API, but I&apos;m keeping it simple here.&lt;/p&gt;&lt;p&gt;Now let&apos;s look at the actual component:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;template webc:is=&amp;quot;noscript&amp;quot;&amp;gt;&amp;lt;a :href=&amp;quot;pdf&amp;quot;&amp;gt;&amp;lt;span @text=&amp;quot;pdf&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/template&amp;gt;&amp;lt;div :id=&amp;quot;uid&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;script webc:keep src=&amp;quot;https://documentservices.adobe.com/view-sdk/viewer.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;template webc:type=&amp;quot;11ty&amp;quot; 11ty:type=&amp;quot;liquid&amp;quot;&amp;gt;{% assign pdfname = pdf | split:&amp;quot;/&amp;quot; | last %}&amp;lt;script webc:keep&amp;gt;let clientID = &amp;quot;{{ clientid }}&amp;quot;;document.addEventListener(&amp;quot;adobe_dc_view_sdk.ready&amp;quot;, () =&amp;gt; {  let adobeDCView = new AdobeDC.View({clientId: clientID, divId: &amp;quot;{{uid}}&amp;quot;});  adobeDCView.previewFile(   {      content:   {location: {url: &amp;quot;{{pdf}}&amp;quot;}},      metaData: {fileName: &amp;quot;{{pdfname}}&amp;quot;},      focusOnRendering: false   });});&amp;lt;/script&amp;gt;&amp;lt;/template&amp;gt;&amp;lt;script webc:is=&amp;quot;style&amp;quot; webc:type=&amp;quot;js&amp;quot; webc:keep&amp;gt;`div#${uid} {  width: ${width}px;  height: ${height}px;}`&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&apos;s discuss. I begin by adding a &lt;code&gt;noscript&lt;/code&gt; tag that links to the PDF. That way a user without JavaScript enabled will still be able to get to the document. In order for this to work in WebC, I couldn&apos;t use the &lt;code&gt;noscript&lt;/code&gt; tag directly but had to use a &lt;code&gt;template&lt;/code&gt; instead with the &lt;code&gt;webc:is&lt;/code&gt; directive. Zach explained why... but I honestly don&apos;t quite get it. Not yet anyway.&lt;/p&gt;&lt;p&gt;Next, note the div:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div :id=&amp;quot;uid&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is the part not yet documented. Every &lt;em&gt;instance&lt;/em&gt; of a WebC component will automatically get provided a unique ID. This is especially useful for me as I need a way to associate a unique ID with the PDF library. This ID is safe for DOM IDs so it made sense to use it.&lt;/p&gt;&lt;p&gt;Now for the slightly more complex part. I switch to Liquid so I can be a bit dynamic. Our library has a weird thing where it requires a URL (or file promise) as well as a PDF file name. I&apos;ve already requested we get rid of this requirement, but for now, I get it by simply popping off the last part of the URL.&lt;/p&gt;&lt;p&gt;The rest of the code in that Liquid block just outputs boilerplate PDF Embed code for the library and uses variables for the div ID, the PDF URL, and the filename.&lt;/p&gt;&lt;p&gt;The final part is probably the most confusing. I needed to apply CSS to style the size of the div where the PDF is rendered. To do so, I used a JavaScript string literal. I&apos;m using a &lt;code&gt;script&lt;/code&gt; tag as, technically, it&apos;s JavaScript, but it comes out as CSS, so I map it with &lt;code&gt;webc:is&lt;/code&gt; as I did on top. Again, thanks go to Zach for this tip. Finally, I need to use &lt;code&gt;webc:keep&lt;/code&gt; because the default behavior in WebC is to toll up and bundle all JavaScript and CSS. In this case, I need the block to stay and be used in every particular instance I call the component.&lt;/p&gt;&lt;p&gt;Whew. Hopefully, this made some sense. If you want to test this yourself, I made a Glitch, because &lt;a href=&quot;https://glitch.me&quot;&gt;Glitch&lt;/a&gt; is freaking cool. You can see it here: &lt;a href=&quot;https://glitch.com/edit/#!/impossible-early-system?&quot;&gt;https://glitch.com/edit/#!/impossible-early-system?&lt;/a&gt;&lt;/p&gt;
                        
                
				</content>

                
                <category term="eleventy" />
                
                <category term="adobe" />
                
                <category term="pdf services" />
                
                
                <category term="jamstack" />
                
                <author>
                    <name>Raymond Camden</name>
                    <email>raymondcamden@gmail.com</email>
                </author>
            </entry>
        
            <entry>
                <id>https://www.raymondcamden.com/2023/02/25/update-to-my-eleventy-blog-guide</id>
                <title>Update to My Eleventy Blog Guide</title>
                <updated>2023-02-25T18:00:00+00:00</updated>
                <link href="https://www.raymondcamden.com/2023/02/25/update-to-my-eleventy-blog-guide" rel="alternate" type="text/html" title="Update to My Eleventy Blog Guide"/>
                <content type="html">
				
                        &lt;p&gt;Last January, I &lt;a href=&quot;https://www.raymondcamden.com/2022/01/19/a-guide-to-building-a-blog-in-eleventy&quot;&gt;announced&lt;/a&gt; the release of a guide I had written for building a simple blog in &lt;a href=&quot;https://11ty.dev&quot;&gt;Eleventy&lt;/a&gt;. Now that Eleventy has hit 2.0, I took some time this morning to look at the guide and see what could be updated. The first thing I noticed was that I had a heck of a lot of typos. I fixed those. I then went through the two main versions of the blog (before and after UI was added) and updated the dependencies to the 2.0 release of Eleventy.&lt;/p&gt;&lt;p&gt;That being said, I didn&apos;t do anything else. This is not to say that the &lt;a href=&quot;https://www.11ty.dev/blog/eleventy-v2/&quot;&gt;2.0 release&lt;/a&gt; wasn&apos;t lacking in new features, but as my guide is meant to be as simple as possible, I wasn&apos;t on the lookout to add any new features if it didn&apos;t make sense for the blog. Of course, one of the big features of 2.0 is a big reduction in dependencies, so right away people will benefit from it.&lt;/p&gt;&lt;p&gt;Anyway, you can find the guide here: &lt;a href=&quot;https://cfjedimaster.github.io/eleventy-blog-guide/guide.html&quot;&gt;https://cfjedimaster.github.io/eleventy-blog-guide/guide.html&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;And you can find the repository here: &lt;a href=&quot;https://github.com/cfjedimaster/eleventy-blog-guide&quot;&gt;https://github.com/cfjedimaster/eleventy-blog-guide&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Feedback is always welcome!&lt;/p&gt;
                        
                
				</content>

                
                <category term="eleventy" />
                
                
                <category term="jamstack" />
                
                <author>
                    <name>Raymond Camden</name>
                    <email>raymondcamden@gmail.com</email>
                </author>
            </entry>
        
</feed>